Dropbox DBRestClient/DBRestClientDelegate execute on a thread - ios

I am using the dropbox API with DBRESTClient and DBRestClientDelegate in Dropbox-iOS-SDK
My issue is that I need these to run on a background thread.
When I call the [restClient loadMetadata] I do not get a response to - restClient:loadedMetadata: unless I begin call from the main thread.
Is there a simple workaround/library that I can use which will allow a delegate response on a thread ? I have tried Dropblocks which uses blocks but no luck.

I noticed "Make sure you call DBRestClient methods from the main thread or a thread that has a run loop. Otherwise the delegate methods won't be called." on the Dropbox Page
https://www.dropbox.com/developers-v1/core/start/ios
I used a runloop and it functions on a thread now
This is using a completion block Similarly you could use the delegate to set the flag to NO
self.inQuery = YES;
[self loadMetaDataWithPath:rootFolder mediaType:#(mediaType) completion:^(BOOL complete) {
self.inQuery = NO;
}];
#autoreleasepool {
do {
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
DDLogError(#"currentRunLoop %#",[NSDate date]);
}while(self.inQuery);
}

Related

How do I use an NSRunLoop on an NSOperationQueue?

I have an app which communicates with an ExternalAccessory over Bluetooth, there is some delay in responses so I want the IO to happen on a background thread.
I setup an NSOperationQueue for single-threaded operation to enqueue my requests:
self.sessionQueue = [NSOperationQueue new];
self.sessionQueue.maxConcurrentOperationCount = 1;
If I schedule reads and writes to the EAAccessory streams from that queue, my app crashes because data from the socket can't be delivered without an NSRunLoop on the thread that the queue is using. Immediately after initializing the queue, I create a run loop with an empty NSMachPort to keep it running and start it:
[self.sessionQueue addOperationWithBlock:^{
NSRunLoop* queueLoop = [NSRunLoop currentRunLoop];
[queueLoop addPort:[NSMachPort port] forMode:NSRunLoopCommonModes];
[queueLoop run]; // <- this blocks
}];
This blocks the queue as the run loop will never exit, but I'm not sure how to correctly manage the run loop so that I can successfully read from the accessory streams.
You shouldn't try to run a run loop inside an NSOperation. Grand Central Dispatch owns the thread on which the operation is running. You should start your own thread and use its run loop for your session streams.
However, you need to be aware that NSRunLoop is not generally thread safe, but CFRunLoop is. This means that you need to drop down to the CFRunLoop level when you want to run a block on your session-handling thread.
Also, the only way to get a reference to a background thread's run loop is to run something on that background thread. So step one is to create your own NSThread subclass that exports its own run loop:
typedef void (^MyThreadStartCallback)(CFRunLoopRef runLoop);
#interface MyThread: NSThread
/// After I'm started, I dispatch to the main queue to call `callback`,
// passing my runloop. Then I destroy my reference to `callback`.
- (instancetype)initWithCallback:(MyThreadStartCallback)callback;
#end
#implementation MyThread {
MyThreadStartCallback _callback;
}
- (instancetype)initWithCallback:(MyThreadStartCallback)callback {
if (self = [super init]) {
_callback = callback;
}
return self;
}
- (void)main {
CFRunLoopRef runLoop = CFRunLoopGetCurrent();
dispatch_async(dispatch_get_main_queue(), ^{
_callback(runLoop);
});
_callback = nil;
CFRunLoopRun();
}
#end
Now you can create an instance of MyThread, passing in a callback. When you start MyThread, it will make that callback run back on the main thread, and it will pass its own (MyThread's) run loop to the callback. So you can use a MyThread as your session-handling thread, like this:
#implementation Thing {
CFRunLoopRef _sessionRunLoop;
}
- (void)scheduleStreamsOfSession:(EASession *)session {
MyThread *thread = [[MyThread alloc] initWithCallback:^(CFRunLoopRef runLoop) {
// Here I'm on the main thread, but the session-handling thread has
// started running and its run loop is `runLoop`.
[self scheduleStreamsOfSession:session inRunLoop:runLoop];
}];
[thread start];
}
- (void)scheduleStreamsOfSession:(EASession *)session inRunLoop:(CFRunLoopRef)runLoop {
// Here I'm on the main thread. I'll save away the session-handling run loop
// so I can run more blocks on it later, perhaps to queue data for writing
// to the output stream.
_sessionRunLoop = runLoop;
NSInputStream *inputStream = session.inputStream;
NSOutputStream *outputStream = session.outputStream;
// Here I'm on the main thread, where it's not safe to use the
// session-handling thread's NSRunLoop, so I'll send a block to
// the session-handling thread.
CFRunLoopPerformBlock(runLoop, kCFRunLoopCommonModes, ^{
// Here I'm on the session-handling thread, where it's safe to
// use NSRunLoop to schedule the streams.
NSRunLoop *currentRunLoop = [NSRunLoop currentRunLoop];
[inputStream scheduleInRunLoop:currentRunLoop forMode:NSRunLoopCommonModes];
[outputStream scheduleInRunLoop:currentRunLoop forMode:NSRunLoopCommonModes];
});
// CFRunLoopPerformBlock does **not** wake up the run loop. Since I want
// to make sure the block runs as soon as possible, I have to wake up the
// run loop manually:
CFRunLoopWakeUp(_sessionRunLoop);
}
#end
Any thread can have an NSRunLoop created for it if needed, the main thread of any Cocoa or AppKit application has one running by default and any secondary threads must run them programmatically. If you were spawning an NSThread the thread body would be responsible for starting the NSRunLoop but an NSOperationQueue creates it's own thread or threads and dispatches operations to them.
When using an API which expects an NSRunLoop to deliver events to and from a background thread, either of your own creation, or one that libdispatch has created, you are responsible for making sure the NSRunLoop is run. Typically you will want to run the loop until some condition is met in each of your NSBlockOperation tasks, I wrote a category on NSRunLoop which simplifies this:
#import <Foundation/Foundation.h>
#interface NSRunLoop (Conditional)
-(BOOL)runWhileCondition:(BOOL *)condition inMode:(NSString *)mode inIntervals:(NSTimeInterval) quantum;
#end
#pragma mark -
#implementation NSRunLoop (Conditional)
-(BOOL)runWhileCondition:(BOOL *)condition inMode:(NSString *)mode inIntervals:(NSTimeInterval) quantum {
BOOL didRun = NO;
BOOL shouldRun = YES;
NSPort *dummyPort = [NSMachPort port];
[self addPort:dummyPort forMode:NSDefaultRunLoopMode];
while (shouldRun) {
#autoreleasepool {
didRun = [self runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:quantum]];
shouldRun = (didRun ? *condition : NO);
}
}
[self removePort:dummyPort forMode:NSDefaultRunLoopMode];
return didRun;
}
#end
With this condition you can schedule an NSBlockOperation which will start the run loop and run until the specified condition is NO:
__block BOOL streamOperationInProgress = YES;
[self.sessionQueue addOperationWithBlock:^{
NSRunLoop *queueLoop = [NSRunLoop currentRunLoop];
NSStream *someStream = // from somewhere...
[someStream setDelegate:self];
[someStream scheduleInRunLoop:queueLoop forMode:NSDefaultRunLoopMode]:
// the delegate implementation of stream:handleEvent:
// sets streamOperationInProgress = NO;
[queueLoop
runWhileCondition:&streamOperationInProgress
inMode:NSDefaultRunLoopMode
inIntervals:0.001];
}];
The wrinkle in the above example is putting the BOOL someplace that the delegate can set it to NO when the operation is complete.
Here's a gist of the NSRunLoop+Condition category.

UILabel text is not updating on iPad

We have a delegate method which will be called for approximately 20 times in a second. In the delegate method we are updating our UILabel which represents the counter like below mentioned code:
- (void) counterUpdated:(NSString *) value
{
lblCounter.text = [NSString stringWithString:value];
// [[NSRunLoop mainRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.5]];
// [lblCounter setNeedDisplay];
}
I read similar problems in stack overflow and I implemented the solutions over there and I checked with keeping [lblCounter setNeedDisplay]; method and [[NSRunLoop mainRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.5]]; after updating lblCounter, but it is not working well as expected.
Any pointers?
Try using Grand Central Dispatch in order to run this in the main thread:
dispatch_async(dispatch_get_main_queue(), ^{
lblCounter.text = value;
});

BAD EXC in [[NSRunLoop currentRunLoop] runMode:* beforeDate:*];

I'm trying to do some stress testing on my app with a unit test, and I'm running into some problems. Below is my code:
//Stress test api and core data
__block BOOL done = NO;
for (int i = 0; i < 100 ; i++) {
DLog(#"in here");
[viewController createList:testList
success:^(Lists *list) {
DLog(#"in success block : %d", i);
STAssertNotNil(list, #"list is not nil");
done = (i == 99);
}
failure:^(NSError *error) {
DLog(#"in fail block");
}
];
}
#autoreleasepool {
while (!done) {
// This executes another run loop.
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
}
}
The problem is, after several iterations, I get a bad access error on the line
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
I've already put the while loop in the #autorelease because of this post. I'm using ARC on the project, so I don't know if that's contributing to the problem..? I need to use the NSRunLoop to force the unit test to wait for the block to complete.
Has anyone run into this problem?
In a similar situation, I did it differently and it worked fine. What I did was in the main test, used dispatch_group_async to the normal queue, and then in the main test waited for the group to finish. That works just fine and you don't mess with the unit test's runloop.
if for some reason the above is not acceptable, then try not using any autorelease pool to see if that works If it does, then move it into the while look. With ARC the need for an autorelease pool is greatly diminished. You can observe memory usage using Instruements too.

What does NSRunLoop do?

I read many posts about NSRunLoop, like this, this, this. But can't figure out what NSRunLoop actually does
What I usually see is a worker thread
wthread = [[NSThread alloc] initWithTarget:self selector:#selector(threadProc) object:nil];
[wthread start];
with a NSRunLoop inside it
- (void)threadProc
{
NSAutoreleasePool* pool1 = [[NSAutoreleasePool alloc] init];
BOOL isStopped = NO;
NSRunLoop *runloop = [NSRunLoop currentRunLoop];
[runloop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];
while (!isStopped)
{
{
NSAutoreleasePool* pool2 = [[NSAutoreleasePool alloc] init];
[runloop runMode:NSDefaultRunLoopMode
beforeDate:[NSDate distantFuture]];
[pool2 release];
}
}
[pool1 release];
}
And the main thread passes some work to this wthread
[self performSelector:#selector(someWork:) onThread:wthread withObject:nil waitUntilDone:NO];
In term of passing work from the main thread to the worker thread, I see many people do this. Why need NSRunLoop here ? What does it do ?
I read that NSRunLoop is used to manage events, why is there nothing except calling runMode inside threadProc ?
The example you've shown is a Cocoa idiom for creating a thread that will continue to run after the method -threadProc exits. Why?
Because:
the NSRunLoop instance you've created has at least one input source ([NSMachPort port])
you've explicitly started the run loop with runMode:beforeDate
Without adding an input source and explicitly starting the run loop, the thread would terminate.
Parenthetically, although run loops are still vital for managing events and certain asynchronous tasks, I would not view NSThread as the default way of architecting most asynchronous work in a Cocoa app nowadays. GCD is a far cleaner way of encapsulating background work.
EDIT:
Submitting work to a serial queue in GCD:
#interface Foo : NSObject
#end
#implementation Foo {
dispatch_queue_t _someWorkerQueue;
}
- (id)init {
self = [super init];
if( !self ) return nil;
_someWorkerQueue = dispatch_queue_create("com.company.MyWorkerQueue", 0);
return self;
}
- (void)performJob {
dispatch_async(_someWorkerQueue, ^{
//do some work asynchronously here
});
dispatch_async(_someWorkerQueue, ^{
//more asynchronous work here
});
}
#end
Much goes on behind the scene. The reason for this is it provides a way for the thread to stop executing when there are no work items. If you ever used a real time OS, tasks need a place to give up the processor so others can run.
What is not well documented is that when you send performSelector:onThread:..., it's the run loop that queues the message and the wakes up to let the thread process it. If you add log messages to the while loop you can see this happen.
For the really curious there is sample code on github you con get to play around with run loops - add a comment and I'll list a few.

Put a boolean False inside a runloop while

i'm trying to get directory file listing from my ftp server using Chilkat library.
In this case, i want to animating a UIActivityIndicatorView when the process is running. But the problem is, the UIActivityIndicatorView never start to animate. The code I use is :
[self.activityIndicator startAnimating];
[selfgetListFromPath:ftpPath withConnection:ftpConnect];
[self.activityIndicator stopAnimating];
activityIndicator is an object of UIActivityIndicatorView, ftpPath is a NSString of my file path in FTP server, and getListFromPath is the method for getting list from FTP server using Chilkat algorithm, ftpConnect is an object of FTP Connection Class.
I was try to use NSRunLoop before called getListFromPath function, so I changed my code into :
[self.activityIndicator startAnimating];
BOOL waitingOnProcessing = YES;
NSRunLoop *currentRunLoop = [NSRunLoop currentRunLoop];
while (waitingOnProcessing && [currentRunLoop runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]]) {
}
[self getListFromPath:ftpPath withConnection:ftpConnect];
[self.activityIndicator stopAnimating];
this make the activityIndicator animating, but the getListFromPath never fired. After a trial, i choosed to changed again my code into :
[self.activityIndicator startAnimating];
BOOL waitingOnProcessing = YES;
NSRunLoop *currentRunLoop = [NSRunLoop currentRunLoop];
while (waitingOnProcessing && [currentRunLoop runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]]) {
waitingOnProcessing = NO;
}
[self getListFromPath:ftpPath withConnection:ftpConnect];
[self.activityIndicator stopAnimating];
it make the activityIndicator animating, and also fired the getListFromPath function. But i'm doubt with this code, am i right with this code? or maybe there is a bad practice for using NSRunLoop?
can somebody tell me
Thank you
This code runs as in a "black box" :
[self.activityIndicator startAnimating];
[self getListFromPath:ftpPath];
[self.activityIndicator stopAnimating];
So UI updates occur when your method returns, and you won't see the update.
What you need to do is startAnimating your activity indicator, then start your getListFromPath in another thread. When that method terminates, you call back your main thread telling it to stopAnimating the indicator.
Use this methods:
[NSObject performSelectorInBackground:withObject:]
to start your getListFromPath thread, then when it's done, call
[NSObject performSelectorOnMainThread:withObject:waitUntilDone:]
to give the control back to the main thread, which will stop the spinner animation.
I dont know the Chilkat library, but it must have some way of telling you that you receive an answer from your ftp server. When you got it, you should use a NSNotification or a protocol to tell your view controller that you got an answer. When that happen you stop the spinner.

Resources