adam tecle
Stored properties in a Swift extension
Sep 07, 2017A neat workaround to get stored properties in extensions is to use associated objects. You can do it in Swift like this:
extension MyObject {
var property: Any? {
get {
return objc_getAssociatedObject(self, &key)
}
set(newValue) {
objc_setAssociatedObject(self, &key, newValue, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC)
}
}
}
A possible application of this in practice is to add some functionality for all instances of an object. Here’s how you can add a showLoadingIndicator()
method to every UIView.
extension UIView {
var loadingIndicator: LoadingIndicator? {
get {
guard let object = objc_getAssociatedObject(self, &key) as? LoadingIndicator else {
return nil
}
return object
}
set(newValue) {
objc_setAssociatedObject(self, &key, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC)
}
}
func showLoadingIndicator() {
setupIfNeeded()
loadingIndicator?.isHidden = false
}
func hideLoadingIndicator() {
loadingIndicator?.isHidden = true
}
private func setupIfNeeded() { ... }
}
Using an associated object in this manner is probably not the first way I’d approach solving the problem of having some class get access to some shared functionality, like showing a loading indicator. If it’s the right tool for the job though, this approach works!
Here’s a convenient way to pretty up your syntax when getting/setting associated object by using a generic helper class, credit to Wojciech Nagrodzki
public final class Association<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) }
}
}
Now instead of those scary Objective-C runtime library methods, we have this nice subscript syntax.
extension SomeType {
private static let association = Association<NSObject>()
var simulatedProperty: NSObject? {
get { return SomeType.association[self] }
set { SomeType.association[self] = newValue }
}
}