Protocol conformance with implicitly unwrapped optionals
Asked Answered
P

1

9

I am trying to make a Swift protocol that I can use on UILabel, UITextField, and UITextView that incorporate their text, attributedText, and font properties.

However, unfortunately these three classes are inconsistent with whether they use optional types for these properties or implicitly unwrapped optionals.

For example, if I create this protocol:

protocol MyProtocol: class {
    var font: UIFont? { get set }
}

And I apply it:

extension UILabel: MyProtocol { }
extension UITextField: MyProtocol { }
extension UITextView: MyProtocol { }

It works fine for UITextField and UITextView but UILabel's font property is UIFont! and so the compiler says UILabel doesn't conform to MyProtocol.

Additionally text and attributedText are optional (String?) for UILabel and UITextField but implicitly unwrapped for UITextView (String!). So it's not even consistent which ones use optionals and which ones use implicitly unwrapped optionals for all three properties.

So then I've had to rename font in the protocol to eg. uiFont as essentially an alias for font with the following implementation in each of the extensions above:

extension UILabel: MyProtocol {
    var uiFont: UIFont? {
        get { font }
        set { font = newValue }
    }
} 
// … and similarly for UITextField and UITextView

This is a bit annoying as it takes away from the simplicity of the protocol.

I found this post on the Swift forum that seems to be the same issue and the discussion seems to say this is not how it's supposed to behave in Swift 4.2, but I am using Swift 5 and still getting this. There was even a proposal to abolish IUOs that got merged.

Note I am using Xcode 11.7 with iOS 13.7 on macOS Catalina 10.15.6 (19G2021).

Is there some way to avoid this problem altogether, or perhaps to make the code a bit cleaner so I don't need to have as much redundancy?

Thanks

Proteose answered 3/9, 2020 at 23:10 Comment(3)
Excellent summary. I’d suggest filing a bug. We are always told that an implicitly unwrapped Optional just “is” an Optional so why should it get in your way like this? (And why are there any IUOs left in the APIs anyway?Liss
bugs.swift.org/browse/SR-13501Proteose
Guess it's not really a bug (see bug at swift.org above).Proteose
B
4

Although it seems like a bug in the Swift, you can extend the protocol itself to make it work:

extension UILabel: MyProtocol { }
extension MyProtocol where Self: UILabel {
    var font: UIFont? {
        get { self.font ?? nil }
        set { self.font = newValue }
    }
}
Busra answered 3/9, 2020 at 23:44 Comment(2)
Great! This reduces the workaround code a lot, since now I only need to add extensions for UILabel and UITextView. Didn't realize you could do that where you use the same property name without a circular reference.Proteose
As the commenter on the Swift bug tracker points out however, this is a bit detrimental to readability / debugging. When you call font on an instance of MyProtocol that is implicitly a UILabel or an explicit instance of UILabel it's not entirely clear whether you're accessing this alias of font or the actual property. In this case it's somewhat harmless but it's probably worth being careful with this kind of approach.Proteose

© 2022 - 2024 — McMap. All rights reserved.