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...
Related
I am now working on an app that works with BLE, Backend server and location. I am facing a problem which I am not sure how to get out of which is what people call "Callback hell". The entire CoreBluetooth framework in iOS is based on a delegate pattern, which until you can use the CBPeripheral has to go to at least 3 callbacks:
DidConnectToPeripheral
DidDiscoverServices
DidDiscoverCharacteristics
But in fact there could be many more, and every action you take with the device will come back as a callback to one of those functions. Now when I want to "Rent" this ble product, I must connect to it, after connecting send a requests to the server and get the user's current location, after that all happens I have to write a value in the bluetooth device and get confirmation. This would not be so difficult, but unfortunately each and every one of those stages is failable, so error handling needs to be added. Not to mention implementing timeout.
I am sure I am not the only one to approach such issues so I looked around and I found 2 things that might help:
the Advanced NSOperations talk in the wwdc 2015, but after trying for 4 days to make it work, it seems like the code is too buggy.
Promisekit but I couldn't find a way to wrap CoreBluetooth.
How are people with even more complicated apps deal with this? in swift or objc.
Some sample problematic code:
-(void)startRentalSessionWithLock:(DORLock *)lock timeOut:(NSTimeInterval)timeout forSuccess:(void (^)(DORRentalSession * session))successBlock failure:(failureBlock_t)failureBlock{
//we set the block to determine what happens
NSAssert(lock.peripheral, #"lock has to have peripheral to connect to");
if (!self.rentalSession) {
self.rentalSession = [[DORRentalSession alloc] initWithLock:nil andSessionDict:#{} active:NO];
}
self.rentalSession.lock = lock;
[self connectToLock:self.rentalSession.lock.peripheral timeOut:timeout completionBlock:^(CBPeripheral *peripheral, NSError *error) {
self.BTConnectionCompleted = nil;
if (!error) {
[[INTULocationManager sharedInstance] requestLocationWithDesiredAccuracy:INTULocationAccuracyHouse timeout:1 delayUntilAuthorized:YES block:^(CLLocation *currentLocation, INTULocationAccuracy achievedAccuracy, INTULocationStatus status) {
if (status == INTULocationStatusSuccess || status == INTULocationStatusTimedOut) {
[self startServerRentalForSessionLockWithUserLocation:currentLocation.coordinate forSuccess:^(DORRentalSession *session) {
if (self.rentalSession.lock.peripheral && self.rentalSession.lock.peripheral.state == CBPeripheralStateConnected) {
[self.rentalSession.lock.peripheral setNotifyValue:YES forCharacteristic:self.rentalSession.lock.charectaristics.sensorCharacteristic];
}else{
//shouldnt come here
}
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
if (self.rentalSession.lock.peripheral.state == CBPeripheralStateConnected) {
!self.rentalSession.lock.open ? [self sendUnlockBLECommandToSessionLock] : nil;
if (successBlock) {
successBlock(session);
}
}else{
[self endCurrentRentalSessionWithLocation:self.rentalSession.lock.latLng andPositionAcc:#(1) Success:^(DORRentalSession *session) {
if (failureBlock) {
failureBlock([[NSError alloc] initWithDomain:DonkeyErrorDomain code:46 userInfo:#{NSLocalizedDescriptionKey:#"Could't connect to lock"}],200);
}
} failure:^(NSError *error, NSInteger httpCode) {
if (failureBlock) {
failureBlock([[NSError alloc] initWithDomain:DonkeyErrorDomain code:45 userInfo:#{NSLocalizedDescriptionKey:#"fatal error"}],200);
}
}];
}
});
} failure:^(NSError *error, NSInteger httpCode) {
if (failureBlock) {
failureBlock(error,httpCode);
}
}];
}else{
NSError *gpsError = [self donkeyGPSErrorWithINTULocationStatus:status];
if (failureBlock) {
failureBlock(gpsError,200);
}
}
}];
}else{
if (failureBlock) {
failureBlock(error,200);
}
}
}];
}
To get rid of this nested calls you can use GCD group + serial execution queue:
dispatch_queue_t queue = ddispatch_queue_create("com.example.queue", NULL);
dispatch_group_t group = dispatch_group_create();
// Add a task to the group
dispatch_group_async(group, queue, ^{
// Some asynchronous work
});
// Make dispatch_group_async and dispatch_group_sync calls here
// Callback to be executed when all scheduled tasks are completed.
dispatch_group_notify(serviceGroup,dispatch_get_main_queue(),^{
// Do smth when everything has finished
});
// wait for all tasks to complete
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
The other solution based on GCD groups is described here
I'm working on the chat app, in chat screen my app need to check new message every 5s. I want to do that in background thread so my app will be not blocked UI. I tried the code below but when user typing the message, the UI seems to be blocked. Also, I cannot fire this task when user exit the chat screen.
This is my code I tried:
getLatestMessagesWithInterval() is called in viewwillAppear()
-(void) getLatestMessagesWithInterval
{
NSLog(#"GET MESSAGE INTERVAL");
[self retrieveLatestChatMessages];
// Call this method again using GCD
dispatch_queue_t q_background = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);
double delayInSeconds = 5.0;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, delayInSeconds * NSEC_PER_SEC);
dispatch_after(popTime, q_background, ^(void){
[self getLatestMessagesWithInterval];
});
}
-(void) retrieveLatestChatMessages
{
if([[NSUserDefaults standardUserDefaults] boolForKey:#"LoggedIn"]) {
NSDictionary * userDictionary = [[NSUserDefaults standardUserDefaults] dictionaryForKey:#"SessionDictionary"];
.....
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[[LSDataManager sharedDataManager] getLatestMessagesWithAuthKey:authenKey andLimit:limit withBlock:^ (NSDictionary* responseDict)
{
if (responseDict) {
[self loadDataFromServer:responseDict];
NSArray* lastMessageArray= nil;
//filter message data
if (self.MessagesArray.count >0) {
//perform data
self.TempdataSource = [[[ContentManager sharedManager] generateConversation:lastMessageArray withSenderID:self.senderID] mutableCopy];
//compare 2 arrays
if ([self.TempdataSource count] == [self.dataSource count]) {
NSLog(#"both are same");
}
else{
NSLog(#"both are different");
self.dataSource = [self.TempdataSource mutableCopy];
dispatch_async(dispatch_get_main_queue(), ^(){
//Add method, task you want perform on mainQueue
[self refreshMessages];
});
}
}
}
}
}];
});
}
}
I have called retrieveLatestChatMessages() to get message from server. the server will return in block. After performing the data, I reload tableview in main thread. Pls. help me to correct it. Thanks in advance.
Communicate with API asynchronously for example use AFNetworking and your UI will not be blocked, also you can set timer for every second and call something like that
if (numberOfSeconds % 5 == 0) {
numberOfSeconds = 0;
retrieveLatestChatMessages()
}
Two options
Option 1
Call that method every 5 seconds using NSTimer.
I would recommend not to use it.
Option 2
Have an app TCP based for chat messages.
This would be best option for chat messages.
Edit 1
learn how to use TCP as per your language. For iOS TCP follow link
http://www.tekritisoftware.com/sites/default/files/Socket_Programing_for_IOS.pdf
I'm trying to do a share operation where I call a function with async block but in my next if statement I need to get the value which is completed in the block to continue. This is my code which will highlight more detail. I heard about NSLock and tried using it but it didnt work, may be I'm doing something lock, I'm not much familiar with locks.
-(void) shareOperation
{
__block NSString *resultText;
BOOL continueSharing;
NSLock *conditionLock=[[NSLock alloc] init];
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(queue, ^{
[conditionLock lock];
[self performSomeAsynchronousOperation completionBlock:^(NSError *tError, bool status)
{
dispatch_async(dispatch_get_main_queue(),
^{
if (status)
{
resultText = #"Operation completed. Would you like to continue?";
}
else
{
resultText = #" Operation failed. Would you still like to continue?";
}
UIAlertView *result = [UIAlertView alertViewWithTitle:nil message:resultText cancelButtonTitle:#"Cancel" otherButtonTitles:[NSArray arrayWithObjects:#"OK",nil] onDismiss:^(int buttonIndex)
{
NSLog(#"selected button index: %d",buttonIndex);
if (buttonIndex == 0)
{
continueSharing=YES;
[conditionLock unlock];
NSLog(#"We are continuing sharing :)");
}
}onCancel:^{
continueSharing=NO;
[conditionLock unlock];
NSLog(#"cancelled");
}]; [result show];
});
}];
});
}
//should continue only after earlier if block finishes, ie after getting the continueSharing value
if (continueSharing)
{
[self performSomeAnotherAsynchronousOperation];
}
}
Rather than using locks (which are only designed to ensure that there is not simultaneous access to some shared resource), you could use semaphores (which are designed so that one thread can wait for a signal from another thread, which is needed here).
So, you should create a semaphore:
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
The alert view completion blocks would signal that semaphore:
dispatch_semaphore_signal(semaphore);
And where you want to wait for that signal (before performSomeAnotherAsynchronousOperation):
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
I tweaked your code a bit, but most notably, changed it so that it would not block the main queue (which you never want to do) by making sure the dispatch_semaphore_wait is done in a background queue. Also note that the dispatch_semaphore_signal is not inside the if statement. This resulted in:
- (void)shareOperation
{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
__block BOOL continueSharing = NO;
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
[self performSomeAsynchronousOperationWithCompletionBlock:^(NSError *tError, bool status){
dispatch_async(dispatch_get_main_queue(),^{
NSString *resultText;
if (status)
resultText = #"Operation completed. Would you like to continue?";
else
resultText = #"Operation failed. Would you still like to continue?";
UIAlertView *alertView = [UIAlertView alertViewWithTitle:nil message:resultText cancelButtonTitle:#"Cancel" otherButtonTitles:#[#"OK"] onDismiss:^(int buttonIndex) {
NSLog(#"selected button index: %d",buttonIndex);
if (buttonIndex == 0) {
continueSharing = YES;
NSLog(#"We are continuing sharing :)");
}
dispatch_semaphore_signal(semaphore);
} onCancel:^{
dispatch_semaphore_signal(semaphore);
NSLog(#"cancelled");
}];
[alertView show];
});
}];
// should continue only after earlier if block finishes, ie after getting the continueSharing value
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
if (continueSharing)
[self performSomeAnotherAsynchronousOperation];
});
}
Even better, you should not use any blocking mechanism like semaphores (certainly not on the main queue), but rather one should simply have the completion block for the alert view initiate the next step of the process directly, itself. I understand that you say that this is not practical in your scenario, but it is generally the correct way to handle these scenarios.
You can use the delay block
dispatch_time_t delayTime = dispatch_time(DISPATCH_TIME_NOW, 0.01 * NSEC_PER_SEC);
dispatch_after(delayTime, dispatch_get_main_queue(), ^(void){
})
I am trying to setup Reachability using the new 2.0 AFNetworking.
In my AppDelegate I initialise the sharedManager.
// Instantiate Shared Manager
[AFNetworkReachabilityManager sharedManager];
Then in the relevant VC method I check to see if isReachable:
// Double check with logging
if ([[AFNetworkReachabilityManager sharedManager] isReachable]) {
NSLog(#"IS REACHABILE");
} else {
NSLog(#"NOT REACHABLE");
}
At present this is not working as expected in the simulator, but I imagine this would need to be tested on device and not simulator.
Question
What I would like to do is monitor the connectivity within the VC. So I run the following in the viewDidLoad:
// Start monitoring the internet connection
[[AFNetworkReachabilityManager sharedManager] startMonitoring];
How would I then register for the changes? What is/would be called once the network connection changes I cannot see this from the documentation.
As you can read in the AFNetworking read me page
[[AFNetworkReachabilityManager sharedManager] setReachabilityStatusChangeBlock:^(AFNetworkReachabilityStatus status) {
NSLog(#"Reachability: %#", AFStringFromNetworkReachabilityStatus(status));
}];
Here's also a link to the official documentation.
I have a singleton AFHTTPRequestOperationManager class. In the singleton has a method:
+(void)connectedCompletionBlock:(void(^)(BOOL connected))block {
[[AFNetworkReachabilityManager sharedManager] startMonitoring];
[[AFNetworkReachabilityManager sharedManager] setReachabilityStatusChangeBlock:^(AFNetworkReachabilityStatus status) {
BOOL con = NO;
NSLog(#"Reachability: %#", AFStringFromNetworkReachabilityStatus(status));
if (status == AFNetworkReachabilityStatusReachableViaWWAN || status == AFNetworkReachabilityStatusReachableViaWiFi) {
con = YES;
}
if (block) {
[[AFNetworkReachabilityManager sharedManager] stopMonitoring];
block(con);
}
}];
}
Before make a request you call this method that return a block indicating if internet is reachable:
[TLPRequestManager connectedCompletionBlock:^(BOOL connected) {
if (connected) {
// Make a request
}else{
UIAlertView *alertView = [[UIAlertView alloc]initWithTitle:#"Notice" message:#"Internet is not available." delegate:nil cancelButtonTitle:#"Ok" otherButtonTitles: nil];
[alertView show];
}
}];
I was just going through your question and all the answers. After that I decided to do all these things once. So, in my existing project I just included the AFNetworking through cocoa-pods and here is the solution which is woking for me completely.
Solution -- First of all AFNetworkReachabilityManager is a singleton class. You don't need to do AppDelegate initialisation for sharedManager.
//[AFNetworkReachabilityManager sharedManager];
#import <AFNetworkReachabilityManager.h>
- (void)viewDidLoad {
//Starting the network monitoring process
[[AFNetworkReachabilityManager sharedManager]startMonitoring];
//Checking the Internet connection...
[[AFNetworkReachabilityManager sharedManager]setReachabilityStatusChangeBlock:^(AFNetworkReachabilityStatus status){
if (status == AFNetworkReachabilityStatusReachableViaWWAN || status == AFNetworkReachabilityStatusReachableViaWiFi) {
UIAlertView *alertNetFound = [[UIAlertView alloc]initWithTitle:#"Network Found" message:#"Please Wait Until It is loading" delegate:self cancelButtonTitle:#"OK" otherButtonTitles:nil, nil];
[alertNetFound show];
}else{
UIAlertView *alertNetNotFound = [[UIAlertView alloc]initWithTitle:#"No Internet" message:#"Please Check Your Internet Connection Honey" delegate:self cancelButtonTitle:#"OK" otherButtonTitles:nil, nil];
[alertNetNotFound show];
}
}];
So, in this case every time the device connects to a network, it will do the startMonitoring process first and after that it will hit the status block every time and will display alert according to the status.
You can do anything according to your choice by replacing the alerts on the status block. I used this to load an webpage automatically from local storage but I removed that code for simplicity.
Its even working with my simulator and Mac mini..
Thanks
Hope this helped.
I use this in the app delegate ->
func reachablityCode() {
AFNetworkReachabilityManager.sharedManager()
AFNetworkReachabilityManager.sharedManager().startMonitoring()
AFNetworkReachabilityManager.sharedManager().setReachabilityStatusChangeBlock({(status) in
let defaults = NSUserDefaults.standardUserDefaults()
if status == .NotReachable {
defaults.setBool(false, forKey:REACHABLE_KEY)
}
else {
defaults.setBool(false, forKey: REACHABLE_KEY)
}
defaults.synchronize()
})
}
And then this in the base file ->
func isReachable() -> Bool {
return NSUserDefaults.standardUserDefaults().boolForKey(REACHABLE_KEY)
}
I am using this method to ask a nearby device to join the session:
When I do it I also start spinning an indicator
[browser invitePeer:key
toSession:session
withContext:nil
timeout:30];
Is there a method called in the moment of timeout? what if the other device goes out of range?
EDIT:
I notice that this state is never called:
if (state == MCSessionStateConnecting) {
NSLog(#"CONNECTING %#", peerID);
}
in case of timeouts on the browser side, you need to watch for the MCSessionStateNotConnected state. i do something like this:
- (void)session:(MCSession *)session
peer:(MCPeerID *)peerID
didChangeState:(MCSessionState)state
{
if (state == MCSessionStateNotConnected)
{
if (self.isWaitingForInvitation)
{
UIAlertView *alertView = [[UIAlertView alloc]
initWithTitle:NSLocalizedString(#"ERROR_TITLE", nil)
message:NSLocalizedString(#"ERROR_TEXT", nil)
delegate:self
cancelButtonTitle:NSLocalizedString(#"NO", #"Não")
otherButtonTitles:NSLocalizedString(#"YES", #"Sim"),
nil];
dispatch_sync(dispatch_get_main_queue(), ^{
[alertView show];
});
self.isWaitingForInvitation = NO;
}
}
use the dispatch_sync to make the alert popup right away.
Using a timer with a timer interval matching timeout parameter could be better idea.