Ios 在苹果公司';s Foundation/Swift/Objective-C,runLoop.run如何阻塞,但仍然允许DispatchWorkItems处理?

Ios 在苹果公司';s Foundation/Swift/Objective-C,runLoop.run如何阻塞,但仍然允许DispatchWorkItems处理?,ios,swift,grand-central-dispatch,foundation,nsrunloop,Ios,Swift,Grand Central Dispatch,Foundation,Nsrunloop,为什么这段代码会这样执行?注意测试代码中的注释,这些注释指示通过和失败的行 更具体地说,RunLoop.current.run(直到:Date(timeintervalncenow:0.01))如何在那里等待,同时仍然允许处理DispatchWorkItem,{[weak self]in self?.name=newName}?如果线程正在运行循环中等待,那么线程如何处理任何工作项 (如果问题没有意义,请纠正我的理解) 班级人员{ 私有(集合)变量名称:String=“” func update

为什么这段代码会这样执行?注意测试代码中的注释,这些注释指示通过和失败的行

更具体地说,
RunLoop.current.run(直到:Date(timeintervalncenow:0.01))
如何在那里等待,同时仍然允许处理
DispatchWorkItem
{[weak self]in self?.name=newName}
?如果线程正在运行循环中等待,那么线程如何处理任何工作项

(如果问题没有意义,请纠正我的理解)

班级人员{
私有(集合)变量名称:String=“”
func updateName(到newName:String){
DispatchQueue.main.async{self?.name=newName}中的[weak self]
}
}
类PersonTests:XCTestCase{
func testUpdateName(){
设sut=Person()
sut.updateName(改为:“Bob”)
xctasertequal(sut.name,“Bob”)//失败:`sut.name`仍然是“”`
assertEventually{sut.name==“Bob”}//通过
}
}
最终功能资产(
超时:时间间隔=1,
断言:()->Bool
) {
让timeoutDate=Date(timeIntervalSinceNow:timeout)
while Date()
while
循环使执行停止,但是
run
命令不只是等待,而是处理该线程的run循环上的事件,包括GCD源、计时器、调度块等的处理


FWIW,当您处理异步方法时,您可以:

  • 使用完成处理程序

    通常,如果您有一个异步方法,为了解释对象的状态(例如,何时关闭微调器,让用户知道何时完成),您需要提供一个完成处理程序。(这是假设简单的
    async
    是一些更复杂的异步模式的简化。)

    如果您真的希望有一个异步方法来异步改变对象,并且您的应用程序当前不需要知道何时完成,那么将该完成处理程序设置为可选:

     func updateName(to name: String, completion: (() -> Void)? = nil) {
         DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { [weak self] in
             self?.name = name
             completion?()
         }
     }
    
    然后,您可以在单元测试中使用期望值,这是测试异步方法的标准方法:

     func testUpdateName() {
         let e = expectation(description: "Person.updateName")
    
         let person = Person()
         person.updateName(to: "Bob") {
             e.fulfill()
         }
    
         waitForExpectations(timeout: 1)
    
         XCTAssertEqual(person.name, "Bob")
     }
    
  • 使用“阅读器”

    前一点是关于测试异步方法的一般观察。但是,如果您确实有一个异步改变对象的方法,您通常不会直接公开改变的属性,而是可以使用“reader”方法以线程安全的方式获取属性值。(例如,在读写器模式中,您可以异步更新,但您的读写器将等待任何挂起的写入首先完成。)

    这样,考虑使用读写器模式:<代码>人>代码>:

     class Person {
         // don't expose the name at all
         private var name: String = ""
    
         // private synchronization reader-writer queue
         private let queue = DispatchQueue(label: "person.readerwriter", attributes: .concurrent)
    
         // perform writes asynchronously with a barrier
         func writeName(to name: String) {
             queue.async(flags: .barrier) {
                 self.name = name
             }
         }
    
         // perform reads synchronously (concurrently with respect to other reads, but synchronized with any writes)
         func readName() -> String {
             return queue.sync {
                 return name
             }
         }
     }
    
    然后测试将使用
    readName

     func testUpdateName() {
         let person = Person()
         person.writeName(to: "Bob")
         let name = person.readName()
         XCTAssertEqual(name, "Bob")
     }
    
    但如果没有同步读取的方法,通常也不会有异步写入的属性。如果仅从主线程使用,问题中的示例将有效。否则,你会有比赛条件


  • 这也是XctExpection等待的工作方式吗?我一直在想这个问题。我明白了,这就像是一个函数调用,在这个特殊的情况下,它会耗尽调度队列case@matt我还没有深入了解期望代码,但我怀疑是这样。它当然不是单纯的等待(例如信号量等)。它可以像运行循环方法一样处理事件。确切地说,这正是我发现的神秘之处。:)@guptron-它比这更广泛,但这是基本的想法。请参阅旧的《线程编程指南》中关于的已存档章节。
     func testUpdateName() {
         let person = Person()
         person.writeName(to: "Bob")
         let name = person.readName()
         XCTAssertEqual(name, "Bob")
     }