為 protocol 中屬性新增預設值

Natai發表於2019-04-17

首先放個簡單的Playground做下示範。

普通 protocol 中的 get set

protocol中屬性可以宣告為{ get }或者{ get set }。大多數人第一眼肯定覺得{ get }是約束為 read-only 屬性,{ get set }是約束為 read-write 屬性。但是在protocol中這些約束都是最小約束,也就是說{ get }屬性至少需要是 readable 的,至於它是否 writable 並不作要求;{ get set }則明確的指出了屬性必須是可讀寫,但是官方文件說明了,用{ get set }宣告的屬性,不能在遵守該協議的型別中被實現為let屬性。

The getter and setter requirements can be satisfied by a conforming type in a variety of ways. If a property declaration includes both the get and set keywords, a conforming type can implement it with a stored variable property or a computed property that is both readable and writeable (that is, one that implements both a getter and a setter). However, that property declaration can’t be implemented as a constant property or a read-only computed property. If a property declaration includes only the get keyword, it can be implemented as any kind of property. For examples of conforming types that implement the property requirements of a protocol, see Property Requirements.

給屬性設定預設值

給屬性設定預設值?這難道不簡單?

protocol FloatingViewProtocol {
    var isDraggable: Bool { get }
}

extension FloatingViewProtocol {
    var isDraggable: Bool {
        return true
    }
}

class FloatingView: FloatingViewProtocol {
    var isDraggable = false
}
複製程式碼

給一個{ get }屬性提供預設值,只需要在extension中一個return就搞定,確實很簡單,但是這裡出現了個問題,在嘗試給FloatingView物件isAutoAdsorb屬性重新賦值時會報錯,提示isDraggable is a get-only property

所以如果想要重新賦值,則該屬性必須是{ get set }的,可給{ get set }屬性提供預設值也比較尷尬:

protocol FloatingViewProtocol {
    var isDraggable: Bool { get set }
}

extension FloatingViewProtocol {
    var isDraggable: Bool {
        get { return true }
        set { }
    }
}

class FloatingView: FloatingViewProtocol { }
複製程式碼

如上確實提供了預設值,但是set { }實在談不上優雅,並且還有個致命的問題,isDraggable不是個儲存屬性。如果FloatingView在宣告時採用了預設的isDraggable值,那麼給FloatingView物件isAutoAdsorb屬性重新賦值並不會被儲存下來!

話說這個時候,我們是不是該聯想一下屬性和例項變數的關係 :)

class FloatingViewProtocolComponent {
    var isDraggable = true
    public init() {}
}

protocol FloatingViewProtocol {
    var component: FloatingViewProtocolComponent { get }
    var isDraggable: Bool { get set }
}

extension FloatingViewProtocol {
    var isDraggable: Bool {
        get { return component.isDraggable }
        set { component.isDraggable = newValue }
    }
}

class FloatingView: FloatingViewProtocol {
    var component = FloatingViewProtocolComponent()
}
複製程式碼

通過一個component屬性來實現類似例項變數的功能,操作還是有點騷。

實現 component 的 protocol 中的 get set

上面提到蘋果文件指明瞭{ get set }宣告的屬性不能被實現為let。但是因為component提供了預設值,那麼該{ get set }屬性就可以不被實現,同時類可以宣告一個同名同型別的let屬性覆蓋協議中的屬性,就造成了{ get set }屬性可以被宣告為let的假象。

總結一下可以被宣告的屬性型別

  • { get }屬性:
    • read-only 計算屬性
    • read-wirte 計算屬性
    • var 儲存屬性(儲存屬性不能被重新賦值)
    • let 儲存屬性
  • { get set }屬性:
    • read-wirte 計算屬性
    • var 儲存屬性
    • let 儲存屬性(需要用 component 類似的東西提供預設值)

相關文章