RunLoop behaves differently between iOS15 and iOS14

My project uses the RunLoop method.

RunLoop.current.run(until: Date.init(timeIntervalSinceNow: 1.0))

[iOS14.8 Device:iPhone7 - iPhone12]

It works well. GUI is not blocked.

[iOS15.1 Device:iPhoneXS - iPhone13]

It works as same as sleep(1) That means GUI is blocked for 1.0 seconds.

[iOS15.1 Device:iPhone8] It works well. GUI is not blocked.

I am very confused this issue. Is it a well known issue? Regards.

Answered by Koji_Kamogawa in 694989022

I got the information from Apple by DTS.

The comments are below.

This is a known issue. However, nested run loops are not a recommended practice today due to complexities of timing event loops and the response chain. Could you replace the logic which requires spinning the run loop with a call to performSelector or a timer instead?

So I rewrote the code using "An escaping closure to handle the completion callback with Timer". It works good, but there was a lot of code changes and the nesting was very deep(In the sense of the caller). I had no other ideas.

ex)

func delayedDisplay(completion: @escaping () -> Void) {
   let partString = moji.prefix(1)
   if (moji == "") {
     print("<END>")
     // 5. Invokes the completion handler when the recursive work ends.
    completion()
     return
  }
   
   print(partString, terminator: "")
   
   // 2. Schedules a single use timer to perform recursion.
  _ = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: false) { timer in
     // 3. Perform some work.
     if let range = self.moji.range(of: partString) {
       self.moji.replaceSubrange(range, with: "")
       // 4. Passes the completion handler again for proper recursion.
       self.delayedDisplay(completion: completion)
    }
  }
}

I am very confused this issue. Is it a well known issue? 

It is not clearly documented what would happen if you call RunLoop.current.run(until:) in the main thread.

Aren't you calling it in the main thread?

Any sort of undocumented behaviors would change at any time without any notices.

Why are you calling RunLoop in your code?

I read Apple's documentation and decided from the explanation of RunLoop, run (), but that is probably a mistake. I'm calling it in the Main thread. Technically, I should have used semaphore and DispatchQueue, but I was able to achieve the desired behavior with RunLoop, so I decided to use it. I was very disappointed・・・・

Thanks

What I read is not to run from the main thread, but:

The RunLoop class is generally not thread-safe, and you must call its methods only within the context of the current thread. Don’t call the methods of a RunLoop object running in a different thread, which might cause unexpected results.

Accepted Answer

I got the information from Apple by DTS.

The comments are below.

This is a known issue. However, nested run loops are not a recommended practice today due to complexities of timing event loops and the response chain. Could you replace the logic which requires spinning the run loop with a call to performSelector or a timer instead?

So I rewrote the code using "An escaping closure to handle the completion callback with Timer". It works good, but there was a lot of code changes and the nesting was very deep(In the sense of the caller). I had no other ideas.

ex)

func delayedDisplay(completion: @escaping () -> Void) {
   let partString = moji.prefix(1)
   if (moji == "") {
     print("<END>")
     // 5. Invokes the completion handler when the recursive work ends.
    completion()
     return
  }
   
   print(partString, terminator: "")
   
   // 2. Schedules a single use timer to perform recursion.
  _ = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: false) { timer in
     // 3. Perform some work.
     if let range = self.moji.range(of: partString) {
       self.moji.replaceSubrange(range, with: "")
       // 4. Passes the completion handler again for proper recursion.
       self.delayedDisplay(completion: completion)
    }
  }
}
RunLoop behaves differently between iOS15 and iOS14
 
 
Q