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 settersetValue: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
Horizontal Paging Uicollectionview with Automatic Item Size in a Vertical Stack View
Change Uipopoverview Background + Arrow Color
Ios: How to Create Expandable Tableview in Swift Without Using Third Party Libraries or Pods
Scene Kit Memory Management Using Swift
Programmatically Auto-Connect to Wifi iOS
How to Access Property or Method from a Variable
How to Reuse Struct on Swift? or Is There Any Other Way
How to Set Response Data into Todayextenstion Widget
Problems with Cropping a Uiimage in Swift
Wkwebview Decidepolicyfornavigationaction Being Call After the Long Press Recogniser Ended
Ios-Charts How to Put Uiimage Beside a Point
Error "Call Can Throw, But Is Not Marked with 'Try' and the Error Is Not Handled"
Modifing One Variable from Another View Controller Swift
Backgroundtimeremaining Returns (35791394 Mins)
How to Update Individual Array Element in Firebase with iOS Swift