views:

647

answers:

3

I'm just getting into iPhone development after many years doing Java development. I'm looking for the Objective-C equivalent to Java's BlockingQueue. Is there something like that?

In case I'm going about things the wrong way, here's what I'm trying to achieve:

I want to display, one at a time, chunks of data pulled from a network server. To keep the user from noticing network lag, I want to always have a few chunks of data pre-fetched. In Java-land, I'd use a thread-safe queue between my fetching thread and my display thread.

+1  A: 

I don't think such a thing exists natively - you're probably going to have to write your own class that maintains a queue of network objects. Your header might look something like:

@interface ObjcBlockingQueue : NSObject {
    // The objects that you're holding onto
    NSArray *objects;
}

@property(nonatomic,retain) NSArray *objects;

- (ServerData *)getNextChunk;

Then you can implement getNextChunk to pop and return the top object off your objects array, and if [objects count] is less than a certain value, launch a thread to fetch some more objects (probably using NSURLConnection with ObjcBlockingQueue being the delegate). You can also have that thread/connection launched inside an overridden init method to prefill the queue.

You might also want to think about adding a

- (BOOL)isChunkAvailable;

method that will let your display thread know whether it can display something new right away or if it has to display a loading message. Depending on where you're displaying the data and how your app is structured, it may also be worth your while to make ObjcBlockingQueue a singleton class.

Tim
Fantastic. Thanks for such a speedy answer. What I'm not quite getting is how thread safety would work. In Java, basic data containers like ArrayList are not thread-safe, and since the NsMutableArray docs don't say otherwise, I assume it is also not thread safe. Given that, would you just do an @syncronized on the NSMutableArray?
William Pietri
This is a good answer, but it requires a fairly good understanding of threading in C, which is different from Java in some ways. Before you write your own class to re-implement something from Java, I suggest you consider using an NSOperation subclass and an NSOperation queue. You can get notified when the NSOperation completes, at which point it will be safe to access some property (like a data property which contains the fully-loaded data for that operation).
Jason Coco
@William: That's certainly one possibility. You could also manage your own locks on objects, if you feel like it. Another option is, like @Jason said, to use NSOperation subclasses and queues. Take a look at "The Objective-C Programming Language: Threading" (http://developer.apple.com/documentation/Cocoa/Conceptual/ObjectiveC/Articles/ocThreading.html) and the Threading Programming Guide (http://developer.apple.com/documentation/Cocoa/Conceptual/Multithreading/Introduction/Introduction.html#//apple_ref/doc/uid/10000057i-CH1-SW1)
Tim
+2  A: 

You can simply spin off an NSOperation and post a notification when the data has come back (finished loading). Take a look at Dave Dribin's blog post on concurrency with NSOperation that shows how to encapsulate an NSURLConnection session:

http://www.dribin.org/dave/blog/archives/2009/05/05/concurrent_operations/

If you are not talking about accessing a web service or site where NSURLConnection is appropriate, you can instead use Cocoa Async Socket if it's straight TCP/IP or UDP:

http://code.google.com/p/cocoaasyncsocket/

Best Regards,

Matt Long
Thanks for posting the link to Dave Dribin's Blog. That saved my life. I'd been running idle loops in for async methods to stay in background threads.
Ben
+2  A: 

Here's an implementation of a blocking queue with a queue and dequeue method. The expectation would be that one thread goes into a loop calling dequeueUnitOfWorkWaitingUntilDate: and processes units of work while a second thread is calling queueUnitOfWork:.

@interface MyBlockingQueue : NSObject {
    NSMutableArray *queue;
    NSConditionLock *queueLock;
}
- (id)dequeueUnitOfWorkWaitingUntilDate:(NSDate *)timeoutData;
- (void)queueUnitOfWork:(id)unitOfWork;
@end

enum {
    kNoWorkQueued = 0,
    kWorkQueued = 1
}

@implementation MyBlockingQueue
- (id)init {
    if ((self = [super init])) {
        queueLock = [[NSConditionLock alloc] initWithCondition:kNoWorkQueued];
        workItems = [[NSMutableArray alloc] init];
    }
    return self;
}

- (void)dealloc {
    [queueLock release];
    [workItems release];
    [super dealloc];
}

- (id)dequeueUnitOfWorkWaitingUntilDate:(NSDate *)timeoutDate {
    id unitOfWork = nil;
    if ([queueLock lockWhenCondition:kWorkQueued beforeDate:timeoutDate]) {
        unitOfWork = [[[queue objectAtIndex:0] retain] autorelease];
        [queue removeObjectAtIndex:0];
        [queueLock unlockWithCondition:([workItems count] ? kWorkQueued : kNoWorkQueued)];
    }
    return unitOfWork;
}

- (void)queueUnitOfWork:(id)unitOfWork {
    [queueLock lock];
    [queue addObject:unitOfWork];
    [queueLock unlockWithCondition:kWorkQueued];
}
@end
Jon Hess
You have to check the return value of lockWhenCondition:beforeDate: returns, otherwise if the time expired without acquiring the lock, you will almost certainly cause an IndexOutOfBounds exception on the queue. Also, in queueUnitOfWork: theConditionLock should be queueLock, no?
Jason Coco
In fact, you're also gonna throw an exception when you unlock since you never actually acquired the lock in the first place.
Jason Coco
Ah of course, my oversight. Thanks for pointing that out. Fixed.
Jon Hess