`ExpressibleByArrayLiteral` 符合 class 及其超级 class => “<superclass> 不可转换为 <subclass>”
`ExpressibleByArrayLiteral` conformed to by class and its superclass => "<superclass> is not convertible to <subclass>"
我希望能够实例化一个子类,这里命名为 MyLabel
,它是 UILabel
的子类,使用数组文字。我正在使用这个 in my framework ViewComposer,它允许使用归因于视图的枚举数组创建 UIView,如下所示:
let label: UILabel = [.text("Hello World"), .textColor(.red)]
在这个问题中,我大大简化了用例,而不是允许编写:
let vanilla: UILabel = [1, 2, 3, 4] // works!
print(vanilla.text!) // prints: "Sum: 10"
我想做的是使用相同的 ExpressibleByArrayLiteral
语法,但使用 UILabel
的子类,称为 MyLabel
。但是,当我尝试时编译器阻止了我:
let custom: MyLabel = [1, 2, 3, 4] // Compilation error: "Could not cast value of type 'UILabel' to 'MyLabel'"
由于符合下面的自定义协议 Makeable
,使用数组字面量实例化 UILabel
是可行的。
是否有可能让编译器理解我指的是子类 MyLabel
的数组文字初始值设定项而不是其超类 UILabel
?
以下代码可能没有任何逻辑意义,但它是一个最小示例,隐藏了我真正想要的东西:
// This protocol has been REALLY simplified, in fact it has another name and important code.
public protocol ExpressibleByNumberArrayLiteral: ExpressibleByArrayLiteral {
associatedtype Element
}
public protocol Makeable: ExpressibleByNumberArrayLiteral {
// we want `init()` but we are unable to satisfy such a protocol from `UILabel`, thus we need this work around
associatedtype SelfType
static func make(values: [Element]) -> SelfType
}
public protocol Instantiatable: ExpressibleByNumberArrayLiteral {
init(values: [Element])
}
// My code might have worked if it would be possible to check for _NON-conformance_ in where clause
// like this: `extension Makeable where !(Self: Instantiatable)`
extension Makeable {
public init(arrayLiteral elements: Self.Element...) {
self = Self.make(values: elements) as! Self
}
}
extension Instantiatable {
init(arrayLiteral elements: Self.Element...) {
self.init(values: elements)
}
}
extension UILabel: Makeable {
public typealias SelfType = UILabel
public typealias Element = Int
public static func make(values: [Element]) -> SelfType {
let label = UILabel()
label.text = "Sum: \(values.reduce(0,+))"
return label
}
}
public class MyLabel: UILabel, Instantiatable {
public typealias Element = Int
required public init(values: [Element]) {
super.init(frame: .zero)
text = "Sum: \(values.reduce(0,+))"
}
public required init?(coder: NSCoder) { fatalError() }
}
let vanilla: UILabel = [1, 2, 3, 4]
print(vanilla.text!) // prints: "Sum: 10"
let custom: MyLabel = [1, 2, 3, 4] // Compilation error: "Could not cast value of type 'UILabel' to 'MyLabel'"
我也尝试通过扩展 ExpressibleByNumberArrayLiteral
来符合 ExpressibleByArrayLiteral
协议(我怀疑这两个解决方案可能是等价的,并且编译成相同的代码..),像这样:
extension ExpressibleByNumberArrayLiteral where Self: Makeable {
public init(arrayLiteral elements: Self.Element...) {
self = Self.make(values: elements) as! Self
}
}
extension ExpressibleByNumberArrayLiteral where Self: Instantiatable {
init(arrayLiteral elements: Self.Element...) {
self.init(values: elements)
}
}
但这也没有用。出现同样的编译错误。
我在上面的大代码块中写了一条注释,如果我能够在 where
条款:
extension Makeable where !(Self: Instantiatable)
但是 AFAIK 那是不可能的,该代码至少无法编译。 extension Makeable where Self != Instantiatable
.
也不行
我想做的可以吗?
我可以接受 MyLabel
变成 final class
。但这没有区别。
请说这是可能的。
经过 Apple Docs, I initially thought this was not possible. However, I did find a post here 之后,它适用于字符串和其他 non-UI classes。从 post 我合并了你不能通过继承方法将 ExpressibleByArrayLiteral 应用到 subclass 的想法,这可能就是你得到错误的原因(我可以用许多其他方法重现很多次)。
最后,通过将 ExpressibleByArrayLiteral 的采用直接移动到您的 UILabel subclass,它似乎起作用了!
public class MyLabel: UILabel, ExpressibleByArrayLiteral {
public typealias Element = Int
public override init(frame: CGRect) {
super.init(frame: frame)
}
required public init(values: [Element]) {
super.init(frame: .zero)
text = "Sum: \(values.reduce(0,+))"
}
public convenience required init(arrayLiteral: Element...) {
self.init()
self.text = "Sum: \(arrayLiteral.reduce(0,+))"
}
public required init?(coder: NSCoder) { fatalError() }
}
let vanilla: MyLabel = [1, 2, 3, 4]
print(vanilla.text) // prints Sum: 10
事实证明,我们甚至无法继承元素类型 classes(字符串、整数等)的可表达性,您仍然需要 re-specify 它的初始化程序。
经过一些调整,我还应用了这些其他方法进行思考!
public class MyLabel: UILabel, ExpressibleByArrayLiteral {
public typealias Element = Int
private var values : [Element]?
public var append : Element? {
didSet {
if let t = append {
values?.append(t)
}
}
}
public var sum : Element {
get {
guard let s = values else {
return 0
}
return s.reduce(0,+)
}
}
public var sumString : String {
get {
return "\(sum)"
}
}
public var label : String {
get {
guard let v = values, v.count > 0 else {
return ""
}
return "Sum: \(sumString)"
}
}
public override init(frame: CGRect) {
super.init(frame: frame)
}
required public init(values: [Element]) {
super.init(frame: .zero)
text = "Sum: \(values.reduce(0,+))"
}
public convenience required init(arrayLiteral: Element...) {
self.init()
self.values = arrayLiteral
self.text = label
}
public required init?(coder: NSCoder) { fatalError() }
}
let vanilla: MyLabel = [1, 2, 3, 4]
print(vanilla.label) //prints out Sum: 10 , without unwrapping ;)
目前,我似乎无法像您那样将可表达性应用于协议方法。但是,随着变通办法的实施,这似乎可以解决问题。猜猜现在我们只需要将初始化程序应用于每个 subclass。不幸的是,但仍然值得一试!
更新 RE-AFFIRM 扩展方法的问题
Swift 的继承在不能保证 superclass 不会被显着改变时禁止便利初始化。虽然您的 init 不会更改 UILABEL 的属性,但扩展的 strict-typing 不支持此类初始化程序的必需和便利的组合。
我从这个 post, which was included in the link above 顺便说一句:
Because this is a non-final class. Consider if there were a subclass
to Stack that had its own required initializer. How would you ensure
that init(arrayLiteral:) called it? It couldn't call it (because it
wouldn't know that it existed). So either init(arrayLiteral:) has to
be required (which means it needs to be part of the main declaration
and not a extension), or Stack has to be final.
If you mark it final, this works like you expect. If you want it to be
subclassed, just move it out of the extension and into the main body.
如果我们看看你得到的两个错误,只需尝试将 UILabel 直接扩展到 ExpressibleByArrayLiteral
,而不是像你正在做的那样通过嵌套的协议网络:
Initializer requirement 'init(arrayLiteral:)' can only be satisfied by a 'required' initializer in the definition of non-final class 'UILabel'
'required' initializer must be declared directly in class 'UILabel' (non in an extension).
所以首先。 ExpressibleByArrayLiteral
需要一个 'required' 初始化方法来符合它,正如编译器所说的那样:你不能直接在你希望自定义的 superclass 的扩展内部实现。不幸的是,单凭这个逻辑..你想要的方法是有缺陷的。
其次。您想要的非常具体的初始化程序,'init(arrayLiteral:),用于最终类型 classes。例如,classes,您在其声明 header 上用关键字 FINAL 标记,或 class TYPES(字符串是一个,数字 classes 也是)。 UI标签只是非 final-class 允许子classing 并且你不能在不破解语言的情况下改变它。为了说明 non-final 和 final,尝试 subclass String
,你会得到一个错误,因为它不是 class
或 protocol
。无论如何,这不会让您通过 App Store。因此,通过设计,您不能通过扩展在 UILabel 本身上使用此方法。
三个。您采用自定义协议方法,并尝试通过扩展和继承将其应用于 UILabel
。我深表歉意,但是 Swift 不会仅仅因为您在约束的两端。您的 protocol
方法虽然优雅,但只是在此处嵌套问题,而不是解决问题。这是因为它只是 re-applies 这些初始化约束回到 UI 标签,而不考虑你自己的中间件。
第四。这里有点逻辑思路。如果您在 XCode(代码文件本身)内部查看 Expressibles
上的 Apple 文档,您会注意到协议特别适用于 RawRepresentable
classes 和类型(Strings
, Ints
, Doubles
, 等等):
For any enumeration with a string, integer, or floating-point raw
type, the Swift compiler automatically adds RawRepresentable
conformance. When defining your own custom enumeration, you give it a
raw type by specifying the raw type as the first item in the
enumeration's type inheritance list. You can also use literals to
specify values for one or more cases.
所以任何像class
的root-level这样的数据表示,都可以被extension
采纳这个。您可以在上面清楚地看到,当立即将此 protocol
添加到 UILabel
时,您也将 RawRepresentable
协议强加给它。它不能采用我的本性,我敢打赌这是 "MyLabel Cannot be cast to UILabel" 错误的根源。 UILabel
不是这些东西之一,这就是它获得此 non-final class
属性的原因:它是一个 UI 元素,它是许多 RawRepresentables 的组合。所以你不应该直接初始化一个 class 是有道理的,它是一个 RawRepresentables 的集合,因为如果在 init
compiler-side 上发生一些混淆并且它不会改变你的目标是原始类型,它可能会完全破坏 class 实例并使每个实例都陷入调试噩梦。
为了说明我要表达的 RawRepresentable
观点,下面是将此方法应用于 String
时会发生的情况,RawRepresentable
符合类型:
extension String: ExpressibleByArrayLiteral {
public typealias Element = Int
public init(arrayLiteral elements: Element...) {
self.init()
self = "Sum: \(elements.reduce(0,+))"//WE GET NO ERRORS
}
}
let t : String = [1,2,3,4]
print(t) // prints -> Sum: 10
而...
extension UILabel: ExpressibleByArrayLiteral {
public typealias Element = Int
public convenience required init(arrayLiteral elements: Element...) { //The compiler even keeps suggesting you add the method types here illogically without taking to account what is there.. ie: it's confused by what you're trying to do..
self.init()
self.text = "Sum: \(elements.reduce(0,+))" //WE GET THE ERRORS
}
}
//CANNOT PRINT.....
我什至会演示约束在 UILabel subclasses 上添加 Expressibles 的程度,它是 second-tiersubclasses:
class Label : UILabel, ExpressibleByArrayLiteral {
public typealias Element = Int
override init(frame: CGRect) {
super.init(frame: frame)
}
public required init(arrayLiteral elements: Element...) {
super.init(frame: .zero)
self.text = "Sum: \(elements.reduce(0,+))"
}
public required init?(coder: NSCoder) { fatalError() }
}
let te : Label = [1,2,3,4]
print(te.text!) //prints: Sum: 10
class SecondLabel : Label {
public typealias Element = Int
required init(arrayLiteral elements: Element...) {
//THIS IS STILL REQUIRED... EVEN THOUGH WE DO NOT NEED TO MANUALLY ADOPT THE PROTOCOL IN THE CLASS HEADER
super.init(frame: .zero)
self.text = "Sum: \(elements.reduce(0,+))"
}
public required init?(coder: NSCoder) { fatalError() }
}
let ta : SecondLabel = [1,2,3,4]
print(ta.text!) //prints: Sum: 10
总之。
Swift就是这样设计的。你不能将这个特定的协议直接应用到它上面,因为 UILabel
是一个语言级别的超级 class 并且提出这些东西的人不希望你有这么多 over-reach 到 UILabel
。所以,你根本不能这样做,因为由于 UILabel
的性质和协议本身,这个协议不能通过 non-final
superclasses 的扩展来应用。他们只是不兼容这种方式。但是,您可以应用y 这是在 per-subclass 基础上的子 classes。这意味着您每次都必须 re-declare 一致的初始化程序。糟透了!但这就是它的工作原理。
我赞扬您的方法,它似乎几乎可以将扩展方法降低到 T。但是,Swift 的构建方式似乎有些您无法避免的事情。我不是唯一肯定这个结论的人(只需检查链接),所以我会要求你删除你的反对票。你有一个我给你的解决方案,我给你参考来验证我的观点,我还提供了代码来告诉你如何在语言的性质中补救这个限制。有时,所需的方法就是没有解决方案。而其他时候,另一种方法是解决问题的唯一方法。
我最终找到了一个使用后缀运算符的解决方案。
因为我需要能够使用ExpressibleByArrayLiteral
实例化UIKitsUILabel
我不能使用murphguys提出的解决方案Label
和SecondLabel
.
我的原始代码通过添加此后缀运算符来工作:
postfix operator ^
postfix func ^<I: Instantiatable>(attributes: [I.Element]) -> I {
return I(values: attributes)
}
这使得代码可以编译和工作。虽然感觉有点"hacky"...
let custom: MyLabel = [1, 2, 3, 4]^ // note use of caret operator. Now compiles
print(custom.text!) // prints "Sum 10"
如果您对我为什么以及如何使用它感兴趣,您可以查看启用此语法的 my framework ViewComposer:
let label: UILabel = [.text("Hello World"), .textColor(.red)]
但我也希望能够创建自己的 Composable
subclass,名为 MyLabel
(或只是 Label
..)
let myLabel: MyLabel = [.text("Hello World"), .textColor(.red)] // does not compile
之前没有用,但现在可以使用插入符号后缀运算符 ^
,像这样:
let myLabel: MyLabel = [.text("Hello World"), .textColor(.red)]^ // works!
目前我认为这是最优雅的解决方案。
我希望能够实例化一个子类,这里命名为 MyLabel
,它是 UILabel
的子类,使用数组文字。我正在使用这个 in my framework ViewComposer,它允许使用归因于视图的枚举数组创建 UIView,如下所示:
let label: UILabel = [.text("Hello World"), .textColor(.red)]
在这个问题中,我大大简化了用例,而不是允许编写:
let vanilla: UILabel = [1, 2, 3, 4] // works!
print(vanilla.text!) // prints: "Sum: 10"
我想做的是使用相同的 ExpressibleByArrayLiteral
语法,但使用 UILabel
的子类,称为 MyLabel
。但是,当我尝试时编译器阻止了我:
let custom: MyLabel = [1, 2, 3, 4] // Compilation error: "Could not cast value of type 'UILabel' to 'MyLabel'"
由于符合下面的自定义协议 Makeable
,使用数组字面量实例化 UILabel
是可行的。
是否有可能让编译器理解我指的是子类 MyLabel
的数组文字初始值设定项而不是其超类 UILabel
?
以下代码可能没有任何逻辑意义,但它是一个最小示例,隐藏了我真正想要的东西:
// This protocol has been REALLY simplified, in fact it has another name and important code.
public protocol ExpressibleByNumberArrayLiteral: ExpressibleByArrayLiteral {
associatedtype Element
}
public protocol Makeable: ExpressibleByNumberArrayLiteral {
// we want `init()` but we are unable to satisfy such a protocol from `UILabel`, thus we need this work around
associatedtype SelfType
static func make(values: [Element]) -> SelfType
}
public protocol Instantiatable: ExpressibleByNumberArrayLiteral {
init(values: [Element])
}
// My code might have worked if it would be possible to check for _NON-conformance_ in where clause
// like this: `extension Makeable where !(Self: Instantiatable)`
extension Makeable {
public init(arrayLiteral elements: Self.Element...) {
self = Self.make(values: elements) as! Self
}
}
extension Instantiatable {
init(arrayLiteral elements: Self.Element...) {
self.init(values: elements)
}
}
extension UILabel: Makeable {
public typealias SelfType = UILabel
public typealias Element = Int
public static func make(values: [Element]) -> SelfType {
let label = UILabel()
label.text = "Sum: \(values.reduce(0,+))"
return label
}
}
public class MyLabel: UILabel, Instantiatable {
public typealias Element = Int
required public init(values: [Element]) {
super.init(frame: .zero)
text = "Sum: \(values.reduce(0,+))"
}
public required init?(coder: NSCoder) { fatalError() }
}
let vanilla: UILabel = [1, 2, 3, 4]
print(vanilla.text!) // prints: "Sum: 10"
let custom: MyLabel = [1, 2, 3, 4] // Compilation error: "Could not cast value of type 'UILabel' to 'MyLabel'"
我也尝试通过扩展 ExpressibleByNumberArrayLiteral
来符合 ExpressibleByArrayLiteral
协议(我怀疑这两个解决方案可能是等价的,并且编译成相同的代码..),像这样:
extension ExpressibleByNumberArrayLiteral where Self: Makeable {
public init(arrayLiteral elements: Self.Element...) {
self = Self.make(values: elements) as! Self
}
}
extension ExpressibleByNumberArrayLiteral where Self: Instantiatable {
init(arrayLiteral elements: Self.Element...) {
self.init(values: elements)
}
}
但这也没有用。出现同样的编译错误。
我在上面的大代码块中写了一条注释,如果我能够在 where
条款:
extension Makeable where !(Self: Instantiatable)
但是 AFAIK 那是不可能的,该代码至少无法编译。 extension Makeable where Self != Instantiatable
.
我想做的可以吗?
我可以接受 MyLabel
变成 final class
。但这没有区别。
请说这是可能的。
经过 Apple Docs, I initially thought this was not possible. However, I did find a post here 之后,它适用于字符串和其他 non-UI classes。从 post 我合并了你不能通过继承方法将 ExpressibleByArrayLiteral 应用到 subclass 的想法,这可能就是你得到错误的原因(我可以用许多其他方法重现很多次)。
最后,通过将 ExpressibleByArrayLiteral 的采用直接移动到您的 UILabel subclass,它似乎起作用了!
public class MyLabel: UILabel, ExpressibleByArrayLiteral {
public typealias Element = Int
public override init(frame: CGRect) {
super.init(frame: frame)
}
required public init(values: [Element]) {
super.init(frame: .zero)
text = "Sum: \(values.reduce(0,+))"
}
public convenience required init(arrayLiteral: Element...) {
self.init()
self.text = "Sum: \(arrayLiteral.reduce(0,+))"
}
public required init?(coder: NSCoder) { fatalError() }
}
let vanilla: MyLabel = [1, 2, 3, 4]
print(vanilla.text) // prints Sum: 10
事实证明,我们甚至无法继承元素类型 classes(字符串、整数等)的可表达性,您仍然需要 re-specify 它的初始化程序。
经过一些调整,我还应用了这些其他方法进行思考!
public class MyLabel: UILabel, ExpressibleByArrayLiteral {
public typealias Element = Int
private var values : [Element]?
public var append : Element? {
didSet {
if let t = append {
values?.append(t)
}
}
}
public var sum : Element {
get {
guard let s = values else {
return 0
}
return s.reduce(0,+)
}
}
public var sumString : String {
get {
return "\(sum)"
}
}
public var label : String {
get {
guard let v = values, v.count > 0 else {
return ""
}
return "Sum: \(sumString)"
}
}
public override init(frame: CGRect) {
super.init(frame: frame)
}
required public init(values: [Element]) {
super.init(frame: .zero)
text = "Sum: \(values.reduce(0,+))"
}
public convenience required init(arrayLiteral: Element...) {
self.init()
self.values = arrayLiteral
self.text = label
}
public required init?(coder: NSCoder) { fatalError() }
}
let vanilla: MyLabel = [1, 2, 3, 4]
print(vanilla.label) //prints out Sum: 10 , without unwrapping ;)
目前,我似乎无法像您那样将可表达性应用于协议方法。但是,随着变通办法的实施,这似乎可以解决问题。猜猜现在我们只需要将初始化程序应用于每个 subclass。不幸的是,但仍然值得一试!
更新 RE-AFFIRM 扩展方法的问题
Swift 的继承在不能保证 superclass 不会被显着改变时禁止便利初始化。虽然您的 init 不会更改 UILABEL 的属性,但扩展的 strict-typing 不支持此类初始化程序的必需和便利的组合。
我从这个 post, which was included in the link above 顺便说一句:
Because this is a non-final class. Consider if there were a subclass to Stack that had its own required initializer. How would you ensure that init(arrayLiteral:) called it? It couldn't call it (because it wouldn't know that it existed). So either init(arrayLiteral:) has to be required (which means it needs to be part of the main declaration and not a extension), or Stack has to be final.
If you mark it final, this works like you expect. If you want it to be subclassed, just move it out of the extension and into the main body.
如果我们看看你得到的两个错误,只需尝试将 UILabel 直接扩展到 ExpressibleByArrayLiteral
,而不是像你正在做的那样通过嵌套的协议网络:
Initializer requirement 'init(arrayLiteral:)' can only be satisfied by a 'required' initializer in the definition of non-final class 'UILabel'
'required' initializer must be declared directly in class 'UILabel' (non in an extension).
所以首先。 ExpressibleByArrayLiteral
需要一个 'required' 初始化方法来符合它,正如编译器所说的那样:你不能直接在你希望自定义的 superclass 的扩展内部实现。不幸的是,单凭这个逻辑..你想要的方法是有缺陷的。
其次。您想要的非常具体的初始化程序,'init(arrayLiteral:),用于最终类型 classes。例如,classes,您在其声明 header 上用关键字 FINAL 标记,或 class TYPES(字符串是一个,数字 classes 也是)。 UI标签只是非 final-class 允许子classing 并且你不能在不破解语言的情况下改变它。为了说明 non-final 和 final,尝试 subclass String
,你会得到一个错误,因为它不是 class
或 protocol
。无论如何,这不会让您通过 App Store。因此,通过设计,您不能通过扩展在 UILabel 本身上使用此方法。
三个。您采用自定义协议方法,并尝试通过扩展和继承将其应用于 UILabel
。我深表歉意,但是 Swift 不会仅仅因为您在约束的两端。您的 protocol
方法虽然优雅,但只是在此处嵌套问题,而不是解决问题。这是因为它只是 re-applies 这些初始化约束回到 UI 标签,而不考虑你自己的中间件。
第四。这里有点逻辑思路。如果您在 XCode(代码文件本身)内部查看 Expressibles
上的 Apple 文档,您会注意到协议特别适用于 RawRepresentable
classes 和类型(Strings
, Ints
, Doubles
, 等等):
For any enumeration with a string, integer, or floating-point raw type, the Swift compiler automatically adds
RawRepresentable
conformance. When defining your own custom enumeration, you give it a raw type by specifying the raw type as the first item in the enumeration's type inheritance list. You can also use literals to specify values for one or more cases.
所以任何像class
的root-level这样的数据表示,都可以被extension
采纳这个。您可以在上面清楚地看到,当立即将此 protocol
添加到 UILabel
时,您也将 RawRepresentable
协议强加给它。它不能采用我的本性,我敢打赌这是 "MyLabel Cannot be cast to UILabel" 错误的根源。 UILabel
不是这些东西之一,这就是它获得此 non-final class
属性的原因:它是一个 UI 元素,它是许多 RawRepresentables 的组合。所以你不应该直接初始化一个 class 是有道理的,它是一个 RawRepresentables 的集合,因为如果在 init
compiler-side 上发生一些混淆并且它不会改变你的目标是原始类型,它可能会完全破坏 class 实例并使每个实例都陷入调试噩梦。
为了说明我要表达的 RawRepresentable
观点,下面是将此方法应用于 String
时会发生的情况,RawRepresentable
符合类型:
extension String: ExpressibleByArrayLiteral {
public typealias Element = Int
public init(arrayLiteral elements: Element...) {
self.init()
self = "Sum: \(elements.reduce(0,+))"//WE GET NO ERRORS
}
}
let t : String = [1,2,3,4]
print(t) // prints -> Sum: 10
而...
extension UILabel: ExpressibleByArrayLiteral {
public typealias Element = Int
public convenience required init(arrayLiteral elements: Element...) { //The compiler even keeps suggesting you add the method types here illogically without taking to account what is there.. ie: it's confused by what you're trying to do..
self.init()
self.text = "Sum: \(elements.reduce(0,+))" //WE GET THE ERRORS
}
}
//CANNOT PRINT.....
我什至会演示约束在 UILabel subclasses 上添加 Expressibles 的程度,它是 second-tiersubclasses:
class Label : UILabel, ExpressibleByArrayLiteral {
public typealias Element = Int
override init(frame: CGRect) {
super.init(frame: frame)
}
public required init(arrayLiteral elements: Element...) {
super.init(frame: .zero)
self.text = "Sum: \(elements.reduce(0,+))"
}
public required init?(coder: NSCoder) { fatalError() }
}
let te : Label = [1,2,3,4]
print(te.text!) //prints: Sum: 10
class SecondLabel : Label {
public typealias Element = Int
required init(arrayLiteral elements: Element...) {
//THIS IS STILL REQUIRED... EVEN THOUGH WE DO NOT NEED TO MANUALLY ADOPT THE PROTOCOL IN THE CLASS HEADER
super.init(frame: .zero)
self.text = "Sum: \(elements.reduce(0,+))"
}
public required init?(coder: NSCoder) { fatalError() }
}
let ta : SecondLabel = [1,2,3,4]
print(ta.text!) //prints: Sum: 10
总之。
Swift就是这样设计的。你不能将这个特定的协议直接应用到它上面,因为 UILabel
是一个语言级别的超级 class 并且提出这些东西的人不希望你有这么多 over-reach 到 UILabel
。所以,你根本不能这样做,因为由于 UILabel
的性质和协议本身,这个协议不能通过 non-final
superclasses 的扩展来应用。他们只是不兼容这种方式。但是,您可以应用y 这是在 per-subclass 基础上的子 classes。这意味着您每次都必须 re-declare 一致的初始化程序。糟透了!但这就是它的工作原理。
我赞扬您的方法,它似乎几乎可以将扩展方法降低到 T。但是,Swift 的构建方式似乎有些您无法避免的事情。我不是唯一肯定这个结论的人(只需检查链接),所以我会要求你删除你的反对票。你有一个我给你的解决方案,我给你参考来验证我的观点,我还提供了代码来告诉你如何在语言的性质中补救这个限制。有时,所需的方法就是没有解决方案。而其他时候,另一种方法是解决问题的唯一方法。
我最终找到了一个使用后缀运算符的解决方案。
因为我需要能够使用ExpressibleByArrayLiteral
实例化UIKitsUILabel
我不能使用murphguys提出的解决方案Label
和SecondLabel
.
我的原始代码通过添加此后缀运算符来工作:
postfix operator ^
postfix func ^<I: Instantiatable>(attributes: [I.Element]) -> I {
return I(values: attributes)
}
这使得代码可以编译和工作。虽然感觉有点"hacky"...
let custom: MyLabel = [1, 2, 3, 4]^ // note use of caret operator. Now compiles
print(custom.text!) // prints "Sum 10"
如果您对我为什么以及如何使用它感兴趣,您可以查看启用此语法的 my framework ViewComposer:
let label: UILabel = [.text("Hello World"), .textColor(.red)]
但我也希望能够创建自己的 Composable
subclass,名为 MyLabel
(或只是 Label
..)
let myLabel: MyLabel = [.text("Hello World"), .textColor(.red)] // does not compile
之前没有用,但现在可以使用插入符号后缀运算符 ^
,像这样:
let myLabel: MyLabel = [.text("Hello World"), .textColor(.red)]^ // works!
目前我认为这是最优雅的解决方案。