Swift package

swift-associated-objects

Stored-like properties for Objective-C objects, with helpers and macros

Stored-like properties where extensions cannot store state

Objective-C associated objects are powerful, but correct getters, stable keys, initialization, and policies add boilerplate. The @AssociatedObject macro turns that machinery into a property declaration while keeping ownership choices visible.

import ObjectiveC
import UIKit

private enum RetryCountKey {
  static var value: UInt8 = 0
}

extension UIViewController {
  var retryCount: Int {
    get {
      objc_getAssociatedObject(
        self,
        &RetryCountKey.value
      ) as? Int ?? 0
    }
    set {
      objc_setAssociatedObject(
        self,
        &RetryCountKey.value,
        newValue,
        .OBJC_ASSOCIATION_RETAIN_NONATOMIC
      )
    }
  }
}
Before
import AssociatedObjectsMacros
import UIKit

extension UIViewController {
  @AssociatedObject(readonly: false)
  var retryCount: Int = 0
}
Macro

Use readonly: true for lazily initialized state that should not be replaced, .atomic when atomic Objective-C association access is required, and .assign only for an intentionally non-owning, non-zeroing reference.

@AssociatedObject(readonly: true)
var state: State = .init()

@AssociatedObject(
  policy: .assign,
  readonly: false
)
var delegateReference: AnyObject?

The macro product adds SwiftSyntax to cold builds. Link only AssociatedObjects when the helper functions and AssociatingObject instance API are the better trade-off.

Use typed helpers without macros

Every NSObject subclass conforms to AssociatingObject, so UIKit and AppKit extensions can use typed instance helpers without importing the macro product. A complex getter can lazily construct and configure an object while a simple setter remains easy to find first:

import AssociatedObjects
import UIKit

extension UIViewController {
  var refreshControl: UIRefreshControl {
    set {
      setAssociatedObject(
        newValue,
        forKey: #function
      )
    }
    get {
      getAssociatedObject(forKey: #function) ?? {
        let refreshControl = UIRefreshControl()
        refreshControl.tintColor = .secondaryLabel
        refreshControl.attributedTitle = NSAttributedString(
          string: "Pull to refresh"
        )
        setAssociatedObject(
          refreshControl,
          forKey: #function
        )
        return refreshControl
      }()
    }
  }
}

#function is a concise stable key when the association belongs to one property. Prefer an explicitly namespaced StaticString when keys could collide across accessors or modules. For Objective-C objects that do not conform to AssociatingObject, the package also provides _getAssociatedObject(...from:) and _setAssociatedObject(...to:).

OptionMeaning
readonly: trueLazily initialize once and prevent replacement
threadSafety: .atomicUse atomic Objective-C association access
policy: .assignStore an intentionally non-owning, non-zeroing reference
getAssociatedObject / setAssociatedObjectAvoid the macro product while keeping typed helpers

Associated objects require Objective-C runtime identity. Prefer an ordinary stored property whenever the original class is under your control.