90

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?

Paulo Mattos
  • 18,845
  • 10
  • 77
  • 85
amleszk
  • 6,192
  • 5
  • 38
  • 43
  • 3
    You can use `objc_setAssociatedObject` from Swift: https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/BuildingCocoaApps/index.html – jtbandes Jun 10 '14 at 04:53
  • On this **ten yr old question** pls note it is now very simple, see the recent answer. No need for libraries, includes, objc, voodoo etc! – Fattie Sep 24 '22 at 17:33
  • @Fattie – I don't think [your answer](https://stackoverflow.com/a/73839279/131160) is simpler than [the highest voted answer](https://stackoverflow.com/a/25428409/131160). – jcsahnwaldt Reinstate Monica Apr 02 '23 at 03:51
  • JCS - the highest voted answer is nowadays wrong (the include is wrong, and it's completely wrong to not allow for the nil). Hope it helps – Fattie Apr 03 '23 at 04:06

9 Answers9

145

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 ?? ""
    }
Klaas
  • 22,394
  • 11
  • 96
  • 107
  • Have you been able to set a function that conforms to a typealias in this fashion? – Morkrom Dec 04 '14 at 23:37
  • A function doesn't work for me, and if I wrap the function in an object I get a "segmentation fault". It seems like a limitation currently. – Morkrom Dec 05 '14 at 17:51
  • I tried this with `var property: Int` but it always return `nil`. It said ` unexpectedly found nil while unwrapping an Optional value` . How can I fix this ? Thank you – Pham Hoan Dec 24 '14 at 09:40
  • 1
    @PhamHoan Int is a Swift native type. Try using NSNumber instead. – Klaas Dec 25 '14 at 17:42
  • that's what I did in object-C but it doesn't work in Swift. However, I asked this question and got a pretty nice answer. http://stackoverflow.com/questions/27635117/set-associated-objects-for-literal-value-in-swift – Pham Hoan Dec 26 '14 at 01:15
  • 1
    @PhamHoan I added a solution for getting an uninitialized object. – Klaas Dec 26 '14 at 15:34
  • This doesn't work with value types. *String, Int, Double, etc.* are automatically bridged, but this solution doesn't work in general with **structs** – HepaKKes Apr 15 '15 at 23:09
  • Using the nil coalescing operator (??) is cleaner: `objc_getAssociatedObject(self, &AssociatedObjectHandle) ?? ""` – Christopher Swasey Aug 04 '15 at 18:20
  • @ChristopherSwasey yes, you are right, but as an answer on SO I prefer to make it more readable for novice Swift programmers. – Klaas Aug 04 '15 at 18:27
  • Feel free to reverse my edit if you'd like. I think the verbosity actually confuses what's going on, imo. – Christopher Swasey Aug 04 '15 at 18:33
  • Just to be sure I understand the purpose of associated objects. It allows you to set the value of a computed property in an extension as you wouldn't be able to do it with just a normal computed property? – Nico Aug 07 '15 at 05:37
  • Yes I read it and I didn't really understand their purpose at first. I got it now . Thanks for the link anyway, might help some people – Nico Aug 07 '15 at 07:58
  • @Klaas - What is the purpose of setting the AssociatedObjectHandle's type to UInt8? Since the address of the variable is being used, wouldn't a normal (inferred) Int work? – Jawwad Feb 10 '16 at 19:24
  • 1
    @Jawwad I guess, it does make no difference. The `UInt8` comes from the linked original thread. – Klaas Feb 10 '16 at 19:44
  • OBJC_ASSOCIATION_RETAIN_NONATOMIC should be OBJC_ASSOCIATION_COPY_NONATOMIC since operating String? – LiangWang Apr 28 '16 at 23:28
  • 1
    hey @Jacky - what makes you say it should be COPY .. please let us know! – Fattie Jan 23 '17 at 01:45
  • I tried this with var property: UITextField but it always return nil. It said ` unexpectedly found nil while unwrapping an Optional value` . How can I fix this ? Thank you – Steve Gear May 12 '18 at 07:10
  • @Klass I tried this with var stringProperty:UITextField { get { return objc_getAssociatedObject(self, &AssociatedObjectHandle) as! UITextField } set { objc_setAssociatedObject(self, &AssociatedObjectHandle, newValue, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC) } }for associating but it always return nil. It said ` unexpectedly found nil while unwrapping an Optional value` . How can I fix this ? Thank you – Steve Gear May 12 '18 at 11:33
  • @SteveGear Are you sure, that you are a) setting a non-nil value before accessing it? b) the exception is thrown in the getter? – Klaas May 13 '18 at 17:06
  • @Klass yes I just used the answer you provided above and just using UITextField instead of String...but getting crash. – Steve Gear May 13 '18 at 17:24
  • @SteveGear Have you tried it with a String and it worked? – Klaas May 13 '18 at 19:35
31

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)
            }
        }
    }
}
HepaKKes
  • 1,555
  • 13
  • 22
  • Can you please explain me what does those association flags mean? I actually am trying to make nav bar scroll with scrollView so am storing scrollView in extension. Which flags should I use? – meteors Oct 02 '15 at 13:37
  • 1
    Also I'm trying to store an array of [AnyObject] using this method but the getter returns me nil always. Somehow the else if of get is not returning properly the value. – meteors Oct 02 '15 at 19:03
  • The kind of associativity policy you decide to use is up to you, it depends on your program. For further information, I suggest you to check this article, http://nshipster.com/associated-objects/, out. I've tried to store arrays, it seems to be working just fine. Which Swift version are you using by the way? – HepaKKes Oct 03 '15 at 14:50
  • This is brilliant. For Swift 2.3 I ended up moving the Generics from the Lift and using `Any` instead. I'll do a blog article about this, I think – Dan Rosenstark Nov 10 '16 at 17:38
  • 5
    This got a lot easier in Swift 3. But anyway, here's myarticle based on your answer here: http://www.yar2050.com/2016/11/associated-object-support-for-swift-23.html – Dan Rosenstark Nov 10 '16 at 19:53
  • @DanRosenstark Thanks! Yeah, agree, got a lot easier in Swift 3. – HepaKKes Nov 11 '16 at 09:59
8

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

BB9z
  • 2,432
  • 1
  • 30
  • 36
5

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)
}
DarkDust
  • 90,870
  • 19
  • 190
  • 224
  • I have tried this method but my value object appears to be immediately deallocated when there are no other references to it from swift code. My target object is an SKNode and my value object is a swift class that extends NSObject. Should this work? – nacross Aug 11 '14 at 22:47
  • Actually deinit appears to be called during objc_setAssociatedObject even though the object has just been created and is used further down in the method. – nacross Aug 11 '14 at 23:09
4

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)
        }
    }
}
flame3
  • 2,812
  • 1
  • 24
  • 32
2

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
RhodanV5500
  • 1,087
  • 12
  • 16
1

For 2022, now very simple:

//  Utils-tags.swift

// Just a "dumb Swift trick" to add a string tag to a view controller.
// For example, with UIDocumentPickerViewController you need to know
// "which button was clicked to launch a picker"

import UIKit
private var _docPicAssociationKey: UInt8 = 0
extension UIDocumentPickerViewController {
    public var tag: String {
        get {
            return objc_getAssociatedObject(self, &_docPicAssociationKey)
               as? String ?? ""
        }
        set(newValue) {
            objc_setAssociatedObject(self, &_docPicAssociationKey,
               newValue, .OBJC_ASSOCIATION_RETAIN)
        }
    }
}
Fattie
  • 27,874
  • 70
  • 431
  • 719
0

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

jaumard
  • 8,202
  • 3
  • 40
  • 63
0

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
}

```

Alpface
  • 1
  • 1