Realm-cocoa: Converting a RLMResults to [RLMObject] most efficiently

Created on 18 Oct 2014  ·  17Comments  ·  Source: realm/realm-cocoa

As of now I'm using:

extension RLMResults {
  func toArray () -> [RLMObject] {
    var array = [RLMObject]()
    for result in self {
      array.append(result)
    }
    return array
  }
}

Is there a faster way available to get this conversion? I use it with generic global functions to implicitly cast the RLMResults to the appropriate RLMObject subclass.

public func RLMQuery <T:RLMObject> (_ predicate: String = "TRUEPREDICATE") -> [T] {
  return T.objectsWithPredicate(NSPredicate(format: predicate)).toArray() as [T]
}

class Photo {
  init (disk: RLMPhoto) {}
}

// first photo in the default realm
let photo = Photo(RLMQuery().first!)

// all photos by the user
let photos: [RLMPhoto] = RLMQuery("creator == \(user.id)")

Thanks! :grin:

T-Help

Most helpful comment

@chrism21

extension RLMResults {
    func toArray<T>(ofType: T.Type) -> [T] {
        return flatMap { $0 as? T }
    }
}

All 17 comments

There isn't really a faster way to do this, as objects aren't even created until they are requested from an RLMArray during enumeration while creating your swift array.

If you don't explicitly need a swift array, it will in many cases be faster to simply operate off the RLMResults object directly as then objects are only created when actually needed. Is there a reason this approach doesn't work for you?

Thanks for the response! I think I'm just prematurely optimizing by asking this question. Sorry for the hassle.

No hassle at all. Your feedback and questions are greatly appreciated.

I tried to replicate the solution that @aleclarson showed with generics.

func toArray<T>(ofType: T.Type) -> [T] {
    var array = [T]()
    for result in self {
        if let result = result as? T {
            array.append(result)
        }
    }
    return array
}

This allows me to do:

 let tracks = Track.allObjects().toArray(Track.self) // tracks is of type [Track]

I met the same problem using Swift and found a better way of doing that, without any enumeration at all.
Of course, it can be improved a lot more! It's currently a kind of wrapper for RLMResults, but sure can be used as a pure Swift dynamic collection instead.
My initial problem was the ugly (but necessary) casting when dealing with RLMResults…

extension RLMRealm {
    func allObjects<T: RLMObject>(fromClass cls: T.Type) -> RLMSwiftyResults<T> {
        return RLMSwiftyResults<T>(results: cls.allObjectsInRealm(self))
    }
}


class RLMSwiftyResults<T: RLMObject> {
    let results: RLMResults
        init(results: RLMResults) {
            self.results = results
        }

        func objectAtIndex(i: UInt) -> T? {
            return self.results.objectAtIndex(i) as? T
        }

        // And all other methods from RLMResults…
}

let mediaItems = RLMRealm.defaultRealm().allObjects(fromClass: MediaItem.self) // mediaItems is now a RLMSwiftyResults<MediaItem>
let firstMediaItem = mediaItem.objectAtIndex(0) // firstMediaItem is now a MediaItem

I also ran into this problem today
Since swift 2.0, we can do this (based on @duemunk's solution):

extension Results {

    func toArray() -> [Results.Generator.Element] {
        var array = [T]()
        for item in self { array.append(item as Results.Generator.Element) }

        return array
    }
}

do {
    let cars = try Realm().objects(Car).toArray() //no need T.Type's paramenter here
} 
catch { 
}

EDIT: updated to use Array(_ sequence:).

let results: Results<...> // Results
let array = Array(results)

Old answer:

You could just use map:

extension Results {
    func toArray() -> [Results.Generator.Element] {
        return map { $0 }
    }
}

For those with still working with RLMObject in Swift (objc & swift projects), you can put the implementation @duemunk used into one line:

extension RLMResults {
    func toArray<T>(ofType: T.Type) -> [T] {
        return (0..<self.count).flatMap { self.objectAtIndex($0) as? T }
    }
}

Thanks @duemunk!

@chrism21

extension RLMResults {
    func toArray<T>(ofType: T.Type) -> [T] {
        return flatMap { $0 as? T }
    }
}

Even nicer, thanks! @jpsim

Edit:

Actually tried it out but get the compile error:

Cannot convert value of type '(T) -> T? to expected argument type '(_, (_) -> [_])' aka '(_, _ -> Array<_>)')

I'm using Realm for Objective-C, so RLMResults is an Objective-C class. Doesn't that mean it won't have a flapMap to begin with?

Is there a way to do this on JS?

@cmajordev sorry I never saw your last message. RLMResults conforms to SequenceType if you're using RLMSupport.swift as we recommend in our installation instructions: https://realm.io/docs/objc/latest/#installation

flatMap is defined on SequenceType.

@DHidee66 I don't know, this is the cocoa repo. You can file issues towards the realm-js repo for questions like that: https://github.com/realm/realm-js/issues/new

@jpsim just wanted to say thanks for the sweet toArray extension!

@jpsim I guess your nice extension would work with Swift 2.x only?
I get compilation error:

Extension of generic Objective-C class cannot access the class's generic parameters in runtime

I guess this is a limitation.

@tomekc just use the Array() initializer as I mentioned in https://github.com/realm/realm-cocoa/issues/1046#issuecomment-143804257

let results: Results<...> // Results
let array = Array(results)

I did this and it seemed pretty neat.

let results = Results<MyObjectModel>
var dataSource = [MyObjectModel]()
dataSource.append(contentsOf: results)

Again:

let results: Results<MyObjectModel>
let dataSource = Array(results)

But if you're finding yourself copying data out of Realm Collections, you're losing many of their advantages, such as their live updating, lazy-loading, notifications & the ability to pass them across threads via ThreadSafeReference to name a few.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

yangmeyer picture yangmeyer  ·  3Comments

dennisgec picture dennisgec  ·  3Comments

matteodanelli picture matteodanelli  ·  3Comments

ishidakei picture ishidakei  ·  3Comments

carvalho-oak picture carvalho-oak  ·  3Comments