In Swift, for Anyobject, How to Setvalue() Then Call Valueforkey()

setValue:forKey is always crashing in swift

To implement KVC(Key-Value Coding) support for a property.
you need the @objc annotation on your property,
Since the current implementation of KVC is written in Objective-C, After adding @objc, Objective-c can see it.

class Foo: NSObject {
@objc var bar = ""
}
let foo = Foo()
foo.setValue("A name", forKey: "bar")
print("Foo.bar: \(foo.bar)")

UIButton this class is not key value code-compliant for the key

It's easiest to "attach some value to the button" via associated objects. You can write an extension for UIButton that does just that

@interface UIButton (AssociatedObject)

@property (nonatomic, strong) id associatedObject;

@end

@implementation UIButton (AssociatedObject)
@dynamic associatedObject;

- (void)setAssociatedObject:(id)object {
objc_setAssociatedObject(self, @selector(associatedObject), object, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

- (id)associatedObject {
return objc_getAssociatedObject(self, @selector(associatedObject));
}

You can read a very good article on NSHipster covering use of associated objects.

Please note that whatever you're trying to actually do, can probably be done much more easily by subclassing the button or storing the value somewhere else.

setting property of nsobject subclass by means of selector setProperty

Read more about key-value coding in About Key-Value Coding, specifically:

Objects typically adopt key-value coding when they inherit from NSObject (directly or indirectly), which both adopts the NSKeyValueCoding protocol and provides a default implementation for the essential methods. Such an object enables other objects, through a compact messaging interface, to do the following:

Access object properties.

The protocol specifies methods, such as the generic getter valueForKey: and the generic setter setValue:forKey:, for accessing object properties by their name, or key, parameterized as a string. The default implementation of these and related methods use the key to locate and interact with the underlying data, as described in Accessing Object Properties.

By subclassing NSObject, Post class implements NSKeyValueCoding.

Basically it means that the properties defined in Post generate corresponding getter and setter methods, which means that they can be accessed using performSelector. This Key-Value coding allows you to perform selectors for getting or setting even properties which names you don't know during compilation - selector can be created from a string variable.

In case you will decide to migrate the project to Swift 4, note that you will have to either mark each property that you want to access this way using @objc, or use @objcMembers annotation on the whole class.

How can I blindly inject an object variable with arbitrary value?

UIViewController is inherited from NSObject. You can use Key-Value-Coding to find if the key exists. Add this extension to your code.

extension NSObject {
func safeValue(forKey key: String) -> Any? {
let copy = Mirror(reflecting: self)
for child in copy.children.makeIterator() {
if let label = child.label, label == key {
return child.value
}
}
return nil
}
}

Now you can use if-let to check if key exists.

if let key = yourViewController.safeValue(forKey: "someKey") {
print("key exists")
yourViewController.setValue("someValue", forKey:"someKey")
}
else {
print("key doesn't exist")
}

You will have to mark your properties with @objc to use KVC.

Swift iOS NSDictionary setValue crash - but why?

You should declare an actual NSMutableDictionary instead of casting to NSDictionary.

And you can use subscript which a bit simpler to use than setValue (which should actually be setObject):

var FDefaultsList = NSMutableDictionary()
let TmpKey: String = "a"
let TmpValue: String = "b"
if TmpKey != "" && TmpValue != "" {
FDefaultsList[TmpValue] = TmpKey
}

A more "Swifty" version could be:

var defaultsList = [String:String]()
let tmpKey = "a"
let tmpValue = "b"
if !tmpKey.isEmpty && !tmpValue.isEmpty {
defaultsList[tmpValue] = tmpKey
}

This class is not key value coding-compliant for the key openKey. How to fix errors

You are using the NSDictionary wrong.
First - in response to the comments of @Willeke, you cannot modify an NSDictionary, you need a NSMutableDictionary. You even better might want to use Dictionary.

Then, you should call setObject:forKey:, not setValue:forKey:

I must admit that this API might be a little confusing, because it is so look-alike:

What you want is to map a Dictionary key entry to an object, which is done by setObject:forKey:, in your example (with a mutable dictionary): testSection.setObject("N", forKey: "openKey"). Using the Swift way with a Swift Dictionary, you would write testSection["openKey"] = "N"

When using setValue:forKey:, you are Key-Value-Coding, e.g. you are calling a method ("sending a message") to the NSDictionary instance, and the name of the Message is what you provided in the key value. This would result in something like calling textSection.openKey = "N", thus the exeption

this class is not key value coding-compliant for the key openKey

In the special case of NSMutableDictionary, as @matt mentioned, both setValue:forKey and setObject:forKey behave the same as long as you do not provide an NSString as a key parameter - in that latter case, KVC will be used.

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.



Related Topics



Leave a reply



Submit