I would like to check "reachability" before/when a webView is shown in my app.
So far I've included the reachbility.h/.m files as well as added the SystemConfiguration.framework to the project.
And that's about where the agreements I have found on the internet end, from all of the posts and blogs etc. etc. everyone has a different idea/opinion of what to do after that. Additionally, I have found a lot partial code snippets that aren't really a complete solution, on which reachibility methods to call etc. etc. how to use them etc.
I have also found that some warn that you should try to reconnect before checking reachability...but I haven't found a consensus or a full solution. My app seems to reconnect to wifi without any extra code... so I'm a litte confused here too...
Any help to clear the muddy waters would be appreciated. I'm just looking for a simple straightforward solution.
Answer Accepted: I would like to note to newbies who may read this q/a later... that you will want to do the following:
Add this into your .h file:
- (BOOL) connectedToNetwork: (NSString *) remoteServer;
- (void) appLoadError: (NSString *) altertTitle alertMessage: (NSString *) altertMsg;
And you will need to import these at the top of your .m file:
sys/socket.h
netinet/in.h
netinet6/in6.h
arpa/inet.h
ifaddrs.h
netdb.h
SystemConfiguration/SystemConfiguration.h
Correct me if it is wrong... It seems to work fine for me...
I have always used this method in my App Delegate when I need to require Internet access. I have tuned it for different access types over time and it has served me well. It is a variant of one of the many methods you can find after a quick Google search on this topic.
It is a tricky thing to come up with a hard and fast strategy around this. The platform itself offers different connectivity options that have pros and cons based on the needs of each specific application. The method I use below is just a general connectivity test meaning the device can reach the Internet via some connectivity mechanism.
- (BOOL) connectedToNetwork: (NSString *) remoteServer {
// Create zero addy
struct sockaddr_in zeroAddress;
bzero(&zeroAddress, sizeof(zeroAddress));
zeroAddress.sin_len = sizeof(zeroAddress);
zeroAddress.sin_family = AF_INET;
// Recover reachability flags
SCNetworkReachabilityRef defaultRouteReachability = SCNetworkReachabilityCreateWithAddress(NULL, (struct sockaddr *)&zeroAddress);
SCNetworkReachabilityFlags flags;
BOOL didRetrieveFlags = SCNetworkReachabilityGetFlags(defaultRouteReachability, &flags);
CFRelease(defaultRouteReachability);
if (!didRetrieveFlags){
NSLog(#"Error. Could not recover network reachability flags");
return NO;
}
BOOL isReachable = flags & kSCNetworkFlagsReachable;
BOOL needsConnection = flags & kSCNetworkFlagsConnectionRequired;
BOOL nonWiFi = flags & kSCNetworkReachabilityFlagsTransientConnection;
NSURL *testURL = [NSURL URLWithString: remoteServer];
NSURLRequest *testRequest = [NSURLRequest requestWithURL:testURL cachePolicy:NSURLRequestReloadIgnoringLocalCacheData timeoutInterval:20.0];
NSURLConnection *testConnection = [[NSURLConnection alloc] initWithRequest:testRequest delegate:self];
return ((isReachable && !needsConnection) || nonWiFi) ? (testConnection ? YES : NO) : NO;
}
I usually call this inside applicationDidFinishLaunching. If the check returns false, I usually generate an error message (see below). This is because Apple forbids exiting the application by any means other than the home button on the device.
...
else if(![self connectedToNetwork: [NSString stringWithFormat: #"http://%#:%#/", sharedSettings.server, sharedSettings.port]]){
[self appLoadError: #"No Internet Detected" alertMessage:#"This application requires an active Internet connection. No content is available."];
}
...
- (void) appLoadError: (NSString *) altertTitle alertMessage: (NSString *) altertMsg {
UIAlertView *loadErr = [[UIAlertView alloc] initWithTitle: altertTitle message: altertMsg delegate: self cancelButtonTitle: #"OK" otherButtonTitles: nil];
[loadErr show];
[loadErr release];
// Load static screen
UIImage *image = [UIImage imageNamed:#"Error_NoInternet.png"];
UIImageView *imageView = [[UIImageView alloc] initWithImage:image];
[controller setNavigationBarHidden: YES animated: NO];
[window addSubview: imageView];
[imageView release];
}
Just a quick note, I believe that method is leaking an NSURLConnection on every call (at the bottom, testConnection).
You might want to use
+ (NSURLConnection *)connectionWithRequest:
(NSURLRequest *)request delegate:(id)delegate
instead.
Related
First of all the questions are failry simiple.. if you just want to see what they are skip to the bottom of this post and you will see them in bold.. for more detail then you can read the rest of this post...
I am just trying to iron out my NSURLConnection so that its working smoothly and I understand this properly. There is a profound lack of example/tutorials for Asynchronous connections on the internet or not any that I can find that explaine what is going on with any level of depth other than getting the connection up and running which after working on it seems pretty simple. Hopefully this question can full the void that I feel is out there for other users.
So, in my .h file i have imported the foundations headers and declared the methods required for the received or lack of received data (errors etc).
.h
#import <UIKit/UIKit.h>
#import <Foundation/Foundation.h> //add foundations
//.. other headers can be imported here
#interface MyViewController: UITableViewController {
//Im not setting any delegates to access the methods because Is all happening in the same
//place so I just use the key word 'self' when accessing the methods declared below
//I'm not sure if this is the best thing to do but I wasn't able to get my head around declaring the delegate or how it would help me with the way I have set up my request etc.
}
- (IBAction)setRequestString:(NSString *)string; //this method sets the request and connection methods
//these methods receive the response from my async nsurlconnection
- (void)receivedData:(NSData *)data;
- (void)emptyReply;
- (void)timedOut;
- (void)downloadError:(NSError *)error;
So thats my header file.. pretty simple not much explaining needed.
.m
//call setRequestString from some other method attached to a button click or something
[self setRequestString:#"rss.xml"];
//..
- (IBAction)setRequestString:(NSString *)string
{
//Set database address
NSMutableString *databaseURL = [[NSMutableString alloc] initWithString:#"http:www.comicbookresources/feeds/"]; // address not real jsut example
//append the string coming in to the end of the databaseURL
[databaseURL appendString:string];
//prepare NSURL with newly created string
NSURL *url = [NSURL URLWithString:databaseURL];
//AsynchronousRequest to grab the data
NSURLRequest *request = [NSURLRequest requestWithURL:url];
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[NSURLConnection sendAsynchronousRequest:request queue:queue completionHandler:^(NSURLResponse *response, NSData *data, NSError *error)
{
if ([data length] > 0 && error == nil){
[self receivedData:data];
}else if ([data length] == 0 && error == nil){
[self emptyReply];
}else if (error != nil && error.code == NSURLErrorTimedOut){ //used this NSURLErrorTimedOut from foundation error responses
[self timedOut];
}else if (error != nil){
[self downloadError:error];
}
}];
}
now set up the methods that were initialized in the .h file and called in the if statement above
- (void)receivedData:(NSData *)data
{
NSString* newStr = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
NSLog(#"%#", newStr); //logs recived data
//now you can see what data is coming in on your log
//do what you want with your data here (i.e. start parsing methods
}
- (void)emptyReply
{
//not sure what to do here yet?
}
- (void)timedOut
{
//also not sure what to do here yet?
}
- (void)downloadError:(NSError *)error
{
NSLog(#"%#", error);
UIAlertView *errorAlert = [[UIAlertView alloc] initWithTitle:#"Error!" message:#"A connection failure occurred." delegate:self cancelButtonTitle:#"OK" otherButtonTitles:nil, nil];
[errorAlert show];
}
Cool so that pretty much the basics of what I have done right there.. now the questions I have are as follows.
Question one:
Where I call NSURLConnection like so
[NSURLConnection sendAsynchronousRequest:request queue:queue completionHandler:^(NSURLResponse *response, NSData *data, NSError *error)
{
What is happening here what is the ^ for is that executing that whole block (including the if statements) on a different thread or something? because it looks alot like grand central dispatch formatting but slightly different.
Question two:
what should I be doing inside emptyReply & timedOut methods?
Question three:
How would I incorporate caching into this? I would like to cache the responses I get back from different requests. i.e. with my setRequestString you will see there is a string input parameter, so i can request different rss feeds with the same method.. I need to figure out how to cache these responses into individual caches.. but im not sure where to start with it.
Finally
If you have made it this far, thank you very much for reading my question. Hopefully with your responses we can get a pretty nice solution going here.. that other people can use for themselves and pick and choose the bits and peices they need that works for there own solution..
Anyway thank you very much for reading and I look forward to your replies.. even if they are just refrences to tutorials or examples you think might help me.. anything is good I just want to fully understand whats going on and whats a good solution.
Read about blocks in Apple documentation. Its new. Or you can read here
You can show errors such as request timed out etc. You don't really have to handle them separately than the error one unless you have special logic.
Try this for caching
NSURLRequest *urlRequest = [NSURLRequest requestWithURL:url cachePolicy:NSURLRequestReloadIgnoringLocalAndRemoteCacheData timeoutInterval:TIMEOUT_INTERVAL];
Originally I needed the ability to use the search API with twitter. I did this using Matt Gemmell's great MGTwitterEngine. That code was very very simple and looked something like this:
- (void)viewDidLoad {
[super viewDidLoad];
tweetArrays = nil;
tweetNameArray = nil;
NSString *username = #"<username>";
NSString *password = #"<password>";
NSString *consumerKey = #"<consumerKey>";
NSString *consumerSecret = #"<consumerSecret>";
// Most API calls require a name and password to be set...
if (! username || ! password || !consumerKey || !consumerSecret) {
NSLog(#"You forgot to specify your username/password/key/secret in AppController.m, things might not work!");
NSLog(#"And if things are mysteriously working without the username/password, it's because NSURLConnection is using a session cookie from another connection.");
}
// Create a TwitterEngine and set our login details.
twitterEngine = [[MGTwitterEngine alloc] initWithDelegate:self];
[twitterEngine setUsesSecureConnection:NO];
[twitterEngine setConsumerKey:consumerKey secret:consumerSecret];
// This has been undepreciated for the purposes of dealing with Lists.
// At present the list API calls require you to specify a user that owns the list.
[twitterEngine setUsername:username];
[twitterEngine getSearchResultsForQuery:#"#HelloWorld" sinceID:0 startingAtPage:1 count:100];
}
This would end up calling the function:
- (void)searchResultsReceived:(NSArray *)searchResults forRequest:(NSString *)connectionIdentifier
And then I could do what I wanted with the searchResults. This required me to include the yajl library.
I then wanted to expand my code to allow users to tweet. I downloaded Ben Gottlieb's great code Twitter-OAuth-iPhone
So there's only one problem. The getSearchResultsForQuery returns a requestFailed with the following error:
Error Domain=HTTP Code=400 "The operation couldn’t be completed. (HTTP error 400.)"
To call this code I simply took the demo project in Twitter-OAuth-iPhone and added a call to getSearchResultsForQuery as seen here:
- (void) viewDidAppear: (BOOL)animated {
if (_engine) return;
_engine = [[SA_OAuthTwitterEngine alloc] initOAuthWithDelegate: self];
_engine.consumerKey = kOAuthConsumerKey;
_engine.consumerSecret = kOAuthConsumerSecret;
UIViewController *controller = [SA_OAuthTwitterController controllerToEnterCredentialsWithTwitterEngine: _engine delegate: self];
if (controller)
[self presentModalViewController: controller animated: YES];
else {
[_engine getSearchResultsForQuery:#"HelloWorld"];
// [_engine sendUpdate: [NSString stringWithFormat: #"Already Updated. %#", [NSDate date]]];
}
}
This as stated above returns a 400 error. Every other twitter API call I add here does work such as:
- (NSString *)getRepliesStartingAtPage:(int)pageNum;
Am I doing anything wrong? Or does getSearchResultsForQuery no longer work? The two code bases seem to use different versions of MGTwitterEngine, could that be causing the problem?
Thanks!
The problem is that you have to instantiate the twitter engine as an instance of SA_OAuthTwitterEngine, instead of MGTwitterEngine. When you call getSearchResultsForQuery, it uses a call to _sendRequestWithMethod to actually send the search request. SA_OAuthTwitterEngine overrides the MGTwitterEngine's implementation, but its version of that function doesn't work with getSearchResultsForQuery.
So, you need to go to getSearchResultsForQuery and make it use the MGTwitterEngine's version of the function.
I'm making an application in Xcode, and running into some problems. I'm using the GameKit framework to allow for bluetooth communication between two iOS devices. The application is setup so that one of the devices is the "master" and the other is the "slave," changing it's screen content based on data received from the "master" device. The user can select whether to be the master or the slave, and when that choice is made, the other device automatically becomes the opposite role. This is all done in one view controller class. When a role is chosen, a subview is added to the baseViewController.
What my problem is, is that when the subview that is added, I would like to be able to send data using the methods in the baseViewController class. With the current setup, the device invoking the action becomeMaster:sender crashes.
What I've tried so far is,
BaseViewController:
-(IBAction)becomeMaster:(id)sender {
[self dataToSend:#"slave"]; //tells peer device to become slave, since this device is master
masterViewController = [[MasterViewController alloc] initWithNibName:#"MasterViewController" bundle:nil];
[masterViewController setBaseViewController:self];
[self.view addSubview:masterViewController.view];
}
-(void)dataToSend:(NSString *)direction {
//—-convert an NSString object to NSData—-
NSData* data;
NSString *str = [NSString stringWithString:direction];
data = [str dataUsingEncoding: NSASCIIStringEncoding];
[self mySendDataToPeers:data];
}
-(void)dataToSend:(NSString *)direction {
//—-convert an NSString object to NSData—-
NSData* data;
NSString *str = [NSString stringWithString:direction];
data = [str dataUsingEncoding: NSASCIIStringEncoding];
[self mySendDataToPeers:data];
}
//----------------------------------------------------------------------------//
- (void)receiveData:(NSData *)data fromPeer:(NSString *)peer inSession:(GKSession *)session context:(void *)context {
//—-convert the NSData to NSString—-
NSString* str;
str = [[NSString alloc] initWithData:data encoding:NSASCIIStringEncoding];
[self useReceivedData:str];
[str release];
}
-(void)useReceivedData:(NSString *)str {
if ([str isEqualToString:#"forward"]) {
[slaveViewController.view setBackgroundColor:[UIColor blackColor]];
}
}
MasterViewController:
-(void)setBaseViewController:(BaseViewController *)bvc {
baseViewController = bvc;
}
-(IBAction)goForward:(id)sender {
actionLabel.text = #"goingForward";
[baseViewController dataToSend:#"forward"];
}
Most of that code is part of the standard Apple documentation/examples, but I included it for understanding the flow of logic.
I believe the problem originates to with the becomeMaster:sender and setBaseViewController:bvc methods. Could anyone help fix? Thanks so much!
What kind of crash are you getting? EXC_BAD_ACCESS? Try turning on NSZombieEnabled in your executable's arguments. It's difficult to say what could be causing the crash, but you might try changing your setBaseViewController: implementation to this:
-(void)setBaseViewController:(BaseViewController *)bvc {
[self willChangeValueForKey:#"baseViewController"];
[baseViewController autorelease]
baseViewController = [bvc retain];
[self didChangeValueForKey:#"baseViewController"];
}
And add [baseViewController release]; to MasterViewController's -dealloc method.
Keep in mind that it's not entirely necessary to have a custom setter for baseViewController. If you have the following property declaration in your header file:
#property (nonatomic, retain) BaseViewController *baseViewController;
And you use #synthesize baseViewController, the -setBaseViewController: method is already generated for you, with key-value observing support built in. If you aren't familiar with Objective-C 2.0 properties, I suggest reading Apple's documentation.
I wanted to check whether internet is connected or not using either the SystemConfiguration or the CFNetwork i am not quite sure which one.
Then i want to know that if the internet is connected then is it connected through wifi or not.
I tried an example where i used the below code
-(IBAction) shownetworkStatus
{
NSString *str = #"http://www.google.com";
NSURL *url = [NSURL URLWithString:str];
NSURLRequest *req = [NSURLRequest requestWithURL:url];
if (req!=NULL) {
lbl.text = #"Connected";
}
else {
lbl.text = #"notConnected";
}
}
some say that its not valid as per apple and you have to use the SystemConfiguration Framework, Please let me know what needs to be done.
Also i personally think that what i am doing in the above code is not proper as if one day google may also be down due to maintenance or some other factors.
Also if you could provide me a link where i could display the name of the WIFI network then it would be really cool.
I searched the internet then i got these Reachability.h code which again is a bouncer as i wana learn the concepts not copy paste them
Thanks and Regards
Building on top of what Raxit mentions, I use the following code (extracted from the reachability example mentioned by Raxit) to check for internet access in my application delegate:
- (BOOL)isReachableWithoutRequiringConnection:(SCNetworkReachabilityFlags)flags
{
BOOL isReachable = flags & kSCNetworkReachabilityFlagsReachable;
BOOL noConnectionRequired = !(flags & kSCNetworkReachabilityFlagsConnectionRequired);
if ((flags & kSCNetworkReachabilityFlagsIsWWAN)) {
noConnectionRequired = YES;
}
return (isReachable && noConnectionRequired) ? YES : NO;
}
- (BOOL)isHostReachable:(NSString *)host
{
if (!host || ![host length]) {
return NO;
}
SCNetworkReachabilityFlags flags;
SCNetworkReachabilityRef reachability = SCNetworkReachabilityCreateWithName(NULL, [host UTF8String]);
BOOL gotFlags = SCNetworkReachabilityGetFlags(reachability, &flags);
CFRelease(reachability);
if (!gotFlags) {
return NO;
}
return [self isReachableWithoutRequiringConnection:flags];
}
- (BOOL)connectedToNetwork {
return [self isHostReachable:#"www.hostyoureallycareabouthavingaconnectionwith.com"];
}
You can use the reachability code provided by Apple. It is sample code. you can find whole detail regarding internet connection. This is the link http://developer.apple.com/library/ios/#samplecode/Reachability/Introduction/Intro.html
I need to be able to send out a UDP message and also receive one in order to discover SSDP devices on the network from the iPhone.
I know that I need to send the packet to the multicast address and my HTTP request needs to look something like this:
M-SEARCH * HTTP/1.1
Host: 239.255.255.250:1900
Man: ssdp:discover
Mx: 3
ST: "urn:schemas-upnp-org:device:InternetGatewayDevice:1"
From reading the docs it appears that I can do all this with CFNetwork and despite reading (and re-reading the docs) I am struggling to get started. Can anyone recommend and tutorials or code snippets to get me over the initial learning hump?
I've got the CFNetwork programming guide:
http://developer.apple.com/mac/library/documentation/Networking/Conceptual/CFNetwork/CFNetwork.pdf
and Beej's Guide to Network programming Using Internet Sockets:
http://beej.us/guide/bgnet/
Thanks
Dave
P.S.
I am unable to use any of the 3rd party libraries and frameworks in this instance.
I have used AsyncUdpSocket successfully to run SSDP Discovery and find controllers. Here are my code snippets:
Initialize and setup the socket:
// AsyncUdpSocket *ssdpSock = [[AsyncUdpSocket alloc] initWithDelegate:self];
AsyncUdpSocket *ssdpSock = [[AsyncUdpSocket alloc] initIPv4];
[ssdpSock setDelegate:self];
Note the first line commented out. I found on the AsyncUdpSocket forums some issues with duplicates. I don't think I was facing them but I did it anyhow.
I added error checking, and it was useful because during my debugging I wasn't closing sockets and I started getting socket setup failures:
NSError *socketError = nil;
if (![ssdpSock bindToPort:1900 error:&socketError]) {
NSLog(#"Failed binding socket: %#", [socketError localizedDescription]);
return statusController;
}
if(![ssdpSock joinMulticastGroup:#"239.255.255.250" error:&socketError]){
NSLog(#"Failed joining multicast group: %#", [socketError localizedDescription]);
return statusController;
}
if (![ssdpSock enableBroadcast:TRUE error:&socketError]){
NSLog(#"Failed enabling broadcast: %#", [socketError localizedDescription]);
return statusController;
}
[ssdpSock sendData:[self.discoverControllerString dataUsingEncoding:NSUTF8StringEncoding]
toHost:#"239.255.255.250"
port:1900
withTimeout:2
tag:1];
Notice the changes I have made to the time out. And then finally did the receive setup, and closed the socket. Note the socket close. Since I am in my own class when I am running this - the code above did not work for me.
[ssdpSock receiveWithTimeout: 2 tag:1];
[NSTimer scheduledTimerWithTimeInterval: 5 target: self
selector:#selector(completeSearch:) userInfo: self repeats: NO];
[ssdpSock closeAfterSendingAndReceiving];
The most important change probably was returning "NO" if I did not find my controller. The first receive was incidentally the discovery message itself coming back. And when I read through the AsyncUdpSocket.h file carefully - returning "NO" when it is not a packet you are looking for helped.
Also note that I am using ARC in my code but I compiled the AsyncUdpSocket without ARC support.
-(void) completeSearch: (NSTimer *)t
{
NSLog(#"%s",__FUNCTION__);
//[ssdpSock close];
//ssdpSock = nil;
}
- (BOOL)onUdpSocket:(AsyncUdpSocket *)sock
didReceiveData:(NSData *)data
withTag:(long)tag
fromHost:(NSString *)host
port:(UInt16)port
{
NSLog(#"%s %ld %# %d",__FUNCTION__,tag,host,port);
NSString *aStr = [[NSString alloc] initWithData:data encoding:NSASCIIStringEncoding];
NSLog(#"%#",aStr);
NSString *compareString = [aStr stringByPaddingToLength:[self.responseString length] withString:#"." startingAtIndex:0];
//NSLog(#"%#", compareString);
//NSLog(#"%#", self.responseString);
if ([compareString isEqualToString:self.responseString])
{
NSLog(#"String Compare, Controller Found!");
[self.controllerList addObject:aStr];
//NSData *controllerIP = [aStr dataUsingEncoding:NSUTF8StringEncoding];
[[NSNotificationCenter defaultCenter] postNotificationName:#"DiscoveredController" object:nil];
return YES;
}
return NO;
}
I have the following code for SSDP search in my app:
-(void)discoverDevices {
ssdpSock = [[AsyncUdpSocket alloc] initWithDelegate:self];
[ssdpSock enableBroadcast:TRUE error:nil];
NSString *str = #"M-SEARCH * HTTP/1.1\r\nHOST: 239.255.255.250:1900\r\nMan: \"ssdp:discover\"\r\nST: mydev\r\n\r\n";
[ssdpSock bindToPort:0 error:nil];
[ssdpSock joinMulticastGroup:#"239.255.255.250" error:nil];
[ssdpSock sendData:[str dataUsingEncoding:NSUTF8StringEncoding]
toHost: #"239.255.255.250" port: 1900 withTimeout:-1 tag:1];
[ssdpSock receiveWithTimeout: -1 tag:1];
[NSTimer scheduledTimerWithTimeInterval: 5 target: self
selector:#selector(completeSearch:) userInfo: self repeats: NO]; }
-(void) completeSearch: (NSTimer *)t {
NSLog(#"%s",__FUNCTION__);
[ssdpSock close];
ssdpSock = nil;}
- (BOOL)onUdpSocket:(AsyncUdpSocket *)sock didReceiveData:(NSData *)data withTag:(long)tag fromHost:(NSString *)host port:(UInt16)port{
NSLog(#"%s %d %# %d",__FUNCTION__,tag,host,port);
NSString *aStr = [[NSString alloc] initWithData:data encoding:NSASCIIStringEncoding];
NSLog(#"%#",aStr);}
It uses the AsyncUdpSocket from CocoaAsyncSocket.
OK, finally done it. Found a class in the public domain (thanks Chris) called AsyncUdpSocket that lets you create a UDP socket which you can then turn on broadcasting and join the multicast address.
There is a nice sendData method, complete with adding to a run loop to prevent blocking.
Hope that helps.
Dave