Swift is a statically typed language, which means that the type of every property, constant, and variable we declare needs to be specified at compile time. Typically, however, this is not something that has to be done manually; instead, the compiler is able to automatically infer various type information based on the assigned value — thanks to Swift’s support for type inference.

So, for example, here we declare a few constants – without specifying any type at all, because the compiler is able to infer the information based on the assigned values:

let number = 42
let string = “Hello, world!”
let array = [1, 1, 2, 3, 5, 8]
let dictionary = [“key”: “value”]

For comparison, here’s what it would look like if we manually specified the type of each constant:

let number: Int = 42
let string: String = “Hello, world!”
let array: [Int] = [1, 1, 2, 3, 5, 8]
let dictionary: [String: String] = [“key”: “value”]

Therefore, to make Swift syntax as lightweight as possible, type inference plays an important role, and type inference applies not only to variable declarations and assignment statements of other types, but in the case of many other types as well.

For example, here we define an enumeration that describes various contact types, and a function that loads Contactan array of values ​​belonging to a specific type:

enum ContactKind {
    case family
    case friend
    case coworker
    case acquaintance
}

func loadContacts(ofKind kind: ContactKind) -> [Contact] {
    ...
}

ContactKind.friendWhile it is common to refer to members of the above enumeration by specifying both the type and the member (eg When calling the above function:

let friends = loadContacts(ofKind: .friend)

What’s really cool is that the above “dot syntax” doesn’t just apply to enumeration members, but also when referencing any static property or method. For example, here we extend the Foundation URLclass and add a static attribute to create a link to this website URL:

extension URL {
    static var swiftBySundell: URL {
        URL(string: “https://swiftbysundell.com”)!
    }
}

Now, when calling any URLmethod that accepts a parameter (such as the new Combine framework enhanced URLSessionAPI), we can simply reference the above property:

let publisher = URLSession.shared.dataTaskPublisher(for: .swiftBySundell)

Very cool! However, although type inference is a very useful feature, there are still some situations where it may be necessary to specify additional type information to achieve the desired results.

A very common example of these situations is dealing with numeric types. When a numeric literal is assigned to a variable or constant, it will be inferred to be of Inttype by default – which is a perfectly reasonable default – but if you wish to use other numeric types, such as Doubleor Float, you will need to specify these types manually. Here are a few ways:

let int = 42
let double = 42 as Double
let float: Float = 42
let cgFloat = CGFloat(42)

Another situation is that when calling a function with a generic return type , you may also need to provide additional type information to the compiler.

For example, here we extend the built-in Bundleclass, adding a generic method that can easily load and decode any JSON file bundled in the application:

extension Bundle {
    struct MissingFileError: Error {
        var name: String
    }

    func decodeJSONFile<T: Decodable>(named name: String) throws -> T {
        guard let url = self.url(forResource: name, withExtension: “json”) else {
            throw MissingFileError(name: name)
        }

        let data = try Data(contentsOf: url)
        let decoder = JSONDecoder()
        return try decoder.decode(T.self, from: data)
    }
}

To learn more about Swift’s built-in error handling mechanism (in the above code, through the use of the throwsand trykeywords), check out the basic article on error handling .

Now imagine that in application development, before the real server and network code is ready, we want to decode Userinstances of the following types from the bundled JSON file:

struct User: Codable {
    var name: String
    var email: String
    var lastLoginDate: Date
}

However, if you call the method like this decodeJSONFile, you will get a compiler error:

// 
let user = try Bundle.main.decodeJSONFile(named: “user-mock”)

This is because the exact type of any given JSON file that is about to be decoded depends on what the generic type T actually references at each call point – and since we didn’t provide the compiler with any such information above, it will Got an error. In this case, the compiler has no way of knowing what type of Userinstance we wish to decode.

To solve this problem, we can use the same technique as above for specifying different types of values, either giving our userconstant an explicit type or using as keywords – like this:

let user: User = try Bundle.main.decodeJSONFile(named: “user-mock”)
let user = try Bundle.main.decodeJSONFile(named: “user-mock”) as User

However, if called in a context where the expected return type is known decodeJSONFile, it is possible again to let Swift’s type inference mechanism find that information – as in the following case, where we define a MockDatawrapper struct called , this structure has an Userattribute of type, and we assign the result to this attribute:

struct MockData {
    var user: User
}

let mockData = try MockData(
    user: Bundle.main.decodeJSONFile(named: “user-mock”)
)

That’s a brief introduction to Swift’s type inference capabilities. It’s worth pointing out that type inference does have a computational cost associated with it, but fortunately these costs occur entirely at compile time (and therefore do not affect the runtime performance of the application), but when dealing with more complex expressions , still worth noting. If we encounter an expression that takes the compiler a long time to figure out, we can always specify the types manually using any of the techniques above.

Leave a Reply

Your email address will not be published. Required fields are marked *