KVC without NSObject (NSPredicate) - swift

I'm trying to use NSPredicate like so:
let namePredicate = NSPredicate(format: "(username BEGINSWITH[c] $word)")
if prefix == "#" {
if countElements(word) > 0 {
suggestionDatasource.users = (suggestionDatasource.users as NSArray).filteredArrayUsingPredicate(namePredicate!.predicateWithSubstitutionVariables(["word" : word])) as [User]
}
}
However, my User class doesn't subclass NSObject so it's not Key Value Compliant. I get unrecognized selector 'valueForKey' whenever I try this. Is there a way I can make a custom class Key Value Compliant without subclassing NSObject? Or perhaps a way to use NSPredicate without having to use KVC.
Thanks

Aren't you making this awfully unnecessarily hard for yourself? Why not just use what Swift gives you - filter, hasPrefix, and so on? I don't know what a User actually is, but let's pretend it's something like this:
struct User {
let username : String = ""
}
And let's pretend that users is like this:
let users = [User(username:"Matt Neuburg"), User(username:"Dudley Doright")]
Then what you seem to be trying to do is trivial in Swift:
let word = "Matt"
let users2 = users.filter {$0.username.hasPrefix(word)}

You can manually implement value(forKey key: String) -> Any? method:
extension Model {
#objc func value(forKey key: String) -> Any? {
switch key {
case "id":
return id
// Other fields
default:
return nil
}
}
}
Note #objc prefix of the method: it's important, as it's allowing NSPredicate to see that the method is implemented.

Related

Swift 3 Generic Extension Arguments

In Swift 2.x, I had a nice little setup that allowed me to store and retrieve dictionary values using enum members:
public enum UserDefaultsKey : String {
case mainWindowFrame
case selectedTabIndex
case recentSearches
}
extension Dictionary where Key : String {
public subscript(key: UserDefaultsKey) -> Value? {
get { return self[key.rawValue] }
set { self[key.rawValue] = newValue }
}
}
This allowed me to access values like this:
let dict = userDefaults.dictionaryForKey("SearchPrefs")
if let recentSearches = dict?[.recentSearches] as? [String] {
// Populate "Recent" menu items
}
… instead of having to access values like this:
let dict = userDefaults.dictionaryForKey("SearchPrefs")
if let recentSearches = dict?[UserDefaultsKey.recentSearches.rawValue] as? [String] {
// Populate "Recent" menu items
}
Note: The use of a string literal to access the dictionary from NSUserDefaults is for example purposes only. I wouldn't actually go out of my way to use an enum for dictionary keys, only to use a string literal to access the dictionary itself. :-)
Anyway, this has worked great for my needs, and it made reading and maintaining code involving NSUserDefaults a lot more pleasant.
Since migrating my project to Swift 3, however, I'm getting the following error:
extension Dictionary where Key: String {
public subscript(key: UserDefaultsKey) -> Value? { <---- Use of undeclared type 'Value'
~~~~~~
get {
return self[key.rawValue]
}
set {
self[key.rawValue] = newValue
}
}
}
I looked at the generated headers for Dictionary, and the generic Key and Value arguments are still present in the Generic Argument Clause of the Dictionary struct, so I'm not too sure what the issue is.
Do I need to rewrite the where clause to conform to some new Swift 3 grammar I'm unaware of? Or … can one no longer access generic placeholder types in extensions?
I just don't know what to do!
My project has only 28 migration errors left to resolve. I'm so close to actually getting to use Swift 3, so I'd love any pointers (as long as they're not Unsafe and/or Raw).
Thanks!
A generic parameter of a concrete type cannot be constrained to a concrete type, currently. This means that something like
extension Dictionary where Key == String
won't compile. It's a limitation of the generics system, and it hopefully won't be a problem in Swift 4.
There is a workaround though, but it's a bit hacky:
protocol StringConvertible {
init(_ string: String)
}
extension String: StringConvertible {}
extension Dictionary where Key: StringConvertible {
subscript(key: UserDefaultsKey) -> Value? {
get { return self[Key(key.rawValue)] }
set { self[Key(key.rawValue)] = newValue }
}
}

deep copy for array of objects in swift

I have this class named Meal
class Meal {
var name : String = ""
var cnt : Int = 0
var price : String = ""
var img : String = ""
var id : String = ""
init(name:String , cnt : Int, price : String, img : String, id : String) {
self.name = name
self.cnt = cnt
self.price = price
self.img = img
self.id = id
}
}
and I have an array of Meal :
var ordered = [Meal]()
I want to duplicate that array and then do some changes to the Meal instances in one of them without changing the Meal instances in the second one, how would I make a deep copy of it?
This search result didn't help me
How do I make a exact duplicate copy of an array?
Since ordered is a swift array, the statement
var orderedCopy = ordered
will effectively make a copy of the original array.
However, since Meal is a class, the new array will contain references
to the same meals referred in the original one.
If you want to copy the meals content too, so that changing a meal in one array will not change a meal in the other array, then you must define Meal as a struct, not as a class:
struct Meal {
...
From the Apple book:
Use struct to create a structure. Structures support many of the same behaviors as classes, including methods and initializers. One of the most important differences between structures and classes is that structures are always copied when they are passed around in your code, but classes are passed by reference.
To improve on #Kametrixom answer check this:
For normal objects what can be done is to implement a protocol that supports copying, and make the object class implements this protocol like this:
protocol Copying {
init(original: Self)
}
extension Copying {
func copy() -> Self {
return Self.init(original: self)
}
}
And then the Array extension for cloning:
extension Array where Element: Copying {
func clone() -> Array {
var copiedArray = Array<Element>()
for element in self {
copiedArray.append(element.copy())
}
return copiedArray
}
}
and that is pretty much it, to view code and a sample check this gist
You either have to, as #MarioZannone mentioned, make it a struct, because structs get copied automatically, or you may not want a struct and need a class. For this you have to define how to copy your class. There is the NSCopying protocol which unifies that on the ObjC world, but that makes your Swift code "unpure" in that you have to inherit from NSObject. I suggest however to define your own copying protocol like this:
protocol Copying {
init(original: Self)
}
extension Copying {
func copy() -> Self {
return Self.init(original: self)
}
}
which you can implement like this:
class Test : Copying {
var x : Int
init() {
x = 0
}
// required initializer for the Copying protocol
required init(original: Test) {
x = original.x
}
}
Within the initializer you have to copy all the state from the passed original Test on to self. Now that you implemented the protocol correctly, you can do something like this:
let original = Test()
let stillOriginal = original
let copyOriginal = original.copy()
original.x = 10
original.x // 10
stillOriginal.x // 10
copyOriginal.x // 0
This is basically the same as NSCopying just without ObjC
EDIT: Sadly this yet so beautiful protocol works very poorly with subclassing...
A simple and quick way is to map the original array into the new copy:
let copyOfPersons: [Person] = allPersons.map({(originalPerson) -> Person in
let newPerson = Person(name: originalPerson.name, age: originalPerson.age)
return newPerson
})
The new Persons will have different pointers but same values.
Based on previous answer here
If you have nested objects, i.e. subclasses to a class then what you want is True Deep Copy.
//Example
var dogsForAdoption: Array<Dog>
class Dog{
var breed: String
var owner: Person
}
So this means implementing NSCopying in every class(Dog, Person etc).
Would you do that for say 20 of your classes? what about 30..50..100? You get it right? We need native "it just works!" way. But nope we don't have one. Yet.
As of now, Feb 2021, there is no proper solution of this issue. We have many workarounds though.
Here is the one I have been using, and one with less limitations in my opinion.
Make your class conforms to codable
class Dog: Codable{
var breed : String = "JustAnyDog"
var owner: Person
}
Create this helper class
class DeepCopier {
//Used to expose generic
static func Copy<T:Codable>(of object:T) -> T?{
do{
let json = try JSONEncoder().encode(object)
return try JSONDecoder().decode(T.self, from: json)
}
catch let error{
print(error)
return nil
}
}
}
Call this method whenever you need true deep copy of your object, like this:
//Now suppose
let dog = Dog()
guard let clonedDog = DeepCopier.Copy(of: dog) else{
print("Could not detach Dog")
return
}
//Change/mutate object properties as you want
clonedDog.breed = "rottweiler"
//Also clonedDog.owner != dog.owner, as both the owner : Person have dfferent memory allocations
As you can see we are piggy backing on Swift's JSONEncoder and JSONDecoder, using power of Codable, making true deep copy no matter how many nested objects are there under our object. Just make sure all your Classes conform to Codable.
Though its NOT an ideal solution, but its one of the most effective workaround.

Using Predicates on Array Objects in Swift returns Error

When I filter an array of custom Swift classes using a Predicate I get the error:
*** NSForwarding: warning: object 0x78ed21a0 of class 'Plantivo1_6.Seed' does not implement methodSignatureForSelector: -- trouble ahead
Unrecognized selector -[Plantivo1_6.Seed valueForKey:]
If I remember correctly this would work in Objective-C. What's my mistake?
let names = ["Tom","Mike","Marc"]
println(names)
let searchPredicate = NSPredicate(format: "SELF CONTAINS[c] %#", "om")
let array = (names as NSArray).filteredArrayUsingPredicate(searchPredicate)
println(array)
println()
let mySeed1 = Seed() // Seed is a class with a `culture` String property
let mySeed2 = Seed()
let mySeed3 = Seed()
mySeed1.culture = "Tom"
mySeed2.culture = "Mike"
mySeed3.culture = "Marc"
let mySeeds = [mySeed1,mySeed2,mySeed3]
println(mySeeds)
let searchPredicate1 = NSPredicate(format: "SELF.culture CONTAINS[c] %#", "om")
let array1 = (mySeeds as NSArray).filteredArrayUsingPredicate(searchPredicate1)
println(array1)
Does your Seed class inherit from NSObject?
If not, this is the message you will get.
Solution:
class Seed: NSObject {
...
Edit:
stklieme is correct - to use NSPredicate, your object's class needs to implement -valueForKey as defined by the NSKeyValueCoding protocol. You can either define your own implementation of -valueForKey, or just make your class inherit from NSObject which takes care of that for you.
This is defined in the Apple docs for NSPredicate,
You can use predicates with any class of object, but the class must support key-value coding for the keys you want to use in a predicate.
In case if you don't want to inherit from NSObject, you can implement value(forKey key: String) -> Any? method by yourself:
extension Model {
#objc func value(forKey key: String) -> Any? {
switch key {
case "id":
return id
// Other fields
default:
return nil
}
}
}
Note #objc prefix of the method: it's important, as it's allowing NSPredicate to see that the method is implemented. You'll still receive does not implement methodSignatureForSelector: crash without it.
Or, even better, make your objects to conform this protocol:
#objc protocol UsableInPredicate {
#objc func value(forKey key: String) -> Any?
}
valueForKey is a key value coding method. Declare the class Seed as a subclass of NSObject which conforms to KVC
Simply,
You need to inherit your model class by NSObject and your issue get fixed.
class Seed: NSObject {
...
}
Reason:
-valueForKey as defined by the NSKeyValueCoding protocol. You can either define your own implementation of -valueForKey, or just make your class inherit from NSObject

How can I easily duplicate/copy an existing realm object

I have a Realm Object which has several relationships, anyone has a good code snippet that generalizes a copy method, to create a duplicate in the database.
In my case i just wanted to create an object and not persist it. so segiddins's solution didn't work for me.
Swift 3
To create a clone of user object in swift just use
let newUser = User(value: oldUser);
The new user object is not persisted.
You can use the following to create a shallow copy of your object, as long as it does not have a primary key:
realm.create(ObjectType.self, withValue: existingObject)
As of now, Dec 2020, there is no proper solution for this issue. We have many workarounds though.
Here is the one I have been using, and one with less limitations in my opinion.
Make your Realm Model Object classes conform to codable
class Dog: Object, Codable{
#objc dynamic var breed:String = "JustAnyDog"
}
Create this helper class
class RealmHelper {
//Used to expose generic
static func DetachedCopy<T:Codable>(of object:T) -> T?{
do{
let json = try JSONEncoder().encode(object)
return try JSONDecoder().decode(T.self, from: json)
}
catch let error{
print(error)
return nil
}
}
}
Call this method whenever you need detached / true deep copy of your Realm Object, like this:
//Suppose your Realm managed object: let dog:Dog = RealmDBService.shared.getFirstDog()
guard let detachedDog = RealmHelper.DetachedCopy(of: dog) else{
print("Could not detach Dog")
return
}
//Change/mutate object properties as you want
detachedDog.breed = "rottweiler"
As you can see we are piggy backing on Swift's JSONEncoder and JSONDecoder, using power of Codable, making true deep copy no matter how many nested objects are there under our realm object. Just make sure all your Realm Model Classes conform to Codable.
Though its NOT an ideal solution, but its one of the most effective workaround.
I had a similar issue and found a simple workaround to get a copy of a realm object. Basically you just need to make the object conform to the NSCopying protocol, something like:
import RealmSwift
import Realm
import ObjectMapper
class Original: Object, NSCopying{
dynamic var originalId = 0
dynamic var firstName = ""
dynamic var lastName = ""
override static func primaryKey() -> String? {
return "originalId"
}
init(originalId: Int, firstName: String, lastName: String){
super.init()
self.originalId = originalId
self.firstName = firstName
self.lastName = lastName
}
func copy(with zone: NSZone? = nil) -> Any {
let copy = Original(originalId: originalId, firstName: firstName, lastName: lastName)
return copy
}
}
then you just call the "copy()" method on the object:
class ViewController: UIViewController {
var original = Original()
override func viewDidLoad() {
super.viewDidLoad()
var myCopy = original.copy()
}
}
The nice thing about having a copy is that I can modify it without having to be in a realm write transaction. Useful when users are editing some data but didn't hit save yet or simply changed their mind.
Since this problem is still alive I post my solution which works but still needs to be improved.
I've created an extension of Object class that has this method duplicate that takes an object objOut and fills the flat properties by looking at self. When a non-flat property is found (aka a nested object) that one is skipped.
// Duplicate object with its flat properties
func duplicate(objOut: Object) -> Object {
// Mirror object type
let objectType: Mirror = Mirror(reflecting: self);
// Iterate on object properties
for child in objectType.children {
// Get label
let label = child.label!
// Handler for flat properties, skip complex objects
switch String(describing: type(of: child.value)) {
case "Double", "Int", "Int64", "String":
objOut.setValue(self.value(forKey: label)!, forKey: label)
break
default:
break
}
}
return objOut
}
Inside the Manager class for my Realms I have the method copyFromRealm() that I use to create my copies of objects.
To give you a practical example this is the structure of my Appointment class:
Appointment object
- flat properties
- one UpdateInfo object
- flat properties
- one AddressLocation object
- flat properties
- one Address object
- flat properties
- one Coordinates object
- flat properies
- a list of ExtraInfo
- each ExtraInfo object
- flat properties
This is how I've implemented the copyFromRealm() method:
// Creates copy out of realm
func copyFromRealm() -> Appointment {
// Duplicate base object properties
let cpAppointment = self.duplicate(objOut: Appointment()) as! Appointment
// Duplicate UIU object
cpAppointment.uiu = self.uiu?.duplicate(objOut: UpdateInfo()) as? UpdateInfo
// Duplicate AddressLocation object
let cpAddress = self.addressLocation?.address?.duplicate(objOut: Address()) as? Address
let cpCoordinates = self.addressLocation?.coordinates?.duplicate(objOut: Coordinates()) as? Coordinates
cpAppointment.addressLocation = self.addressLocation?.duplicate(objOut: AddressLocation()) as? AddressLocation
cpAppointment.addressLocation?.address = cpAddress
cpAppointment.addressLocation?.coordinates = cpCoordinates
// Duplicate each ExtraInfo
for other in self.others {
cpAppointment.others.append(other.duplicate(objOut: ExtraInfo()) as! ExtraInfo)
}
return cpAppointment
}
I wasn't able to find out a good and reasonable way to work with nested objects inside my duplicate() method. I thought of recursion but code complexity raised too much.
This is not optimal but works, if I'll find a way to manage also nested object I'll update this answer.
Swift 5+
Creates a Realm managed copy of an existing Realm managed object with ID
extension RLMObject {
func createManagedCopy(withID newID: String) -> RLMObject? {
let realmClass = type(of: self)
guard let realm = self.realm, let primaryKey = realmClass.primaryKey() else {
return nil
}
let shallowCopy = realmClass.init(value: self)
shallowCopy.setValue(newID, forKey: primaryKey)
do {
realm.beginWriteTransaction()
realm.add(shallowCopy)
try realm.commitWriteTransaction()
} catch {
return nil
}
return shallowCopy
}
}

Swift - Get class name from MetaType?

In the example below T refers to a type that extends NSManagedObject, so why can I not call.
I do not have access to an instance of the class
private func getNewManagedObject <T: NSManagedObject>(type: T.Type) -> T {
// Let's assume all Entity Names are the same as Class names
let className = "" /*Somehow get class name from type ("User")*/
return NSEntityDescription.insertNewObjectForEntityForName(className, inManagedObjectContext: managedObjectContext) as T
}
getNewManagedObject(User.self);
Swift classes can be given a custom Objective-C name, what will make NSStringFromClass print a nicer output in a playground.
import CoreData
#objc(User) class User : NSManagedObject {
}
let className = NSStringFromClass(User.self) // className will be "User"
Without it, NSStringFromClass will print 'ModulName.ClassName' which is arguably better than 'ClassName' only. The ugliness of the playground output is due to the fact that playgrounds have some cryptic implicit module names.
With some experimenting I found out the following. In Playground you can do
class User : NSManagedObject {
}
let s = NSStringFromClass(User) // cryptic output: "__lldb_expr_XX.User"
The XX is some random number. At this point you can get the entity name with
let entityName = s.pathExtension // "User"
It's a bit hacky but maybe it could work for you.
quickly in Swift:
let className = String(YourClass)
Extension variant (in my opinion, it's more convenient):
extension NSObject {
class var string: String{
get {
return String(self)
}
}
}
//using:
let className = YourClass.string
Switf 2 solutions
let className = String(Int.self) // String
let className2 = String(4.dynamicType) // Int
func doThings<T>(type: T.Type) -> String {
return String(T) // Whatever type passed
}