Przełączam aplikację z Objective-C na Swift, w której mam kilka kategorii z przechowywanymi właściwościami, na przykład:
@interface UIView (MyCategory)
- (void)alignToView:(UIView *)view
alignment:(UIViewRelativeAlignment)alignment;
- (UIView *)clone;
@property (strong) PFObject *xo;
@property (nonatomic) BOOL isAnimating;
@end
Ponieważ rozszerzenia Swift nie akceptują przechowywanych właściwości, takich jak te, nie wiem, jak zachować tę samą strukturę, co kod Objc. Przechowywane właściwości są naprawdę ważne dla mojej aplikacji i uważam, że firma Apple musiała opracować jakieś rozwiązanie do robienia tego w Swift.
Jak powiedział jou, w rzeczywistości szukałem powiązanych obiektów, więc zrobiłem (w innym kontekście):
import Foundation
import QuartzCore
import ObjectiveC
extension CALayer {
var shapeLayer: CAShapeLayer? {
get {
return objc_getAssociatedObject(self, "shapeLayer") as? CAShapeLayer
}
set(newValue) {
objc_setAssociatedObject(self, "shapeLayer", newValue, UInt(OBJC_ASSOCIATION_RETAIN))
}
}
var initialPath: CGPathRef! {
get {
return objc_getAssociatedObject(self, "initialPath") as CGPathRef
}
set {
objc_setAssociatedObject(self, "initialPath", newValue, UInt(OBJC_ASSOCIATION_RETAIN))
}
}
}
Ale dostaję EXC_BAD_ACCESS, gdy robię:
class UIBubble : UIView {
required init(coder aDecoder: NSCoder) {
...
self.layer.shapeLayer = CAShapeLayer()
...
}
}
Jakieś pomysły?
ios
swift
associated-object
Marcos Duarte
źródło
źródło
Odpowiedzi:
Interfejs API obiektów powiązanych jest nieco kłopotliwy w użyciu. Możesz usunąć większość boilerplate'u za pomocą klasy pomocniczej.
public final class ObjectAssociation<T: AnyObject> { private let policy: objc_AssociationPolicy /// - Parameter policy: An association policy that will be used when linking objects. public init(policy: objc_AssociationPolicy = .OBJC_ASSOCIATION_RETAIN_NONATOMIC) { self.policy = policy } /// Accesses associated object. /// - Parameter index: An object whose associated object is to be accessed. public subscript(index: AnyObject) -> T? { get { return objc_getAssociatedObject(index, Unmanaged.passUnretained(self).toOpaque()) as! T? } set { objc_setAssociatedObject(index, Unmanaged.passUnretained(self).toOpaque(), newValue, policy) } } }
Pod warunkiem, że możesz „dodać” właściwość do klasy goal-c w bardziej czytelny sposób:
extension SomeType { private static let association = ObjectAssociation<NSObject>() var simulatedProperty: NSObject? { get { return SomeType.association[self] } set { SomeType.association[self] = newValue } } }
Co do rozwiązania:
extension CALayer { private static let initialPathAssociation = ObjectAssociation<CGPath>() private static let shapeLayerAssociation = ObjectAssociation<CAShapeLayer>() var initialPath: CGPath! { get { return CALayer.initialPathAssociation[self] } set { CALayer.initialPathAssociation[self] = newValue } } var shapeLayer: CAShapeLayer? { get { return CALayer.shapeLayerAssociation[self] } set { CALayer.shapeLayerAssociation[self] = newValue } } }
źródło
NSNumber
LubNSValue
i napisać dodatkową parę akcesorów, które byłyby pożądanego typu (Int, Bool, itp.).NSObjectProtocol
[String]?
jest strukturą, to jest ten sam przypadek co dla Int, Bool. Możesz użyćNSArray
do przechowywania kolekcjiNSString
instancji.Podobnie jak w przypadku Objective-C, nie można dodawać właściwości przechowywanych do istniejących klas. Jeśli rozszerzasz klasę Objective-C (
UIView
zdecydowanie jedną), nadal możesz używać Skojarzonych obiektów do emulacji przechowywanych właściwości:dla Swift 1
import ObjectiveC private var xoAssociationKey: UInt8 = 0 extension UIView { var xo: PFObject! { get { return objc_getAssociatedObject(self, &xoAssociationKey) as? PFObject } set(newValue) { objc_setAssociatedObject(self, &xoAssociationKey, newValue, objc_AssociationPolicy(OBJC_ASSOCIATION_RETAIN)) } } }
Klucz asocjacji to wskaźnik, który powinien być unikalny dla każdego powiązania. W tym celu tworzymy prywatną zmienną globalną i używamy jej adresu pamięci jako klucza z
&
operatorem. Zobacz Używanie języka Swift z kakao i Objective-C, aby uzyskać więcej informacji na temat obsługi wskaźników w języku Swift.AKTUALIZACJA dla Swift 2 i 3
import ObjectiveC private var xoAssociationKey: UInt8 = 0 extension UIView { var xo: PFObject! { get { return objc_getAssociatedObject(self, &xoAssociationKey) as? PFObject } set(newValue) { objc_setAssociatedObject(self, &xoAssociationKey, newValue, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN) } } }
AKTUALIZACJA dla Swift 4
W Swift 4 jest to znacznie prostsze. Struktura Holder będzie zawierała prywatną wartość, którą nasza obliczona własność ujawni światu, dając w zamian iluzję zachowania własności przechowywanej.
Źródło
extension UIViewController { struct Holder { static var _myComputedProperty:Bool = false } var myComputedProperty:Bool { get { return Holder._myComputedProperty } set(newValue) { Holder._myComputedProperty = newValue } } }
źródło
objc_AssociationPolicy
, dzięki! Zaktualizowałem odpowiedźWięc myślę, że znalazłem metodę, która działa czyściej niż te powyżej, ponieważ nie wymaga żadnych zmiennych globalnych. Mam to stąd: http://nshipster.com/swift-objc-runtime/
Istota jest taka, że używasz takiej struktury:
extension UIViewController { private struct AssociatedKeys { static var DescriptiveName = "nsh_DescriptiveName" } var descriptiveName: String? { get { return objc_getAssociatedObject(self, &AssociatedKeys.DescriptiveName) as? String } set { if let newValue = newValue { objc_setAssociatedObject( self, &AssociatedKeys.DescriptiveName, newValue as NSString?, UInt(OBJC_ASSOCIATION_RETAIN_NONATOMIC) ) } } } }
UPDATE dla Swift 2
private struct AssociatedKeys { static var displayed = "displayed" } //this lets us check to see if the item is supposed to be displayed or not var displayed : Bool { get { guard let number = objc_getAssociatedObject(self, &AssociatedKeys.displayed) as? NSNumber else { return true } return number.boolValue } set(value) { objc_setAssociatedObject(self,&AssociatedKeys.displayed,NSNumber(bool: value),objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC) } }
źródło
Rozwiązanie wskazane przez jou nie obsługuje typów wartości , działa to również dobrze z nimi
Owijki
import ObjectiveC final class Lifted<T> { let value: T init(_ x: T) { value = x } } private func lift<T>(x: T) -> Lifted<T> { return Lifted(x) } func setAssociatedObject<T>(object: AnyObject, value: T, associativeKey: UnsafePointer<Void>, policy: objc_AssociationPolicy) { if let v: AnyObject = value as? AnyObject { objc_setAssociatedObject(object, associativeKey, v, policy) } else { objc_setAssociatedObject(object, associativeKey, lift(value), policy) } } func getAssociatedObject<T>(object: AnyObject, associativeKey: UnsafePointer<Void>) -> T? { if let v = objc_getAssociatedObject(object, associativeKey) as? T { return v } else if let v = objc_getAssociatedObject(object, associativeKey) as? Lifted<T> { return v.value } else { return nil } }
Możliwe rozszerzenie klasy (przykład użycia):
extension UIView { private struct AssociatedKey { static var viewExtension = "viewExtension" } var referenceTransform: CGAffineTransform? { get { return getAssociatedObject(self, associativeKey: &AssociatedKey.viewExtension) } set { if let value = newValue { setAssociatedObject(self, value: value, associativeKey: &AssociatedKey.viewExtension, policy: objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC) } } } }
To naprawdę świetne rozwiązanie, chciałem dodać kolejny przykład użycia, który zawierał struktury i wartości, które nie są opcjami. Można również uprościć wartości AssociatedKey.
struct Crate { var name: String } class Box { var name: String init(name: String) { self.name = name } } extension UIViewController { private struct AssociatedKey { static var displayed: UInt8 = 0 static var box: UInt8 = 0 static var crate: UInt8 = 0 } var displayed: Bool? { get { return getAssociatedObject(self, associativeKey: &AssociatedKey.displayed) } set { if let value = newValue { setAssociatedObject(self, value: value, associativeKey: &AssociatedKey.displayed, policy: objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC) } } } var box: Box { get { if let result:Box = getAssociatedObject(self, associativeKey: &AssociatedKey.box) { return result } else { let result = Box(name: "") self.box = result return result } } set { setAssociatedObject(self, value: newValue, associativeKey: &AssociatedKey.box, policy: objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC) } } var crate: Crate { get { if let result:Crate = getAssociatedObject(self, associativeKey: &AssociatedKey.crate) { return result } else { let result = Crate(name: "") self.crate = result return result } } set { setAssociatedObject(self, value: newValue, associativeKey: &AssociatedKey.crate, policy: objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC) } } }
źródło
lift
metody iLifted
klasy, powinni jednak używaćgetAssociatedObject
&setAssociatedObject
funkcji. Dla większej przejrzystości dodam „Przykład użycia” w nawiasach obok nagłówka.Nie możesz definiować kategorii (rozszerzeń Swift) z nowym magazynem; wszelkie dodatkowe właściwości muszą być obliczane, a nie przechowywane. Składnia działa dla celu C, ponieważ
@property
w kategorii zasadniczo oznacza „Zapewnię metodę pobierającą i ustawiającą”. W języku Swift musisz zdefiniować je samodzielnie, aby uzyskać obliczoną właściwość; coś jak:extension String { public var Foo : String { get { return "Foo" } set { // What do you want to do here? } } }
Powinno działać dobrze. Pamiętaj, że nie możesz przechowywać nowych wartości w seterze, pracować tylko z istniejącym stanem dostępnej klasy.
źródło
Moje 0,02 $. Ten kod jest napisany w języku Swift 2.0
extension CALayer { private struct AssociatedKeys { static var shapeLayer:CAShapeLayer? } var shapeLayer: CAShapeLayer? { get { return objc_getAssociatedObject(self, &AssociatedKeys.shapeLayer) as? CAShapeLayer } set { if let newValue = newValue { objc_setAssociatedObject(self, &AssociatedKeys.shapeLayer, newValue as CAShapeLayer?, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC) } } } }
Wypróbowałem wiele rozwiązań i stwierdziłem, że jest to jedyny sposób na rozszerzenie klasy o dodatkowe parametry zmiennych.
źródło
type 'AssociatedKeys' cannot be defined within a protocol extension
Dlaczego warto polegać na środowisku wykonawczym objc? Nie rozumiem. Używając czegoś podobnego do poniższego, uzyskasz prawie identyczne zachowanie właściwości przechowywanej, używając tylko czystego podejścia Swift :
extension UIViewController { private static var _myComputedProperty = [String:Bool]() var myComputedProperty:Bool { get { let tmpAddress = String(format: "%p", unsafeBitCast(self, to: Int.self)) return UIViewController._myComputedProperty[tmpAddress] ?? false } set(newValue) { let tmpAddress = String(format: "%p", unsafeBitCast(self, to: Int.self)) UIViewController._myComputedProperty[tmpAddress] = newValue } } }
źródło
Wolę tworzyć kod w czystym języku Swift i nie polegać na dziedzictwie Objective-C. Z tego powodu napisałem czyste rozwiązanie Swifta, które ma dwie zalety i dwie wady.
Zalety:
Czysty kod Swift
Działa na klasach i uzupełnieniach, a dokładniej na
Any
obiekcieNiedogodności:
Kod powinien wywołać metodę,
willDeinit()
aby zwolnić obiekty połączone z konkretną instancją klasy, aby uniknąć wycieków pamięciNie można utworzyć rozszerzenia bezpośrednio do UIView dla tego dokładnego przykładu, ponieważ
var frame
jest rozszerzeniem do UIView, a nie częścią klasy.EDYTOWAĆ:
import UIKit var extensionPropertyStorage: [NSObject: [String: Any]] = [:] var didSetFrame_ = "didSetFrame" extension UILabel { override public var frame: CGRect { get { return didSetFrame ?? CGRectNull } set { didSetFrame = newValue } } var didSetFrame: CGRect? { get { return extensionPropertyStorage[self]?[didSetFrame_] as? CGRect } set { var selfDictionary = extensionPropertyStorage[self] ?? [String: Any]() selfDictionary[didSetFrame_] = newValue extensionPropertyStorage[self] = selfDictionary } } func willDeinit() { extensionPropertyStorage[self] = nil } }
źródło
extensionPropertyStorage
jest współdzielone ze wszystkimi instancjami zgodnie z projektem. Jest to zmienna globalna (słownik), która najpierw usuwa odwołania do instancji UILabel (NSObject
), a następnie do jej właściwości ([String: Any]
).class
nieruchomości;)frame
jest właściwością instancji. Skąd się bierze własność klasy?Za pomocą kategorii Obj-c można dodawać tylko metody, a nie zmienne instancji.
W swoim przykładzie użyłeś @property jako skrótu do dodawania deklaracji metod pobierających i ustawiających. Nadal musisz wdrożyć te metody.
Podobnie w Swift możesz dodać rozszerzenia użycia, aby dodać metody instancji, właściwości obliczone itp., Ale nie właściwości przechowywane.
źródło
Otrzymuję również problem EXC_BAD_ACCESS. Wartość w
objc_getAssociatedObject()
iobjc_setAssociatedObject()
powinna być obiektem. Iobjc_AssociationPolicy
powinna być zgodna z Object.źródło
Próbowałem użyć objc_setAssociatedObject, jak wspomniano w kilku odpowiedziach tutaj, ale po kilku niepowodzeniach cofnąłem się i zdałem sobie sprawę, że nie ma powodu, aby tego potrzebować. Korzystając z kilku pomysłów tutaj, wymyśliłem ten kod, który po prostu przechowuje tablicę moich dodatkowych danych (w tym przykładzie MyClass) indeksowanych przez obiekt, z którym chcę je powiązać:
class MyClass { var a = 1 init(a: Int) { self.a = a } } extension UIView { static var extraData = [UIView: MyClass]() var myClassData: MyClass? { get { return UIView.extraData[self] } set(value) { UIView.extraData[self] = value } } } // Test Code: (Ran in a Swift Playground) var view1 = UIView() var view2 = UIView() view1.myClassData = MyClass(a: 1) view2.myClassData = MyClass(a: 2) print(view1.myClassData?.a) print(view2.myClassData?.a)
źródło
Oto uproszczone i bardziej wyraziste rozwiązanie. Działa zarówno w przypadku typów wartości, jak i typów referencyjnych. Podejście do podnoszenia jest zaczerpnięte z odpowiedzi @HepaKKes.
Kod stowarzyszenia:
import ObjectiveC final class Lifted<T> { let value: T init(_ x: T) { value = x } } private func lift<T>(_ x: T) -> Lifted<T> { return Lifted(x) } func associated<T>(to base: AnyObject, key: UnsafePointer<UInt8>, policy: objc_AssociationPolicy = .OBJC_ASSOCIATION_RETAIN, initialiser: () -> T) -> T { if let v = objc_getAssociatedObject(base, key) as? T { return v } if let v = objc_getAssociatedObject(base, key) as? Lifted<T> { return v.value } let lifted = Lifted(initialiser()) objc_setAssociatedObject(base, key, lifted, policy) return lifted.value } func associate<T>(to base: AnyObject, key: UnsafePointer<UInt8>, value: T, policy: objc_AssociationPolicy = .OBJC_ASSOCIATION_RETAIN) { if let v: AnyObject = value as AnyObject? { objc_setAssociatedObject(base, key, v, policy) } else { objc_setAssociatedObject(base, key, lift(value), policy) } }
Przykład użycia:
1) Utwórz rozszerzenie i skojarz z nim właściwości. Użyjmy zarówno właściwości value, jak i typu referencyjnego.
extension UIButton { struct Keys { static fileprivate var color: UInt8 = 0 static fileprivate var index: UInt8 = 0 } var color: UIColor { get { return associated(to: self, key: &Keys.color) { .green } } set { associate(to: self, key: &Keys.color, value: newValue) } } var index: Int { get { return associated(to: self, key: &Keys.index) { -1 } } set { associate(to: self, key: &Keys.index, value: newValue) } } }
2) Teraz możesz używać zwykłych właściwości:
let button = UIButton() print(button.color) // UIExtendedSRGBColorSpace 0 1 0 1 == green button.color = .black print(button.color) // UIExtendedGrayColorSpace 0 1 == black print(button.index) // -1 button.index = 3 print(button.index) // 3
Więcej szczegółów:
źródło
Kolejny przykład użycia obiektów powiązanych z Objective-C i obliczonych właściwości dla Swift 3 i Swift 4
import CoreLocation extension CLLocation { private struct AssociatedKeys { static var originAddress = "originAddress" static var destinationAddress = "destinationAddress" } var originAddress: String? { get { return objc_getAssociatedObject(self, &AssociatedKeys.originAddress) as? String } set { if let newValue = newValue { objc_setAssociatedObject( self, &AssociatedKeys.originAddress, newValue as NSString?, .OBJC_ASSOCIATION_RETAIN_NONATOMIC ) } } } var destinationAddress: String? { get { return objc_getAssociatedObject(self, &AssociatedKeys.destinationAddress) as? String } set { if let newValue = newValue { objc_setAssociatedObject( self, &AssociatedKeys.destinationAddress, newValue as NSString?, .OBJC_ASSOCIATION_RETAIN_NONATOMIC ) } } } }
źródło
Jeśli chcesz ustawić niestandardowy atrybut ciągu na UIView, tak zrobiłem to w Swift 4
Utwórz rozszerzenie UIView
extension UIView { func setStringValue(value: String, key: String) { layer.setValue(value, forKey: key) } func stringValueFor(key: String) -> String? { return layer.value(forKey: key) as? String } }
Aby użyć tego rozszerzenia
let key = "COLOR" let redView = UIView() // To set redView.setStringAttribute(value: "Red", key: key) // To read print(redView.stringValueFor(key: key)) // Optional("Red")
źródło
Co powiesz na przechowywanie mapy statycznej do klasy, która rozszerza się w ten sposób:
extension UIView { struct Holder { static var _padding:[UIView:UIEdgeInsets] = [:] } var padding : UIEdgeInsets { get{ return UIView.Holder._padding[self] ?? .zero} set { UIView.Holder._padding[self] = newValue } } }
źródło
Próbowałem przechowywać właściwości przy użyciu objc_getAssociatedObject, objc_setAssociatedObject, bez powodzenia. Moim celem było stworzenie rozszerzenia dla UITextField, aby sprawdzić długość znaków wprowadzania tekstu. Poniższy kod działa dobrze dla mnie. Mam nadzieję, że to komuś pomoże.
private var _min: Int? private var _max: Int? extension UITextField { @IBInspectable var minLength: Int { get { return _min ?? 0 } set { _min = newValue } } @IBInspectable var maxLength: Int { get { return _max ?? 1000 } set { _max = newValue } } func validation() -> (valid: Bool, error: String) { var valid: Bool = true var error: String = "" guard let text = self.text else { return (true, "") } if text.characters.count < minLength { valid = false error = "Textfield should contain at least \(minLength) characters" } if text.characters.count > maxLength { valid = false error = "Textfield should not contain more then \(maxLength) characters" } if (text.characters.count < minLength) && (text.characters.count > maxLength) { valid = false error = "Textfield should contain at least \(minLength) characters\n" error = "Textfield should not contain more then \(maxLength) characters" } return (valid, error) } }
źródło
_min
i_max
są one globalne i będą takie same we wszystkich wystąpieniach UITextField? Nawet jeśli to zadziała, ta odpowiedź nie ma związku, ponieważ Marcos pyta o zmienne instancji .Oto alternatywa, która również działa
public final class Storage : AnyObject { var object:Any? public init(_ object:Any) { self.object = object } } extension Date { private static let associationMap = NSMapTable<NSString, AnyObject>() private struct Keys { static var Locale:NSString = "locale" } public var locale:Locale? { get { if let storage = Date.associationMap.object(forKey: Keys.Locale) { return (storage as! Storage).object as? Locale } return nil } set { if newValue != nil { Date.associationMap.setObject(Storage(newValue), forKey: Keys.Locale) } } } } var date = Date() date.locale = Locale(identifier: "pt_BR") print( date.locale )
źródło
Uważam, że to rozwiązanie jest bardziej praktyczne
AKTUALIZACJA dla Swift 3
extension UIColor { static let graySpace = UIColor.init(red: 50/255, green: 50/255, blue: 50/255, alpha: 1.0) static let redBlood = UIColor.init(red: 102/255, green: 0/255, blue: 0/255, alpha: 1.0) static let redOrange = UIColor.init(red: 204/255, green: 17/255, blue: 0/255, alpha: 1.0) func alpha(value : CGFloat) -> UIColor { var r = CGFloat(0), g = CGFloat(0), b = CGFloat(0), a = CGFloat(0) self.getRed(&r, green: &g, blue: &b, alpha: &a) return UIColor(red: r, green: g, blue: b, alpha: value) } }
... potem w swoim kodzie
class gameController: UIViewController { @IBOutlet var game: gameClass! override func viewDidLoad() { self.view.backgroundColor = UIColor.graySpace } }
źródło