Как сохранить свойства в Swift, так же, как я имел на Objective-C?
я переключаю приложение с Objective-C на Swift, в котором у меня есть несколько категорий с сохраненными свойствами, например:
@interface UIView (MyCategory)
- (void)alignToView:(UIView *)view
alignment:(UIViewRelativeAlignment)alignment;
- (UIView *)clone;
@property (strong) PFObject *xo;
@property (nonatomic) BOOL isAnimating;
@end
поскольку расширения Swift не принимают сохраненные свойства, подобные этим, я не знаю, как поддерживать ту же структуру, что и код Objc. Сохраненные свойства действительно важны для моего приложения, и я считаю, что Apple, должно быть, создала какое-то решение для этого в Swift.
как сказал Джоу, то, что я искал, на самом деле использовало связанные объекты, так что я сделал (в другом контексте):
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))
}
}
}
но я получаю EXC_BAD_ACCESS при выполнении:
class UIBubble : UIView {
required init(coder aDecoder: NSCoder) {
...
self.layer.shapeLayer = CAShapeLayer()
...
}
}
какие идеи?
16 ответов:
связанные объекты API является немного громоздким в использовании. Вы можете удалить большую часть шаблона с помощью вспомогательного класса.
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) } } }при условии, что вы можете "добавить" свойство в класс objective-c более читаемым образом:
extension SomeType { private static let association = ObjectAssociation<NSObject>() var simulatedProperty: NSObject? { get { return SomeType.association[self] } set { SomeType.association[self] = newValue } } }
как и в Objective-C, вы не можете добавить сохраненное свойство к существующим классам. Если вы расширяете класс Objective-C (
UIViewопределенно), вы все еще можете использовать Связанные Объекты для эмуляции сохраненных свойств:для 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)) } } }ключевые ассоциации-это указатель, который должен быть уникальным для каждой ассоциации. Для этого мы создаем закрытую глобальную переменную и используем ее адрес памяти в качестве ключа с
&оператор. Смотрите использование Swift с какао и Objective-C более подробно о том, как указатели обрабатываются в Swift.обновлено для Swift 2 и 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) } } }
поэтому я думаю, что нашел метод, который работает чище, чем те, что выше, потому что он не требует никаких глобальных переменных. Я получил его отсюда: http://nshipster.com/swift-objc-runtime/
суть в том, что вы используете структуру вот так:
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) ) } } } }обновление для 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) } }
решение указал jou не поддерживает типы значений, это прекрасно работает с ними, а также
фантики
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 } }возможный расширение класс (пример использования):
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) } } } }это действительно такое отличное решение, я хотел бы добавить еще один пример использования, который включал структуры и значения, которые не являются optionals. Кроме того, значения 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) } } }
вы не можете определить категории (расширения Swift) с новым хранилищем; любые дополнительные свойства должны быть вычисляемые вместо того, чтобы хранить. Синтаксис работает для Objective C, потому что
@propertyв категории по существу означает "я предоставлю геттер и сеттер". В Swift вам нужно будет определить их самостоятельно, чтобы получить вычисляемое свойство; что-то вроде:extension String { public var Foo : String { get { return "Foo" } set { // What do you want to do here? } } }должно работать нормально. Помните, что вы не можете хранить новые значения в сеттере, а только работать с существующими доступное состояние класса.
мои $0.02. Этот код написан в 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) } } } }Я пробовал много решений, и обнаружил, что это единственный способ фактически расширить класс с дополнительными переменными параметрами.
Я предпочитаю делать код в чистом Swift и не полагаться на Objective-C heritage. Из-за этого я написал чисто быстрое решение с двумя плюсами и двумя минусами.
плюсы:
чистый Swift код
работает на классах и завершениях или более конкретно на
Anyобъектнедостатки:
код должен вызвать метод
willDeinit()чтобы освободить объекты, связанные с конкретным экземпляром класса, чтобы избежать утечек памятивы не можете сделать расширение непосредственно в UIView для этого точного примера, потому что
var frameявляется расширением UIView, а не частью класса.EDIT:
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 } }
зачем полагаться на objc runtime? Я не понял. Используя что-то вроде следующего, вы достигнете почти идентичного поведения сохраненного свойства, используя только чистый быстрый подход:
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 } } }
с категориями Obj-c вы можете добавлять только методы, а не переменные экземпляра.
в вашем примере вы использовали @property в качестве ярлыка для добавления Объявлений метода getter и setter. Вам все еще нужно реализовать эти методы.
аналогично в Swift вы можете добавить расширения use для добавления методов экземпляра, вычисляемых свойств и т. д. но не сохраненные свойства.
Я также получаю проблему EXC_BAD_ACCESS.Значение в
objc_getAssociatedObject()иobjc_setAssociatedObject()должен быть объект. А тоobjc_AssociationPolicyдолжен соответствовать объект.
Я попытался использовать objc_setAssociatedObject, как упоминалось в нескольких ответах здесь, но после неудачи с ним несколько раз я отступил и понял, что мне это не нужно. Заимствуя из нескольких идей здесь, я придумал этот код, который просто хранит массив любых моих дополнительных данных (MyClass в этом примере), индексированных объектом, с которым я хочу связать его:
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)
вот упрощенное и более выразительное решение. Он работает как для значений, так и для ссылочных типов. Подход к подъему взят из ответа @HepaKKes.
Ассоциации код:
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) } }пример использования:
1) создать расширение и связать свойства. Давайте использовать свойства как значения, так и ссылочного типа.
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) Теперь вы можете использовать как обычные свойства:
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больше подробности:
- подниматься необходим для оборачивать типы значения.
- по умолчанию поведение объекта сохранить. Если вы хотите узнать больше о связанных объектах, я бы рекомендовал проверить в этой статье.
еще один пример с использованием связанных объектов Objective-C и вычисленных свойств для Swift 3 и 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 ) } } } }
Я попытался сохранить свойства с помощью objc_getAssociatedObject, objc_setAssociatedObject, без каких-либо успехов. Моей целью было создать расширение для UITextField, чтобы проверить длину символов ввода текста. Следующий код отлично работает для меня. Надеюсь, это кому-то поможет.
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) } }
вот альтернатива, которая также работает
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 )
Я нашел это решение более практично
обновлено для 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) } }...тогда в вашем коде
class gameController: UIViewController { @IBOutlet var game: gameClass! override func viewDidLoad() { self.view.backgroundColor = UIColor.graySpace } }
Comments