I am trying to centralize my app's networking code. Basically, in any of the various places that need information from a server, I create an object serverRequest of my class ServerRequest to get the information. When ServerRequest is done, it needs to send the information back to the calling object. Of course it should work asynchronously -- I don't want my app to grind to a halt while it is waiting.
This return of the information is the tricky part. It seems my options are delegation and notification. As far as I can tell, they both have their issues:
DELEGATION:
I pass myself off as a delegate to the serverRequest object. The problem is that if I am deallocated before the request completes, serverRequest will be messaging a deallocated object and my program will crash. To prevent this, I would have to keep track of all my server requests (there might be more than one) and let them all know in my dealloc method so that I don't get any more messages. All of this is possible, but it sure seems like a pain.
NOTIFICATION:
Seems like a lot of work to pass the information around. I have to add myself as an observer to the notification center, then remove myself when I deallocate. Furthermore, I have to pass into ServerRequest the information of what kind of notification to post when it is done. And I ServerRequest has to shove the received data into an NSDictionary, which I then get it back out of after it is passed.
Both methods ought to work, but they both seem like an awful lot of effort just to have ServerRequest wake up the calling code and pass it an object. I am thinking notification is a bit more flexible, a bit less of a pain, and a bit less likely to cause a crash, but I'm not really happy with either approach. Any feedback would be appreciated. Thanks.
I would go with the list approach. Just have a requestController containing an NSMutableArray that keeps track of all the requests. The advantage of this is that, when your controller gets deallocated, you can do something like [requests makeObjectsPerformSelector: #selector(cancelRequest)] to stop all those requests from hogging the network. It also helps in debugging, because you can actually ask each object what requests it has pending, gauge the performance impact of many pending requests etc. When a request finishes, the request controller can be informed and can remove it from its list with a simple removeObject.
Also, someone has to own your objects. In manually managed memory, ObjC objects can retain themselves, but if you ever want to move to GC, having an array is a much cleaner solution than CFRetaining free-floating objects.
You should not be retaining your delegates. see Checking for a valid delegate object before sending it a message
you can retain the delegate passed in and you would then not be de-allocated until the server request is also finished.
e.g.
#interface ServerRequest : NSObject
{
id delegate;
}
#property (retain) id delegate;
#end
#implementation ServerRequest
#synthesize delegate;
#end
But then you need to avoid releasing the ServerRequest from the other end, or you can make the initiator of the ServerRequest release it when it is itself released and that would take away the problem. To do that
#interface SomeObject : NSObject
{
ServerRequest getsomedata;
}
#property (retain) ServerRequest getsomedata;
#end
- (void)f()
{
[self setGetsomedata:[[ServerRequest alloc] init]];
[[self getsomedata] release]; // take away the refcount from allocating, setting the property will retain
}
I encountered similar problems in the past, but chose a slightly different design (similar to what #uliwitness suggested.
I choose to separate the request (i.e. the actual content) from the delivery system. In your case that would mean the serverRequest holds the content of the request (URL, data, etc.) but doesn't do the actual communication with the server. The delegate for the server request would be a singleton CommLayer class which will actually take care of sending the request, receiving it and notifying delegates about request completion.
So to send a serverRequest you would do call something like [CommLayer sendRequest:serverRequest withDelegate:myDelegate].
Now the CommLayer is the actual class that holds the delegate not the serverRequest, and you can always notify the CommLayer that your class is not valid anymore using something like [CommLayer removeDelegate:myDelegate]
Sure it's more work, but you really get a lot of benefits from this design, to name a few:
Real management of network traffic. You can decide how many open connection you want at once and queue requests.
You can cancel unneeded requests
Related
I have two iOS apps written in Objective-c. For one of the Apps i am using my own logging functionality.I am storing the logs in local Database and sending the log messages to server for debugging and checking performance of app.
In the other App i have used os_log for logging. In most of the places I am using the following line to log events.
os_log(OS_LOG_DEFAULT, "Log message goes here");
Now I want to store these logs to local DB and send it to my server. Is it possible to do this?
I read in some article that we can use method swizzling to log events. Is it possible to use method swizzling here. If not, is there any way to save the os_log messages to local db.
You can define a new macro to call os_log and save method
#define my_log(p1, p2) [MyLog logWithParam:p1 p2:p2]
#interface MyLog : NSObject
+(void)logWithParam:(NSString*)p1 p2:(NSString*)p2;
#end
#implementation MyLog
+(void)logWithParam:(NSString*)p1 p2:(NSString*)p2{
os_log(p1, p2);
[self saveLogToLocalFile];
}
#end
I have a program that progresses as follows. I call a method called getCharacteristics. This method connects to a remote server via a NSURL connection (all networking code done in another file) and when it receives a response it makes a method call back to the original class. This original class then parses the data (xml) and stores its contents as a map.
The problem I'm having is that it appears that somewhere in this transaction another thread is being spawned off.
Here is sample code showing what I'm doing:
#property map
- (void) aMethod
{
[[WebService getSingleton] callWebService: andReportBackTo: self]
Print "Ready to Return"
return map;
}
- (void) methodThatIsReportedBackToAfterWebServiceRecievesResponse
{
//Parse data and store in map
Print "Done Parsing"
}
The problem that I am running into is that map is being returned before it can be fully created. Additionally, "Ready to Return" is being printed before "Done parsing" which suggests to me that there are multiple threads at work. Am I right? If so, would a simple lock be the best way to make it work?
NSURLConnection will execute in another thread if you tell it to execute asynchronously.
In my opinion the best way to deal with this would be to write your own delegate protocol, and use delegation to return your map when the you have downloaded and parsed your data.
You could retrieve your data synchronously using NSURLConnection, but you may force the user to wait for an extended period of time especially if a connection timeout occurs. I would avoid this approach.
I am try to handle multiple NSURLConnection at the same time by using a different delegate for each connection, for each NSURLConnection I create, I create a new delegate object, but for some reason only one NSURLConnection works at a time, any NSURLConnection I try to start whilst one is already running simply do not start, my delegate does not receive any of the method calls connection:didReceiveResponse:, connection:didReceiveData:, connectionDidFinishLoading: or connection:didFailWithError:. Am I misunderstanding something about how NSURLConnection and its delegate works. Reading other posts most people seem to have a single delegate for all there connections and then use some kind of dictionary to get the right object to handle the right connection. Is that the way you have to do it.
What you are describing should work fine. You can have multiple NSURLConnections in flight at once, each with its own delegate.
If you want to know why your case isn't working, you'll probably need to show your code in your question.
I hope that at least answers the general question.
I had async NSURLConnections working just fine when I was using my viewController as the delegate, have all sorts of connections now and I want to do them all through a singleton Connection object. So I'm having Connection create a new delegate object for each connection it makes. Connection is instantiated in the app delegate, but the +(void)send:(Message *) function probably terminates.
My feeling about how this works is that the delegate listeners get put in the run loop (I'm not totally clear on this but I think they're not in separate threads. Shouldn't matter because the delegates allocate their own responseData memory.) and the connectionDidFinishLoading executes just fine, but with an empty responseData. By that I mean I find myself in connectionDidFinishLoading but responseData has zero bytes.
Code creating the app delegate (in the send method) is:
ConnectionDelegate *delegate = [[ConnectionDelegate alloc] init];
NSURLConnection *connection=[[NSURLConnection alloc] initWithRequest:request delegate:delegate];
So my question is two folded:
Is the problem that the send method terminates? The delegate pointer has local scope.
If that's the problem, what can I do to keep the delegate alive?
You should double check your delegate's implementation of connection:didReceiveData:
Remember that you are responsible for collecting the incoming data segments there.
Please read Apple's doc(, especially URL loading system programming guide) and sample code again if any doubt.
Inside ClassA:
-(void)authenticateUser
{
authenticate_Obj = [classB_Obj authenticateMobileUser];
}
Inside ClassB:
-(AuthenticateObj*)authenticateMobileUser
{
[mobile_Obj AuthenticateMobileServer:self action:#selector(Handler:)];
return authenticate_G_Obj;
}
-(void)Handler:(id)value
{
authenticate_G_Obj = (AuthenticateObj*)value;
}
Now once the authenticateMobileUser method of classB returns the controll back to ClassA, we will get the Object authenticate_Obj initiated.
My problem is , when i run the project the authenticate_Obj is NULL... actually when it enters the handler method , the Object is initiallized. but the controlled is returned back to ClassA, without entering into Handler method. I guess this is the problem of Asynchronous execution.
How to make it enter into handler method and then only return the controll to ClassA??
Plz help me..
Thank You.
It sounds like what you think you want to do is to block execution until authentication completes. This might be possible if AuthenticateMobileServer spawns a background thread to work in -- you'd use a synchronisation object such as NSLock -- but it's really a Bad Idea. Why have a background thread at all if you're going to block anyway? And thread synchronisation is notoriously tricky and prone to errors if you don't know what you're doing, which (let's face it) you don't.
Instead, you probably should accept that there will be a period of uncertainty while the authentication takes place, during which your app should keep processing in some intermediate state, and then use a callback to notify you when the authentication is complete and you can then go on with whatever it is you need to do with the authenticated user.
There are a bunch of ways you could do this, and there's not enough detail in the question to say exactly which would be best. But you already seem to be using something very similar within ClassB, so I'd say do the same from ClassA:
Inside ClassA:
-(void)authenticateUser
{
authenticate_Obj = nil;
[classB_Obj authenticateMobileUserAndNotify:self action:#selector(authenticatedObject:)];
// returns more or less immediately, not yet authenticated
}
-(void)authenticatedObject:(YourAuthObjectClass*) authObj
{
authenticate_Obj = authObj;
// do post-authentication stuff here
}
Inside ClassB:
-(void)authenticateMobileUserAndNotify:(id)target action:(SEL)sel
{
// I'm making these ivars for simplicity, there might be other considerations though
callbackTarget = target;
callbackSelector = sel;
[mobile_Obj AuthenticateMobileServer:self action:#selector(Handler:)];
}
-(void)Handler:(id)value
{
authenticate_G_Obj = (AuthenticateObj*)value;
[callbackTarget performSelectorOnMainThread:callbackSelector withObject:authenticate_G_Obj waitUntilDone:NO];
}
Obviously this is just a sketch and not intended to be used as is. And you'll need to consider what goes on in your app while in the waiting state, with authentication in progress but authenticate_Obj still nil. But hopefully you get the idea.
I think you are saying that AuthenticateMobileServer:action: is asynchronous and you want to block until it's finished so you can get the return value. Unfortunately, we can't really tell you without knowing how it works. The main question is does it run the Handler action on the main thread or a secondary thread.
If it runs the action on the main thread, the best strategy is to return immediately from authenticateMobileUser without waiting for the authentication object and disable the UI elements that depend on being authenticated. Then later when you get the authentication object, you should re-enable the UI elements.
If it runs the action on a background thread, the easiest thing is to set up another method similar to Handler (by the way, the naming convention for methods and variables is to start with lower case), which you then invoke from Handler with performSelectorOnMainThread:waitUntilDone:. You can then use the same strategy as outlined above.
Both answers of JeremyP and walkytalky are correct and go at the heart of creating a respondsive UI. The rule of thumb:
If you doing potentially blocking operations such as networking on the main thread, you will get in trouble.
There are at least two reasons:
you are blocking the run loop so it cannot process user events anymore. This will result in a spinning beachball on the mac and a unresponsive UI on both mac and iOS.
If you are on iOS, there is a watchdog going around and checking if your UI is still responding to user events. If you are blocking the UI longer than I think 20s you will be terminated with the error code 0x8badf00d.
So to get this things done which maybe take some time you have to do it on the background thread. As the two answers of JeremyP and walkytalky point out often you get a callback. That is fine but there are in total three ways of messaging:
Delegation
Notifications
Kev-value-observing
All three can be and are used. There are subtle differences between them. One of the most important is that delegation is a 1:1 messaging whereas the other to are a 1:n messaging.
Now that said do not think that you have to use NSThread. Have a look at NSOperation and NSOperationQueue instead. They allow to encapsulate pieces of work in an operation and let them run on a queue in the background. Also if you are using these callbacks with the #selector(methodname:) syntax there is something new: blocks. Often there are equivalent methods which take a block instead of a selector to be executed as a callback.
To finish here is the golden rule:
You may update your model on the background thread, but NEVER update your UI on a background thread.
Check out the WWDC10 videos about these topics. There is a great 2-part talk about networking which explains the concepts in detail.