我希望能够使用数组文字实例化一个名为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,由于它不是classprotocol,因此会出现错误。但这绝对不会让您通过App Store。因此,通过DESIGN,您无法通过扩展在UILabel本身上使用此方法。

三。您采用了自定义协议(protocol)的方法,并尝试通过扩展和继承将其应用于UILabel。我确实表示歉意,但是Swift并不会忽略它的语言结构,仅仅是因为您在结构的两端之间添加了一些自定义代码。您的protocol方法虽然很优雅,但只是将问题嵌套在这里,没有解决。这是因为它只是将这些初始化约束重新应用到UILabel上,而不管您自己的中间件如何。

第四。在这里有些逻辑上的思路。如果您查看XCode内Expressibles上的Apple Docs(代码文件本身),则会注意到该协议(protocol)尤其适用于RawRepresentable类和类型(StringsIntsDoubles等):



因此,任何作为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的构建方式似乎存在一些您无法规避的东西。我不是唯一肯定这一结论的人(只需检查链接),所以我请您删除您的否决票。您已经提供了一个解决方案,已经为您提供了证明我的观点的引用,并且我还提供了代码以向您展示如何解决这种语言性质的约束。有时只是无法解决所需的方法。在其他时候,另一种方法是解决问题的唯一方法。

10-04 23:19
查看更多