I have a class that sends webservice calls and delivers the response via delegation.
I now want to add a caching layer in between the views and my webservice class.
This new class should serialize the calls in a way that every call is delayed until the callback of the previous call has finished.
I tried to realize that with GCD:
- (void)callWebserviceFunctionX {
dispatch_group_notify(self.serviceGroup, self.serialQueue, ^{
dispatch_group_enter(self.serviceGroup);
// call webservice
});
}
And in the callback:
-(void)callbackFunctionXWithResponse:(id)response {
// do something
dispatch_group_leave(self.serviceGroup);
}
The idea to group each call and its callback with dispatch_group_enterand dispatch_group_leave and wait for previous groups using dispatch_group_notify.
However, this approach does not seem to work as I intended.
Is there a better way to achieve this?
UPDATE:
I tried every combination of dispatch_group_enter, dispatch_group_leave, dispatch_group_notifyand dispatch_group_async I can think of without success.
I also thought about NSOperationand NSOperationQueue, but - if I understood correctly - that would force me to write a separate class for every webservice call.
Is there another alternative I did not think of yet?
I think you'd be better off using NSOperation, dependencies between them to ensure serialisation and NSOperationQueue to run them.
To avoid creating a NSOperation subclass for each request you could use the builtin NSBlockOperation, you provide a block to each instance and adding dependencies between the NSBlockOperation instances should give you the aimed serialisation.
Hope this helps.
Regards
You could use MKNetworkKit as your Networking solution. This uses NSOperationQueue under the hood and you can use NSOperation dependencies to serialize your request / responses. MKNetworkKit also supports caching of the responses so might help with your caching implementation also.
MKNetworkKit Overview
http://blog.mugunthkumar.com/products/ios-framework-introducing-mknetworkkit/
Someone had a similar problem using MKNetworkKit and GCD
MKNetworkKit and GCD dispatch_group_t
Related
I need to make an asynchronous call so that the second method is only called after the first one is completed.Both methods are network calls. Something like this:
signIn()
getContacts()
I want to make sure that getContacts only gets called after the signIn is completed. FWIW, I can't edit the methods signatures because they are from a Google SDK.
This is what I tried:
let queue = DispatchQueue(label: "com.app.queue")
queue.async {
signIn()
getContacts()
}
Async calls, by their nature, do not run to completion then call the next thing. They return immediately, before the task they were asked to complete has even been scheduled.
You need some method to make the second task wait for the first to complete.
NauralInOva gave one good solution: use a pair of NSOprations and make them depend on each other. You could also put the 2 operations into a serial queue and the second one wouldn't begin until the first is complete.
However, if those calls trigger an async operation on another thread, they may still return and the operation queue may trigger the second operation (the getContacts() call without waiting for signIn() to complete.
Another option is to set up the first function to take a callback:
signIn( callback: {
getContacts()
}
A third option is to design a login object that takes a delegate, and the login object would call a signInComplete method on the delegate once the signIn is complete.
This is such a common thing to do that most networking APIs are built for it "out out of the box." I'd be shocked if the Google API did not have some facility for handling this.
What Google framework are you using? Can you point to the documentation for it?
You're looking for NSOperation. You can use NSOperation to chain operations together using dependencies. Once one operation complete's it can pass it's completion block to the next operation. An example for your use case might be:
// AuthOperation is a subclass of NSOperation
let signInOperation = AuthOperation()
// ContactsOperation is a subclass of NSOperation
let getContactsOperation = ContactsOperation()
getContactsOperation.addDependency(signInOperation)
Ray Wenderlich has a great tutorial covering NSOperation. The tutorial uses a downloading operation to load images asynchronously with a dependency that will filter the photo upon completion of the network request.
There is also a great sample project by Apple that uses operations with asynchronous network requests.
My example on iOS 6:
10 Multi-Part requests need to be sent (in order) to the server.
(so the request forms a queue)
progress should be shown.
if one request fails all following should fail
a request queue should be cancellable
Can AFNetworking help me with this? Or should I try to build something with NSOperations and run the loops myself?
If I need to pass context data between theses requests for example a transaction id produced by the first request. Are there any considerations about thread visibility I need to consider?
AFNetworking can do this. I recommend that you use AFHTTPRequestOperationManager (which itself uses NSOperation), rather than AFHTTPSessionManager. There are ways to do it with AFHTTPSessionManager, but none as elegant as with operations.
Under the hood, here's what you'd do without the manager:
You will use a request serializer to make your NSMutableURLRequest (for example, [AFHTTPRequestSerializer -multipartFormRequestWithMethod:URLString:parameters:constructingBodyWithBlock:error:]; there's a similar JSON request serializer too).
Once you have a URL Request, make the operation with [AFHTTPRequestOperation -initWithRequest:]. You should also set its completion blocks.
Finally, add your operation to [AFHTTPRequestOperationManager manager].operationQueue and start it.
Now that you understand how this is basically all working together, here's a simpler approach:
Subclass AFHTTPRequestOperationManager, optionally setting the requestSerializer if you don't like the default
Override (or copy with new implementation) -POST:parameters:constructingBodyWithBlock:success:failure:] - what you want to do is NOT start your operation right away.
Set the NSOperation dependency chains
start the first one
Now that dispatch_get_current_queue is deprecated in iOS 6, how do I use dispatch_after to execute something in the current queue?
The various links in the comments don't say "it's better not to do it." They say you can't do it. You must either pass the queue you want or dispatch to a known queue. Dispatch queues don't have the concept of "current." Blocks often feed from one queue to another (called "targeting"). By the time you're actually running, the "current" queue is not really meaningful, and relying on it can (and historically did) lead to dead-lock. dispatch_get_current_queue() was never meant for dispatching; it was a debugging method. That's why it was removed (since people treated it as if it meant something meaningful).
If you need that kind of higher-level book-keeping, use an NSOperationQueue which tracks its original queue (and has a simpler queuing model that makes "original queue" much more meaningful).
There are several approaches used in UIKit that are appropriate:
Pass the call-back dispatch_queue as a parameter (this is probably the most common approach in new APIs). See [NSURLConnection setDelegateQueue:] or addObserverForName:object:queue:usingBlock: for examples. Notice that NSURLConnection expects an NSOperationQueue, not a dispatch_queue. Higher-level APIs and all that.
Call back on whatever queue you're on and leave it up to the receiver to deal with it. This is how callbacks have traditionally worked.
Demand that there be a runloop on the calling thread, and schedule your callbacks on the calling runloop. This is how NSURLConnection historically worked before queues.
Always make your callbacks on one of the well-known queues (particularly the main queue) unless told otherwise. I don't know of anywhere that this is done in UIKit, but I've seen it commonly in app code, and is a very easy approach most of the time.
Create a queue manually and dispatch both your calling code and your dispatch_after code onto that. That way you can guarantee that both pieces of code are run from the same queue.
Having to do this is likely because the need of a hack. You can hack around this with another hack:
id block = ^foo() {
[self doSomething];
usleep(delay_in_us);
[self doSomehingOther];
}
Instead of usleep() you might consider to loop in a run loop.
I would not recommend this "approach" though. The better way is to have some method which takes a queue as parameter and a block as parameter, where the block is then executed on the specified queue.
And, by the way, there are ways during a block executes to check whether it runs on a particular queue - respectively on any of its parent queue, provided you have a reference to that queue beforehand: use functions dispatch_queue_set_specific, and dispatch_get_specific.
I have simple class for perform network stuff. It's a singleton and it encapsulates NSOperationQueue inside it. When class' user calls some method to getting data from network, this class creates proper instance of operation class inherited from NSOperation sets up it and adds to queue for performing. Obviously, that performing is making asynchronously in separated threads. After getting data from network NSOperation inherited object notifies my network class and it notifies interested delegates about data getting finished or error.
Question is, how can I make unit tests for checking network class' logic? Also, I don't actually want to test server side behavior. I just want to replace actual async call to server with mock and predefined answers to after test handlers' behavior. I want to check how are my classes work, not server side. I understand commonly logic for testing stuff like that but I little bit confused with using OCMock for it.
Best answer will be code example. I'm using OCUnit and OCMock in my project for unit testing.
Also any articles or github links will be perfect.
If all the asynchronous calls go through an internal method in your class, you can simply create a partial mock on your object and use stub/expect on that method. You can then call the public methods as normal and use the mock to verify that the internal method is called. Using the partial mock stops the real implementation from being called, so no network activity should occur.
As to the other half, the call-backs from the asynchronous operation, simply call the method that would be called directly from your tests, then check that your class does the right thing, either by checking its state with OCUnit asserts, or, if it in turn uses callbacks, with another mock.
So I know this is regarding OCMock... but I thought I'd put it out there that I do this successfully with Kiwi and it looks like this.
it(#"should refresh the client's temporary API key if it is stale before sending the request", ^{
ISLDataServiceAdd *addRequest = [ISLDataServiceAdd withRecord:#{ISLFieldContact_FirstName: #"Jason"} table:ISLTableContact];
[[clientMock shouldEventually] receive:#selector(apiKey) andReturn:VALID_API_KEY];
[[clientMock shouldEventually] receive:#selector(hasTemporaryAPIKey) andReturn:theValue(YES)];
[[clientMock shouldEventually] receive:#selector(isTemporaryAPIKeyStale) andReturn:theValue(YES)];
[[clientMock shouldEventually] receive:#selector(refreshTemporaryAPIKeyAndWait:)];
[addRequest sendRequestUsingClient:clientMock completion:nil failure:nil];
});
sendRequestUsingClient:completion:failure: is an asynchronous call, so by using shouldEventually with Kiwi, it knows that it needs to wait some time (default is 1 second) before those selectors will be called.
I've searched a lot within SO but I can't find the right answer for my question.
Here the problem:
I'm figuring out the right mechanism to send multiple upload requests within an NSOperation subclass. In particular, this class performs two different operation within its main method:
First it retrieves data from a local db
Then it sends the composed data to a web server
Since, these two operations can take time to executed I wrapped them, as already said, within an NSOperation.
To upload data I decided to adopt a sync pattern (I need to sync my application with the number of upload requests that has been successfully submitted to the web server).
To perform a similar upload I'm using ASIHttpRequest in a synch fashion like the following.
for (int i = 0; i < numberOfUploads; i++) {
// 1-grab data here...
// 2-send data here
ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:url];
[request startSynchronous];
NSError *error = [request error];
if (!error) {
int response = [request responseStatusCode];
if(response == 200)
uploads++;
}
}
So, my questions are:
is this a valid solution to upload data to a web server?
is it valid to create ASIHTTPRequest *request within a background thread?
do I have to use an async pattern? If yes, how to?
Note I'm using ASIHttpRequest for sync requests but I think the same pattern could be applied with NSUrlConnection class through
sendSynchronousRequest:returningResponse:error:
Thank you in advance.
To answer your questions directly:
Yes, calling an NSUrlConnection (in your case, ASI wrapper) with a sync call is valid in an NSOperation.
You can create NSUrlConnections in background threads, but there are a couple of things to remember here:
If you use it on a background thread, you have to either call the synchronous methods, or you have to keep the thread alive yourself. Using the async in an NSOperation is explained pretty well here: How do I do an Asychronous NSURLConnection inside an NSOperation? I have used this pattern and it works well.
NSUrlConnnection Delegate callbacks call back to the thread that the NSUrlConnection was created on. Just something to remember.
You do not have to use the async pattern, but you can. The async pattern provides more flexibility. For example, if you need to cancel the operation, you have the ability to cancel the NSUrlConnection request with the async pattern. With the sync pattern, you are forced to let it run (unless you kill the thread explicitly).
One note: I would reconsider using ASI as it is no longer supported. AFNetworking seems to be the most popular replacement, though I chose to start using NSUrlConnection directly.
whenever you want to call using ASIHTTPRequest in background thread you have to call synchronous call only, because threads will close the request as soon they are sent,and about your questions
1, this is valid solution but call using synchronours only
2. you can call ASIHTTPRequest in background thread or you can call it using
nsurlconnection sendSynchronousRequest:returningResponse:error:
async pattern will not work for background thread you have to use them on main thread only .
hope it helps .