`ExpressibleByArrayLiteral`符合类及其超类=&gt; &#34; <superclass>不能转换为<subclass>&#34;

时间:2017-06-06 14:48:44

标签: ios swift uiview swift3 swift-protocols

我希望能够使用数组文字实例化一个子类,这里名为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。但这没什么区别。

请说请注意,这是可能的。

2 个答案:

答案 0 :(得分:7)

经过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 

事实证明,我们甚至无法在元素类型类(字符串,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需要“必要的”&#39;初始化方法符合它,正如编译器所说:你不能直接在你希望自定义的超类的扩展内部实现。不幸的是,单凭这个逻辑......你想要的方法是有缺陷的。

二。你想要的非常具体的初始化程序&quot; init(arrayLiteral :),用于最终类型的类。就像在你的标识中使用关键字FINAL标记的类的声明标题,或类TYPES(字符串是一个,数字类也是如此)。 UILabel根本不是允许子类化的最终类,你不能在不破坏语言的情况下改变它。为了说明非final和final,尝试子类String,您将收到错误,因为它不是classprotocol。无论如何都无法通过App Store。因此,通过DESIGN,您无法通过扩展名在UILabel上使用此方法。

三。您采用自定义协议方法,并尝试通过扩展和继承将其应用于UILabel。我道歉,但Swift只是不会忽略它的语言结构,因为您在它之间分层一些自定义代码constrtaint的两端。你的protocol方法尽管很优雅,但它只是将问题嵌套在这里,而不是解决问题。这是因为无论您自己的中间件如何,它都会将这些初始化约束重新应用到UILabel上。

四。在这里有一点逻辑思路。如果您查看XCode内部Expressibles内的Apple文档(代码文件本身),您会注意到协议特别适用于RawRepresentable类和类型(Strings,{{1} },Ints等。):

  

对于具有字符串,整数或浮点原始的任何枚举   类型,Swift编译器自动添加Doubles   一致性。定义自己的自定义枚举时,请给它一个   通过将原始类型指定为原始类型的原始类型   枚举的类型继承列表。您也可以使用文字   指定一个或多个案例的值。

因此,任何作为RawRepresentable的根级别的数据表示都可以通过class采用此方法。您可以清楚地看到上面的内容,在将此extension立即添加到protocol时,您还要将UILabel协议强加给它。它不能采用我的本性,我愿意下注是&#34; MyLabel的来源不能投到UILabel&#34;错误。 RawRepresentable不是其中之一,这就是它获取此UILabel属性的原因:它是一个UI元素,它是许多RawRepresentables的组合。因此,你不应该直接初始化一个RawRepresentables集合的类是有道理的,因为如果在non-final class编译器端发生了一些混淆,并且它不会改变Raw类型,那么你就可以了。重新瞄准,它可能只是完全破坏了类实例,并使每个人都陷入了调试的噩梦。

为了说明我尝试制作的init点,以下是将此方法应用于RawRepresentableString - 符合类型时会发生什么:

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,我无法使用提及ExpressibleByArrayLiteralLabel的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 ,如下所示:

^

现在这是我认为最优雅的解决方案。