满足 ExpressibleByArrayLiteral 协议

问题描述 投票:0回答:2

为什么当我在 swift 中扩展 ExpressibleByArrayLiteral 协议时,我需要使 init 成为必需的。在协议的定义中,init方法只是公共的。

我的内容与文档中的内容几乎相同,https://developer.apple.com/reference/swift/expressiblebyarrayliteral,而且编译器仍然抱怨将其设置为必需的

init(arrayLiteral: Element...)
。我唯一的区别是我在一个类中实现它,而不是在一个结构中。有什么建议吗?

更新:

这是我的代码的实现:

public class Stack<T> {
private var buffer: [T]

init() {
    self.buffer = []
}

public func push(_ value: T) {
    self.buffer.append(value)
} 

public func pop() -> T? {
    return self.buffer.popLast()
}

var size: Int {
    return self.buffer.count
}

var isEmpty: Bool {
    return self.size == 0
}
} 

extension Stack: ExpressibleByArrayLiteral {
    init(arrayLiteral: T...) {
        for item in arrayLiteral {
            self.push(item)
        }
    }
}

我遇到的错误是:

  1. 指定的初始值设定项不能在“Stack”的扩展中声明;您的意思是这是一个方便的初始化程序吗?

  2. 初始化程序“init(arrayLiteral:)”必须声明为公共,因为它符合公共协议“ExpressibleByArrayLiteral”中的要求

  3. 初始化器要求 'init(arrayLiteral:)' 只能通过非最终类 'Stack' 定义中的

    required
    初始化器来满足

问题

  1. 错误 #1 -> 为什么我不能在扩展中声明指定的 init?

  2. 错误#2 ->我理解这部分,协议将其定义为公共。但为什么文档在没有 public 关键字的情况下实现它?

  3. 错误#3 ->这几乎是我最大的问题,为什么需要这样做。

swift protocols
2个回答
5
投票

原因是继承:协议内的任何

init
如果被类采用,则必须标记为
required
struct
不能被继承。

对于类,

init
必须初始化同一类的实例或返回
nil
。当该类采用协议时,它本身及其所有子类必须提供自己的实现,以便编译器可以验证这一事实:

class ClassA : ExpressibleByArrayLiteral {
    required init(arrayLiteral elements: Self.Element...) {
        // this implicitly returns an instance of ClassA
    }
}

class ClassB : ClassA {
   // without its own init(arrayLitteral:), it will fallback on ClassA's init and return
   // an instance of ClassA, which Swift does not allow.
}

这是由于 Swift 的静态类型系统造成的。与 Objective-C 不同,在 Objective-C 中,您认为创建了一个

NSString
,但实际上却得到了一个
_NSContiguousString
(所谓的 类簇 设计模式)

NSString * str = [[NSString alloc] initWithString:@"Hello world"];
// surprise: you may get an _NSContiguousString instead

只需删除扩展并在类的定义中实现它即可:

public class Stack<T> : ExpressibleByArrayLiteral {
    private var buffer: [T]

    init() {
        self.buffer = []
    }

    public func push(_ value: T) {
        self.buffer.append(value)
    } 

    public func pop() -> T? {
        return self.buffer.popLast()
    }

    var size: Int {
        return self.buffer.count
    }

    var isEmpty: Bool {
        return self.size == 0
    }

    // MARK: -
    required public init(arrayLiteral: T...) {
        self.buffer = []
        for item in arrayLiteral {
            self.push(item)
        }
    }
}

2
投票

对于错误 #1 -> 为什么我不能在扩展中声明指定的 init?

因为方便初始化器必须调用指定的初始化器。扩展名可以在其他文件中定义。使用当前的结构,该类可能没有在此编译单元(文件)中定义指定的初始化程序,这会给当前编译器带来太多混乱。 (这并不是说这是一个无法解决的类型论问题;它只是在需要弄清楚单个文件中的所有内容时会产生问题。)扩展是扩展。如果它们从根本上改变了对象的工作方式(例如创建指定的初始值设定项),那么它们可能无法成为扩展。

对于错误 #2 -> 我理解这部分,协议将其定义为公共。但为什么文档在没有 public 关键字的情况下实现它呢?

因为文档渲染器总是删除协议上的

public
。如果这导致混乱,请向 bugs.swift.org 提交错误。

对于错误 #3 -> 这几乎是我最大的问题,为什么需要这样做。

因为这不是最后一堂课。考虑是否存在

Stack
的子类,它有自己所需的初始值设定项。您如何确保
init(arrayLiteral:)
调用了它?它无法调用它(因为它不知道它存在)。因此,要么
init(arrayLiteral:)
必须是
required
(这意味着它需要成为主声明的一部分,而不是扩展),或者
Stack
必须是
final

如果您标记它

final
,这将按您的预期工作。如果你希望它被子类化,只需将它从扩展中移出到主体中,然后像这样定义它:

public convenience required init(arrayLiteral: T...) {
    self.init()
    for item in arrayLiteral {
        self.push(item)
    }
}
© www.soinside.com 2019 - 2024. All rights reserved.