CKModifyRecordsOperation modifyRecordsCompletionBlock not being called - ios

I'm using CKModifyRecordsOperation to save a set of records and if I have internet connection all works well and completion block is being called. But when I don't have connection the completion block is not being called and I don't get any information that my operations failed.
I'm using the following code in completion block
modifyOperations.modifyRecordsCompletionBlock = ^(NSArray *savedRecords, NSArray *deletedRecordIDs, NSError *error)
{
if(error){
NSLog(#"Error: %#", error.localizedDescription);
}
item.creatorRecordId = record.recordID;
};
and then I'm performing operation using
[self.publicDB addOperation:modifyOperations];
Any ideas how can I get an information if the operation failed for example in the case where there is no internet connection?

CloudKit operations have their qualityOfService property set to NSQualityOfServiceUtility by default.
Operations that use NSQualityOfServiceUtility or NSQualityOfServiceBackground may be marked as using discretionary network requests. The system can hold discretionary network requests if network connectivity is poor, so you might not get a response from the server until conditions improve and the system sends the request.
If you'd like your request to be sent immediately in all cases, set CKOperation.qualityOfService to NSQualityOfServiceUserInitiated or NSQualityOfServiceUserInteractive.

Related

Can AVContentKeySession makeStreamingContentKeyRequestDataForApp safely be forced to synchronous?

I have an app that streams audio protected by FairPlay DRM. It originally shipped using AVAssetResourceLoaderDelegate to deliver FairPlay keys, but now I'm updating it to use AVContentKeySession for iOS 11.2 and later. Side note: if you're trying to do this and are frustrated at the lack of documentation, there's sample code at the "FairPlay Streaming Server SDK (4.2.0)" link here.
Each of my audio products is broken into many tracks. When I open an audio product, I queue up more than one track, via AVQueuePlayer. Each of those tracks generates a request for a FairPlay key. In the case when there is no persistent key already downloaded, each of those requests goes to the key server, downloads a key, generates a persistent key, and saves it. Each track has the same key, so they all end up with the same persistent key data, and each one overwrites the last one to finish.
Because the cost of my key servers is dependent on the number of key requests I make, I'd like to have only the first request actually hit the key server, and subsequent requests use the persistent key. But the method used to get the SPC data to pass up to the key server, makeStreamingContentKeyRequestDataForApp, uses an async completion block. The equivalent method on AVAssetResourceLoadingRequest is synchronous.
My question: is it safe to force this call to be synchronous using a semaphore? Like this:
-(void)handleOnlineRequest:(AVContentKeyRequest *)req
NSData *appCert = [self _getAppCertData];
NSData *assetId = [self _kidFromRequest:req];
dispatch_semaphore_t sema = dispatch_semaphore_create(0);
[req makeStreamingContentKeyRequestDataForApp:appCert
contentIdentifier:assetId
options:nil
completion:^
NSData *contentKeyRequestData, NSError *error)
{
//request key data and respond to request
dispatch_semaphore_signal(sema);
}];
dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
dispatch_release(semaphore);
The effect is especially pronounced when downloading audio that has not been streamed before. Download speeds for audio using AVAssetDownloadTask are very slow, so I initiate many at once, and each one generates a key request.

AFNetworking Offline Queue

Currently I am queueing a simple offline request using AFHTTPRequestOperationManager and it doesn't seem to work in the desired manner:
Here is the responsible code and below are different execution patterns:
#interface ViewController ()
{
AFHTTPRequestOperationManager *manager;
}
#end
#implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
manager = [AFHTTPRequestOperationManager manager];
NSOperationQueue *operationQueue = manager.operationQueue;
[[AFNetworkReachabilityManager sharedManager] startMonitoring];
[[AFNetworkReachabilityManager sharedManager] setReachabilityStatusChangeBlock:^(AFNetworkReachabilityStatus status){
NSLog(#"Reachability: %#", AFStringFromNetworkReachabilityStatus(status));
switch (status) {
case AFNetworkReachabilityStatusReachableViaWWAN:
case AFNetworkReachabilityStatusReachableViaWiFi:
NSLog(#"Operation: %#", operationQueue.operations);
[operationQueue setSuspended:NO];
NSLog(#"ONLINE");
break;
case AFNetworkReachabilityStatusNotReachable:
default:
NSLog(#"Operation: %#", operationQueue.operations);
[operationQueue setSuspended:YES];
NSLog(#"OFFLINE");
break;
}
}];
manager.responseSerializer = [AFHTTPResponseSerializer serializer];
[manager GET:#"http://www.google.com"
parameters:nil
success:^(AFHTTPRequestOperation *operation, id response){
NSLog(#"success");
}
failure:^(AFHTTPRequestOperation *operation, NSError *failure){
NSLog(#"failure");
}];
}
Pattern 1:
Device on AirPlane mode
Run
Console output:
2015-03-21 16:03:54.486 OfflineSupport[928:227748] Reachability: Not Reachable
2015-03-21 16:03:54.494 OfflineSupport[928:227748] Operation: (
"<AFHTTPRequestOperation: 0x1701d0c20, state: isExecuting, cancelled: NO request: <NSMutableURLRequest: 0x170014ab0> { URL: http://www.google.com }, response: (null)>"
)
2015-03-21 16:03:54.494 OfflineSupport[928:227748] OFFLINE
2015-03-21 16:03:54.544 OfflineSupport[928:227748] failure
Wifi Activated
Console output contd.:
2015-03-21 16:04:05.594 OfflineSupport[928:227748] Reachability: Reachable via WiFi
2015-03-21 16:04:05.595 OfflineSupport[928:227748] Operation: (
)
2015-03-21 16:04:05.595 OfflineSupport[928:227748] ONLINE
Pattern 2:
Wifi Active
Run
Console output:
2015-03-21 16:05:43.818 OfflineSupport[934:228478] Reachability: Reachable via WiFi
2015-03-21 16:05:43.826 OfflineSupport[934:228478] Operation: (
"<AFHTTPRequestOperation: 0x1701dde20, state: isExecuting, cancelled: NO request: <NSMutableURLRequest: 0x17001ad10> { URL: http://www.google.com }, response: (null)>"
)
2015-03-21 16:05:43.826 OfflineSupport[934:228478] ONLINE
2015-03-21 16:05:43.960 OfflineSupport[934:228478] success
AirPlane activated
Console output contd.:
2015-03-21 16:05:53.437 OfflineSupport[934:228478] Reachability: Not Reachable
2015-03-21 16:05:53.438 OfflineSupport[934:228478] Operation: (
)
2015-03-21 16:05:53.438 OfflineSupport[934:228478] OFFLINE
In pattern 1, the request results in the failure block as there is no access. But when the device comes online, the request is not executed again. Is there something I am missing here? Do I have to configure something on the operation queue or in the failure block?
Reference: AFNetworking 2.0 queue request when device is offline with setReachabilityStatusChangeBlock does nothing, IOS - best way to queue requests to be sent when connection is reestablished
A few observations:
In pattern 1, you have a bit of a race condition because the reachability status block runs asynchronously, so if you start reachability and immediately add operation, the status may not have been identified as being offline yet, and thus the queue may not have been suspended and thus the operation may start immediately (and fail because you're offline).
The problem is solved if you suspend the queue before starting reachability and before starting any operations. If you're actually offline, the queue will stay offline and any operations that were added will be suspended, too. But if you were really online, the reachability block will be called reasonably quickly and and the queue will be promptly be unsuspended. It eliminates this race condition.
The suspended state of a queue does not affect operations that have already started. In only impacts those operations that have not yet started. So, if the connection goes offline while a network operation was in progress, there is no built in mechanism to pause the operation until the connection is restored, nor restart the operation when the status changes. If you want that functionality, you'd have to implement that yourself.
A few more observations:
It's worth noting, though, that just because reachability says that connectivity is available, it doesn't guarantee that the request will succeed. You still need to gracefully handle failed requests.
To the prior point, if you want a more reliable "can I connect to a particular server", you might consider using managerForDomain rather than sharedManager. Just make sure to keep a strong reference to the resulting AFNetworkReachabilityManager, because unlike the singleton, it won't keep a strong reference to itself.
The AFHTTPRequestOperationManager is from version 2.x, and you might consider upgrading to the latest version (so that you use AFHTTPSessionManager, a NSURLSession based implementation). The NSURLConnection used in 2.x has been deprecated.
The AFHTTPSessionManager is, unfortunately, not NSOperation-based. But if you want to enjoy the "send the requests only when the connection is established" functionality, you can either wrap them in asynchronous NSOperation subclass yourself (see AFNetworking 3.0 AFHTTPSessionManager using NSOperation) you can use a background session (see AFNetworking 2.0 and background transfers, while written for AFNetworking 2.x, outlines the essentials of using AFHTTPSessionManager and background session which still largely applies for version 3).

How return data from an HTTP request in Swift/Objective C

I'm trying to use Coinbase's API to get information about my online bitcoin wallet, and I'm trying to use Swift's NSURLSession object to do so. Perhaps I'm missing something obvious in the Apple docs, but after reading through the information about NSURLSession and NSURLSessionTask I still do not understand how to make an HTTP request and then return the body of the response so that the body can persist throughout the life of my app. As of now I only see the ability to use completion blocks which return void, or delegates which either return void themselves or use completion blocks which also return void. I want to use the data I get from the response later in the app, but because I'm using completion blocks I must handle the response data immediately after the response arrives.
To make it clear, I want to do something along the lines of the pseudocode function below:
func makeHTTPCall(urlString : String) -> String? {
create NSURLSession object
create request with proper headers and using the passed-in urlString
use the session object to send out the request
get the response object, extract the response body as a string, and return it
}
Then later, I could call something like this:
let myObject : MyObject = MyObject()
let respData : String = myObject.makeHTTPCall("https://coinbase.com/api/v1/account/balance")
This data is returning a JSON Object string, which is the String I want to persist beyond the life of the response and its completion block. How can I do this in either Swift or Objective C, since I'll be able to use either in Xcode 6?
EDIT: Two answers have been posted, but they miss the fundamental point of this question. I need to RETURN the data which I receive from the response. Both answers (and all other answers I've seen on SO) simply print the data received. I would like code that doesn't use a void-returning completion handler, but instead returns the data so that it can be used later in the lifecycle of the app. If there is anything unclear about my question, please tell me, though I don't see how this can be made clearer.
In the edit to your question, you say:
I need to RETURN the data which I receive from the response. Both answers (and all other answers I've seen on SO) simply print the data received. I would like code that doesn't use a void-returning completion handler, but instead returns the data so that it can be used later in the lifecycle of the app. If there is anything unclear about my question, please tell me, though I don't see how this can be made clearer.
I understand the appeal of this strategy, because it feels so intuitively logical. The problem is that your networking requests should always run asynchronously (e.g. use that completion handler pattern to which you allude).
While there are techniques making a function "wait" for the asynchronous request to complete (i.e. to make the asynchronous NSURLSession method behave synchronously or use one of the old synchronous network request methods), this is a really bad idea for a number of reasons:
If you do this from the main thread, it results in a horrible user experience (the app will be unresponsive while the request is in progress and the user won't know if the app is busy doing something or whether it's frozen for some unknown reason).
Again, if you do this from the main thread, you also risk having the iOS "watch dog" process kill your app (because if you block the main queue for more than a few seconds at the wrong time, particularly as the app comes to foreground, the OS will unceremoniously terminate your app). See Technical Q&A #1693 for a discussion on the problems of doing synchronous network requests.
We generally prefer the asynchronous network techniques because they offer more features unavailable with synchronous techniques (e.g. making requests cancelable, offer progress updates when using delegate-based network requests, etc.).
You really should use the completion handler pattern that those other questions suggest, and manage the changing state of the app in those handlers. In those situations where you absolutely cannot let the user proceed until some network request is done (e.g. you can't let the user buy something until you confirm their bitcoin balance, and you can't do that until they log in), then change the UI to indicate that such a request is in progress. For example, dim the UI, disable the controls, pop up an activity indicator view (a.k.a., a "spinner"), etc. Only then would you initiate the request. And upon completion of the request, you would restore the UI. I know it seems like a lot, but it's the right way to do it when the user absolutely cannot proceed until the prior request is done.
I'd also think long and hard as to whether it's truly the case that you absolutely have to force the user to wait for the prior network request to complete. Sometimes there are situations where you can let the user do/review something else while the network request is in progress. Yes, sometimes that isn't possible, but if you can find those sorts of opportunities in your app, you'll end up with a more elegant UX.
I know that problem and use this code for synchronous requests:
func synchronousRequest() -> NSDictionary {
//creating the request
let url: NSURL! = NSURL(string: "exampledomain/...")
var request = NSMutableURLRequest(URL: url)
request.HTTPMethod = "GET"
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
var error: NSError?
var response: NSURLResponse?
let urlData = NSURLConnection.sendSynchronousRequest(request, returningResponse: &response, error: &error)
error = nil
let resultDictionary: NSDictionary = NSJSONSerialization.JSONObjectWithData(urlData!, options: NSJSONReadingOptions.MutableContainers, error: &error) as! NSDictionary
return resultDictionary
}
What you are asking for is a synchronous network request. There are many ways to do this, such as...
NSData's init(contentsOfURL aURL: NSURL!)
NSURLConnection's synchronous request method
...etc.
These methods will block the current thread until they complete - which can be a potentially long time. Network requests can have very high timeouts, it may be several minutes before the device gives up. NSData's init with contents of URL will return NSData, not void, and does not execute asynchronously. It will block until it is complete, which is why it's recommended to not do these types of requests from the main thread. The UI will be frozen until it completes.
In general the use of synchronous networking methods is discouraged. Asynchronous network requests are greatly preferred for a number of reasons. Using an asynchronous method that takes a completion block as a parameter will not prevent you from using the returned data elsewhere in your application. The block is executed when the network request has finished (wether it succeeds or fails) and it is passed the data, response metadata, and error. You are free to do what you want with that data - nothing prevents you from persisting it, passing it off to another object, etc. Based on your comments it sounds like you want to take the data that was the result of the network request and set it as the value of a property somewhere - that is entirely doable using an asynchronous method that uses a block as a completion handler.
In objective-C you can use __block and get the data when the operation finishes:
__block NSData *myData;
NSURLSession *session = [NSURLSession sharedSession];
[[session dataTaskWithURL:[NSURL URLWithString:urlString]
completionHandler:^(NSData *data,
NSURLResponse *response,
NSError *error) {
myData = data;
}] resume];

How to tell if TWRequest performRequestWithHandler failed or succeeded

I am posting a request and checking for errors like so:
// Send request out on a background thread
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void) {
[postRequest performRequestWithHandler:^(NSData *responseData, NSHTTPURLResponse *urlResponse, NSError *error) {
if ([urlResponse statusCode] == 200) {
NSLog(#"Tweet Successful");
}else {
NSLog(#"Tweet Failed");
if (responseData) {
// Use the NSJSONSerialization class to parse the returned JSON
NSError *jsonError;
NSArray *requestResponse =
[NSJSONSerialization JSONObjectWithData:responseData
options:NSJSONReadingMutableLeaves
error:&jsonError];
if (requestResponse) {
NSLog(#"%#",requestResponse);
} else {
NSLog(#"%#",jsonError);
}
}
}
}];
});
It seems to work fine, so far as the request is concerned. My intention was to queue the request on failure and try again at a later date, or not depending on the error and how many attempts had failed thus far.
The problem I have is that I find it frequently appears to fail, with error 34 "Page not Found" where in fact the request did succeed, was posted as intended and can be seen clear as day in twitter stream.
Now, if I can't trust the error code returned then I can't really go on and keep retrying. How do I know for sure if it succeeds or fails?
RECENT OBSERVATIONS:
Recently, I tried to post a photo from Apple photo app directly and it sent me an alert that read "Tweet may not have been successful". That is funny, because that is basically what I was coming to as my only option in this case. This makes me think that even Apple had to acknowledge that there is not way to tell for sure that post failed when the return does not confirm it.
according to every example found in researching this problem, none of which use any other API than what you are using, and including this example from twitter's own documentation for posting a photo to the API, none of them check the urlResponse code first.
the most that ever seems to be done is this example from twitter's documentation where a GET is performed and then the response is processed. notice that it checks the responseData first, and if it exists, it is simply treated like success. only if there is no responseData is there an attempt to bother looking at the error. the example does not bother with the urlResponse … and neither does any other example i saw in 10 minutes of googling.
(finally, i doubt this matters, or it may be because you cleaned up the example for the display, but you're processing the error on the main-queue when you are not performing any UI. you could do the processing in the handler immediately, and pass along whatever post-processing you do to whatever UI you are trying to display it with. i doubt post-processing the response in the main-queue as opposed to the queue of the handler (as shown in both examples cited here and all other examples i've seen) is really causing a problem, but i wonder if it might cut down on the incidence of false negatives you are seeing. at the very least, it will make your response and any UI display of the response cleaner and more efficient.)

How to make webservice pass errors through NSURLConnection's connection:didFailWithError:?

What does a web service need to do to cause NSURLConnection's delegate to receive the connection:didFailWithError: message?
For example:
iOS app passes a token to the web service, web service looks up the token, and then the web service needs to respond with an error saying "invalid token" or something of the like.
Currently, the data is received, and in "connectionDidFinishLoading:" is parsed for error messages. This means I'm error checking in two places, which I am trying to avoid.
I have both the iOS app and web service completely under my control.
In my experience (the three most dangerous words in programming), -connection:didFailWithError: is only called if the HTTP exchange failed. This is usually a network error or maybe an authentication error (I don't use authentication). If the HTTP message succeeds, no matter the response code, -connectionDidFinishLoading: is called.
My solution: call -connection:didFailWithError: when I detected an error. That way all my error handling code is in one place.
At the top of my -connectionDidFinishLoading:, I have:
NSError *error;
NSDictionary *result = [self parseResultWithData:self.connectionData error:&error];
if (!result) {
[self connection:connection didFailWithError:error];
return;
}
There are many conditions on which the delegate connection:didFailWithError: of NSUrlConnection may invoke.Here's a list of those errors or constants.I think an alertview would be better to show http errors in connection:didFailWithError:.
-(void) connection:(NSURLConnection *)connection didFailWithError: (NSError *)error
{
UIAlertView *errorAlert= [[UIAlertView alloc] initWithTitle: [error localizedDescription] message: [error localizedFailureReason] delegate:nil cancelButtonTitle:#"Done" otherButtonTitles:nil];
[errorAlert show];
[errorAlert release];
NSLog (#"Connection Failed");
}
While not directly related to your question, I would encourage you to move to a more high level library. I can heartily recommend AFNetworking, it is production ready and I have used it in many projects. This will allow you to inspect the response code of each request in the failure block. This project also abstracts away a lot of the low level handling that you would otherwise be required to write for network communication; I'm speaking here about parsing and creating XML / JSON strings to communicate with a service.
To give you a more focused answer to your question, I would call the cancel method of your NSURLConnection once you have noticed an error in connectionDidFinishLoading:. This will automatically cancel the request and call the failure method of the delegate object.
The documentation for NSURLConnection is pretty dry, and the failure method of the delegate does not specifically document the failure cases. You may be able to find more information in the URL Loading System Programming Guide.
I couldn't see the forrest for the trees.
I needed to step back from connection:didFailWithError: and look at a different delegate method connection:didReceiveResponse:!!
With the web service fully under my control, the endpoint could respond with a 500 status code, which gets picked up in connection:didReceiveResponse, and pass along some JSON further explaining the situation, which gets picked up and processed in connection:didReceiveData:.
The NSURLConnection delegate hangs onto a couple more bits of state throughout the process, but it has the best code smell I've found so far.
Jeffery's answer was by far most correct: the connection:didFailWithError: callback is only in relation to the network failing, any response from the web service means the connection didn't fail!

Resources