I'm using swift 3, to make delayed events, this is the code
public func delay(bySeconds seconds: Double, dispatchLevel: DispatchLevel = .main, closure: #escaping () -> Void)
{
let dispatchTime = DispatchTime.now() + seconds
dispatchLevel.dispatchQueue.asyncAfter(deadline: dispatchTime, execute: closure)
}
public enum DispatchLevel
{
case main, userInteractive, userInitiated, utility, background
var dispatchQueue: DispatchQueue
{
switch self
{
case .main: return DispatchQueue.main
case .userInteractive: return DispatchQueue.global(qos: .userInteractive)
case .userInitiated: return DispatchQueue.global(qos: .userInitiated)
case .utility: return DispatchQueue.global(qos: .utility)
case .background: return DispatchQueue.global(qos: .background)
}
}
}
override func viewDidAppear(_ animated: Bool)
{
}
override func viewDidLoad()
{
super.viewDidLoad()
for i in 0..<20
{
delay(bySeconds: 1.0+Double(i)*0.5, dispatchLevel: .background)
{
print("__ \(i)")
// delayed code that will run on background thread
}
}
}
the actual output, notice the change of pattern after 10
__ 0
__ 1
__ 2
__ 3
__ 4
__ 5
__ 6
__ 7
__ 8
__ 9
__ 10
__ 12
__ 11
__ 14
__ 13
__ 16
__ 15
__ 17
__ 18
__ 19
the expected output
__ 0
__ 1
__ 2
__ 3
__ 4
__ 5
__ 6
__ 7
__ 8
__ 9
__ 10
__ 11
__ 12
__ 13
__ 14
__ 15
__ 16
__ 17
__ 18
__ 19
is there something wrong with the delay extension?
The key response is that you are using asynchronous function and concurent-queue(background) .
dispatchLevel.dispatchQueue.asyncAfter(deadline: dispatchTime, execute: closure)
The above code will retunrs immediately and it just set the task to be executed in a fututr time, as result your delay function will also return immediately. As result this does not block the loop from going to next (in contract with a sycn function).
Another side is the fact that background queue is a concurrent queue means no grantee that tasks will finish executing in the same order they are added the queue and this explain the resuts you get.
By contrast if you use main-queue as it is a serial-queue, then there is a garantee that it execute and finish one by one in the order they are added. but you are going to block the UI/ responsiveness of the app
You made the assumption that when task is scheduled first on concurrent queue, it will be executed first. A concurrent queue distribute its tasks over multiple threads, and even though they are added to each thread in order, each task will have a random delay that may cause them to execute out of order.
To illustrate the point, let's strip down your code to the bare minimum, and measure when a task was scheduled vs. when it was actually executed:
let queue = DispatchQueue.global(qos: .userInteractive)
for i in 0..<20 {
let scheduledTime = DispatchTime.now() + Double(i) * 0.5
queue.asyncAfter(deadline: scheduledTime) {
let threadID = pthread_mach_thread_np(pthread_self()) // The thread that the task is executed on
let executionTime = DispatchTime.now()
let delay = executionTime.uptimeNanoseconds - scheduledTime.uptimeNanoseconds
print(i, scheduledTime.uptimeNanoseconds, executionTime.uptimeNanoseconds, delay, threadID, separator: "\t")
}
}
// Wait for all the tasks to complete. This is not how you should wait
// but this is just sample code to illustrate a point.
sleep(15)
Result, in nanoseconds (some formatting added):
i scheduledTime executionTime delay threadID
0 142,803,882,452,582 142,803,883,273,138 820,556 3331
1 142,804,383,177,169 142,804,478,766,410 95,589,241 3331
2 142,804,883,221,388 142,804,958,658,498 75,437,110 3331
3 142,805,383,223,641 142,805,428,926,049 45,702,408 3331
4 142,805,883,224,792 142,806,066,279,866 183,055,074 3331
5 142,806,383,225,771 142,806,614,277,038 231,051,267 3331
6 142,806,883,229,494 142,807,145,347,839 262,118,345 3331
7 142,807,383,230,527 142,807,696,729,955 313,499,428 3331
8 142,807,883,231,420 142,808,249,459,465 366,228,045 3331
9 142,808,383,232,293 142,808,779,492,453 396,260,160 3331
10 142,808,883,233,183 142,809,374,609,495 491,376,312 3331
12 142,809,883,237,042 142,809,918,923,562 35,686,520 4355
11 142,809,383,234,072 142,809,918,923,592 535,689,520 3331
13 142,810,383,238,029 142,811,014,010,484 630,772,455 3331
14 142,810,883,238,910 142,811,014,040,582 130,801,672 4355
15 142,811,383,239,808 142,812,119,998,576 736,758,768 4355
16 142,811,883,240,686 142,812,120,019,559 236,778,873 3331
18 142,812,883,242,410 142,813,228,621,306 345,378,896 4355
17 142,812,383,241,550 142,813,228,646,734 845,405,184 3331
19 142,813,383,245,491 142,814,307,199,255 923,953,764 3331
The 20 tasks were distributed across 2 threads (3331 and 4355). Within each thread, the tasks were executed in order but the threads may be sent to a different CPU cores and hence causes out-of-order execution. Each task is also randomly delayed by up to 900ms. That's the trade-off in using queues: you have no control over the delay since who know what else is running on these global queues. You have 3 options here:
If timing is super-critical and you want to get a task executed with as little delay as possible, create and manage your own thread.
Use a serial instead of concurrent queue.
Scheduling dependent tasks by introducing a delay is usually a bad idea anyhow. Look into NSOperationQueue which allows you to specify the dependencies between tasks.
I have an existing iPhone app that I'm adding a UISplitViewController to. The iPad part works like a charm, but I'm having a guaranteed crash with the iPhone 6(S) Plus.
Setup - Master is a UITabBarController. Initial detail is a view with a placeholder logo view. Once an object is selected, detail is replaced with a UITabBarController.
Whenever I select an item and open up Detail in the iPhone 6 Plus and rotate it from portrait (detail only visible) to landscape (where the master would be visible), it crashes. This does not occur on rotation with the placeholder detail view.
Before the crash, it does call the delegate methods primaryViewControllerForExpandingSplitViewController and splitViewController(splitViewController: UISplitViewController, separateSecondaryViewControllerFromPrimaryViewController. However, everything works fine on the iPad.
I've done a ton of searching already and only seen a couple twitter mentions of this type of crash. Things like setting or not setting the displayModeButtonItem don't help.
I recreated this crash in a fresh project - it can be downloaded here:
https://github.com/sschale/SplitViewCrash/
Crash log:
Crashed Thread: 0 Dispatch queue: com.apple.main-thread
Exception Type: EXC_BAD_ACCESS (SIGSEGV)
Exception Codes: KERN_PROTECTION_FAILURE at 0x00007fff53609ff8
Exception Note: EXC_CORPSE_NOTIFY
VM Regions Near 0x7fff53609ff8:
MALLOC_TINY 00007f8405000000-00007f8405300000 [ 3072K] rw-/rwx SM=PRV
--> STACK GUARD 00007fff4fe0a000-00007fff5360a000 [ 56.0M] ---/rwx SM=NUL stack guard for thread 0
Stack 00007fff5360a000-00007fff53dff000 [ 8148K] rw-/rwx SM=COW thread 0
Thread 0 Crashed:: Dispatch queue: com.apple.main-thread 0 liboainject.dylib 0x000000010e5e59b2
0 liboainject.dylib 0x000000010e5e59b2
_writeEventToSharedMemory + 27
1 liboainject.dylib 0x000000010e5e55d7 _OARecordFinalEvent + 1161
2 liboainject.dylib 0x000000010e5e79f1 ___swapMethods_block_invoke_6 + 338
3 libobjc.A.dylib 0x000000010f4f9b6b weak_read_no_lock + 89
4 libobjc.A.dylib 0x000000010f4fa4c6 objc_loadWeakRetained + 104
5 com.apple.UIKit 0x00000001110510b6 -[UIViewController presentedViewController] + 58
6 com.apple.UIKit 0x0000000111033fc6 -[UIViewController _canBecomeDeepestUnambiguousResponder] + 31
7 com.apple.UIKit 0x0000000111033fde -[UIViewController _canBecomeDeepestUnambiguousResponder] + 55
8 com.apple.UIKit 0x0000000111033fde -[UIViewController _canBecomeDeepestUnambiguousResponder] + 55
9 com.apple.UIKit 0x0000000111033fde -[UIViewController _canBecomeDeepestUnambiguousResponder] + 55
10 com.apple.UIKit 0x0000000111033fde -[UIViewController _canBecomeDeepestUnambiguousResponder] + 55
//(500 more of those)
....
Thread 1:: Dispatch queue: com.apple.libdispatch-manager
0 libsystem_kernel.dylib 0x0000000116e49ee2 kevent64 + 10
1 libdispatch.dylib 0x0000000116ac57f0 _dispatch_mgr_invoke + 260
2 libdispatch.dylib 0x0000000116ac558a _dispatch_mgr_thread + 54
Thread 2:
0 libsystem_kernel.dylib 0x0000000116e495e2 __workq_kernreturn + 10
1 libsystem_pthread.dylib 0x0000000116e0d578 _pthread_wqthread + 1283
2 libsystem_pthread.dylib 0x0000000116e0b341 start_wqthread + 13
Thread 3:
0 libsystem_kernel.dylib 0x0000000116e495e2 __workq_kernreturn + 10
1 libsystem_pthread.dylib 0x0000000116e0d578 _pthread_wqthread + 1283
2 libsystem_pthread.dylib 0x0000000116e0b341 start_wqthread + 13
Thread 4:
0 libsystem_kernel.dylib 0x0000000116e495e2 __workq_kernreturn + 10
1 libsystem_pthread.dylib 0x0000000116e0d578 _pthread_wqthread + 1283
2 libsystem_pthread.dylib 0x0000000116e0b341 start_wqthread + 13
Thread 5:
0 libsystem_kernel.dylib 0x0000000116e495e2 __workq_kernreturn + 10
1 libsystem_pthread.dylib 0x0000000116e0d578 _pthread_wqthread + 1283
2 libsystem_pthread.dylib 0x0000000116e0b341 start_wqthread + 13
This crashes on iPad too. Use Multitasking to resize the app to Compact width (e.g. 1/3rd screen), press the Launch Detail button, then resize it to Regular width.
When you're in Compact width, the split view controller is "collapsed". That means that it no longer shows separate primary and secondary view controllers at the same time -- instead, it "collapses" them into a single view controller hierarchy. When it's in that environment, it often needs your help in order to act sensibly. The default behavior works well when both your primary and secondary view controllers are UINavigationControllers, but not in other cases.
(In your app, your primary is a UITabBarController, and after you "Launch Detail" once, the secondary is also a UITabBarController. You may want to reconsider that design, because it makes things more difficult. Keep reading.)
Your app's "Launch Detail" button performs a "Show Detail" segue, which effectively calls this method on UISplitViewController:
public func showDetailViewController(vc: UIViewController, sender: AnyObject?)
Note the comment in the header:
// In a horizontally-compact environment the master view controller
// or detail view controller is sent the showViewController:sender:
// message. If neither one of them provide an implementation for this
// method then it will fall back to a full screen presentation.
By "master view controller or detail view controller", it means the view controller that is currently shown, which in your case is a UITabBarController. But UITabBarController does not implement anything for showViewController() because it doesn't have enough information -- where would it show a view controller? Would it add a new tab, or replace an old one, or what?
So, as a result, you get that fallback, full-screen presentation. I doubt you actually want that user experience.
Later on, when the size changes back to Regular and the split view controller expands, it gets confused by the presentation and eventually crashes. (See what I mean about the defaults not being very good?)
One way to fix this is to implement the delegate method to handle the showDetail. When the width is Compact, explicitly find the view controller you want to put the new view controller onto, and do it. I think you probably want to push onto the nav controller in the first tab:
func splitViewController(splitViewController: UISplitViewController, showDetailViewController vc: UIViewController, sender: AnyObject?) -> Bool
{
if splitViewController.traitCollection.horizontalSizeClass == .Compact {
// The default implementation will not handle this properly.
// Find the appropriate navigation controller and push onto it.
// It would be better to have a direct outlet to the appropriate navigation controller,
// but this will work for an example...
if let tabBarController = splitViewController.viewControllers.first as? UITabBarController {
if let navController = tabBarController.viewControllers?.first as? UINavigationController {
navController.pushViewController(vc, animated: true)
// we handled the "show detail", so split view controller,
// please don't do anything else
return true
}
}
}
// we did not handle the "show detail", so split view controller,
// please do your default behavior
return false
}
If you do that, you will also want to implement this delegate method. When the size is changed back to Regular, you will want to handle the "expand" by popping that view controller off of the same nav controller, then returning it:
optional public func splitViewController(
splitViewController: UISplitViewController
separateSecondaryViewControllerFromPrimaryViewController
primaryViewController: UIViewController) -> UIViewController?
I've run into an interesting issue with the Swift compiler, which seems to be caused by a very simple generic function. I've got a workaround, but I'd really like to understand what the underlying issue is.
In my app I have a requirement to fade in some UICollectionViewCells in a given order, sequentially, with a slight overlap between the animations.
I've implemented this using the methods below. The revealCells method takes an array of cells. If the collection is empty, it simply returns. Otherwise it animates the fade-in on the first cell in the array, and then waits a certain amount of time before making a recursive call to itself, passing all the cells except the one it just animated.
Code below:
func revealCells(cells:[UICollectionViewCell],animationTime:NSTimeInterval,overlap:Double) {
if cells.count > 0 {
let firstCell = cells.first
UIView.animateWithDuration(0.3, animations: { () -> Void in
firstCell?.alpha = 1.0
let timeMinusOverlap = animationTime - overlap
let delayTime = dispatch_time(DISPATCH_TIME_NOW,Int64(timeMinusOverlap * Double(NSEC_PER_SEC)))
dispatch_after(delayTime, dispatch_get_main_queue(), { () -> Void in
self.revealCells(self.cdr(cells),animationTime: animationTime,overlap: overlap)
})
})
}
}
}
//Returns the collection passed into the function with its first element removed
//i.e the traditional LISP cdr function
func cdr<T>(input:[T]) -> [T] {
var rVal = [T]()
if input.count == 1 {
rVal.append(input.first!)
} else {
rVal = [T](input[1...input.count-1])
}
return rVal
}
All this works fine in the simulator. But when I try to archive the build, the swift compiler crashes with the message Swift Compiler Error Command failed due to signal: Segmentation fault 11. My setup is Xcode 6.3.1 (iOSSDK 8.3), and my min deployment target is 8.3.
Fixing the problem is trivial - if I just replace the code inside the dispatch_after with:
let newCells = [UICollectionViewCell](cells[1...cells.count-1])
self.revealCells(newCells,animationTime: animationTime,overlap: overlap)
the problem goes away. So it seems to be something to do with the generic function, or possibly something block related.
Stack trace is:
0 swift 0x000000010105ba18 llvm::sys::PrintStackTrace(__sFILE*) + 40
1 swift 0x000000010105bef4 SignalHandler(int) + 452
2 libsystem_platform.dylib 0x00007fff8725ef1a _sigtramp + 26
3 libsystem_platform.dylib 0x000000000000000f _sigtramp + 2027557135
4 swift 0x00000001021a98cb llvm::AsmPrinter::EmitFunctionBody() + 4379
5 swift 0x000000010116c84c llvm::ARMAsmPrinter::runOnMachineFunction(llvm::MachineFunction&) + 220
6 swift 0x0000000100d81d13 llvm::MachineFunctionPass::runOnFunction(llvm::Function&) + 99
7 swift 0x00000001024d5edf llvm::FPPassManager::runOnFunction(llvm::Function&) + 495
8 swift 0x00000001024d60cb llvm::FPPassManager::runOnModule(llvm::Module&) + 43
9 swift 0x00000001024d658f llvm::legacy::PassManagerImpl::run(llvm::Module&) + 975
10 swift 0x0000000100a09b41 performIRGeneration(swift::IRGenOptions&, swift::Module*, swift::SILModule*, llvm::StringRef, llvm::LLVMContext&, swift::SourceFile*, unsigned int) + 4369
11 swift 0x0000000100a09cb3 swift::performIRGeneration(swift::IRGenOptions&, swift::SourceFile&, swift::SILModule*, llvm::StringRef, llvm::LLVMContext&, unsigned int) + 51
12 swift 0x0000000100945687 frontend_main(llvm::ArrayRef<char const*>, char const*, void*) + 6647
13 swift 0x0000000100943ae6 main + 1814
14 libdyld.dylib 0x00007fff8a46b5c9 start + 1
(a long list of program arguments, which are mostly the names of file being compiled)
1. Running pass 'Function Pass Manager' on module '/Users/tolleyr/Library/Developer/Xcode/DerivedData/ParseTestQuiz-fgnfjkxxlyqfnrfrfntgtsjnrcfv/Build/Intermediates/ArchiveIntermediates/ParseTestQuiz/IntermediateBuildFilesPath/ParseTestQuiz.build/Release-iphoneos/ParseTestQuiz.build/Objects-normal/armv7/QuizQuestionViewController.o'.
2. Running pass 'ARM Assembly / Object Emitter' on function '#_TFC13ParseTestQuiz26QuizQuestionViewController13viewDidAppearfS0_FSbT_'
(the last part enabled me to figure out what code was causing the problem). The command being run was CompileSwift normal armv7
I'm going to file a radar for this, since the compiler itself is crashing , but thought I'd post it here in case anyone has an idea of what might be going on, or has run into the same issue.
I'm implementing an iOS app. and found that sometimes the screen became black little by little.
for example, in a view controller, there are a collection view, a button, a page controller, and sometimes I found the collection view became black(or invisible), only black background is shown, after 1-2 seconds, the button is gone, then the whole screen is black. but if I put the app into background, then bring it back, everything is ok.
here is the code:
- (void)application:(UIApplication *)application performFetchWithCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler
{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
NSTimeInterval timeLeft = [UIApplication sharedApplication].backgroundTimeRemaining - 1.0;
if(timeLeft < 0)
{
completionHandler(UIBackgroundFetchResultNoData);
}
//create new thread to do something, and sleep at current thread
...create new threads
[NSThread sleep:timeLeft];
completionHandler(UIBackgroundFetchResultNewData);
});
}
the issue could be reproduced if I repeat the following actions several times:
Do some thing
Put app to background(press home button)
Bring app back to foreground
repeat 1 - 3
we found following error in organizer for our app:
: CoreAnimation: warning, deleted thread with uncommitted CATransaction; set CA_DEBUG_TRANSACTIONS=1 in environment to log backtraces.
after added , I got the following log:
Myapp[11496] : CoreAnimation: warning, deleted thread with uncommitted CATransaction; created by:
0 QuartzCore 0x31ca6a75 + 268
1 QuartzCore 0x31ca6929 + 224
2 QuartzCore 0x31cabddb + 86
3 QuartzCore 0x31cab9f7 + 150
4 UIKit 0x3203f919 + 344
5 UIKit 0x320bb11b + 138
6 UIKit 0x322b0ebf + 218
7 UIKit 0x322b1169 + 104
8 UIKit 0x322b1735 + 36
9 Myapp 0x002e538d __61-[AppDelegate application:performFetchWithCompletionHandler:]_block_invoke + 632
10 libdispatch.dylib 0x3a487d53 + 10
11 libdispatch.dylib 0x3a48d689 + 228
12 libdispatch.dylib 0x3a48d8dd + 56
13 libsystem_pthread.dylib 0x3a5b8c17 _pthread_wqthread + 298
14 libsystem_pthread.dylib 0x3a5b8adc start_wqthread + 8
my questions is:
how could application:performFetchWithCompletionHandler cause animation issue?
to answer questions:
1. I'm sure that phone is not going to sleep
2. source code. sorry the project is too big
Check that you are working with UI on main thread only, this should be the case.
Edit: - I've seen this one, although never used it(written by Peter Steinberger).
Also this answer can help avoiding several more problems.
The thread created to perform your fetch invokes a call to update your ui, when it finishes performing whatever task it is doing, and its no longer needed it is deallocated. UIUpdates MUST be performed on the main thread or they might not be performed right away.
Have you tried dispatching to the main thread whatever ui update you are performing there?
It's hard to know what you your completion handler is doing, but if it's updating UI try to dispatch them on the main thread
- (void)application:(UIApplication *)application performFetchWithCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler
{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
NSTimeInterval timeLeft = [UIApplication sharedApplication].backgroundTimeRemaining - 1.0;
if(timeLeft < 0)
{
dispatch_async(dispatch_get_main_queue(), ^{
completionHandler(UIBackgroundFetchResultNoData);
});
}
//create new thread to do something, and sleep at current thread
...create new threads
[NSThread sleep:timeLeft];
dispatch_async(dispatch_get_main_queue(), ^{
completionHandler(UIBackgroundFetchResultNewData);
});
});
}