No Internet Connection Handling on UIWebView and NSURLRequest [duplicate] - objective-c

This question already has answers here:
How can I check for an active Internet connection on iOS or macOS?
(46 answers)
Closed 9 years ago.
I have an app which is entirely web-based and needs an internet connection to navigate around. Basically a website viewed through a UIWebView.
I need to be able to tell the user that no pages can load if they have no internet connection. Is there a simple way I can do this. Perhaps a check if NSURLRequest failed?
Cheers

I would look at Apple's Reachability sample code to implement this reliably. One advantage of this approach is that you can notify the user as to current network status even the user isn't actually clicking on any links in the web view.

please check the following
stackoverflow1
stackoverflow2
stackoverflow3

1>Add SystemConfiguration.framework to your project
2>import following .h files in your Connection.h file
#import <sys/socket.h>
#import <netinet/in.h>
#import <SystemConfiguration/SystemConfiguration.h>
3>declare the following class method in your Connection.h file
+(BOOL)hasConnectivity;
4>define this method in your Connection.m file
+(BOOL)hasConnectivity {
struct sockaddr_in zeroAddress;
bzero(&zeroAddress, sizeof(zeroAddress));
zeroAddress.sin_len = sizeof(zeroAddress);
zeroAddress.sin_family = AF_INET;
SCNetworkReachabilityRef reachability = SCNetworkReachabilityCreateWithAddress(kCFAllocatorDefault, (const struct sockaddr*)&zeroAddress);
if(reachability != NULL) {
//NetworkStatus retVal = NotReachable;
SCNetworkReachabilityFlags flags;
if (SCNetworkReachabilityGetFlags(reachability, &flags)) {
if ((flags & kSCNetworkReachabilityFlagsReachable) == 0)
{
// if target host is not reachable
return NO;
}
if ((flags & kSCNetworkReachabilityFlagsConnectionRequired) == 0)
{
// if target host is reachable and no connection is required
// then we'll assume (for now) that your on Wi-Fi
return YES;
}
if ((((flags & kSCNetworkReachabilityFlagsConnectionOnDemand ) != 0) ||
(flags & kSCNetworkReachabilityFlagsConnectionOnTraffic) != 0))
{
// ... and the connection is on-demand (or on-traffic) if the
// calling application is using the CFSocketStream or higher APIs
if ((flags & kSCNetworkReachabilityFlagsInterventionRequired) == 0)
{
// ... and no [user] intervention is needed
return YES;
}
}
if ((flags & kSCNetworkReachabilityFlagsIsWWAN) == kSCNetworkReachabilityFlagsIsWWAN)
{
// ... but WWAN connections are OK if the calling application
// is using the CFNetwork (CFSocketStream?) APIs.
return YES;
}
}
}
return NO;
}

Related

How to properly close a FFmpeg stream and AVFormatContext without leaking memory?

I have built an app that uses FFmpeg to connect to remote IP cameras in order to receive video and audio frames via RTSP 2.0.
The app is built using Xcode 10-11 and Objective-C with a custom FFmpeg build config.
The architecture is the following:
MyApp
Document_0
RTSPContainerObject_0
RTSPObject_0
RTSPContainerObject_1
RTSPObject_1
...
Document_1
...
GOAL:
After closing Document_0 no FFmpeg objects should be leaked.
The closing process should stop-frame reading and destroy all objects which use FFmpeg.
PROBLEM:
Somehow Xcode's memory debugger shows two instances of MyApp.
FACTS:
macOS'es Activity Monitor doesn't show two instances of MyApp.
macOS'es Activity Monitor doesn't any instances of FFmpeg or other child processes.
The issue is not related to some leftover memory due to a late memory snapshot since it can be reproduced easily.
Xcode's memory debugger shows that the second instance only having RTSPObject's AVFormatContext and no other objects.
The second instance has an AVFormatContext and the RTPSObject still has a pointer to the AVFormatContext.
FACTS:
Opening and closing the second document Document_1 leads to the same problem and having two objects leaked. This means that there is a bug that creates scalable problems. More and more memory is used and unavailable.
Here is my termination code:
- (void)terminate
{
// * Video and audio frame provisioning termination *
[self stopVideoStream];
[self stopAudioStream];
// *
// * Video codec termination *
avcodec_free_context(&_videoCodecContext); // NULL pointer safe.
self.videoCodecContext = NULL;
// *
// * Audio codec termination *
avcodec_free_context(&_audioCodecContext); // NULL pointer safe.
self.audioCodecContext = NULL;
// *
if (self.packet)
{
// Free the packet that was allocated by av_read_frame.
av_packet_unref(&packet); // The documentation doesn't mention NULL safety.
self.packet = NULL;
}
if (self.currentAudioPacket)
{
av_packet_unref(_currentAudioPacket);
self.currentAudioPacket = NULL;
}
// Free raw frame data.
av_freep(&_rawFrameData); // NULL pointer safe.
// Free the swscaler context swsContext.
self.isFrameConversionContextAllocated = NO;
sws_freeContext(scallingContext); // NULL pointer safe.
[self.audioPacketQueue removeAllObjects];
self.audioPacketQueue = nil;
self.audioPacketQueueLock = nil;
self.packetQueueLock = nil;
self.audioStream = nil;
BXLogInDomain(kLogDomainSources, kLogLevelVerbose, #"%s:%d: All streams have been terminated!", __FUNCTION__, __LINE__);
// * Session context termination *
AVFormatContext *pFormatCtx = self.sessionContext;
BOOL shouldProceedWithInputSessionTermination = self.isInputStreamOpen && self.shouldTerminateStreams && pFormatCtx;
NSLog(#"\nTerminating session context...");
if (shouldProceedWithInputSessionTermination)
{
NSLog(#"\nTerminating...");
//av_write_trailer(pFormatCtx);
// Discard all internally buffered data.
avformat_flush(pFormatCtx); // The documentation doesn't mention NULL safety.
// Close an opened input AVFormatContext and free it and all its contents.
// WARNING: Closing an non-opened stream will cause avformat_close_input to crash.
avformat_close_input(&pFormatCtx); // The documentation doesn't mention NULL safety.
NSLog(#"Logging leftovers - %p, %p %p", self.sessionContext, _sessionContext, pFormatCtx);
avformat_free_context(pFormatCtx);
NSLog(#"Logging content = %c", *self.sessionContext);
//avformat_free_context(pFormatCtx); - Not needed because avformat_close_input is closing it.
self.sessionContext = NULL;
}
// *
}
IMPORTANT: The termination sequence is:
New frame will be read.
-[(RTSPObject)StreamInput currentVideoFrameDurationSec]
-[(RTSPObject)StreamInput frameDuration:]
-[(RTSPObject)StreamInput currentCGImageRef]
-[(RTSPObject)StreamInput convertRawFrameToRGB]
-[(RTSPObject)StreamInput pixelBufferFromImage:]
-[(RTSPObject)StreamInput cleanup]
-[(RTSPObject)StreamInput dealloc]
-[(RTSPObject)StreamInput stopVideoStream]
-[(RTSPObject)StreamInput stopAudioStream]
Terminating session context...
Terminating...
Logging leftovers - 0x109ec6400, 0x109ec6400 0x109ec6400
Logging content = \330
-[Document dealloc]
NOT WORKING SOLUTIONS:
Changing the order of object releases (The AVFormatContext has been freed first but it didn't lead to any change).
Calling RTSPObject's cleanup method much sooner to give FFmpeg more time to handle object releases.
Reading a lot of SO answers and FFmpeg documentation to find a clean cleanup process or newer code which might highlight why the object release doesn't happen properly.
I am currently reading the documentation on AVFormatContext since I believe that I am forgetting to release something. This believe is based on the memory debuggers output that AVFormatContext is still around.
Here is my creation code:
#pragma mark # Helpers - Start
- (NSError *)openInputStreamWithVideoStreamId:(int)videoStreamId
audioStreamId:(int)audioStreamId
useFirst:(BOOL)useFirstStreamAvailable
inInit:(BOOL)isInitProcess
{
// NSLog(#"%s", __PRETTY_FUNCTION__); // RTSP
self.status = StreamProvisioningStatusStarting;
AVCodec *decoderCodec;
NSString *rtspURL = self.streamURL;
NSString *errorMessage = nil;
NSError *error = nil;
self.sessionContext = NULL;
self.sessionContext = avformat_alloc_context();
AVFormatContext *pFormatCtx = self.sessionContext;
if (!pFormatCtx)
{
// Create approp error.
return error;
}
// MUST be called before avformat_open_input().
av_dict_free(&_sessionOptions);
self.sessionOptions = 0;
if (self.usesTcp)
{
// "rtsp_transport" - Set RTSP transport protocols.
// Allowed are: udp_multicast, tcp, udp, http.
av_dict_set(&_sessionOptions, "rtsp_transport", "tcp", 0);
}
av_dict_set(&_sessionOptions, "rtsp_transport", "tcp", 0);
// Open an input stream and read the header with the demuxer options.
// WARNING: The stream must be closed with avformat_close_input()
if (avformat_open_input(&pFormatCtx, rtspURL.UTF8String, NULL, &_sessionOptions) != 0)
{
// WARNING: Note that a user-supplied AVFormatContext (pFormatCtx) will be freed on failure.
self.isInputStreamOpen = NO;
// Create approp error.
return error;
}
self.isInputStreamOpen = YES;
// user-supplied AVFormatContext pFormatCtx might have been modified.
self.sessionContext = pFormatCtx;
// Retrieve stream information.
if (avformat_find_stream_info(pFormatCtx,NULL) < 0)
{
// Create approp error.
return error;
}
// Find the first video stream
int streamCount = pFormatCtx->nb_streams;
if (streamCount == 0)
{
// Create approp error.
return error;
}
int noStreamsAvailable = pFormatCtx->streams == NULL;
if (noStreamsAvailable)
{
// Create approp error.
return error;
}
// Result. An Index can change, an identifier shouldn't.
self.selectedVideoStreamId = STREAM_NOT_FOUND;
self.selectedAudioStreamId = STREAM_NOT_FOUND;
// Fallback.
int firstVideoStreamIndex = STREAM_NOT_FOUND;
int firstAudioStreamIndex = STREAM_NOT_FOUND;
self.selectedVideoStreamIndex = STREAM_NOT_FOUND;
self.selectedAudioStreamIndex = STREAM_NOT_FOUND;
for (int i = 0; i < streamCount; i++)
{
// Looking for video streams.
AVStream *stream = pFormatCtx->streams[i];
if (!stream) { continue; }
AVCodecParameters *codecPar = stream->codecpar;
if (!codecPar) { continue; }
if (codecPar->codec_type==AVMEDIA_TYPE_VIDEO)
{
if (stream->id == videoStreamId)
{
self.selectedVideoStreamId = videoStreamId;
self.selectedVideoStreamIndex = i;
}
if (firstVideoStreamIndex == STREAM_NOT_FOUND)
{
firstVideoStreamIndex = i;
}
}
// Looking for audio streams.
if (codecPar->codec_type==AVMEDIA_TYPE_AUDIO)
{
if (stream->id == audioStreamId)
{
self.selectedAudioStreamId = audioStreamId;
self.selectedAudioStreamIndex = i;
}
if (firstAudioStreamIndex == STREAM_NOT_FOUND)
{
firstAudioStreamIndex = i;
}
}
}
// Use first video and audio stream available (if possible).
if (self.selectedVideoStreamIndex == STREAM_NOT_FOUND && useFirstStreamAvailable && firstVideoStreamIndex != STREAM_NOT_FOUND)
{
self.selectedVideoStreamIndex = firstVideoStreamIndex;
self.selectedVideoStreamId = pFormatCtx->streams[firstVideoStreamIndex]->id;
}
if (self.selectedAudioStreamIndex == STREAM_NOT_FOUND && useFirstStreamAvailable && firstAudioStreamIndex != STREAM_NOT_FOUND)
{
self.selectedAudioStreamIndex = firstAudioStreamIndex;
self.selectedAudioStreamId = pFormatCtx->streams[firstAudioStreamIndex]->id;
}
if (self.selectedVideoStreamIndex == STREAM_NOT_FOUND)
{
// Create approp error.
return error;
}
// See AVCodecID for codec listing.
// * Video codec setup:
// 1. Find the decoder for the video stream with the gived codec id.
AVStream *stream = pFormatCtx->streams[self.selectedVideoStreamIndex];
if (!stream)
{
// Create approp error.
return error;
}
AVCodecParameters *codecPar = stream->codecpar;
if (!codecPar)
{
// Create approp error.
return error;
}
decoderCodec = avcodec_find_decoder(codecPar->codec_id);
if (decoderCodec == NULL)
{
// Create approp error.
return error;
}
// Get a pointer to the codec context for the video stream.
// WARNING: The resulting AVCodecContext should be freed with avcodec_free_context().
// Replaced:
// self.videoCodecContext = pFormatCtx->streams[self.selectedVideoStreamIndex]->codec;
// With:
self.videoCodecContext = avcodec_alloc_context3(decoderCodec);
avcodec_parameters_to_context(self.videoCodecContext,
codecPar);
self.videoCodecContext->thread_count = 4;
NSString *description = [NSString stringWithUTF8String:decoderCodec->long_name];
// 2. Open codec.
if (avcodec_open2(self.videoCodecContext, decoderCodec, NULL) < 0)
{
// Create approp error.
return error;
}
// * Audio codec setup:
if (self.selectedAudioStreamIndex > -1)
{
[self setupAudioDecoder];
}
// Allocate a raw video frame data structure. Contains audio and video data.
self.rawFrameData = av_frame_alloc();
self.outputWidth = self.videoCodecContext->width;
self.outputHeight = self.videoCodecContext->height;
if (!isInitProcess)
{
// Triggering notifications in init process won't change UI since the object is created locally. All
// objects which need data access to this object will not be able to get it. Thats why we don't notifiy anyone about the changes.
[NSNotificationCenter.defaultCenter postNotificationName:NSNotification.rtspVideoStreamSelectionChanged
object:nil userInfo: self.selectedVideoStream];
[NSNotificationCenter.defaultCenter postNotificationName:NSNotification.rtspAudioStreamSelectionChanged
object:nil userInfo: self.selectedAudioStream];
}
return nil;
}
UPDATE 1
The initial architecture allowed using any given thread. Most of the below code would mostly run on the main thread. This solution was not appropriate since the opening of the stream input can take several seconds for which the main thread is blocked while waiting for a network response inside FFmpeg. To solve this issue I have implemented the following solution:
Creation and the initial setup are only allowed on the background_thread (see code snippet "1" below).
Changes are allowed on the current_thread(Any).
Termination is allowed on the current_thread(Any).
After removing main thread checks and dispatch_asyncs to background threads, leaking has stopped and I can't reproduce the issue anymore:
// Code that produces the issue.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// 1 - Create and do initial setup.
// This block creates the issue.
[self.rtspObject = [[RTSPObject alloc] initWithURL: ... ];
[self.rtspObject openInputStreamWithVideoStreamId: ...
audioStreamId: ...
useFirst: ...
inInit: ...];
});
I still don't understand why Xcode's memory debugger says that this block is retained?
Any advice or idea is welcome.
If you use av_format_open_input to open a file, you must use avformat_close_input to free it. Using free_context will leak all io related allocations.

SFAuthorizationPluginView example NameAndPassword hangs after button is pressed

I'm using the patched version of NameAndPassword as is here: https://github.com/skycocker/NameAndPassword
When using this outside of the login window (e.g. system.login.screensaver or my own test right), with only the NameAndPassword plug-in, the window hangs for about 10 seconds after pressing Ok or cancel.
Adding some logs, I see that MechanismDestroy is getting called on the plugin and NameAndPassword is released but it never gets to dealloc in NameAndPassword. I also don't see PluginDestroy getting called. The SecurityAgent hangs around for an additional 10 seconds after MechanismDestroy is called.
I saw this related post: SecurityAgentPlugin not working anymore on Yosemite (SFAuthorizationPluginView)
But following the accepted answer doesn't work and according to the Authorization Plug-in Reference didDeactivate shouldn't need to be called (there are no calls to RequestInterrupt and MechanismDeactivate is never called). Calling SetResult should be sufficient.
I can hack it by getting the window from the view and closing it forcibly but there must be a way to get it to work as intended.
I found different hack - I got the reference count of the auth-mechanism instance and release all but the last one. Do this after the setResult call.
This solves the issue. It shouldn't be like that - but Apple doesn't give us much of a choice.
If my reply cant help someone, i have had the same problem and find a different way (not clean, it's even rather dirty) because i could not make the proposed solutions work.
I destroy the window if the identifiers are valid (I test them). Like that if the identifiers are not valid the window is still there.
- (void)buttonPressed:(SFButtonType)inButtonType
{
NSString *userNameString;
NSString *passwordString;
userNameString = mUserName;
passwordString = [mPPasswordSecureTextField stringValue];
// if the OK button was pressed, write the identity and credentials and allow authorization,
// otherwise, if the cancel button was pressed, cancel the authorization
if (inButtonType == SFButtonTypeOK)
{
const char *puserName = [userNameString UTF8String];
const char *ppassword = [passwordString UTF8String];
AuthorizationValue userNameValue = { strlen(puserName) + 1, (char*)puserName };
AuthorizationValue userPasswordValue = { strlen(ppassword) + 1, (char*)ppassword };
// add the username and password to the context values
[self callbacks]->SetContextValue([self engineRef], kAuthorizationEnvironmentUsername, 1, &userNameValue);
[self callbacks]->SetContextValue([self engineRef], kAuthorizationEnvironmentPassword, 1, &userPasswordValue);
// allow authorization
[self callbacks]->SetResult([self engineRef], kAuthorizationResultAllow);
// to know if we must close the window
// try to auth with the provided user and pswd
BOOL status = [self macosTestLogin: puserName with: ppassword];
if(status == YES)
{
// the user and pwd are good, we can close the window
NSView* v;
// if we are in sleep, screensaver and lock mode (don't work in loggin mode,
// but don't be sad loggin mode have a workaround in config authdb,
// the setting is shared true)
if (mUseIPView) {
v = mPasswordView;
NSWindow* w = [v window];
[w close];
}
}
// suggested workaround (don't work)
[self didDeactivate];
}
else if (inButtonType == SFButtonTypeCancel)
{
// cancel authorization
[self callbacks]->SetResult([self engineRef], kAuthorizationResultUserCanceled);
}
}
And the test function :
- (BOOL) macosTestLogin: (const char *)userName with: (const char *)password
{
// hack to know if we must close the window
// try to auth with the provided user and pswd
AuthorizationRef authorization = NULL;
AuthorizationItem items[2];
items[0].name = kAuthorizationEnvironmentPassword;
items[0].value = (char*) password;
items[0].valueLength = strlen(password);
items[0].flags = 0;
items[1].name = kAuthorizationEnvironmentUsername;
items[1].value = (char*) userName;
items[1].valueLength = strlen(userName);
items[1].flags = 0;
AuthorizationRights rights = {2, items};
AuthorizationEnvironment enviroment = {2, items};
// creates a new authorization reference and provides an option to authorize or preauthorize rights.
AuthorizationCreate(NULL, &enviroment, kAuthorizationFlagDefaults, &authorization);
AuthorizationFlags flag = kAuthorizationFlagDefaults| kAuthorizationFlagExtendRights;
OSStatus status = AuthorizationCopyRights(authorization, &rights, &enviroment, flag, NULL);
if(status == errAuthorizationSuccess)
{
return YES;
}
return NO;
}
This work for me, i do this only in 'screensaver' mode, on login the option shared = true is sufficient.

Find out if Mac is Force Touch - capable

Is it possible to find out if a Mac is Force Touch capable - either via a built-in Trackpad, like the new MacBook, or a Bluetooth device like the Magic Trackpad 2?
I'd like to present preferences specific to Force Touch if the Mac is Force Touch capable, but not display (or disable) those preferences if Force Touch is not available.
In the portion after the separator, you see the options I have in mind in the pic linked here. (sorry, embedding the pic itself didn't work).
So, not showing the preferences wouldn't restrict users who don't have force touch, it would just let users who have it configure how it should work, and those settings would be useless to users who don't have it.
Is there a way to achieve this?
Thank you and kind regards,
Matt
Edit: It's in Objective-C.
I figured it out:
+ (BOOL)isForceTouchCapable
{
if (![[self class] isAtLeastElCapitan])
return NO;
io_iterator_t iterator;
//get default HIDDevice dictionary
CFMutableDictionaryRef mDict = IOServiceMatching(kIOHIDDeviceKey);
//add manufacturer "Apple Inc." to dict
CFDictionaryAddValue(mDict, CFSTR(kIOHIDManufacturerKey), CFSTR("Apple Inc."));
//get matching services, depending on dict
IOReturn ioReturnValue = IOServiceGetMatchingServices(kIOMasterPortDefault, mDict, &iterator);
BOOL result = YES;
if (ioReturnValue != kIOReturnSuccess)
NSLog(#"error getting matching services for force touch devices");
else
{
//recursively go through each device found and its children and grandchildren, etc.
result = [[self class] _containsForceTouchDevice:iterator];
IOObjectRelease(iterator);
}
return result;
}
+ (BOOL)_containsForceTouchDevice:(io_iterator_t)iterator
{
io_object_t object = 0;
BOOL success = NO;
while ((object = IOIteratorNext(iterator)))
{
CFMutableDictionaryRef result = NULL;
kern_return_t state = IORegistryEntryCreateCFProperties(object, &result, kCFAllocatorDefault, 0);
if (state == KERN_SUCCESS && result != NULL)
{
if (CFDictionaryContainsKey(result, CFSTR("DefaultMultitouchProperties")))
{
CFDictionaryRef dict = CFDictionaryGetValue(result, CFSTR("DefaultMultitouchProperties"));
CFTypeRef val = NULL;
if (CFDictionaryGetValueIfPresent(dict, CFSTR("ForceSupported"), &val))
{
Boolean aBool = CFBooleanGetValue(val);
if (aBool) //supported
success = YES;
}
}
}
if (result != NULL)
CFRelease(result);
if (success)
{
IOObjectRelease(object);
break;
} else
{
io_iterator_t childIterator = 0;
kern_return_t err = IORegistryEntryGetChildIterator(object, kIOServicePlane, &childIterator);
if (!err)
{
success = [[self class] _containsForceTouchDevice:childIterator];
IOObjectRelease(childIterator);
} else
success = NO;
IOObjectRelease(object);
}
}
return success;
}
Just call + (BOOL)isForceTouchCapable and it will return YES if a Force Touch device is available (a Magic Trackpad 2 or a built in force-touch-trackpad) or NO if there isn't.
For those interested in how this came to be, I wrote about it on my blog with an example project.

Notification when wifi connected (OS X)

I need a notification from the system when airport is connecting to an ap. Is there any possibility to do that with the SystemConfiguration framework? I have problems to understand the systemconfigurations api documentation.
You are on the right track with the SystemConfiguration network, which offers the SCNetworkReachability set of functions. You could try using
SCNetworkReachabilitySetCallback
to set a callback which will be called when the reachability changes; and
SCNetworkReachabilityScheduleWithRunLoop
to schedule the reachability check within the run loop
Or you might try using a reachability framework (both for MacOS and iOS) which is built on top of the SystemConfiguration framework to make things even easier (higher-level).
If you want to go the SystemConfiguration way, this is how you check for present reachability and install the callback to be notified of changes (source):
- (void)checkReachability {
NSString *server = [[NSUserDefaults standardUserDefaults] stringForKey:#"NCIDServer"];
if (server == nil) {
ncid_message_callback(self, [NSLocalizedString(#"No caller ID server was specified.", nil) UTF8String]);
return;
}
const char *serverName = [[[server componentsSeparatedByString:#":"] objectAtIndex:0] UTF8String];
SCNetworkReachabilityContext context = {0, (void *)self, NULL, NULL, NULL};
networkReachability = SCNetworkReachabilityCreateWithName(NULL, serverName);
if (networkReachability == NULL)
goto fail;
// If reachability information is available now, we don't get a callback later
SCNetworkConnectionFlags flags;
if (SCNetworkReachabilityGetFlags(networkReachability, &flags))
networkReachabilityCallback(networkReachability, flags, self);
if (!SCNetworkReachabilitySetCallback(networkReachability, networkReachabilityCallback, &context))
goto fail;
if (!SCNetworkReachabilityScheduleWithRunLoop(networkReachability, [[NSRunLoop currentRunLoop] getCFRunLoop], kCFRunLoopCommonModes))
goto fail;
return;
fail:
if (networkReachability != NULL)
CFRelease(networkReachability);
networkReachability = NULL; //-- ivar representing current reachability
}
And this is a sample of the callback:
static void networkReachabilityCallback(SCNetworkReachabilityRef target,
SCNetworkConnectionFlags flags,
void *object) {
// Observed flags:
// - nearly gone: kSCNetworkFlagsReachable alone (ignored)
// - gone: kSCNetworkFlagsTransientConnection | kSCNetworkFlagsReachable | kSCNetworkFlagsConnectionRequired
// - connected: kSCNetworkFlagsIsDirect | kSCNetworkFlagsReachable
if (networkReachability == NULL)
return;
if ((flags & kSCNetworkFlagsReachable) && !(flags & kSCNetworkFlagsConnectionRequired)) {
if (isReachable) // typically receive a reachable message ~20ms before the unreachable one
return;
isReachable = YES;
ncid_network_kill();
[NSThread detachNewThreadSelector:#selector(runThread:) toTarget:object withObject:nil];
} else {
isReachable = NO;
ncid_network_kill();
}
}
You need to use Apple's Reachability class for this. http://developer.apple.com/library/ios/#samplecode/Reachability/Listings/Classes_Reachability_m.html#//apple_ref/doc/uid/DTS40007324-Classes_Reachability_m-DontLinkElementID_6
You can check this thread for how to use this class-
How do I receive notifications that the connection has changed type (3G, Edge, Wifi, GPRS)

Threading and Sockets in Objective-C

NOTE: I've edited my question. I've got it to connect and perform the first callback, but subsequent callbacks don't go through at all.
This is my first time writing Objective-C (with GNUstep; it's for a homework assignment). I've got the solution working, but I am trying to add something more to it. The app is a GUI client that connects to a server and gets data from it. Multiple clients can connect to the same server. If any one of the clients changes data that is residing on the server, the server sends a callback to all registered clients. This solution was originally implemented in Java (both client and server) and for the latest assignment, the professor wanted us to write an Objective-C client for it. He said that we don't need to handle callbacks, but I wanted to try anyway.
I am using NSThread and I wrote something that looks like this:
CallbackInterceptorThread.h
#import <Foundation/Foundation.h>
#import "AppDelegate.h"
#interface CallbackInterceptorThread : NSThread {
#private
NSString* clientPort;
AppDelegate* appDelegate;
}
- (id) initWithClientPort: (NSString*) aClientPort
appDelegate: (AppDelegate*) anAppDelegate;
- (void) main;
#end
CallbackInterceptorThread.m
#import <Foundation/Foundation.h>
#import "CallbackInterceptorThread.h"
#define MAXDATASIZE 4096
#implementation CallbackInterceptorThread
- (id) initWithClientPort: (NSString*) aClientPort
appDelegate: (AppDelegate*) anAppDelegate {
if((self = [super init])) {
[clientPort autorelease];
clientPort = [aClientPort retain];
[appDelegate autorelease];
appDelegate = [anAppDelegate retain];
}
return self;
}
- (void) main {
GSRegisterCurrentThread();
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
char* buffer = malloc(MAXDATASIZE);
Cst420ServerSocket* socket = [[Cst420ServerSocket alloc] initWithPort: clientPort];
[socket retain];
NSString* returnString;
while(YES) {
printf("Client waiting for callbacks on port %s\n", [clientPort cString]);
if([socket accept]) {
printf("Connection accepted!\n");
while(YES) {
printf("Inner loop\n");
sleep(1);
returnString = [socket receiveBytes: buffer maxBytes: MAXDATASIZE beginAt: 0];
printf("Received from Server |%s|\n", [returnString cString]);
if([returnString length] > 0) {
printf("Got a callback from server\n");
[appDelegate populateGui];
}
printf("Going to sleep now\n");
sleep(1);
}
[socket close];
}
}
}
#end
Cst420ServerSocket has been provided to us by the instructor. It looks like this:
#import "Cst420Socket.h"
#define PORT "4444"
/**
* Cst420Socket.m - objective-c class for manipulating stream sockets.
* Purpose: demonstrate stream sockets in Objective-C.
* These examples are buildable on MacOSX and GNUstep on top of Windows7
*/
// get sockaddr, IPv4 or IPv6:
void *get_in_addr(struct sockaddr *sa){
if (sa->sa_family == AF_INET) {
return &(((struct sockaddr_in*)sa)->sin_addr);
}
return &(((struct sockaddr_in6*)sa)->sin6_addr);
}
#implementation Cst420ServerSocket
- (id) initWithPort: (NSString*) port{
self = [super init];
int ret = 0;
memset(&hints, 0, sizeof hints);
hints.ai_family = AF_INET;
hints.ai_socktype = SOCK_STREAM;
hints.ai_flags = AI_PASSIVE; // use my IP
const char* portStr = [port UTF8String];
if ((rv = getaddrinfo(NULL, portStr, &hints, &servinfo)) != 0) {
fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(rv));
ret = 1;
}else{
for(p = servinfo; p != NULL; p = p->ai_next) {
if ((sockfd = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP))==-1){
perror("server: socket create error");
continue;
}
if (bind(sockfd, p->ai_addr, p->ai_addrlen) == -1) {
#if defined(WINGS)
closesocket(sockfd);
#else
close(sockfd);
#endif
perror("server: bind error");
continue;
}
break;
}
if (p == NULL) {
fprintf(stderr, "server: failed to bind\n");
ret = 2;
}else{
freeaddrinfo(servinfo); // all done with this structure
if (listen(sockfd, BACKLOG) == -1) {
perror("server: listen error");
ret = 3;
}
}
if (ret == 0){
return self;
} else {
return nil;
}
}
}
- (BOOL) accept {
BOOL ret = YES;
#if defined(WINGS)
new_fd = accept(sockfd, NULL, NULL);
#else
new_fd = accept(sockfd, (struct sockaddr *)&their_addr, &sin_size);
#endif
if (new_fd == -1) {
perror("server: accept error");
ret = NO;
}
connected = ret;
return ret;
}
- (int) sendBytes: (char*) byteMsg OfLength: (int) msgLength Index: (int) at{
int ret = send(new_fd, byteMsg, msgLength, 0);
if(ret == -1){
NSLog(#"error sending bytes");
}
return ret;
}
- (NSString* ) receiveBytes: (char*) byteMsg
maxBytes: (int) max
beginAt: (int) at {
int ret = recv(new_fd, byteMsg, max-1, at);
if(ret == -1){
NSLog(#"server error receiving bytes");
}
byteMsg[ret+at] = '\0';
NSString * retStr = [NSString stringWithUTF8String: byteMsg];
return retStr;
}
- (BOOL) close{
#if defined(WINGS)
closesocket(new_fd);
#else
close(new_fd);
#endif
connected = NO;
return YES;
}
- (void) dealloc {
#if defined(WINGS)
closesocket(sockfd);
#else
close(sockfd);
#endif
[super dealloc];
}
#end
Our professor also provided us an example of a simple echo server and client (the server just spits back whatever the client sent it) and I've used the same pattern in the thread.
My initial problem was that my callback interceptor thread didn't accept any (callback) connections from the server. The server said that it could not connect back to the client (ConnectException from Java; it said "Connection refused"). I was able to fix this by changing my instructor's code. In the connect function (not shown), he had set the hints to use AF_UNSPEC instead of AF_INET. So Java was seeing my localhost IP come through as 0:0:0:0:0:0:0:1 (in IPv6 format). When Java tried to connect back to send a callback, it received an exception (not sure why it cannot connect to an IPv6 address).
After fixing this problem, I tried out my app again and this time the callback from the server was received by my client. However, subsequent callbacks fail to work. After receiving the first callback, the busy-loop keeps running (as it should). But when the server sends a second callback, it looks like the client cannot read it in. On the server side I can see that it sent the callback to the client successfully. It's just that the client is having trouble reading in the data. I added some print statements (see above) for debugging and this is what I get:
Client waiting for callbacks on port 2020
Connection accepted!
Inner loop
Received from Server |A callback from server to 127.0.0.1:2020|
Got a callback from server
Going to sleep now
Inner loop
Received from Server ||
Going to sleep now
Inner loop
Received from Server ||
Going to sleep now
Inner loop
... (and it keeps going regardless of the second callback being sent)
Here is how I am starting the thread (from the GUI):
CallbackInterceptorThread* callbackInterceptorThread = [[CallbackInterceptorThread alloc] initWithClientPort: clientPort appDelegate: self];
[callbackInterceptorThread start];
I think I've got it working. So from the Java side (the server), this was what I was doing:
Socket socket = new Socket(clientAddress, clientPort);
BufferedOutputStream out = new BufferedOutputStream(socket.getOutputStream());
out.write(("A callback from server to " + clientAddress + ":" + clientPort).getBytes());
out.flush();
out.close();
I put some debugging print-statements in my professor's code and noticed that in receiveBytes, recv was returning 0. The return value of recv is the length of the message that it received. So it received a zero-length string. But a return value of 0 also means that the peer closed the connection properly (which is exactly what I had done from the Java side with out.close()). So I figured that if I needed to respond to the second callback, I would need to accept the connection again. So I changed my busy loop to this:
printf("Client waiting for callbacks on port %s\n", [clientPort cString]);
while([socket accept]) {
printf("Connection accepted!\n");
returnString = [socket receiveBytes: buffer maxBytes: MAXDATASIZE beginAt: 0];
printf("Received from Server |%s|\n", [returnString cString]);
if([returnString length] > 0) {
printf("Got a callback from server\n");
[appDelegate populateGui];
}
}
[socket close];
and that seemed to do the trick. I am not sure if this is the right way to do it, so I am open to suggestions for improvement!