Q1: Can I call a method and have it execute on a background thread from inside another method that is currently executing on the main thread?
Q2: As an extension of the above, can I call a method and have it execute on a background thread from inside another method that is currently executing on some other background thread itself?
Q3: And one final question given the above : if I initialize an instance of some object X on some thread (main/background) and then have a method Y, of that object X, executing on some other background thread, can this method Y send messages and update an int property (e.g. of that Object X, or is such communication not possible ?
The reason I'm asking this last question is because I've been going over and over it again and I can't figure what is wrong here:
The following code returns zero acceleration and zero degrees values :
MotionHandler.m
#implementation MotionHandler
#synthesize currentAccelerationOnYaxis; // this is a double
-(void)startCompassUpdates
{
locationManager=[[CLLocationManager alloc] init];
locationManager.desiredAccuracy = kCLLocationAccuracyBest;
locationManager.delegate=self;
[locationManager startUpdatingHeading];
NSLog(#"compass updates initialized");
}
-(int) currentDegrees
{
return (int)locationManager.heading.magneticHeading;
}
-(void) startAccelerationUpdates
{
CMMotionManager *motionManager = [[CMMotionManager alloc] init];
motionManager.deviceMotionUpdateInterval = 0.01;
[motionManager startDeviceMotionUpdatesToQueue:[NSOperationQueue currentQueue]
withHandler:^(CMDeviceMotion *motion, NSError *error)
{
self.currentAccelerationOnYaxis = motion.userAcceleration.y;
}
];
}
#end
Tester.m
#implementation Tester
-(void)test
{
MotionHandler *currentMotionHandler = [[MotionHandler alloc] init];
[currentMotionHandler performSelectorInBackground:#selector(startCompassUpdates) withObject:nil];
[currentMotionHandler performSelectorInBackground:#selector(startAccelerationUpdates) withObject:nil];
while(1==1)
{
NSLog(#"current acceleration is %f", currentMotionHandler.currentAccelerationOnYaxis);
NSLog(#"current degrees are %i", [currentMotionHandler currentDegrees]);
}
SomeViewController.m
#implementation SomeViewController
-(void) viewDidLoad
{
[myTester performSelectorInBackground:#selector(test) withObject:nil];
}
#end
However, the following code returns those values normally :
Tester.m
#interface Tester()
{
CLLocationManager *locationManager;
double accelerationOnYaxis;
// more code..
}
#end
#implementation Tester
- (id) init
{
locationManager=[[CLLocationManager alloc] init];
locationManager.desiredAccuracy = kCLLocationAccuracyBest;
locationManager.delegate=self;
[locationManager startUpdatingHeading];
// more code..
}
-(void) test
{
CMMotionManager *motionManager = [[CMMotionManager alloc] init];
motionManager.deviceMotionUpdateInterval = 0.01;
[motionManager startDeviceMotionUpdatesToQueue:[NSOperationQueue mainQueue]
withHandler:^(CMDeviceMotion *motion, NSError *error)
{
accelerationOnYaxis = motion.userAcceleration.y;
}
];
while(1==1)
{
NSLog(#"current acceleration is %f", accelerationOnYaxis);
NSLog(#"current degrees are %i", locationManager.heading.magneticHeading);
}
}
SomeViewController.m
#implementation SomeViewController
-(void) viewDidLoad
{
[myTester performSelectorInBackground:#selector(test) withObject:nil];
}
What's wrong with the first version? I really want to use that first one because it seems much better design-wise.. Thank you for any help!
Calling performSelectorInBackground:withObject: is the same as if you called the detachNewThreadSelector:toTarget:withObject: method of NSThread with the current object, selector, and parameter object as parameters (Threading Programming Guide). No matter where you call it, a new thread will be created to perform that selector. So to answer your first two questions: yes and yes.
For your final question, as long as this Object X is the same object in both methods, any of X's properties can be updated. But, beware that this can yield unexpected results (ie. see Concurrency Programming Guide). If multiple methods are updating X's property, values can be overwritten or disregarded. But, if you are only updating it from method Y and reading it from all other methods, such problems shouldn't occur.
You should take a look at the Grand Central Dispatch documentation from Apple. It allows you to use multiple threads in a block-based structure.
2 importants function are dispatch_sync() and dispatch_async().
Some examples:
To execute a certain block of code on a background thread and wait until it is finished:
__block id someVariable = nil;
dispatch_sync(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{
// do some heavy work in the background
someVariable = [[NSObject alloc] init];
});
NSLog(#"Variable: %#", someVariable);
This function modifies the variable someVariable which you can use later on. Please note that the main thread will be paused to wait for the background thread. If that is not what you want, you can use dispatch_async() as follows:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{
// do some heavy work in the background
NSObject *someVariable = [[NSObject alloc] init];
// notify main thread that the work is done
dispatch_async(dispatch_get_main_queue(), ^{
// call some function and pass someVariable to it, it will be called on the main thread
NSLog(#"Variable: %#", someVariable);
});
});
Related
New iOS developer here. I've been searching for an answer to this in documentation on blocks and the altimeter, but I'm coming up short. I assume there's some simple thing I'm missing, but can't figure it out.
I have a custom class called PressureSensor. Simplistically speaking, the class has a property:
#property (nonatomic, strong, readwrite) NSMutableArray *pressure;
I load NSNumber values from the altimeter into this array.
The initializer for the class is:
- (instancetype)init
{
self = [super init];
if (self)
{
if (self.altimeterIsAvailable)
{
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[self.altimeter startRelativeAltitudeUpdatesToQueue:queue withHandler:^(CMAltitudeData *altitudeData, NSError *error) {
dispatch_async(dispatch_get_main_queue(), ^ {
[self.pressure addObject:altitudeData.pressure];
NSLog(#"Pressure 1: %#", [self.pressure lastObject]);
});
}];
NSLog(#"Pressure 2: %#", [self.pressure lastObject]);
}
}
return self;
}
When I run the app on my phone, I assume that pressure is successfully added to the self.pressure array, because the pressure is printed to the console by the "Pressure 1" line, which accesses the lastObject of self.pressure. However, it seems that these changes don't hold outside this block, as the Pressure 2 line outputs (null) to the console, and it doesn't seem like I can do anything with self.pressure outside this block.
Am I missing something about how blocks work exactly? Do I just need a __block somewhere? I'm completely at a loss here.
Addendum: self.altimeterIsAvailable is defined elsewhere. That part of the code shouldn't have any issues.
EDIT: The error ended up being elsewhere. For future readers who browse this post, the above code should be a perfectly valid way to add to a property array in a block.
This is not an answer but I'd like to mention it.
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[self.altimeter startRelativeAltitudeUpdatesToQueue:queue withHandler:^(CMAltitudeData *altitudeData, NSError *error) {
dispatch_async(dispatch_get_main_queue(), ^ {
...
});
}];
Creating a queue, and dispatch_async to the main queue. It's redundant. You can use NSOperationQueue +mainQueue method for it directly.
NSOperationQueue *queue = [NSOperationQueue mainQueue];
[self.altimeter startRelativeAltitudeUpdatesToQueue:queue withHandler:^(CMAltitudeData *altitudeData, NSError *error) {
...
}];
I'm writing a mac app that runs its own web server, using the GCDWebServer library (https://github.com/swisspol/GCDWebServer). My app delegate handles GET requests like so:
__weak typeof(self) weakSelf = self;
[webServer addDefaultHandlerForMethod:#"GET"
requestClass:[GCDWebServerRequest class]
processBlock:^GCDWebServerResponse *(GCDWebServerRequest* request) {
return [weakSelf handleRequest:request];
}];
And then the handleRequest method returns the response data, something like:
return [GCDWebServerDataResponse responseWithHTML:#"<html><body><p>Hello World!</p></body></html>"];
So far so good. Except now I want the handleRequest method to use NSSpeechSynthesizer to create an audio file with some spoken text in it, and then wait for the speechSynthesizer:didFinishSpeaking method to be called before returning to the processBlock.
// NSSpeechSynthesizerDelegate method:
- (void)speechSynthesizer:(NSSpeechSynthesizer *)sender didFinishSpeaking:(BOOL)success
{
NSLog(#"did finish speaking, success: %d", success);
// return to processBlock...
}
Problem is, I have no idea how to do this. Is there a way to return from the speechSynthesizer:didFinishSpeaking method into the processBlock defined above?
You need to run the speech synthesizer on a separate thread with its own run loop, and use a lock to allow your request thread to wait for the operation to complete on the speech thread.
Assuming the web server maintains its own thread(s) and runloop, you can use your app's main thread to run the speech synthesizer, and you can use NSCondition to signal completion to the web response thread.
A basic (untested) example (without error handling):
#interface SynchroSpeaker : NSObject<NSSpeechSynthesizerDelegate>
- (id)initWithText:(NSString*)text outputUrl:(NSURL*)url;
- (void)run;
#end
#implementation SynchroSpeaker
{
NSCondition* _lock;
NSString* _text;
NSURL* _url;
NSSpeechSynthesizer* _synth;
}
- (id)initWithText:(NSString*)text outputUrl:(NSURL*)url
{
if (self = [super init])
{
_text = text;
_url = url;
_lock = [NSCondition new];
}
return self;
}
- (void)run
{
NSAssert(![NSThread isMainThread], #"This method cannot execute on the main thread.");
[_lock lock];
[self performSelectorOnMainThread:#selector(startOnMainThread) withObject:nil waitUntilDone:NO];
[_lock wait];
[_lock unlock];
}
- (void)startOnMainThread
{
NSAssert([NSThread isMainThread], #"This method must execute on the main thread.");
[_lock lock];
//
// Set up your speech synethsizer and start speaking
//
}
- (void)speechSynthesizer:(NSSpeechSynthesizer *)sender didFinishSpeaking:(BOOL)success
{
//
// Signal waiting thread that speaking has completed
//
[_lock signal];
[_lock unlock];
}
#end
It's used like so:
- (id)handleRequest:(id)request
{
SynchroSpeaker* speaker = [[SynchroSpeaker alloc] initWithText:#"Hello World" outputUrl:[NSURL fileURLWithPath:#"/tmp/foo.dat"]];
[speaker run];
////
return response;
}
GCDWebServer does run into its own threads (I guess 2 of them) - not in the main one. My solution needed to run code in Main Thread when calling the ProcessBlock.
I found this way that suits my needs:
First declare a weak storage for my AppDelegate: __weak AppDelegate *weakSelf = self;. Doing so I can access all my properties within the block.
Declare a strong reference to AppDelegate from within the block like so: __strong AppDelegate* strongSelf = weakSelf;
Use NSOperationQueue to align the operation on mainThread:
[[NSOperationQueue mainQueue] addOperationWithBlock:^ {
//Your code goes in here
NSLog(#"Main Thread Code");
[strongSelf myMethodOnMainThread];
}];
In this way myMethodOnMainThread surely will run where it's supposed to.
For sake of clarity I quote my relevant code section:
webServer = [[GCDWebServer alloc] init];
webServer.delegate = self;
__weak AppDelegate *weakSelf = self;
// Add a handler to respond to GET requests
[webServer addDefaultHandlerForMethod:#"GET"
requestClass:[GCDWebServerRequest class]
asyncProcessBlock:^(GCDWebServerRequest* request, GCDWebServerCompletionBlock completionBlock) {
__strong AppDelegate* strongSelf = weakSelf;
[[NSOperationQueue mainQueue] addOperationWithBlock:^ {
//Your code goes in here
NSLog(#"Main Thread Code");
[strongSelf myMethodOnMainThread];
}];
GCDWebServerDataResponse* response = [GCDWebServerDataResponse responseWithJSONObject:packet];
completionBlock(response);
}];
GCWebServer supports fully asynchronous responses as of version 3.0 and later [1].
[webServer addDefaultHandlerForMethod:#"GET"
requestClass:[GCDWebServerRequest class]
asyncProcessBlock:^(GCDWebServerRequest* request, GCDWebServerCompletionBlock completionBlock) {
// 1. Trigger speech synthesizer on main thread (or whatever thread it has to run on) and save "completionBlock"
// 2. Have the delegate from the speech synthesizer call "completionBlock" when done passing an appropriate response
}];
[1] https://github.com/swisspol/GCDWebServer#asynchronous-http-responses
I have an application in which a long running process (> 1 min) is placed onto an NSOperationQueue (Queue A). The UI is fully-responsive while the Queue A operation runs, exactly as expected.
However, I have a different kind of operation the user can perform which runs on a completely separate NSOperationQueue (Queue B).
When a UI event triggers the placement of an operation on Queue B, it must wait until after the currently-executing operation on Queue A finishes. This occurs on an iPod Touch (MC544LL).
What I expected to see instead was that any operation placed onto Queue B would more or less begin immediately executing in parallel with the operation on Queue A. This is the behavior I see on the Simulator.
My question is two parts:
Is the behavior I'm seeing on my device to be expected based on available documentation?
Using NSOperation/NSOperationQueue, how do I pre-empt the currently running operation on Queue A with a new operation placed on Queue B?
Note: I can get exactly the behavior I'm after by using GCD queues for Queues A/B, so I know my device is capable of supporting what I'm trying to do. However, I really, really want to use NSOperationQueue because both operations need to be cancelable.
I have a simple test application:
The ViewController is:
//
// ViewController.m
// QueueTest
//
#import "ViewController.h"
#interface ViewController ()
#property (strong, nonatomic) NSOperationQueue *slowQueue;
#property (strong, nonatomic) NSOperationQueue *fastQueue;
#end
#implementation ViewController
-(id)initWithCoder:(NSCoder *)aDecoder
{
if (self = [super initWithCoder:aDecoder]) {
self.slowQueue = [[NSOperationQueue alloc] init];
self.fastQueue = [[NSOperationQueue alloc] init];
}
return self;
}
-(void)viewDidLoad
{
NSLog(#"View loaded on thread %#", [NSThread currentThread]);
}
// Responds to "Slow Op Start" button
- (IBAction)slowOpStartPressed:(id)sender {
NSBlockOperation *operation = [[NSBlockOperation alloc] init];
[operation addExecutionBlock:^{
[self workHard:600];
}];
[self.slowQueue addOperation:operation];
}
// Responds to "Fast Op Start" button
- (IBAction)fastOpStart:(id)sender {
NSBlockOperation *operation = [[NSBlockOperation alloc] init];
[operation addExecutionBlock:^{
NSLog(#"Fast operation on thread %#", [NSThread currentThread]);
}];
[self.fastQueue addOperation:operation];
}
-(void)workHard:(NSUInteger)iterations
{
NSLog(#"SlowOperation start on thread %#", [NSThread currentThread]);
NSDecimalNumber *result = [[NSDecimalNumber alloc] initWithString:#"0"];
for (NSUInteger i = 0; i < iterations; i++) {
NSDecimalNumber *outer = [[NSDecimalNumber alloc] initWithUnsignedInteger:i];
for (NSUInteger j = 0; j < iterations; j++) {
NSDecimalNumber *inner = [[NSDecimalNumber alloc] initWithUnsignedInteger:j];
NSDecimalNumber *product = [outer decimalNumberByMultiplyingBy:inner];
result = [result decimalNumberByAdding:product];
}
result = [result decimalNumberByAdding:outer];
}
NSLog(#"SlowOperation end");
}
#end
The output I see after first pressing the "Slow Op Start" button followed ~1 second later by pressing the "Fast Op Start" button is:
2012-11-28 07:41:13.051 QueueTest[12558:907] View loaded on thread <NSThread: 0x1d51ec30>{name = (null), num = 1}
2012-11-28 07:41:14.745 QueueTest[12558:1703] SlowOperation start on thread <NSThread: 0x1d55e5f0>{name = (null), num = 3}
2012-11-28 07:41:25.127 QueueTest[12558:1703] SlowOperation end
2012-11-28 07:41:25.913 QueueTest[12558:3907] Fast operation on thread <NSThread: 0x1e36d4c0>{name = (null), num = 4}
As you can see, the second operation does not begin executing until after the first operation finishes, despite the fact that these are two separate (and presumably independent) NSOperationQueues.
I have read the Apple Concurrency Guide, but find nothing describing this situation. I've also read two SO questions on related topics (link, link), but neither seems to get to the heart of the problem I'm seeing (pre-emption).
Other things I've tried:
setting the queuePriority on each NSOperation
setting the queuePriority on each NSOperation while placing both types of operations onto the same queue
placing both operations onto the same queue
This question has undergone multiple edits, which may make certain comments/answers difficult to understand.
I suspect the problem you are having is that both operation queues are executing their blocks on the underlying default priority dispatch queue. Consequently, if several slow operations are enqueued before the fast operations then perhaps you will see this behaviour.
Why not either set the NSOperationQueue instance for the slow operations so that it only executes one operation at any given time (i.e. set maxConcurrentOperationCount to one for this queue), or if your operations are all blocks then why not use GCD queues directly? e.g.
static dispatch_queue_t slowOpQueue = NULL;
static dispatch_queue_t fastOpQueue = NULL;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
slowOpQueue = dispatch_queue_create("Slow Ops Queue", NULL);
fastOpQueue = dispatch_queue_create("Fast Ops Queue", DISPATCH_QUEUE_CONCURRENT);
});
for (NSUInteger slowOpIndex = 0; slowOpIndex < 5; slowOpIndex++) {
dispatch_async(slowOpQueue, ^(void) {
NSLog(#"* Starting slow op %d.", slowOpIndex);
for (NSUInteger delayLoop = 0; delayLoop < 1000; delayLoop++) {
putchar('.');
}
NSLog(#"* Ending slow op %d.", slowOpIndex);
});
}
for (NSUInteger fastBlockIndex = 0; fastBlockIndex < 10; fastBlockIndex++) {
dispatch_async(fastOpQueue, ^(void) {
NSLog(#"Starting fast op %d.", fastBlockIndex);
NSLog(#"Ending fast op %d.", fastBlockIndex);
});
}
As far as using the NSOperationQueue as per your comments about needing the operation cancellation facilities etc. can you try:
- (void)loadSlowQueue
{
[self.slowQueue setMaxConcurrentOperationCount:1];
NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
NSLog(#"begin slow block 1");
[self workHard:500];
NSLog(#"end slow block 1");
}];
NSBlockOperation *operation2 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(#"begin slow block 2");
[self workHard:500];
NSLog(#"end slow block 2");
}];
[self.slowQueue addOperation:operation];
[self.slowQueue addOperation:operation2];
}
As I think the two blocks you add to the operation on the slow queue are being executed in parallel on the default queue and preventing your fast operations from being scheduled.
Edit:
If you're still finding the default GCD queue is choking, why not create an NSOperation subclass that executes blocks without using GCD at all for your slow operations, this will still give you the declarative convenience of not creating a separate subclass for each operation but use the threading model of a regular NSOperation. e.g.
#import <Foundation/Foundation.h>
typedef void (^BlockOperation)(NSOperation *containingOperation);
#interface PseudoBlockOperation : NSOperation
- (id)initWithBlock:(BlockOperation)block;
- (void)addBlock:(BlockOperation)block;
#end
And then for the implementation:
#import "PseudoBlockOperation.h"
#interface PseudoBlockOperation()
#property (nonatomic, strong) NSMutableArray *blocks;
#end
#implementation PseudoBlockOperation
#synthesize blocks;
- (id)init
{
self = [super init];
if (self) {
blocks = [[NSMutableArray alloc] initWithCapacity:1];
}
return self;
}
- (id)initWithBlock:(BlockOperation)block
{
self = [self init];
if (self) {
[blocks addObject:[block copy]];
}
return self;
}
- (void)main
{
#autoreleasepool {
for (BlockOperation block in blocks) {
block(self);
}
}
}
- (void)addBlock:(BlockOperation)block
{
[blocks addObject:[block copy]];
}
#end
Then in your code you can do something like:
PseudoBlockOperation *operation = [[PseudoBlockOperation alloc] init];
[operation addBlock:^(NSOperation *operation) {
if (!operation.isCancelled) {
NSLog(#"begin slow block 1");
[self workHard:500];
NSLog(#"end slow block 1");
}
}];
[operation addBlock:^(NSOperation *operation) {
if (!operation.isCancelled) {
NSLog(#"begin slow block 2");
[self workHard:500];
NSLog(#"end slow block 2");
}
}];
[self.slowQueue addOperation:operation];
Note that in this example any blocks that are added to the same operation will be executed sequentially rather than concurrently, to execute concurrently create one operation per block. This has the advantage over NSBlockOperation in that you can pass parameters into the block by changing the definition of BlockOperation - here I passed the containing operation, but you could pass whatever other context is required.
Hope that helps.
Long time lurker, first time poster. I'm relatively new to objective-C so my apologies if I'm asking something fairly simple. My google & stack overflow-fu has let me down here, so I figured somebody could maybe help.
I have a synchronous process executing, say, three functions in a row - call it A -> B-> C , where task A executes, followed by B, followed by C.
Now, B involves an asynchronous process with a delegate callback for completion. But B must complete before C is executed, so I need some mechanism such that C is not triggered before B has finished. I imagine there must be a common design pattern for this problem?
Initially naive solution would be -
execute A
execute B
while (!B finished) {}
execute C
...but this seems really lame.
I suspect I can do this with some kind of block, but for the life of me I just can't figure it out. Could anyone help?
appreciate any assistance!
Guillaume
Thanks for all the feeback - apologies for not responding sooner. I've now resolved this in a slightly different way to the suggestions:
Firstly, I extended NSObject to have the following method -
#import "NSObject+LTExtensions.h"
#implementation NSObject (Testing)
- (void) performSelectorWithBlock: (SEL) selector withSemaphore:(dispatch_semaphore_t)semaphore
{
[self performSelector:selector]; // This selector should complete the semaphore
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
dispatch_release(semaphore);
}
#end
This allows me to execute a block via a selector. When the block executes, the thread on which it is executed will wait until signaled to proceed by a specific dispatch semaphore.
What we can then do is as follows:
Call A
Create a dispatch semaphore and define a selector which executes B
Call the method defined above to execute B and wait for the selector to complete
When B is completed (via a delegate callback), it signals the dispatch semaphore to suspend the wait
I then execute C
So we have
A
B -> Asynchronous with delegate callback
C
Here's a simple example of how the above is implemented
-(void) methodA {
// ... do something
// Assign your semaphore (this is a dispatch_semaphore_t)
self.semaphore = dispatch_semaphore_create(0);
[self performSelectorWithBlock:#selector(methodB) withSemaphore:semaphore];
[self methodC];
}
-(void) methodB {
// ... do whatever needs to be done asynchronously
CFRunLoopRun();
}
-(void) methodBDelegateCallBack {
// This is called when B completes
// Signal completion
dispatch_semaphore_signal(self.semaphore);
CFRunLoopStop(CFRunLoopGetCurrent());
}
-(void) methodC {
...
}
Works very well without any issues (but I am new to Obj C, so there may be glaring issues with my approach).
Another approach to this problem might be the following: create an helper object for the async task and copy a completion block when the task is called. Call the completion block using the delegate methods once the async task is finished. As a result we might execute the tasks in order like the following:
FSTask *taskA = [FSTask taskWithName:#"Task A"];
FSAsyncTask *taskB = [FSAsyncTask asyncTaskWithName:#"Task B"];
FSTask *taskC = [FSTask taskWithName:#"Task C"];
[taskA performTaskWithCompletionBlock:^ (NSString *result) {
NSLog(#"%#", result);
[taskB performTaskWithCompletionBlock:^ (NSString *result) {
NSLog(#"%#", result);
[taskC performTaskWithCompletionBlock:^ (NSString *result) {
NSLog(#"%#", result);
}];
}];
}];
So how is this achieved? Well, look at the task objects below ...
FSTask.m - synchronous work on main thread ...
#interface FSTask ()
#property (nonatomic, copy) NSString *name;
#end
#implementation FSTask
#synthesize name = _name;
+ (FSTask *)taskWithName:(NSString *)name
{
FSTask *task = [[FSTask alloc] init];
if (task)
{
task.name = name;
}
return task;
}
- (void)performTaskWithCompletionBlock:(void (^)(NSString *taskResult))block
{
NSString *message = [NSString stringWithFormat:#"%#: doing work on main thread ...", _name];
NSLog(#"%#", message);
if (block)
{
NSString *result = [NSString stringWithFormat:#"%#: result", _name];
block(result);
}
}
#end
FSAsyncTask.m - asynchronous work on background thread ...
#interface FSAsyncTask ()
#property (nonatomic, copy) void (^block)(NSString *taskResult);
#property (nonatomic, copy) NSString *name;
- (void)performAsyncTask;
#end
#implementation FSAsyncTask
#synthesize block = _block;
#synthesize name = _name;
+ (FSAsyncTask *)asyncTaskWithName:(NSString *)name
{
FSAsyncTask *task = [[FSAsyncTask alloc] init];
if (task)
{
task.name = name;
}
return task;
}
- (void)performTaskWithCompletionBlock:(void (^)(NSString *taskResult))block
{
self.block = block;
// the call below could be e.g. a NSURLConnection that's being opened,
// in this case a NSURLConnectionDelegate method will return the result
// in this delegate method the completion block could be called ...
dispatch_queue_t queue = dispatch_queue_create("com.example.asynctask", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^ {
[self performAsyncTask];
});
}
#pragma mark - Private
- (void)performAsyncTask
{
for (int i = 0; i < 5; i++)
{
NSString *message = [NSString stringWithFormat:#"%d - %#: doing work on background thread ...", i, _name];
NSLog(#"%#", message);
[NSThread sleepForTimeInterval:1];
}
// this completion block might be called from your delegate methods ...
if (_block)
{
dispatch_async(dispatch_get_main_queue(), ^ {
NSString *result = [NSString stringWithFormat:#"%#: result", _name];
_block(result);
});
}
}
#end
You can assign a block property to B where it would be used to execute a block of code before calling the delegate method. something like:
#property (nonatomic, copy)void(^yourBlock)(id blockParameter);
So, after calling B's delegate, you could call upon this block and execute it. Inside this block, you can call C's method.
the way I handled this is.
I created a NSMutableDictionary before the async call.
Then i make the async call. and do a check for the value I am waiting for
NSMutableDictionary *dictionary = [NSMutableDictionary dictionary];
[AsyncCallClass asyncCall:^{
#synchronized(dictionary) {
[dictionary setValue:myValue forKey:#"result"];
}
}];
while (true){
#synchronized(dictionary){
if ([dictionary valueForKey:#"resultValue"] != nil){
break;
}
}
[NSThread sleepForTimeInterval:.25];
}
MyResultClass *result = [dictionary valueForKey:#"resultValue"];
you can add time out for this too to stop it from being an infinite loop. but this is my solution. and it seems to work pretty well.
Here is the typical code I use to do such things (adapt the completionBlock signature and method names to your needs of course)
typedef void (^BCompletionBlock)(void);
#interface B : NSObject <BDelegate>
#property(nonatomic, copy) BCompletionBlock completionBlock;
-(void)doAsynchronousActionWithCompletion:(BCompletionBlock)aCompletionBlock;
#end
#implementation B
-(void)doAsynchronousActionWithCompletion:(BCompletionBlock)aCompletionBlock
{
// Store the completion block for later use
self.completionBlock = aCompletionBlock;
// Then execute your asynchronous action, that will call some delegate method when done
[self doYourAsynchronousActionWithDelegate:self];
}
-(void)yourBDelegateMethodCalledWhenDone
{
// Upon your async task completion, call your completion block then
if (self.completionBlock) self.completionBlock();
}
#end
Then here is an example usage:
-(void)doActions
{
[a doSynchronousAction];
[b doAsynchronousActionWithCompletion:^{
[c doSynchronousAction];
// A,B,C are now done
}];
}
I do this quite all the time to "convert" actions that uses delegate methods (to tell me when they are done) to actions that uses completionBlocks (have some classes to do this for UIAlertViews, UIActionsSheets, and many more cases for example) and it works like a charm.
I find it much more easier to use completionBlocks than the delegate mechanism in such cases.
You can also pass C in a block like so...
define a custom block
typedef void(^myCompletion)(BOOL complete);
Create your B method
-(void)performBWithCompletionBlock:(myCompletion)complete;
{
// do your things
[self.delegate delegateCallback];
complete(YES);
}
then create BG / async ABC
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ // now we're on a BG queue to perform our async tasks
[self performA];
[self performBWithCompletionBlock:^(BOOL complete) {
if (complete == YES)
[self performC];
}];
});
If you want C to be on the main thread
dispatch_async(dispatch_get_main_queue(), ^{
[self performC];
});
I have an app, where i use function FSMoveObjectToTrashSync. It works in background thread. I need ability for my app, to click on button to pause it or continue(if it paused) how i can make it?
Example of code:
NSMutableArray *fileArray = [NSMutableArray array withobjects:#"file1url", #"file2", #"file3", nil];
NSMutableArray *threadArray = [[NSMutableArray alloc] init];
-(void)myFunc{
for (NSURL *url in fileArray){
NSThread *thread = [[NSThread alloc] initWithTarget:self selector:#selector(mySelectorWith:) object:url];
[thread start];
[threadArray addObject:thread];
}
}
-(void)mySelectorWith:(NSURL *) url{
FSRef source;
FSPathMakeRef((const UInt8 *)[[url path] fileSystemRepresentation], &source, NULL);
FSMoveObjectToTrashSync(&source, NULL, kFSFileOperationDefaultOptions);
}
PS:sorry for my english, i'm from Belarus... =(
One solution would be to replace the for loop on a single thread with an NSOperation subclass. Each operation should trash exactly one object; you then create one operation for each object you want to trash and put all of the operations on an NSOperationQueue.
The operation queue will run each operation on a thread, and it can even run multiple operations on multiple threads if it sees enough computing power laying around to do it.
An operation queue can be paused and resumed at will; when you suspend the queue, any operations in that queue that are already running will finish, but no more will start until you resume the queue.
You could use an NSConditionLock. An NSConditionLock is similar to a condition variable. It has a couple of basic methods, lockWhenCondition, and unlockWithCondition, and lock. A typical usage is to have your background thread waiting on the condition lock with "lockWhenCondition:", and the in you foreground thread to set the condition, which causes the background thread to wake up. The condition is a simple integer, usually an enumeration.
Here's an example:
enum {
kWorkTodo = 1,
kNoWorkTodo = 0
}
- (id)init {
if ((self = [super init])) {
theConditionLock = [[NSConditionLock alloc] initWithCondition: kNoWorkTodo];
workItems = [[NSMutableArray alloc] init];
}
}
- (void)startDoingWork {
[NSThread detachNewThreadSelector:#selector(doBackgroundWork) toTarget:self withObject:nil];
}
- (void)doBackgroundWork:(id)arg {
while (YES) {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
NSArray *items = nil;
[theConditionLock lockWhenCondition:kWorkTodo]; // Wait until there is work to do
items = [NSArray arrayWithArray:workItems]
[workItems removeAllObjects];
[theConditionLock unlockWithCondition:kNoWorkTodo];
for(id item in items) {
// Do some work on item.
}
[pool drain];
}
}
- (void)notifyBackgroundThreadAboutNewWork {
[theConditionLock lock];
[workItems addObject:/* some unit of work */];
[theConditionLock unlockWithCondition:kWorkTodo];
}
In this example, when startDoingWork is called doBackgroundWork: will start on a background thread, but then stop because there isn't any work to do. Once notifyBackgroundThreadAboutNewWork is called, then doBackgroundWork: will fire up and process the new work, and then go back to sleep waiting for new work to be available, which will happen the next time notifyBackgroundThreadAboutNewWork is called.