Using generics to simplify persisting codable collections

By Ryan Romanchuk
On
import JapxCodable
import RealmSwift
import Alamofire

protocol Serviceable {
    typealias CollectionCallback = (([DecodableType]?, Error?) -> Void)?
    typealias ObjectCallback = ((DecodableType?, Error?) -> Void)?
    typealias PaginationCallback = ((_ collection: [DecodableType]?, _ moreResults: Bool, _ error: Error?) -> Void)?
    
    
    typealias ObjectDataResponse = AFDataResponse<DecodableType>
    typealias CollectionDataResponse = AFDataResponse<JapxResponse<[DecodableType]>>
    
    associatedtype RealmType: Object
    associatedtype DecodableType: JapxDecodable
}

protocol WithSaveHooks {
    func afterCreate()
}

extension Serviceable {
    /**
     This method takes a collection of `[RealmType]` resources in the form of a `[JapxDecodable]`,  and then creates
     or updates the Realm object, calls the completion block and then returns the newly saved managed realm object.

     - warning: This function will operate and return  on the same thread it was called on

     - parameter [RealmType]: The Codable representation
     - returns: Discardable `(realm, [RealmType])` tuple with realm instance and collection as managed object
     */
    @discardableResult
    static func success<K: Any>(_ unmanaged: [K]) -> (Realm, [RealmType]) {
        try! autoreleasepool {
            var managedObjects = [RealmType]()
            do {
                let realm = try Realm()
                realm.beginWrite()
                unmanaged.forEach { (unmanagedObject) in
                    let object = realm.create(RealmType.self, value: unmanagedObject, update: .modified)
                    (object as? WithSaveHooks)?.afterCreate()
                    managedObjects.append(object)
                }
                try realm.commitWrite()
                return (realm, managedObjects)
            } catch let error {
                log.error(error)
                SentrySDK.capture(error: error)
                throw error
            }
        }
    }
}
class CheckIn: Object, JapxDecodable, WithSaveHooks {
    enum CodingKeys: String, CodingKey {
        case id
    }
    
    @Persisted(primaryKey: true) var id: String
}
talk