Swift's type inference ability has been a core part of the language from the beginning, and it greatly reduces the work of manually specifying types when declaring variables and properties with default values. For example, expressionsvar number = 7
No type comments are required, because the compiler can infer values7
It's oneInt
, oursnumber
Variables should be typed accordingly.
Swift 5.6, released together as part of Xcode 13.3, continues to extend these types of reasoning capabilities by introducing the concept of "type placeholders", which is very useful when dealing with collections and other common types.
For example, suppose we want to create aCombine
There is a default integer value insideCurrentValueSubject
Examples of . The initial idea on how to do this might be to simply pass our default value to the initializer of that body and then store the result locally in onelet
Declared property (like creating a normal oneInt
the same value). However, doing this will give us the following compiler error:
// Error: "Generic parameter 'Failure' could not be inferred" // Error: "Unable to infer generic `Failure` parameter"let counterSubject = CurrentValueSubject(0)
This is becauseCurrentValueSubject
It is a generic type, and it is not only necessary to instantiate itOutput
Type, also requiredFailure
Type - This is the type of error that the body can throw.
Since we don't want our subject to throw any errors in this case, we'll give it aFailure
Value of typeNever
(This is used in SwiftCombine
a common practice of ). But in order to do this, we need to specify ourInt
Output type – like this:
let counterSubject = CurrentValueSubject<Int, Never>(0)
However, since Swift 5.6, this situation does not exist - because we can now use a type placeholder to represent our subject'sOutput
Type, which lets us again use the compiler to automatically infer the type for us, just like declaring a normalInt
The value is the same:
let counterSubject = CurrentValueSubject<_, Never>(0)
This is good, but it can be said that this is not a big improvement in swift. After all, we use_
replaceInt
Just save two characters and manually specify the likeInt
Such a simple type is not problematic at the beginning.
** But now let's see how this feature extends to more complex types, and this is where it really starts to shine. **For example, suppose our project contains the following function, let's load a PDF file with user annotations:
func loadAnnotatedPDF(named: String) -> Resource<PDF<UserAnnotations>> { ... }
The above function uses a rather complex generic as its return type, which is probably because we need to reuse ours in multiple placesResource
Type, also because we chose to use*Phantom Type*To specify which PDF we are currently processing.
Now let's see if we call the above function when creating the body instead of just using a simple integer, then we've previously basedCurrentValueSubject
What will the code look like:
// Before Swift 5.6: let pdfSubject = CurrentValueSubject<Resource<PDF<UserAnnotations>>, Never>( loadAnnotatedPDF(named: name) ) // Swift 5.6: let pdfSubject = CurrentValueSubject<_, Never>( loadAnnotatedPDF(named: name) )
This is a pretty big improvement. The Swift 5.6-based version not only saves us some input, but also becausepdfSubject
The type now comes completely fromloadAnnotatedPDF
function, which may make iterating easier for the function (and its associated code) - because if we change the return type of the function, the manual type annotation that needs to be updated will be reduced.
However, it is worth pointing out that in the above case, there is another way to take advantage of Swift's type reasoning capabilities—that is, useType alias, notType placeholder. For example, we can define aUnfailingValueSubject
Type alias, which we can use to easily create bodies that do not produce any errors:
typealias UnfailingValueSubject<T> = CurrentValueSubject<T, Never>
With the above, we can now create ourpdfSubject
- because the compiler can inferT
What type does it mean, and the failure typeNever
Has been hardcoded into our new type alias:
let pdfSubject = UnfailingValueSubject(loadAnnotatedPDF(named: name))
But that doesn't mean that type aliases are usually better than type placeholders, because if we want to define new type aliases for each specific case, then this will also make our codebase more complex. Sometimes it's definitely a good idea to specify everything in inline (like when using type placeholders) because this allows us to define completely independent expressions.
Before we summarize, let's also look at how type placeholders are used with set literals - for example when creating a dictionary. Here we choose to manually specify our dictionaryKey
Type (to be able to use dot syntax to refer to various cases of enumerations), and use a type placeholder for the value of the dictionary:
enum UserRole { case local case remote } let latestMessages: [UserRole: _] = [ .local: "", .remote: "" ]
This is the type placeholder - a new feature introduced in Swift 5.6 that can be really useful when dealing with slightly more complex generic types. But it is worth pointing out that these placeholders can only be used when calling the site, not when specifying the return type of a function or computing the attribute.
Summarize
This is the end of this article about Swift type placeholders. For more related contents of Swift type placeholders, please search for my previous articles or continue browsing the related articles below. I hope everyone will support me in the future!