70
protocol Car {
     var wheels : Int { get set}

     init(wheels: Int)

}

extension Car {
    init(wheels: Int) {
        self.wheels = wheels
    }
}

on self.wheels = wheels i get the error

Error: variable 'self' passed by reference before being initialized

How can I define the initializer in the protocol extension?

mfaani
  • 33,269
  • 19
  • 164
  • 293
bogen
  • 9,954
  • 9
  • 50
  • 89
  • 2
    If you're using simple structures you can omit the initializer in the protocol, since it will be synthesized by the compiler! – heyfrank Feb 28 '18 at 13:51

4 Answers4

88

As you can see this doesn't work under these circumstances because when compiling, one has to make sure that all properties are initialized before using the struct/enum/class.

You can make another initializer a requirement so the compiler knows that all properties are initialized:

protocol Car {
    var wheels : Int { get set }
    // make another initializer
    // (which you probably don't want to provide a default implementation)
    // a protocol requirement. Care about recursive initializer calls :)
    init()
    init(wheels: Int)

}

extension Car {
    // now you can provide a default implementation
    init(wheels: Int) {
        self.init()
        self.wheels = wheels
    }
}

// example usage

// mark as final
final class HoverCar: Car {
    var wheels = 0
    init() {}
}

let drivableHoverCar = HoverCar(wheels: 4)
drivableHoverCar.wheels // 4

As of Xcode 7.3 beta 1 it works with structs as expected but not with classes since if they are not final the init(wheels: Int) in the protocol is a required init and it can be overridden therefore it cannot be added through an extension. Workaround (as the complier suggests): Make the class final.

Another workaround (in depth; without final class)

To work with classes without making them final you can also drop the init(wheels: Int) requirement in the protocol. It seems that it behaves no different than before but consider this code:

protocol Car {
    var wheels : Int { get set }
    init()
    // there is no   init(wheels: Int)
}

extension Car {
    init(wheels: Int) {
        self.init()
        print("Extension")
        self.wheels = wheels
    }
}

class HoverCar: Car {
    var wheels = 0
    required init() {}
    init(wheels: Int) {
        print("HoverCar")
        self.wheels = wheels
    }
}

// prints "HoverCar"
let drivableHoverCar = HoverCar(wheels: 4)

func makeNewCarFromCar<T: Car>(car: T) -> T {
    return T(wheels: car.wheels)
}

// prints "Extension"
makeNewCarFromCar(drivableHoverCar)

So if you make a Car from a generic context where the type on which you call init is only to be known as Car the extension initializer is called even though an initializer is defined in HoverCar. This only occurs because there is no init(wheels: Int) requirement in the protocol.

If you add it you have the former problem with declaring the class as final but now it prints two times "HoverCar". Either way the second problem probably never occurs so it might be a better solution.

Sidenote: If I have made some mistakes (code, language, grammar,...) you're welcome to correct me :)

Qbyte
  • 12,753
  • 4
  • 41
  • 57
  • Can you add the declaration of the class to further illustrate your solution? – Craig Grummitt Jan 14 '16 at 23:01
  • @CraigGrummitt Which class do you mean? `Car` is a protocol and its declaration is complete. – Qbyte Jan 14 '16 at 23:25
  • I mean a class that would implement the Car protocol. – Craig Grummitt Jan 15 '16 at 00:56
  • @CraigGrummitt Thank you for commenting on this answer. While adding an example I found a second solution which is probably better than the first one :) See edited answer above – Qbyte Jan 16 '16 at 00:36
  • 1
    thanks for showing the class. The main problem I see with this solution is that you either need to declare a default value for wheels(in the var declaration or the required init()) or you need to declare it an implicitly unwrapped Optional. It's as good a solution as we have for the problem, I just wonder whether that in itself smells like perhaps we just shouldn't be implementing initializers in protocol extensions! :) – Craig Grummitt Jan 18 '16 at 16:35
  • 1
    @CraigGrummitt I totally agree with you. The requirement of a default init which forces you to set a default value for wheels is kind of bad. However there is a discussion going on for Swift 3 about partial initializers which could solve this problem. – Qbyte Jan 18 '16 at 16:45
  • 1
    @Qbyte `drivableHoverCar.wheels` prints 4, not 6. – slider Nov 03 '17 at 01:15
  • "but now it prints two times 'HoverCar'" mentioned for the 2nd approach. Why would that happen??? – pravir Jul 09 '20 at 07:19
  • @pravir This behavior is due to static and dynamic dispatch, see 'Examples' in https://medium.com/flawless-app-stories/static-vs-dynamic-dispatch-in-swift-a-decisive-choice-cece1e872d. If we specify 'init(wheels:Int)' in the protocol, then it uses dynamic dispatch. If the method is only defined in the `extension`, then it uses static dispatch. – Qbyte Jul 10 '20 at 15:18
  • If I understand this correctly, this is a **horribly wrong answer**. That is: things are just working for you because the concrete class in your **example** is a class that can be initialized with `init()`. 9 out of 10, you have a class that can't be initialized that simply... – mfaani Nov 10 '21 at 17:27
7

My understanding is that this isn't possible, because the protocol extension can't know which properties the conforming class or struct has - and therefore cannot guarantee they are correctly initialized.

If there are ways to get around this, I'm very interested to know! :)

6

@Qbyte is correct.

In addition, you can take a look at my Configurable

In that I have Initable protocol

public protocol Initable {
    // To make init in protocol extension work
    init()
}

public extension Initable {
    public init(@noescape block: Self -> Void) {
        self.init()
        block(self)
    }
}

Then in order to conform to it

extension Robot: Initable { }

I have 2 ways, using final or implement init

final class Robot {
    var name: String?
    var cute = false
}

class Robot {
    var name: String?
    var cute = false

    required init() {

    }
}
onmyway133
  • 45,645
  • 31
  • 257
  • 263
2

May not be the same but in my case instead of using init I used a static func to return the object of the class.

protocol Serializable {
   static func object(fromJSON json:JSON) -> AnyObject?
}

class User {
    let name:String

    init(name:String) {
        self.name = name
    }
}

extension User:Serializable {

    static func object(fromJSON json:JSON) -> AnyObject? {
        guard let name = json["name"] else {
            return nil
        }

        return User(name:name)
     }

}

Then to create the object I do something like:

let user = User.object(fromJSON:json) as? User

I know its not the best thing ever but its the best solution I could find to not couple business model with the data layer.

NOTE: I'm lazy and I coded everything directly in the comment so if something doesn't work let me know.

Mijail
  • 186
  • 10