iOS 11 NSPredicate search on Swift array crashing - NSUnknownKeyException - ios

I am using NSPredicate to filter an array in Swift. The problem is after updating to iOS 11 (Xcode 9 /w Swift 4), I keep getting a crash on the filter line. Here is the crash log:
Terminating app due to uncaught exception 'NSUnknownKeyException', reason: >'[ valueForUndefinedKey:]: this class is not key >value coding-compliant for the key name.'
Here is an example of the class that I have an array of:
final class Model: NSObject {
let name: String
init(name: String) {
self.name = name
}
}
Here is the code that is crashing:
let myArray = [Model(name: "Jason"), Model(name: "Brian")]
let predicate = NSPredicate(format: "name == 'Jason'")
let filteredArray = myArray.filter { predicate.evaluate(with: $0)}
Question is why is this crashing now that I updated to iOS 11?

After fighting with this for a while, I finally came across the answer!
A subtlety of updating to Swift 4 is that classes that are subclasses of NSObject are no longer implicitly exposed to objective-c like they were before. Because of this, you need to explicitly annotate classes/functions with #objc. The compiler will notify you of places where the annotation is needed, but not in this case.
Ultimately because of this, the key-value lookup was no longer implicitly exposed to objective-c, which is needed to filter with NSPredicate. The code below fixes the crash!
Solution 1
extension Model {
#objc override func value(forKey key: String) -> Any? {
switch key {
case "name":
return name
default:
return nil
}
}
}
Solution 2
Alternative thanks to Uros19: Instead of implementing the above function, you could annotate the property directly with #objc (e.g., #objc let name: String). You lose a little bit of clarity as to why you are annotating the property with #objc, but this is only a small consideration.
I hope this saves some people time and frustration :)

Related

EXC_BAD_ACCESS in parent class init() with Xcode 10.2

I’ve been pulling my hair on a weird bug in Xcode 10.2.
I’ve had this swift util method to decode JSON objects using ObjectMapper as I can’t call ObjectMapper from ObjectiveC:
#objc static func mapModel(fromClass: AnyClass, jsonDict: [String : Any]) -> Any? {
guard let mappableClass = fromClass as? Mappable.Type else {
fatalError("class \(fromClass) is not Mappable")
}
return mappableClass.init(JSON: jsonDict)
}
Here's how I call it in ObjectiveC:
FullUser *object = [ModelMapperHelper mapModelFromClass:FullUser.class jsonDict:response.resultData];
I had to use AnyClass because Mappable is not compatible with ObjC.
This code has been working fine until I updated to Xcode 10.2.
The last line in the thread in the debugger is swift_checkMetadataState
It’s still working fine with devices on iOS 12.2 and above but it’s crashing with a EXC_BAD_ACCESS at the init() line on iOS 12.1 and below.
If I use the actual class like FullUser.init(JSON: jsonDict) it's all working fine.
Another interesting thing, if I don't pass FullUser.class from objc but set it in swift like so:
#objc static func mapFullUser(jsonDict: [String : Any]) -> Any {
let fromClass = FullUser.self
let mappableClass = fromClass as Mappable.Type
return mappableClass.init(JSON: jsonDict)!
}
it all works fine.
Does anyone have any idea why it’s crashing on older iOS versions and how I can change that code?
Update 1
By just adding print("swift=\(FullUser.self)") before the init() call it's working fine!
I'm starting to think this is a class loader issue.
Maybe it has to do with the fact that the project has 2 targets.
Update 2
I removed the 2nd target and it's still crashing.
Update 3
It also crashes if I just pass the class name as a string and get the class from NSClassFromString in swift.
Update 4
It doesn't crash if I decode the parent class User which FullUser inherits.
#objc(User)
class User: NSObject, Mappable {
#objc(FullUser)
class FullUser: User {
Update 5
I tried to call a different static function than init and it's not crashing.

Error while adding array of own objects to UserDefaults in Swift 3.1

Unfortunately I am not able to add a list of my own class objects to UserDefaults. The following error is generated:
NSForwarding: warning: object 0x6080002502c0 of class 'ClrLearn.highscoreStructure' does not implement methodSignatureForSelector: -- trouble ahead Unrecognized selector -[ClrLearn.highscoreStructure >replacementObjectForKeyedArchiver:]
The class looks as follows (it has been modified according to the various topics on the stack for example that one - how can store custom objects in NSUserDefaults):
class highscoreStructure {
var name : String = ""
var score : Int = 0
init(name: String, score: Int) {
self.name = name
self.score = score
}
required init(coder decoder: NSCoder) {
self.name = decoder.decodeObject(forKey: "name") as? String ?? ""
self.score = decoder.decodeInteger(forKey: "score")
}
func encode(with coder: NSCoder) {
coder.encode(name, forKey: "name")
coder.encode(score, forKey: "score")
}
}
Ok, feels like I made something wrong to the Stack rules, so sorry rmaddy - it was first and last time, I prommise. :)
But going back to the problem, first part was solved by vadian - thanks a lot! But still that part of my app not work:
I've set rootObject (NSKeyedArchiver.archivedData(withRootObject: highscoreStructObjects)) as my array of objects (so stupid mistake!) but still have errors like that:
[ClrLearn.HighscoreStructure encodeWithCoder:]: unrecognized selector sent >to instance 0x6080002586c0
or
Terminating app due to uncaught exception 'NSInvalidArgumentException', >reason: '-[ClrLearn.HighscoreStructure >encodeWithCoder:]: unrecognized >selector sent to instance >0x6080002586c0' –
Ps. I'm not sure is it the place where I should error - debug log is still not clear at all to me, at least not clean as one in Visual Studio. :) Maybe I should paste something other?
Pps. This line of code looks like:
let encodedData = NSKeyedArchiver.archivedData(withRootObject: highscoreStructObjects)
UserDefaults.standard.set(encodedData, forKey: "highscores")
To be able to implement NSCoding the class must inherit from NSObject.
class HighscoreStructure : NSObject { ...
By the way, class names are supposed to start with a capital letter.
And decodeObject(forKey: "name") can never be nil you can safely write
self.name = decoder.decodeObject(forKey: "name") as! String
Ok, I hope that this time I wont't make any mistake - the problem was solved by rmaddy in other "topic", but vadian was very, but very close - to implement NSCoding I need to inherit from NSObject as he wrote but also from... NSCoding! Isn't it obvious? For me it wasn't... In the other hand maybe he tried to tell me that I should inherit from both but my english was to bad to get it. Anyway I found an answer so thank You very much Vadian, Rmaddy and sorry one more time for breaking some kinds of SOF rules... It was first and last time! Oh and there is a thread when I finally find the answer, and yeap I was blind that I missed it earlier - encodeWithCoder: unrecognized selector sent to instance

Why doesn't my Swift 3.0 decoder decodeInteger work for integers?

So we've been using Groups for saving and retrieving some data across an extension and the main app and everything worked fine for Swift 2.3 but then we updated to Swift 3.0 and got some issues.
The current implementation that gives us issues is like following:
open class SomeClass: NSObject, NSCoding {
open var someVar: Int!
open func encode(with aCoder: NSCoder) {
aCoder.encode(self.someVar, forKey:"someVar")
}
public required convenience init?(coder decoder: NSCoder) {
// this is where it breaks
self.someVar = decoder.decodeInteger(forKey: "someVar")
}
}
The following error is thrown:
*** Terminating app due to uncaught exception 'NSInvalidUnarchiveOperationException', reason: '*** -[NSKeyedUnarchiver decodeInt32ForKey:]: value for key (someVar) is not an integer number'
The funny thing is the old implementation for Swift 2.3 works without any issues: self.someVar = decoder.decodeObject(forKey: "someVar") as! Int (I've understood from other posts that this would not work...)
So what could I be doing wrong? It should be said that the original value is retrieved from a float and cast to an int.
This problem is caused by multiple changes in Swift 3.
In Swift 3 the encode method is overloaded for every type. We have:
encode(Any?, forKey: String)
and
encode(Int, forKey: String)
The compiler chooses the correct method depending on the type of the first parameter (in Swift 2 you had two different method names therefore the type information was not needed).
You are putting Int! there. The behavior for Implicitly Unwrapped Optionals in Swift 3 changed due to Swift Evolution 0054.
If you read the changes, you can note that converting an IUO into a regular optional is preferred over unwrapping, therefore converting to Any? is preferred over unwrapping into Int.
However, the appearance of ! at the end of a property or variable declaration's type no longer indicates that the declaration has IUO type; rather, it indicates that (1) the declaration has optional type, and (2) the declaration has an attribute indicating that its value may be implicitly forced. (No human would ever write or observe this attribute, but we will refer to it as #_autounwrapped.)
The problem should be fixed by
aCoder.encode(self.someVar!, forKey:"someVar")
First check if you have encoded data with older versions of swift, if this is the case you still need to use
aDecoder.decodeObject(forKey: "someVar")
So a more complete solution in this case would be
aDecoder.decodeObject(forKey: "age") as? Int ?? aDecoder.decodeInteger(forKey: "age")
If this is not the case then please make sure "someVar" actually has a value set

Swift + Realm newbie: Problems with a simple Realm object and its initializers

I've been a long time Objective-C developer and heard about Realm some weeks ago. On the other hand I've always wanted to migrate little by little to Swift, so I created a small project involving Realm + Swift.
What does it mean? I'm a Swift + Realm newbie.
Anyway, I created a small demo/Proof of concept for a project I have in mind and I thought it had to be easier. But Xcode compiler says otherwise.
My problem is with one of my Objects' initializer(s).
My intentions were simple, but apparently Realm needs more initializers than I wanted.
The code of one of my Realm Objects is this:
import Foundation
import Realm
import RealmSwift
class Partida: Object
{
dynamic var id_partida: String
dynamic var inventario: Inventory?
required init ()
{
inventario = Inventory()
id_partida = "id_partida_test"
super.init()
}
required init(value: AnyObject, schema: RLMSchema) {
//fatalError("init(value:schema:) has not been implemented")
super.init(value: value, schema: schema)
}
required init(realm: RLMRealm, schema: RLMObjectSchema) {
//fatalError("init(realm:schema:) has not been implemented")
super.init(realm: realm, schema: schema)
}
override class func primaryKey() -> String? {
return "id_partida"
}
}
My original code only had the "normal" init initializer. But Xcode forced me to create two additional initializers more (value and realm ones).
If I compile the code I've just pasted above, Xcode complains in the 2nd and 3rd required initializers, specifically in the super.init part.
It says:
Property 'self.id_partida' not initialized at super.init call
I understand the meaning of it, but I don't know how to avoid the error because if I remove both super.init lines, the program crashes in runtime.
if I uncomment the fatalError lines, they also crashes in runtime.
In fact I don't want to use these 2 initializers. If I could, I wouldn't add them, but Xcode needs to, apparently. The only code I really want to add to my object init function is "the simple" init function, which was the only part of code considered mine.
I think I might have some concept misunderstandings in Realm + Swift + initializers.
I'm also having the feeling Xcode is forcing me to add code I don't need and/or I don't understand either.
Any help on understanding "required init" initializers in Realm will be more than welcome.
Official Realm + Swift documentation is beyond my knowledge as I don't understand many of its concepts even after re-reading them many times.
Google and StackOverflow haven't been really helpful this time...
Thanks.
Initializers in Swift definitely behave a bit differently to Objective-C, so I can definitely see the angle you're coming from here.
In this case though, since you're just using the initializer to set some default values, it's wholly un-necessary since you should be able to assign the default values to the properties themselves:
class Partida: Object
{
dynamic var id_partida = "id_partida_test"
dynamic var inventario: Inventory? = Inventory()
override class func primaryKey() -> String? {
return "id_partida"
}
}
Let me know if that still doesn't work! :)
Because it already has init () in Object class, you are using subclass of Object, so you already have its init in Realm object, you should give your var init value, like dynamic var id_partida: String = "id_partida_test", and then if you call let test = Partida() it already has your 2 init value, other init should be marked with convenience
When you save the Object to persistent store, it should be always have value, you can use Realm's optional then need read the documentation
Here's my sample Realm class so that u got the idea:
import Foundation
import RealmSwift
import SwiftyJSON
class ProjectModel: Object {
dynamic var id: Int = 0
dynamic var name: String = ""
//Dont need this, call init() already have value
required init() {
id = 0
name = ""
super.init()
}
convenience init(fromJson json: JSON!){
self.init()
if json == nil {
return
}
id = json["id"].intValue
name = json["name"].stringValue
}
override class func primaryKey() -> String? {
return "id"
}
}

updateValue not working for Dictionary

I'm creating a test app using Swift in Xcode, and I've run into an annoying issue. I'm writing a simple class that will act as a cache using a Dictionary object. My implementation is below:
import Foundation
import UIKit
class ImageCache {
var dict:Dictionary<String,NSData>?;
init() {
dict = Dictionary<String,NSData>();
}
func exists(id:String) -> Bool {
return dict!.indexForKey(id)!==nil;
}
func getImage(id:String) -> UIImage? {
if(!exists(id)) {
return nil;
}
return UIImage(data: (dict!)[id]);
}
func setData(id:String, data:NSData) {
dict!.updateValue(data, forKey: id);
}
}
The issue is in the last method, with Xcode stating "Could not find member 'UpdateValue'". This is weird, because the code hint seems to show it just fine:
But when I try to compile:
Could this potentially be a bug in Xcode? Or am I missing something super-obvious?
this is not a bug or a quirk in the compiler.
it is how Optional implemented (which may be flawed or not)
what happened is that the Optional store the Dictionary as immutable object (with let perhaps). So even Optional it is mutable, you can't modify the underlying Dictionaryobject directly (without reassign the Optional object).
updateValue(forKey:) is mutating method, you can't call it on immutable object and hence the error.
you can workaround it by doing
var d = dict!
d.updateValue(data, forKey: id)
because you copy the dictionary to another mutable variable, which then is mutable and able to call mutating method on it
but without dict = d, your change won't be applied on dict because Dictionary is value type, it makes copy on every assignment
related answer
Smells like a bug or a quirk in the compiler.
I just tried something like
var d = dict!
d.updateValue(data, forKey: id)
and it works as expected.

Resources