I have created a class NetCalculator which I am calling when a button is pressed. The method calculate network it gets 2 NSStrings and returns an id object (either "Network" object or "UIAlertView". Then I am checking which object is and I present the data. When I am using the UIAlertView the app is crashing after showing 2-3 alerts.
Any ides why this happens? On terminal it doesnt show any error just some random hexadecimal.
-(IBAction)calculate:(id)sender {
id result;
Network *network = [[Network alloc]init];
NetCalculator *netCalculated = [[NetCalculator alloc] init];
result = [netCalculated calculateNetworkWithIP:ipLabel.text andSubnet:subnetLabel.text];
if([result isKindOfClass:[Network class]]){
network = result;
NSLog(#"network %#",network.networkIP);
}
else if([result isKindOfClass:[UIAlertView class]]) {
UIAlertView *alert;
alert = result;
[alert show];
}
};
Your code is quite strange to me. Your method calculateNetworkWithIP could return a Network result or a UIAlertView result. I wouldn't follow such an approach.
If the problem relies on memory you should show us hot that method is implemented.
Anyway, I would propose some changes (The following code does not take into account ARC or non ARC code). In particular, I would modify the calculateNetworkWithIP to return a Network result. An error will populated if a problem arises and it is passed as an argument.
- (Network*) calculateNetworkWithIP:(NSString *)ip subnet:(NSString*)subnet error:(NSError**)error
If all is ok, the result would be be a Network and so print it or reused it somewhere. Otherwise an NSError would be returned and based on that create and show an alert view.
So, here pseudo code to do it.
NetCalculator *netCalculated = [[NetCalculator alloc] init];
NSError* error = nil;
Network* networkResult = [netCalculated calculateNetworkWithIP:ipLabel.text subnet:subnetLabel.text error:&error];
if(error != nil) {
// create and show an alert view with the error you received
} else {
// all ok so, for example, save the result in a instance variable
}
To follow a similar approach you can take a look at why is "error:&error" used here (objective-c).
Related
I have a method that accepts an NSString by reference, and the idea is that if an error occurs, the string will contain a specific error message; otherwise, it'll be nil.
-(BOOL)doStuffThatCouldProduceAnError:(NSString *)error {
...
// An error occurred, so set the string
error = #"Foo Bar is invalid"
return NO;
}
But the problem is, the caller of doStuffThatCouldProduceAnError doesn't see the error message:
-(void)someMethod {
NSString *error;
[self doStuffThatCouldProduceAnError:error];
[NSLog #"Message: %#", error]; // Logs "[nil]"
}
I'm not sure how to search for a solution, and what I did try to search on doesn't cover the passing by reference and setting from another method. I've also tried NSMutableString, but that doesn't seem to make any difference.
Thank you in advance!
Edit: I forgot to mention that I've tried using error = [error stringByAppendingString:...], but that didn't work either.
You are not passing by reference.
You got to do
-(BOOL)doStuffThatCouldProduceAnError:(NSString **)error {
*error = #"Foo Bar is invalid"
and
[self doStuffThatCouldProduceAnError:&error];
I am attempting to create a feature with my app that allows people to be easily added and/or removed from a group on iOS. The code below works perfectly if I:
1) add one person at a time
2) remove one person at a time
If however I select multiple people to add and/or remove simultaneously only the first action - first add if any or first remove if no additions - is committed to the address book. This method can be called over and over again with any one individual action and it will succeed. I cannot determine why I cannot get it to save multiple adds/deletes within the context of a single call to save. It should be pointed out that no error is giving and stepping through the code the booleans used to determine success indicate that everything worked perfectly.
I have attempted various combinations of adjusting code, for example: create a local address book, retrieve the groupRef, save the address book all within each of the for loops. I've also investigated reverting the address book after saving it to no avail. Thoughts or experiences?
-(IBAction)save:(id)sender
{
#synchronized(self)
{
CFErrorRef error = NULL;
BOOL success = NO;
BOOL successfulSave = NO;
ABAddressBookRef localAddressBook = ABAddressBookCreateWithOptions(NULL, &error);
group = ABAddressBookGetGroupWithRecordID(localAddressBook, groupID);
for (CFIndex addItemCount = 0; addItemCount < [theAddList count]; ++addItemCount)
{
NSNumber *addID = [theAddList objectAtIndex:addItemCount];
ABRecordRef thePersonID = ABAddressBookGetPersonWithRecordID(localAddressBook, [addID intValue]);
success = success || ABGroupAddMember(group, thePersonID, &error);
}
for (CFIndex removeItemCount = 0; removeItemCount < [theRemoveList count]; ++removeItemCount)
{
NSNumber *removeID = [theRemoveList objectAtIndex:removeItemCount];
ABRecordRef thePersonID = ABAddressBookGetPersonWithRecordID(localAddressBook, [removeID intValue]);
success = success || ABGroupRemoveMember(group, thePersonID, &error);
}
if (success)
{
successfulSave = ABAddressBookSave(localAddressBook, &error);
}
if (!(successfulSave && success))
{
UIAlertView *addressBookUpdateAlert = [[UIAlertView alloc] initWithTitle:#"AddressBook Error" message:#"An update to your address book could not be processed at this time. Try again shortly." delegate:nil cancelButtonTitle:#"Ok" otherButtonTitles:nil];
// we can't update a GUI element from a background thread - appears sensitivity didn't appear until iOS 8
[[NSOperationQueue mainQueue] addOperationWithBlock:
^{
[addressBookUpdateAlert show];
}];
}
else
{
[[self navigationController] popViewControllerAnimated:YES];
}
}
}
I can't explain it, but I made a minor change which should have done a better job of trapping an error and instead seems to caused to code to magically start working - which by the way is usually code for you over wrote memory somewhere.
Anyway I did the following and it started working:
Set success = YES before entering the loop
Changed the || test to an && test in both loops
All I changed and yet somehow it just started working...weird...I'm off to test/hammer the thing in case I'm seeing things...
I have a method which returns a boolean value. It should return true if at least one image/asset has been found from a URL. The code is as shown below.
Inside the block when i print the count of objects in the array, it prints properly. However, outside the block, the count is zero and it does not enter the if block and the method always returns FALSE. I guess this happens because the function is returning before the block is being executed. How do I tackle this issue? How do I ensure that the method returns true if atleast one URL has been added to self.imageURLs inside the block?
-(BOOL)getPhotos
{
self.imagesFound = FALSE;
//get all image url's from database
//for each row returned do the following:
while (sqlite3_step(statement) == SQLITE_ROW)
{
NSString *URLString = [[NSString alloc] initWithUTF8String:(const char *) sqlite3_column_text(statement, 0)];
NSURL *imageURL = [NSURL URLWithString:URLString];
ALAssetsLibrary *library = [[ALAssetsLibrary alloc] init];
[library assetForURL:imageURL resultBlock:^(ALAsset *asset)
{
if (asset) {
//if an image exists with this URL, add it to self.imageURLs
[self.imageURLs addObject:URLString];
NSLog(#"no. of objects in array: %lu", (unsigned long)self.imageURLs.count);
}
}
failureBlock:^(NSError *error)
{
// error handling
NSLog(#"failure-----");
}];
}
if (self.imageURLs.count > 0) {
NSLog(#"self.imageURLs count = %lu", (unsigned long)self.imageURLs.count);
self.imagesFound = TRUE;
}
else
{
UIAlertView *alert = [[UIAlertView alloc]initWithTitle:#"Sorry!" message:#"No photos found" delegate:self cancelButtonTitle:#"OK" otherButtonTitles:nil, nil];
[alert show];
}
return self.imagesFound;
}
This is may be because of the blocks are sometimes asynchronous, that is after executing
[library assetForURL:imageURL resultBlock:^(ALAsset *asset).....
It wont wait for completing the execution of block, it will immediately executes the next statements.
check which log is printing first
NSLog(#"no. of objects in array: %lu", (unsigned long)self.imageURLs.count);
or
a log after the
[library assetForURL:imageURL resultBlock:^(ALAsset *asset).....
A block can therefore maintain a set of state (data) that it can use
to impact behavior when executed.
Write all code after the execution completion of your block and send a notification instead of returning the value.
[library assetForURL:imageURL resultBlock:^(ALAsset *asset)
{
if (asset)
{
//if an image exists with this URL, add it to self.imageURLs
[self.imageURLs addObject:URLString];
self.imagesFound = TRUE;
//send a notificaton from here as TRUE,
}
}failureBlock:^(NSError *error)
{
//self.imagesFound = NO;
//display error alert.
//send a notificaton from here as FALSE,
}];
This kind of thing is pretty much exactly what either delegates or completion handlers on blocks are for. Because your block is asynchronous, it moves on and executes the rest of your code before any chance of your boolean being set.
To do it via delegation, you would:
create a class that when instantiated does what you want it to do on a background thread, and set the caller as a delegate.
When it is done, call back the delegate method, and then carry your script on accordingly from there.
OR simply add a completion handler which will kick off the rest of your code that is dependent on the result that you are currently not getting at all.
In my experience, although the delegation takes longer to write, and is much more difficult to get your head around if you're new, it makes your code more reusable, and better abstracted in order to be able to be reused elsewhere in your application where the same asynchronous count or operation is required.
I want to start a task that runs on another thread "just in case it is needed" to minimize the time the user will have to wait on it later. If there is time for it to complete, the user will not have to wait, but if it has not completed then waiting would be necessary.
Something like, opening a database in viewDidLoad: that will be needed when and if the user pushes a button on the screen. If I wait to open the database until the user actually pushes the button there is a lag. So I want to open it early. Since I don't know how long it will take to open and I don't know how long until the user hits the button, I need a way of saying, if that other task has not completed yet then wait, otherwise just go ahead.
For example:
#implementation aViewController
- (void) viewDidLoad {
[self.dbManager openOrCreateDbWithCompletionHandler: ^(NSError *err) {
if( err ) NSLog( #"There was a problem opening the database" );
}];
}
- (IBAction) goButtonTouched: (id) sender {
// Wait here until the database is open and ready to use.
if( ???DatabaseNotAvailableYet??? ) {
[self putSpinnerOnScreen];
???BlockProgressHereUntilDatabaseAvailable???
[self takeSpinnerOffScreen];
}
// Use the database...
NSManagedObjectContext *context = [self theDatabaseContext];
// Build the search request for the attribute desired
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName: NSStringFromClass([Destinations class])];
request.predicate = [NSPredicate predicateWithFormat: #"dId == %#", sender.tag];
request.sortDescriptors = nil;
// Perform the search
NSError *error = nil;
NSArray *matches = [context executeFetchRequest: request error: &error];
// Use the search results
if( !matches || matches.count < 1 ) {
NSLog( #"Uh oh, got a nil back from my Destination fetch request!" );
UIAlertView *alert = [[UIAlertView alloc] initWithTitle: #"No Info"
message: #"The database did not have information for this selection"
delegate: nil
cancelButtonTitle: #"OK"
otherButtonTitles: nil];
[alert show];
} else {
MyOtherViewController *movc = [[MyOtherViewContoller alloc] init];
movc.destDetails = [matches lastObject];
[self.navigationController pushViewController: movc animated: YES];
}
}
#end
My hope is that there is never a spinner on the screen and never any delay for the user but, since I don't know how long it will take for the database connection to be established, I have to be prepared for it not being ready when the user pushes the button.
I can't use the call back for when openOrCreateDbWithCompletionHandler: completes since I don't want to do anything then, only when the user pushes the button.
I thought about using a semaphore but it seems like I would only signal it once (in the completion handler of the openOrCreateDbWithCompletionHandler: call) but would wait on it every time a button was pushed. That seems like it would only work for the first button push.
I thought about using dispatch_group_async() for openOrCreateDbWithCompletionHandler: then dispatch_group_wait() in goButtonTouched: but since openOrCreateDbWithCompletionHandler: does its work on another thread and returns immediately, I don't think the wait state would be set.
I can simply set a my own flag, something like before the openOrCreateDbWithCompletionHandler:, self.notOpenYet = YES;, then in its completion handler do self.notOpenYet = NO;, then in goButtonTouched: replace ???DatabaseNotAvailableYet??? with self.notOpenYet, but then how do I block progress on its state? Putting in loops and timers seems kludgy since I don't know if the wait will be nanoseconds or seconds.
This seems like a common enough situation, I am sure that you have all done this sort of thing commonly and it is poor education on my side, but I have searched stackOverflow and the web and have not found a satisfying answer.
I think, blocking execution is a bad habit unless you are building your own event loop, which is rarely necessary. You also don't need to do any GCD stuff in your case. Just get a feeling for async.
The following should work:
#implementation aViewController
- (void) viewDidLoad {
self.waitingForDB = NO;
self.databaseReady = NO;
[self.dbManager openOrCreateDbWithCompletionHandler: ^(NSError *err) {
if( err ){
NSLog( #"There was a problem opening the database" )
}else{
[self performSelectorOnMainThread:#selector(handleDatabaseReady) withObject:nil waitUntilDone:NO];
};
}];
}
- (void)handleDatabaseReady{
self.databaseReady = YES;
if(self.waitingForDB){
[self takeSpinnerOffScreen];
[self go];
}
}
- (IBAction) goButtonTouched: (id) sender {
// Wait here until the database is open and ready to use.
if( !self.databaseReady ) {
self.waitingForDB = YES;
[self putSpinnerOnScreen];
else{
[self go];
}
}
-(void)go{
// Use the database...
NSManagedObjectContext *context = [self theDatabaseContext];
// Build the search request for the attribute desired
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName: NSStringFromClass([Destinations class])];
request.predicate = [NSPredicate predicateWithFormat: #"dId == %#", sender.tag];
request.sortDescriptors = nil;
// Perform the search
NSError *error = nil;
NSArray *matches = [context executeFetchRequest: request error: &error];
// Use the search results
if( !matches || matches.count < 1 ) {
NSLog( #"Uh oh, got a nil back from my Destination fetch request!" );
UIAlertView *alert = [[UIAlertView alloc] initWithTitle: #"No Info"
message: #"The database did not have information for this selection"
delegate: nil
cancelButtonTitle: #"OK"
otherButtonTitles: nil];
[alert show];
} else {
MyOtherViewController *movc = [[MyOtherViewContoller alloc] init];
movc.destDetails = [matches lastObject];
[self.navigationController pushViewController: movc animated: YES];
}
}
#end
Performing the call to handleDatabaseReady on the main thread guarantees that no race conditions in setting/reading your new properties will appear.
I'd go with the flag. You don't want to block the UI, just show the spinner and return from the goButtonTouched. However, you do need to cancel the spinner, if it is active, in openOrCreateDbWithCompletionHandler:.
This is rather a simple scenario. You make a method that does the stuff. Lets call it doStuff. From main thread, you call performSelectorInBackgroundThread:#selector(doStuff). Do not enable the button by default. Enable it at the end of doStuff so that user won't tap on it until you are ready. To make it more appealing, you can place a spinner in the place of the button and then replace it with the button when doStuff completes.
There are a number of classes and APIs you can use to achieve this kind of thing. You can use NSThread with synchronization primitives like semaphores and events to wait for it to finish when the user actually presses the button. You can use an NSOperation subclass (with an NSOperationQueue), and you can use GCD queues.
I would suggest you take a look at some the information in the Concurrency Programming Guide from Apple.
In your case you would probably be best served adding the operation to a GCD background queue using dispatch_async in combination with a semaphore which you can wait on when the user taps the button. You can check out the question "How do I wait for an asynchronously dispatched block to finish?" for an example.
I'm basically implementing a fancier NSURLConnection class that downloads data from a server parses it into a dictionary, and returns an NSDictionary of the data. I'm trying add a completion block option (in addition to a delegate option), but it crashes anytime I try to store that data in another class.
[dataFetcher_ fetchDataWithURL:testURL completionHandler:^(NSDictionary *data, NSInteger error) {
contentDictionary_ = data;
}];
I can NSLog that data just fine, and basically do whatever I want with it, but as soon as I try to save it into another variable it crashes with a really obscure message.
EDIT: the crash message is EXC_BAD_ACCESS, but the stack trace is 0x00000000 error: address doesn't contain a section that points to a section in a object file.
I'm calling this function in the init method of a singleton. It DOES let me save the data if I set this in the completion block.
[SingletonClass sharedInstance].contentDictionary = data
But then the app gets stuck forever because sharedInstance hasn't returned yet, so the singleton object is still nil, so sharedInstance in the completion block calls init again, over and over.
EDIT 2: The singleton code looks like this:
+ (SingletonClass*)sharedInstance {
static SingletonClass *instance;
if (!instance) {
instance = [[SingletonClass alloc] init];
}
return instance;
}
- (id)init {
self = [super init];
if (self) {
dataFetcher_ = [[DataFetcher alloc] init];
NSString *testURL = #"..."
[dataFetcher_ fetchDataWithURL:testURL completionHandler:^(NSDictionary *data, NSInteger error) {
[SingletonClass sharedInstance].contentDictionary = data;
}];
}
return self;
}
Like I said, this works fine but repeats the initialize code over and over until the app crashes. This only happens the first time I run the app on a device, because I cache the data returned and it doesn't crash once I have the data cached. I would like to be able to just say self.contentDictionary = data, but that crashes.
Specify a variable to be used in the block with the __block directive outside of the block:
__block NSDictionary *contentDictionary_;
[dataFetcher_ fetchDataWithURL:testURL completionHandler:^(NSDictionary *data, NSInteger error) {
contentDictionary_ = data;
}];
You're invoking recursion before ever setting the "instance". (which I now see you understand from OP).
In your block, you can use the ivar or an accessor instead of
[SingletonClass sharedInstance].contentDictionary
use:
_contentDictionary = [data copy]; or self.contentDictionary=data;
assuming that the ivar backing the contentDictionary property is _contentDictionary.
It sounds like you tried self.contentDictionary and it failed? I got it to work in a test, with ARC turned, so there may be something about your dataFetcher that is affecting this. In my test dataFetcher just returns a dictionary with a single element.
Turns out the issue was with a bunch of different parts. My URL was empty sometimes, and my data fetcher would just fail immediately and call the completion block. In my completion block I hadn't included any error handling, so if the singleton class hadn't initialized, it would repeat forever. With a real URL this doesn't happen.
I still would like to figure out why it crashes when I try to assign the data to an ivar, though.