我希望能够使用数组文字实例化一个名为MyLabel
的子类,它是UILabel
的子类。我正在使用这个in my framework ViewComposer,它允许使用归因于 View 的枚举数组来创建UIViews,如下所示:
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'"
由于符合下面的自定义协议(protocol)
UILabel
,因此使用数组文字实例化Makeable
的方法是可行的。是否有可能使编译器理解我是在指的是子类
MyLabel
而不是其父类(super class)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'"
我还尝试通过扩展
ExpressibleByArrayLiteral
来遵循ExpressibleByNumberArrayLiteral
协议(protocol)(我怀疑这两个解决方案可能是等效的,并且可以编译为相同的代码..),如下所示: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之后,我最初认为这是不可能的。但是,我确实找到了here帖子,该帖子适用于Strings和其他非UI类。从这篇文章中,我合并了您不能通过继承方法将ExpressibleByArrayLiteral应用于子类的想法,这可能就是您得到错误的原因(我可以使用许多其他方法来多次复制该错误)。
最后,通过将ExpressibleByArrayLiteral采用直接移动到您的UILabel子类上,似乎可以正常工作!
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
事实证明,我们甚至不能继承元素类型类(字符串,整数等)的可表达性,您仍然必须为其重新指定初始化程序。
通过一些调整,我还应用了其他方法进行思考!
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 ;)
就目前而言,我似乎无法像您一样将可扩展性应用于协议(protocol)方法。但是,随着变通办法的发展,这似乎可以解决问题。现在猜,我们只需要将初始化程序应用于每个子类。不幸的是,但是仍然值得一看!
更新以使用扩展方法重新发布问题
当无法保证父类(super class)不会被显着改变时,Swift的继承会禁止便捷初始化。虽然您的init不会更改UILABEL的属性,但扩展的严格类型只是不支持这种类型的初始化程序的required和便捷组合。
我是从post btw链接中的above那里获取的:
而且,如果我们查看您遇到的两个错误,只需尝试直接将UILabel扩展为
ExpressibleByArrayLiteral
,而不是通过嵌套的协议(protocol)网络(如您正在执行的操作)即可: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
需要一个“必需”的初始化方法来使其符合该标准,正如编译器所说:不能直接在要自定义的父类(super class)的扩展内部实现。不幸的是,仅凭这种逻辑..您所需的方法是有缺陷的。第二。您想要的非常特定的初始化程序'init(arrayLiteral :)用于最终类型的类。与之类似,您在声明 header 上用关键字FINAL标记的类或类TYPES(字符串是一个,数字类也是)。 UILabel根本不是允许子类化的最终类,并且您不能在不修改语言的情况下更改它。为了说明非最终形式,请尝试子类
String
,由于它不是class
或protocol
,因此会出现错误。但这绝对不会让您通过App Store。因此,通过DESIGN,您无法通过扩展在UILabel本身上使用此方法。三。您采用了自定义协议(protocol)的方法,并尝试通过扩展和继承将其应用于
UILabel
。我确实表示歉意,但是Swift并不会忽略它的语言结构,仅仅是因为您在结构的两端之间添加了一些自定义代码。您的protocol
方法虽然很优雅,但只是将问题嵌套在这里,没有解决。这是因为它只是将这些初始化约束重新应用到UILabel上,而不管您自己的中间件如何。第四。在这里有些逻辑上的思路。如果您查看XCode内
Expressibles
上的Apple Docs(代码文件本身),则会注意到该协议(protocol)尤其适用于RawRepresentable
类和类型(Strings
,Ints
,Doubles
等):因此,任何作为
class
根级数据表示形式的数据都可以通过extension
来采用。您可以在上面清楚地看到,将此protocol
立即添加到UILabel
时,您还对其施加了RawRepresentable
协议(protocol)。我无法打赌,它不能采用我的本性,这是“MyLabel无法转换为UILabel”错误的根源。 UILabel
并非其中之一,这就是为什么要获得non-final class
属性的原因:它是一个UI元素,是许多RawRepresentables的组合。因此,有意义的是,您不应该直接直接初始化由RawRepresentables组成的类,因为如果在init
编译器端发生一些混淆,并且它没有改变您想要的Raw类型,则可能只是完全破坏了该类实例,并且使每个实例都陷入了调试的噩梦。为了说明我要说明的
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子类上添加Expressibles以及第二层子类方面的约束程度:
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是通过这种方式设计的。您不能直接在该协议(protocol)上应用此特定协议(protocol),因为
UILabel
是语言级别的父类(super class),并且想出这些东西的人不希望您对UILabel
进行过多介绍。因此,您根本无法做到这一点,因为由于non-final
的性质和协议(protocol)本身,无法通过UILabel
父类(super class)的扩展来应用此协议(protocol)。他们只是不兼容这种方式。但是,您可以在每个子类的基础上对其子类应用此方法。这意味着您每次都必须重新声明符合条件的初始化程序。糟透了!但这就是它的工作方式。我赞扬您的方法,似乎几乎可以将扩展方法降低到T。但是,Swift的构建方式似乎存在一些您无法规避的东西。我不是唯一肯定这一结论的人(只需检查链接),所以我请您删除您的否决票。您已经提供了一个解决方案,已经为您提供了证明我的观点的引用,并且我还提供了代码以向您展示如何解决这种语言性质的约束。有时只是无法解决所需的方法。在其他时候,另一种方法是解决问题的唯一方法。