7

Protocols in Swift can declare the init() method in their definition. However, I can't think of any use case where this solves any problem other than forcing the conforming classes to define the init() as in the protocol. We can call the declared methods on the protocol type but init on protocol cannot be used to instantiate its object, which is its only purpose.

What problem does declaring init() method in a protocol solve?

Shubham
  • 935
  • 1
  • 7
  • 25

3 Answers3

3

I think the real utility comes when it's used as a constraint in a generic class o function. This is real code from one of my projects.

I declare a protocol with a init:

protocol JSONCreatable {
    init(fromJson json: JSON)
}

Then, in a generic function where I return a class that conforms to that protocol:

import SwiftyJSON

extension JSON {
    func asObject<T>() -> T? where T: JSONCreatable {
        if isEmpty {
            return nil
        }
        return T(fromJson: self)
    }

    func asArray<T>() -> [T] where T: JSONCreatable {
        return array?.map{ json in T(fromJson: json) } ?? []
    }
}

This allows me to do things like this:

let user: User = json["user"].asObject()
let results: [Element] = json["elements"].asArray()
redent84
  • 18,901
  • 4
  • 62
  • 85
  • Usually, I find that the generic functions of the form `() -> T` are often better written as initialisers in an extension of `SomeProtocol`. In the case of `[T]`, I would also consider writing an `init` in a constrained `Array` extension. Probably just a matter a preference though. I find `let array = [SomeJSONCreatable](json: someJSON)` more natural than `let array: [SomeJSONCreatable] = someJSON.asArray()`. – Hamish Mar 09 '17 at 13:04
2

It forces class to have init(data: data) from some data, example:

protocol JSONable {
    init(data: JSON)
}

forces all classes, that are JSONable to have an initialiser from JSON, so you are always sure, that you can create an instance from JSON.

JuicyFruit
  • 2,638
  • 2
  • 18
  • 35
  • 1
    Looks like thats what the question says, I think the question is what purpose does it serve (besides ensuring the conformance to implement the init method), if i cannot call Something like `JSONable(data: <*someJson*>)` – Sumeet Mar 09 '17 at 11:40
  • @uchiha well, then I don't get the question, ensuring that you will 100% create an instance is crucial and I can't imagine any other purposes. – JuicyFruit Mar 09 '17 at 11:44
  • 1
    @uchiha, but you can do `func f(jsonString: String) -> T { return T(data: parseJSON(jsonString)) }` – user28434'mstep Mar 09 '17 at 11:52
2

It's commonly used in order to allow for protocol extensions and generic placeholders constrained to protocols to call the initialiser on the given concrete type that conforms to the protocol. For example, consider RangeReplaceableCollection's default implementation of init<S : Sequence>(_ elements: S):

extension RangeReplaceableCollection {

    // ...

    /// Creates a new instance of a collection containing the elements of a
    /// sequence.
    ///
    /// - Parameter elements: The sequence of elements for the new collection.
    public init<S : Sequence>(_ elements: S) where S.Iterator.Element == Iterator.Element {
      self.init()
      append(contentsOf: elements)
    }
    // ...
}

Without init() being defined as a protocol requirement of RangeReplaceableCollection, there's no way for the extension to know that we can call init() in order to create a new instance of the conforming type.

But it can also be used directly outside of generics and extensions – for example, it can be used to construct a new instance represented by a given existential metatype (the metatype of 'some concrete type that conforms to a protocol'):

protocol P {
    init()
}
struct S : P {
    init() {}
}

let s: P = S()
let s1 = type(of: s).init() // creates a new instance of S, statically typed as P.

In this example:

  • type(of: s) returns the dynamic type of s as P.Type (an existential metatype), as s is statically typed as P. Remember that type(of:) is a (T) -> T.Type operation.

  • init() constructs a new instance of the underlying concrete type, in this case S.

  • The new instance is statically typed as P (i.e boxed in an existential container).

Hamish
  • 78,605
  • 19
  • 187
  • 280
  • I think `type(of: s)` will return `S.Type` here. Moreover, we cannot call initializer on `P.Type` – Shubham Mar 09 '17 at 12:27
  • @Shubham No, because `s` is typed as `P`. And you can call the initialiser on `P.Type` – feel free to try it for yourself :) It's only `P.Protocol` (the type of the protocol itself) that you *can't* call an initialiser on, as it doesn't represent a concrete type. – Hamish Mar 09 '17 at 12:27
  • I tried it on playground. `type(of: s)` returns `S.Type`, & `P.Type.init()` fails with "P.Type has no member named init" – Shubham Mar 09 '17 at 12:36
  • @Shubham Try using the code in my example... You've likely typed `s` as `S` – and `P.Type.init()` is not legal. You need to call `init()` on a metatype *value*, typed as `P.Type` (e.g the result of `type(of:)` when applied to some `P` expression). – Hamish Mar 09 '17 at 12:37
  • Oh, okay. And I double checked it. The dynamic type of `s` changes to `S.type` after assigning it to object of type `S`. – Shubham Mar 09 '17 at 12:39
  • @Shubham Yes it will do, because the compiler now knows it's an `S`, so doesn't need to bother with existentials :) (but calling `init` on an `S.Type` is obviously just as valid as calling it on a `P.Type`) – Hamish Mar 09 '17 at 12:40