My azure blob is never returned, but no error is either - objective-c

I have an IOS app using azure storage blobs for jpg photos. My issue is when retrieving the blobs for display.
Most of the time they are retrieved fine. But just occasionally an odd one will not be returned. Instead 749 bytes are returned but with error still = nil.
Now that would be fine, no problem really. However EVERY time after that when I try to retrieve that blob again then the same issue occurs.
All the surrounding blobs are returned fine. The blob in question is fine and can be retrieved using another device.
I have spent lots of time clearing all variables involved and recalling the blob in question and no matter what only ever 749 bytes are returned. Deleting the app from the device and reinstalling it is the only workaround!
So I presume Azure storage or mobile services think the returned data was ok (since it had no error) and keeps sending the same - how can I prevent that and demand a true retry?
The actual retrieving code below was courtesy of github: thank you Ajayi13 it is almost awesome
[request fetchDataWithBlock:^(NSData* data, NSError* error)
{
if(error)
{
if(block)
{
block(nil, error);
}
else if([(NSObject*)_delegate respondsToSelector:#selector(storageClient:didFailRequest:withError:)])
{
[_delegate storageClient:self didFailRequest:request withError:error];
}
return;
}
if(block)
{
block(data, nil);
}
else if([(NSObject*)_delegate respondsToSelector:#selector(storageClient:didGetBlobData:blob:)])
{
[_delegate storageClient:self didGetBlobData:data blob:blob];
}
}];
I have now added the following code based on AdamSorrin's response and a blog post BY DANIEL PASCO: https://blackpixel.com/writing/2012/05/caching-and-nsurlconnection.html
- (NSCachedURLResponse *)connection:(NSURLConnection *)connection willCacheResponse:(NSCachedURLResponse *)cachedResponse {
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse*)[cachedResponse response];
// Look up the cache policy used in our request
if([connection currentRequest].cachePolicy == NSURLRequestUseProtocolCachePolicy) {
NSDictionary *headers = [httpResponse allHeaderFields];
NSString *cacheControl = [headers valueForKey:#"Cache-Control"];
NSString *expires = [headers valueForKey:#"Expires"];
if((cacheControl == nil) && (expires == nil)) {
NSLog(#"server does not provide expiration information and we are using NSURLRequestUseProtocolCachePolicy");
return nil; // don't cache this
}
}
return nil;
}
BUT this has not fixed my issue :o(

I'm not sure exactly what networking library you're using (your request object), so I'm going to assume that it's based off of NSURLSession and NSURLRequest. If not, the details here will be wrong, but the underlying reason still might be correct.
I would guess that your problem is two-fold.
For NSURLSession downloadTaskWithRequest:completionHandler:, the NSError parameter passed into the completion handler block is set only for client-side errors. Retryable service-side errors (throtting, server busy, etc.) aren't detected as such on the client - you need to look at the HTTP response code/message and handle appropriately.
Look at the NSURLSessionConfiguration documentation, specifically requestCachePolicy. My guess is that you're getting stale data from the cache when you try and re-fetch the contents of the blob. You can use this parameter to force the request to re-fetch the data from the service, if this is indeed causing the problem.

Related

Incrementing a Variable from an Asynchronous Block in Objective-C

I have run into a bit of a conundrum with a service I am working on in objective-c. The purpose of the service is to parse through a list of core-data entities and download a corresponding image file for each object. The original design of the service was choking my web-server with too many simultaneous download requests. To get around that, I moved the code responsible for executing the download request into a recursive method. The completion handler for each download request will call the method again, thus ensuring that each download will wait for the previous one to complete before dispatching.
Where things get tricky is the code responsible for actually updating my core-data model and the progress indicator view. In the completion handler for the download, before the method recurses, I make an asynchronous call the a block that is responsible for updating the core data and then updating the view to show the progress. That block needs to have a variable to track how many times the block has been executed. In the original code, I could simply have a method-level variable with block scope that would get incremented inside the block. Since the method is recursive now, that strategy no longer works. The method level variable would simply get reset on each recursion. I can't simply pass the variable to the next level either thanks to the async nature of the block calls.
I'm at a total loss here. Can anyone suggest an approach for dealing with this?
Update:
As matt pointed out below, the core issue here is how to control the timing of the requests. After doing some more research, I found out why my original code was not working. As it turns out, the timeout interval starts running as soon as the first task is initiated, and once the time is up, any additional requests would fail. If you know exactly how much time all your requests will take, it is possible to simply increase the timeout on your requests. The better approach however is to use an NSOperationQueue to control when the requests are dispatched. For a great example of how to do this see: https://code-examples.net/en/q/19c5248
If you take this approach, keep in mind that you will have to call the completeOperation() method of each operation you create on the completion handler of the downloadTask.
Some sample code:
-(void) downloadSkuImages:(NSArray *) imagesToDownload onComplete:(void (^)(BOOL update,NSError *error))onComplete
{
[self runSerializedRequests:imagesToDownload progress:weakProgress downloaded:0 index:0 onComplete:onComplete ];
}
-(void)runSerializedRequests:(NSArray *) skuImages progress:(NSProgress *) progress downloaded:(int) totalDownloaded index:(NSUInteger) index onComplete:(void (^)(BOOL update,NSError *error))onComplete
{
int __block downloaded = totalDownloaded;
TotalDownloadProgressBlock totalDownloadProgressBlock = ^BOOL (SkuImageID *skuImageId, NSString *imageFilePath, NSError *error) {
if(error==nil) {
downloaded++;
weakProgress.completedUnitCount = downloaded;
//save change to core-data here
}
else {
downloaded++;
weakProgress.completedUnitCount = downloaded;
[weakSelf setSyncOperationDetail:[NSString stringWithFormat:#"Problem downloading sku image %#",error.localizedDescription]];
}
if(weakProgress.totalUnitCount==weakProgress.completedUnitCount) {
[weakSelf setSyncOperationIndicator:SYNC_INDICATOR_WORKING];
[weakSelf setSyncOperationDetail:#"All product images up to date"];
[weakSelf setSyncOperationStatus:SYNC_STATUS_SUCCESS];
weakProgress.totalUnitCount = 1;
weakProgress.completedUnitCount = 1;
onComplete(false,nil);
return true;
}
return false;
};
NSURLSessionDownloadTask *downloadTask = [manager downloadTaskWithRequest:request progress:nil destination:nil
completionHandler:^(NSURLResponse * _Nonnull response, NSURL * _Nullable filePath, NSError * _Nullable error) {
NSLog(#"finished download %u of %lu", index +1, (unsigned long)skuImages.count);
if(error != nil)
{
NSLog(#"Download failed for URL: %# with error: %#",skuImage.url, error.localizedDescription);
}
else
{
NSLog(#"Download succeeded for URL: %#", skuImage.url);
}
dispatch_async(dispatch_get_main_queue(), ^(void){
totalDownloadProgressBlock(skuImageId, imageFilePath, error);
});
[self runSerializedRequests:manager skuImages:skuImages progress:progress downloaded:downloaded index:index+1 onComplete:onComplete ];
}];
NSLog(#"Starting download %u of %lu", index +1, (unsigned long)skuImages.count);
[downloadTask resume];
}
The original design of the service was choking my web-server with too many simultaneous download requests. To get around that, I moved the code responsible for executing the download request into a recursive method.
But that was never the right way to solve the problem. Use a single persistent custom NSURLSession with your own configuration, and set the configuration's httpMaximumConnectionsPerHost.

LinkedIn Objective C sharing

we are working on sharing with the linkedIn objective C SDK, latest version.
Using this code:
NSString *url = #"https://api.linkedin.com/v1/people/~/shares";
NSString *payload = #"{\"visibility\":[{\"code\":\"anyone\"}],\"comment\":\"Check out developer.linkedin.com! http://linkd.in/1FC2PyG\"}";
if ([LISDKSessionManager hasValidSession]) {
[[LISDKAPIHelper sharedInstance] postRequest:url stringBody:payload success:^(LISDKAPIResponse *response) {
// do something with response
NSLog(#"Success: %#", response.description);
dispatch_async(dispatch_get_main_queue(), ^{
_responseLabel.text = response.description;
});
} error:^(LISDKAPIError *apiError) {
// do something with error
NSLog(#"Error: %#", apiError.description);
dispatch_async(dispatch_get_main_queue(), ^{
_responseLabel.text = apiError.description;
});
}];
}
lifted pretty much from their sample page. (Had to update a bit, the URL on the site was declared with initWithString which is no longer around).
We have requested and receive a valid session and requested the w_share permission as required in the updated spec.
Here is the actual error:
Error Domain=LISDKErrorAPIDomain Code=400 "(null)" UserInfo={LISDKAuthErrorAPIResponse=<LISDKAPIResponse: 0x1288cc100>}
Any hints would be appreciated!
According to Linked-in's docs and API Console, XML is the default and you need to specify that you want JSON, like this:
https://api.linkedin.com/v1/people/~/shares?format=json
And possibly with a header (not sure if the LISDKAPIHelper) knows how to do that part.
The documentation is really unclear. I suggest you capture the packets with something like CharlesProxy and see if what is getting sent is what you expect. Alternatively, use your same code, but send XML instead of JSON.

How to get message-body immediately after receive a HTTP header by using NSURLSession?

I want to use NSURLSession to receive a xml stream from server and display each xml immediately on the screen.
Here is my delegate code:
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data {
// Enumerate each message-body.
[data enumerateByteRangesUsingBlock:^(const void * _Nonnull bytes, NSRange byteRange, BOOL * _Nonnull stop) {
// Convert message-body to xml string.
NSString *string = [[NSString alloc] initWithBytes:bytes
length:byteRange.length
encoding:NSUTF8StringEncoding];
dispatch_async(dispatch_get_main_queue(), ^{
// Some code to display string.
// ...
});
}];
}
This code works fine except one problem.
The problem is, sometimes when receiving stream, the didReceiveData doesn't be called immediately after didReceiveResponse, it sometimes receive more than one HTTP messages, and then call didReceiveData once to pass all messages which it just receive for me.
It can sometimes take a while for receiving multiple messages, and makes my application not able to display the xml in realtime.
Is there any configuration or property can make it call didReceiveData immediately? I read the document but find nothing useful.
Thanks a bunch.
Update:
I tried to use NSURLConnection to do the same things, it runs perfectly without this problem.
Each didReceiveData is called behind didReceiveResponse immediately.
How can I make the didReceiveData of NSURLSession work just like NSURLConnection?
IIRC, NSURLSession should send data as it receives it, but only after it receives a certain about of data, or after a period of time.
If you're trying to get individual chunks of data, you might instead consider sending them back from the server as a multipart response. Each "part" would contain one of your messages, and you would get a new didReceiveResponse: callback between each one.
With that said, I'm not sure why NSURLConnection would behave differently. They use a lot of the same code under the hood. You might try filing a bug with Apple.

MagicalRecord + Core Data not finding objects between contexts despite objectID string comparison working fine

I have an NSFetchedResultsController that queries on a Core Data entity, 'MyGalleryPhoto'.
I'm trying to delete some objects, and coming up against some problems. I'm using MagicalRecord. Here is my original attempt at the code, which in my view should work fine. At the point the code is run, the objects definitely exist, because they display in the fetchedResultsController.
[MagicalRecord saveInBackgroundWithBlock:^(NSManagedObjectContext *localContext) {
for (MyGalleryPhoto *myGalleryPhoto in [self.fetchedResultsController.fetchedObjects objectsAtIndexes: self.selectedIndexes]) {
NSError *error = nil;
MyGalleryPhoto *localMyGalleryPhoto = (MyGalleryPhoto *) [localContext existingObjectWithID: myGalleryPhoto.objectID error: &error];
NSLog(#"error: %#:%#", [error localizedDescription], [error userInfo]);
NSLog(#"mygp: %#", [localMyGalleryPhoto description]);
[localMyGalleryPhoto deleteInContext: localContext];
}
} completion:^(void){
}];
This code does not work. The myGalleryPhoto entry is not found and the error returned is: "The operation couldn’t be completed. (Cocoa error 133000.)" I've also tried using MR_inContext, which just calls existingObjectWithId:error:.
After a lot of messing around I've come up with this vile frankenstein's monster, that gets all the records out of the entity and compares the string representations of the ObjectIDs. This works fine. Why? I'm using a copy of MagicalRecord I downloaded from GitHub today, XCode up to date, latest SDK, et cetera.
[MagicalRecord saveInBackgroundWithBlock:^(NSManagedObjectContext *localContext) {
NSArray *allMyGalleryPhotos = [MyGalleryPhoto findAllInContext: localContext];
for (MyGalleryPhoto *myGalleryPhoto in [self.fetchedResultsController.fetchedObjects objectsAtIndexes: self.selectedIndexes]) {
MyGalleryPhoto *myGalleryPhotoToDelete = nil;
for (MyGalleryPhoto *existingMyGalleryPhoto in allMyGalleryPhotos) {
NSString *existingURLString = [[existingMyGalleryPhoto.objectID URIRepresentation] absoluteString];
NSString *URLString = [[myGalleryPhoto.objectID URIRepresentation] absoluteString];
NSLog(#"ExistingURLString: %#", existingURLString);
NSLog(#"URLString: %#", URLString);
if ([URLString isEqualToString: existingURLString]) {
myGalleryPhotoToDelete = existingMyGalleryPhoto;
}
}
if (myGalleryPhotoToDelete) [myGalleryPhotoToDelete deleteInContext: localContext];
}
} completion:^(void){
}];
Cocoa Error 13000 is a Referential Integrity error, as described in the documentation. That means you're looking for an object that doesn't exist in the store. On a more practical level, that means that your contexts (I'm assuming you have more than one Managed Object Context) are not in sync. That is, you've added a new object to one context, while the other doesn't have that object because the previous context has not been saved.
Regarding your code, the first problem I see in the first example is that you are crossing thread boundaries at the very start. The fetchedResultsController has a reference to objects in another context (I'm going to assume the default context). Every time saveInBackground is called, it gives you a new context to use, but it also puts that block of code on a background thread. Crossing thread boundaries, even in the new version of Core Data, is going to give you crazy, hard to track down problems at random times.
The gist if what you're trying to do in the first (simpler) block of code is you have a collection of photo objects you want to remove from your application. I would do something like this instead:
NSPredicate *objectsToDelete = [NSPredicate predicateWithFormat:#"self in %#", self.fetchedResultsController.fetchedObjects];
[MagicalRecord saveInBackgroundWithBlock:^(NSManagedObjectContext *)localContext
{
[MyGalleryPhoto deleteAllMatchingPredicate:objectsToDelete inContext:localContext];
}];
The deleteAllMatchingPredicate method should do a lookup of the objects in the correct context (which you weren't doing in the first block of code) so they can be deleted. It also sets up the objects to load as faults, so we're not going to load everything in memory, only to delete it immediately. It'll load only what it needs, and no more.
I would not use existingObjectWithID: in this case. This method never loads faults. Your use case means it'll load the entire object into memory, only to delete it anyway.

Using blocks within blocks in Objective-C: EXC_BAD_ACCESS

Using iOS 5's new TWRequest API, I've ran into a brick wall related with block usage.
What I need to do is upon receiving a successful response to a first request, immediately fire another one. On the completion block of the second request, I then notify success or failure of the multi-step operation.
Here's roughly what I'm doing:
- (void)doRequests
{
TWRequest* firstRequest = [self createFirstRequest];
[firstRequest performRequestWithHandler:^(NSData* responseData,
NSHTTPURLResponse* response,
NSError* error) {
// Error handling hidden for the sake of brevity...
TWRequest* secondRequest = [self createSecondRequest];
[secondRequest performRequestWithHandler:^(NSData* a,
NSHTTPURLResponse* b,
NSError* c) {
// Notify of success or failure - never reaches this far
}];
}];
}
I am not retaining either of the requests or keeping a reference to them anywhere; it's just fire-and-forget.
However, when I run the app, it crashes with EXC_BAD_ACCESS on:
[secondRequest performRequestWithHandler:...];
It executes the first request just fine, but when I try to launch a second one with a handler, it crashes. What's wrong with that code?
The methods to create the requests are as simple as:
- (TWRequest*)createFirstRequest
{
NSString* target = #"https://api.twitter.com/1/statuses/home_timeline.json";
NSURL* url = [NSURL URLWithString:target];
TWRequest* request = [[TWRequest alloc]
initWithURL:url parameters:params
requestMethod:TWRequestMethodGET];
// _twitterAccount is the backing ivar for property 'twitterAccount',
// a strong & nonatomic property of type ACAccount*
request.account = _twitterAccount;
return request;
}
Make sure you're keeping a reference/retaining the ACAccountStore that owns the ACAccount you are using to sign the TWRequests.
If you don't, the ACAccount will become invalid and then you'll get EXC_BAD_ACCESS when trying to fire a TWRequest signed with it.
I'm not familiar with TW*, so consider this a wild guess ... try sending a heap-allocated block:
[firstRequest performRequestWithHandler:[^ (NSData *responseData, ...) {
...
} copy]];
To clarify, I think the block you're sending is heap-allocated, so while TW* might be retaining it, it won't make any difference if it has already gone out of scope.