In my app, I have my main file that creates a new instance of a class and then uses NSOperationQueue to run the class functions in the background, like so:
NSOperationQueue backgroundQueue = [NSOperationQueue new];
MyClass mc = [MyClass alloc];
NSInvocationOperation* operation = [[NSInvocationOperation alloc] initWithTarget:mc selector:#selector(runEvents) object:nil];
[backgroundQueue addOperation:operation];
MyClass then does stuff in RunEvents, but I'm having difficulty passing data to the UI. I'm just trying to update a label on my storyboard, which I can do in my main class that calls MyClass, but how do I update it from MyClass?
The typical answer is to create your class as a NSOperation subclass and give it a custom completion block. If your goal is update the UI or some model object in the completion block, make sure to dispatch that block back to the main queue:
// CustomOperation.h
#import <Foundation/Foundation.h>
typedef void(^CustomOperationCompletion)(NSString *string);
#interface CustomOperation : NSOperation
#property (nonatomic, copy) CustomOperationCompletion customOperationCompletion;
- (id)initWithCustomCompletion:(CustomOperationCompletion)completion;
#end
and
// CustomOperation.m
#import "CustomOperation.h"
#implementation CustomOperation
- (id)initWithCustomCompletion:(CustomOperationCompletion)completion {
self = [super init];
if (self) {
self.customOperationCompletion = completion;
}
return self;
}
- (void)main {
NSLog(#"%s starting", __FUNCTION__);
sleep(5);
NSString *string = [[NSDate date] description];
if (self.customOperationCompletion) {
[[NSOperationQueue mainQueue] addOperationWithBlock: ^{
self.customOperationCompletion(string);
}];
}
NSLog(#"%s ending", __FUNCTION__);
}
#end
Then you can invoke it with something like:
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
CustomOperation *operation = [[CustomOperation alloc] initWithCustomCompletion:^(NSString *string) {
// update the UI with the results of the operation; here I'm just going to log it
NSLog(#"all done, string=%#", string);
}];
[queue addOperation:operation];
Clearly, change your CustomOperationCompletion parameters to include whatever you want to return (I'm just passing a string back).
Related
My goal is to achieve synchronized communication to custom Device i.e. next command can be send only when reply is received. Now I'm doing it in this way
Device class implements DeviceDelegate protocol
//Device.h
#class Device;
#protocol DeviceDelegate <NSObject>
- (void)didReciveReplyWithData:(NSData *)data;
#end
#interface Device : NSObject {}
In DeviceViewController implementation:
#interface DeviceViewController()
{
BOOL waitingForReply = false;
}
#end
#implementation DeviceViewController
- (void)sendCommandWithData:(NSData *)data
{
if ( waitingForReply == false)
{
//send command code
waitingForReply = true;
}
}
- (void)didReciveReplyWithData:(NSData *)data
{
//code
waitingForReply = false;
}
#end
but I wish to do it in more elegant way i.e. by using GCD (semaphores?) with blocks (completionHandler?). Any ideas?
PS. Sorry, but I forgot to mention: all commands sended to device while
waitingForReply = true
should be ignored!!!.
Possibly the best approach here would be to create a queue of commands with NSOperationQueue.
Since, presumably, the communication with the device is asynchronous you will have to subclass NSOperation to encapsulate the communication.
#interface DeviceCommandOperation : NSOperation <DeviceDelegate>
#property (nonatomic, assign) BOOL waitingForReply;
#property (nonatomic, copy) NSData *dataToSend;
#property (nonatomic, copy) NSData *dataReceived;
#end
#implementation DeviceCommandOperation
- (instancetype)initWithData:(NSData *)dataToSend
{
self = [super init];
if (self)
{
_dataToSend = [dataToSend copy];
}
return self;
}
- (void)setWaitingForReply:(BOOL)waitingForReply
{
if (_waitingForReply != waitingForReply)
{
[self willChangeValueForKey:#"isExecuting"];
[self willChangeValueForKey:#"isFinished"];
_waitingForReply = waitingForReply;
[self didChangeValueForKey:#"isExecuting"];
[self didChangeValueForKey:#"isFinished"];
}
}
- (void)start
{
self.waitingForReply = YES;
// Simulate sending a command and waiting for response.
// You will need to replace this with your actual communication mechanism.
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
// In reality this call would presumably come from the Device
[self didReceiveReplyWithData:someData];
});
}
- (void)didReceiveReplyWithData:(NSData *)data
{
self.dataReceived = data;
self.waitingForReply = NO;
}
#pragma mark - NSOperation
- (BOOL)isAsynchronous
{
return YES;
}
- (BOOL)isExecuting
{
return _waitingForReply;
}
- (BOOL)isFinished
{
return !_waitingForReply;
}
#end
This operation could then be used from your DeviceViewController (it would probably be better architecturally to have this responsibility elsewhere but that's not the topic of this question).
#interface DeviceViewController ()
#property (nonatomic, strong) NSOperationQueue *operationQueue;
#end
#implementation DeviceViewController
- (NSOperationQueue *)operationQueue
{
if (_operationQueue == nil)
{
_operationQueue = [[NSOperationQueue alloc] init];
}
return _operationQueue;
}
- (void)sendNextCommand
{
NSData *data = // Get data for the next command
[self sendCommandWithData:data];
}
- (void)sendCommandWithData:(NSData *)data
{
NSLog(#"Queueing operation");
DeviceCommandOperation *operation = [[DeviceCommandOperation alloc] initWithData:data];
// The operation's completionBlock gets called on a background queue
[operation setCompletionBlock:^{
NSLog(#"DeviceCommandOperation completed");
// Process operation.dataReceived
[self sendNextCommand];
}];
[self.operationQueue addOperation:operation];
}
#end
This approach will allow you to determine what (if any) command to send next, based on the reply to the previous command.
If you know all of the "commands" you will want to send initially and don't need finer grained control you could create instances of DeviceCommandOperation for each command, set the queue's maxConcurrentOperationCount to 1, and add each DeviceCommandOperation to the queue (in the order you want them to be processed).
I want to program a NSOperationQueue to perform tasks on the other thread.
So I created NSOperationQueue and class that derive from NSOperation . I added there a protocol to know if the task finished without errors and so on. But if I add an instance in my other class and set the delegate there then the main thread waits for async task to exectute the delegate code. How to prevent from it. I want to do something in my app when the background task is running.
AsyncTask.h
#protocol AsyncTaskEventsProtocol <NSObject>
-(void)OnFinishedSuccess:(NSObject *)sender information:(NSString *)inf;
-(void)OnFinishedFailed:(NSObject *)sender information:(NSString *)inf exception:(NSException *) ex;
#end
#interface AsyncOperation : NSOperation
#property id<AsyncTaskEventsProtocol> delegate;
#end
Async.m
#import "AsyncOperation.h"
#implementation AsyncOperation
#synthesize delegate;
-(void)main{
NSException * ex;
#try {
NSLog([NSThread isMainThread] ? #"This is the main thread" : #"This is not the main thread");
}
#catch (NSException *exception) {
ex = exception;
}
if(delegate != nil)
{
if(ex == nil){
[delegate OnFinishedSuccess:nil information:#"Data has been sent"];
}
else
{
[delegate OnFinishedFailed:nil information:#"Data has not been sent." exception:ex];
}
}
}
#end
In other class where create a queue
AsyncOperation * op = [[AsyncOperation alloc] init];
op.delegate = self;
NSOperationQueue * queue = [[NSOperationQueue alloc] init];
[queue addOperation:op]
And the handler
-(void)OnFinishedSuccess:(NSObject *)sender information:(NSString *)inf
{
UIAlertView * view = [[UIAlertView alloc] initWithTitle:#"Test" message:#"Message" delegate:nil cancelButtonTitle:#"ok" otherButtonTitles:nil];
[view show];
}
First of all, I'd like to say that I'm really having a bad time trying to configure and use my SQLite DB in a background thread so that the main thread is not blocked.
After I found a little guide somewhere on the Internet, I've decided to go for the FMDB wrapper.
All the methods related to the DB operations are in the same class and this is where I'm getting errors:
I've set the static variables like this:
static FMDatabaseQueue *_queue;
static NSOperationQueue *_writeQueue;
static NSRecursiveLock *_writeQueueLock;
Then in my init method I have:
- (id)init {
self = [super init];
if (self) {
_queue = [FMDatabaseQueue databaseQueueWithPath:[self GetDocumentPath]];
_writeQueue = [NSOperationQueue new];
[_writeQueue setMaxConcurrentOperationCount:1];
_writeQueueLock = [NSRecursiveLock new];
}
return self;
}
And this is the method that gives me the error:
- (void)UpdateTime:(NSString *)idT :(int)userId {
[_writeQueue addOperationWithBlock:^{
[_writeQueueLock lock];
[_queue inDatabase:^(FMDatabase *dbase) {
AppDelegate *deleg = (AppDelegate *)[[UIApplication sharedApplication] delegate];
if (![dbase executeUpdate:#"update orari set orario=datetime(orario, '? minutes') where nome=? and dataid>=? and idutente=?"
withArgumentsInArray:#[[NSNumber numberWithFloat:deleg.diff], deleg.nome, [NSNumber numberWithInt:deleg.idMed], [NSNumber numberWithInt: userId]]]) {
NSLog(#"error");
}
}];
[_writeQueueLock unlock];
}];
[_writeQueue addOperationWithBlock:^{
[_writeQueueLock lock];
[_queue inDatabase:^(FMDatabase *dbase) {
AppDelegate *deleg = (AppDelegate *)[[UIApplication sharedApplication] delegate];
if (![dbase executeUpdate:#"UPDATE orari SET presa=1 where dataid=? and idutente=?"
withArgumentsInArray:#[[NSNumber numberWithInt:deleg.identific], [NSNumber numberWithInt: userId]]]) {
NSLog(#"error");
}
}];
[_writeQueueLock unlock];
}];
[self AddNotification];
}
These are the errors I'm getting:
*** -[NSRecursiveLock dealloc]: lock (<NSRecursiveLock: 0xc38b350> '(null)') deallocated while still in use
DB Error: 5 "database is locked"
*** -[NSRecursiveLock unlock]: lock (<NSRecursiveLock: 0x13378d20> '(null)') unlocked when not locked
From the guide I've read, I supposed that the access to my DB would have been "serialized", and each update would have been added to a queue and executed one at a time.
As you can see, I have a lot to learn about this topic, so any help would really be appreciated.
As I can See you have not created shared instance or singleton instance of this init call
- (id)init {
self = [super init];
if (self) {
_queue = [FMDatabaseQueue databaseQueueWithPath:[self GetDocumentPath]];
_writeQueue = [NSOperationQueue new];
[_writeQueue setMaxConcurrentOperationCount:1];
_writeQueueLock = [NSRecursiveLock new];
}
return self;
}
This should be a singleton call as you will create multiple instance of NSOperationQueue which will make DB vulnurable in a multi-threaded environment, try making it singleton call for your database either using GCD or
static DBManager *sharedInstance = nil;
+(DBManager*)getSharedInstance{
if (!sharedInstance) {
sharedInstance = [[super allocWithZone:NULL]init];
_queue = [FMDatabaseQueue databaseQueueWithPath:[self GetDocumentPath]];
_writeQueue = [NSOperationQueue new];
[_writeQueue setMaxConcurrentOperationCount:1];
_writeQueueLock = [NSRecursiveLock new];
}
return sharedInstance;
}
It might solve your problem and this is first time I am answering here and I am new to the environment so please be little bit forgiving :) Thanks
This question already has answers here:
NSOperationQueue serial FIFO queue
(3 answers)
Closed 9 years ago.
I'm having trouble understanding the way NSOperationQueue works.
Say I have:
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
queue.maxConcurrentOperationCount=1;
[queue addOperationWithBlock:^{
[someObject someSelector];
}];
[queue addOperationWithBlock:^{
[someObject anotherSelector];
}];
The second block is being called even before the first block finishes - the opposite of what I want. I tried using – performSelectorOnMainThread:withObject:waitUntilDone: instead, but the second block is still being executed first - presumably because the block thread is not being completed on the main thread, and so it is not blocked with waitUntilDone. I added a break point inside my someSelector block, and it is reached after a break point inside the second block.
I don't quite get it. Help me!!
If there are explicit dependencies between the operations, then use addDependency:
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
queue.maxConcurrentOperationCount=1;
NSOperation *operation1 = [NSBlockOperation blockOperationWithBlock:^{
[someObject someSelector];
}];
NSOperation *operation2 = [NSBlockOperation blockOperationWithBlock:^{
[someObject anotherSelector];
}];
[operation2 addDependency:operation1];
[queue addOperation:operation1];
[queue addOperation:operation2];
If your operations are doing asynchronous activity, then you should define a custom operation, and only call completeOperation (which will post the isFinished message) when the asynchronous task is done).
// SomeOperation.h
#import <Foundation/Foundation.h>
#interface SomeOperation : NSOperation
#end
and
// SomeOperation.m
#import "SomeOperation.h"
#interface SomeOperation ()
#property (nonatomic, readwrite, getter = isFinished) BOOL finished;
#property (nonatomic, readwrite, getter = isExecuting) BOOL executing;
#end
#implementation SomeOperation
#synthesize finished = _finished;
#synthesize executing = _executing;
#pragma Configure basic operation
- (id)init
{
self = [super init];
if (self) {
_finished = NO;
_executing = NO;
}
return self;
}
- (void)start
{
if ([self isCancelled]) {
self.finished = YES;
return;
}
self.executing = YES;
[self main];
}
- (void)completeOperation
{
self.executing = NO;
self.finished = YES;
}
- (void)main
{
// start some asynchronous operation
// when it's done, call `completeOperation`
}
#pragma mark - Standard NSOperation methods
- (BOOL)isConcurrent
{
return YES;
}
- (void)setExecuting:(BOOL)executing
{
[self willChangeValueForKey:#"isExecuting"];
_executing = executing;
[self didChangeValueForKey:#"isExecuting"];
}
- (void)setFinished:(BOOL)finished
{
[self willChangeValueForKey:#"isFinished"];
_finished = finished;
[self didChangeValueForKey:#"isFinished"];
}
#end
Thus, with the following code, it won't start operation2 until the asynchronous task initiated in main in SomeOperation object, operation1, calls its completeOperation method.
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
queue.maxConcurrentOperationCount=1;
NSOperation *operation1 = [[SomeOperation alloc] init];
NSOperation *operation2 = [NSBlockOperation blockOperationWithBlock:^{
[someObject anotherSelector];
}];
[operation2 addDependency:operation1];
[queue addOperation:operation1];
[queue addOperation:operation2];
I am writing a web-connected application that needs to execute several asynchronous requests to load data needed lower down in the dependency tree.
Fig 1.
For visualization purposes, consider an example with ASIHTTPRequests A,B,C,D,E, and F:
A's url depends on the result of B and C,
and B's url depends on the result of D, E, and F.
B and C can be computed concurrently, and so can D, E, and F.
NSOperationQueue = [(D,E,F),(B,C),A]
Thus far, I have created an NSOperationQueue that contains a dependency tree of ASIHTTPRequests. However, the URLs of the ASIHTTPRequests should depend on the results of the previous operations, and, right now, they do not.
The question: What is the best way to pass the results of the computations performed by multiple NSOperations to the NSOperation that depends on them, and how can I set this up with ASIHTTPRequests?
Thanks in advance,
Julian Ceipek
I would do the following.
To start with, queue:
D, E, F and C
In the requestFinished delegate callback for D, E & F, check if the other all 3 requests have finished, if so send B.
Do the same for the callbacks for B & C - if they've both finished, send A.
You'd need some kind of object that's shared by all requests to store the results / status of earlier requests into.
I ended up solving this problem by wrapping the ASIHTTPRequest in a custom NSOperation object that populated the request in such a way that custom request B contained a pointer to an object in D, E, and F's ASIHTTPRequest UserInfo Dictionary. While I liked #JosephH's solution, I couldn't figure out how to easily generate a dictionary or array with dependency tree intact.
A simplified version of my custom NSOperationObject is provided below; any suggestions are welcome. I used Apple's Concurrency Programming Guide extensively as a reference, but as I have not had any prior experience extending the NSOperation class, I am sure that there is a better way to do this.
#import <Foundation/Foundation.h>
#import "SyncableData.h"
#import "ASIHTTPRequest.h"
#interface PushContentRequest : NSOperation <ASIHTTPRequestDelegate> {
BOOL executing;
BOOL finished;
id <SyncableData> data;
ASIHTTPRequest *request;
NSURL *url;
id <ASIHTTPRequestDelegate> delegate;
}
- (id)initWithDataObject:(id <SyncableData>)theData url:(NSURL *)theUrl delegate:(id <ASIHTTPRequestDelegate>)theDelegate;
#end
#import "PushContentRequest.h"
#implementation PushContentRequest
- (id)initWithDataObject:(id <SyncableData>)theData url:(NSURL *)theUrl delegate:(id <ASIHTTPRequestDelegate>)theDelegate {
if ((self = [super init])) {
executing = NO;
finished = NO;
data = [theData retain];
url = [theUrl retain];
delegate = [theDelegate retain];
}
return self;
}
- (BOOL)isConcurrent {
return YES;
}
- (BOOL)isExecuting {
return executing;
}
- (BOOL)isFinished {
return finished;
}
- (void)start {
if ([self isCancelled]) {
[self willChangeValueForKey:#"isFinished"];
finished = YES;
[self didChangeValueForKey:#"isFinished"];
return;
}
NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
request = [ASIHTTPRequest requestWithURL:url];
NSString *xmlToPost = [[NSString alloc] initWithString: [theData getXMLRep]];
[request appendPostData:[xmlToPost dataUsingEncoding:NSUTF8StringEncoding]];
[request setDelegate:self];
NSDictionary *userInfoDict = [[NSDictionary alloc] initWithObjectsAndKeys:data, #"theData", nil];
[request setUserInfo:userInfoDict];
[userInfoDict release];
[xmlToPost release];
[self willChangeValueForKey:#"isExecuting"];
[request start];
executing = YES;
[self didChangeValueForKey:#"isExecuting"];
[pool release];
}
- (void)dealloc {
[delegate release];
[url release];
[data release];
[super dealloc];
}
- (void)requestFinished:(ASIHTTPRequest *)therequest {
[delegate requestFinished:therequest];
[self willChangeValueForKey:#"isFinished"];
[self willChangeValueForKey:#"isExecuting"];
executing = NO;
finished = YES;
[self didChangeValueForKey:#"isExecuting"];
[self didChangeValueForKey:#"isFinished"];
}
- (void)requestFailed:(ASIHTTPRequest *)therequest {
[delegate requestFailed:therequest];
[self willChangeValueForKey:#"isFinished"];
[self willChangeValueForKey:#"isExecuting"];
executing = NO;
finished = YES;
[self didChangeValueForKey:#"isExecuting"];
[self didChangeValueForKey:#"isFinished"];
}
#end
The delegate of this PushContentRequest currently handles the interpretation of the ASIHTTPRequest's UserInfo Dictionary and the request in my implementation, though I suppose that it might make more sense to do this processing within the PushContentRequest's body.