Using reflection to set object properties without using setValue forKey
I found a way around this when I was looking to solve a similar problem - that KVO can't set the value of a pure Swift protocol field. The protocol has to be marked @objc, which caused too much pain in my code base.
The workaround is to look up the Ivar using the objective C runtime, get the field offset, and set the value using a pointer.
This code works in a playground in Swift 2.2:
import Foundation
class MyClass
{
var myInt: Int?
}
let instance = MyClass()
// Look up the ivar, and it's offset
let ivar: Ivar = class_getInstanceVariable(instance.dynamicType, "myInt")
let fieldOffset = ivar_getOffset(ivar)
// Pointer arithmetic to get a pointer to the field
let pointerToInstance = unsafeAddressOf(instance)
let pointerToField = UnsafeMutablePointer<Int?>(pointerToInstance + fieldOffset)
// Set the value using the pointer
pointerToField.memory = 42
assert(instance.myInt == 42)
Notes:
- This is probably pretty fragile, you really shouldn't use this.
- But maybe it could live in a thoroughly tested and updated reflection library until Swift gets a proper reflection API.
- It's not that far away from what Mirror does internally, see the code in Reflection.mm, around here: https://github.com/apple/swift/blob/swift-2.2-branch/stdlib/public/runtime/Reflection.mm#L719
- The same technique applies to the other types that KVO rejects, but you need to be careful to use the right UnsafeMutablePointer type. Particularly with protocol vars, which are 40 or 16 bytes, unlike a simple class optional which is 8 bytes (64 bit). See Mike Ash on the topic of Swift memory layout: https://mikeash.com/pyblog/friday-qa-2014-08-01-exploring-swift-memory-layout-part-ii.html
Edit: There is now a framework called Runtime at https://github.com/wickwirew/Runtime which provides a pure Swift model of the Swift 4+ memory layout, allowing it to safely calculate the equivalent of ivar_getOffset without invoking the Obj C runtime. This allows setting properties like this:
let info = try typeInfo(of: User.self)
let property = try info.property(named: "username")
try property.set(value: "newUsername", on: &user)
This is probably a good way forward until the equivalent capability becomes part of Swift itself.
How to set a member variable value using reflection in Swift?
Unfortunately, you cannot modify value with
Mirror
in basic usage of this.
You can make it without reflection if it acceptable for you:
class MyObject: NSObject
{
@objc public var myString : String = "Not working"
}
func test()
{
let value = "It works!"
let member = "myString"
let myObject = MyObject()
myObject.setValue(value, forKey: member)
print("New value: \(myObject.myString)")
}
OR
Take a look at this: Reflection
Hope it helps you.
Set property values of an Objective-C class using reflection
Objective C properties automatically conform to the NSKeyValueCoding
protocol. You can use setValue:forKey:
to set any property value by a string property name.
NSDictionary * objectProperties = @{@"propertyName" : @"A value for property name",
@"anotherPropertyName" : @"MOAR VALUE"};
//Assuming class has properties propertyName and anotherPropertyName
NSObject * object = [[NSObject alloc] init];
for (NSString * propertyName in objectProperties.allKeys)
{
NSString * propertyValue = [objectProperties valueForKey:propertyName];
[object setValue:propertyValue
forKey:propertyName];
}
In Swift, how can I use setValueForKey to set the textAlignment property of an object?
You have to use raw value NSTextAlignment.Center.rawValue
object.setValue(NSTextAlignment.Center.rawValue, forKey: "textAlignment")
Issue trying to use setValue forKey on Realm Object
Because if you define associatedtype Object
in Saveable
protocol, associatedtype Object
hides RealmSwift's Object
type due to conflict name.
If you'd like to identify each Object
types, you should use RealmSwift.Object
to represent RealmSwift's Object
type. Like the following:
extension Saveable {
static func setString(o: RealmSwift.Object, field:String, value: String, realm: Realm) {
try! realm.write {
o.setValue(value, forKey: field)
}
}
...
}
Using getter/setter for property makes it not appear in reflection (mirror)
For the first problem I ended up using the following extension, which does see properties with getters/setters unlike Mirror:
extension NSObject {
func propertiesNames() -> [String] {
var count : UInt32 = 0
let classToInspect = type(of: self)
guard let properties : UnsafeMutablePointer <objc_property_t> = class_copyPropertyList(classToInspect, &count) else { return [] }
var propertyNames : [String] = []
let intCount = Int(count)
for i in 0..<intCount {
let property : objc_property_t = properties[i]
let propertyName = NSString(utf8String: property_getName(property))!
propertyNames.append(propertyName as String)
}
free(properties)
return propertyNames
}
}
As for the second issue I ended up copying each property over from the theme to the button as they are always the same. The goal was to avoid having to maintain a Theme class to bridge values every time something new is implemented in ZFButton.
Access Objective-C property dynamically using the name of the property
While @weichsel is correct, there is a better way.
Use:
[anObject valueForKey: @"propertyName"];
and
[anObject setValue:value forKey:@"propertyName"];
Obviously, @"propertyName"
can be an NSString
that is dynamically composed at runtime.
This technique is called Key Value Coding and is fundamental to Cocoa.
Why this is better is because -valueForKey
will do what is necessary to "box" whatever type the property returns into an object. Thus, if the property is of type int
, it'll return an NSNumber
instance containing the int.
This is much easier to deal with -- performSelector
will only work for types that happen to fit into a pointer's worth of memory.
Note that there is also -setValue:forKey:
.
Related Topics
Update All Value in One Attribute Core Data
Swift Interstitial Making a Banner Ad
Launch Safari (Not Default Browser) at Url in Swift
Display Data in Two Different Tableview in the Same View in Swift
How to Make Sure Data in Variable Is Loaded Before Using It
Swift Nsunknownkeyexception - Setvalue:Forundefinedkey
Behavior of the Cameranode Is Unclear When Moving a 3D Model in a Scene with Scenekit
How to Pass Closure as a Parameter in Perform(Selector, Withobject)
Core Data Update in Swift While Selecting Any Row in List Table View Not Working
Retrieve an Image from Firebase to an Uiimage Swift5
Get All Documents at Once in a Completion Handler with Getdocuments in Firestore
How to Deallocate All References Elements from an Array
Iterate a Grid of Views Swiftui
Swift: Casting a Floatingpoint Conforming Value to Double
Getting Pixel Format from Cgimage
How Does One Use Nsdateformatter's Islenient Option
How to Move a Model and Generate Its Collision Shape at the Same Time