Proper use of beginBackgroundTaskWithExpirationHandler - ios

I'm a bit confused about how and when to use beginBackgroundTaskWithExpirationHandler.
Apple shows in their examples to use it in applicationDidEnterBackground delegate, to get more time to complete some important task, usually a network transaction.
When looking on my app, it seems like most of my network stuff is important, and when one is started I would like to complete it if the user pressed the home button.
So is it accepted/good practice to wrap every network transaction (and I'm not talking about downloading big chunk of data, it mostly some short xml) with beginBackgroundTaskWithExpirationHandler to be on the safe side?

If you want your network transaction to continue in the background, then you'll need to wrap it in a background task. It's also very important that you call endBackgroundTask when you're finished - otherwise the app will be killed after its allotted time has expired.
Mine tend look something like this:
- (void) doUpdate
{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[self beginBackgroundUpdateTask];
NSURLResponse * response = nil;
NSError * error = nil;
NSData * responseData = [NSURLConnection sendSynchronousRequest: request returningResponse: &response error: &error];
// Do something with the result
[self endBackgroundUpdateTask];
});
}
- (void) beginBackgroundUpdateTask
{
self.backgroundUpdateTask = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{
[self endBackgroundUpdateTask];
}];
}
- (void) endBackgroundUpdateTask
{
[[UIApplication sharedApplication] endBackgroundTask: self.backgroundUpdateTask];
self.backgroundUpdateTask = UIBackgroundTaskInvalid;
}
I have a UIBackgroundTaskIdentifier property for each background task
Equivalent code in Swift
func doUpdate () {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), {
let taskID = beginBackgroundUpdateTask()
var response: URLResponse?, error: NSError?, request: NSURLRequest?
let data = NSURLConnection.sendSynchronousRequest(request, returningResponse: &response, error: &error)
// Do something with the result
endBackgroundUpdateTask(taskID)
})
}
func beginBackgroundUpdateTask() -> UIBackgroundTaskIdentifier {
return UIApplication.shared.beginBackgroundTask(expirationHandler: ({}))
}
func endBackgroundUpdateTask(taskID: UIBackgroundTaskIdentifier) {
UIApplication.shared.endBackgroundTask(taskID)
}

The accepted answer is very helpful and should be fine in most cases, however two things bothered me about it:
As a number of people have noted, storing the task identifier as a property means that it can be overwritten if the method is called multiple times, leading to a task that will never be gracefully ended until forced to end by the OS at the time expiration.
This pattern requires a unique property for every call to beginBackgroundTaskWithExpirationHandler which seems cumbersome if you have a larger app with lots of network methods.
To solve these issues, I wrote a singleton that takes care of all the plumbing and tracks active tasks in a dictionary. No properties needed to keep track of task identifiers. Seems to work well. Usage is simplified to:
//start the task
NSUInteger taskKey = [[BackgroundTaskManager sharedTasks] beginTask];
//do stuff
//end the task
[[BackgroundTaskManager sharedTasks] endTaskWithKey:taskKey];
Optionally, if you want to provide a completion block that does something beyond ending the task (which is built in) you can call:
NSUInteger taskKey = [[BackgroundTaskManager sharedTasks] beginTaskWithCompletionHandler:^{
//do stuff
}];
Relevant source code available below (singleton stuff excluded for brevity). Comments/feedback welcome.
- (id)init
{
self = [super init];
if (self) {
[self setTaskKeyCounter:0];
[self setDictTaskIdentifiers:[NSMutableDictionary dictionary]];
[self setDictTaskCompletionBlocks:[NSMutableDictionary dictionary]];
}
return self;
}
- (NSUInteger)beginTask
{
return [self beginTaskWithCompletionHandler:nil];
}
- (NSUInteger)beginTaskWithCompletionHandler:(CompletionBlock)_completion;
{
//read the counter and increment it
NSUInteger taskKey;
#synchronized(self) {
taskKey = self.taskKeyCounter;
self.taskKeyCounter++;
}
//tell the OS to start a task that should continue in the background if needed
NSUInteger taskId = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{
[self endTaskWithKey:taskKey];
}];
//add this task identifier to the active task dictionary
[self.dictTaskIdentifiers setObject:[NSNumber numberWithUnsignedLong:taskId] forKey:[NSNumber numberWithUnsignedLong:taskKey]];
//store the completion block (if any)
if (_completion) [self.dictTaskCompletionBlocks setObject:_completion forKey:[NSNumber numberWithUnsignedLong:taskKey]];
//return the dictionary key
return taskKey;
}
- (void)endTaskWithKey:(NSUInteger)_key
{
#synchronized(self.dictTaskCompletionBlocks) {
//see if this task has a completion block
CompletionBlock completion = [self.dictTaskCompletionBlocks objectForKey:[NSNumber numberWithUnsignedLong:_key]];
if (completion) {
//run the completion block and remove it from the completion block dictionary
completion();
[self.dictTaskCompletionBlocks removeObjectForKey:[NSNumber numberWithUnsignedLong:_key]];
}
}
#synchronized(self.dictTaskIdentifiers) {
//see if this task has been ended yet
NSNumber *taskId = [self.dictTaskIdentifiers objectForKey:[NSNumber numberWithUnsignedLong:_key]];
if (taskId) {
//end the task and remove it from the active task dictionary
[[UIApplication sharedApplication] endBackgroundTask:[taskId unsignedLongValue]];
[self.dictTaskIdentifiers removeObjectForKey:[NSNumber numberWithUnsignedLong:_key]];
}
}
}

Here is a Swift class that encapsulates running a background task:
class BackgroundTask {
private let application: UIApplication
private var identifier = UIBackgroundTaskInvalid
init(application: UIApplication) {
self.application = application
}
class func run(application: UIApplication, handler: (BackgroundTask) -> ()) {
// NOTE: The handler must call end() when it is done
let backgroundTask = BackgroundTask(application: application)
backgroundTask.begin()
handler(backgroundTask)
}
func begin() {
self.identifier = application.beginBackgroundTaskWithExpirationHandler {
self.end()
}
}
func end() {
if (identifier != UIBackgroundTaskInvalid) {
application.endBackgroundTask(identifier)
}
identifier = UIBackgroundTaskInvalid
}
}
The simplest way to use it:
BackgroundTask.run(application) { backgroundTask in
// Do something
backgroundTask.end()
}
If you need to wait for a delegate callback before you end, then use something like this:
class MyClass {
backgroundTask: BackgroundTask?
func doSomething() {
backgroundTask = BackgroundTask(application)
backgroundTask!.begin()
// Do something that waits for callback
}
func callback() {
backgroundTask?.end()
backgroundTask = nil
}
}

As noted here and in answers to other SO questions, you do NOT want to use beginBackgroundTask only just when your app will go into the background; on the contrary, you should use a background task for any time-consuming operation whose completion you want to ensure even if the app does go into the background.
Therefore your code is likely to end up peppered with repetitions of the same boilerplate code for calling beginBackgroundTask and endBackgroundTask coherently. To prevent this repetition, it is certainly reasonable to want to package up the boilerplate into some single encapsulated entity.
I like some of the existing answers for doing that, but I think the best way is to use an Operation subclass:
You can enqueue the Operation onto any OperationQueue and manipulate that queue as you see fit. For example, you are free to cancel prematurely any existing operations on the queue.
If you have more than one thing to do, you can chain multiple background task Operations. Operations support dependencies.
The Operation Queue can (and should) be a background queue; thus, there is no need to worry about performing asynchronous code inside your task, because the Operation is the asynchronous code. (Indeed, it makes no sense to execute another level of asynchronous code inside an Operation, as the Operation would finish before that code could even start. If you needed to do that, you'd use another Operation.)
Here's a possible Operation subclass:
class BackgroundTaskOperation: Operation {
var whatToDo : (() -> ())?
var cleanup : (() -> ())?
override func main() {
guard !self.isCancelled else { return }
guard let whatToDo = self.whatToDo else { return }
var bti : UIBackgroundTaskIdentifier = .invalid
bti = UIApplication.shared.beginBackgroundTask {
self.cleanup?()
self.cancel()
UIApplication.shared.endBackgroundTask(bti) // cancellation
}
guard bti != .invalid else { return }
whatToDo()
guard !self.isCancelled else { return }
UIApplication.shared.endBackgroundTask(bti) // completion
}
}
It should be obvious how to use this, but in case it isn't, imagine we have a global OperationQueue:
let backgroundTaskQueue : OperationQueue = {
let q = OperationQueue()
q.maxConcurrentOperationCount = 1
return q
}()
So for a typical time-consuming batch of code we would say:
let task = BackgroundTaskOperation()
task.whatToDo = {
// do something here
}
backgroundTaskQueue.addOperation(task)
If your time-consuming batch of code can be divided into stages, you might want to bow out early if your task is cancelled. In that case, just return prematurely from the closure. Note that your reference to the task from within the closure needs to be weak or you'll get a retain cycle. Here's an artificial illustration:
let task = BackgroundTaskOperation()
task.whatToDo = { [weak task] in
guard let task = task else {return}
for i in 1...10000 {
guard !task.isCancelled else {return}
for j in 1...150000 {
let k = i*j
}
}
}
backgroundTaskQueue.addOperation(task)
In case you have cleanup to do in case the background task itself is cancelled prematurely, I've provided an optional cleanup handler property (not used in the preceding examples). Some other answers were criticised for not including that.

I implemented Joel's solution. Here is the complete code:
.h file:
#import <Foundation/Foundation.h>
#interface VMKBackgroundTaskManager : NSObject
+ (id) sharedTasks;
- (NSUInteger)beginTask;
- (NSUInteger)beginTaskWithCompletionHandler:(CompletionBlock)_completion;
- (void)endTaskWithKey:(NSUInteger)_key;
#end
.m file:
#import "VMKBackgroundTaskManager.h"
#interface VMKBackgroundTaskManager()
#property NSUInteger taskKeyCounter;
#property NSMutableDictionary *dictTaskIdentifiers;
#property NSMutableDictionary *dictTaskCompletionBlocks;
#end
#implementation VMKBackgroundTaskManager
+ (id)sharedTasks {
static VMKBackgroundTaskManager *sharedTasks = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedTasks = [[self alloc] init];
});
return sharedTasks;
}
- (id)init
{
self = [super init];
if (self) {
[self setTaskKeyCounter:0];
[self setDictTaskIdentifiers:[NSMutableDictionary dictionary]];
[self setDictTaskCompletionBlocks:[NSMutableDictionary dictionary]];
}
return self;
}
- (NSUInteger)beginTask
{
return [self beginTaskWithCompletionHandler:nil];
}
- (NSUInteger)beginTaskWithCompletionHandler:(CompletionBlock)_completion;
{
//read the counter and increment it
NSUInteger taskKey;
#synchronized(self) {
taskKey = self.taskKeyCounter;
self.taskKeyCounter++;
}
//tell the OS to start a task that should continue in the background if needed
NSUInteger taskId = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{
[self endTaskWithKey:taskKey];
}];
//add this task identifier to the active task dictionary
[self.dictTaskIdentifiers setObject:[NSNumber numberWithUnsignedLong:taskId] forKey:[NSNumber numberWithUnsignedLong:taskKey]];
//store the completion block (if any)
if (_completion) [self.dictTaskCompletionBlocks setObject:_completion forKey:[NSNumber numberWithUnsignedLong:taskKey]];
//return the dictionary key
return taskKey;
}
- (void)endTaskWithKey:(NSUInteger)_key
{
#synchronized(self.dictTaskCompletionBlocks) {
//see if this task has a completion block
CompletionBlock completion = [self.dictTaskCompletionBlocks objectForKey:[NSNumber numberWithUnsignedLong:_key]];
if (completion) {
//run the completion block and remove it from the completion block dictionary
completion();
[self.dictTaskCompletionBlocks removeObjectForKey:[NSNumber numberWithUnsignedLong:_key]];
}
}
#synchronized(self.dictTaskIdentifiers) {
//see if this task has been ended yet
NSNumber *taskId = [self.dictTaskIdentifiers objectForKey:[NSNumber numberWithUnsignedLong:_key]];
if (taskId) {
//end the task and remove it from the active task dictionary
[[UIApplication sharedApplication] endBackgroundTask:[taskId unsignedLongValue]];
[self.dictTaskIdentifiers removeObjectForKey:[NSNumber numberWithUnsignedLong:_key]];
NSLog(#"Task ended");
}
}
}
#end

Related

Singleton property returns different values depending on invocation

Background
In my app, I have class called FavoritesController that manages objects that the user's marked as favorites, and this favorite status is then used throughout the app. The FavoritesController is designed as a singleton class as there are a number of UI elements throughout the app that needs to know the 'favorite status' for objects in different places, also network requests need to be able to signal that a favorite needs to be invalidated if the server says so.
This invalidation part happens when the server responds with a 404 error, indicating that the favorite object must be removed from the user's favorites. The network fetch function throws an error, which triggers the FavoritesController to remove the object and then send a notification to interested parties that they need to refresh.
The problem
When using a unit test to check the quality of the 404 implementation, all methods are triggered as intended – the error is thrown and caught, the FavoritesController deletes the object and sends the notification. In some instances though, the deleted favorite is still there – but it depends on from where the query is done!
If I query inside the singleton the deletion went OK, but if I query from a class that makes use of the singleton, the deletion didn't happen.
Design details
The FavoritesController property favorites uses an ivar with all accesses #synchronized(), and the value of the ivar is backed by a NSUserDefaults property.
A favorite object is an NSDictionary with two keys: id and name.
Other info
One weird thing which I fail to understand why it happens: in some deletion attempts, the name value for the favorite object gets set to "" but the id key retains its value.
I've written unit tests that add an invalid favorite and checks that it gets removed on first server query. This test passes when starting with empty set of favorites, but fails when there is an instance of 'semi-deleted' object as above (that retains its id value)
The unit test now consistently passes, but in live usage the failure to delete remains. I suspect that this is due to NSUserDefaults not saving to disk immediately.
Steps I've tried
Making sure that the singleton implementation is a 'true' singleton, i.e. sharedController always returns the same instance.
I thought there was some sort of 'capture' problem, where a closure would keep its own copy with outdated favorites, but I think not. When NSLogging the object ID it returns the same.
Code
FavoritesController main methods
- (void) serverCanNotFindFavorite:(NSInteger)siteID {
NSLog(#"Server can't find favorite");
NSDictionary * removedFavorite = [NSDictionary dictionaryWithDictionary:[self favoriteWithID:siteID]];
NSUInteger index = [self indexOfFavoriteWithID:siteID];
[self debugLogFavorites];
dispatch_async(dispatch_get_main_queue(), ^{
[self removeFromFavorites:siteID completion:^(BOOL success) {
if (success) {
NSNotification * note = [NSNotification notificationWithName:didRemoveFavoriteNotification object:nil userInfo:#{#"site" : removedFavorite, #"index" : [NSNumber numberWithUnsignedInteger:index]}];
NSLog(#"Will post notification");
[self debugLogFavorites];
[self debugLogUserDefaultsFavorites];
[[NSNotificationCenter defaultCenter] postNotification:note];
NSLog(#"Posted notification with name: %#", didRemoveFavoriteNotification);
}
}];
});
}
- (void) removeFromFavorites:(NSInteger)siteID completion:(completionBlock) completion {
if ([self isFavorite:siteID]) {
NSMutableArray * newFavorites = [NSMutableArray arrayWithArray:self.favorites];
NSIndexSet * indices = [newFavorites indexesOfObjectsPassingTest:^BOOL(NSDictionary * entryUnderTest, NSUInteger idx, BOOL * _Nonnull stop) {
NSNumber * value = (NSNumber *)[entryUnderTest objectForKey:#"id"];
if ([value isEqualToNumber:[NSNumber numberWithInteger:siteID]]) {
return YES;
}
return NO;
}];
__block NSDictionary* objectToRemove = [[newFavorites objectAtIndex:indices.firstIndex] copy];
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(#"Will remove %#", objectToRemove);
[newFavorites removeObject:objectToRemove];
[self setFavorites:[NSArray arrayWithArray:newFavorites]];
if ([self isFavorite:siteID]) {
NSLog(#"Failed to remove!");
if (completion) {
completion(NO);
}
} else {
NSLog(#"Removed OK");
if (completion) {
completion(YES);
}
}
});
} else {
NSLog(#"Tried removing site %li which is not a favorite", (long)siteID);
if (completion) {
completion(NO);
}
}
}
- (NSArray *) favorites
{
#synchronized(self) {
if (!internalFavorites) {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
self->internalFavorites = [self.defaults objectForKey:k_key_favorites];
});
if (!internalFavorites) {
internalFavorites = [NSArray array];
}
}
return internalFavorites;
}
}
- (void) setFavorites:(NSArray *)someFavorites {
#synchronized(self) {
internalFavorites = someFavorites;
[self.defaults setObject:internalFavorites forKey:k_key_favorites];
}
}
- (void) addToFavorites:(NSInteger)siteID withName:(NSString *)siteName {
if (![self isFavorite:siteID]) {
NSDictionary * newFavorite = #{
#"name" : siteName,
#"id" : [NSNumber numberWithInteger:siteID]
};
dispatch_async(dispatch_get_main_queue(), ^{
NSArray * newFavorites = [self.favorites arrayByAddingObject:newFavorite];
[self setFavorites:newFavorites];
});
NSLog(#"Added site %# with id %ld to favorites", siteName, (long)siteID);
} else {
NSLog(#"Tried adding site as favorite a second time");
}
}
- (BOOL) isFavorite:(NSInteger)siteID
{
#synchronized(self) {
NSNumber * siteNumber = [NSNumber numberWithInteger:siteID];
NSArray * favs = [NSArray arrayWithArray:self.favorites];
if (favs.count == 0) {
NSLog(#"No favorites");
return NO;
}
NSIndexSet * indices = [favs indexesOfObjectsPassingTest:^BOOL(NSDictionary * entryUnderTest, NSUInteger idx, BOOL * _Nonnull stop) {
if ([[entryUnderTest objectForKey:#"id"] isEqualToNumber:siteNumber]) {
return YES;
}
return NO;
}];
if (indices.count > 0) {
return YES;
}
}
return NO;
}
Singleton implementation of FavoritesController
- (instancetype) init {
static PKEFavoritesController *initedObject;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
initedObject = [super init];
self.defaults = [NSUserDefaults standardUserDefaults];
});
return initedObject;
}
+ (instancetype) sharedController
{
return [self new];
}
Unit testing code
func testObsoleteFavoriteRemoval() {
let addToFavorites = self.expectation(description: "addToFavorites")
let networkRequest = self.expectation(description: "network request")
unowned let favs = PKEFavoritesController.shared()
favs.clearFavorites()
XCTAssertFalse(favs.isFavorite(313), "Should not be favorite initially")
if !favs.isFavorite(313) {
NSLog("Adding 313 to favorites")
favs.add(toFavorites: 313, withName: "Skatås")
}
let notification = self.expectation(forNotification: NSNotification.Name("didRemoveFavoriteNotification"), object: nil) { (notification) -> Bool in
NSLog("Received notification: \(notification.name.rawValue)")
return true
}
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
NSLog("Verifying 313 is favorite")
XCTAssertTrue(favs.isFavorite(313))
addToFavorites.fulfill()
}
self.wait(for: [addToFavorites], timeout: 5)
NSLog("Will trigger removal for 313")
let _ = SkidsparAPI.fetchRecentReports(forSite: 313, session: SkidsparAPI.session()) { (reports) in
NSLog("Network request completed")
networkRequest.fulfill()
}
self.wait(for: [networkRequest, notification], timeout: 10)
XCTAssertFalse(favs.isFavorite(313), "Favorite should be removed after a 404 error from server")
}
To give context around my answers, this is what the code in question looked like when suggesting the change:
- (NSArray *)favorites {
#synchronized(internalFavorites) {
if (!internalFavorites) {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
internalFavorites = [self.defaults objectForKey:k_key_favorites];
});
if (!internalFavorites) {
internalFavorites = [NSArray array];
}
}
}
return internalFavorites;
}
I was suspicious of the check if (!internalFavorites) { that followed #synchronized(internalFavorites) because that meant that there was an expectation of #synchronized being passed nil, which results in a noop.
This meant multiple calls to to favorites or setFavorites could happen in funny ways since they wouldn't actually be synchronized. Giving #sychronized an actual object to synchronize on was crucial for thread safety. Synchronizing on self is fine, but for a particular class, you have to be careful not to synchronize too many things on self or you'll be bound to create needless blocking. Providing simple NSObjects to #sychronized is a good way to narrow the scope of what you're protecting.
Here's how you can avoid using self as your lock.
- (instancetype)init {
static PKEFavoritesController *initedObject;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
initedObject = [super init];
self.lock = [NSObject new];
self.defaults = [NSUserDefaults standardUserDefaults];
});
return initedObject;
}
+ (instancetype)sharedController {
return [self new];
}
- (NSArray *)favorites {
#synchronized(_lock) {
if (!internalFavorites) {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
self->internalFavorites = [self.defaults objectForKey:k_key_favorites];
});
if (!internalFavorites) {
internalFavorites = [NSArray array];
}
}
}
return internalFavorites;
}
Regarding the abnormalities between test runs, definitely calling synchronize on the NSUserDefaults will help because calls to change the defaults are asynchronous, which means other threads are involved. There are like 3 layers of caching as well, and specifically for the purposes of running tests synchronize should ensure that things are completely and cleanly committed before Xcode pulls the plug on the test run. The documentation very abruptly insists that it's not a necessary call, but if it truly weren't necessary it wouldn't exist :-). On my first iOS projects, we always called synchronize after every defaults change... so, I think that documentation is more aspirational on the Apple engineers' parts. I'm glad this intuition helped you.

NSOperationQueue Is Finished Before All Operations Are Completed

I have x number different task that need to run. Each task is responsible for going out to the internet, downloading some data, process that data, then saving that data.
They all run at the same time on a background thread. Though I would be fine with them running one after the other.
When all 4 tasks are complete I would like to be notified that they have finished in some fashion.
However, NSOperationQueue is firing off the completed queue before any of the other actual operations have started.
My Log with the current setup:
fetchMyData: completionOperation YES
FILE: MySet1 CREATED
FILE: MySet2 CREATED
FILE: MySet3 CREATED
FILE: MySet4 CREATED
Is there a better way to chain the operations together and fully waiting on all of them to complete before firing the final operation?
Here is what I am doing:
- (void) timeToUpdate {
[self fetchMyData:^(BOOL done) {
NSLog(#"fetchMyData: completionOperation : %#", done ? #"YES":#"NO");
}];
- (void) fetchMyData: (void(^)(BOOL done)) completion {
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
NSOperation *completionOperation = [NSBlockOperation blockOperationWithBlock:^{
NSLog(#"fetchMyData: completionOperation");
completion(true);
}];
NSOperation *operation;
operation = [NSBlockOperation blockOperationWithBlock:^{
[self downloadDataSet:#"MySet1"];
}];
[completionOperation addDependency:operation];
[queue addOperation:operation];
operation = [NSBlockOperation blockOperationWithBlock:^{
[self downloadDataSet:#"MySet2"];
}];
[completionOperation addDependency:operation];
[queue addOperation:operation];
operation = [NSBlockOperation blockOperationWithBlock:^{
[self downloadDataSet:#"MySet3"];
}];
[completionOperation addDependency:operation];
[queue addOperation:operation];
operation = [NSBlockOperation blockOperationWithBlock:^{
[self downloadDataSet:#"MySet4"];
}];
[completionOperation addDependency:operation];
[queue addOperation:operation];
[queue addOperation:completionOperation];
}
//The below is just abstract sudo code that works and does its job of downloading, processing and saving the data.
- (void) downloadDataSet:(SomeSet) {
[NSURLSessionUploadTask uploadTaskWithRequest:task
fromData: Someset
completionHandler: {
process(set);
}]
}
- process:(data) {
doSomethignWithData(data)
//then
write(data)
}
- write(data) {
//save to file system, sql, coredata whatever
if(success){
NSLog(#"FILE: data CREATED");
}
}
The problem is that your operations are calling uploadTaskWithRequest:fromData: completionHandler:, an asynchronous operation which returns before its job has been completed. What you should do is to look into using an asynchronous NSOperation; basically you override the isAsynchronous method to return YES, and then you set the operation's finished property when uploadTaskWithRequest:fromData: completionHandler:'s completion handler is called. This way, any operations that depend on your download operations will wait until they are actually complete before firing.
Here's a handy set of async NSOperation subclasses that I use; it's Swift, not Objective-C, but it illustrates the concept and should provide a decent pseudocode for you to build your own implementation.
open class AsyncOperation: Operation {
override open var isAsynchronous: Bool { return true }
override open var isExecuting: Bool { return self.state == .started }
override open var isFinished: Bool { return self.state == .done }
private enum State {
case initial
case started
case done
}
private var state: State = .initial {
willSet {
// due to a legacy issue, these have to be strings. Don't make them key paths.
self.willChangeValue(forKey: "isExecuting")
self.willChangeValue(forKey: "isFinished")
}
didSet {
self.didChangeValue(forKey: "isFinished")
self.didChangeValue(forKey: "isExecuting")
}
}
public init(name: String? = nil) {
super.init()
if #available(macOS 10.10, *) {
self.name = name
}
}
final override public func start() {
self.state = .started
self.main {
if case .done = self.state {
fatalError("AsyncOperation completion block called twice")
}
self.state = .done
}
}
final override public func main() {}
open func main(completionHandler: #escaping () -> ()) {
fatalError("Subclass must override main(completionHandler:)")
}
}
open class AsyncBlockOperation: AsyncOperation {
private let closure: (_ completionHandler: #escaping () -> ()) -> ()
public init(name: String? = nil, closure: #escaping (_ completionHandler: #escaping () -> ()) -> ()) {
self.closure = closure
super.init(name: name)
}
override open func main(completionHandler: #escaping () -> ()) {
self.closure(completionHandler)
}
}
Alternatively, you could eschew the use of NSOperation and use a dispatch_group_t to get similar behavior; call dispatch_group_enter() initially, call dispatch_group_leave() in the download task's completion handler, and use dispatch_notify() to run your completion block.

GCD serial queue and race condition

I have two methods which run on a serial queue. Each method return a copy of some class. I'm trying to achieve thread safety solution while also mainting data integrity.
for example:
-(Users *) getAllUsers
{
__block copiedUsers;
dispatch_sync(_backgroundQueue, ^{
copiedUsers = [self.users copy]; // return copy object to calling thread.
});
return copiedUsers;
}
-(Orders *) getAllOrders
{
__block copiedOrders;
dispatch_sync(_backgroundQueue, ^{
copiedOrders = [self.Orders copy]; // return copy object to calling thread.
});
return copiedOrders;
}
In addition to this two methods, I have a worker class that add/remove users and orders, all done via a serial queue backgroundQueue.
If in the main thread I call getAllUsers and then getAllOrders right after the other my data integrity isn't safe because between the two calls the worker class might have changed the model.
my question is how can I make to the caller a nice interface that allows multiple methods to run atomically?
Model is only updated from backgroundQueue serial queue.
Client talks to model via a method that receives a block that runs in the background queue.
In addition, not to freeze main thread, I create another queue and run a block that talks with the gateway method.
P.S - attention that dispatch_sync is called only in runBlockAndGetNeededDataSafely to avoid deadlocks.
Code sample:
aViewController.m
ManagerClass *m = [ManagerClass new];
dispatch_queue_t q = dispatch_queue_create("funnelQueue", DISPATCH_QUEUE_SERIAL);
dispatch_block_t block_q = ^{
__Users *users;
__Orders *orders;
[manager runBlockAndGetNeededDataSafely:^
{
users = [manager getUsers];
orders = [manager getOrders];
dispatch_async(dispatch_get_main_queue(),
^{
// got data safely - no thread issues, copied objects. update UI!
[self refreshViewWithUsers:users
orders:orders];
});
}];
}
dispatch_async(q, block_q);
Manager.m implementation:
-(void) runBlockInBackground:(dispatch_block_t) block
{
dispatch_sync(self.backgroundQueue, block);
}
-(Users *) getAllUsers
{
return [self.users copy];
}
-(Orders *) getAllOrders
{
return [self.Orders copy];
}
To answer your question about how to checking the current queue:
First when you create the queue, give it a tag:
static void* queueTag = &queueTag;
dispatch_queue_t queue = dispatch_queue_create("a queue", 0);
dispatch_queue_set_specific(queue, queueTag, queueTag, NULL);
and then run a block like this:
-(void)runBlock:(void(^)()) block
{
if (dispatch_get_specific(queueTag) != NULL) {
block();
}else {
dispatch_async(self.queue, block);
}
}
Your example doesn't work. I suggest to use completion callback. You should have an option to know when the worker finish his job to return to value.
- (void)waitForCompletion:(BOOL*)conditions length:(int)len timeOut:(NSInteger)timeoutSecs {
NSDate *timeoutDate = [NSDate dateWithTimeIntervalSinceNow:timeoutSecs];
BOOL done = YES;
for (int i = 0; i < len; i++) {
done = done & *(conditions+i);
}
do {
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:timeoutDate];
if([timeoutDate timeIntervalSinceNow] < 0.0)
break;
//update done
done = YES;
for (int i = 0; i < len; i++) {
done = done & *(conditions+i);
}
} while (!done);
}
-(void) getAllUsers:(void(^)(User* user, NSError* error))completion
{
dispatch_async(_backgroundQueue, ^{
BOOL condition[2] = [self.userCondition, self.orderCondition];
[self waitForCompletion: &condition[0] length:2 timeOut:60];
if (completion) {
completion([self.users copy], nil);
}
});
}

multiple return values in callback blocks

people I am working with don't wanna use delegate methods for a couple of reasons and they want me to come up with a solution;
basically they want a callback block with multiple return values, and when the first return value arrived block should still wait for the upcoming return values
something like
[parserobject parsefile:file withCallback {
if (started) {
//file parsing started
//this should get executed first
}
//if file parsing succesfully started also check if it
//succesfully finished or failed in the same callback
if (finished) {
//file parsing finished
}
if(failed)
{
//this represents if parsing failed any time from start to finish
//if so exit the callback
}
}];
I have seen people using structs or nsintegers to return different values but usually only one result is returned....
is that above code block possible to written in objective-c?
This is your function
typedef void(^VOIDCALLBACK)(void); //Just a type here
typedef void(^BOLLCALLBACK)(bool succeed);//Just a type here
-(void)parserobjectWithFile:(NSString *)file StartCallBack:(VOIDCALLBACK) startCallBack completionBlock:(BOLLCALLBACK) completion{
// When start
dispatch_async(dispatch_get_main_queue(), ^{
startCallBack();//Make call back run in main queue
});
//When finisehd set succeed to yes,otherwise set to no
BOOL succeed = YES;//Pass in to indicator if succeed
dispatch_async(dispatch_get_main_queue(), ^{
completion(succeed); //Make call back run in main queue
});
}
Then you call it like this
[self parserobjectWithFile:#""
StartCallBack:^{
//Here task is start
}
completionBlock:^(bool succeed) {
//Here task is done.Check succeed to figure out if succeed
}];
A simple example
This is the function part
#implementation ViewController
-(void)viewDidAppear:(BOOL)animated{
[self parserobjectWithFile:#"" StartCallBack:^{
NSLog(#"start");
} completionBlock:^(bool succeed) {
NSLog(#"finished");
}];
}
typedef void(^VOIDCALLBACK)(void); //Just a type here
typedef void(^BOLLCALLBACK)(bool succeed);//Just a type here
-(void)parserobjectWithFile:(NSString *)file StartCallBack: (VOIDCALLBACK) startCallBack completionBlock:(BOLLCALLBACK) completion{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
//Start
dispatch_async(dispatch_get_main_queue(), ^{
startCallBack();//Make call back run in main queue
});
sleep(2.0);
BOOL succeed = YES;//Pass in to indicator if succeed
dispatch_async(dispatch_get_main_queue(), ^{
completion(succeed); //Make call back run in main queue
});
});
}
#end
This will output
2015-06-11 23:39:25.426 OCTest[767:144615] start
2015-06-11 23:39:27.426 OCTest[767:144615] finished
It is possible to write a block that does different things depending on the state reached so far. In order for that to work, declare variables that represent block's state with the __block modifier:
__block BOOL started = NO;
__block BOOL finished = NO;
__block BOOL failed = NO;
[parserobject parsefile:file withCallback:^ {
if (started) {
//file parsing started
//this should get executed first
} else {
...
// Conditionally set started to YES
started = YES;
}
//if file parsing succesfully started also check if it
//succesfully finished or failed in the same callback
if (finished) {
//file parsing finished
}
if(failed) {
//this represents if parsing failed any time from start to finish
//if so exit the callback
}
}];

Testing asynchronous call in unit test in iOS

I am facing a problem while unit testing an asynchronous call in iOS. (Although it is working fine in view controllers.)
Has anyone faced this issue before? I have tried using a wait function but I'm still facing the same problem.
Please suggest an example of a good way to do this.
You'll need to spin the runloop until your callback is invoked. Make sure that it gets invoked on the main queue, though.
Try this:
__block BOOL done = NO;
doSomethingAsynchronouslyWithBlock(^{
done = YES;
});
while(!done) {
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
}
You can also use a semaphore (example below), but I prefer to spin the runloop to allow asynchronous blocks dispatched to the main queue to be processed.
dispatch_semaphore_t sem = dispatch_semaphore_create(0);
doSomethingAsynchronouslyWithBlock(^{
//...
dispatch_semaphore_signal(sem);
});
dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
Here is Apple's description of native support for async testing.
TL;DR manual:
Look at XCTextCase+AsynchronousTesting.h
There is special class XCTestExpectation with only one public method: - (void)fulfill;
You should init instance of this class and in success case call fulfill method. Otherwise your test will fail after timeout that you specify in that method:
- (void)waitForExpectationsWithTimeout:(NSTimeInterval)timeout handler:(XCWaitCompletionHandler)handlerOrNil;
Example:
- (void)testAsyncMethod
{
//Expectation
XCTestExpectation *expectation = [self expectationWithDescription:#"Testing Async Method Works Correctly!"];
[MyClass asyncMethodWithCompletionBlock:^(NSError *error) {
if(error)
NSLog(#"error is: %#", error);
else
[expectation fulfill];
}];
//Wait 1 second for fulfill method called, otherwise fail:
[self waitForExpectationsWithTimeout:1 handler:^(NSError *error) {
if(error)
{
XCTFail(#"Expectation Failed with error: %#", error);
}
}];
}
I think many of the suggested solutions in this post has the problem that if the asynchronous operation does not complete the "done" flag is never set, and the test will hang forever.
I have successfully used this approach in many of my test.
- (void)testSomething {
__block BOOL done = NO;
[obj asyncMethodUnderTestWithCompletionBlock:^{
done = YES;
}];
XCTAssertTrue([self waitFor:&done timeout:2],
#"Timed out waiting for response asynch method completion");
}
- (BOOL)waitFor:(BOOL *)flag timeout:(NSTimeInterval)timeoutSecs {
NSDate *timeoutDate = [NSDate dateWithTimeIntervalSinceNow:timeoutSecs];
do {
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:timeoutDate];
if ([timeoutDate timeIntervalSinceNow] < 0.0) {
break;
}
}
while (!*flag);
return *flag;
}
Since Xcode 6 this built in to XCTest as a category:
See https://stackoverflow.com/a/24705283/88164
Here's another alternative, XCAsyncTestCase, that works well with OCMock if you need to use it. It's based on GHUnit's async tester, but is uses the regular XCTest framework instead.
Fully compatible with Xcode Bots.
https://github.com/iheartradio/xctest-additions
Usage is the same, just import and subclass XCAsyncTestCase.
#implementation TestAsync
- (void)testBlockSample
{
[self prepare];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(){
sleep(1.0);
[self notify:kXCTUnitWaitStatusSuccess];
});
// Will wait for 2 seconds before expecting the test to have status success
// Potential statuses are:
// kXCTUnitWaitStatusUnknown, initial status
// kXCTUnitWaitStatusSuccess, indicates a successful callback
// kXCTUnitWaitStatusFailure, indicates a failed callback, e.g login operation failed
// kXCTUnitWaitStatusCancelled, indicates the operation was cancelled
[self waitForStatus:kXCTUnitWaitStatusSuccess timeout:2.0];
}
AGAsyncTestHelper is a C macro for writing unit tests with asynchronous operations and works with both SenTestingKit and XCTest.
Simple and to the point
- (void)testAsyncBlockCallback
{
__block BOOL jobDone = NO;
[Manager doSomeOperationOnDone:^(id data) {
jobDone = YES;
}];
WAIT_WHILE(!jobDone, 2.0);
}
Sam Brodkin already gave the right answer.
Just to make the answer looks better at first sight, I bring the sample code here.
Use XCTestExpectation.
// Test that the document is opened. Because opening is asynchronous,
// use XCTestCase's asynchronous APIs to wait until the document has
// finished opening.
- (void)testDocumentOpening
{
// Create an expectation object.
// This test only has one, but it's possible to wait on multiple expectations.
XCTestExpectation *documentOpenExpectation = [self expectationWithDescription:#"document open"];
NSURL *URL = [[NSBundle bundleForClass:[self class]]
URLForResource:#"TestDocument" withExtension:#"mydoc"];
UIDocument *doc = [[UIDocument alloc] initWithFileURL:URL];
[doc openWithCompletionHandler:^(BOOL success) {
XCTAssert(success);
// Possibly assert other things here about the document after it has opened...
// Fulfill the expectation-this will cause -waitForExpectation
// to invoke its completion handler and then return.
[documentOpenExpectation fulfill];
}];
// The test will pause here, running the run loop, until the timeout is hit
// or all expectations are fulfilled.
[self waitForExpectationsWithTimeout:1 handler:^(NSError *error) {
[doc closeWithCompletionHandler:nil];
}];
}
you can use async api calling in swift like this
private let serverCommunicationManager : ServerCommunicationManager = {
let instance = ServerCommunicationManager()
return instance
}()
var expectation:XCTestExpectation?
func testAsyncApiCall() {
expectation = self.expectation(description: "async request")
let header = ["Authorization":"eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImp0aSI6ImQ4MmY1MTcxNzI4YTA5MjI3NWIzYWI3OWNkOTZjMGExOTI4MmM2NDEyZjMyYWQzM2ZjMzY4NmU2MjlhOWY2YWY1NGE0MDI4MmZiNzY2NWQ3In0.eyJhdWQiOiIxIiwianRpIjoiZDgyZjUxNzE3MjhhMDkyMjc1YjNhYjc5Y2Q5NmMwYTE5MjgyYzY0MTJmMzJhZDMzZmMzNjg2ZTYyOWE5ZjZhZjU0YTQwMjgyZmI3NjY1ZDciLCJpYXQiOjE1MDg4MjU1NTEsIm5iZiI6MTUwODgyNTU1MSwiZXhwIjoxNTQwMzYxNTUxLCJzdWIiOiIiLCJzY29wZXMiOltdfQ.osoMQgiY7TY7fFrh5r9JRQLQ6AZhIuEbrIvghF0VH4wmkqRUE6oZWjE5l0jx1ZpXsaYUhci6EDngnSTqs1tZwFTQ3srWxdXns2R1hRWUFkAN0ri32W0apywY6BrahdtiVZa9LQloD1VRMT1_QUnljMXKsLX36gXUsNGU6Bov689-bCbugK6RC3n4LjFRqJ3zD9gvkRaODuOQkqsNlS50b5tLm8AD5aIB4jYv3WQ4-1L74xXU0ZyBTAsLs8LOwvLB_2B9Qdm8XMP118h7A_ddLo9Cyw-WqiCZzeZPNcCvjymNK8cfli5_LZBOyjZT06v8mMqg3zszWzP6jOxuL9H1JjBF7WrPpz23m7dhEwa0a-t3q05tc1RQRUb16W1WhbRJi1ufdMa29uyhX8w_f4fmWdAnBeHZ960kjCss98FA73o0JP5F0GVsHbyCMO-0GOHxow3-BqyPOsmcDrI4ay006fd-TJk52Gol0GteDgdntvTMIrMCdG2jw8rfosV6BgoJAeRbqvvCpJ4OTj6DwQnV-diKoaHdQ8vHKe-4X7hbYn_Bdfl52gMdteb3_ielcVXIaHmQ-Dw3E2LSVt_cSt4tAHy3OCd7WORDY8uek4Paw8Pof0OiuqQ0EB40xX5hlYqZ7P_tXpm-W-8ucrIIxgpZb0uh-wC3EzBGPjpPD2j9CDo"]
serverCommunicationManager.sendServerRequest(httpMethodType: .get, baseURL: "http://192.168.2.132:8000/api/v1/user-role-by-company-id/2", param: nil, header: header) { (isSuccess, msg , response) in
if isSuccess
{
let array = response as! NSArray
if array.count == 8
{
XCTAssertTrue(true)
self.expectation?.fulfill()
}
else
{
XCTAssertFalse(false)
XCTFail("array count fail")
}
}
}
waitForExpectations(timeout: 5) { (error) in
if let error = error{
XCTFail("waiting with error: \(error.localizedDescription)")
}
}
}
I suggest you should have a look on the tests of Facebook-ios-sdk. It's a good example of how to test async unit test on iOS, though personally I think async tests should be break into sync tests.
FBTestBlocker: a blocker that prevent current thread exits with specified timeout. You can drag and drop this to your project, but you need to remove OCMock related stuff if you don't have that in you project.
FBTestBlocker.h
FBTestBlocker.m
FBURLConnectionTests: test examples you should look at.
FBURLConnectionTests.h
FBURLConnectionTests.m
This code snippet should give you some idea
- (void)testExample
{
FBTestBlocker *_blocker = [[FBTestBlocker alloc] initWithExpectedSignalCount:1];
__block BOOL excuted = NO;
[testcase test:^(BOOL testResult) {
XCTAssert(testResult, #"Should be true");
excuted = YES;
[_blocker signal];
}];
[_blocker waitWithTimeout:4];
XCTAssertTrue(excuted, #"Not executed");
}
Try KIWI framework. It's powerful and might help you with other kinds of tests.
I recommend you connection semaphore + runloop, i also wrote method which take block:
// Set the flag to stop the loop
#define FLEND() dispatch_semaphore_signal(semaphore);
// Wait and loop until flag is set
#define FLWAIT() WAITWHILE(dispatch_semaphore_wait(semaphore, DISPATCH_TIME_NOW))
// Macro - Wait for condition to be NO/false in blocks and asynchronous calls
#define WAITWHILE(condition) \
do { \
while(condition) { \
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:1]]; \
} \
} while(0)
method:
typedef void(^FLTestAsynchronousBlock)(void(^completion)(void));
void FLTestAsynchronous(FLTestAsynchronousBlock block) {
FLSTART();
block(^{
FLEND();
});
FLWAIT();
};
and call
FLTestAsynchronous(^(void(^completion)()){
[networkManager signOutUser:^{
expect(networkManager.currentUser).to.beNil();
completion();
} errorBlock:^(NSError *error) {
expect(networkManager.currentUser).to.beNil();
completion();
}];
});
If you are using XCode 6, you can test async network calls like this:
XCTest and asynchronous testing in Xcode 6

Resources