Is there a way to set associated objects in 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 ?? ""
}
Associated objects and Swift arrays
The NSMutableArray
here is a reference type. The consequence of this is that the moment you hand somebody a reference to your view object's queue
, your view object loses all control of it. The recipient of the reference can rearrange items, delete all items, etc., and your view wouldn't know about it until next time it tried reading it.
Generally speaking, this sort of implicit data sharing has been found to be a bad idea, because it breaks encapsulation, and complicates systems, because you can no longer reason locally about code, because there's always the threat that another thread has a reference to your aliased object, and is changing it under your feet.
This model isn't compatible with Swift's value-types, such as Array
. If queue
were an Array<T>
, every person who accessed it would get back their own value. Semantically, these copies are all completely isolated from each other, and there's no way in which a mutation done through one reference can cause an effect that's observable via the other reference.
If you wanted to faithfully preserve the reference semantics of your current code, you would need a mechanism to listen for changes to the NSMutableArray
, and update every individual Array<T>
that was derived from it. This isn't really practical, nor is it a good idea.
Here's what I would do:
Make the interface more explicitly transactional. More than likely, you can hide the
queue
entirely. Make it private, and have your view expose public methods likepush
andpop
.import Foundation
class C: NSObject {
private enum AssociatedObjectKeys {
static var queue: Int8 = 0
}
private var queue: Array<Int> {
get {
guard let existingValue = objc_getAssociatedObject(self, &AssociatedObjectKeys.queue) else {
self.queue = []
return []
}
guard let existingArray = existingValue as? Array<Int> else {
fatalError("Found an associated object that had the wrong type!")
}
return existingArray
}
set {
objc_setAssociatedObject(self, &AssociatedObjectKeys.queue, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
}
}
public func printQueueForDebugging() {
print(self.queue)
}
public func push(_ newValue: Int) {
self.queue.append(newValue)
}
public func pushAll<S: Sequence>(_ newValues: S) where S.Element == Int {
self.queue.append(contentsOf: newValues)
}
public func pop() -> Int? {
if self.queue.isEmpty { return nil }
return self.queue.removeFirst()
}
}
let c = C()
c.printQueueForDebugging()
c.pushAll(1...3)
c.printQueueForDebugging()
c.push(4)
c.printQueueForDebugging()
print(c.pop() as Any)
print(c.pop() as Any)
print(c.pop() as Any)
print(c.pop() as Any)
print(c.pop() as Any)I would use a type-safe Swift wrapper to tuck away associated object sets/gets, which can do type checking and casting automatically behind the scenes. The new property wrapper feature is perfect for this.
Extract out a separate
Queue<T>
data structure.Array<T>
by itself doesn't make for a good queue, because removing at the start hasO(n)
time complexity. There are lots of data structure libraries out there, I would use one of their queues instead.
Associated objects in Swift, does global key actually produce specific instances?
Objective-C's concept of "associated objects" lets you connect one "target" object instance with one "source" object instance. It's a unidirectional association where only the source knows the target:
objc_setAssociatedObject(source, key, target, ...)
The association uses a key to be able to connect and distinguish any number of associated objects with one source. The keys must obviously be different—but just for one source instance.
Because you have to provide both the key and the source instance to retrieve the associated object, it's not necessary to use really unique keys. The implementation of objc_***AssociatedObject
can form a process-unique key by combining the instance pointer and the key.
So for your example, yes, both aoAA
and aoBB
will return individual values per each UIViewController
instance, even though keyA
and keyB
are global.
To be absolutely clear, you do need different keys for each associated object in a given extension. So, aoAA
and aoBB
each need their own key, as shown in the example code (to wit, the pointers to keyA
and keyB
). But as is asked in the question it's correct have only one key for each associated object, no matter how many conforming classes the extension is used in.
Can I add associated object to Swift Struct?
Based on @Hamish's comment, I created the following solution to workaround the issue.
Preconditions:
Have a framework which proposes prefilled objects, the app works on these objects and the framework should know which of the properties are modified during the processing of this object later.
Not using looooong initializers to setup all properties of
MyObject
is a design decision.
In my example, the usage of the myObject
is a dummy and shows what happens in the framework and what happens in the app.
// protocol is used, as we could handle more modifiable structs/classes in a common way
protocol PropertyState {
var isModified: Bool {get set}
}
internal struct ModifiableString : PropertyState {
var string: String
var isModified: Bool
}
class MyObject: PropertyState {
internal var _name = ModifiableString(string: "", isModified: false)
public var name: String {
get {
return _name.string
}
set(value) {
_name.string = value
_name.isModified = true
}
}
// + N similar properties (they can be other types than String, by implementing other structs, like ModifiableBool)
var isModified: Bool {
get {
return _name.isModified // || _myAnotherProperty.isModified
}
set(value) {
_name.isModified = value
// _myAnotherProperty.isModified = value
}
}
}
// internal filling of the object inside of the framework
let myObject = MyObject()
myObject.name = "originalValue"
print(myObject.isModified) // true
// filling the object with values ended, so we can set the state
myObject.isModified = false
print(myObject.isModified) // false
// the app can work with the object
// let myObject = Framework.getObject()
myObject.name = "modifiedValue"
// now the framework should now which properties are modified
print(myObject._name.isModified) // true
Swift objc_getAssociatedObject always nil
You cannot add associated objects to a Swift Array
(or any Swift value type). objc_setAssociatedObject()
and objc_getAssociatedObject()
are from the
Objective-C runtime, they expect an instance of NSObject
as first
argument.
Your code compiles and runs only because any Swift value is automatically
bridged to an object if necessary.
When you call
objc_setAssociatedObject(self, ...)
thenself
is bridged to a (temporary) instance ofNSArray
, and the association
is made on that object.Later, when
objc_getAssociatedObject(self, ...)
is called,
another (temporary) instance ofNSArray
is created, and that
has no associated object.
That's why you get nil
as the result.
set associated objects for literal value in Swift
NSData
is part of a class cluster, so your custom init method is not necessarily called,
e.g.
let d = NSMutableData()
does not use your init
method. The next problem is that your init method calls
itself recursively, therefore
let d = NSData()
crashes with a stack overflow. Note also that the Objective-C code relies on
undefined behaviour, because it replaces a method in a class extension.
So better remove your custom initialization, and change the getter to
return a default value if the associated object has not been set.
This can easily be achieved with an optional cast (as? Int
) and the
nil-coalescing operator (??
):
extension NSData {
var position: Int {
get {
return objc_getAssociatedObject(self, &xoAssociationKey) as? Int ?? 0
}
set {
objc_setAssociatedObject(self, &xoAssociationKey, newValue, objc_AssociationPolicy(OBJC_ASSOCIATION_RETAIN_NONATOMIC))
}
}
}
cant set stored property with associated object in extention
it only works right for classes (not structs) and on top only for those that are objc compatible.
for a workaround, see also: https://wezzard.com/2015/10/09/associated-object-and-swift-struct/
How to use associated objects with enums?
Change your code to this
extension UIViewController {
private struct AssociatedKeys {
static var handle = "handle"
static var enumContext = "enumContext"
}
enum CustomStringEnum: String {
case One = "One"
case Two = "Two"
case Three = "Three"
}
var customEnum: CustomStringEnum {
get {
let rawvalue = objc_getAssociatedObject(self, &AssociatedKeys.enumContext)
if rawvalue == nil{
return .One
}else{
return CustomStringEnum(rawValue: rawvalue as! String)!;
}
}
set {
objc_setAssociatedObject(self, &AssociatedKeys.enumContext, newValue.rawValue, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC)
}
}
var descriptiveName: String {
get {
return objc_getAssociatedObject(self, &AssociatedKeys.handle) as! String
}
set {
objc_setAssociatedObject(
self,
&AssociatedKeys.handle,
newValue as NSString?,
.OBJC_ASSOCIATION_RETAIN_NONATOMIC
)
}
}
}
Then it will work
Binding Protocols with Associated Objects - Protocol Oriented Programming with Swift
I think in this case you need to use a concrete type to alias FactoryBuilder
instead of BindedBuilder
, as protocols do not conform to themselves.
This code effectively compiles, would something like that match your requirements?
class ConcreteFactory: BindedFactory {
typealias FactoryBuilder = ConcreteObject
var parameters: ConcreteParameters {
return ConcreteParameters(string: "Hello ")
}
}
Otherwise you can also try type erasing BindedBuilder
and create AnyBindedBuilder
, as suggested in the same link.
Related Topics
How to Get the Current Date in Short Format in Swift
Instantiated Optional Variable Shows as Nil in Xcode Debugger
How to Save and Read Array of Array in Nsuserdefaults in Swift
What Is the In-Practice Difference Between Generic and Protocol-Typed Function Parameters
Uialertcontroller - Add Custom Views to Actionsheet
How to Get Current Language Code With Swift
Swipe-Able Table View Cell in iOS 9
Is This Response from the Compiler Valid
How to Determine If a String Contains a Character from a Set in Swift
Difference Between 'Let' and 'Var' in Swift
How to Print the Type or Class of a Variable in Swift
Overriding Superclass Property With Different Type in Swift
Cfrunloop in Swift Command Line Program
Checking If an Object Is a Given Type in Swift
Swift: How to Use Preprocessor Flags (Like '#If Debug') to Implement API Keys