CloudKit: 获取特定记录类型的所有记录?
CloudKit: Fetch all records with a certain record type?
我目前已经在我的应用程序中设置了 CloudKit,因此我正在使用以下代码的帮助添加新记录,
CKRecordID *recordID = [[CKRecordID alloc] initWithRecordName:@"stringArray"];
CKRecord *record = [[CKRecord alloc] initWithRecordType:@"Strings" recordID:recordID];
[record setObject:[NSArray arrayWithObjects:@"one", @"two", @"three", @"four", nil] forKey:@"stringArray"];
[_privateDatabase saveRecord:record completionHandler:nil];
但是,现在我希望能够获取所有记录类型相同的记录,"Strings," 和 return 那些编译成 NSArray 的记录。我该怎么做呢?目前,我想出的只是如何使用recordID单独获取每条记录,这很麻烦,必须有更简单的方法。
[_privateDatabase fetchRecordWithID:recordID completionHandler:^(CKRecord *record, NSError *error) {
if (error) {
// Error handling for failed fetch from private database
}
else {
NSLog(@"ICLOUD TEST: %@", [record objectForKey:@"stringArray"]);
}
}];
啊啊啊,我明白了。使用下面的代码,我能够在数据库上创建对 运行 的查询,然后 return 完成块中的 NSArray,我循环遍历它,然后 returned 值对于 NSLog 中保存的密钥。
NSPredicate *predicate = [NSPredicate predicateWithValue:YES];
CKQuery *query = [[CKQuery alloc] initWithRecordType:@"Strings" predicate:predicate];
[_privateDatabase performQuery:query inZoneWithID:nil completionHandler:^(NSArray *results, NSError *error) {
for (CKRecord *record in results) {
NSLog(@"Contents: %@", [record objectForKey:@"stringArray"]);
}
}];
以下函数将 return 请求记录类型的所有记录:
let database = CKContainer(identifier: "container_here").privateCloudDatabase
typealias RecordsErrorHandler = ([CKRecord], Swift.Error?) -> Void
func fetchRecords(forType type: String, completion: RecordsErrorHandler? = nil) {
var records = [CKRecord]()
let query = CKQuery(recordType: type, predicate: NSPredicate(value: true))
let queryOperation = CKQueryOperation(query: query)
queryOperation.zoneID = CloudAssistant.shared.zone.zoneID
queryOperation.recordFetchedBlock = { record in
records.append(record)
}
queryOperation.queryCompletionBlock = { cursor, error in
self.fetchRecords(with: cursor, error: error, records: records) { records in
completion?(records, nil)
}
}
database.add(queryOperation)
}
private func fetchRecords(with cursor: CKQueryCursor?, error: Swift.Error?, records: [CKRecord], completion: RecordsHandler?) {
var currentRecords = records
if let cursor = cursor, error == nil {
let queryOperation = CKQueryOperation(cursor: cursor)
queryOperation.recordFetchedBlock = { record in
currentRecords.append(record)
}
queryOperation.queryCompletionBlock = { cursor, error in
self.fetchRecords(with: cursor, error: error, records: currentRecords, completion: completion)
}
database.add(queryOperation)
} else {
completion?(records)
}
}
这是 Swift 3.0 中的答案。
func myQuery() {
let predicate = NSPredicate(value: true)
let query = CKQuery(recordType: "tableName", predicate: predicate)
publicDatabase.perform(query, inZoneWith: nil) { (record, error) in
for record: CKRecord in record! {
//...
// if you want to access a certain 'field'.
let name = record.value(forKeyPath: "Name") as! String
}
}
}
Swift4的解决方案,
显示如何获取类型 "YourTable" 的所有记录,还打印 System Field
和 Custom Field
:
let query = CKQuery(recordType: "YourTable", predicate: NSPredicate(value: true))
CKContainer.default().publicCloudDatabase.perform(query, inZoneWith: nil) { (records, error) in
records?.forEach({ (record) in
// System Field from property
let recordName_fromProperty = record.recordID.recordName
print("System Field, recordName: \(recordName_fromProperty)")
// Custom Field from key path (eg: deeplink)
let deeplink = record.value(forKey: "deeplink")
print("Custom Field, deeplink: \(deeplink ?? "")")
})
}
在尝试获取所有记录并了解 Cloudkit 存储的结构和细节时,我发现在调试期间提供以下功能很有用。这使用信号量来保留用于打印的数据结构。可能有更优雅的方法来做到这一点,但这个有效!
//
// Print a list of records in all zones in all databases
//
func printRecordsInContainers() {
let myContainer = CKContainer.default()
// Edit the following dictionary to include any known containers and possible record types
let containerRecordTypes: [CKContainer: [String]] = [ myContainer: ["MyRecordType", "OldRecordType", "MyUser", "PrivateInfo"] ]
let containers = Array(containerRecordTypes.keys)
for containerz in containers {
let databases: [CKDatabase] = [containerz.publicCloudDatabase, containerz.privateCloudDatabase, containerz.sharedCloudDatabase]
for database in databases {
var dbType = "<None>"
if database.databaseScope.rawValue == 1 { dbType = "Public" }
if database.databaseScope.rawValue == 2 { dbType = "Private" }
if database.databaseScope.rawValue == 3 { dbType = "Shared" }
//print("\(database.debugDescription)")
print("\n\n\n ---------------------------------------------------------------------------------------------------")
print(" ----- Container: \(containerz.containerIdentifier ?? "??") ----- Database: \(dbType)")
let semaphore1 = DispatchSemaphore(value: 0) // Initiate semaphore1 to wait for closure to return
database.fetchAllRecordZones { zones, error in
if let error = error {
print(" Error Fetching Zones: \(error.localizedDescription)")
}
else if let zones = zones {
print("~~~~ \(zones.count) : \(zones)")
for zone in zones {
print("----- Zone ID: \(zone.zoneID)\n")
for recordType in containerRecordTypes[container] ?? [] {
print("[ Record Type: \(recordType.description) ]")
let query = CKQuery(recordType: recordType, predicate: NSPredicate(value: true))
let semaphore = DispatchSemaphore(value: 0) // Initiate semaphore to wait for closure to return
database.perform(query, inZoneWith: zone.zoneID) { records, error in
if let error = error {
print(" Error in Record Query: \(error.localizedDescription)")
}
else if let records = records {
printRecordDescriptions(records)
}
semaphore.signal() // Send semaphore signal to indicate closure is complete
}
semaphore.wait() // Wait for semaphore to indicate that closure is complete
}
}
}
else {
print(" Error in fetchAllRecordZones")
}
semaphore1.signal() // Send semaphore1 signal to indicate closure is complete
}
semaphore1.wait() // Wait for semaphore1 to indicate that closure is complete
}
}
}
class func printRecordDescriptions(_ records: [CKRecord]) {
print("Records and Contents List:")
for record in records {
print(" Record: \(record.recordID)")
for key in record.allKeys() {
print(" Key - \(key)")
}
}
print("Record List End\n")
}
Swift 5
在浏览了 SO 上的大量帖子和解决方案之后,我设法找到了一个适合我需要的解决方案,并且对于只想从 iCloud 获取给定类型的所有记录的任何人来说应该足够简单。
解决方案
解决方案使用 CKDatabase 的扩展来引入一种方法来处理 cursor: CKQueryOperation.Cursor
of CKQueryOperation
以继续向 iCloud 请求更多记录。在这种方法中,我分派到后台队列,这样我就可以阻止它并等待操作完全完成,无论是在收到错误还是在收到最后一批记录时。一旦信号量解锁队列,它就会继续调用带有结果的主完成块。我也在完成处理程序中利用 Swift 的 Result
类型。
extension CKDatabase {
func fetchAll(
recordType: String, resultsLimit: Int = 100, timeout: TimeInterval = 60,
completion: @escaping (Result<[CKRecord], Error>) -> Void
) {
DispatchQueue.global().async { [unowned self] in
let query = CKQuery(
recordType: recordType, predicate: NSPredicate(value: true)
)
let semaphore = DispatchSemaphore(value: 0)
var records = [CKRecord]()
var error: Error?
var operation = CKQueryOperation(query: query)
operation.resultsLimit = resultsLimit
operation.recordFetchedBlock = { records.append([=10=]) }
operation.queryCompletionBlock = { (cursor, err) in
guard err == nil, let cursor = cursor else {
error = err
semaphore.signal()
return
}
let newOperation = CKQueryOperation(cursor: cursor)
newOperation.resultsLimit = operation.resultsLimit
newOperation.recordFetchedBlock = operation.recordFetchedBlock
newOperation.queryCompletionBlock = operation.queryCompletionBlock
operation = newOperation
self?.add(newOperation)
}
self?.add(operation)
_ = semaphore.wait(timeout: .now() + 60)
if let error = error {
completion(.failure(error))
} else {
completion(.success(records))
}
}
}
}
用法
对于熟悉 Swift 的闭包语法和 Result
类型的人来说,使用该方法相当简单。
let database: CKDatabase = ...
database.fetchAll(recordType: "User") { result in
switch result {
case .success(let users):
// Handle fetched users, ex. save them to the database
case .failure(let error):
// Handle Error
}
}
}
我目前已经在我的应用程序中设置了 CloudKit,因此我正在使用以下代码的帮助添加新记录,
CKRecordID *recordID = [[CKRecordID alloc] initWithRecordName:@"stringArray"];
CKRecord *record = [[CKRecord alloc] initWithRecordType:@"Strings" recordID:recordID];
[record setObject:[NSArray arrayWithObjects:@"one", @"two", @"three", @"four", nil] forKey:@"stringArray"];
[_privateDatabase saveRecord:record completionHandler:nil];
但是,现在我希望能够获取所有记录类型相同的记录,"Strings," 和 return 那些编译成 NSArray 的记录。我该怎么做呢?目前,我想出的只是如何使用recordID单独获取每条记录,这很麻烦,必须有更简单的方法。
[_privateDatabase fetchRecordWithID:recordID completionHandler:^(CKRecord *record, NSError *error) {
if (error) {
// Error handling for failed fetch from private database
}
else {
NSLog(@"ICLOUD TEST: %@", [record objectForKey:@"stringArray"]);
}
}];
啊啊啊,我明白了。使用下面的代码,我能够在数据库上创建对 运行 的查询,然后 return 完成块中的 NSArray,我循环遍历它,然后 returned 值对于 NSLog 中保存的密钥。
NSPredicate *predicate = [NSPredicate predicateWithValue:YES];
CKQuery *query = [[CKQuery alloc] initWithRecordType:@"Strings" predicate:predicate];
[_privateDatabase performQuery:query inZoneWithID:nil completionHandler:^(NSArray *results, NSError *error) {
for (CKRecord *record in results) {
NSLog(@"Contents: %@", [record objectForKey:@"stringArray"]);
}
}];
以下函数将 return 请求记录类型的所有记录:
let database = CKContainer(identifier: "container_here").privateCloudDatabase
typealias RecordsErrorHandler = ([CKRecord], Swift.Error?) -> Void
func fetchRecords(forType type: String, completion: RecordsErrorHandler? = nil) {
var records = [CKRecord]()
let query = CKQuery(recordType: type, predicate: NSPredicate(value: true))
let queryOperation = CKQueryOperation(query: query)
queryOperation.zoneID = CloudAssistant.shared.zone.zoneID
queryOperation.recordFetchedBlock = { record in
records.append(record)
}
queryOperation.queryCompletionBlock = { cursor, error in
self.fetchRecords(with: cursor, error: error, records: records) { records in
completion?(records, nil)
}
}
database.add(queryOperation)
}
private func fetchRecords(with cursor: CKQueryCursor?, error: Swift.Error?, records: [CKRecord], completion: RecordsHandler?) {
var currentRecords = records
if let cursor = cursor, error == nil {
let queryOperation = CKQueryOperation(cursor: cursor)
queryOperation.recordFetchedBlock = { record in
currentRecords.append(record)
}
queryOperation.queryCompletionBlock = { cursor, error in
self.fetchRecords(with: cursor, error: error, records: currentRecords, completion: completion)
}
database.add(queryOperation)
} else {
completion?(records)
}
}
这是 Swift 3.0 中的答案。
func myQuery() {
let predicate = NSPredicate(value: true)
let query = CKQuery(recordType: "tableName", predicate: predicate)
publicDatabase.perform(query, inZoneWith: nil) { (record, error) in
for record: CKRecord in record! {
//...
// if you want to access a certain 'field'.
let name = record.value(forKeyPath: "Name") as! String
}
}
}
Swift4的解决方案,
显示如何获取类型 "YourTable" 的所有记录,还打印 System Field
和 Custom Field
:
let query = CKQuery(recordType: "YourTable", predicate: NSPredicate(value: true))
CKContainer.default().publicCloudDatabase.perform(query, inZoneWith: nil) { (records, error) in
records?.forEach({ (record) in
// System Field from property
let recordName_fromProperty = record.recordID.recordName
print("System Field, recordName: \(recordName_fromProperty)")
// Custom Field from key path (eg: deeplink)
let deeplink = record.value(forKey: "deeplink")
print("Custom Field, deeplink: \(deeplink ?? "")")
})
}
在尝试获取所有记录并了解 Cloudkit 存储的结构和细节时,我发现在调试期间提供以下功能很有用。这使用信号量来保留用于打印的数据结构。可能有更优雅的方法来做到这一点,但这个有效!
//
// Print a list of records in all zones in all databases
//
func printRecordsInContainers() {
let myContainer = CKContainer.default()
// Edit the following dictionary to include any known containers and possible record types
let containerRecordTypes: [CKContainer: [String]] = [ myContainer: ["MyRecordType", "OldRecordType", "MyUser", "PrivateInfo"] ]
let containers = Array(containerRecordTypes.keys)
for containerz in containers {
let databases: [CKDatabase] = [containerz.publicCloudDatabase, containerz.privateCloudDatabase, containerz.sharedCloudDatabase]
for database in databases {
var dbType = "<None>"
if database.databaseScope.rawValue == 1 { dbType = "Public" }
if database.databaseScope.rawValue == 2 { dbType = "Private" }
if database.databaseScope.rawValue == 3 { dbType = "Shared" }
//print("\(database.debugDescription)")
print("\n\n\n ---------------------------------------------------------------------------------------------------")
print(" ----- Container: \(containerz.containerIdentifier ?? "??") ----- Database: \(dbType)")
let semaphore1 = DispatchSemaphore(value: 0) // Initiate semaphore1 to wait for closure to return
database.fetchAllRecordZones { zones, error in
if let error = error {
print(" Error Fetching Zones: \(error.localizedDescription)")
}
else if let zones = zones {
print("~~~~ \(zones.count) : \(zones)")
for zone in zones {
print("----- Zone ID: \(zone.zoneID)\n")
for recordType in containerRecordTypes[container] ?? [] {
print("[ Record Type: \(recordType.description) ]")
let query = CKQuery(recordType: recordType, predicate: NSPredicate(value: true))
let semaphore = DispatchSemaphore(value: 0) // Initiate semaphore to wait for closure to return
database.perform(query, inZoneWith: zone.zoneID) { records, error in
if let error = error {
print(" Error in Record Query: \(error.localizedDescription)")
}
else if let records = records {
printRecordDescriptions(records)
}
semaphore.signal() // Send semaphore signal to indicate closure is complete
}
semaphore.wait() // Wait for semaphore to indicate that closure is complete
}
}
}
else {
print(" Error in fetchAllRecordZones")
}
semaphore1.signal() // Send semaphore1 signal to indicate closure is complete
}
semaphore1.wait() // Wait for semaphore1 to indicate that closure is complete
}
}
}
class func printRecordDescriptions(_ records: [CKRecord]) {
print("Records and Contents List:")
for record in records {
print(" Record: \(record.recordID)")
for key in record.allKeys() {
print(" Key - \(key)")
}
}
print("Record List End\n")
}
Swift 5
在浏览了 SO 上的大量帖子和解决方案之后,我设法找到了一个适合我需要的解决方案,并且对于只想从 iCloud 获取给定类型的所有记录的任何人来说应该足够简单。
解决方案
解决方案使用 CKDatabase 的扩展来引入一种方法来处理 cursor: CKQueryOperation.Cursor
of CKQueryOperation
以继续向 iCloud 请求更多记录。在这种方法中,我分派到后台队列,这样我就可以阻止它并等待操作完全完成,无论是在收到错误还是在收到最后一批记录时。一旦信号量解锁队列,它就会继续调用带有结果的主完成块。我也在完成处理程序中利用 Swift 的 Result
类型。
extension CKDatabase {
func fetchAll(
recordType: String, resultsLimit: Int = 100, timeout: TimeInterval = 60,
completion: @escaping (Result<[CKRecord], Error>) -> Void
) {
DispatchQueue.global().async { [unowned self] in
let query = CKQuery(
recordType: recordType, predicate: NSPredicate(value: true)
)
let semaphore = DispatchSemaphore(value: 0)
var records = [CKRecord]()
var error: Error?
var operation = CKQueryOperation(query: query)
operation.resultsLimit = resultsLimit
operation.recordFetchedBlock = { records.append([=10=]) }
operation.queryCompletionBlock = { (cursor, err) in
guard err == nil, let cursor = cursor else {
error = err
semaphore.signal()
return
}
let newOperation = CKQueryOperation(cursor: cursor)
newOperation.resultsLimit = operation.resultsLimit
newOperation.recordFetchedBlock = operation.recordFetchedBlock
newOperation.queryCompletionBlock = operation.queryCompletionBlock
operation = newOperation
self?.add(newOperation)
}
self?.add(operation)
_ = semaphore.wait(timeout: .now() + 60)
if let error = error {
completion(.failure(error))
} else {
completion(.success(records))
}
}
}
}
用法
对于熟悉 Swift 的闭包语法和 Result
类型的人来说,使用该方法相当简单。
let database: CKDatabase = ...
database.fetchAll(recordType: "User") { result in
switch result {
case .success(let users):
// Handle fetched users, ex. save them to the database
case .failure(let error):
// Handle Error
}
}
}