init() 在 Swift 协议中有什么作用?

7

在Swift中,协议可以在定义中声明init()方法。然而,我无法想到除了强制符合类定义init()之外,这解决了任何问题的用例。我们可以在协议类型上调用声明的方法,但是协议上的init不能用于实例化其对象,这是其唯一的目的。

声明协议中的init()方法解决了什么问题?


2
否则,协议扩展如何能够创建符合协议的给定类型的新实例呢?;) - Hamish
啊!对了。很好的观点。在协议扩展中很相关。 - Shubham
1
以下是一个例子:https://dev59.com/z18e5IYBdhLWcg3wjaxI。 - Martin R
3个回答

3
我认为它的真正用途是在泛型类或函数中作为约束时。这是我一个项目中的真实代码。
我声明了一个带有init的协议:
protocol JSONCreatable {
    init(fromJson json: JSON)
}

然后,在一个通用函数中,我返回符合该协议的类:

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) } ?? []
    }
}

这使我能够做像这样的事情:
let user: User = json["user"].asObject()
let results: [Element] = json["elements"].asArray()

通常,我发现形式为<T:SomeProtocol>() - > T的通用函数更好地编写为SomeProtocol扩展中的初始化程序。在[T]的情况下,我还会考虑在受限制的Array扩展中编写一个init。不过这可能只是个人偏好。我认为let array = [SomeJSONCreatable](json:someJSON)let array:[SomeJSONCreatable] = someJSON.asArray()更自然。 - Hamish

2

它强制类从某些数据中具有init(data: data)方法,例如:

protocol JSONable {
    init(data: JSON)
}

强制所有实现了JSONable接口的类都要有一个从JSON初始化的方法,这样你就可以确保始终能够从JSON创建一个实例。


1
看起来这就是问题所在,我认为问题是它除了确保符合实现init方法的目的之外还有什么作用,如果我不能调用类似于JSONable(data: <*someJson*>)的东西。 - Sumeet
@uchiha 嗯,那我就不明白问题了,确保你100%创建一个实例是至关重要的,我想不出其他任何目的。 - JuicyFruit
1
@uchiha,但你可以使用func f<T: JSONable>(jsonString: String) -> T { return T(data: parseJSON(jsonString)) } - user28434'mstep

2

这通常用于允许协议扩展和泛型占位符约束协议,以调用符合该协议的具体类型上的初始化程序。例如,考虑 RangeReplaceableCollection 的默认实现 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)
    }
    // ...
}

如果在 RangeReplaceableCollection 协议中没有定义 init() 为必需项,扩展就无法知道我们可以调用 init() 来创建符合类型的新实例。

但是它也可以直接在泛型和扩展之外使用 - 例如,它可以用于构建一个由给定存在类型的元类型(表示“一些遵循协议的具体类型”的元类型)所表示的新实例:

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.

在这个例子中:
  • type(of: s) 返回 s 的动态类型为 P.Type(一种存在类型元类型),因为 s 静态类型为 P。请记住,type(of:) 是一个 (T) -> T.Type 操作。

  • init() 构造了一个新的实例,其底层具体类型为 S

  • 新实例的静态类型为 P(即封装在存在容器中)。


我认为 type(of: s) 在这里会返回 S.Type。此外,我们不能在 P.Type 上调用初始化程序。 - Shubham
@Shubham 不可以,因为 s 的类型是 P。而你可以在 P.Type 上调用初始化器 - 请随意尝试 :) 只有 P.Protocol (协议本身的类型)不能调用初始化器,因为它不代表具体的类型。 - Hamish
我在 playground 上尝试了一下。 type(of: s) 返回 S.Type,而 P.Type.init() 则失败并提示 "P.Type没有名为 init 的成员"。 - Shubham
@Shubham 试着使用我示例中的代码...你可能将s打成了大写的S - P.Type.init()是不合法的。你需要在元类型上调用init(),并将其类型定义为P.Type(例如,当应用于某些P表达式时,type(of:)的结果)。 - Hamish
哦,好的。我又仔细检查了一下。在将其分配给类型为 S 的对象之后,s 的动态类型会更改为 S.type - Shubham
@Shubham 是的,它会有效,因为编译器现在知道它是 S,所以不需要处理存在类型 :) (但在 S.Type 上调用 init 与在 P.Type 上调用它一样有效)。 - Hamish

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接