Andreas Utzinger
Andreas Utzinger

Reputation: 402

Using init as a Closure

Recently I saw the following code line in a book (about CoreData)

return modelURLs(in: modelName).compactMap(NSManagedObjectModel.init)

I know what the code does but the question is: Why and how does it work? There should be a closure as the argument of the compactMap function but there's only a "NSManagedObjectModel.init" in NORMAL parenthesis. What's the secret about it? What is it doing there? I would understand it if there's a static/class property called init which returns a closure but I don't think there is.

Unfortunately the book doesn't say more about this line of code. I would like to have further readings from the apple docs but I can't find anything. When I make a google search about "init in closures" then I don't get helpful results.

So you guys are my last hope :)

By the way: the function modelURLs(in: modelName) returns an Array of URLs but that's not really important here.

Upvotes: 2

Views: 206

Answers (1)

Joakim Danielson
Joakim Danielson

Reputation: 51971

When using closures different syntax can be used as in the below example that converts an int array to a string array

let array = [1, 2, 3]

The following calls to compactMap will all correctly convert the array and generate the same result

let out1 = array.compactMap({return String($0)})
let out2 = array.compactMap({String($0)})
let out3 = array.compactMap {String($0)}
let out4 = array.compactMap(String.init)

When there are two init methods that takes the same number and types of argument then you must add the full signature for the init method to use. Consider this simple example struct

struct TwoTimesInt: CustomStringConvertible {
    let value: Int
    let twiceTheValue: Int

    var description: String {
        return "\(value) - \(twiceTheValue)"
    }

    init(value: Int) {
        self.value = value
        self.twiceTheValue = 2 * value
    }
}

With only 1 init method we can do

let out5 = array.compactMap(TwoTimesInt.init)

But if we add a second init method

    init(twiceTheValue: Int) {
        self.value = twiceTheValue / 2
        self.twiceTheValue = twiceTheValue
    }

Then we need to give the full signature of the init method to use

let out6 = array.compactMap( TwoTimesInt.init(value:) )

Another thing worth mentioning when it comes to which method is selected is to look at the full signature of the init method including if it returns an optional value or not. So for example if we change the signature of the second init method to return an optional value

    init?(twiceTheValue: Int) {
        self.value = twiceTheValue / 2
        self.twiceTheValue = twiceTheValue
    }

then compactMap will favour this init since it expects a closure that returns an optional value, so if we remove the argument name in the call

let out7 = array.compactMap(TwoTimesInt.init)

will use the second init while the map function on the other hand will use the first init method if called the same way.

let out8 = array.map(TwoTimesInt.init)

Upvotes: 3

Related Questions