Using selectors for callbacks? - iphone

I think i should be using selectors (or even a different paradigm), but even after R'ing TFM I can't figure out what i'm supposed to do. It's all related to callbacks from a delegate
I have my main Model object:
#implementation Model
#synthesize myConnection; // which is an NSURLConnection
...
-(void)someMethod{
MyConnectionDelegate *mcd = [[MyConnectionDelegate alloc]initWithCallingObject:self];
myConnection = [[NSURLConnection alloc] initWithRequest:requestForToken delegate:mcd];
...
}
-(void)didGetCalledBack:(NSArray *)resultArray{
NSLog(#"got the callback");
}
and then in my delegate:
#implementation MyConnectionDelegate
#synthesize callingObject; // which is of type id
#synthesize resultArray; // NSArray
-(id)initWithCallingObject:(id)caller{
...//std [self init] block
self.callingObject = caller;
return self;
...
}
-(void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response{
...
}
//and all the other NSURLConnection delegate methods
- (void)connectionDidFinishLoading:(NSURLConnection *)connection{
...
// finish building array of results into self.resultArray
[self.callingObject didGetCalledBack:self.resultArray];
}
So...
1) I think i should be using selectors, or something else rather than hardcoding the fact that the caller (delegator?) needs to implement -didGetCalledBack:
Right? IF so, how? (and why, other than cleanliness)
2) Or is my whole implementation wrong in the way i'm attempting to use a callback from the delegate of the NSURLConnection back to the delegator wrong?
I've looked at the Apple samplecode etc but nothing i've seen ever has anything other than delegate:self. Maybe i should have delegate:self too for the NSURLConnection, but I'm making many connections and if i do delegate:self my delegate methods (like -didReceiveData:) become a big mess of if (connection ==connection1){ type code.
thanks,
richard

I think i should be using selectors, or something else rather than hardcoding the fact that the caller (delegator?) needs to implement -didGetCalledBack: Right? IF so, how? (and why, other than cleanliness)
Nothing wrong with what you are doing. You might want to consider declaring a protocol for a calling object e.g.
#protocol CallingObject <NSObject>
-(void) didGetCallBack: (NSArray*) resultArray;
#end
And then
#interface Model : NSObject <CallingObject> // ...
and
#interface MyConnectionDelegate : NSObject
{
// ...
}
-(id) initWithCallingObject: (id<CallingObject>) calller;
// ...
#end
That will give you some compile time checking that the calling object implements the required method(s).

Maybe i should have delegate:self too for the NSURLConnection, but I'm making many connections and if i do delegate:self my delegate methods (like -didReceiveData:) become a big mess of if (connection ==connection1){ type code.
Then don't use explicit comparisons - use containers or similar abstractions to react to different connections.
E.g. to use the results of connections with different controls, use a dictionary that maps from NSURLConnections to those controls so the following:
if (connection == connection1) [obj1 doStuff];
else if (connection == connection2) [obj2 doStuff];
// ...
becomes:
[[connectionClients objectForKey:connection] doStuff];

Related

Let delegates run in other than main thread

I am generally wondering how to let swift delegates run in a dedicated thread other than the main thread.
More specifically, I am currently using the HueSDK4EDK to establish a connection of my app to a Hue bridge. Part of the process is to define state observers and connection observers (as delegates) to handle incoming events.
private func buildBridge(withBridge bridge : BridgeInfoModel) -> PHSBridge {
return PHSBridge.init(block: { (builder) in
...
builder?.bridgeConnectionObserver = self
builder?.add(self)
}, withAppName: AppBundleName, withDeviceName: DeviceName)
}
The delegates are implemented in extensions, such as the connection observer:
extension HueApiManager : PHSBridgeConnectionObserver {
func bridgeConnection(_ bridgeConnection: PHSBridgeConnection!, handle connectionEvent: PHSBridgeConnectionEvent) {
...
}
}
Since some code within the connection observer may be time intensive, I am wondering if there is a more elegant way than this:
func bridgeConnection(_ bridgeConnection: PHSBridgeConnection!, handle connectionEvent: PHSBridgeConnectionEvent) {
let apiThread = DispatchQueue.global(qos: .utility)
apiThread.async {
...
}
}
Thanks a lot!
Yes:
let apiThread = DispatchQueue.global(qos: .utility)
func bridgeConnection(_ bridgeConnection: PHSBridgeConnection!, handle connectionEvent: PHSBridgeConnectionEvent) {
apiQueue.async {
...
}
}
There's no reason to fetch the queue every time; you can just store it as a property.
I know this isn't what you're thinking of, but it is the best approach. Just perform the dispatch when you need it.
It is possible to build something like what you're actually thinking of in ObjC using forwardInvocation:, but it doesn't translate well into Swift (the underlying machinery can't be implemented in Swift), and I don't recommend it. A trampoline is an object (sometimes an NSProxy object) that can accept any message, do something with it (like move it to another queue), and then redispatch it to another object. The problem is that it's hard to tell the complier "trust me, this will implement every method you need at runtime" (because you can't actually promise that, so it might crash). Even in ObjC, these often wound up being more trouble than they were worth, and the only reason they were worth the trouble in the first place is ObjC didn't always have GCD or blocks, and when blocks were added, the syntax was a headache. In Swift, it's much simpler. Just add the .async call.
Just to show what a trampoline looks like, though, here's what one would look like that uses an operation queue (very similar to a dispatch queue). This is written in pre-ARC ObjC and shouldn't be read as an example of modern ObjC.
#import <Foundation/Foundation.h>
#interface OperationQueueInvocationTrampoline : NSObject
{
#private
NSOperationQueue *myQueue;
id myTarget;
}
- (id)initWithQueue:(NSOperationQueue *)queue;
#end
#import "OperationQueueInvocationTrampoline.h"
#interface OperationQueueInvocationTrampoline ()
#property (nonatomic, readwrite, retain) NSOperationQueue *queue;
#property (nonatomic, readwrite, assign) id target;
#end
#implementation OperationQueueInvocationTrampoline
#synthesize queue = myQueue;
#synthesize target = myTarget;
- (id)initWithQueue:(NSOperationQueue *)queue
{
self = [super init];
if (self != nil)
{
self.queue = queue;
}
return self;
}
- (void)dealloc
{
[myQueue release];
myQueue = nil;
myTarget = nil;
[super dealloc];
}
- (NSMethodSignature *)methodSignatureForSelector:(SEL)selector
{
return [self.target methodSignatureForSelector:selector];
}
- (void)forwardInvocation:(NSInvocation *)invocation
{
[invocation setTarget:self.target];
self.target = nil;
NSOperation *operation = [[[NSInvocationOperation alloc] initWithInvocation:invocation] autorelease];
[self.queue addOperation:operation];
}
- (id)prepareWithInvocationTarget:(id)target
{
self.target = target;
return self;
}
#end
You'd use it like this:
id delegate = [[OperationQueueInvocationTrampoline alloc] initWithTarget:self];
[otherThing setDelegate:delegate];
This worked pretty well back when most protocols were informal (so there was no type-checking here, and you could just pass id), but it's gotten messier and messier to make this compile without warnings, and it would definitely be a headache in Swift today. It's have to save a lot of trouble, and it doesn't.
(Side note: queues are not the same thing as threads. The issue at hand is what queue things are dispatched to, not what thread they run on.)
I don't think this is possible unless that library specifically supports it.
One thing you could do is create a "proxy" delegate class that forwards all delegate method calls on a specific DispatchQueue.
It's not a perfect solution, but at least you can keep your HueApiManager a bit cleaner because you do not need apiThread.async everywhere.
class DelegateProxy: PHSBridgeConnectionObserver {
weak var delegate: PHSBridgeConnectionObserver?
var queue = DispatchQueue.global(qos: .utility)
func bridgeConnection(_ bridgeConnection: PHSBridgeConnection!, handle connectionEvent: PHSBridgeConnectionEvent) {
queue.async {
delegate?.bridgeConnection(bridgeConnection, handle: connectionEvent)
}
}
// Other delegate methods...
}

Passing data between classes / asynchronous requests / iOS

I am converting my application from Syncronous to Asyncronous HTTP requests and have ran into a problem that looks like it will require quite a big reworking of how the application handles its data. Let me try to explain
Previously it was like this:
-Class1, Class2 and Class3 were all subclasses of UIViewController
-Helper class
-Content display class
They do broadly different things but the common trait is their interaction with the helper class. They gather details of a request in a number of different ways from a user and then eventually send a request to the helper class.
When it was done syncronously the helper class would return the data. Each class would then interpret the data (XML files) and pass them on to the Content display class via a segue
So something broadly like this:
Class1:
//Get user input
SomeData *data = [helperclass makerequest];
id vcData = [data process];
[self performSegueWithIdentifier:#"segueIdentifier"];
---
- (void)prepareForSegue:(UIStoryboardSegue *)segue
{
DestinationViewController *destination = (DestinationViewController *)segue.destinationViewController;
destination.data = vcData;
}
Content display class:
- (void)viewDidLoad
{
[super viewDidLoad];
[self.data presentdata];
}
Now it looks like this
I dealt with this problem by first making it work with Class1 with a view to deploying the fix to class2 and class3. So class1 and helper now interact like this
Class1:
//Get user input
SomeData *data = [helperclass makerequestWithSender:self];
id vcData = [data process];
[self performSegueWithIdentifier:#"segueIdentifier"];
---
- (void)prepareForSegue:(UIStoryboardSegue *)segue
{
DestinationViewController *destination = (DestinationViewController *)segue.destinationViewController;
destination.data = vcData;
}
Now the biggest problem I am facing is how to get the data from helperclass back to Class1. I managed to get it to work by doing
(void)makeRequestWithSender:(Class1*)sender
{
[NSURLConnection sendAsynchronousRequest:...
{
[sender sendData:data];
}
}
However, when I have came to roll this out to the other 2 GUI classed which will compose the request I am having difficulty with. My first thought was to set sender:(id) but that fails at the line [sender sendData:data] telling me that id does not have an method sendData: or similar.
Hopefully I wasn't too vague here and you guys can help. If required I will be able to post code snippets but for now can anyone help with a better suggestion about how to structure the code for this request?
You basically want to use the 'observer pattern' or a (maybe) slightly changed setup, so you can use delegation.
Observer pattern
You gain the mechanic via the NSNotificationCenter and NSNotifications. Your 3 different UIViewController subclasses each subscribe to a specific NSNotification and you notify them via posting a notification via the NSNotificationCenter.
The following code is an example of how you can approach the problem in your viewcontroller subclasses:
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
// subscribe to a specific notification
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(doSomethingWithTheData:) name:#"MyDataChangedNotification" object:nil];
}
- (void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
// do not forget to unsubscribe the observer, or you may experience crashes towards a deallocated observer
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
...
- (void)doSomethingWithTheData:(NSNotification *)notification {
// you grab your data our of the notifications userinfo
MyDataObject *myChangedData = [[notification userInfo] objectForKey:#"myChangedDataKey"];
...
}
In your helper class, after the data changed you have to inform the observers, e.g.
-(void)myDataDidChangeHere {
MyDataObject *myChangedData = ...;
// you can add you data to the notification (to later access it in your viewcontrollers)
[[NSNotificationCenter defaultCenter] postNotificationName:#"MyDataChangedNotification" object:nil userInfo:#{#"myChangedDataKey" : myChangedData}];
}
via #protocol
Presuming all your UIViewController subclasses reside in a parent viewcontroller, you can implement a protocol in your helper class and make the parent viewcontroller the delegate. Then the parent viewcontroller may inform the child uiviewcontrollers via passing a message.
Your helper class declaration could look like this (presuming ARC):
#protocol HelperDelegate;
#interface Helper : NSObject
#property (nonatomic, weak) id<HelperDelegate> delegate;
...
#end
#protocol HelperDelegate <NSObject>
-(void)helper:(Helper *)helper dataDidChange:(MyDataObject*)data;
#end
In the helper implementation you would inform the delegate via:
...
if ([self.delegate respondsToSelector:#selector(helper:dataDidChange:)]) {
[self.delegate helper:self dataDidChange:myChangedDataObject];
}
...
Your parent viewcontroller would need to be the delegate of the helper class and implement its protocol; a rough sketch, in the declaration
#interface ParentViewController : UIViewController <HelperDelegate>
and for the implementation in short version
// you alloc init your helper and assign the delegate to self, also of course implement the delegate method
-(void)helper:(Helper *)helper dataDidChange:(MyDataObject*)data {
[self.myCustomChildViewController doSomethingWithTheNewData:data];
}
Besides..
You might ask yourself which method to prefer. Both are viable, the main difference is that via the observer pattern you get more objects to be informed 'at once', whereas a protocol can only have one delegate and that one has to forward the message if needed. There are a lot of discussions around about pros and cons. I'd suggest you read up on them once you made up your mind (sorry ain't got enough reputation to post more than two links, so please search on stackoverflow). If something is unclear, please ask.
Some reasonable ideas here. To elaborate/add my opinion:
First, which object ought to tell the downloader (HelperClass) to begin downloading? My practice is to do this in the view controller that will present the data. So I generally start network requests after a segue (like in viewWillAppear: of the presented vc), not before.
Next, when one class needs to execute code provided for another, I first think about if it makes sense to do it using a block. Very often (not always) blocks make more sense and provide more readable code than, say, delegate, notification, KVO, etc. I think NSURLConnection completion, for example, is better suited to blocks than delegate. (and Apple kind of agrees, having introduced + (void)sendAsynchronousRequest:(NSURLRequest *)request queue:(NSOperationQueue *)queue completionHandler:(void (^)(NSURLResponse*, NSData*, NSError*))handler).
So my pattern for your app would be this:
// Class1.m
// when user has completed providing input
...
// don't do any request yet. just start a segue
[self performSegueWithIdentifier:#"ToContentDisplayClass" sender:self];
...
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
// don't do a request yet, just marshall the data needed for the request
// and send it to the vc who actually cares about the request/result
if ([segue.identifier isEqualToString:#"ToContentDisplayClass"]) {
NSArray *userInput = // collect user input in a collection or custom object
ContentDisplayClass *vc = segue.destinationViewController;
vc.dataNeededForRequest = userInput;
}
...
Then in ContentDisplayClass.m
// this is the class that will present the result, let it make the request
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
HelperClass *helper = [[HelperClass alloc]
initWithDataNeededForRequest:self.dataNeededForRequest];
// helper class forms a request using the data provided from the original vc,
// then...
[helper sendRequestWithCompletion:^(NSURLResponse *response, NSData *data, NSError *error) {
if (!error) {
// interpret data, update view
self.label.text = // string we pulled out of data
} else {
// present an AlertView? dismiss this vc?
}
}];
This depends on HelperClass implementing the block form of NSURLConnection
// HelperClass.m
- (id)initWithDataNeededForRequest:(id)dataNeededForRequest {
// standard init pattern, set properties from the param
}
- (void)sendRequestWithCompletion:(void (^)(NSURLResponse *, NSData *, NSError *))completion {
NSURLRequest *request = ...
// the stuff we need to formulate the request has been setup in init
// use NSURLConnection block method
[NSURLConnection sendAsynchronousRequest:request
queue:[NSOperationQueue mainQueue]
completionHandler:completion];
}
Edit - there are several rationale's for making the VC transition before starting the network request:
1) Build the standard behavior around the success case: unless the app is about testing network connections, the success case is that the request works.
2) The cardinal principal for an app is to be responsive, to do something sensible immediately upon user actions. So when the user does something to initiate the request, an immediate vc transition is good. (what instead? a spinner?). The newly presented UI might even reduce the perceived latency of the request by giving user something new to look at while it runs.
3) What should an app do when a request fails? If the app doesn't really need the request to be useful, then doing nothing is a good option, so you'd want to be on the new vc. More typically, the request is necessary to proceed. The UI should be "responsive" to request failure, too. Typical behavior is to present an alert that offers some form of "retry" or "cancel". For either choice, the place the UI wants to be is on the new vc. Retry is more obvious, because that's where it always is when it tries to fetch the data. For cancel, the way to be "responsive" to cancel is to go back to the old vc, a vc transition back isn't ugly, it's what the user just asked for.
I'm not 100% clear on how you're handling the data now, but to change your data to asynchronous calls, I would use blocks. For instance your current synchronous code like this:
//Get user input
data = [helperclass makerequest]
sendData = [data process]
would turn into something like this:
//Get user input
data = [helperclass makerequestWithSuccess:^{
sendData = [data process]
}];
Using a success block will allow you to wait to process the data until the makerequest was finished.
Your new makerequest function would now look like this:
-(void)makerequestWithSuccess:(void (^)(void))success{
// Put your makerequest code here
// After your makerequest is completed successfully, call:
success();
}
Hope this helps!
I'm not sure that I understood your problem correctly, but if it's sort of:
Start task A asynchronously.
When task A finished successfully, get its result and start task B whose input is result A.
When task B finished successfully, get its result and start task C whose input is result B.
...
When finished successfully, be happy, otherwise print error.
A code example would look like this:
typedef (void)(^completion_block_t)(id result);
-(void) asyncTaskA:(completion_block_t)completionHandler;
-(void) asyncTaskBWithInput:(id)input completion:(completion_block_t)completionHandler;
-(void) asyncTaskCWithInput:(id)input completion:(completion_block_t)completionHandler;
-(void) asyncSomethingWithCompletion:(completion_block_t)completionHandler;
-(void) asyncSomethingWithCompletion:(completion_block_t)completionHandler
{
[self asyncTaskA:^(id resultA){
if (![resultA isKindOfClass:[NSError class]]) {
[self asyncTaskBWithInput:resultA completion:^(id resultB){
if (![resultB isKindOfClass:[NSError class]]) {
[self asyncTaskCWithInput:resultB completion:^(id resultC) {
completionHandler(resultC);
}];
}
else {
completionHandler(resultB); // error;
}
}];
}
else {
completionHandler(resultA); // error
}
}];
}
And you use it like:
[self asyncSomethingWithCompletion:^(id result){
if ([result isKindOfClass:[NSError class]]) {
NSLog(#"ERROR: %#", error);
}
else {
// success!
self.myData = result;
}
}];
The "continuation" and error handling makes this a bit confusing (and Objective-C syntax doesn't really add for more readability).
Another example with a third party library support:
The same logic can be written as this:
-(Promise*) asyncTaskA;
-(Promise*) asyncTaskBWithInput;
-(Promise*) asyncTaskCWithInput;
-(Promise*) asyncSomething;
- (Promise*) asyncSomething
{
return [self asyncTaskA]
.then(id^(id result) {
return [self asyncTaskBWithInput:result];
}, nil)
.then(id^(id result) {
return [self asyncTaskCWithInput:result];
}, nil);
}
And it is used as follows:
[self asyncSomething]
.then(^(id result) {
self.myData = result;
return nil;
},
^id(NSError* error) {
NSLog(#"ERROR: %#", error);
return nil;
});
If you like the latter more, the "Promise" framework is available on GitHub: RXPromise - I'm the author ;)
I'm not sure if what I've done in the past is relevant to your problem, but what I've done is create a download class that has a delegate protocol with a single method: -(void)downloadFinished:(id) data.
Any class that needs to get asynchronous data, creates an instance of this download class, and sets itself as the delegate. I call downloadFinished: from both connection:didFailWithError: and connectionDidFinishLoading:. Then, in the implementation of that method in the delegate, I check whether the data's class is NSData or NSError, and evaluate that data however is appropriate for that class.

Obj-C autorelease network request released before completing

I have a wrapper class that is built around a network request.
lets call it 'WrapperNetworkRequest'
The reason I wrap it is because I process the JSON response once it comes back.
Then using the 'WrapperNetworkRequestDelegate' protocol, return the response to the delegate.
The problem I am having is that when using the 'WrapperNetworkRequest' class as an autoreleased object, it auto-releases before the network request can complete.
I came up with what I think is a horrible idea ([self retain]+[self release] at the appropriate times)
Any ideas on what is a proper way to handle this/What I am doing wrong?
Thanks
Instead of having your WrapperNetworkRequest retain itself in its NSURLConnectionDelegate implementation methods, have its delegate assume ownership. One possible way to do this:
#interface Foo : NSObject <WrapperNetworkRequestDelegate>
#property (nonatomic, retain) WrapperNetworkRequest * wrappedRequest;
#end
#implementation Foo
#synthesize wrappedRequest;
//....
- (void)bar
{
WrapperNetworkRequest * request = [WrapperNetworkRequest aNewAutoreleasedRequest];
request.delegate = self;
self.wrappedRequest = request; // Foo instance assumes ownership
[request goGetData];
}
//....
#end
Retaining self is a perfectly sane idea when you have to extend the object's lifetime. How exactly you do that is another matter: you can do [self retain] or you can add self to a shared retaining array keeping all objects of the given kind. Think NSOperationQueue. After you add an operation object to a queue, you can safely release the operation because by adding it to the queue you've transferred ownership of the operation to the queue.

Passing along Methods - Objective-C

I'm fairly new to Objective-C, and it would be really helpful if someone could help me with the following task:
I have a class TheController that has a method DoTask. The goal of DoTask is to reach out to a MasterUtility (also a custom made class) and get Data, and then send it back when it is done (it uses a thread). Specifically, I want it to send it to dataReceiver in ReportsViewController. I think I need to use #selector or something like that. Here is some code:
#implementation ReportsViewController
-(void)doTask {
MasterUtilities *mu = [[MasterUtilities alloc] init];
[mu getDataAndSendTo:[WHAT GOES HERE]]
}
-(void)dataReceiver:(NSArray *)data {
NSLog(#"data: %#",data);
}
#end
Here is MasterUtilities
#implementation MasterUtilities
- (void)getDataAndSendTo:[WHAT GOES HERE] {
NSArray *data = [[NSArray init] alloc];
....getting data here....
[WHAT GOES HERE? HOW DO I CALL THE METHOD (dataReceiver) IN ReportsViewController?]
}
#end
Can anyone fill in the areas that indicate "WHAT GOES HERE"? Thank you!!
You could use a block:
typedef void (^Callback)(NSArray*);
[somebody doSomethingAndPerform:^(NSArray *data) {
// do whatever you want with the data
}];
- (void) doSomethingAndPerform: (Callback) callback
{
NSArray *data = …;
callback(data);
}
This is very flexible, but maybe too complex. If you want something simpler, you can always just pass the selector and target, just as you thought:
[somebody doSomethingAndCall:#selector(dataReceiver:) on:self];
- (void) doSomethingAndCall: (SEL) selector on: (id) target
{
NSArray *data = …;
[target performSelector:selector withObject:data];
}
Or you can use a protocol:
#protocol DataConsumer
- (void) handleData: (NSArray*) data;
#end
// this class has to implement DataConsumer
[somebody doSomethingAndNotify:self];
- (void) doSomethingAndNotify: (id <DataConsumer>) consumer
{
NSArray *data = …;
[consumer handleData:data];
}
This solution is a bit heawyweight, but the advantage is that the compiler catches some errors for you. There’s also more coupling, but it’s far from being a problem.
You have to use the Target-Action design pattern, which is widely used in Cocoa.
Good luck!
You may wish to reconsider how you approach this problem.
Rather than trying to get your MasterUtilities instance to send the data to your other method, why not have your getData method return the data from the method and then have your ReportsViewController pass the data to dataReciever: ?

Objective-C equivalent of Java's BlockingQueue?

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.
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
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,
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.