Я впервые работаю с CloudKit, и у меня возникают проблемы с выполнением CKQueryOperation для запроса всех записей заданного типа. Не помогает и то, что Apple объявила устаревшей большую часть вещей, которые я нашел в Интернете, и что их документация по этим вещам совершенно пуста, кроме объявления func. Я думаю, что у меня есть «скелет» кода, но я не уверен, что входит в .recordMatchedBlock и .queryResultsBlock.
У меня есть функция queryAllNotes(), которая должна запрашивать все записи в общедоступной базе данных типа «Заметки» и возвращать массив кортежей заголовка заметки и связанного с ним идентификатора облака, который представляет собой просто уникальное имя записи, данное ему при добавлении в базу данных.
Вот код для queryAllNotes():
private func queryAllNotes() -> [(title: String, cloudID: String)] {
/*
query all notes in the cloud DB into an array to populate
the tableView
*/
var resultArray: [(title: String, cloudID: String)] = []
//set the cloud database to .publicCloudDatabase
let container = CKContainer.default()
let cloudDB = container.publicCloudDatabase
let pred = NSPredicate(value: true) //true -> return all records
let query = CKQuery(recordType: "Notes", predicate: pred)
let queryOperation = CKQueryOperation(query: query)
queryOperation.database = cloudDB
queryOperation.resultsLimit = 100
queryOperation.recordMatchedBlock = { (record: CKRecord) in
let noteTitle = record["Title"] as! String
let noteCloudID = record.recordID.recordName
resultArray.append((noteTitle, noteCloudID))
}
queryOperation.queryResultBlock = { (cursor, error) in
}
return resultArray
}
Насколько я понимаю, .recordMatchedBlock вызывается для каждой записи, возвращаемой запросом, поэтому я думаю, что он завершен, но я могу сильно ошибаться. Что касается .queryResultBlock, насколько я понимаю, запрос технически возвращает только одну запись за раз, и этот блок, по сути, говорит запросу запуститься снова для следующей записи для всех записей в .resultLimit. Как я могу структурировать этот запрос? Я очень хочу понять, что делает каждый из этих блоков.
Также это для приложения macOS; Я не знаю, отличается ли код для macOS и iOS, но я подумал, что должен включить это на всякий случай.
Также я получаю сообщение об ошибке «Тип выражения неоднозначен без дополнительного контекста», что, как я предполагаю, связано с тем, что я не завершил настройку своего запроса. Если это по другой причине, это также может объяснить, почему это происходит.
Я называю эту функцию внутри viewDidLoad() так:
//array var for the array that is used to populate the tableView
var noteRecords: [(title: String, cloudID: String)] = []
override func viewDidLoad() {
super.viewDidLoad()
// do additional setup here
// set serachField delegate
searchField.delegate = self
// set tableView delegate and data source
tableView.delegate = self
tableView.dataSource = self
// load all NoteRecords in public cloud db into noteRecords
noteRecords = queryAllNotes()
}
@vadian Я немного запутался в том, что вы говорите, поэтому не могли бы вы проверить редактирование, где я добавил, где я звоню queryAllNotes() внутри viewDidLoad(), это мой первый раз, когда я использую cloudKit и асинхронные функции, поэтому, если бы вы могли дать ответ о том, как Структурируйте CKqueryOperation внутри func и как вызвать его внутри viewDidLoad(), это было бы очень полезно.
@vadian Я удалил асинхронный тег, который, я думаю, все еще был там из предыдущей версии той функции, которую я написал, которая не работала, поэтому я думаю, что мне просто нужно правильно структурировать CKQueryOperation сейчас, я также отредактировал вопрос на отразить мой текущий код
Иногда все еще может быть необходимо использовать CKQueryOperation, пока мы не получим полную замену асинхронного API, но я не вижу необходимости в чем-то большем, чем самый распространенный вариант использования. Таким образом, я действительно не думаю, что этот вопрос касается CKQueryOperation. stackoverflow.com/questions/71200053/…
@Jessy Я видел этот пост ранее и был немного сбит с толку кодом, поэтому не могли бы вы немного объяснить его, чтобы я знал, как правильно его использовать для заполнения моего массива noteRecords? Извините, если это глупый вопрос, я впервые работаю с cloudKit, и я немного растерялся.
Я понятия не имею, что могло бы вас смутить, а что нет — пожалуйста, задайте любой конкретный вопрос в другом месте, а затем отправьте мне сообщение!
@Jessy Где бы я поместил код, чтобы взять индекс записи «Title» и идентификатор облака, который является просто уникальным именем записи в базе данных, и добавить их в мой массив, который возвращает func, и как бы я назвал его для заполнения моего массива noteRecords?
Сделайте то, что сказал vadian, и создайте структуру. map CKRecords использует инициализатор вашей структуры. Берите оттуда то, что вам нужно.





С новым шаблоном async получать данные из CloudKit стало намного проще.
Вместо CKQueryOperation вы вызываете records(matching:resultsLimit:) напрямую и сопоставляете результат с тем, что вам нравится.
Возможная ошибка передается вызывающей стороне.
func queryAllNotes() async throws -> [(title: String, cloudID: String)] {
//set the cloud database to .publicCloudDatabase
let container = CKContainer.default()
let cloudDB = container.publicCloudDatabase
let pred = NSPredicate(value: true) //true -> return all records
let query = CKQuery(recordType: "Notes", predicate: pred)
let (notesResults, _) = try await cloudDB.records(matching: query,
resultsLimit: 100)
return notesResults
.compactMap { _, result in
guard let record = try? result.get(),
let noteTitle = record["Title"] as? String else { return nil }
return (title: noteTitle, cloudID: record.recordID.recordName)
}
}
И использовать его
override func viewDidLoad() {
super.viewDidLoad()
// do additional setup here
// set serachField delegate
searchField.delegate = self
// set tableView delegate and data source
tableView.delegate = self
tableView.dataSource = self
// load all NoteRecords in public cloud db into noteRecords
Task {
do {
noteRecords = try await queryAllNotes()
tableView.reloadData()
} catch {
print(error)
}
}
}
Пожалуйста, посмотрите соответствующее видео с WWDC 2021 для получения подробной информации об API async CloudKit, а также Примеры Apple на GitHub.
Примечание:
Вместо кортежа используйте структуру. Кортежи в качестве массива источников данных не рекомендуются.
Сейчас я пытаюсь изменить эту функцию для работы с настраиваемыми зонами в частной базе данных, и мне было интересно, не могли бы вы взглянуть на этот вопрос здесь
Вы можете создать несколько зон в частной базе данных с помощью modifyRecordZones(saving:deleting:)
Я уже создал зоны в частной базе данных и поместил в них некоторые предварительно заполненные данные, чтобы проверить их, мне больше интересно, как бы я изменил эту функцию, которую вы предоставили, для работы с определенной зоной в частной базе данных. . Если я полностью пропущу отметку, я был бы более чем признателен за подробное объяснение вопроса, который я связал
Прочтите документацию CKDatabase. Есть много API, которые имеют параметр зоны записи. Этот параметр можно опустить, если адресуется зона по умолчанию.
Я попытался использовать .records(matching: , inZoneWith: ) , и когда я отлаживаю код, сеанс завершается, когда он достигает этой строки. Сейчас я пытаюсь использовать fetch(withQuery: , inZoneWith: ), но не знаю, как написать обработчик завершения, так как для этой функции нет документации. Если бы вы могли дать некоторое представление о том, почему функция .records не выполняется правильно, или как настроить обработчик завершения для функции .fetch, мы будем вам очень признательны.
Вы смешиваете подпись
asyncс асинхронным API в синхронном контексте. Вы не можете использовать оба одновременно. Либо используйте новыйasync/await, либо добавьте обработчик завершения.