iyOmSd/Title: Swift

[Swift] iCloud 연동 CloudKit 사용하기(2/2) - 데이터 연결

냄수 2022. 2. 16. 17:55
반응형

2022.02.15 - [iyOmSd/Title: Swift] - [Swift] iCloud 연동 CloudKit 사용하기(1/2) - 추가 및 설정

앞 게시물에서 정의한 아이클라우드를 Xcode내에서 사용할 코드를 작성할거에요

 

저번에 Book 이라는 Record타입을 만들었죠

Xcode에서 그 타입을 불러오고 데이터를 넣어줄 수 있도록 코딩할거에요

 

CloudKit을 import해주고

 

기본적으로 알아야할 타입들은

CKRecord

CKContainer

CKQuery

CKQueryOperation

이렇게 있어요

 

시뮬레이터로 실행할때 테스트하려면

우선 애플계정을 로그인해줘야해요

 

시뮬레이터 -> 설정 -> 프로필쪽에 계정로그인을 해줘야

아이클라우드 테스트를 해볼 수 있어요

안하면 권한문제로 테스트를 해볼 수가 없어요!

요롷게 이제 로그인에 성공했다면

시작해봅시다~!~

 

 

데이터 삽입

데이터 삽입에는

CKRecord

CKContainer를 사용해요

 

타입에서 보이지만

Record는 저희가 정의한 그 Book 레코드를 나타낼때 그 레코드가 맞아요

let record = CKRecord(recordType: "Book")

record.setValuesForKeys(["name": "swift책",
                         "price": 10000])

이런식으로 레코드를 생성할 수 있어요

 

레코드와 그 안에 필드에 값을 넣어줘야 하잖아요?

이런식으로 필드에맞는 키값과 원하는 값을 넣어서 만들 수 있고 이 방법말고도 다른방법도 있어요!

여기까지 만들 레코드를 정의해줬고

 

컨테이너에접근해서 데이터베이스로 가서 저장을 해주면되요

let container = CKContainer(identifier: "capabilities에서 추가한 container이름을 넣어주세요")
        
container.publicCloudDatabase.save(record) { record, error in
    print("저장완료! \(record)")
}

데이터베이스가 public에 정의한 레코드이니까

publicCloudDatabase으로 하면되구요

 

이렇게 코드를 실행하면

저장완료! Optional(<CKRecord: 0x7f9803b09690; recordType=Book, recordID=3AC34761-EBEE-48B6-B2C1-1095A00AF600:(_defaultZone:__defaultOwner__), recordChangeTag=kzp87n0c, values={
    name = "swift\Ucc45";
    price = 10000;
}>
{
	creatorUserRecordID -> <CKRecordID: 0x6000010009c0; recordName=__defaultOwner__, zoneID=_defaultZone:__defaultOwner__>
	lastModifiedUserRecordID -> <CKRecordID: 0x6000010009e0; recordName=__defaultOwner__, zoneID=_defaultZone:__defaultOwner__>
	creationDate -> 2022-02-16 07:21:51 +0000
	modificationDate -> 2022-02-16 07:21:51 +0000
	modifiedByDevice -> 28E1DA0E177C55935941601BA15EFC7D6878D5500F57C80930767E5640C1A126
	price -> 10000 (type q)
	name -> "swift책"
})

잘 저장됬다는 로그와

클라우드 콘솔에서 조회를해보면

잘 저장된게 보이네요

 

 

 

데이터 조회

레코드에 보이는 저 데이터를 받아올줄도 알아야겠죠?

CKQuery와

CKQueryOperation을 사용해요

이 둘의차이가 뭔가요!?

 

CKQuery

검색프로세스의 첫단계로 쿼리를 만들어요

검색할 레코드 타입, 정렬 매개변수를 포함하여 검색 파라미터를 만들어요

쿼리를 사용하여 결과를 생성하기위해 CKQueryOperation 인스턴스를 생성해야해요

검색조건을 정의하는 객체라고 생각하면 될것같네요!

 

검색조건을 만들었으면 실행을해야겠죠

 

CKQueryOperation

검색조건이 정의된 CKQuery객체로 초기화하고

결과를 처리하고 작업을 실행할 수 있도록 queryCompletionBlock속성에 핸들러를 할당할 수 있어요

검색이 많은 레코드를 생성하는경우 Operation객체는 나머지 레코드를 얻기위해 Cursor와 전체결과의 일부를 블록에 즉시 전달 할 수 있어요

다음결과배치를 처리할 준비가 되면 Cursor를 사용하여 별도의 CKQueryOperation객체를 생성하고 실행해야해요

선택적으로 resultsLimit, desireKey등 원하는 속성을 지정하여 결과를 구성할 수 있어요

queryOperation객체를 대상 데이터베이스의 add()함수에 전달하여 작업을 실행할 수 있어요 (나머지 레코드를얻기위해 추가작업을 해야하는 경우와 같은때)

 

iOS15+부터는 함수명이 달라졌어요

완료시 불리는 콜백: queryCompletionBlock -> queryResultBlock

검색시 불리는 콜백: recordFetchedBlock -> recordMatchedBlock

 

 

Book에 접근해서 모든 책정보를 불러올거에요

let predicate = NSPredicate(value: true)
let query = CKQuery(recordType: "Book", predicate: predicate)
let operation = CKQueryOperation(query: query)
operation.database = container.publicCloudDatabase

operation.recordMatchedBlock = { recordID, result in
    print("💿", recordID)
    switch result {
    case .success(let record):
        print("📀", record)
    case .failure(let error):
        print(error)
    }
}

operation.start()

NSPredicate를 이용해서 쿼리문을 넣어줘야하구요

위와같이 true인경우는 모든 목록을 조회할 수 있어요

 

CKQuery로 Book 레코드타입을 넣어주고

이 쿼리를 가지고 CKQueryOperation을 만들어주면되요

operation의 데이터베이스를 지정해줘야하는데

default값은 nil로 private database를 가리켜요

따라서 저희는 public이니까 정의해줘야하구요!

 

recordMatchedBlock을 이용해서 검색결과를 이용한 핸들러를 정의해줄 수 있어요

마지막으로 start()함수를 통해 실행시켜주면

💿 <CKRecordID: 0x600003279100; recordName=580DE16C-266C-48D9-A7A6-AC464C80F57D, zoneID=_defaultZone:__defaultOwner__>
📀 <CKRecord: 0x7fc63ae05f00; recordType=Book, recordID=580DE16C-266C-48D9-A7A6-AC464C80F57D:(_defaultZone:__defaultOwner__), recordChangeTag=kzp92wuw, values={
    name = "swift\Ucc45";
    price = 10000;
}>

이렇게 모든 목록을 가져올 수 있어요

 

 

모든 작업이 끝났을 때를 정의하는 queryResultBlock은

operation.queryResultBlock = { result in
    print("🍎finish🍎")
    switch result {
    case .success(let cursor):
        print(cursor)
    case .failure(let error):
        print(error)
    }
}

검색조회가 완료되면

마지막으로 실행되요

 

 

커서를 이용한조회

cursor를 사용해보기위한 테스트를 해볼거에요

검색양이 많을 때 사용가능해요

 

테스트환경은

resultsLimit를 2로 설정해서 2개만보여주도록하고

레코드는 2개를 초과한 수가 있어야해요

 

그렇게 실행하면

💿 <CKRecordID: 0x60000292e320; recordName=092E700E-FE92-DB7D-DAA4-EAD851E89DF0, zoneID=_defaultZone:__defaultOwner__>
📀 <CKRecord: 0x7fef42004080; recordType=Book, recordID=092E700E-FE92-DB7D-DAA4-EAD851E89DF0:(_defaultZone:__defaultOwner__), recordChangeTag=kznwixbt, values={
    name = "\Uc601\Uc5b4\Ucc45";
    price = 10000;
}>
💿 <CKRecordID: 0x6000029173e0; recordName=580DE16C-266C-48D9-A7A6-AC464C80F57D, zoneID=_defaultZone:__defaultOwner__>
📀 <CKRecord: 0x7fef42004b10; recordType=Book, recordID=580DE16C-266C-48D9-A7A6-AC464C80F57D:(_defaultZone:__defaultOwner__), recordChangeTag=kzp92wuw, values={
    name = "swift\Ucc45";
    price = 10000;
}>
🍎finish🍎
Optional(<CKQueryCursor: 0x600002901d40; id=12d4812c0aba5312, zone=(null)>)

원래 nil이뜨던 cursor부분이 객체가 존재하는걸 볼 수 있어요

 

여기서 이제 의문이 드는게 생겨요

위에서

커서를이용해서 QueryOperation를 만들어서 데이터베이스의 add함수를 실행시켜라~ 라고했죠

publicCloudDatabase.add()를 사용해요

 

지금까지 데이터 조회는

Operation에서 recordMatchedBlock을 정의하고 start()를 통해서 했는데

Operation함수가아니라 데이터베이스의 함수를 사용하네요??

 

위에서 publicCloudDatabase에서 save()만 사용했었는데

fetch()도 있어요 저장도 가능하고 불러오기도 가능하죠

 

차이는?!

recordMatchedBlock

단일 실행으로 조회할때마다 실행되는 클로져에요

2개의 데이터를 가져왔다면

저 클로져는 2번이 실행되는거죠

 

하지만 

publicCloudDatabase.fetch()

배열리스트를 불러와요 한번만 실행되는거죠

 

 

데이터삽입처럼 조회 방법도 다양하게 있어요

우선 커서는 queryResultBlock에서 받아올 수 있어요

아래의 방법은 Operation을 이용해서 조회했던 작업을 다시 만들어서 실행하는거에요

// 방법1
// Operation정의후 start()실행
operation.queryResultBlock = { result in
     print("🍎finish🍎")
     switch result {
     case .success(let cursor):
         print(cursor)
         if let cursor = cursor {
             let nextOperation = CKQueryOperation(cursor: cursor)
             nextOperation.database = container.publicCloudDatabase
             nextOperation.resultsLimit = 1
             nextOperation.recordMatchedBlock = { recordID, result in
                 print("💿💿", recordID)
                 switch result {
                 case .success(let record):
                     print("📀📀", record)
                 case .failure(let error):
                     print(error)
                 }
             }
             nextOperation.start()
         }
     case .failure(let error):
         print(error)
     }
 }

 

 

아래의 방법은 문서처럼 add()를 사용하는 방법이에요

// 방법2
// Operation정의후 add()사용
operation.queryResultBlock = { result in
    print("🍎finish🍎")
    switch result {
    case .success(let cursor):
        print(cursor)
        if let cursor = cursor {
            let nextOperation = CKQueryOperation(cursor: cursor)
            nextOperation.resultsLimit = 1
            nextOperation.recordMatchedBlock = { recordID, result in
                print("💿💿", recordID)
                switch result {
                case .success(let record):
                    print("📀📀", record)
                case .failure(let error):
                    print(error)
                }
            }
            container.publicCloudDatabase.add(nextOperation)
        }
    case .failure(let error):
        print(error)
    }
}

 

Operation에 데이터베이스를 지정하고 start()를 하는것과

데이터베이스에 add()를 실행해주는 차이네요

 

실행결과는

query <CKQuery: 0x6000001de7c0; recordType=Book, predicate=TRUEPREDICATE>
💿 <CKRecordID: 0x600000199980; recordName=092E700E-FE92-DB7D-DAA4-EAD851E89DF0, zoneID=_defaultZone:__defaultOwner__>
📀 <CKRecord: 0x7fe4e1205300; recordType=Book, recordID=092E700E-FE92-DB7D-DAA4-EAD851E89DF0:(_defaultZone:__defaultOwner__), recordChangeTag=kznwixbt, values={
    name = "\Uc601\Uc5b4\Ucc45";
    price = 10000;
}>
💿 <CKRecordID: 0x6000001eb3c0; recordName=580DE16C-266C-48D9-A7A6-AC464C80F57D, zoneID=_defaultZone:__defaultOwner__>
📀 <CKRecord: 0x7fe4e100d370; recordType=Book, recordID=580DE16C-266C-48D9-A7A6-AC464C80F57D:(_defaultZone:__defaultOwner__), recordChangeTag=kzp92wuw, values={
    name = "swift\Ucc45";
    price = 10000;
}>
🍎finish🍎
Optional(<CKQueryCursor: 0x60000019d4a0; id=12d4812c0aba5312, zone=(null)>)
💿💿 <CKRecordID: 0x6000001972c0; recordName=91D77BBA-27F4-4F11-5E8E-AEB058431BAE, zoneID=_defaultZone:__defaultOwner__>
📀📀 <CKRecord: 0x7fe4e1708270; recordType=Book, recordID=91D77BBA-27F4-4F11-5E8E-AEB058431BAE:(_defaultZone:__defaultOwner__), recordChangeTag=kznwbwhs, values={
    name = abcd;
    price = 2000;
}>

finish이후 추가적으로 실행되는게 잘 보이네요

 

 

데이터 삭제

 

마지막으로 해볼건 데이터 삭제!

delete의 파라미터를 보아하니...

id를 넘겨주면 삭제가 되는것 같네요!

 

한번해봅시다!

func delete(id: CKRecord.ID) {
    container.publicCloudDatabase.delete(withRecordID: id) { recordID, error in
        print("삭제완료:", recordID)
        print(error)
    }
}

let predicate = NSPredicate(value: true)
let query = CKQuery(recordType: "Book", predicate: predicate)
let operation = CKQueryOperation(query: query)
operation.database = container.publicCloudDatabase

operation.recordMatchedBlock = { recordID, result in
    switch result {
    case .success(let record):
        if let name = record["name"] as? String {
            if !name.contains("책") {
                delete(id: recordID)
            }
        }
    case .failure(let error):
        print(error)
    }
}

operation.start()

레코드를 조회하고

조회목록중 name필드에 '책'이 포함되지않으면 삭제하는 로직을 만들어봤어요

삭제완료: Optional(<CKRecordID: 0x60000108f360; recordName=91D77BBA-27F4-4F11-5E8E-AEB058431BAE, zoneID=_defaultZone:__defaultOwner__>)
nil

삭제가 잘 됬다고 뜨네요

콘솔에서 확인해볼까요?

삭제전
삭제후

'책'글자가 들어가지않은 abcd가 사라진걸 볼 수 있어요!

 

 

 

 

 

https://developer.apple.com/documentation/cloudkit/designing_and_creating_a_cloudkit_database

 

Apple Developer Documentation

 

developer.apple.com

https://developer.apple.com/documentation/cloudkit/ckquery#1666032

 

Apple Developer Documentation

 

developer.apple.com

 

반응형