클라우드킷 레코드의 로컬 캐시 유지하기 CloudKit


Maintaining a Local Cache of CloudKit Records

https://developer.apple.com/library/archive/documentation/DataManagement/Conceptual/CloudKitQuickStart/MaintainingaLocalCacheofCloudKitRecords/MaintainingaLocalCacheofCloudKitRecords.html

앱이 오프라인에서 실행되거나 성능을 향상시키기 위해 클라우드킷 레코드의 로컬 캐시를 추가 할 수 있다. 또는 자체적인 데이터 저장소를 두고 클라우드킷에서 데이터를 유지하기위한 지원을 추가하기를 원할 수도 있다.

일반 작업흐름

앱을 로컬 캐시를 유지한 후 앱이 따른 일반 흐름은 다음과 같다.
1. 새로운 장치에서 처음으로 앱이 실행되면 사용자의 프라이비트 그리고 공유된 데이터베이스에 변화에 대한 구독된다.
2. 사용자가 자체데이터를 장치 A에서 수정하고 앱은 이런 변화를 클라우드킷에 보낸다.
3. 앱은 같은 사용자의 장치 B에서 푸시 알림을 받아 서버에서 변화가 있음을 감지한다.
4. 장치B는 최종적으로 변화가 있음을 서버에게 묻고 이런 변화로 로컬 캐시를 업데이트 한다.

컨테이너 초기화하기

앱의 초기화 로직은 앱이 실행될 때 언제나 실행되어야 한다. 앱은 이미 생성한 존과 구독에 상관없이 지역적으로 캐시되어 모든 실행에서 불필요한 요청이 없게 한다.

먼저 다음 코드는 이 예시를 따르는 아이템을 정의한다.

let container = CKContainer.default()
let privateDB = container.privateCloudDatabase
let sharedDB = container.sharedCloudDatabase

// 사용자의 장치사이에 일관된 존 ID를 사용한다.
// CKCurrentUserDefaultName은 존ID를 생성할 때의 현재 사용자의 ID를 지정한다.
let zoneID = CKRecordZoneID(zoneName: "Todos", ownerName: CKCurrentUserDefaultName)

// 실행 사이에서 유지되도록 디스크에 저장한다.
var createdCustomZone = false
var subscribedToPrivateChanges = false
var subscribedToSharedChanges = false

let privateSubscriptionId = "private-changes"
let sharedSubscriptionId = "shared-changes"

사용자 존 생성하기

클라우드킷의 기능성추적 변화를 사용하려면 사용자의 프라이비트 데이터베이스에 사용자 존 내의 데이터를 저장해야 한다. 사용자 존은 아래에 보여진 바와 같이 CKModifyRecordZonesOperation 을 사용해 생성할 수 있다.

let createZoneGroup = DispatchGroup()
if !self.createdCustomZone {
  createZoneGroup.enter()
  let customZone = CKRecordZone(zoneID: zoneID)
  let createZoneOperation = CKModifyRecordZonesOperation(recordZonesToSave: [customZone], recordZoneIDsToDelete: [])
  createZoneOperation.modifyRecordZonesCompletionBlock = ( (saved, deleted, error) in
    if (error == nil) {
      self.createdCustomZone = true
    }
    // else custom error handling
    createZoneGroup.leave()
  }
  createZoneOperation.qualityOfService = .userInitiated
  self.privateDB.add(createZoneOperation)
}

변화 알림에 구독하기

다른 장치로부터의 변화에 구독할 필요가 있다. 구독은 클라우드킷에서 관심있는 데이터가 무엇인지를 알려 데이터가 변화될 때 푸시알림을 보낼 수 있게 한다.

앱은 데이터베이스 변화 (CKDatabaseSubscription)에 두 개의 구독을 생성할 필요가 있는데, 하나는 프라이비트 데이터베이스 그리고 하나는 공유된 데이터베이스이다.

if !self.subscribedToPrivateChanges {
  let createSubscriptionOperation = self.createDatabaseSubscriptionOperation(subscriptionId: privateSubscriptionId)
  createSubscriptionOperation.modifySubscriptionCompletionBlock = { (subscriptions, deletedIds, error) in
    if error == nil {
      self.subscribedToPrivateChanges = true
    }
    // else custom error handling
  }
  self.privateDB.add(createSubscriptionOperation)
}

if !self.subscribedToSharedChanges {
  let createSubscriptionOperation = self.createDatabaseSubscriptionOperation(subscriptionId: sharedSubscriptionId)
  createSubscriptionOperation.modifySubscriptionsCompletionBlock = { (subscriptions, deletedIds, error) in
    if error == nil {
      self.subscribedToSharedChanges = true
    }
  }
  self.sharedDB.add(createSubscriptionOperation)
}

// 앱이 실행중이 아닐 때 발생한 변화를 얻는다.
createZoneGroup.notify(queue: DispatchQueue.global()) {
  if self.createdCustomZone {
    self.fetchChanges(in: .private) {}
    self.fetchChanges(in: .shared) {}
  }
}

이들 구독은 클라우드킷에게 데이터베이스내의 레코드나 존이 추가, 수정, 삭제되면 이 장치상의 앱에게 푸시알림을 보내게 한다.

사일런트 푸시 알림을 보내도록 구독을 설정할 수도 있다. 이들 알림은 앱을 깨워 변화를 페치하지만 앱은 사용자에게 엘럿을 보여주지 않는다.

func createDatabaseSubscriptionOperation(subscriptionId: String) -> CKModifySubscriptionsOperation {
  let subscription = CKDatabaseSubscription.init(subscriptionID: subscriptionId)
  let notificationInfo = CKNotificationInfo()
  // 사일런트 알림 보내기
  notificationInfo.shouldSendContentAvailable = true
  subscription.notificationInfo = notificationInfo

  let operation = CKModifySubscriptionsOperation(subscriptionsToSave: [subscription], subscriptionIDsToDelete: [])
  operation.qualityOfService = .utility
  return operation
}

푸시알림 리스닝

클라우드킷을 사용하는 앱을 설정하는 부분으로서 원격 알림을 위해 앱을 리슨하게 해야 한다.
CKSubscription으로 실행되는 리모트 알림으로 알 수 있는 userInfo 딕셔너리의 CKNotification(fromRemoteNotificationDictionary: dict) 를 사용한다.

func application(_ application: UIApplication, didFinishLaunchingWithOperations launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
  application.registerForRemoteNotification()
  return true
}

func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable: Any], fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {
  print("Received notification!")
  let viewController = self.window?.rootViewController as? ViewController
  guard let viewController = self.window?.rootViewController as? ViewController else {return}
  let dict = userInfo as! [String: NSObject]
  guard let notification: CKDatabaseNotification = CKNotification(fromRemoteNotificationDictionary:dict) as? CKDatabaseNotification else { return }
  viewController!.fetchChanges(in: notification.databaseScope) {
    completionHandler(.newData)
  }
}


변화 페칭

앱이 실행하거나 푸시를 받으면 앱은 CKFetchDatabaseChangesOperation 을 사용하고 CKFetchRecordZoneChangesOperation으로 마지막으로 업데이트한 것에서 변화된 것만 서버에게 질의한다.

이 연산의 핵심은 previousServerChangeToken 객체로서 서버에게 앱이 마지막으로 서버에 물어본 때를 알려주도록 한다. 서버는 그 시간 이후에 변화를 반환하도록 한다.

먼저 CKFetchDatabaseChangesOperation 을 사용해 어떤 존이 변경되었는지를 찾고
1. 새롭거나 업데이트된 존을 위한 ID들을 취합한다.
2. 존에서 삭제된 로컬 데이터를 제거한다.

데이터베이스 변화를 얻기 위한 예시코드가 여기에 있다.

func fetchChanges(in databaseScope: CKDatabaseScope, completion: @escaping() -> Void) {
  switch databaseScope {
  case .private:
    fetchDatabaseChanges(database: self.privateDB, databaseTokenKey: "private", completion: completion)
  case .shared:
    fetchDatabaseChanges(database: self.sharedDB, databaseTokenKey: "shared", completion: completion)
  case .public:
    fatalError()
  }
}

func fetchDatabaseChanges(database: CKDatabase, databaseTokenKey: String, completion: @escaping() -> Void) {
  var changedZoneIDs: [CKRecordZoneID] = []
  let changeToken = ... // Read Change token from disk
  let operation = CKFetchDatabaseChangesOperation(previousServerChangeToken: changeToken)

  operation.recordZoneWithIDChangedBlock = { (zoneID) in
    changedZoneIDs.append(zoneID)
  }

  operation.recordZoneWithIDWasDeletedBlock = { (zoneID) in
    // Write this zone deletion to memory
  }

  operation.changeTokenUpdatedBlock = {(token) in
    // 이 데이터베이스의 존 삭제를 디스크 플러쉬한다.
    // 이 새로운 데이터베이스 변경 토큰을 메모리에 적는다.
  }

  operation.fetchDatabaseChangesCompletionBlock = {(token, moreComing, error) in
    if let error = error {
      print("Error during fetch shared database changes operation", error)
      completion()
      return
    }
    // 이 데이터베이스의 존 삭제를 디스크 플러쉬한다.
    // 이 새로운 데이터베이스 변화 토큰을 메모리에 적는다.
    self.fetchZoneChanges(database: database, databaseTokenKey: databaseTokenKey, zoneIDs: changedZoneIDs) {
      // 인메모리 데이터베이스 토큰을 디스크로 플러쉬한다.
      completion()
    }
  }
  operation.qualityOfService = .userInitiated
  database.add(operation)
}

다음으로 앱은 CKFetchRecordZoneChangesOperation 객체를 사용해 취합한 존 IDs집합으로 다음을 수행한다.
1. 모든 변화된 레코드에 대해 생성과 업데이트를 수행
2. 더이상 존재하지 않는 레코드를 삭제한다.
3. 존 변화 토큰을 업데이트 한다.

여기에 존 변화를 페치하는 예시코드가 있다.

func fetchZoneChanges(database: CKDatabase, databaseTokenKey: String, zoneIDs: [CKRecordZoneID], completion: @escaping() -> Void) {
  // 각 존에 대해 이전 변화 토큰을 살펴본다.
  var optionsByRecordZoneID = [CKRecordZoneID: CKFetchRecordZoneChangesOptions]()
  for zoneID in zoneIDs {
    let options = CKFetchRecordZoneChangesOption()
    options.previousServerChangeToken = ... // Read change token from disk
      optionsByRecordZoneID[zoneID] = options
  }
  let operation = CKFetchRecordZoneChangesOperation(recordZoneIDs: zoneIDs, optionsByRecordZoneID: optionsByRecordZoneID)

  operation.recordChangedBlock = { (record) in
    print("Record changed: ", record)
    // write this record change to memory
  }
  operation.recordWithIDWasDeletedBlock = ( (recordId) in
    print("Record deleted: ", recordId)
    // write this record deletion to memory
  }
  operation.recordZoneChageTokensUpdatedBlock = ( (zoneId, token, data) in
    // 이 존 내의 레코드 변화와 삭제를 디스크에서 플러시 한다.
    // 이 새로운 존 변경 토큰을 디스크에 적는다.
  }

  operation.recordZoneFetchCompletionBlock = { (zoneId, changeToken, _, _, error) in
    if let error = error {
      print("Error fetching zone changes for \(databaseTokenKey) database: ", error)
      return
    }
    // 이 존 내의 레코드 변화와 삭제를 디스크에서 플러시 한다.
    // 새로운 존 변경 토큰을 디스크에 적는다.
  }
  
  operation.fetchRecordZoneChangesCompletionBlock = { (error) in
    if let error = error {
      print("Error fetching zone changes for \(databaseTokenKey) database: ", error)
    }
    completion()
  }
  database.add(operation)
}

위의 코드는 메모리에 변화를 적는데에대한 몇가지 코멘트가 있고 디스크에 변화를 플러싱한다. 일반적인 흐름은 다음과 같다.

데이터베이스에서는:
- zone 삭제에 대해 메모리에 적는다.
- 데이터베이스에 대해 새로운 변화가 있으면 메모리에 토큰을 적는다. 그리고 다음을 수행한다.
  . 디스크에 인메모리 존을 유지 (삭제된 존을 삭제하고 페치하기를 원하는 콘의 리스트 기록) 하거나 또는
  . 디스크에 존 삭제를 유지하고 모든 수정된 레코드 존에 대한 변화를 페치한다.

일러두기: 데이터베이스 변경을 페치할 때 데이터베이스 변화토컨을 얻기전에 모든 존 당 콜백을 유지하여야 한다.
- 최종적으로, 업데이트된 데이터베이스 변경 토큰을 디스크에 플러시한다.

비슷하게, 존에 대해서는
- 존 내의 레코드 변경에 대해 메모리에 적는다.
- 존에 대해 새로운 변화 토큰은 해당 존 내의 모든 인메모리 레코드 변경과 해당 존의 업데이트된 변화 토큰까지 커밋한다.

레코드 메타데이터 저장하기

서버상의 레코드와 로컬 데이터 저장소내의 레코드를 연관시키려면 레코드(레코드 이름, 존ID, 변화태그, 생성 날짜 등)를 위한 메타데이터를 저장해야 한다. 이는 CKRecord, encodeSystemFieldsWithCoder라는 편리한 메소드가 시스템 필드를 위해 도와준다. 자체적인 사용자 필드는 구별해서 처리해야 한다.

어떻게 지역적으로 저장하기 위해 메타데이터를 읽어들이는지 보여준다.
// CKRecord 로부터 메타데이터를 얻는다.
let data = NSMutableData()
let coder = NSKeyedArchiver.init(forWritingWith: data)
coder.requiresSecureCoding = true
record.encodeSystemFields(with: coder)
coder.finishEncoding()

// 이 메타데이터를 로컬 객체에 저장한다.
yourLocalObject.encodeSystemFields = data

로컬 데이터에 기반해 클라우드킷에 변화를 보낼 때 로컬 캐시 객체를 클라우드킷 객체로 읽을 수 있으며 이들을 처리해 클라우드킷내의 저장소에 맞게 처리할 수 있다.

// CKRecord를 그 메타데이터로 설정한다.
let coder = NSKeyedUnarchiver(forReadingWith: yourLocalObject.encodedSystemFields!)
coder.requiresSecureCoding = true
let record = CKRecord(coder: coder)
coder.finishDecoding()
// 커스텀 필드 작성하기

고급 로컬 캐싱

사용자는 클라우드킷 서버의 데이터를 아이클라우드 설정 -> 저장소 관리 메뉴로 앱 데이터를 삭제할 수 있다. 이에 대한 처리를 섬세하게 해야 하며 이들이 존재하지 않으면 존과 구독을 다시 생성해야 한다. 이 상황에서의 에러는 userDeletedZone이다.

연산 의존 시스템은 WWDC2015의 고급 NSOperations 에서 클라우드킷 연산을 관리하는 훌륭한 방법을 설명하는데 계정과 네트워크 상태 그리고 존과 구독은 적절한 시간에 생성되어야 한다는 것이다.

언제든 네트워크 연결이 사라질 수 있으니 모든 연산에 대해 networkUnavailable 에러를 적절히 처리해야 한다.

네트워크 접근성을 확인하고 네트워크가 다시 가용하면 연산을 다시 시도한다.

함께 보기

WWDC 2016 클라우드킷 최고 연습 비디오 에서 로컬 캐싱에 대해 더 살펴본다.
https://developer.apple.com/videos/play/wwdc2016/231/








스키마 배포하기 CloudKit


Deploying the Schema

https://developer.apple.com/library/archive/documentation/DataManagement/Conceptual/CloudKitQuickStart/DeployingYourCloudKitApp/DeployingYourCloudKitApp.html

디벨롭먼트 환경에서 스키마를 완료하고 앱을 테스트하면 프로덕션으로 스키마를 배포할 준비가 된것이다. 배포는 프로덕션 환경으로 스키마를 진행되게 하지만, 디벨롭먼트 환경의 레코드를 프로덕션 환경으로 복사하지는 않는다. 그러므로, 배포한 이 후, 필요하면 프로덕션 환경으로 채운다. 그러면 앱을 프로덕션 환경에서 테스트 할 수 있다. 디벨롭먼트 환경에서 스키마를 변화시킬 수 있지만 스키마를 배포한 이 후에는 레코드 형식을 생성하고 필드 추가하는 것으로만 제한된다. 인덱스는 스키마의 일부분으로서 레코드 형식 변경과 유사하게 배포되어야 한다. 디벨롭먼트 스키마를 배포한 이 후 변화는 프로덕션 스키마로 합쳐진다.

이 챕터에서 작업을 수행하려면 프로덕션 환경을 수정하는 권한을 가져야 한다. 만약 개인이면 팀 운영자이고 이런 프리빌리지를 갖는다. 그렇지 않다면 다른 팀 멤버에게 권한 할당하기 부분에서 설명된바와 같이 팀 운영자에게 물어보고 프로덕션 권한을 수정한다. 

앱 배포 가이드의 아이튠즈 커넥트에 앱 업로드하기에 설명되어 있듯 스키마를 배포한 이 후 아이튠즈 커넥트에 앱을 업로드하고 iOS와 tvOS앱에 대해서는 테스트플라이트를 사용해 앱을 배포하기에 설명되어 있듯 테스트플라이트를 사용해 테스팅을 위해 앱을 배포한다. 스토어에 제출이 준비될 때 앱 제출하기 부분을 살펴본다.

디벨롭먼트 스키마를 프로덕션으로 배포하기

앱을 배포하면 클라우드킷은 컨테이너 스키마를 프로덕션 환경으로 복사한다. 이는 레코드 형식, 시큐리티 규칙, 그리고 구독 형식이 포함되어 있지만, 디벨롭먼트 환경에서 생성된 레코드는 포함되지 않는다. 스키마를 프로덕션 환경으로 배포하면, 디벨롭 먼트환경에서 배포된 레코드 현식과 필드는 삭제할 수 없다.

주의: 프로덕션환경에서 레코드를 보려면, 고급 로컬 캐싱에 설명된 다음 단계전에 연관된 레코드 형식을 위한 아이디 메타데이터 인덱스를 활성화한다. 프로덕션 환경에서 메타데이터 인덱스를 변경할 수 없다.

프로덕션에서 스키마를 배포하려면
1. 클라우드킷 대쉬보드에서 "프로덕션으로 배포.." 버튼을 클릭한다.
2. 배포될 변화를 확인한다.
3. "변화 배포하기" 를 클릭한다.

단계 확인하기

스키마가 프로덕션 환경에 복사되었는지를 확인한다.
프로덕션 스키마와 데이터를 보려면
1. 프로덕션 환경에서 데이터를 클릭한다.
2. 탭바에서 레코드 형식을 클릭한다.
3. 보기 원하는 레코드 형식을 선택한다.

다른 팀 멤버에 권한을 할당한다.

조직을 위해서는, 아래에 보여져 있듯, 멤버 권한을 변경함으로서 클라우드킷 앱 배포의 책임을 위임할 수 있다.

Privilege

Description

Manage Team

Can change the privileges of other team members, except the team agent. The team agent always has all privileges.

Edit Development

  • Can edit the development schema by using CloudKit Dashboard.

  • Can view records in development.

Edit Production

  • Can deploy the development schema to production.

  • Can view the production schema.

  • Can view and edit records in production.

각 컨테이너에 구별된 멤버 권한을 지정할 수 있다. 권한은 팀에 귀속된 모든 컨테이너에 대해 할당되지 않는다.
팀 멤버에게 권한을 부여하려면
1. 클라우드킷 대시보드에서 적절한 컨테이너내에서 "컨테이너 퍼미션.." 을 클릭한다.
2. 팀 멤버의 행과 권한 열에서 원하는 팀 멤버에게 권한을 부여한다.
  만약 권한이 변경할 수 없거나 변경할 권한이 없다면 체크박스는 비활성화된다.

리캡

이 챕터에서 프로덕션 환경으로 디벨롭먼트 환경 스키마를 배포하는지 그리고 개발하는 앱을 어떻게 계속 유지하는지를 배웠다.




클라우드킷 앱 테스트하기 CloudKit


Testing Your CloudKit App

https://developer.apple.com/library/archive/documentation/DataManagement/Conceptual/CloudKitQuickStart/TestingYourApp/TestingYourApp.html

클라우드킷 앱에 다른 아이클라우드 계정을 사용한 다중 장치에서 실행하는 다중의 동시적 사용자로 테스트를 해본다. 초기에는, 클라우드킷 앱을 개발이나 프로덕션 환경에서 지정된 테스트 장치에서만 테스트한다.

아이튠즈 커넥트에 업로드한다. 내부 테스터를 초청하거나 외부 테스터를 초청하여 앱을 테스트한다. 테스트플라이트 앱을 사용해앱을 다운로드한다. 테스트플라이트나 스토어에서 배포된 앱은 개발환경을 사용하지 못함을 기억하자. 테스트 플라이트로 배포하기에 대한 더 자세한 사항은 앱 배포 가이드내 테스트 플라이트를 사용해 앱을 배포하기부분을 살펴본다.

Ad-Hoc 프로비져닝을 사용해 앱 배포하기

애드혹 프로비져닝은 Xcode가 필요없이 테스트 장비에서 앱을 실행할 수 있도록 한다. 애드혹 프로비져닝 프로필을 사용하여 앱을 내보내기전에 개발자 계정에서 테스팅을 위한 모든 장치를 등록한다. 애드혹 배포는 장치의 동일한 풀을 사용하여 등록할 수 있는 장치의 갯수는 제한되어 있다. 다중 테스트 장비를 등록하려면 멤버센터를 사용해 장치 등록 부분을 살펴본다.

애드혹 테스팅을 위해 앱을 내보낼 때 개발 또는 프로덕션 환경을 선택한다. 만약 프로덕션을 위한 개발 스키마 배포에서 설명된 것과 같은 프로덕션을 위한 스키마로 배포되지 않았다면 개발 환경을 선택한다.

애드혹 테스팅을 위해 앱을 아카이브하고 내보내기하려면

1. 일반 장치나 연결된 장치를 선택하여 실행을 클릭한다.
  시뮬레이터를 위해 빌트된 앱의 아카이브는 생성할 수 없다.
2. 제품 > 아카이브를 선택한다.
  아카이브 오거나이져가 보여지고 새로운 아카이브가 표시된다.
3. 아카이브 오거나이져에서 아카이브를 선택하고 내보내기를 클릭한다.
4. "애드혹 배포" 를 선택하고 다음을 클릭한다.

../Art/8_save_ad_hoc_2x.png

5. 보여진 대화상자에서 팝업메뉴의 팀을 선택하고 선택을 클릭한다.
  필요한 경우, Xcode는 배포 인증서과 애드혹 프로비져닝 프로필을 생성한다.

6. 장치 지원 대화상자에서, 유니버셜 앱인지 특정 장치인지를 선택하고 다음을 클릭한다.

7. 다음 대화상자에서, 팝업 메뉴로 부터 컨테이너 환경을 선택하고 다음을 클릭한다.
- 프로덕션 환경에서 데이터를 접근하려면 프로덕션을 선택한다.
- 디벨롭먼트 환경에서 데이터에 접근하려면 디벨롭먼트를 선택한다.

../Art/8_choose_environment_2x.png

8. 대화상자에서 앱, 엔타이틀먼트, 그리고 프로비져닝 프로필을 확인하고 내보내기를 클릭한다.
  애드혹 프로비져닝 프로필의 이름은 XC Ad Hoc: 으로 시작한다.

9. iOS 앱 파일을 위한 파일명과 위치를 입력하고 내보내기를 클릭한다.
  파인더는 내보내진 파일을 보여준다. iOS App 파일은 .ipa 확장자를 가진다.

이후에, 테스터에게 iOS 앱파일을 보낸다. 테스트 장비에서 앱 설치하기에 설명되어 있듯이 아이튠즈를 사용해 그 장치에 앱을 설치한다.

팀 프로비져닝 프로필을 사용해 앱 배포하기 (맥)

Xcode를 사용해 앱을 내보내기전에 개발자 계정내의 테스팅을 위해 사용할 맥 컴퓨터를 등록한다. 팀프로비져닝 프로필로 다중 맥 컴퓨터를 추가하려면 멤버 센터를 사용하여 장치 등록하기 부분을 살펴본다. Xcode에서 프로비져닝 프로필을 새로고침에서 설명되어 있듯, 맥 컴퓨터를 추가한 후, Xcode내의 프로필을 새로고침하여 팀 프로비져닝 프로필을 재 생성한다.

팀 프로비져닝 프로필을 사용해 코드 서명된 앱을 내보내려면

1. 스키마 툴바 메뉴로부터 대상을 선택하고 실행을 클릭한다.
2. 제품 > 아카이브를 선택한다.
  아카이브 오거나이져가 보여지고 새로운 아카이브가 표시된다.
3. 아카이브 오거나이져에서, 아카이브를 선택하고 내보내기를 클릭한다.
4. 디벨롭먼트 서명된 어플리케이션을 선택하고 다음을 클릭한다.
  Xcode는 번들에 팀 프로비져닝 프로필을 임베드하고 디벨롭먼트 인증서로 앱을 코드사인한다.
5. 보여진 대화상자에서 팝업메뉴에서 팀을 선택하고 선택하기를 클릭한다.
  필요한경우, Xcode는 필요한 서명과 프로비져닝 프로필을 생성한다.
6. 보여진 대화상자에서, 앱, 엔타이틀먼트, 그리고 프로비져닝 프로필을 리뷰하고 내보내기를 클릭한다.
  파인더는 내보내진 파일을 보여준다.

다음에, 테스터에게 앱을 배포하고 지정된 맥 컴퓨터에서 실행한다. 앱은 팀 프로비져닝 프로필내에서 지정된 맵컴퓨터에서만 실행된다.
만약 어떤 맥에서 실행되지 않는다면 확인되지 않는 개발자이므로 OS X에서 보안 설정을 변경한다.

확인되지 않은 개발자의 앱을 실행하려면
1. 파인더에서, 앱 아이콘을 컨트롤 클릭
2. 열기 클릭
3. 보여지는 게이트키퍼 대화상자에서 열기를 클릭

리캡

이 챕터에서 다음을 배웠다
- 테스팅을 위해 클라우드킷 앱을 배포하는 다른 방법을 사용하는 방법
- 지정된 장치에서 실행되도록 앱을 제한하는 방법
- 테스팅을 위해 디벨롭먼트나 프로덕션 컨테이너를 어떻게 선택하는지에 대한 방법




레코드 변화 구독하기 CloudKit


Subscribing to Record Changes

https://developer.apple.com/library/archive/documentation/DataManagement/Conceptual/CloudKitQuickStart/SubscribingtoRecordChanges/SubscribingtoRecordChanges.html

마지막 쿼리와 동일한 데 질의를 반복하는 것은 비효율적이다. 대신에, 레코드 변화에 구독하여 백그라운드에서 쿼리를 실행할 수 있게 한다. 서버는 변화를 알려준다. 예를 들어, 한 사용자가 특정 아티스트에 대한 아트워크에 흥미를 가진다면 앱은 해당 아티스트가 업로드한 새로운 아트워크가 생겨나면 알 수 있다.

../Art/subscriptions_2x.png

데이터베이스에 구독을 저장하기

코드에서, 구독 객체를 생성해 관련된 변화의 형식, 레코드 형식, 프리디케이트를 지정한다. 그러면 데이터베이스에 구독 객체를 저장한다.

구독을 생성하고 저장하려면
1. 프리디케이트 객체를 생성한다.
  예를 들면, 아티스트로부터 아트워크로 구독한다. (아트워크 레코드 형식내 아티스트 필드는 참조형식이다)
CKRecordID* artistRecordID = [[CKRecordID alloc] initWithRecordName:@"Mei Chen"];
NSPredicate* predicate = [NSPredicate predicateWithFormat:@"artist=%@", artistRecordID];

일러두기: 프리디케이트의 형식 문자열의 가능한 값 형식은 CKRecord, CKRecordID, 그리고 CKReference 객체이다. 레코드 이름을 알고 있다면 해당 레코드 이름을 포함하는 레코드 아이디를 생성할 수 있다.

2. 레코드 형식, 프리디케이트, 그리고 알림 옵션을 가지는 구독객체를 생성한다.
CKSubscription* subscription = [[CKSubscription alloc] initWithRecordType:@"Artwork" predicate:predicate options:CKSubscriptionOptionsFiresOnRecordCreation];

options파라미터의 가능한 값은 CKSubscriptionOptionsFiresOnRecordCreation, CKSubscriptionOptionsFiresOnRecordDeletion, CKSubscriptionOptionsFiresOnRecordUpdate, CKSubscriptionOptionsFiresOnce, options가 비트마스크이므로 변화형식의 조합을 지정할 수 있다.

3. 클라우드킷 알림 객체를 생성한다.
CKNotificationInfo* notificationInfo = [CKNotificationInfo new];
notificationInfo.alertLocalizationKey = @"New artwork by your favorite artist.";
notificationInfo.shouldBadge = YES;
지역화된 문자열을 사용자에게 보여주려면 알림의 alertLocalizationKey 속성을 설정한다. (alertBody 속성이 아닌)

4. 구독의 알림 객체를 새로운 클라우드킷 알림 객체로 설정한다.
subscription.notificationInfo = notificationInfo;

5. 데이터베이스에 구독을 저장한다.
CKDatabase* publicDatabase = [[CKContainer defaultContainer] publicCloudDatabase];
[publicDatabase saveSubscription:subscription completionHandler:^(CKSubscription* subscription, NSError* error) {
  if (error) {
  }
}];

Xcode에서 앱을 실행해 데이터베이스에 구독을 저장한다.

푸시알림 등록

데이터베이스에 구독을 저장하는 것은 구독이 실행될 때 자동적으로 알림을 받게 하는 것이 아니다. 클라우드킷은 애플 푸시 알림 서비스 APNs를 사용해 앱에 구독 알림을 보내어 이들을 얻으려면 푸시 알림을 등록해야 한다.

iOS와 tvOS 앱에서 이 코드를 application:didFinishLaunchingWithOptions: 프로토콜에 추가하여 푸시 알림을 위한 등록을 수행한다.

UIUserNotificationSettings* notificationSettings = [UIUserNotificationSettings settingsForTypes:UIUserNotificationTypeAlert categories:nil];
[application registerUserNotificationSettings:notificationSettings];
[application registerForRemoteNotifications];

맥앱에 대해서, applicationDidFinishLaunching: 프로토콜 메소드를 구현하여 푸시 알림을 등록한다.

선택적으로 application:didRegisterForRemoteNotificationWithDeviceToken: 과 application:didFailToRegisterForRemoteNotificationsWithError: 메소드로 앱이 푸시 알림 등록을 성공했는지 실패했는지 알 수 있다.

일러두기: 구독알림을 받기 위해 개발자 계정에서 앱의 명시적 앱ID를 위한 푸시 알림을 활성화 할 필요는 없다. Xcode는 클라우드킷활성화하면 자동적으로 APNs 엔타이틀먼트를 추가한다.

코드에서 푸시 알림 다루기

다음, application:didReceiveRemoteNotification: 메소드를 구현해 구독 알림을 처리한다. iOS와 tvOS 앱에서 UIApplicationDelegate 프로토콜 메소드를 구현하며 맥 앱에서는 NSApplicationDelegate 를 구현한다. 예를 들어 프리디케이트가 생성, 업데이트 또는 삭제될 때에 매치되면 뷰를 업데이트하도록 구현한다.

1. application:didReceiveRemoteNotification: 프로토콜을 앱 델리게이트에 추가한다.
- (void)application:(UIApplication*)application didReceiveRemoteNotification:(NSDictionary*)userInfo {
}

2. application:didReceiveRemoteNotification: 메소드에서 userInfo 파라미터를 CKNotification 객체로 컨버트한다.
CKNotification* cloudKitNotification = [CKNotification notificationFromRemoteNotificationDictionary:userInfo];

3. 알림의 바디를 얻는다.
NSString* alertBody = cloudKitNotification.alertBody;

4. CKQueryNotification 객체로부터 새롭거나 수정된 레코드를 얻는다.
if (cloudKitNotification.notificationType == CKNotificationTypeQuery) {
  CKRecordID* recordID = [(CKQueryNotification*)cloudKitNotification recordID];
}

5. 레코드 변화에 따라 뷰를 업데이트하거나 사용자에게 알린다.

테스트 구독

Xcode를 통해 초기에 구독을 테스트 할 수 있고 클라우드킷 대시보드를 사용해 레코드를 생성, 수정, 삭제할 수 있다. 그러면 다중 장치에서 앱을 실행해 완전하게 구독을 테스트할 수 있다. 한장치를 사용해 변화를 새성하고 다른 장치를 통해 구독 알림을 받는다. 다중 장치를 사용하는 것은 알림은 알림이 발생한 곳에서는 얻어지지 않기 때문이다.

iOS 와 tvOS 에 대해 맥에 접속된 장치를 사용해 구독 알림을 테스트한다. 만약 푸시 알림을 성공적으로 등록했다면 다이얼로구가 보여지고 앱에 알림을 받을지 사용자 퍼미션을 물어본다.

리캡

이 챕터에서는 다음을 배웠다.
- 프리디케이트를 사용하여 레코드 변화에 구독하는 방법
- 구독 알림 다루기



코어 데이터로부터 전환


코어 데이터로부터의 전환

https://inessential.com/2010/02/26/on_switching_away_from_core_data

지난 몇 달간 NetNewsWire를 위해 많은 성능개선을 수행했다. 아직 변화는 건내지 않은건 아직 충분히 완성되지 않았기 때문이다. 다른 개발자들이 아마도 관심둘 것으로 생각되어 이곳에 정리한다.

할 수 있는 많은 부분을 개선했다. 샤크에서 많은 시간을 소비하고, 코어 데이터로 모든 다중 스레딩 처리를 했으며 자체 큐 시스템에서 NSOperationQueue 로 바꿨으며, XML파싱을 수정하는 등의 작업을 수행했다. 하지만 퍼포먼스와 메모리 사용은 충분히 개선되지 않았다.

남은 것은 확실히 코어데이터이다. 

여기에 직접적인 데이터 베이스 엑세스가 코어 데이터를 사용하는 것보다 나은 경우를 나열한다.

1. 많은 뉴스 아이템을 읽은것/읽지 않은것으로 마크할때
Google Reader API로 부터 아이템 아이디의 큰 리스트를 얻는다. 

코어 데이터에서는 이 리스트 각 아이템에 대해 상태를 변경한다. 리스트는 10000개가 될 수 도 있다. 좋은 방식은 아니다.

이 것은 아주 데이터베이스스러운 연산으로서 한 번의 쿼리로 동작한다

2. 많은 아이템을 삭제할 때

코어 데이터는 하나하나 처리한다.

3. 외부 시스템으로부터 유일 아이디로 작업할 때

코어 데이터는 유일키 처리를 하지만 이 게 그것을 의미하지는 않는다. 뉴스 아이템은 유일 키가 있고 다른 데이터베이스에서 얻는다. 

새로고침을 하면 앱이 이전에 본 것을 알고 있다. 이전에 다운로드 된 것도 있고 변화된 것도 있다. (물론 이전것을 피한다.)

이 의미는 피드에서 각 아이템에 대해 이전에 저장된 것이면 앱은 존재하는 뉴스 아이템을 얻어야 한다. 이 것이 느리다. (다양한 기법을 시도했다. : 프리패칭, 필요할 때만 패칭, 피드에 대해서 존재하는 아이템의 아이디만 패팅, 셋이나 딕셔너리내의 존재하는 아이딤나 저장, 어떤 것도 도움이 되지 않았다.) 보통 해결책은 원본보다 더 안좋았다.

아이템의 수천개는 새로고침 세션동안 수천 아이템이 나올 수 있거 모든 아이템이 이미 존재하는지 확인해야하며 이 것은 큰 성능 히트이다. 페치하는 것보다 나아야 한다. 

더 직접적인 접근으로 INSERT or REPLACE INTO 로 처리 할 수 있다. 이 것은 아이템을 추가하거나 존재하는 아이템을 뒤바꾸는 것으로 빠르다.

4. 존재하는 아이템 테스팅하기

간혹 앱은 데이터베이스에 이미 존재하는지 확인해야 할 때가 있다. 코어데이터에서는 페치이다.
SQLite는 SELECT 1 FROM someTable WHERE uniqueID = whatever

이론상 인덱스만 확인한다 테이블 자체에서 어떤것도 얻지 않으므로 빠르다.



1 2 3 4 5 6 7 8 9 10 다음