Swift Language Proprietà, in un'estensione del protocollo, ottenuta utilizzando l'oggetto associato.


Esempio

In Swift, le estensioni del protocollo non possono avere proprietà reali.

Tuttavia, in pratica puoi usare la tecnica "oggetto associato". Il risultato è quasi esattamente come una proprietà "reale".

Ecco la tecnica esatta per aggiungere un "oggetto associato" a un'estensione di protocollo:

Fondamentalmente, si utilizza l'obiettivo-c "objc_getAssociatedObject" e _set calls.

Le chiamate di base sono:

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

Ecco un esempio completo. I due punti critici sono:

  1. Nel protocollo, è necessario utilizzare ": class" per evitare il problema di mutazione.

  2. Nell'estensione, è necessario utilizzare "where Self: UIViewController" (o qualsiasi altra classe appropriata) per fornire il tipo di conferma.

Quindi, per una proprietà di esempio "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
}

In qualsiasi classe conforme, ora hai "aggiunto" la proprietà "p":

Puoi usare "p" proprio come useresti qualsiasi proprietà ordinaria nella classe conforme. Esempio:

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 
    }
}

Nota. DEVI inizializzare la pseudo proprietà.

Xcode non imporrà l'inizializzazione di "p" nella classe conforme.

È essenziale inizializzare "p", magari in viewDidLoad della classe di conferma.

Vale la pena ricordare che p è in realtà solo una proprietà calcolata . p è in realtà solo due funzioni, con zucchero sintattico. Non c'è p "variabile" da nessuna parte: il compilatore non "assegna un po 'di memoria per p" in alcun senso. Per questo motivo, non ha senso aspettarsi che Xcode imponga "l'inizializzazione p".

In effetti, per parlare in modo più accurato, è necessario ricordare di "usare p per la prima volta, come se si stesse inizializzando". (Anche in questo caso, molto probabilmente sarebbe nel tuo codice viewDidLoad.)

Per quanto riguarda il getter in quanto tale.

Notare che si bloccherà se il getter viene chiamato prima che sia impostato un valore per "p".

Per evitare ciò, considera il codice come:

    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
        }

Ripetere. Xcode non imporrà l'inizializzazione di p nella classe conforme. È essenziale inizializzare p, ad esempio in viewDidLoad della classe conforme.

Rendere il codice più semplice ...

Potresti voler utilizzare queste due funzioni globali:

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)
}

Si noti che non fanno nulla, tranne che per salvare la digitazione e rendere il codice più leggibile. (Sono essenzialmente macro o funzioni inline.)

Il tuo codice diventa quindi:

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()
    }
}

(Nell'esempio su _aoGet, P è initalizable: invece di P () potresti usare "", 0, o qualsiasi valore predefinito.)