Using generics to simplify persisting codable collections

By Ryan Romanchuk
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
    static func success<K: Any>(_ unmanaged: [K]) -> (Realm, [RealmType]) {
        try! autoreleasepool {
            var managedObjects = [RealmType]()
            do {
                let realm = try Realm()
                unmanaged.forEach { (unmanagedObject) in
                    let object = realm.create(RealmType.self, value: unmanagedObject, update: .modified)
                    (object as? WithSaveHooks)?.afterCreate()
                try realm.commitWrite()
                return (realm, managedObjects)
            } catch let error {
                SentrySDK.capture(error: error)
                throw error
class CheckIn: Object, JapxDecodable, WithSaveHooks {
    enum CodingKeys: String, CodingKey {
        case id
    @Persisted(primaryKey: true) var id: String