In Swift, Why Can't I Instantiate a Protocol When It Has an Initialiser

In swift, why can't I instantiate a protocol when it has an initialiser?

Imagine protocols are adjectives. Movable says you can move it, Red says it has color = "red"... but they don't say what it is. You need a noun. A Red, Movable Car. You can instantiate a Car, even when low on details. You cannot instantiate a Red.

Why can't you initialize a protocol that has default implementation?

A protocol is not a concrete type, so when you would call Protocol.init(), the compiler wouldn't know what concrete type to initialise. It would only know that the type you want to initialise has the interface described by Protocol.

The compiler has to know the concrete type because of the memory allocation. A protocol only defines a subset of required properties (and methods) that its conformant types must have, but doesn't define the whole set of properties its conformant types will have, since that actually differs type-by-type. Due to this, if you were to able to initialise a protocol type, the compiler and the runtime would have no idea about how much memory to allocate for that particular object, which is a piece of information without which no object can be initialised.

Calling a protocol extension initializer

An init within a protocol is required, and therefore has to be implemented explicitly i.e. a default implementation cannot be utilised.

As for 'explicitly calling a protocol extension's initializer', you cannot instantiate a protocol type.

I would suggest using inheritance for this.

Protocol type cannot be instantiated

You are trying to create an instance of a protocol Color(color: item), which is not possible.

Find one possible generic solution below. I could not find a non-static (nice) solution so far.

protocol Color {
var color: UIColor {get set}
init(color: UIColor)
}

struct Ball : Color {
var color: UIColor
}

extension Color {
static func item<T:Color>(_ item: T, inColors colors: [UIColor]) -> [T] {
var coloredItems = [T]()
for color in colors {
let newColoredItem = T.init(color: color)
coloredItems.append(newColoredItem)
}

return coloredItems
}
}

let ball = Ball(color: .white)
let coloredBalls = type(of:ball).item(ball, inColors: [.red, .green, .blue])

print(coloredBalls)

Prints:

[Ball(color: UIExtendedSRGBColorSpace 1 0 0 1), Ball(color: UIExtendedSRGBColorSpace 0 1 0 1), Ball(color: UIExtendedSRGBColorSpace 0 0 1 1)]

How to define initializers in a protocol extension?

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 :)

Invoking Swift Protocol Initializers

As it turns out, you CAN use the static methods given by a protocol, even when the classes involved (not instantiations thereof) are cast to their protocol. You can even use generics to make the example instantiate method more conveniently have return type of the type you give it, though that causes a different problem as shown below. Here's a bunch of code demonstrating things you can and can't do:

public protocol ProtocolTest {
init(s: String)

static func factory(s: String) -> Self
}

public class Foo : ProtocolTest, CustomStringConvertible {
public let s: String

public required init(s: String) {
self.s = s
}

public class func factory(s: String) -> Self {
return Self(s: s)
}

public var description: String { return "{\(Self.self)|s:\"\(s)\"}" }
}

public class Bar : Foo {
}

public func instantiateGeneric<T : ProtocolTest>(clazz: T.Type) -> T {
return clazz.init(s: "qwertyGeneric")
}

public func instantiateNongeneric(clazz: ProtocolTest.Type) -> ProtocolTest {
return clazz.init(s: "qwertyNongeneric")
}

public func run() {
let ptts: [ProtocolTest.Type] = [Foo.self, Bar.self]

// Success
let aInit : Bar = Bar.init(s: "qwertyInit")
// Success
let aGeneric : Bar = instantiateGeneric(clazz: Bar.self)
// Compile error: Cannot convert value of type 'ProtocolTest' to specified type 'Bar'
let aNongeneric : Bar = instantiateNongeneric(clazz: Bar.self)

for ptt in ptts {
// Success
let bInit : ProtocolTest = ptt.init(s: "qwertyInit")
// Compile error: Protocol type 'ProtocolTest' cannot conform to 'ProtocolTest' because only concrete types can conform to protocols
let bGeneric1 : ProtocolTest = instantiateGeneric(clazz: ptt)
// Compile error: Cannot invoke 'instantiateGeneric' with an argument list of type '(clazz: ProtocolTest.Type)'
let bGeneric2 = instantiateGeneric(clazz: ptt)
// Success
let bNongeneric : ProtocolTest = instantiateNongeneric(clazz: ptt)
// This works too, btw:
let bFactory : ProtocolTest = ptt.factory(s: "qwertyFactory")
}
}

I'm not sure exactly what's up with instantiateGeneric in the loop. It seems like a reasonable line of code. Let me know if you have an explanation. Perhaps regarding the <T : ProtocolTest>, ProtocolTest doesn't technically conform to ProtocolTest, or perhaps it must be a strict subclass? Not sure. It would be nice to be able to use the same method for both uses, but I'm pretty pleased with what I can do, already. This discovery may be enough to get me to actually LIKE Swift, haha.

Creating a new instance of a class with a protocol-conforming class as an argument

You don't need to pass any type at all. The using parameter is completely unnecessary. Delete it. This is a generic; passing the type makes the generic pointless!

Just resolve the generic when you declare what you want to instantiate. (And do not call something Data, that name is taken.)

So, if we start with this:

protocol DataFetcherType {
init(_ mainData: String, fetchData: Bool)
}
class DataFetcher1: DataFetcherType {
required init(_ mainData: String, fetchData: Bool) {
}
}
class DataFetcher2: DataFetcherType {
required init(_ mainData: String, fetchData: Bool) {
}
}

... then this is all you need:

struct MyData<FetcherType: DataFetcherType> {
var mainData: String
var fetcher: FetcherType
init(_ mainData: String, fetchData: Bool = true) {
self.mainData = mainData
self.fetcher = FetcherType.init(mainData, fetchData: fetchData)
}
}

...because to make one, you can just resolve the type:

    let mydata = MyData<DataFetcher1>("hey")

Also, notice that I have changed the type of the variable fetcher. It should be the generic type, not the protocol.

In general if you find yourself passing types around in Swift, that's a bad smell and you should rethink. It can be done, and it sometimes is done, but on the whole Swift does not like metatypes and you should avoid them.

And the same is true of protocols; if you find you are typing something as a protocol, rethink.

This is exactly why generics exist, so you don't do those things.

Conform to a protocol and use self as argument in another class initializer that takes that protocol

As commented, you can't use self in the constant declaration, since at that point you don't still have a self. You have a self once all the needed variables are initialized and calls to super.init made, not before that.

The best way to achieve it is to change it to a variable with an assumed value that will be set during initialization, at the end of it actually:

protocol MyProtocol : class {
func handle()
}

class MyService {

var delegate: MyProtocol

init(delegate: MyProtocol) {
self.delegate = delegate
}
}

class MyClass : MyProtocol {

var service:MyService!

init() {
service = MyService(delegate: self)
}

func handle() {
NSLog("handle")
}
}

You can also make the variable a lazy one instead of using the init method, as suggested by Christopher Swasey:

class MyClass : MyProtocol {

lazy var service:MyService = MyService(delegate: self)

func handle() {
NSLog("handle")
}
}


Related Topics



Leave a reply



Submit