I have a code that checks if an internet connection is there. If there is no internet, I display an alert. Here is the code:
- (void)testInternetConnection
{
__unsafe_unretained typeof(self) weakSelf = self;
internetReachableFoo = [Reachability reachabilityWithHostname:#"www.your-voc.com"];
// Internet is reachable
internetReachableFoo.reachableBlock = ^(Reachability*reach)
{
// Update the UI on the main thread
dispatch_async(dispatch_get_main_queue(), ^{
internetActivated = YES;
NSLog(#"Yayyy, we have the interwebs!");
});
};
// Internet is not reachable
internetReachableFoo.unreachableBlock = ^(Reachability*reach)
{
// Update the UI on the main thread
dispatch_async(dispatch_get_main_queue(), ^{
internetActivated = NO;
if(alertLoaded == NO){
if(weakSelf.isViewLoaded && weakSelf.view.window){
UIAlertView * alert = [[UIAlertView alloc] initWithTitle:#"Pas de connexion internet" message:#"Une connexion est requise pour utiliser l'application" delegate:weakSelf cancelButtonTitle:nil otherButtonTitles:#"Réessayer", #"Mode hors-ligne", nil];
[alert show];
alertLoaded = YES;
}
}
NSLog(#"Someone broke the internet :(");
});
};
[internetReachableFoo startNotifier];
}
I use the isViewLoaded and view.window to be sure to only display the alert if the current window is the one that is loaded and displayed.
But some times, when I shut down the wifi, my simulator crashes with the following error;
-[UIScrollViewDelayedTouchesBeganGestureRecognizer isViewLoaded]: unrecognized selector sent to instance 0x7fbe73da4060
2014-12-05 14:16:57.142 [838:117938] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[UIScrollViewDelayedTouchesBeganGestureRecognizer isViewLoaded]: unrecognized selector sent to instance 0x7fbe73da4060'
What is wrong?
Thanks a lot
UPDATE: I use Tony Million's version of Reachability, so the testInternetConnection method is called whenever the internet status changes.
Your weakSelf variable is probably a weak pointer and is already released when code reaches this point.
Related
I have a weird issue with Zebra printer. On the big picture, I have codes to fetch items to print one by one from the queue. So if when the printing initiated, and there's 3 items on the queue, the code will loop and fetch the first data in the queue, send it to the printer, and delete the first data in the queue. Sort of like dequeueing.
The problem is, if it's the code that looping and sending the data directly to the printer, the printer will only print the first item. The next item is gone, even though NSLog shows that the printer connection opened, data sent, printing successful, and printer connection closed, for every single item.
But if each time the code print one label, and then the app shows message box like "press OK to print next label", and then the user tap the OK button, it can print the second and the rest of the label after every tap of the button.
I have then tried to emulate this. I've tried to use timer to "push the button programmatically" [btnPrint sendActionsForControlEvents:UIControlEventTouchUpInside], I also use timer to call the function directly, or giving delay to the thread, but none works. It has to be initiated from a button which tapped from human touch. I don't know why.
Here's the code:
// main function to print
-(void) printLabel {
if ([dataToPrint count] > 0) {
[self printWithData:[dataToPrint objectAtIndex:0]];
}
}
-(void)printWithData:(NSString*) data;
{
NSString *zplString = data;
// do something with zplString
NSLog(#"Sending data to printer");
printHandler = [[PrintingHandler alloc] init];
[printHandler setDelegate:self];
[printHandler initialize];
[printHandler printToSerial:bluetoothSerialNumber withData:zplString];
}
// delegate to call if the print is success
-(void) printIsSuccess
{
[dataToPrint removeObjectAtIndex:0];
// in here, I just use sleep code instead of button tap emulation to avoid unnecessarily too long code
[NSThread sleepForTimeInterval:2.0f];
[self printLabel];
}
// this is method inside PrintingHandler class that get called by PrintingHandler (printToSerial:)
-(void) printLabelWithData:(NSString*) zplData toPrinter:(NSString*) serial withSender:(id) sender
{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^ {
// Instantiate connection to Zebra Bluetooth accessory
id<ZebraPrinterConnection, NSObject> thePrinterConn = [[MfiBtPrinterConnection alloc] initWithSerialNumber:serial];
// Open the connection - physical connection is established here.
BOOL success = [thePrinterConn open];
NSError *error = nil;
// Send the data to printer as a byte array.
success = success && [thePrinterConn write:[zplData dataUsingEncoding:NSUTF8StringEncoding] error:&error];
[NSThread sleepForTimeInterval:1.0f];
//Dispath GUI work back on to the main queue!
dispatch_async(dispatch_get_main_queue(), ^{
if (success != YES || error != nil) {
[delegate printFailed];
UIAlertView *errorAlert = [[UIAlertView alloc] initWithTitle:#"Error" message:[error localizedDescription] delegate:nil cancelButtonTitle:#"Ok" otherButtonTitles:nil];
[errorAlert show];
[errorAlert release];
}
else if (success != YES) {
NSLog(#"Print is not success, but no error raised");
[delegate printSuccess];
}
else
{
NSLog(#"Print is success");
[delegate printSuccess];
}
});
// Close the connection to release resources.
NSLog(#"printer connection closed");
[thePrinterConn close];
[thePrinterConn release];
});
}
Sorry, I've found the solution. The problem is it's too fast between opening the connection to the printer and sending data to the printer. I was putting some delay, but I put the delay at wrong position.
So the step to print currently is:
Open bluetooth printer connection
Send data
Delay
Close printer connection
When the correct one should be:
Open bluetooth printer connection
Delay
Send data
Close printer connection
Here I put the answer so this can help other people with same problem.
-(void) printLabelWithData:(NSString*) zplData toPrinter:(NSString*) serial withSender:(id) sender
{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^ {
// Instantiate connection to Zebra Bluetooth accessory
id<ZebraPrinterConnection, NSObject> thePrinterConn = [[MfiBtPrinterConnection alloc] initWithSerialNumber:serial];
// Open the connection - physical connection is established here.
BOOL success = [thePrinterConn open];
NSError *error = nil;
[NSThread sleepForTimeInterval:1.0f]; // this is the important one
// Send the data to printer as a byte array.
success = success && [thePrinterConn write:[zplData dataUsingEncoding:NSUTF8StringEncoding] error:&error];
//Dispath GUI work back on to the main queue!
dispatch_async(dispatch_get_main_queue(), ^{
if (success != YES || error != nil) {
[delegate printFailed];
UIAlertView *errorAlert = [[UIAlertView alloc] initWithTitle:#"Error" message:[error localizedDescription] delegate:nil cancelButtonTitle:#"Ok" otherButtonTitles:nil];
[errorAlert show];
[errorAlert release];
}
else if (success != YES) {
NSLog(#"Print is not success, but no error raised");
[delegate printSuccess];
}
else
{
NSLog(#"Print is success");
[delegate printSuccess];
}
});
// Close the connection to release resources.
NSLog(#"printer connection closed");
[thePrinterConn close];
[thePrinterConn release];
});
}
I am trying to learn AFNetworking. when I run following code I get there is no internet. But in another check it says it is connected to the internet and device is connected to internet through wifi. This the output:
2016-02-19 15:16:40.315 AFNetworkingSample[377:47927] There is no internet connection
2016-02-19 15:16:40.331 AFNetworkingSample[377:47927] Reachability: Reachable via WiFi
Any idea why return value of connected method is false?
- (void)viewDidLoad {
[super viewDidLoad];
[[AFNetworkReachabilityManager sharedManager] startMonitoring];
if (self.connected)
{
NSLog(#"We have internet connection");
}
else
{
NSLog(#"There is no internet connection");
}
[[AFNetworkReachabilityManager sharedManager] setReachabilityStatusChangeBlock:^(AFNetworkReachabilityStatus status) {
NSLog(#"Reachability: %#", AFStringFromNetworkReachabilityStatus(status));
}];
}
-(BOOL)connected {
return [AFNetworkReachabilityManager sharedManager].reachable;
}
UPDATE
When I change it to following it works fine and detect internet properly. Anyone can explain this behaviour?
[[AFNetworkReachabilityManager sharedManager] setReachabilityStatusChangeBlock:^(AFNetworkReachabilityStatus status) {
NSLog(#"Reachability: %#", AFStringFromNetworkReachabilityStatus(status));
if (self.connected)
{
NSLog(#"We have internet connection");
}
else
{
NSLog(#"There is no internet connection");
}
}];
UPDATE2
When I wait for two second it detects internet properly:
double delayInSeconds = 2.0;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC));
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
if (self.connected)
{
NSLog(#"We have internet connection");
}
else
{
NSLog(#"There is no internet connection");
}
});
UPDATE3:
Is this how to practice to avoid race condition?
- (void)viewDidLoad {
[super viewDidLoad];
[[AFNetworkReachabilityManager sharedManager] setReachabilityStatusChangeBlock:^(AFNetworkReachabilityStatus status) {
NSLog(#"Reachability: %#", AFStringFromNetworkReachabilityStatus(status));
if([AFNetworkReachabilityManager sharedManager].reachable)
{
NSLog(#"Internet connection started again");
}
else
{
UIAlertController *alert = [UIAlertController alertControllerWithTitle:#"Alert"
message:#"Error Retrieving Data"
preferredStyle:UIAlertControllerStyleActionSheet];
UIAlertAction *firstAction = [UIAlertAction actionWithTitle:#"OK"
style:UIAlertActionStyleDefault handler:^(UIAlertAction * action) {
NSLog(#"You pressed button OK");
}];
[alert addAction:firstAction];
[self presentViewController:alert animated:YES completion:nil];
}
}];
double delayInSeconds = 2.0;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC));
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
[[AFNetworkReachabilityManager sharedManager] startMonitoring];
});
}
[[AFNetworkReachabilityManager sharedManager] startMonitoring];
The above is, effectively, an asynchronous call. You've told AFNetwork that you're interested in reachability. As such, it is going to go off and do whatever it needs to do to determine and monitor reachability in the background.
Since it is not a one shot asynchronous call, having a completion block on that method doesn't really fit the pattern of a normal completion handler. It could have been a "status handler" that is called whenever a state change happens, but the API is also designed to be used for passive polling (which some of your other code patterns, like the timer one, inadvertently do -- and, as you noted, not waiting long enough gives you the wrong answer, as expected).
By calling setReachabilityStatusChangeBlock:, you are registering a bit of code to be executed when the reachability status changes. Use that to trigger your "hey! reachability changed!" code, and all is well.
The only remaining issue is whether there is a race condition. You should probably set the block before calling startMonitoring. And, to be safe, you'll likely want to check to see if the network is connected immediately after you call startMonitoring as, technically, if the network was connected, then there may not be a state change that triggers a call to your state changed handler block.
You don't want to block the thread for 2 seconds to try and avoid the race condition. Do something like this:
... set up change handler block ...
... start monitoring ...
... check current internet state & alert user if necessary ...
Move your alert out of the change handler block and into a method that both the change handler block and the "check current internet state & alert user if necessary" mention above can call.
use Reachability like this.....
- (void)viewDidLoad {
if (self.isConnected)
{
NSLog(#"We have internet connection");
}
else
{
NSLog(#"There is no internet connection");
}
}
-(BOOL)isConnected
{
Reachability *aReachability = [Reachability reachabilityWithHostName:KWebserviceURL];
NetworkStatus netStatus = [aReachability currentReachabilityStatus];
if(netStatus==0)
{
// NoAccess
return NO;
}
else if(netStatus==1)
{
// ReachableViaWiFi
return YES;
} else if(netStatus==2)
{
// ReachableViaWWAN
return YES;
}
else
{
// Reachable
return YES;
}
}
if you want to make global class ror check internet connection then make NSObject see in my another ans....Check here
Hope it Helps you...
I'm working on an iOS app and I've encountered a problem during testing: the app crashes when I try to enter my next ViewController and I get these messages:
2014-06-29 14:22:46.674 IOS2 Practica[55472:60b] -[Logger login:]: unrecognized selector sent to instance 0x9242230
2014-06-29 14:22:46.679 IOS2 Practica[55472:60b] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[Logger login:]: unrecognized selector sent to instance 0x9242230'
From other cases I've looked up, it might be possible that something happens when I attempt to use "segue" to switch to the next ViewController but I can't pinpoint the exact source.
I append the code segment where I define the segue in my main View Controller:
- (void)logger:(id)sender {
NSString *user = self.username.text;
NSString *pass = self.password.text;
NSString * urlBase = #"http://www.v2msoft.com/clientes/lasalle/curs-ios/login.php?username=lluis&password=seguro";
[JSONHTTPClient getJSONFromURLWithString:urlBase completion:^(id json,JSONModelError *err){
JsonUser * user = [[JsonUser alloc]initWithDictionary:json error:nil];
if(user.succeed) {
self.user_id = user.user_id;
[self performSegueWithIdentifier:#"Login" sender:self];
} else {
NSLog(#"error");
}
}];
}
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
if (_username.text.length == 0 || _password.text.length == 0) {
[[[UIAlertView alloc] initWithTitle:#"Error"
message:#"Algun dels camps és incorrecte o està buit!"
delegate:self
cancelButtonTitle:#"Cancel"
otherButtonTitles:nil, nil]
show];
} else {
Supermarket * supermercat = segue.destinationViewController;
supermercat.user_id = self.user_id;
}
}
Thank you in advance for any insight on this topic.
Somewhere in your code (not the bit you posted), you should be calling a login: method on a Logger instance. That method is not defined in Logger and that is why the app crashes.
Pay attention to the : at the end of Login:: possibly you have defined a login method (which does not take any argument), but you are calling it with an argument. Or maybe you would have liked to call logger:.
You can try 2 things out: either you find out where the login: method is called (some button bound to it in Storyboard?) or you rename logger: into login:.
I've implemented the following exception handler using:
NSSetUncaughtExceptionHandler(&ExceptionHandler)
The handler then makes a call to handleException using the following:
[[AppContext alloc] init] performSelectorOnMainThread:#selector(handleException:) withObject:exception waitUntilDone:YES];
Which has been implemented as such:
NSString *message = [NSString stringWithFormat:MSG_UNHANDLED_EXCEPTION_FORMAT, exception.reason, [exception callStackSymbols]];
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:TITLE_UNHANDLED_EXCEPTION message:message delegate:self cancelButtonTitle:nil otherButtonTitles:nil];
[alert show];
CFRunLoopRef runLoop = CFRunLoopGetCurrent();
CFArrayRef allModes = CFRunLoopCopyAllModes(runLoop);
while (!_quit) {
for (NSString *mode in (__bridge NSArray *)allModes) {
CFRunLoopRunInMode((__bridge CFStringRef)mode, 0.001, false);
}
}
CFRelease(allModes);
This works well except for the following scenario. I still get the UIAlertView to show but the user interaction is no longer available (user can't scroll message or tap OK).
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(){
// do long work on another thread...
dispatch_async(dispatch_get_main_queue(), ^(){
// updating the UI so get back on main thread...
// exception occurs in this block (example)...
#throw [NSException exceptionWithName:#"DispatchAsyncToMainThreadException" reason:#"Example" userInfo:nil];
});
Note: I am not actually throwing an exception in my production code. This is purely to show where the unhandled exception is being generated.
What am I missing that should be accounted for when the exception is being raised from within a block that has been dispatched back to the main queue.
The documentation for NSSetUncaughtExceptionHandler states that you should be doing last-minute error logging before the application terminates. You should not expect any UI to work at this point and should only be logging somewhere to the file system the details of the error. When the user opens the application again you can let them know something went wrong and possible offer recovery/reporting options.
Discussion Sets the top-level error-handling function where you can perform last-minute logging before the program terminates.
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.