Using Reflection to Set Object Properties Without Using Setvalue Forkey

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



Leave a reply



Submit