fetchRecordCompletionBlock和信号量-帮助理解执行顺序

问题描述 投票:0回答:1

上下文:

  • 具有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 { $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()尚未完成,在代码尝试在此之后执行代码行并使用weeks数组之前,它仍然爆炸了

我的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: { $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)
}
swift cloudkit
1个回答
0
投票

首先将数据源数组始终声明为非可选的空数组,以消除不必要的展开可选的包装

var weeks = [Week]()

错误是您没有在正确的位置使用返回的数据。

由于闭包是异步的,因此必须继续进行[[inside闭包

fetchWeeks(for: challenge!.weeks!) { weeks in self.weeks = weeks self.weeks = weeks.sorted(by: { $0.weekSequence < $1.weekSequence }) }
或更简单

fetchWeeks(for: challenge!.weeks!) { weeks in self.weeks = weeks.sorted(by: { $0.weekSequence < $1.weekSequence }) }

并且如果您需要重新加载表视图,也请执行以下操作

inside

闭包fetchWeeks(for: challenge!.weeks!) { weeks in self.weeks = weeks.sorted(by: { $0.weekSequence < $1.weekSequence }) self.rideTableView.reloadData() }
为此,您必须在主线程上调用completion

DispatchQueue.main.async { completion(weeks) }

最后删除丑陋的信号灯!
© www.soinside.com 2019 - 2024. All rights reserved.