Danny의 iOS 컨닝페이퍼
article thumbnail
Published 2022. 12. 20. 01:44
[iOS/Swift] Realm 사용법 Xcode/Library

Package Manager

https://github.com/realm/realm-swift.git

 

글을 엉망진창으로 써놔 추후에 다시 깔끔히 정리해 올릴 예정입니다. 😓 

 

간단 사용법 맛보기

1. AppDelegate에서 realm 생성

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        
    do {
        let realm = try Realm()
    } catch {
        print("Error initialising new realm \(error)")
    }
        
    return true
}

 

2. 데이터 모델 제작

import RealmSwift

// 슈퍼클라스로 Object 사용, 그 후 Realm의 객체를 정의
class Data: Object {
    // Realm을 사용하기 위해선 dynamic 키워드가 필요 (객체를 동적 디스패치를 사용하도록 지시)
    // 변수가 변할때 런타임시 모니터링 후 업데이트
    // ⭐️ 동적 디스패치는 실제로 Obj-C APId에서 제공 되므로 꼭 @objc키워드 필요
    @objc dynamic var name: String = ""
    @objc dynamic var age: Int = 0
}

 

3. 저장 

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
    
    // 데이터 베이스 주소 찾기
    print(Realm.Configuration.defaultConfiguration.fileURL)
    
    // 만든 모델 데이터에 값 저장하기
    let data = Data()
    data.name = "Daniel"
    data.age = 20
    
    // Realm으로 모델 값 저장
    do {
        let realm = try Realm()
        try realm.write {
            realm.add(data)
        }
    } catch {
        print("Error initialising new realm \(error)")
    }
    
    return true
}

 

4. CRUD (자세한 설명은 아래 예제 참고)

// Create (생성)

// 생성해 둔 객체 Results<T>를 add 값으로 사용
func saveCategorys(category: Results<T>) {
    do {
        try realm.write {
            realm.add(category)
        }
    } catch {
        print("Error save: \(error)")
    }
}
// Read (읽기)

// objects는 만들어둔 모델의 타입의 메타타입으로 사용
realm.objects(Model.self)
// Update (업데이트)

// 동작은 Create와 같다.
// 업데이트를 이와같이 컴플리션으로 묶어주면 사용하면 편리해짐
func updateItem(completion: () -> Void) {
    do {
        try realm.write {
            completion()
        }
    } catch {
        print("Error save: \(error)")
    }
}
// Delete

func deleteItme(itme: Item) {
    do {
        try realm.write {
            realm.delete(itme)
        }
    } catch {
        print("Error delete: \(error)")
    }
}

 

RelationShip을 이용한 사용법

1. AppDelegate에서 realm 생성

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        
    do {
        let realm = try Realm()
    } catch {
        print("Error initialising new realm \(error)")
    }
        
    return true
}

 

2. 모델 생성 후 RelationShip 만들기 정방향 모델 역방향 모델

Realm을 사용하기 위해선 dynamic 키워드가 필요 (객체를 동적 디스패치를 사용하도록 지시)
변수가 변할때 런타임시 모니터링 후 업데이트
⭐️ 동적 디스패치는 실제로 Obj-C API에서 제공 되므로 꼭 @objc키워드 필요

 

  • 정방향 모델
import Foundation
import RealmSwift

// 만들고 싶은 모델에 Object 채택, 그 후 Realm의 객체를 정의
class Category: Object {
    @objc dynamic var name: String = ""
    
    // 정방향 릴레이션 설정하는 방법 (배열을 만들어준다.)
    // List는 Realm에서 일종의 배열이다 (ex. Array<Int> 배열의 타입 같다)
    let items = List<Item>()
}

 

  • 역방향 모델
import Foundation
import RealmSwift

class Item: Object {
    @objc dynamic var title: String = ""
    @objc dynamic var done: Bool = false
    
    // 역방향 릴레이션 설정 방법
    // fromType: 정방향 모델의 메타타입으로 설정, property: 정방향 릴레이션에서 만들어준 배열 이름(items)
    var parentCategory = LinkingObjects(fromType: Category.self, property: "items")
}

 

3. 로컬 영역 설정(VC) - CRUD

class CategoryController: UIViewController {
    // 로컬영역에서 사용기하기 위해 실행
    // 이미 AppDelegate에서 실행시켰으므로 로컬영역에서는 try!로 간단히 사용
    let realm = try! Realm()
    
    // 데이터를 읽을 수 있게 Realm의 Results 타입으로 만들어주자
    // ⭐️ Results는 자동업데이트 컨테이너이다 (속성이 변할 때 마다 append를 자동으로 수행.)
    var categorys: Results<Category>?
    
    // Create (모델을 담는다)
    func saveCategorys(category: Category) {
        do {
            try realm.write({
                realm.add(category)
            })
        } catch {
            print("Error save: \(error)")
        }
    }
    
    // Read
    func loadCategorys() {
        // 단 한줄이면 되는데 오른손의 타입이 Results<Category> 타입인걸 명심하자
        categorys = realm.objects(Category.self)
    }
    
    // Update 동작은 Create와 같다
    func updateItem(completion: () -> Void) {
        do {
            try realm.write {
                completion()
            }
        } catch {
            print("Error save: \(error)")
        }
    }
    
-------------------------------------------------------------------------------------------------
    override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        performSegue(withIdentifier: "goToItems", sender: self)
    }
    // 데어터 전달
    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        let destivationVC = segue.destination as! ItemViewController
        if let indexPath = tableView.indexPathForSelectedRow {
            // 선택택 category의 인덱스로 불러오게 하여 selectCategory 속성을 트리거하게 만듬
            destivationVC.selectCategory = categorys?[indexPath.row]
        }
    }
}
 class ItemViewController: UIViewController {

         let realm = try! Realm()

     var todoItems: Results<Item>?

     // 데이터 전달 받기위해
     var selectCategory: Category? {
         didSet {
             loadItems()
         }
     }

         // Create (⭐️ 릴레이션 관계에 있으므로 selectCategory.items에 접근해서 추가하는 방식으로 해야된다)
         func saveItem(itmes: Item) {
         do {
             try realm.write({
                 selectCategory?.items.append(itmes)
             })
         } catch {
             print("Error save: \(error)")
         }
     }

         // Read
         func loadItems() {
         // category의 relationship인 itmes를 불러온다
         todoItems = selectCategory?.items.sorted(byKeyPath: "title", ascending: true)
     }

      // Update 동작은 Create와 같다
          func updateItem(completion: () -> Void) {
         do {
             try realm.write {
                                 // 업데이트 조건
                 completion()
             }
         } catch {
             print("Error update: \(error)")
         }
     }

         // delete
     func deleteItme(itme: Item) {
         do {
             try realm.write {
                 realm.delete(itme)
             }
         } catch {
             print("Error delete: \(error)")
         }
     }
 // 데이터 베이스 주소 찾기
 print(Realm.Configuration.defaultConfiguration.fileURL)

 

 

싱글톤으로 CRUD 구현

import UIKit
import RealmSwift

protocol DataBase {
    func read<T: Object>(_ object: T.Type) -> Results<T>
    func write<T: Object>(_ object: T)
    func delete<T: Object>(_ object: T)
    func sort<T: Object>(_ object: T.Type, by keyPath: String, ascending: Bool) -> Results<T>
}

final class DataBaseManager: DataBase {

    static let shared = DataBaseManager()

    private let database: Realm

    private init() {
        self.database = try! Realm()
    }

    func getLocationOfDefaultRealm() {
        print("Realm is located at:", database.configuration.fileURL!)
    }

    func read<T: Object>(_ object: T.Type) -> Results<T> {
        return database.objects(object)
    }

    func write<T: Object>(_ object: T) {
        do {
            try database.write {
                database.add(object, update: .modified)
                print("New Item")
            }

        } catch let error {
            print(error)
        }
    }

    func update<T: Object>(_ object: T, completion: @escaping ((T) -> ())) {
        do {
            try database.write {
                completion(object)
            }

        } catch let error {
            print(error)
        }
    }

    func delete<T: Object>(_ object: T) {
        do {
            try database.write {
                database.delete(object)
                print("Delete Success")
            }

        } catch let error {
            print(error)
        }
    }

    func sort<T: Object>(_ object: T.Type, by keyPath: String, ascending: Bool = true) -> Results<T> {
        return database.objects(object).sorted(byKeyPath: keyPath, ascending: ascending)
    }
}
// 싱글톤 객체 가져오기
private let database = DataBaseManager.shared

// Realm 파일 위치 가져오기
database.getLocationOfDefaultRealm()

// Create
let task = Shopping(title: inputTextField.text!, createdAt: Date())
database.write(task)

// Read
shoppingList = database.read(Shopping.self)

// Update
let model = RealmManager.shared.read(RealmDataModel.self)[indexPath.row]
RealmManager.shared.update(model) { model in
    model.mainLoad = true
}

// Sort
tasks = database.sort(Shopping.self, by: "title")

// Delete
database.delete(self.shoppingList[indexPath.row])

 

 

Cheat Sheet

필터링

func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
    
    todoItems = todoItems?.filter("title CONTAINS[cd] %@", searchBar.text!).sorted(byKeyPath: "title", ascending: true)
}

컬러 저장할 때는 hexValue로 저장하여 데이터 저장하는 게 편하다.

[iOS/Swift] UIColor (RGB대신 HEX Color 이용하기) 를 참고해 주세요.

view.backgroundColor = UIColor(hexString: <String>)

async를 사용할 때

  • 이유는 모르겠지만 realm사용 시 클로저 안에다 비동기처리를 하면 앱이 다운이 된다. 오직 메인스레드에서만 돌려야 한다.
  • 아래와 같이 메인으로 감싼 후 사용하면 작동됨
func setupWeatherList() {
    DispatchQueue.main.async {
        self.read(RealmDataModel.self).forEach { model in
            if model.loadMain == true {
                Task {
                    await WeatherManager.shared.eachWeatherData(lat: model.lat, lon: model.lon)
                }
            }
        }
    }
}
  • Task는 비동기 작업이다. 그러므로 awiat에선 완료된 순서대로 데이터를 받으므로 순서가 보장이 안된다.
  • 번거롭더라도 새로운 변수에 담아서 사용해야 한다. (or 새로운 모델을 만들어 사용)
  • 동기와 비동기 코드를 순서보장을 받고 싶을 땐 AsyncSquense를 사용하는 것이 좋은 방법 중 하나라고 생각한다.
func setupWeatherList() {
    var list: [CLLocation] = []
    let weatherData = RealmManager.shared.sort(RealmDataModel.self, by: "date")
    weatherData.forEach { result in
        list.append(CLLocation(latitude: result.lat, longitude: result.lon))
    }
    let data = AsyncStream<CLLocation> { continuation in
        for location in list {
            continuation.yield(location)
        }
        continuation.finish()
    }
    Task {
        for await location in data {
            let coordinate = location.coordinate
            await self.eachWeatherData(lat: coordinate.latitude, lon: coordinate.longitude)
        }
    }
}
반응형
profile

Danny의 iOS 컨닝페이퍼

@Danny's iOS

포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!