I'm creating a REST client class for my iPad app. So I created a BOOL method which does the login using an NSURLConnection subclass I created earlier.
This JWURLConnection has block type properties for the finishLoading and failWithError operations.
The Problem is that the URL connection most likely finishes (or fails) AFTER this method is completely executed. A cannot use an extra method to use performSelector:waitUntilDone: too because I have to wait for the connection.
Now I tried using plain C semaphores and an extra thread (so that the semaphore blocks only the RESTClient thread, not the URLConnections one), but I had no success; the method started waiting but the whole connection stuff was frozen, thus there where no NSLogs from the connection.
The JWURLConnection starts it's own thread by itself within the -start method:
- (void)start { dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ [super start]; }); }
Here is the code I tried it with (using semaphores):
- (BOOL)loginWithUsername:(NSString *)uName ansPassword:(NSString *)pWord {
__block BOOL loginSucceeded = NO;
dispatch_semaphore_t sema = dispatch_semaphore_create(0);
JWURLConnection *connection = [JWURLConnection connectionWithPOSTRequestToURL:POSTData:];
[connection setFinished^(NSData *data) {
// validate server response and set login variable
loginSucceeded = YES;
dispatch_semaphore_signal(sema);
}];
[connection setFailed:^(NSError *error) {
loginSucceeded = NO;
NSLog(#"Login failed: %#", [error description]);
dispatch_semaphore_signal(sema);
}];
[connection start];
dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
// do some more stuff like error handling / reporting here
return loginSucceeded;
}
I hope you can lead my the right direction...
The JWURLConnection starts it's own thread by itself within the -start method:
- (void)start { dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ [super start]; }); }
You need to ensure that a NSURLConnection's delegate methods will be scheduled on a NSRunLoop or a NSOperationQueue. While the start method could actually take care of this - the given code and your comment indicate it does not ;) In short, dispatch_async does not guarantee that the underlaying thread has a run loop and a dispatch queue does not even guarantee that the underlaying thread is always the same.
The docs show how to schedule a connection.
I would suggest to schedule the connection on the main thread, and change this to a NSOperationQueue when required.
Your loginWithUsername:andPassword: method will simply return immediately since you call/invoke an asynchronous function/method.
Employing asynchronous patterns is kinda "infectious". Once you started using asynchronous programming style, you cant get "rid of" it unless you use synchronization primitives that block the current thread. I would suggest to keep the async style:
- (void) loginWithUsername:(NSString *)uName
andPassword:(NSString *)pWord
completion:(void(^)(id result))onCompletion;
And later:
[self loginWithUsername:#"Me" andPassword:#"secret" completion:^(id result) {
if ([result != [isKindOfError class]]) {
[self fetchImagesWithURL:url completion: ^(id result) {
...
}];
}
}];
Related
I have a loop function and in it called [NSThread sleepForTimeInterval:2.0];. it mean after 2s, loop function is called. I want when pass new view, this loop function is stop and when back, it is called again.
I use this code to call loop function:
-(void) viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
loop = YES;
delete=NO;
temp = [FileCompletedArray mutableCopy];
NSOperationQueue *queue = [NSOperationQueue new];
operations = [[NSInvocationOperation alloc] initWithTarget:self selector:#selector(updateArray) object:nil];
[queue addOperation:operations];
[operations release];
}
And loop function:
-(void)updateArray{
while (loop)
{
NSLog(#"start loop");
if(loop){
[NSThread sleepForTimeInterval:2.0];
NSLog(#"start send request");
NSURL *url1 = [NSURL URLWithString:#"http://server.com"];
NSMutableURLRequest *afRequest = [httpClient requestWithMethod:#"POST" path:nil parameters:params1] ;
operation= [[AFHTTPRequestOperation alloc] initWithRequest:afRequest];
NSLog(#" request sent");
[operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
NSLog(#"Server response1");
}
failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"error: %#", error);
}
];
[httpClient enqueueHTTPRequestOperation:operation];
}
else
return;
}
}
And viewdisappear()
-(void) viewWillDisappear:(BOOL)animated
{
[super viewWillDisappear:animated];
loop = NO;
delete=NO;
[operations cancel] ;
}
My problem is when pass new view, updateArray still call. It not stop. Do you have suggestion?
You can try it using key value observers. You can implement the changes in following method which will be automatically called as a particular value changes. First you have to set the notification for the ivar that is going to be changed.
[self addObserver:self forKeyPath:#"loop" options:NSKeyValueObservingOptionNew context:nil];
Then you have to implement the changes as per requirement in the following method:
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
There are a couple of issues that leap out at me:
If your intent is to actually cancel the sleepForTimeInterval, I don't believe you can do that. There are other mechanisms (such as timers) that are much better suited for that problem.
As an aside, you are issuing an asynchronous request every two seconds. But you have no assurances that your previous request will have completed in that period of time, though. As a result, you can end up with a backlog of multiple network requests that will continue to run after the view is dismissed.
I would have thought that you'd want to initiate the "wait two seconds" inside the completion block of your asynchronous request, to ensure your requests don't get backlogged behind your "every two seconds" logic. Clearly that won't work with your current while loop unless you make the request synchronous, so you might refactor this code, replacing the while loop with something that performs a single request, and in the completion block, waits two seconds before initiating the next request.
You are checking the state of loop, waiting two seconds, and then issuing your request. So if the view disappeared while it was performing the two second sleep, there's nothing here stopping the request from being issued after you finished sleeping.
At the very least, if you're going to use sleepForTimeInterval, you presumably want to check loop state after you finish sleeping. But, to my first point, it's better to use some cancelable mechanism, such as a timer.
If you're saying that your loop never exits, I'd suggest you check to make sure the appearance methods are getting called like you think they should be.
Personally, I'd be inclined to do this with a timer which can easily be canceled/invalidated:
Define my properties:
#property (nonatomic, strong) NSTimer *timer;
#property (nonatomic, getter = isLooping) BOOL looping;
#property (nonatomic, weak) AFHTTPRequestOperation *operation;
Have my appearance methods set the looping variable and start/stop the scheduled requests as appropriate:
- (void)viewDidAppear:(BOOL)animated
{
self.looping = YES;
[self scheduleRequestIfLooping];
}
- (void)viewWillDisappear:(BOOL)animated
{
self.looping = NO;
[self cancelScheduledRequest];
}
The methods that do the starting and stopping of the scheduled requests would use the NSTimer:
- (void)scheduleRequestIfLooping
{
if ([self isLooping]) {
self.timer = [NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:#selector(initiateRequest:) userInfo:nil repeats:NO];
}
}
- (void)cancelScheduledRequest
{
[self.timer invalidate];
self.timer = nil;
// you might want to cancel any `AFHTTPRequestOperation`
// currently in progress, too
[self.operation cancel];
}
Note, whether the cancel method should cancel both the timer and any current request in progress (if any) is up to you.
Finally, put the scheduling of the next request inside the completion block of the current request.
- (void)initiateRequest:(NSTimer *)timer
{
// create AFHTTPRequestOperation
AFHTTPRequestOperation operation = ...
// now schedule next request _after_ this one, by initiating that request in the completion block
[operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
NSLog(#"Server response1");
[self scheduleRequestIfLooping];
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"error: %#", error);
// not sure if you want to schedule request if last one failed
}];
self.operation = operation; // save this in the property
}
In the above, I'm using the main thread for the timer. If you really want to do it on a background queue, you can, but (a) it seems unnecessary to me as the network requests already happen on a background thread; and (b) if you do this on a background thread, you might want to make sure you're doing the necessary thread-safe handling of your state variables and the like. It just seemed like an unnecessary complication to me.
I have an array of objects to be processed. The objects have a method like below
#interface CustomObject : NSObject
- (void)processWithCompletionBlock:(void (^)(BOOL success))completionBlock;
#end
The processing of each object takes various time and can have different results. And it is known that the processing itself is executing concurrently. To say the truth it would be great to limit the number of concurrent operations because they are pretty intensive.
So I need to enumerate this array of objects and process them. If some object processing fails I need to skip all the rest objects. And of course I need to be notified after all objects will be enumerated and processed.
Should it be solved by the creation of NSOperationQueue and NSOperation subclass? How this class could look to fulfill these requirements? Are there some other elegant approaches?
This is exactly what NSOperation is designed for. Dispatch queues are much lower-level handlers, and you'd have to construct many of the pieces you need for this. You can of course do that (NSOperationQueue is built on top of GCD), but you'd be reinventing NSOperation.
You can handle NSOperation two ways for the most part. If it's simple, you can just create an NSBlockOperation. If it's a bit more complex, you can subclass NSOperation and override the main method to do what you want.
There are several ways to cancel all the other operations. You could have a separate operation queue per group. Then you can easily call cancelAllOperations to shut down everything. Or you could have a separate controller that knows the list of related operations and it could call cancel on them.
Remember that "cancel" just means "don't schedule if it hasn't stared, and set isCancelled if it has." It doesn't abort a running operation. If you want to abort a running operation, the operation needs to periodically check isCancelled.
You typically should limit the number of concurrent operations the queue will run. Use setMaximimumConcurrentOperationCount:.
There are two ways to determine that all the operations are finished. You can make an extra operation (usually a BlockOperation) and use addDependency: to make it depend on all the other operations. That's a nice asynchronous solution. If you can handle a synchronous solution, then you can use waitUntilAllOperationsAreFinished. I typically prefer the former.
Use NSOperationQueue and make your Class an NSOperation
Use this method to queue your work
- (void)addOperations:(NSArray *)ops waitUntilFinished:(BOOL)wait
Add a reference to the operation queue to the NSOperation subclass you create
If an error occurs call
- (void)setSuspended:(BOOL)suspend
on NSOperationQueue
Ok. To help others to understand how this approach can be handled I am sharing my own code.
To limit the number of concurrent threads we can call the -(void)setMaximimumConcurrentOperationCount: method of NSOperationQueue instance.
To iterate objects and provide the completion mechanism we can define the following method:
- (void)enumerateObjects:(NSArray *)objects
{
// define the completion block
NSBlockOperation *completionOperation = [NSBlockOperation blockOperationWithBlock:^{
NSLog(#"Update UI");
}];
// register dependencies
for (CustomObject *obj in objects) {
CustomOperation *operation = [[CustomOperation alloc] initWithCustomObject:obj];
[completionOperation addDependency:operation]; // set dependencies for the completion block
[_operationQueue addOperation:operation];
}
// register completionOperation on main queue to avoid the cancellation
[[NSOperationQueue mainQueue] addOperation:completionOperation];
}
Overwrite the - (void)start method of the NSOperation subclass to start our custom operation:
- (void)start
{
// We need access to the operation queue for canceling other operations if the process fails
_operationQueue = [NSOperationQueue currentQueue];
if ([self isCancelled]) {
// Must move the operation to the finished state if it is canceled.
[self willChangeValueForKey:#"isFinished"];
_finished = YES;
[self didChangeValueForKey:#"isFinished"];
return;
}
[self willChangeValueForKey:#"isExecuting"];
// We do not need thread detaching because our `-(void)processWithCompletionBlock:` method already uses dispatch_async
[self main]; // [NSThread detachNewThreadSelector:#selector(main) toTarget:self withObject:nil];
_executing = YES;
[self didChangeValueForKey:#"isExecuting"];
}
Overwrite the - (void)main method of the NSOperation subclass to process our custom object:
- (void)main
{
#try {
NSLog(#"Processing object %#", _customObject);
[_customObject processWithCompletionBlock:^(BOOL success) {
_processed = success;
if (!success) {
NSLog(#"Cancelling other operations");
[_operationQueue cancelAllOperations];
}
[self completeOperation];
}];
}
#catch (NSException *exception) {
NSLog(#"Exception raised, %#", exception);
}
}
Thanx to #Rob for pointing me out to the missing part.
I've a multi-threading application in which each thread has to do some job, but at a certain point some code needs to be executed serially (like writing into sqlite3 database), so I'm calling that code to be performed on main thread using:
[self performSelectorOnMainThread:#selector(serialJob:) withObject:object waitUntilDone:YES];
and every thing went just fine except that when that code needs some time the user interaction with the application gets disabled until that code has been finished, so is there any way to make another ONE thread that can be run on background and can be called whenever I need it just like the main one so I can replace the previous call with:
[self performSelector:#selector(serialJob:) onThread:REQUIRED_THREAD withObject:object waitUntilDone:YES];
this thread should be some class's static data member to be accessed from all over the code.
any help would be very appreciated, and many thanks in advance...
This is quite easy to do, just spawn your thread and let it run it's runloop using [[NSRunLoop currentRunLoop] run]. That's all that is required to be able to use performSelector:onThread: with a custom thread.
If you are on iOS 4 or newer you should consider using Grand Central Dispatch queues instead of threads though. The GCD APIs are much easier to use and can utilize the system resources much better.
Like Sven mentioned, look into Grand Central Dispatch.
You can create a queue like this:
dispatch_queue_t myQueue = dispatch_queue_create("com.yourcompany.myDataQueue", NULL);
Now you can call blocks on that queue:
dispatch_async(myQueue, ^{
// Your code to write to DB.
});
When you're done, don't forget to release the queue:
dispatch_release(myQueue);
Due to the my question that I need the current thread to be blocked until the database job has been finished, I've tried these two solutions and they worked perfectly. You can either use critical sections or NSOperationQueue and I prefer the first one, here is the code for both of them:
define some class "DatabaseController" and add this code to its implementation:
static NSString * DatabaseLock = nil;
+ (void)initialize {
[super initialize];
DatabaseLock = [[NSString alloc] initWithString:#"Database-Lock"];
}
+ (NSString *)databaseLock {
return DatabaseLock;
}
- (void)writeToDatabase1 {
#synchronized ([DatabaseController databaseLock]) {
// Code that writes to an sqlite3 database goes here...
}
}
- (void)writeToDatabase2 {
#synchronized ([DatabaseController databaseLock]) {
// Code that writes to an sqlite3 database goes here...
}
}
OR to use the NSOperationQueue you can use:
static NSOperationQueue * DatabaseQueue = nil;
+ (void)initialize {
[super initialize];
DatabaseQueue = [[NSOperationQueue alloc] init];
[DatabaseQueue setMaxConcurrentOperationCount:1];
}
+ (NSOperationQueue *)databaseQueue {
return DatabaseQueue;
}
- (void)writeToDatabase {
NSInvocationOperation * operation = [[NSInvocationOperation alloc] initWithTarget:self selector:#selector(FUNCTION_THAT_WRITES_TO_DATABASE) object:nil];
[operation setQueuePriority:NSOperationQueuePriorityHigh];
[[DatabaseController databaseQueue] addOperations:[NSArray arrayWithObject:operation] waitUntilFinished:YES];
[operation release];
}
these two solutions block the current thread until the writing to database is finished which you may consider in most of the cases.
I have some class A. In this class i have a method,
which calls [self performSelectorInBackground:...]. And it starts downloading
some info from internet.
After i tap Home button, then enter the app again, this background method keeps working.
So, if i call this method again, i have bad_access, because background method is already working and i call it twice.
Can i stop performing selector in background of the class A? For example in my applicationDidEnterBackground?
Or can i check, if selector is performing or something?
I found couple things like
[[NSRunLoop currentRunLoop] cancelPerformSelectorsWithTarget:a];
[NSObject cancelPreviousPerformRequestsWithTarget:a selector:#selector(startDownload) object:nil];
But they didn't work for me.
So
my objAppDelegate:
#inteface ObjAppDelegate
{
A *a;
}
#implementation ObjAppDelegate
{
-(void)applicationDidEnterBackground:(UIApplication *)application
{
//or it can be didBecomeActive..
//here. check if background task of class A is running, or just stop it ??
}
}
#implementation A
{
//some timer, or event, etc.
-(void)startDownload
{
[self performSelectorInBackground:#selector(runBackgroundTask) withObject:nil];
}
-(void)runBackgroundTask
{
//some network stuff..
}
}
i did it like this:
threadForDownload = [[NSThread alloc] initWithTarget:self selector:#selector(threadMain:) object:nil];
[threadForDownload start];
[self performSelector:#selector(startDownload) onThread:threadForDownload withObject:nil waitUntilDone:NO];
(void)threadMain:(id)data {
NSAutoreleasePool *pool = [NSAutoreleasePool new];
NSRunLoop *runloop = [NSRunLoop currentRunLoop];
[runloop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];
while (YES) {
[runloop runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
}
[pool release];
}
In my startDownload method i look at activity indicator to check, whether
startDownload is already running..
-(void)startDownload
{
if (![[UIApplication sharedApplication] isNetworkActivityIndicatorVisible]) // flag..
{
//....
}
}
// I make visible networkActivityIndicator every time i start downloading
You can easily create a BOOL instance variable to determine whether background task is active.
BOOL isBackgroundTaskRunning;
Then in runBackgroundTask
if (isBackgroundTaskRunning) {
// already running
return;
}
isBackgroundTaskRunning = TRUE;
...
isBackgroundTaskRunning = FALSE;
Here's what to do:
the background task saves its thread to a property somewhere using NSThread currentThread
the background task periodically checks the thread's isCancelled property.
the main thread sends cancel to the thread object saved by the background thread in step 1.
On exit, the background thread sets the property to nil.
All of the operations on the property used to store the thread in have to be protected by #synchronized or equivalent to prevent the main thread from sending cancel to a deallocated thread object.
The background thread can't do IO operations that block for more than a short period of time. In particular, synchronous downloading of URLs using NSURLConnection is out. If you are using NSURLConnection, you'll want to move to the asynchronous methods and a run loop (arguably, in that case, you can do away with the background thread altogether). If you are using POSIX level IO, use poll() with a timeout.
I don't think that it would be save to force the interruption of a method. What you can do is to change the state of your object and check that state inside your method implementation to early return in case of a cancel (but don't forget to release allocated objects).
This is how NSOperationQueue works. From the documentation:
Cancelling an operation does not immediately force it to stop what it is doing. Although respecting the value returned by the isCancelled is expected of all operations, your code must explicitly check the value returned by this method and abort as needed.
Run the method in a background thread, and keep a record of the NSThread. Then later, you can just end the thread.
I use AsyncSocket on the iPhone to communicate with a server. AsyncSocket is based on run loops but my app is based on threads. That means, I start a new thread to write data and wait until a response is received on the same thread. But I can't call an AsyncSocket's method directly from another thread, I have to use:
[self performSelectorOnMainThread:#selector(writeSomeData:) withObject:dataToWrite waitUntilDone:YES];
It does work, but I cannot get the response from my method "writeSomeData:" called this way, because performSelectorOnMainThread returns nothing.
The method writeSomeData: does something like this:
-(NSData *)writeData:(NSData *)dataToWrite {
dataReceived = nil; // AsyncSocket writes data to this variable
[asyncSocket writeData:dataToWrite withTimeout:-1 tag:0];
[asyncSocket readDataToData:[#"<EOF" dataUsingEncoding:NSUTF8StringEncoding] withTimeout:-1 tag:0];
int counter = 0;
while (dataReceived == nil && counter < 5) {
// runLoop is [NSRunLoop currentRunloop]
[runLoop runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.3]];
++counter;
}
return [dataReceived copy];
}
I could get the response by accessing the class variable "dataReceived", but it's content is changed at this time.
Can anybody tell me how to use AsyncSocket (or generally, how to deal with run loop based classes) on separate threads, so that if I call a method of that class it blocks until the method is executed and a response is received?
Thank you.
Try using GCD(Grand Central Dispatch) to write your data on a separate thread and than come back to the main thread the moment that the data was written. You could do it like this:
// call this on the main thread
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
NSData *data = [self writeData:dataToWrite];
dispatch_async(dispatch_get_main_queue(), ^{
// do something with the data on the main thread.
});
});
I hope something like this can help you...