使用關聯物件實現的協議擴充套件中的屬性

在 Swift 中,協議擴充套件不能具有真正的屬性。

但是,在實踐中,你可以使用關聯物件技術。結果幾乎就像一個真正的屬性。

以下是向協議擴充套件新增關聯物件的確切技術:

從根本上說,你使用 objective-c“objc_getAssociatedObject”和_set 呼叫。

基本呼叫是:

get {
   return objc_getAssociatedObject(self, & _Handle) as! YourType
   }
set {
   objc_setAssociatedObject(self, & _Handle, newValue, .OBJC_ASSOCIATION_RETAIN)
    }

這是一個完整的例子。兩個關鍵點是:

  1. 在協議中,你必須使用“:class”來避免突變問題。

  2. 在擴充套件中,你必須使用“where self:UIViewController”(或任何適當的類)來提供確認型別。

因此,對於示例屬性 p

import Foundation
import UIKit
import ObjectiveC          // don't forget this

var _Handle: UInt8 = 42    // it can be any value

protocol Able: class {
    var click:UIView? { get set }
    var x:CGFloat? { get set }
    // note that you >> do not << declare p here
}

extension Able where Self:UIViewController {
       
    var p:YourType { // YourType might be, say, an Enum
      get {
          return objc_getAssociatedObject(self, & _Handle) as! YourType
          // HOWEVER, SEE BELOW
          }
      set {
          objc_setAssociatedObject(self, & _Handle, newValue, .OBJC_ASSOCIATION_RETAIN)
          // often, you'll want to run some sort of "setter" here...
          __setter()
          }
    }
    
    func __setter() { something = p.blah() }
    
    func someOtherExtensionFunction() { p.blah() }
    // it's ok to use "p" inside other extension functions,
    // and you can use p anywhere in the conforming class
}

在任何符合要求的類中,你現在已經新增了屬性 p

你可以像使用符合類中的任何普通屬性一樣使用 p。例:

class Clock:UIViewController, Able {
    var u:Int = 0

    func blah() {
      u = ...
      ... = u
      // use "p" as you would any normal property
      p = ...
      ... = p
    }

    override func viewDidLoad() {
      super.viewDidLoad()
      pm = .none // "p" MUST be "initialized" somewhere in Clock 
    }
}

注意。你必須初始化偽屬性

Xcode 中不會強制你在貼合類初始化 P

初始化 p 至關重要,可能在確認類的 viewDidLoad 中。

值得記住的是,p 實際上只是一個計算屬性。p 實際上只是兩個函式,帶有語法糖。在任何地方都沒有 p變數:編譯器在任何意義上都不“為 p 分配一些記憶體”。因此,期望 Xcode 強制執行“初始化 p”是沒有意義的。

實際上,為了更準確地說,你必須記住“第一次使用 p,就好像你正在初始化它”。 (同樣,這很可能在你的 viewDidLoad 程式碼中。)

關於這樣的 getter。

請注意,如果在設定 p 的值之前呼叫 getter,它將崩潰

為避免這種情況,請考慮以下程式碼:

    get {
        let g = objc_getAssociatedObject(self, &_Handle)
        if (g == nil) {
            objc_setAssociatedObject(self, &_Handle, _default initial value_, .OBJC_ASSOCIATION)
            return _default initial value_
        }
        return objc_getAssociatedObject(self, &_Handle) as! YourType
        }

重複。Xcode 中不會強制你在貼合類初始化頁。初始化 p 是必要的,比如在符合類的 viewDidLoad 中。

使程式碼更簡單……

你可能希望使用這兩個全域性函式:

func _aoGet(_ ss: Any!, _ handlePointer: UnsafeRawPointer!, _ safeValue: Any!)->Any! {
    let g = objc_getAssociatedObject(ss, handlePointer)
    if (g == nil) {
        objc_setAssociatedObject(ss, handlePointer, safeValue, .OBJC_ASSOCIATION_RETAIN)
        return safeValue
    }
    return objc_getAssociatedObject(ss, handlePointer)  
}

func _aoSet(_ ss: Any!, _ handlePointer: UnsafeRawPointer!, _ val: Any!) {
    objc_setAssociatedObject(ss, handlePointer, val, .OBJC_ASSOCIATION_RETAIN)
}

請注意,除了儲存型別之外,它們不執行任何操作,並使程式碼更具可讀性。 (它們本質上是巨集或行內函數。)

然後你的程式碼變成:

protocol PMable: class {
    var click:UILabel? { get set } // ordinary properties here
}

var _pHandle: UInt8 = 321

extension PMable where Self:UIViewController {

    var p:P {
        get {
            return _aoGet(self, &_pHandle, P() ) as! P
        }
        set {
            _aoSet(self, &_pHandle, newValue)
            __pmSetter()
        }
    }
    
    func __pmSetter() {
        click!.text = String(p)
    }
    
    func someFunction() {
        p.blah()
    }
}

(在_aoGet 的示例中,P 是初始化的:而不是 P(),你可以使用“”,0 或任何預設值。)