return statement is not stopping method execution - objective-c

Recently, I am facing the strange behavior of iOS compiler. I have written one method named hasConnectivity. In this method, first I check that internet connectivity is available using Rechability classes. If yes, then I again check the network by calling NSURLRequest to Apple.
Below is my code:
-(BOOL)hasConnectivity {
if(!isOnline)
{
return TRUE;
}
NSURL* url = [NSURL URLWithString:#"http://www.apple.com/"];
NSStringEncoding encoding;
NSError* error = nil;
NSString* pageData = [NSString stringWithContentsOfURL:url
usedEncoding:&encoding error:&error];
NSLog(#"page data=%#",pageData);
if(pageData)
{
return TRUE;
}
else
{
return FALSE;
}
}
When I called this method, Control goes in the first block and executes return TRUE; statement so ideally control should return from that point but is not returning from that point. Instead, it is jumping to the next block (if condition of pageData) and executes return FALSE; statement.
I have restarted Xcode also restarted PC and debugged so many times but still I didn't get the success. If anyone have faced the same problem then please provide me the solution.

Make sure that the build configuration you are building on (probably Debug if you didn't change the defaults) have the optimizations turned off:
Having the optimizations turned on while debugging can cause weird issues like this.

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.

My azure blob is never returned, but no error is either

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.

Objective-C Framework Error Handling

I'm creating a framework for use by a Cocoa Application on 10.6 and later.
The purpose of the framework is to parse a text file.
Obviously, there are errors that could occur, such as file not found, permissions issues, etc.
What is the right way to handle errors within the framework and notify the host application?
My thoughts were:
Do nothing and let the host application catch any exceptions.
Have the host application register its first responder with the framework, catch any exceptions, convert them into NSError and pass them to the host app's responder chain.
Do either of those options make sense? Are there other options? What's the right way to handle this?
I have read the error and exception handling guides, but they don't cover this situation and only describe error handling within the application itself.
I would say the correct way is to use NSError directly yourself in all methods that can error. I have done this recently with a utility class I created, and it works very well. You then allow the application to decide what do to with the error (crash, log, something else) and the framework doesn't need to worry.
Here are the private class methods I used to create the error objects, allowing for underlying POSIX errors (errno etc.):
#pragma mark - Private Methods
- (NSError *)error:(NSString *)localizedDescription
code:(EZipFileError)code
underlyingError:(NSError *)underlyingError
{
NSMutableDictionary *errorDetail = [NSMutableDictionary dictionary];
[errorDetail setValue:localizedDescription forKey:NSLocalizedDescriptionKey];
if (underlyingError != nil)
{
[errorDetail setValue:underlyingError forKey:NSUnderlyingErrorKey];
}
return [NSError errorWithDomain:#"MyErrorDomain"
code:(NSInteger)code
userInfo:errorDetail];
}
- (NSError *)error:(NSString *)localizedDescription
code:(EZipFileError)code
{
return [self error:localizedDescription
code:code
underlyingError:nil];
}
- (NSError *)error:(NSString *)localizedDescription
code:(EZipFileError)code
posixError:(int)posixError
{
NSMutableDictionary *underlyingErrorDetail = [NSMutableDictionary dictionary];
[underlyingErrorDetail setValue:[NSString stringWithUTF8String:strerror(posixError)]
forKey:NSLocalizedDescriptionKey];
NSError *underlyingError = [NSError errorWithDomain:NSPOSIXErrorDomain
code:posixError
userInfo:underlyingErrorDetail];
return [self error:localizedDescription
code:code
underlyingError:underlyingError];
}
Which is used as follows:
if (![self isOpen])
{
if (error != NULL)
{
*error = [self error:#"File is not open"
code:ErrorNotOpen];
}
return nil;
}
Here's an example that uses the underlying POSIX error version of the method:
filefp = fopen([filename UTF8String], "rb");
if (filefp == NULL)
{
if (error != NULL)
{
*error = [self error:#"Failed to open file"
code:ErrorOpenFileFailed
posixError:errno];
}
return NO;
}
Exceptions should be used only for terminal errors in Objective-C. More specifically, Cocoa and Cocoa Touch don't guarantee that exceptions thrown across their boundaries will come out the other side, so you shouldn't use exceptions for error handling.
The right way to report errors to the caller is via an NSError object. You'll notice that many Cocoa and Cocoa Touch methods include a NSError* parameter for exactly that purpose.

Different Behavior Between Debug and Release Builds

I'm using the SOCKit library to implement a URL router for my app. I have a custom Router class that keeps track of all the valid routes and implements a match method that, given a route NSString, matches it to a corresponding view controller. To make things easier, the matchable view controllers must implement the Routable protocol, which requires an initWithState: method that takes an NSDictionary as a parameter. Here's the relevant code:
- (id)match:(NSString *)route
{
for (NSArray *match in routePatterns) {
const SOCPattern * const pattern = [match objectAtIndex:kPatternIndex];
if ([pattern stringMatches:route]) {
Class class = [match objectAtIndex:kObjectIndex];
NSLog(#"[pattern parameterDictionaryFromSourceString:route]: %#", [pattern parameterDictionaryFromSourceString:route]);
UIViewController<Routable> *vc;
vc = [[class alloc] initWithState:[pattern parameterDictionaryFromSourceString:route]];
return vc;
}
}
return nil;
}
When I run the app with the debug configuration, [pattern parameterDictionaryFromSourceString:route] produces what is expected:
[pattern parameterDictionaryFromSourceString:route]: {
uuid = "e9ed6708-5ad5-11e1-91ca-12313810b404";
}
On the other hand, when I run the app with the release configuration, [pattern parameterDictionaryFromSourceString:route] produces an empty dictionary. I'm really not sure how to debug this. I've checked my own code to see if there are any obvious differences between the debug and release builds to no avail and have also looked at the SOCKit source code. Ideas? Thanks!
I just ran into this issue myself today. The issue in my case was that Release builds blocked assertions, but in -performSelector:onObject:sourceString: and -parameterDictionaryFromSourceString: is this important line:
NSAssert([self gatherParameterValues:&values fromString:sourceString],
#"The pattern can't be used with this string.");
Which, when assertions are converted to no-ops, vanishes, and the gathering never happens. With no parameter values, not much happens! I changed it to the following (and will submit an issue to the GitHub repo):
if( ![self gatherParameterValues:&values fromString:sourceString] ) {
NSAssert(NO, #"The pattern can't be used with this string.");
return nil;
}
EDIT: reported as issue #13.

Objective-C can't use stringWithContentsOfURL data in IF statement

Hey, I'm trying to ping my server and check the version of the app - all goes well so far:
NSURL *url = [NSURL URLWithString:#"http://thetalkingcloud.com/static/ping_desktop_app.php?v=1.1"];
NSError *theNetworkError;
NSString *content = [NSString stringWithContentsOfURL:url encoding:NSASCIIStringEncoding error:&theNetworkError];
//make sure something was returned
if (!content) {
//didn't connect
//my code that goes here is all fine and works
} else {
//there was some content returned by the server
//this NSLog prints out the expected data ('success' without the quotes)
NSLog(#"server returned data: >|%#|<", content);
//PROBLEM:
//neither of these two EVER become true, even when the server DOES return success or update
//connected, check what the server returned
if (content == #"success") {
NSLog(#"all went perfect");
} else if (content == #"update") {
NSLog(#"version error");
}
}
The IF statements aren't working for some reason, can anyone help me out please?
Thanks.
You are not checking the contents of content with your current conditional statement, you are checking to see if it is a valid object/pointer and not nil, which it is (valid) since you've just declared it.
Use NSString's length method and test for 0 (or isEqualToString:)
Also see this previous question for another alternative.
Use the isEqualToString: method, not pointer equality (==).