Permalink
Browse files

Tweaked some documentation, fixed some memory leaks in MRR mode, and …

…changed where the CFSocketRef is created, and with which address family.
  • Loading branch information...
AlanQuatermain committed Dec 20, 2011
1 parent c71a6f4 commit 649aa23cd24b170605923398f4737b8205e9464a
Showing with 82 additions and 32 deletions.
  1. +0 −4 AQSocket/AQSocket.h
  2. +82 −28 AQSocket/AQSocket.m
View
@@ -99,8 +99,6 @@ typedef void (^AQSocketEventHandler)(AQSocketEvent event, id info);
return the `error` parameter will be initialized to a new autoreleased NSError
instance containing detailed information.
@param saddr A socket address structure containing a remote server address.
- @param port The port number to which to connect.
- @param timeout The maximum time to wait for the connection to initialize.
@param error If this method returns `NO`, then on return this value contains
an NSError object detailing the error.
@result `YES` if the connection initialization takes place, `NO` if something prevented it from doing so.
@@ -113,7 +111,6 @@ typedef void (^AQSocketEventHandler)(AQSocketEvent event, id info);
the caller to pass a DNS hostname to specify the destination for the connection.
@param hostname The DNS hostname of the server to which to attempt connection.
@param port The port number to which to connect.
- @param timeout The maximum time to wait for the connection to initialize.
@param error If this method returns `NO`, then on return this value contains
an NSError object detailing the error.
@result Returns `YES` if the async connection attempt began, `NO` otherwise.
@@ -128,7 +125,6 @@ typedef void (^AQSocketEventHandler)(AQSocketEvent event, id info);
for the connection.
@param address The IPv4/6 address of the server to which to attempt connection.
@param port The port number to which to connect.
- @param timeout The maximum time to wait for the connection to initialize.
@param error If this method returns `NO`, then on return this value contains
an NSError object detailing the error.
@result Returns `YES` if the async connection attempt began, `NO` otherwise.
View
@@ -51,7 +51,7 @@
#define USING_MRR (!__has_feature(objc_arc))
-/// A sly little NSData class simplifying the dispatch_data_t -> NSData transition.
+// A sly little NSData class simplifying the dispatch_data_t -> NSData transition.
@interface _AQDispatchData : NSData
{
dispatch_data_t _ddata;
@@ -100,6 +100,8 @@ - (NSUInteger) length
@end
+#pragma mark -
+
@interface AQSocket (CFSocketConnectionCallback)
- (void) connectedSuccessfully;
- (void) connectionFailedWithError: (SInt32) err;
@@ -119,10 +121,12 @@ static void _CFSocketConnectionCallBack(CFSocketRef s, CFSocketCallBackType type
static BOOL _SocketAddressFromString(NSString * addrStr, BOOL isNumeric, UInt16 port, struct sockaddr_storage * outAddr, NSError * __autoreleasing* outError)
{
- // Glags for getaddrinfo():
- // AI_ADDRCONFIG: Only return IPv4 or IPv6 if the local host has configured
+ // Flags for getaddrinfo():
+ //
+ // `AI_ADDRCONFIG`: Only return IPv4 or IPv6 if the local host has configured
// interfaces for those types.
- // AI_V4MAPPED: If no IPv6 addresses found, return an IPv4-mapped IPv6
+ //
+ // `AI_V4MAPPED`: If no IPv6 addresses found, return an IPv4-mapped IPv6
// address for any IPv4 addresses found.
int flags = AI_ADDRCONFIG|AI_V4MAPPED;
@@ -184,9 +188,14 @@ static BOOL _SocketAddressFromString(NSString * addrStr, BOOL isNumeric, UInt16
return ( 0 );
}
+#pragma mark -
+
@implementation AQSocket
{
+ int _socketType;
+ int _socketProtocol;
CFSocketRef _socketRef;
+ CFRunLoopSourceRef _socketRunloopSource;
dispatch_io_t _socketIO;
AQSocketReader * _socketReader;
dispatch_source_t _readWatcher;
@@ -201,22 +210,8 @@ - (id) initWithSocketType: (int) type
if ( self == nil )
return ( nil );
- CFSocketContext ctx = {
- .version = 0,
- .info = (__bridge void *)self, // just a plain bridge cast
- .retain = CFRetain,
- .release = CFRelease,
- .copyDescription = CFCopyDescription
- };
-
- _socketRef = CFSocketCreate(kCFAllocatorDefault, AF_INET6, type, (type == SOCK_DGRAM ? IPPROTO_UDP : IPPROTO_TCP), kCFSocketConnectCallBack, _CFSocketConnectionCallBack, &ctx);
- if ( _socketRef == NULL )
- {
-#if USING_MRR
- [self release];
-#endif
- return ( nil );
- }
+ _socketType = type;
+ _socketProtocol = (type == SOCK_STREAM ? IPPROTO_TCP : IPPROTO_UDP);
return ( self );
}
@@ -235,15 +230,31 @@ - (void) dealloc
}
if ( _socketIO != NULL )
{
- // closing the socket IO also releases _socketRef
+ // Closing the socket IO also releases _socketRef
dispatch_io_close(_socketIO, DISPATCH_IO_STOP);
dispatch_release(_socketIO);
}
else if ( _socketRef != NULL )
{
- // not got around to initializing the dispatch IO channel
+ // Not got around to initializing the dispatch IO channel yet,
+ // so we will have to release the socket ref manually.
CFRelease(_socketRef);
}
+ if ( _socketRunloopSource != NULL )
+ {
+ // Ensure the source is no longer scheduled in any run loops.
+ CFRunLoopRemoveSource(CFRunLoopGetMain(), _socketRunloopSource, kCFRunLoopDefaultMode);
+ CFRunLoopRemoveSource(CFRunLoopGetMain(), _socketRunloopSource, (__bridge CFStringRef)
+#if TARGET_OS_IPHONE
+ UITrackingRunLoopMode
+#else
+ NSEventTrackingRunLoopMode
+#endif
+ );
+
+ // Now we can safely release our reference to it.
+ CFRelease(_socketRunloopSource);
+ }
#if USING_MRR
[_socketReader release];
#endif
@@ -283,26 +294,47 @@ - (BOOL) connectToAddress: (struct sockaddr *) saddr
return ( NO );
}
+ // Create the socket with the appropriate socket family from the address
+ // structure.
+ CFSocketContext ctx = {
+ .version = 0,
+ .info = (__bridge void *)self, // just a plain bridge cast
+ .retain = CFRetain,
+ .release = CFRelease,
+ .copyDescription = CFCopyDescription
+ };
+
+ _socketRef = CFSocketCreate(kCFAllocatorDefault, saddr->sa_family, _socketType, _socketProtocol, kCFSocketConnectCallBack, _CFSocketConnectionCallBack, &ctx);
+ if ( _socketRef == NULL )
+ {
+ // We failed to create the socket, so build an error (if appropriate)
+ // and return `NO`.
+ if ( error != NULL )
+ {
+ // This error code is -1004.
+ *error = [NSError errorWithDomain: NSURLErrorDomain code: NSURLErrorCannotConnectToHost userInfo: nil];
+ }
+
+ return ( NO );
+ }
+
// Create a runloop source for the socket reference and bind it to a
// runloop that's guaranteed to be running some time in the future: the main
// one.
- CFRunLoopSourceRef rls = CFSocketCreateRunLoopSource(kCFAllocatorDefault, _socketRef, 0);
- CFRunLoopAddSource(CFRunLoopGetMain(), rls, kCFRunLoopDefaultMode);
+ _socketRunloopSource = CFSocketCreateRunLoopSource(kCFAllocatorDefault, _socketRef, 0);
+ CFRunLoopAddSource(CFRunLoopGetMain(), _socketRunloopSource, kCFRunLoopDefaultMode);
// We also want to ensure that the connection callback fires during
// input event tracking. There are different constants for this on iOS and
// OS X, so I've used a compiler switch for that.
- CFRunLoopAddSource(CFRunLoopGetMain(), rls, (__bridge CFStringRef)
+ CFRunLoopAddSource(CFRunLoopGetMain(), _socketRunloopSource, (__bridge CFStringRef)
#if TARGET_OS_IPHONE
UITrackingRunLoopMode
#else
NSEventTrackingRunLoopMode
#endif
);
- // This becomes owned by the runloop modes now.
- CFRelease(rls);
-
// Start the connection process.
// Let's fire off the connection attempt and wait for the socket to become
// readable, which means the connection succeeded. The timeout value of
@@ -314,6 +346,10 @@ - (BOOL) connectToAddress: (struct sockaddr *) saddr
length: saddr->sa_len
freeWhenDone: NO];
CFSocketError err = CFSocketConnectToAddress(_socketRef, (__bridge CFDataRef)data, -1);
+#if USING_MRR
+ [data release];
+#endif
+
if ( err != kCFSocketSuccess )
{
NSLog(@"Error connecting socket: %ld", err);
@@ -447,6 +483,20 @@ - (void) connectedSuccessfully
// 2. The AQSocketReader object which will serve as our read buffer.
// 3. The dispatch source which will notify us of incoming data.
+ // Before all that though, we'll remove the CFSocketRef from the runloop.
+ CFRunLoopRemoveSource(CFRunLoopGetMain(), _socketRunloopSource, kCFRunLoopDefaultMode);
+ CFRunLoopRemoveSource(CFRunLoopGetMain(), _socketRunloopSource, (__bridge CFStringRef)
+#if TARGET_OS_IPHONE
+ UITrackingRunLoopMode
+#else
+ NSEventTrackingRunLoopMode
+#endif
+ );
+
+ // All done with this one now.
+ CFRelease(_socketRunloopSource);
+ _socketRunloopSource = NULL;
+
// First, the IO channel. We will have its cleanup handler release the
// CFSocketRef for us; in other words, the IO channel now owns the
// CFSocketRef.
@@ -518,6 +568,10 @@ - (void) connectionFailedWithError: (SInt32) err
{
NSError * info = [NSError errorWithDomain: NSPOSIXErrorDomain code: err userInfo: nil];
self.eventHandler(AQSocketEventConnectionFailed, info);
+
+ // Get rid of the socket now, since we might try to re-connect, which will
+ // create a new CFSocketRef.
+ CFRelease(_socketRef); _socketRef = NULL;
}
@end

0 comments on commit 649aa23

Please sign in to comment.