Looping through NSMutableArray in Swift - ios

How can Ioop through an NSMutableArray in Swift? What I have tried:
var vehicles = NSMutableArray()
The array contains objects from class: Vehicle
for vehicle in vehicles {
println(vehicle.registration)
}
I cannot run the above code without the compiler telling me registration doesn't belong to AnyObject. At this point I assumed that was because I hadn't told the for loop what type of class item belongs to. So I modified by code:
for vehicle: Vehicle in vehicles {
println(vehicle.registration)
}
Now the compiler complains about downcasting... how can I simply gain access to the custom registration property whilst looping through the array of Vehicles?

This should work:
for vehicle in (vehicles as NSArray as! [Vehicle]) {
println(vehicle.registration)
}

As Romain suggested, you can use Swift array. If you continue to use NSMutableArray, you could do either:
for object in vehicles {
if let vehicle = object as? Vehicle {
print(vehicle.registration)
}
}
or, you can force unwrap it, using a where qualifier to protect yourself against cast failures:
for vehicle in vehicles where vehicle is Vehicle {
print((vehicle as! Vehicle).registration)
}
or, you can use functional patterns:
vehicles.compactMap { $0 as? Vehicle }
.forEach { vehicle in
print(vehicle.registration)
}
Obviously, if possible, the question is whether you can retire NSMutableArray and use Array<Vehicle> (aka [Vehicle]) instead. So, instead of:
let vehicles = NSMutableArray()
You can do:
var vehicles: [Vehicle] = []
Then, you can do things like:
for vehicle in vehicles {
print(vehicle.registration)
}
Sometimes we're stuck with Objective-C code that's returning NSMutableArray objects, but if this NSMutableArray was created in Swift, it's probably preferable to use Array<Vehicle> instead.

NSMutableArray comes from the Objective-C world. Now you can use generics and strongly-typed arrays, like this:
var vehicles = [Vehicle]()
...
for vehicle in vehicles {
println(vehicle.registration)
}

Related

Swift Realm - get types of all database objects

I have a list of various objects in my Realm Database all of which are created as default ClassName: Object classes. Is there any way to get types (classes names) of those objects, that can be saved as variables, or create an enum of these types?
The issue is that Realm Results objects are homogenous - they only contain one object type. That translates to meaning you will never have a situation where Results would contain Person and Dog classes at the same time.
That being said we have the option of using the Realm AnyRealmValue
Let me set this up: Suppose you have a PersonClass and a DogClass
let dog = DogClass()
let person = PersonClass()
and a class with a List that can contain AnyRealmValue
class MyClass: Object {
#Persisted var myList = List<AnyRealmValue>()
}
when then need to cast those objects to AnyRealmValue to make this work
let obj0: AnyRealmValue = .object(dog)
let obj1: AnyRealmValue = .object(person)
and we add those to the List
let m = MyClass()
m.myList.append(obj0)
m.myList.append(obj1)
You mentioned switch but here's a simple if...then clause to handle them differently - depending on the class
if let person = item.object(PersonClass.self) {
print("is a person")
} else if let dog = item.object(DogClass.self) {
print("is a dog")
}

How can I properly copy objects from one Realm object to another object

Based on the following code I would like to be able to create a new ItemList from an existing one. In other words I have an ItemList called First List and I want to create a new ItemList, call it Second List and fill it with the Items from First List.
The way I have it right now is that it creates the Second List as expected, the Items from the First List show in Second List but what doesn't work is when I want to delete only the Items from First List, it deletes Items from both lists. I guess I'm not truly copying the items.
So the question is, how can I copy Items from First List to Second List?
Object Models:
class ItemList: Object {
dynamic var listName = ""
dynamic var createdAt = NSDate()
let items = List<Item>()
}
class Item:Object{
dynamic var productName:String = ""
dynamic var createdAt = NSDate()
}
Code to create Second List from First List
This Works fine, it creates Second List and adds the items from First List but I don't think I'm making copies just showing them in Second List.
let newList = ItemList()
newList.listName = "Second List"
if let selectedList = realm.objects(ItemList.self).filter("listName = %#", "First List").first{
let itemsFromFirstList = selectedList.items
newList.items.append(objectsIn:itemsFromFirstList)
}
try! realm.write {
realm.add(newList)
}
This code is supposed to delete only the items from First List
This actually deletes items from both First List and Second List
let listToDelete = realm.objects(ItemList.self).filter("listName = %#", "First List").first
try! realm.write {
for item in (listToDelete?.items)! {
realm.delete(realm.objects(Item.self).filter("productName = %#", item.productName).first!)
}
}
What you want to do is use:
for record in postsDB.objects(PostModel.self) {
if !combinedDB.objects(PostModel.self).filter("postId == \(record.parentId)").isEmpty {
combinedDB.create(PostModel.self, value: record, update: false)
}
}
The create method is inherited from Object. It tells the target to create a new object. Use true if you want it to look to see if there is already a record there, and update it if there is.
PostModel is the Object type, record is what you want copied.
Edit: I added the if statement to provide more context. You didn't show your class definitions, so I was guessing. This is a working example. I ask for a set of records from DatabaseA and copy it to DatabaseB (postsDB to combinedDB).
So if the type of the object you're trying to insert is a List, I'd recommend you define a subclass of Object, and have at least the list you need as a property.
class TagList: Object {
dynamic var tag = ""
var list = List<PostModel>()
override class func primaryKey() -> String? {
return "tag"
}
}
Full working example illustrating: creating new objects, copying all objects to a second list, deleting from second list after copying, adding to first list (which didn't get anything deleted from it.
import Foundation
import RealmSwift
class Letter: Object {
dynamic var letter = "a"
}
class Letters: Object {
var letterList = List<Letter>()
}
class ListExample {
let listRealmStore = try! Realm() // swiftlint:disable:this force_try
func testThis() {
print(Realm.Configuration.defaultConfiguration.fileURL!)
listRealmStore.beginWrite()
addSingleItems() // add 3 objects to the DB
let firstList = Letters()
let allObjects = listRealmStore.objects(Letter.self)
for item in allObjects {
firstList.letterList.append(item)
}
let secondList = Letters()
let itemsToCopy = firstList.letterList
for item in itemsToCopy {
let obj = listRealmStore.create(Letter.self)
obj.letter = item.letter
secondList.letterList.append(obj)
}
let third = Letter()
third.letter = "Z"
listRealmStore.add(third)
firstList.letterList.append(third)
secondList.letterList.removeLast()
do {
try listRealmStore.commitWrite()
} catch let error {
print("couldn't commit db writes: \(error.localizedDescription)")
}
print("list one:\n\(firstList)")
print("list two:\n\(secondList)")
}
func addSingleItems() {
for letter in ["a", "b", "c"] {
let objectToInsert = Letter()
objectToInsert.letter = letter
listRealmStore.add(objectToInsert)
}
}
}
Results in:
list one:
Letters {
letterList = List<Letter> (
[0] Letter {
letter = a;
},
[1] Letter {
letter = b;
},
[2] Letter {
letter = c;
},
[3] Letter {
letter = Z;
}
);
}
list two:
Letters {
letterList = List<Letter> (
[0] Letter {
letter = a;
},
[1] Letter {
letter = b;
}
);
}
Are you really trying to create copies of your items, or do you just want to be able to remove them from lists independently?
When you do:
newList.items.append(objectsIn: itemsFromFirstList)
you end up with the same objects being in both lists. List<T> just stores references to objects that live within the Realm. Appending an object to a List just references the existing object, it doesn't copy the object.
When you call Realm.delete(_:) you remove that object entirely from the Realm, not just from a single list that it is a member of. To remove an object from a List, you should instead use List.remove(objectAtIndex:).
One part the solution you are looking for could be like this, make copy objects in the list, or you can just use this idea to clone whole list it self:
Previously answered here
As of now, Dec 2020, there is not 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 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 Note")
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.

Swift: Store multiple Types in a single array

How do I store multiple Types in a single array?
I'm doing the following:
var vehicles = [string]()
let mustang = Car() // car is a class name
mustang.brandName = "Ford"
mustang.modelName = "Mustang" // string type
mustang.modelYear = 1968
mustang.isConvertibible = true
mustang.isHatchback = false
mustang.hasSunroof = false // bool type
mustang.numberOfDoors = 2 // int type
mustang.powerSource = "gas engine"
// Add it to array
vehicles.append(mustang.brandName)
vehicles.append(mustang.modelName)
vehicles.append(mustang.modelYear) // Error: int type not supported
vehicles.append(mustang.isConvertibible) // Error: bool type not supported
vehicles.append(mustang.brandName)
vehicles.append(mustang.brandName)
How should I achieve this? I'm new to Swift / iOS.
Instead of creating string array you can create the array of car like this and store directly car object
var cars = [Car]()
let mustang = Car() // car is a class name
mustang.brandName = "Ford"
mustang.modelName = "Mustang" // string type
mustang.modelYear = 1968
mustang.isConvertibible = true
mustang.isHatchback = false
mustang.hasSunroof = false // bool type
mustang.numberOfDoors = 2 // int type
mustang.powerSource = "gas engine"
cars.append(mustang)
Or if you want to store different types of object then you cant create array of AnyObject, So that it will store Any type of instance or object inside that array.
var arr = [AnyObject]()
let car = Car()
let bike = Bike()
arr.append(car)
arr.append(bike)
Swift 5
Using protocol oriented programming
A little more swifty solution could be to use protocols. This is type safe, so you won't have to deal with unwrapping and casting optionals.
1. Define a protocol
protocol ModeOfTransportation {
// Define anything in common between objects
// Example:
var color: UIColor { get set }
}
2. Make your models conform to the protocol
(The compiler will ensure you give it the correct types: ex. color)
struct Car: ModeOfTransportation {
var color = UIColor.red
}
struct Bicycle: ModeOfTransportation {
var color = UIColor.blue
}
3. Give your array the protocol as a type
class Example {
var myTransportList: [ModeOfTransportation] = []
func addModesOfTransport() {
let car = Car()
let bike = Bicycle()
myTransportList.append(car)
myTransportList.append(bike)
}
}
I think you're doing something really wrong.
Arrays are designed to hold only one type of stuff. If you really want to hold different types of stuff. Here's some possible methods:
you can try creating an array of AnyObjects:
-
var vehicles = [AnyObject]()
This won't work if the type you want to store does not conform to AnyObject. If you have such a type, you will have to use Any.
you can create an array of strings and convert all the values you want to store to a string
You can just create an array of Cars to store all the properties of a car. Then you can add more cars to it later on. I think this is what you intended.
Per the Swift documentation:
AnyObject can represent an instance of any class type.
Any can represent an instance of any type at all, including function types.
So you could make an array of
var vehicles = [AnyObject]()
and this would take objects (class instances) of any type.
However, the documentation goes on to say:
Use Any and AnyObject only when you explicitly need the behavior and capabilities they provide. It is always better to be specific about the types you expect to work with in your code.
So, ideally you are specific about the type your array can hold.

Efficient way to filter array of Person objects based on an array of string names

I have an array of names:
var namesArray = ["Bert","Tony","Phil","George", "David"]
I then have an array of Person Objects:
var personsArray: [Person]
And a snippet of my Person class is:
class Person {
var name: String
...some code omitted...
}
I am looking for a way to filter my array of Persons objects to only include the Person whos name is found in the namesArray.
I considered using the .filter on the array but I need to loop over two arrays.
let filterByNameArray = persons.filter({
($0.name == //string)!
})
But I believe this is incorrect as I need to loop through the names array also. I solved my issue using a double for loop:
var pArray: [Person] = []
for person in personsArray {
for nameString in namesArray {
if person.name == nameString {
pArray.append(person)
}
}
}
However, this is ugly and uses a significant amount of CPU so my question is,is there a more efficient way to do this? :) Im sure there is.
Use the contains method on the namesArray to search all of it.
let filteredByNameArray = persons.filter {
namesArray.contains($0.name)
}

Storing an array of strings using Realm's RLMArray

Does anyone know how you can use Realm to store an array of strings? I'm trying to map the following response into Realm correctly:
"zoneInfo": {
"tariffInfoLines": [
"In this city you pay per minute."
]
}
We have a zoneInfo object that contains a tariffInfoLines array. This tariffInfoLines array contains strings. In Realm there are two different variable types for storing data. The first is RLMObject which allows your standard NSString, int, long etc.
The second type is RLMArray, which is used for arrays (as NSArray is not supported). You have to give the array a type, which must be a class that subclasses RLMObject. We have so far got around this by using a ABCRealmString object, as shown below:
#property RLMArray<ABCRealmString> *tariffInfoLines;
ABCRealmString contains an NSString property (it is basically a wrapper):
#property NSString *value;
However what this means is that when Realm tries to map the response to persist the data, it is looking for a value for the key "value" (the name of the property). It appears that it expects a response similar to the following:
"zoneInfo": {
"tariffInfoLines": [
{
"value": "In this city you pay per minute."
},
]
}
In the project, we have it working for the following structure:
"userOptions": [
{
"wantsEmailNotifications": true,
"wantsPushNotifications": false
},
]
This has an array, with objects inside that have clear key value pairs that Realm can map to. The zoneInfo structure appears to be the only place that we have an array with sets of values inside without them being inside an object or having any keys.
If anyone could shed some light on this, regarding if this is possible using Realm, or whether an API change is required to match a structure that Realm can map.
Cross posting from the github issue response: Although this example demonstrates how to store flat arrays of strings on a Realm model, you can extend this pattern to store anything from arrays of integers to native Swift enum's. Basically anything that you can map to a representable type in Realm.
class RealmString: Object {
dynamic var stringValue = ""
}
class Person: Object {
var nicknames: [String] {
get {
return _backingNickNames.map { $0.stringValue }
}
set {
_backingNickNames.removeAll()
_backingNickNames.appendContentsOf(newValue.map({ RealmString(value: [$0]) }))
}
}
let _backingNickNames = List<RealmString>()
override static func ignoredProperties() -> [String] {
return ["nicknames"]
}
}
// Usage...
let realm = try! Realm()
try! realm.write {
let person = Person()
person.nicknames = ["John", "Johnny"]
realm.add(person)
}
for person in realm.objects(Person) {
print("Person's nicknames: \(person.nicknames)")
}
// Prints:
// Person's nicknames: ["John", "Johnny"]
UPDATE (most of the previous answers are no longer correct):
You can now store primitive types or their nullable counterparts (more specifically: booleans, integer and floating-point number types, strings, dates, and data) directly within RLMArrays or Lists. If you want to define a list of such primitive values you no longer need to define cumbersome single-field wrapper objects. Instead, you can just store the primitive values themselves.
Lists of primitive values work much the same way as lists containing objects, as the example below demonstrates for Swift:
class Student : Object {
#objc dynamic var name: String = ""
let testScores = List<Int>()
}
// Retrieve a student.
let realm = try! Realm()
let bob = realm.objects(Student.self).filter("name = 'Bob'").first!
// Give him a few test scores, and then print his average score.
try! realm.write {
bob.testScores.removeAll()
bob.testScores.append(94)
bob.testScores.append(89)
bob.testScores.append(96)
}
print("\(bob.testScores.average()!)") // 93.0
All other languages supported by Realm also supports lists of primitive types.
For the Swift 3.0 here is the change (in my case the Xcode 8 compiler didn't offer auto fix when i switched to swift 3.0 so I had some pain to resolve it).
_backingNickNames.append(objectsIn: newValue.map { RealmString(value: [$0]) })
The RealmString approach is good, but you end up with a new RealmString every time you update the values, leaving a ton of unused objects laying around if you don't clean them up.
I would suggest using something like:
fileprivate let separator = "\u{FFFF}"
class Person: Object {
fileprivate dynamic var _nicknames: String?
var nicknames: [String] {
get { return _nicknames?.components(separatedBy: separator) ?? [] }
set { _nicknames = newValue.isEmpty ? nil : newValue.joined(separator: separator) }
}
override static func ignoredProperties() -> [String] {
return ["nicknames"]
}
}
extension String {
func toStringObject() -> StringObject {
return StringObject(initValue: self)
}
}
extension Sequence where Iterator.Element == String {
func toStringObjects() -> List<StringObject> {
let list = List<StringObject>()
for s in self {
list.append(s.toStringObject())
}
return list
}
}
extension Int {
func toIntObject() -> IntObject {
return IntObject(initValue: self)
}
}
extension Sequence where Iterator.Element == Int {
func toIntObjects() -> List<IntObject> {
let list = List<IntObject>()
for s in self {
list.append(s.toIntObject())
}
return list
}
}

Resources