2016-08-04 25 views
3

Während es möglich ist, setMyProperty: Methode in obj-c zu ersetzen, frage ich mich, wie es in swift tun?Methode swizzling für Immobilien in Swift

Zum Beispiel möchte ich UIScrollView::setContentOffset: ersetzen:

let originalSelector: Selector = #selector(UIScrollView.setContentOffset) 
let replaceSelector: Selector = #selector(UIScrollView.setContentOffsetHacked) 
... 

... aber nach der Ausführung originalSelectorsetContentOffset:animaed enthält. Also, wie Setter Methode der Eigenschaft an selector übergeben?

+0

Dieser Artikel können Ihnen helfen http://nshipster.com/swift-objc-runtime/ –

+1

Und wenn Sie wollen, dass die nach unten dem Kaninchen-Loch jagen : http://stackoverflow.com/questions/25651081/method-swizzling-in-swift?rq=1 – BaseZen

+0

@VictorSigler dieser Artikel deckt nicht diesen speziellen Fall mit Property Setter. – brigadir

Antwort

2

von Swift Ab 2.3 (XCode 8) ist es möglich, Setter und Getter zuweisen Variable zum Wähler:

Die Objective-C-Selektoren für die Getter oder Setter einer Eigenschaft kann jetzt referenziert werden mit #selector. Zum Beispiel:

let sel: Selector = #selector(setter: UIScrollView.contentOffset) 

Mehr Details here

3

[neu geschrieben, nachdem weitere Forschung]

Hier ist eine aufwendige Abhilfe basierend auf dem unten

http://nshipster.com/swift-objc-runtime/

[WARNUNG von den Autoren]

Abschließend erinnern, dass mit Bastelei Die Objective-C-Laufzeitumgebung sollte eher ein letzter Ausweg als ein Startpunkt sein. Das Ändern der Frameworks, auf denen Ihr Code basiert, sowie der von Ihnen ausgeführte Code von Drittanbietern ist eine schnelle Möglichkeit, den gesamten Stack zu destabilisieren. Trete sanft!

Also hier ist es, müssen alle Accessoren und Mutatoren abgedeckt werden, so ist es eine Menge. Plus, da Sie mit den Werten intervenieren müssen, aber die ursprüngliche gespeicherte Eigenschaft erneut verwenden müssen, da Sie hier keinen neuen Speicher einführen können, haben Sie einige seltsam aussehende Funktionen, die rekursiv zu sein scheinen, aber nicht wegen Laufzeitwechsel . Dies ist das erste Mal, dass der Compiler eine Warnung für meinen Code generiert hat, von dem ich weiß, dass er zur Laufzeit ist.

Naja, es ist eine interessante akademische Übung.

extension UIScrollView { 
    struct StaticVars { 
     static var token: dispatch_once_t = 0 
    } 

    public override class func initialize() { 
     dispatch_once(&StaticVars.token) { 
      guard self == UIScrollView.self else { 
       return 
      } 
      // Accessor 
      method_exchangeImplementations(
       class_getInstanceMethod(self, Selector("swizzledContentOffset")), 
       class_getInstanceMethod(self, Selector("contentOffset")) 
      ) 
      // Two-param setter 
      method_exchangeImplementations(
       class_getInstanceMethod(self, #selector(UIScrollView.setContentOffset(_:animated:))), 
       class_getInstanceMethod(self, #selector(UIScrollView.swizzledSetContentOffset(_:animated:))) 
      ) 
      // One-param setter 
      method_exchangeImplementations(
       class_getInstanceMethod(self, #selector(UIScrollView.swizzledSetContentOffset(_:))), 
       class_getInstanceMethod(self, Selector("setContentOffset:"))) 
     } 
    } 

    func swizzledSetContentOffset(inContentOffset: CGPoint, animated: Bool) { 
     print("Some interceding code for the swizzled 2-param setter with \(inContentOffset)") 
     // This is not recursive. The method implementations have been exchanged by runtime. This is the 
     // original setter that will run. 
     swizzledSetContentOffset(inContentOffset, animated: animated) 
    } 


    func swizzledSetContentOffset(inContentOffset: CGPoint) { 
     print("Some interceding code for the swizzled 1-param setter with \(inContentOffset)") 
     swizzledSetContentOffset(inContentOffset) // not recursive 
    } 


    var swizzledContentOffset: CGPoint { 
     get { 
      print("Some interceding code for the swizzled accessor: \(swizzledContentOffset)") // false warning 
      return swizzledContentOffset // not recursive, false warning 
     } 
    } 
} 
+0

Danke, aber ich muss 'setContentOffset' für' UIScrollView' und seine Unterklassen wirklich ersetzen. Der Grund ist - ich implementiere benutzerdefinierte Pull-to-Refresh-Kontrolle. – brigadir

+0

OK, fertig. Lass mich wissen wie es funktioniert. – BaseZen

+0

Ich sehe 'public var contentOffset: CGPoint' in' UIScrollView' interface - so ist es lesend-schreibend, nicht schreibgeschützt. 'setContentOffset (animiert:)' ist eine eigenständige Methode. Beim Ziehen von scroll-view wird dessen 'contentOffset' über Setter eingestellt, nicht über' setContentOffset: animated'. Sieht so aus, als ob deine Antwort nicht mein Problem behandelt. – brigadir