fetchRecordCompletionBlock 和信号量 - 帮助理解执行顺序
fetchRecordCompletionBlock and semaphores - help understanding execution order
上下文:
- 包含所有数据的应用 CloudKit
- ViewController 调用查询以加载表视图的数据
- tableview 崩溃,因为 tableview 的数据数组没有
从 CK 回来
- 我研究了信号量并且快要了
工作但似乎无法弄清楚在哪里放置
semaphore.signal() 以获得完全正确的行为
在 viewDidLoad 中,我调用函数:
Week.fetchWeeks(for: challenge!.weeks!) { weeks in
self.weeks = weeks
}
和函数:
static func fetchWeeks(for references: [CKRecord.Reference],
_ completion: @escaping ([Week]) -> Void) {
let recordIDs = references.map { [=11=].recordID }
let operation = CKFetchRecordsOperation(recordIDs: recordIDs)
operation.qualityOfService = .utility
let semaphore = DispatchSemaphore(value: 0)
operation.fetchRecordsCompletionBlock = { records, error in
let weeks = records?.values.map(Week.init) ?? []
DispatchQueue.main.async {
completion(weeks)
//Option 1: putting semaphore.signal() here means it never completes
// beyond initialization of the week records
}
//Option 2: putting semaphore.signal() here means it completes after the
// initialization of the Week items, but before completion(weeks) is done
// so the array isn't initialized in the view controller in time. so the
// VC tries to use weeks and unwraps a nil.
semaphore.signal()
}
Model.currentModel.publicDB.add(operation)
semaphore.wait() // blocking the thread until .signal is called
}
注意:我已经测试过视图控制器中的周数组最终已正确设置 - 所以它似乎纯粹是一个时间问题:)
我已经测试了 .signal() 的位置,如果我将它放在 'DispatchQueue.main.async' 块中,它永远不会被触发 - 可能是因为该块本身正在等待信号。
但是,如果我把它放在其他地方,那么 viewcontroller 会在那个时候开始,并且不会及时调用完成(周)。
也许这很明显 - 但作为我第一次使用信号量 - 我正在努力弄清楚!
Update 1: It works with DispatchQueue(label: "background")
一旦我意识到 semaphore.wait() 永远不会在主线程上被 semaphore.signal() 调用,我就能够让它工作。
所以我将其更改为:
DispatchQueue.main.async
到
DispatchQueue(label: "background").async 并在里面弹出 semaphore.signal() 并且它成功了
Comments/critiques 欢迎!
static func fetchWeeks(for references: [CKRecord.Reference],
_ completion: @escaping ([Week]) -> Void) {
NSLog("inside fetchWeeks in Week ")
let recordIDs = references.map { [=12=].recordID }
let operation = CKFetchRecordsOperation(recordIDs: recordIDs)
operation.qualityOfService = .utility
let semaphore = DispatchSemaphore(value: 0)
operation.fetchRecordsCompletionBlock = { records, error in
if error != nil {
print(error?.localizedDescription)
}
let weeks = records?.values.map(Week.init) ?? []
DispatchQueue(label: "background").async {
completion(weeks)
semaphore.signal()
}
}
Model.currentModel.publicDB.add(operation)
semaphore.wait() // blocking the thread until .signal is called
}
}
Update 2: Trying to avoid use of semaphores
根据评论线程 - 我们不需要将信号量与 CloudKit 一起使用 - 所以我很可能在做一些愚蠢的事情:)
将 fetchWeeks() 移动到 viewController 以尝试隔离问题...但它仍然爆炸,因为 fetchWeeks() 在代码尝试执行之后的行并使用之前尚未完成星期数组
我的viewController:
class ChallengeDetailViewController: UIViewController {
@IBOutlet weak var rideTableView: UITableView!
//set by the inbound segue
var challenge: Challenge?
// set in fetchWeeks based on the challenge
var weeks: [Week]?
override func viewDidLoad() {
super.viewDidLoad()
rideTableView.dataSource = self
rideTableView.register(UINib(nibName: K.cellNibName, bundle: nil), forCellReuseIdentifier: K.cellIdentifier)
rideTableView.delegate = self
fetchWeeks(for: challenge!.weeks!) { weeks in
self.weeks = weeks
}
//This is where it blows up as weeks is nil
weeks = weeks!.sorted(by: { [=13=].weekSequence < .weekSequence })
}
//moved this to the view controller
func fetchWeeks(for references: [CKRecord.Reference],
_ completion: @escaping ([Week]) -> Void) {
let recordIDs = references.map { [=13=].recordID }
let operation = CKFetchRecordsOperation(recordIDs: recordIDs)
operation.qualityOfService = .utility
operation.fetchRecordsCompletionBlock = { records, error in
if error != nil {
print(error?.localizedDescription)
}
let weeks = records?.values.map(Week.init) ?? []
DispatchQueue.main.sync {
completion(weeks)
}
}
Model.currentModel.publicDB.add(operation)
}
再次声明:永远不要将信号量与 CloudKit API.
一起使用
首先,始终将数据源数组声明为非可选空数组,以避免不必要地展开可选
var weeks = [Week]()
错误是你没有在正确的地方使用获取的数据。
由于闭包是异步的,您必须在内部闭包
fetchWeeks(for: challenge!.weeks!) { [weak self] weeks in
self?.weeks = weeks
self?.weeks = weeks.sorted(by: { [=11=].weekSequence < .weekSequence })
}
或更简单
fetchWeeks(for: challenge!.weeks!) { [weak self] weeks in
self?.weeks = weeks.sorted{ [=12=].weekSequence < .weekSequence }
}
如果您需要重新加载 table 视图,也可以在闭包 中进行
fetchWeeks(for: challenge!.weeks!) { [weak self] weeks in
self?.weeks = weeks.sorted{ [=13=].weekSequence < .weekSequence }
self?.rideTableView.reloadData()
}
为此你必须在主线程上调用 completion
DispatchQueue.main.async {
completion(weeks)
}
最后删除丑陋的信号量!
let semaphore = DispatchSemaphore(value: 0)
...
semaphore.signal()
...
semaphore.wait()
上下文:
- 包含所有数据的应用 CloudKit
- ViewController 调用查询以加载表视图的数据
- tableview 崩溃,因为 tableview 的数据数组没有 从 CK 回来
- 我研究了信号量并且快要了 工作但似乎无法弄清楚在哪里放置 semaphore.signal() 以获得完全正确的行为
在 viewDidLoad 中,我调用函数:
Week.fetchWeeks(for: challenge!.weeks!) { weeks in
self.weeks = weeks
}
和函数:
static func fetchWeeks(for references: [CKRecord.Reference],
_ completion: @escaping ([Week]) -> Void) {
let recordIDs = references.map { [=11=].recordID }
let operation = CKFetchRecordsOperation(recordIDs: recordIDs)
operation.qualityOfService = .utility
let semaphore = DispatchSemaphore(value: 0)
operation.fetchRecordsCompletionBlock = { records, error in
let weeks = records?.values.map(Week.init) ?? []
DispatchQueue.main.async {
completion(weeks)
//Option 1: putting semaphore.signal() here means it never completes
// beyond initialization of the week records
}
//Option 2: putting semaphore.signal() here means it completes after the
// initialization of the Week items, but before completion(weeks) is done
// so the array isn't initialized in the view controller in time. so the
// VC tries to use weeks and unwraps a nil.
semaphore.signal()
}
Model.currentModel.publicDB.add(operation)
semaphore.wait() // blocking the thread until .signal is called
}
注意:我已经测试过视图控制器中的周数组最终已正确设置 - 所以它似乎纯粹是一个时间问题:)
我已经测试了 .signal() 的位置,如果我将它放在 'DispatchQueue.main.async' 块中,它永远不会被触发 - 可能是因为该块本身正在等待信号。
但是,如果我把它放在其他地方,那么 viewcontroller 会在那个时候开始,并且不会及时调用完成(周)。
也许这很明显 - 但作为我第一次使用信号量 - 我正在努力弄清楚!
Update 1: It works with DispatchQueue(label: "background")
一旦我意识到 semaphore.wait() 永远不会在主线程上被 semaphore.signal() 调用,我就能够让它工作。
所以我将其更改为: DispatchQueue.main.async 到 DispatchQueue(label: "background").async 并在里面弹出 semaphore.signal() 并且它成功了
Comments/critiques 欢迎!
static func fetchWeeks(for references: [CKRecord.Reference],
_ completion: @escaping ([Week]) -> Void) {
NSLog("inside fetchWeeks in Week ")
let recordIDs = references.map { [=12=].recordID }
let operation = CKFetchRecordsOperation(recordIDs: recordIDs)
operation.qualityOfService = .utility
let semaphore = DispatchSemaphore(value: 0)
operation.fetchRecordsCompletionBlock = { records, error in
if error != nil {
print(error?.localizedDescription)
}
let weeks = records?.values.map(Week.init) ?? []
DispatchQueue(label: "background").async {
completion(weeks)
semaphore.signal()
}
}
Model.currentModel.publicDB.add(operation)
semaphore.wait() // blocking the thread until .signal is called
}
}
Update 2: Trying to avoid use of semaphores
根据评论线程 - 我们不需要将信号量与 CloudKit 一起使用 - 所以我很可能在做一些愚蠢的事情:)
将 fetchWeeks() 移动到 viewController 以尝试隔离问题...但它仍然爆炸,因为 fetchWeeks() 在代码尝试执行之后的行并使用之前尚未完成星期数组
我的viewController:
class ChallengeDetailViewController: UIViewController {
@IBOutlet weak var rideTableView: UITableView!
//set by the inbound segue
var challenge: Challenge?
// set in fetchWeeks based on the challenge
var weeks: [Week]?
override func viewDidLoad() {
super.viewDidLoad()
rideTableView.dataSource = self
rideTableView.register(UINib(nibName: K.cellNibName, bundle: nil), forCellReuseIdentifier: K.cellIdentifier)
rideTableView.delegate = self
fetchWeeks(for: challenge!.weeks!) { weeks in
self.weeks = weeks
}
//This is where it blows up as weeks is nil
weeks = weeks!.sorted(by: { [=13=].weekSequence < .weekSequence })
}
//moved this to the view controller
func fetchWeeks(for references: [CKRecord.Reference],
_ completion: @escaping ([Week]) -> Void) {
let recordIDs = references.map { [=13=].recordID }
let operation = CKFetchRecordsOperation(recordIDs: recordIDs)
operation.qualityOfService = .utility
operation.fetchRecordsCompletionBlock = { records, error in
if error != nil {
print(error?.localizedDescription)
}
let weeks = records?.values.map(Week.init) ?? []
DispatchQueue.main.sync {
completion(weeks)
}
}
Model.currentModel.publicDB.add(operation)
}
再次声明:永远不要将信号量与 CloudKit API.
一起使用首先,始终将数据源数组声明为非可选空数组,以避免不必要地展开可选
var weeks = [Week]()
错误是你没有在正确的地方使用获取的数据。
由于闭包是异步的,您必须在内部闭包
fetchWeeks(for: challenge!.weeks!) { [weak self] weeks in
self?.weeks = weeks
self?.weeks = weeks.sorted(by: { [=11=].weekSequence < .weekSequence })
}
或更简单
fetchWeeks(for: challenge!.weeks!) { [weak self] weeks in
self?.weeks = weeks.sorted{ [=12=].weekSequence < .weekSequence }
}
如果您需要重新加载 table 视图,也可以在闭包 中进行
fetchWeeks(for: challenge!.weeks!) { [weak self] weeks in
self?.weeks = weeks.sorted{ [=13=].weekSequence < .weekSequence }
self?.rideTableView.reloadData()
}
为此你必须在主线程上调用 completion
DispatchQueue.main.async {
completion(weeks)
}
最后删除丑陋的信号量!
let semaphore = DispatchSemaphore(value: 0)
...
semaphore.signal()
...
semaphore.wait()