ios 如何在 Swift 中存储属性,就像我在 Objective-C 上一样?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/25426780/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
How to have stored properties in Swift, the same way I had on Objective-C?
提问by Marcos Duarte
I am switching an application from Objective-C to Swift, which I have a couple of categories with stored properties, for example:
我正在将一个应用程序从 Objective-C 切换到 Swift,我有几个带有存储属性的类别,例如:
@interface UIView (MyCategory)
- (void)alignToView:(UIView *)view
alignment:(UIViewRelativeAlignment)alignment;
- (UIView *)clone;
@property (strong) PFObject *xo;
@property (nonatomic) BOOL isAnimating;
@end
As Swift extensions don't accept stored properties like these, I don't know how to maintain the same structure as the Objc code. Stored properties are really important for my app and I believe Apple must have created some solution for doing it in Swift.
由于 Swift 扩展不接受这样的存储属性,我不知道如何维护与 Objc 代码相同的结构。存储属性对我的应用程序非常重要,我相信 Apple 一定已经创建了一些解决方案来在 Swift 中执行此操作。
As said by jou, what I was looking for was actually using associated objects, so I did (in another context):
正如 jou 所说,我正在寻找的实际上是使用关联对象,所以我做了(在另一个上下文中):
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))
}
}
}
But I get an EXC_BAD_ACCESS when doing:
但是在执行以下操作时我得到了 EXC_BAD_ACCESS:
class UIBubble : UIView {
required init(coder aDecoder: NSCoder) {
...
self.layer.shapeLayer = CAShapeLayer()
...
}
}
Any ideas?
有任何想法吗?
采纳答案by Wojciech Nagrodzki
Associated objects API is a bit cumbersome to use. You can remove most of the boilerplate with a helper class.
关联对象 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) }
}
}
Provided that you can "add" a property to objective-c class in a more readable manner:
假设您可以以更易读的方式“添加”一个属性到objective-c 类:
extension SomeType {
private static let association = ObjectAssociation<NSObject>()
var simulatedProperty: NSObject? {
get { return SomeType.association[self] }
set { SomeType.association[self] = newValue }
}
}
As for the solution:
至于解决方案:
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 }
}
}
回答by jou
As in Objective-C, you can't add stored property to existing classes. If you're extending an Objective-C class (UIView
is definitely one), you can still use Associated Objectsto emulate stored properties:
与在 Objective-C 中一样,您不能向现有类添加存储属性。如果您正在扩展一个 Objective-C 类(UIView
绝对是一个),您仍然可以使用关联对象来模拟存储的属性:
for Swift 1
对于 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))
}
}
}
The association key is a pointer that should be the unique for each association. For that, we create a private global variable and use it's memory address as the key with the &
operator. See the Using Swift with Cocoa and Objective-C
on more details how pointers are handled in Swift.
关联键是一个指针,对于每个关联应该是唯一的。为此,我们创建了一个私有全局变量,并使用它的内存地址作为&
操作符的键。有关如何在 Swift 中处理指针的更多详细信息,请参阅将Swift 与 Cocoa 和 Objective-C 一起使用
。
UPDATED for Swift 2 and 3
为 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)
}
}
}
UPDATED for Swift 4
为 Swift 4 更新
In Swift 4, it's much more simple. The Holder struct will contain the private value that our computed property will expose to the world, giving the illusion of a stored property behaviour instead.
在 Swift 4 中,它要简单得多。Holder 结构将包含我们的计算属性将向世界公开的私有值,从而产生存储属性行为的错觉。
extension UIViewController {
struct Holder {
static var _myComputedProperty:Bool = false
}
var myComputedProperty:Bool {
get {
return Holder._myComputedProperty
}
set(newValue) {
Holder._myComputedProperty = newValue
}
}
}
回答by AlexK
So I think I found a method that works cleaner than the ones above because it doesn't require any global variables. I got it from here: http://nshipster.com/swift-objc-runtime/
所以我想我找到了一种比上面的方法更简洁的方法,因为它不需要任何全局变量。我从这里得到它:http: //nshipster.com/swift-objc-runtime/
The gist is that you use a struct like so:
要点是您使用这样的结构:
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 for Swift 2
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)
}
}
回答by HepaKKes
The solution pointed out by joudoesn't support value types, this works fine with them as well
jou指出的解决方案不支持值类型,这也适用于它们
Wrappers
包装纸
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
}
}
A possible Class extension(Example of usage):
一个可能的 类扩展(使用示例):
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)
}
}
}
}
This is really such a great solution, I wanted to add another usage example that included structs and values that are not optionals. Also, the AssociatedKey values can be simplified.
这真的是一个很棒的解决方案,我想添加另一个使用示例,其中包含非可选的结构和值。此外,可以简化 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)
}
}
}
回答by Adam Wright
You can't define categories (Swift extensions) with new storage; any additional properties must be computedrather than stored. The syntax works for Objective C because @property
in a category essentially means "I'll provide the getter and setter". In Swift, you'll need to define these yourself to get a computed property; something like:
你不能用新的存储定义类别(Swift 扩展);任何额外的属性都必须计算而不是存储。该语法适用于 Objective C,因为@property
在类别中本质上意味着“我将提供 getter 和 setter”。在 Swift 中,您需要自己定义这些以获取计算属性;就像是:
extension String {
public var Foo : String {
get
{
return "Foo"
}
set
{
// What do you want to do here?
}
}
}
Should work fine. Remember, you can't store new values in the setter, only work with the existing available class state.
应该工作正常。请记住,您不能在 setter 中存储新值,只能使用现有的可用类状态。
回答by Amro Shafie
My $0.02. This code is written in Swift 2.0
我的 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)
}
}
}
}
I have tried many solutions, and found this is the only way to actually extend a class with extra variable parameters.
我尝试了很多解决方案,发现这是实际扩展具有额外变量参数的类的唯一方法。
回答by valvoline
Why relying on objc runtime? I don't get the point. By using something like the following you will achieve almost the identical behaviour of a stored property, by using only a pure Swift approach:
为什么要依赖 objc 运行时?我不明白重点。通过使用类似以下内容,您将实现与存储属性几乎相同的行为,仅使用纯 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
}
}
}
回答by vedrano
I prefer doing code in pure Swift and not rely on Objective-C heritage. Because of this I wrote pure Swift solution with two advantages and two disadvantages.
我更喜欢用纯 Swift 编写代码,而不是依赖于 Objective-C 的传统。因此,我编写了具有两个优点和两个缺点的纯 Swift 解决方案。
Advantages:
好处:
Pure Swift code
Works on classes and completions or more specifically on
Any
object
纯 Swift 代码
适用于类和完成或更具体的
Any
对象
Disadvantages:
缺点:
Code should call method
willDeinit()
to release objects linked to specific class instance to avoid memory leaksYou cannot make extension directly to UIView for this exact example because
var frame
is extension to UIView, not part of class.
代码应该调用方法
willDeinit()
来释放链接到特定类实例的对象以避免内存泄漏对于这个确切的示例,您不能直接
var frame
扩展到 UIView,因为它是对 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
}
}
回答by Mike Pollard
With Obj-c Categories you can only add methods, not instance variables.
使用 Obj-c Categories 你只能添加方法,不能添加实例变量。
In you example you have used @property as a shortcut to adding getter and setter method declarations. You still need to implement those methods.
在您的示例中,您使用 @property 作为添加 getter 和 setter 方法声明的快捷方式。您仍然需要实现这些方法。
Similarly in Swift you can add use extensions to add instance methods, computed properties etc. but not stored properties.
同样,在 Swift 中,您可以添加使用扩展来添加实例方法、计算属性等,但不能添加存储属性。
回答by RuiKQ
I also get an EXC_BAD_ACCESS problem.The value in objc_getAssociatedObject()
and objc_setAssociatedObject()
should be an Object. And the objc_AssociationPolicy
should match the Object.
我也遇到了 EXC_BAD_ACCESS 问题。中的值objc_getAssociatedObject()
并且objc_setAssociatedObject()
应该是一个对象。并且objc_AssociationPolicy
应该匹配对象。