ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [iOS_MyInventory] Realm 적용 2 - Struct 와 같이 사용하기
    Project/[release] iOS - MyInventory 2020. 3. 21. 11:02

    Realm 에서 지원하는 객체모델은 다음과 같이 class 형태로 작성을 해야한다.

    class Dog: Object {
        @objc dynamic var name = ""
        @objc dynamic var age = 0
    }

     

     

     

    Realm 객체모델을  구조체로(struct) 사용하기 위해서는,

    struct <---> RealmObject 를 맵핑하는 중간다리가 필요하다.

    이를 Persistable 프로토콜이 담당한다.

    public protocol Persistable {
        
        associatedtype ManagedObject: RealmSwift.Object
        
        // RealmObject -> Struct 변환
        init(managedObject: ManagedObject)
        
        // Struct -> RealmObject
        func managedObject() -> ManagedObject
    }

     

     

    Realm모델에 따른 struct 모델을 구성한다.

    새로운 Person 객체 생성시, 내부에서 id 를 1씩 증가해 나가므로, Person 에 해당하는 데이터만 넣어주면 된다.

    그리고 Person Struct 는  Persistable 을 준수하여, Realm 객체를 Struct 로, 또는 Struct 를 Realm 객체로 변환하게 된다. 

    @objcMembers
    class RealmPerson: Object {
        dynamic var id: Int = 0
        dynamic var firstName: String = ""
        dynamic var lastName: String = ""
        var gender = RealmOptional<Int>(0)
        var age = RealmOptional<Int>(0)
        dynamic var myImage: Data? = nil
        
        override static func primaryKey() -> String? {
            return "id"
        }
    }
    
    
    struct Person {
        public let id: Int
        public let firstName: String
        public let lastName: String
        public let gender: Int?
        public let age: Int?
        public let myImage: UIImage?
        
        public init(firstName: String, lastName: String, gender: Int?, age: Int?, myImage: UIImage? = nil) {
            self.id = try! RealmManager().incrementID()
            self.firstName = firstName
            self.lastName = lastName
            self.gender = gender
            self.age = age
            self.myImage = myImage
        }
    }
    
    
    //MARK: Struct To RealmObject
    extension Person: Persistable {
        // RealmObject -> Struct
        public init(managedObject: RealmPerson) {
            self.id = managedObject.id
            self.firstName = managedObject.firstName
            self.lastName = managedObject.lastName
            self.gender = (managedObject.gender as? Int ?? nil)
            self.age = (managedObject.age as? Int ?? nil)
            
            if let imageData = managedObject.myImage {
                self.myImage = UIImage(data: imageData)
            } else {
                self.myImage = nil
            }
        }
        
        // Struct -> RealmObject
        public func managedObject() -> RealmPerson {
            let person = RealmPerson()
            person.id = self.id
            person.firstName = self.firstName
            person.lastName = self.lastName
            person.age = RealmOptional<Int>(self.age)
            person.gender = RealmOptional<Int>(self.gender)
            person.myImage = self.myImage?.pngData()
            return person
        }
    }
    

     

     

    그리고 Realm객체 변경을 담당하는 WriteTransaction Class 를 작성한다.

    데이터 삽입작업(add) 을 할경우, 

    Persistable 을 준수하는 모델(struct)을 받아서,  Realm객체로 변경하여 데이터 삽입을 하게된다.

    public final class WriteTransaction {
        private let realm: Realm
        
        internal init(realm: Realm) {
            self.realm = realm
        }
        
        // Add newObject
        public func add<T: Persistable>(_ value: T) {
            realm.add(value.managedObject())
        }
    }

     

     

     

    마지막으로, realm에 관한 사용자 작업을 담당하는  RealmManager Class 를 수정한다.

    여기서는 realm 인스턴스를 생성하고, 이 인스턴스를 통해 CRUD에 관련된 트랜젝션을 실행한다. 

    (모든 Realm 객체변경은 트랜젝션 내부에서 실행해야 한다)

    /**
    * RealmManager Class
    */
       internal static func realm() -> Realm? {
            do {
                return try Realm()
            } catch {
                print(error.localizedDescription)
            }
            return nil
        }
        
        
        public func write(_ block: (WriteTransaction) throws -> Void)
        throws {
            guard let realm = realm() else { return }
            
            let transaction = WriteTransaction(realm: realm)
            try realm.write {
                try block(transaction)
            }
        }
        
        func incrementID() -> Int {
            guard let realm = realm() else { return 0 }
            return (realm.objects(RealmPerson.self).max(ofProperty: "id") as Int? ?? 0) + 1
        }
        
        public static let DBPath: URL? = {
            let appGroupID: String = K_GROUP_ID
            
            guard let groupDir: URL = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: appGroupID) else {
                return nil
            }
            
            let realmPath = groupDir.appendingPathComponent(K_DB_NAME)
            return realmPath
        }()

     

     

     

    ViewController 에서 데이터 추가를 하려면 이렇게 작성하면 된다.

    let person = Person(firstName: "트랜잭션", lastName: "테스트", gender: 0, age: 22, myImage: UIImage(named: "IMG_3966"))
    let realmManager = try! RealmManager()
    try! realmManager.write { transaction in
         print(person)
         transaction.add(person)
    }

     

    데이터 삽입 결과

     

     

     

     

    기존의 struct 모델을 그대로 사용하여 realm객체에 맵핑할 수 있다는 장점이 있지만,

    이를 위해서 중간에 protocol, generic, 함수형 프로그래밍을 통한, 재사용성과 확장성을 생각해야만 한다.

     

     

     

     

     

     

    참고자료

    https://medium.com/@ludovicjamet/how-to-use-struct-with-realm-615fcbc8f0ee

     

    How to use struct with Realm ?

    By default, Realm Objects are classes and not structs because they are not values, but auto-updating objects pointing to data in Realm…

    medium.com

    댓글

Designed by Tistory.