[SwiftUI Mater] #25 CloudKit: Utility class

Woozoo·2023년 4월 25일
0

[SwiftUI Review]

목록 보기
40/41
post-custom-banner

제네릭하게 쓸 수 있게 만들어봅시다

static 함수로 작성해주고,
weak self는 지금 당장 필요없어서 지워줄거

DispatchQueue.main.async도 지워줍시다



뷰모델로와서 CloudKitUtility에서 작성한 메소드 사용해주면
뙇!

Combine으로 바꿔볼까요!!


Future를 사용해서
언젠가 값이 방출된다! 를 지정하고
promise에 result를 넣어줍니다!

🤔future 다 까먹었네... 복습 필요


차이가 보이나요!!
뷰모델에서 combine으로 작성한 코드와 escaping으로 작성한 코드.

시뮬레이터 실행해보면 뙇!


뭔가 더 빠른 것같기도 하네요


requestApplicationPermission도 수정해줍시다

fetchUserRecordID도 만들어주고!

지금 보면 returnedError 도 받고 있는데 커스텀한 CloudKitError도 받을 수 있게 해주고 있음

음...
이 요청을 하는 중에 CloudKit에서 내려주는 에러를 사용해도 되고,
직접 만든 enum 타입의 LocalizedError를 사용해줘도 된다!!


static func fetchUserRecordID(completion: @escaping (Result<CKRecord.ID, Error>) -> ()) {
    CKContainer.default().fetchUserRecordID { returnedID, returnedError in
        if let id = returnedID {
            completion(.success(id))
        } else if let error = returnedError {
            completion(.failure(error))
        } else {
            completion(.failure(CloudKitError.iCloudCouldNotFetchUserRecordID))
        }
    }
}

static func discoverUserIdentity(id: CKRecord.ID, completion: @escaping (Result<String, Error>) -> ()) {
    CKContainer.default().discoverUserIdentity(withUserRecordID: id) {  returnedIdentity, returnedError in
        if let name = returnedIdentity?.nameComponents?.givenName {
            completion(.success(name))
        } else {
            completion(.failure(CloudKitError.iCloudCouldNotDiscoverUser))
        }
    }
}

static func discoverUserIdentity(completion: @escaping (Result<String, Error>) -> ()) {
    fetchUserRecordID { fetchCompletion in
        switch fetchCompletion {
        case .success(let recordID):
            CloudKitUtility.discoverUserIdentity(id: recordID) { discoverCompletion in
                switch discoverCompletion {
                case .success(let name):
                    completion(.success(name))
                case .failure(let error):
                    completion(.failure(error))
                }
            }
        case .failure(let error):
            completion(.failure(error))
        }
    }
}

지금 위에 두개의 메소드를 합친 discoverUserIdentity 메소드를 구성해줬는데
switch가 두번 반복되는 게 비효율적인 거 같지 않음?

이렇게 깔끔하게 가능함!
고대로 그냥 completion 이라는 클로져 이름만 바로 전달해주는 방식으로!

한번 더 가서 completion 클로져를 combine화 시켜주는 Future를 사용할 수 있게 메소드 작성해주면 뙇!


어떤가요!
뷰모델에서 작성했었던 로직들이 상당히 깔끔해졌죠!!

CloudKitUtility라는 class로 작성했으니까 다른 플젝에서도 그대로 복붙하면 되고!!

그리고!!!!
뷰모델에서 CloudKit을 임포트 해주지 않아도 된다는거!!!

Generic하고 Reusable 한 코드가 완성 되었습니다.


두번째 페이지 CRUD

RecordMatchedBlock을 CloudKitUtility class로 옮기는 과정을 진행함

지금은 어떤 FruitModel이라는 특정한 모델을 fetch 해서 가져오고 이걸 처리하고 있는데 좀 더 Generic하게 쓸 수 있게 구성해줄 예정

지금 작성하는 코드는 완벽하게 이해하려면 여러번의 반복이 필요하다!!

FruitModel에서 실패가 가능한 init을 작성해주고
addRecordMatchedBlock에서 FruitModel을 간편하게 전달해줬음!

근데 여전히 FruitModel로 지금 CloudKitUtility에서 사용하고 있잖음...

어떻게 하면 Generic한 Model을 전달할 수 있을까요?

프로토콜이 필요합니다!!!!


와...
대박이다


보이시나요!! 이 코드의 아름다움이


뷰모델에서 CloudKitUtility를 fetch해서 가져올 때 return되는 아이템은 우리가 원하는 걸로 맞춰야하잖음
타입 자체를 (returnedItems: [FruitModel])로 맞춰주는겨!!!

이런 식으로 T, 제네릭한 타입을 선언하고 필요한 경우에 프로토콜을 채택해서 필수로 가져야하는 것들을 가지게 하는 방식으로 코드를 구성해주면 됩니다
(오픈 소스나 라이브러리 같은 것들을 개발할 때 요런 뉘앙스로 코드를 구성해주게 됨! 많이 봤죠)

extension CloudKitUtility {
    
    static func fetch<T: CloudKitableProtocol>(
        predicate: NSPredicate,
        recordType: CKRecord.RecordType,
        sortDescriptors: [NSSortDescriptor]? = nil,
        resultsLimit: Int? = nil
    ) -> Future<[T], Error> {
        Future { promise in
            CloudKitUtility.fetch(predicate: predicate, recordType: recordType, sortDescriptors: sortDescriptors, resultsLimit: resultsLimit) { (items: [T]) in
                promise(.success(items))
            }
        }
    }
}

Future를 써서 combine화 해주는 거 다시 연습해보고


이 코드를 이해하게 되면 코코아 팟 같은 것들이 어떤 식으로 코드가 작성이 됐는지 이해할 수 있겠죠!!

지금은 뉘앙스를 캐치할 수 있는 정도.
활용해서 사용하려면 반복숙달 필요!!


닉은 Update 메소드는 Model 안에 작성하는 걸 추구하는 듯

조금 더 struct를 let 하게 사용하고,
모델 자체에 update를 심어서 따로 외부에서 새로운 모델을 구성해서 전달하기보다는 가지고 있던 모델 자체를 정말 조금 변형해주는 뉘앙스로 구성을 하는 걸 보여준다

save 메소드는 따로 Future까지 작성해서 combine화 하지는 않았고
(do catch 블록안에서 하려면 조금 까다롭다고 함)


Challenge!

PushNotification 뷰 CloudKitutility로 이식하기!

아직은 부족해서 3번째 복습때 도전해봅시다

profile
우주형
post-custom-banner

0개의 댓글