使用关联对象实现的协议扩展中的属性

在 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 或任何默认值。)