Is there a way to set associated objects in Swift?

SwiftObjective CAssociated Object

Swift Problem Overview


Coming from Objective-C you can call function objc_setAssociatedObject between 2 objects to have them maintain a reference, which can be handy if at runtime you don't want an object to be destroyed until its reference is removed also. Does Swift have anything similar to this?

Swift Solutions


Solution 1 - Swift

Here is a simple but complete example derived from jckarter's answer.

It shows how to add a new property to an existing class. It does it by defining a computed property in an extension block. The computed property is stored as an associated object:

import ObjectiveC

// Declare a global var to produce a unique address as the assoc object handle
private var AssociatedObjectHandle: UInt8 = 0

extension MyClass {
	var stringProperty:String {
		get {
			return objc_getAssociatedObject(self, &AssociatedObjectHandle) as! String
		}
		set {
			objc_setAssociatedObject(self, &AssociatedObjectHandle, newValue, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC)
		}
	}
}

EDIT:

If you need to support getting the value of an uninitialized property and to avoid getting the error unexpectedly found nil while unwrapping an Optional value, you can modify the getter like this:

	get {
		return objc_getAssociatedObject(self, &AssociatedObjectHandle) as? String ?? ""
	}

Solution 2 - Swift

The solution supports all the value types as well, and not only those that are automagically bridged, such as String, Int, Double, etc.

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

Solution 3 - Swift

I wrote a modern wrapper available at https://github.com/b9swift/AssociatedObject

You may be surprised that it even supports Swift structures for free.

Swift struct association

Solution 4 - Swift

Obviously, this only works with Objective-C objects. After fiddling around with this a bit, here's how to make the calls in Swift:

import ObjectiveC

// Define a variable whose address we'll use as key.
// "let" doesn't work here.
var kSomeKey = "s"



func someFunc() {
    objc_setAssociatedObject(target, &kSomeKey, value, UInt(OBJC_ASSOCIATION_RETAIN))

    let value : AnyObject! = objc_getAssociatedObject(target, &kSomeKey)
}

Solution 5 - Swift

Update in Swift 3.0 For example this is a UITextField

import Foundation
import UIKit
import ObjectiveC

// Declare a global var to produce a unique address as the assoc object handle
var AssociatedObjectHandle: UInt8 = 0

extension UITextField
{
    var nextTextField:UITextField {
    get {
        return objc_getAssociatedObject(self, &AssociatedObjectHandle) as! UITextField
    }
    set {
        objc_setAssociatedObject(self, &AssociatedObjectHandle, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
        }
    }
}

Solution 6 - Swift

Klaas answer just for Swift 2.1:

import ObjectiveC

let value = NSUUID().UUIDString
var associationKey: UInt8 = 0
        
objc_setAssociatedObject(parentObject, &associationKey, value, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC)

let fetchedValue = objc_getAssociatedObject(parentObject, &associationKey) as! String

Solution 7 - Swift

Just add #import <objc/runtime.h> on your brindging header file to access objc_setAssociatedObject under swift code

Solution 8 - Swift

The above friend has answered your question, but if it is related to closure properties, please note:


    import UIKit
    public extension UICollectionView {

    typealias XYRearrangeNewDataBlock = (_ newData: [Any]) -> Void
    typealias XYRearrangeOriginaDataBlock = () -> [Any]

    // MARK:- associat key
    private struct xy_associatedKeys {
        static var originalDataBlockKey = "xy_originalDataBlockKey"
        static var newDataBlockKey = "xy_newDataBlockKey"
    }
    
    
    private class BlockContainer {
        var rearrangeNewDataBlock: XYRearrangeNewDataBlock?
        var rearrangeOriginaDataBlock: XYRearrangeOriginaDataBlock?
    }

    
    private var newDataBlock: BlockContainer? {
        get {
            if let newDataBlock = objc_getAssociatedObject(self, &xy_associatedKeys.newDataBlockKey) as? BlockContainer {
                return newDataBlock
            }
            return nil
        }
        
        set(newValue) {
            objc_setAssociatedObject(self, xy_associatedKeys.newDataBlockKey, newValue, .OBJC_ASSOCIATION_COPY_NONATOMIC)
        }
    }
    convenience init(collectionVewFlowLayout : UICollectionViewFlowLayout, originalDataBlock: @escaping XYRearrangeOriginaDataBlock, newDataBlock:  @escaping XYRearrangeNewDataBlock) {
        self.init()
        
        
        let blockContainer: BlockContainer = BlockContainer()
        blockContainer.rearrangeNewDataBlock = newDataBlock
        blockContainer.rearrangeOriginaDataBlock = originalDataBlock
        self.newDataBlock = blockContainer
    }

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionamleszkView Question on Stackoverflow
Solution 1 - SwiftKlaasView Answer on Stackoverflow
Solution 2 - SwiftHepaKKesView Answer on Stackoverflow
Solution 3 - SwiftBB9zView Answer on Stackoverflow
Solution 4 - SwiftDarkDustView Answer on Stackoverflow
Solution 5 - Swiftflame3View Answer on Stackoverflow
Solution 6 - SwiftRhodanV5500View Answer on Stackoverflow
Solution 7 - SwiftjaumardView Answer on Stackoverflow
Solution 8 - SwiftAlpfaceView Answer on Stackoverflow