apple/iOS

[iOS] UICollectionView에 대해서 알아보기 6편 (Realm, async, await, Delegate)

lgvv 2022. 9. 4. 14:19

UICollectionView에 대해서 알아보기 6편 (Realm, async, await, Delegate)

 

드디어 6편이다!

올해 봄, 기본기의 부족함을 너무 크게 느껴서, 기본기부터 다시 돌아보는 시간을 가졌는데, 이번에 Realm과 FelxLayout, PinLayout 등을 학습하면서 확실히 이전보다 더 빠르고 잘(?) 이해가 되는 것 같다.

AppleCollectionView.zip
2.70MB

모든 코드는 SPM으로 빌드가 가능한 상태로 올려드립니다.

 

 

(주요내용)

이번에는 Realm과 Delegate를 활용해서 어떻게 처리하는지 알아보고

async, await을 활용해서 클로저를 없애고, RxSwift랑 잘 묶어보자!

 

 

🌿 UI 결과물 🌿 

첫번째에서 세번째까지 셀 컨텐츠 내용이 업데이트 되었다.

 

셀 내에 업데이트 버튼이 위치한다. 

 

즉, 셀 내에서 CRUD작업 중 UD에 해당하는 작업을 처리한다. (CR은 ViewController가 처리)

 

우선 모델을 설계했는데, 나는 Note모델 내에 Realm을 달고 CRUD 함수를 모두 구현했다.

따라서 Note 모델과 관련한 데이터 처리는 Note모델을 참조하도록 하면 더 구조가 깔끔하지 않을까 생각했다.

(위의 초록색의 내용은 제 생각입니다! 아직 제대로 클린 아키텍쳐에 대해서 학습하고 적용해 본 경험이 부족합니다.)

 

Realm의 Note 모델을 같이보자.

 

🌿 Note 모델

//
//  Note.swift
//  AppleCollectionView
//
//  Created by Hamlit Jason on 2022/09/01.
//

import Foundation
import RealmSwift

class Note: Object {
    private static let realm = try! Realm()
    
    /// 고유 id
    @Persisted(primaryKey: true) var id: String = UUID().uuidString
    
    /// 기록하는 시간
    @Persisted var date: Date = Date()
    /// 노트의 아이디
    @Persisted var title: String
    /// 노트의 내용
    @Persisted var content: String
    
}

extension Note {
    /// 랜덤한 노트를 생성
    static func createNote(action : (() -> Void)? = nil) {
        let title = String.createRandomString(length: 5)
        let content = String.createRandomString(length: 30)
        
        let note = Note()
        note.title = title
        note.content = content
        
        
        try! realm.write {
            realm.add(note)
        }
        
        action?()
    }
    
    /// 노트에 해당하는 정보를 읽음
    static func readNote() -> Results<Note> {
         return realm.objects(Note.self)
    }
    
    /// 해당 노트를 업데이트
    static func updateNote(with note: Note, indexPath: IndexPath) async throws -> Void {
        let note = note
        
        // NOTE: - Realm은 이렇게해서 업데이트가 가능하다.
        try! realm.write {
            print("🎉 Realm의 update는 어떤 스레드에서? \(Thread.current)")
            note.content = String.createRandomString(length: 30)
            
            return
        }
    }
    
    /// 해당 노트를 삭제
    static func deleteNote(with note: Note, action : (() -> Void)? = nil) {
        try! realm.write {
            realm.delete(note)
        }
        
        action?()
    }
}

 

Realm을 사용할때 @objc dynamic이라는 포스팅이 많이 보였는데 최신 버전에서는 @Persisted를 사용한다.

10.10 버전이후 @persisted 사용

 

// RealmSwift Version 10.10 이상
@Persisted

// RealmSwift 👋 sayGoodBye
@objc dynamic

primaryKey도 그렇고 propertyWrapper를 사용하는게 더 swifty하다!

근데, SwiftUI에서 SceneDelegate의 라이프 사이클을 소괄호에 값을 넣어주던데 그거랑 형태가 닮았다!

SDK 만드는게 3000만큼 재미있는데, 나도 저런 형태 하나 만들어 봐야겠다.

 

각설하고, 다시 글로 돌아가자면 모델 내에 realm을 인스턴스를 생성하고, 해당 모델의 extension에서 CRUD를 처리한다. 그리고 컴플리션 핸들러를 작성해주는데, 그 이유는 해당 작업이 끝나고 해당 메소드를 호출한 장소에서 수행해야하는 작업이 있을 수도 있기 때문이다.

 

 

🌿 async await 부분을 조금 더 자세히 보자.

	/// 해당 노트를 업데이트
    static func updateNote(with note: Note, indexPath: IndexPath) async throws -> Void {
        let note = note
        
        // NOTE: - Realm은 이렇게해서 업데이트가 가능하다.
        try! realm.write {
            print("🎉 Realm의 update는 어떤 스레드에서? \(Thread.current)")
            note.content = String.createRandomString(length: 30)
            
            return
        }
    }

업데이트 부분에 처음으로 async await을 적용해 보았다.

 

업데이트는 위의 UI를 보면 알겠지만 Cell 내에서 이벤트를 처리하고 있다.

        updateButton.rx.tap
            .debug("🌿 update")
            .withUnretained(self)
            .bind { owner, _ in
                Task {
                    await owner.noteCellDelegate?.noteCell(updateNote: owner.note, indexPath: owner.indexPath)
                }
            }
            .disposed(by: disposeBag)

아 이걸 사용하려고 하니까 Cell의 업데이트를 ViewController로 delegate를 전달해주어야 하는데, 왜냐하면 CollectionView를 업데이트 해줘야 하기 때문!

 

프로포콜은 다음과 같이 작성해 주었다.

/// NOTE: - AnyObject는 클래스만 사용가능한 프로토콜
protocol NoteCellDelegate: AnyObject {
    /// 노트셀에서 노트 삭제
    func noteCell(deleteNote note: Note)
    /// 노트셀에서 노트 업데이트
    func noteCell(updateNote note: Note, indexPath: IndexPath) async
}

 

네이밍을 어떻게 하는게 최선인지는 아직 가이드라인은 없지만, 최대한 Apple의 내장 프레임워크를 뜯어보면서 구조를 어떻게 잡고 네이밍은 어떻게 했고, 폴더 배치는 어떻게 하는지 찾아보면서 작성하고 있다.

해당 부분은 tableView나 collectionView의 delegate 메소드와 동일한 형태로 작성했다.

 

근데 update를 보면 async를 사용했다.

 

그래서 collectionView를 들고있는 NoteViewContoller를 보면 이렇게 작성했다.

extension NoteViewController: NoteCellDelegate {
    func noteCell(updateNote note: Note, indexPath: IndexPath) async {        
        try? await Note.updateNote(with: note, indexPath: indexPath)
        self.collectionView.reloadItems(at: [indexPath])
    }
    
    func noteCell(deleteNote note: Note) {
        Note.deleteNote(with: note) { [unowned self] in
            self.collectionView.reloadData()
        }
    }
}

중요한 것은 collectionView의 reloadData()를 사용하지 않고, reloadItems를 사용하고 있다.

셀 자체를 전부 다시그리는 것 보다 이게 더 성능이 GOOD

 

사실 cell 하나만 업데이트 하는 메소드가 있다는 것은 몰랐는데, 다른 분 블로그를 읽다가 알게 되었음!

다른 분들 글도 꼼꼼하게 읽자 !_! 정말 유익하다.

 

마지막으로 async await을 이용해서 프로젝트의 SDK 부분을 전부 바꿔보고 싶다.

 

 

 

 

 

 

 

(참고)

https://twitter.com/realm/status/1414633070683115527

 

트위터에서 즐기는 Realm

“#ObjectiveC no more – we’ve replaced our @objc dynamic var property syntax with a #swift property wrapper called Persisted – we think it’s more modern and swifty, what do you think?”

twitter.com

https://velog.io/@yoonjong/Swift-Realm-%EC%8B%9C%EC%9E%91%ED%95%98%EA%B8%B0

 

Swift Realm 시작하기

기존에 나와있는 (적어도 내가 본) 모든 Realm에 관한 글들이 다 레거시 버전이고, 문법 또한 최신 버전과 호환이 되지 않아 공식문서를 보며 살짝 정리해봤다.

velog.io

https://roniruny.tistory.com/236

 

[Swift] Realm 진짜.그냥.간단.정리

Realm 진짜.그냥.간단.정리 realm 파일에 접근하기 let localRealm = try! Realm() 데이터 저장하기 let task = UserDiary(~) try! = localRealm.write { localReam.add(task) } 데이터 가져오기 realm에서 읽어..

roniruny.tistory.com

https://roniruny.tistory.com/233?category=906090 

 

[iOS] Realm 기본 설계 + Realm에 저장하고 가져오기

Database https://www.mongodb.com/docs/realm/sdk/swift/quick-start/ 데이터베이스 : 데이터를 저장한 파일들의 집합체 - DBMS : DataBase Management System : 데이터베이스를 관리하기 위한 소프트웨어, 엑셀..

roniruny.tistory.com

https://www.hackingwithswift.com/quick-start/concurrency/how-to-fix-the-error-async-call-in-a-function-that-does-not-support-concurrency

 

How to fix the error “async call in a function that does not support concurrency” - a free Swift Concurrency by Example tuto

Was this page useful? Let us know! 1 2 3 4 5

www.hackingwithswift.com

https://velog.io/@yoonjong/Swift-Realm-%EC%8B%9C%EC%9E%91%ED%95%98%EA%B8%B0

 

Swift Realm 시작하기

기존에 나와있는 (적어도 내가 본) 모든 Realm에 관한 글들이 다 레거시 버전이고, 문법 또한 최신 버전과 호환이 되지 않아 공식문서를 보며 살짝 정리해봤다.

velog.io

https://www.mongodb.com/docs/realm/sdk/swift/crud/filter-data/

 

Filter Data - Swift SDK — Realm

String sorting and case-insensitive queries are only supported for character sets in 'Latin Basic', 'Latin Supplement', 'Latin Extended A', and 'Latin Extended B' (UTF-8 range 0-591).

www.mongodb.com

https://zeddios.tistory.com/233

 

함수(Function) VS 메소드(Method)

안녕하세요 :) Zedd입니다. 갑자기 Swift에서 함수와 메소드의 명칭..? 언제 함수라고 불러야하고 메소드라고 불러야하는지 제가 정확히 개념을 모르는 것 같아서 정리하려고해요 :) 함수(Function) VS

zeddios.tistory.com

https://zeddios.tistory.com/1230

 

[Swift Concurrency] Async/await

안녕하세요 :) Zedd입니다. 오늘은 매우 핫한 async, await를 한번 보려고 합니다. 2021.03.25일 기준 async-await proposal은 Swift 5.5에서 구현된 상태입니다. + ) 2021.06.15 글 수정. @asyncHandler 내용 삭..

zeddios.tistory.com