我希望能够使用数组文字实例化一个子类,这里名为MyLabel
,它是UILabel
的子类。我正在使用这个in my framework ViewComposer,它允许使用一个枚举视图的枚举数组来创建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'"
由于符合以下自定义协议UILabel
,使用数组文字的Makeable
实例化无效。
是否有可能让编译器理解我指的是子类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'"
我还尝试通过扩展ExpressibleByArrayLiteral
来符合ExpressibleByNumberArrayLiteral
协议(我怀疑这两个解决方案可能是等效的,并编译为相同的代码..),如下所示:
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
。但这没什么区别。
请说请注意,这是可能的。
答案 0 :(得分:7)
最后,通过将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
事实证明,我们甚至无法在元素类型类(字符串,Ints等)上继承Expressibility,您仍然需要为它重新指定初始值设定项。
通过一些调整,我还应用了其他方法进行思考!
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 ;)
目前,我似乎无法将Expressibility应用于您所采用的协议方法。但是,随着变通办法的出现,这似乎就成了伎俩。现在猜猜我们只需要将初始化器应用于每个子类。不幸的是,但仍然值得一看!
使用扩展方法重新确认问题
当Swift的继承无法保证超类不会被戏剧性地改变时,它会禁止便利。虽然您的init不会更改UILABEL的属性,但扩展的严格类型不支持此类初始化程序所需和方便的组合。
我是从post获取此内容的,above包含在链接中{{3}} btw:
因为这是一个非最终类。考虑是否有子类 到Stack有自己需要的初始化程序。你会如何保证? 那个init(arrayLiteral :)叫它吗?它无法调用它(因为它 我不知道它存在)。所以要么init(arrayLiteral :)必须 是必需的(这意味着它必须是主要声明的一部分 而不是扩展),或Stack必须是最终的。
如果你把它标记为最终,这就像你期望的那样。如果你想要它 子类化,只需将其移出扩展并进入主体。
如果我们看一下你得到的两个错误,只需尝试将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
需要“必要的”'初始化方法符合它,正如编译器所说:你不能直接在你希望自定义的超类的扩展内部实现。不幸的是,单凭这个逻辑......你想要的方法是有缺陷的。
二。你想要的非常具体的初始化程序" init(arrayLiteral :),用于最终类型的类。就像在你的标识中使用关键字FINAL标记的类的声明标题,或类TYPES(字符串是一个,数字类也是如此)。 UILabel根本不是允许子类化的最终类,你不能在不破坏语言的情况下改变它。为了说明非final和final,尝试子类String
,您将收到错误,因为它不是class
或protocol
。无论如何都无法通过App Store。因此,通过DESIGN,您无法通过扩展名在UILabel上使用此方法。
三。您采用自定义协议方法,并尝试通过扩展和继承将其应用于UILabel
。我道歉,但Swift只是不会忽略它的语言结构,因为您在它之间分层一些自定义代码constrtaint的两端。你的protocol
方法尽管很优雅,但它只是将问题嵌套在这里,而不是解决问题。这是因为无论您自己的中间件如何,它都会将这些初始化约束重新应用到UILabel上。
四。在这里有一点逻辑思路。如果您查看XCode内部Expressibles
内的Apple文档(代码文件本身),您会注意到协议特别适用于RawRepresentable
类和类型(Strings
,{{1} },Ints
等。):
对于具有字符串,整数或浮点原始的任何枚举 类型,Swift编译器自动添加
Doubles
一致性。定义自己的自定义枚举时,请给它一个 通过将原始类型指定为原始类型的原始类型 枚举的类型继承列表。您也可以使用文字 指定一个或多个案例的值。
因此,任何作为RawRepresentable
的根级别的数据表示都可以通过class
采用此方法。您可以清楚地看到上面的内容,在将此extension
立即添加到protocol
时,您还要将UILabel
协议强加给它。它不能采用我的本性,我愿意下注是" MyLabel的来源不能投到UILabel"错误。 RawRepresentable
不是其中之一,这就是它获取此UILabel
属性的原因:它是一个UI元素,它是许多RawRepresentables的组合。因此,你不应该直接初始化一个RawRepresentables集合的类是有道理的,因为如果在non-final class
编译器端发生了一些混淆,并且它不会改变Raw类型,那么你就可以了。重新瞄准,它可能只是完全破坏了类实例,并使每个人都陷入了调试的噩梦。
为了说明我尝试制作的init
点,以下是将此方法应用于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
我甚至会演示约束在UILabel子类上添加Expressibles的程度,以及它的第二层子类:
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.....
总之。
Swift就是这样设计的。你不能直接将这个特定的协议应用到它上面,因为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
是一个语言级别的超类,而那些提出这些东西的人并不希望你在UILabel
中超出这个范围。因此,您根本无法执行此操作,因为UILabel
和协议本身的性质导致此协议无法通过non-final
超类的扩展来应用。它们只是不兼容这种方式。但是,您可以基于每个子类在其子类上应用它。这意味着您每次都必须重新声明符合标准的初始值设定项。太糟糕了!但它只是它的运作方式。
我赞扬你的方法,似乎差不多将扩展方法降到了T.但是,似乎有些东西在Swift的构建方式中是你无法规避的。我不是唯一一个肯定这个结论的人(只是查看链接),所以我会要求你删除你的downvote。您有一个我给您的解决方案,我已经为您提供了验证我的观点的参考资料,并且我还提供了代码来向您展示如何使用语言来解决这一限制问题&#39性质。有时候,没有解决方案。而其他时候,另一种方法是解决问题的唯一方法。
答案 1 :(得分:3)
我最终得到了一个使用后缀运算符的解决方案。
由于我需要能够使用<div>
{% block content %}
{% include 'include_layout.html' %}
Content of the child template
{% endblock %}
</div>
设置UIKits UILabel
,我无法使用提及ExpressibleByArrayLiteral
和Label
的murphguys解决方案。
我的原始代码通过添加此后缀运算符来工作:
SecondLabel
这使代码编译和工作。虽然感觉有点&#34; hacky&#34; ...
postfix operator ^
postfix func ^<I: Instantiatable>(attributes: [I.Element]) -> I {
return I(values: attributes)
}
如果您对我使用它的原因和方式感兴趣,可以查看启用此语法的my framework ViewComposer:
let custom: MyLabel = [1, 2, 3, 4]^ // note use of caret operator. Now compiles
print(custom.text!) // prints "Sum 10"
但我也希望能够创建自己的Composable
subclass let label: UILabel = [.text("Hello World"), .textColor(.red)]
(或MyLabel
...)
Label
之前没有用,但现在可以使用插入符后缀运算符let myLabel: MyLabel = [.text("Hello World"), .textColor(.red)] // does not compile
,如下所示:
^
现在这是我认为最优雅的解决方案。