【发布时间】:2020-01-20 08:35:50
【问题描述】:
上下文:
- 包含 CloudKit 中所有数据的应用程序
- ViewController 调用查询来加载 tableview 的数据
- 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 { $0.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
}
注意:我已经测试了视图控制器中的 week 数组最终是否正确设置 - 所以它似乎纯粹是一个时间问题 :)
我已经测试了 .signal() 的位置,如果我将它放在“DispatchQueue.main.async”块中,它永远不会被触发 - 可能是因为该块本身正在等待信号。
但是,如果我将它放在其他任何地方,则视图控制器会在此时启动,并且不会及时调用完成(周)。
也许这很明显 - 但作为我第一次使用信号量 - 我正在努力弄清楚!
更新 1:它适用于 DispatchQueue(label: "background")
一旦我发现 semaphore.wait() 永远不会在主线程上被 semaphore.signal() 调用,我就能让它工作。
所以我把它从: DispatchQueue.main.async 到 DispatchQueue(label: "background").async 并在里面弹出 semaphore.signal() 就成功了
欢迎评论/批评!
static func fetchWeeks(for references: [CKRecord.Reference],
_ completion: @escaping ([Week]) -> Void) {
NSLog("inside fetchWeeks in Week ")
let recordIDs = references.map { $0.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
}
}
更新 2:尽量避免使用信号量
每个评论线程——我们不应该在 CloudKit 中使用信号量——所以我很可能在做一些愚蠢的事情 :)
将 fetchWeeks() 移动到 viewController 以尝试隔离问题...但它仍然会崩溃,因为 fetchWeeks() 在代码尝试执行之后的行并使用周数组之前尚未完成
我的视图控制器:
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: { $0.weekSequence < $1.weekSequence })
}
//moved this to the view controller
func fetchWeeks(for references: [CKRecord.Reference],
_ completion: @escaping ([Week]) -> Void) {
let recordIDs = references.map { $0.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 中使用信号量。它根本不需要。崩溃发生在哪里?并且强烈建议处理
fetchRecordsCompletionBlock中的潜在error -
@vadian - 当主视图控制器尝试使用尚未从 DispatchQueue.main.async 返回的数组时,崩溃发生在主视图控制器中。错误的好地方,将解决。
-
@vadian - 我通过使用 DispatchQueue(label: "background").async 解决了这个问题 - 尽管您强调 Never 在 CloudKit 中使用信号量 - 我很感兴趣了解我是否应该以不同的方式解决它,这只是运气吗?还添加了错误处理:) - 我是一个完整的初学者 - 所以你可以分享的任何见解都会非常有帮助!
-
信号量完全没有意义,因为完成处理程序无论如何都是异步的。这是可怕的做法。删除它!
-
删除它会导致应用程序崩溃......并且它可以使用它。没有它,应用程序会继续尝试使用返回的数组 Before 它存在。如果我可以提供有关我的应用程序流程的更多信息 - 请告诉我!