CFHostSampleObjC/QHostNameQuery.m
/* |
Copyright (C) 2017 Apple Inc. All Rights Reserved. |
See LICENSE.txt for this sample’s licensing information |
Abstract: |
Resolves an IP address to a list of DNS names. |
*/ |
#import "QHostNameQuery.h" |
NS_ASSUME_NONNULL_BEGIN |
@interface QHostNameQuery () |
/// The underlying CFHost object that does the resolution. |
@property (nonatomic, strong, readwrite, nullable) CFHostRef host __attribute__ (( NSObject )); |
/// The run loop on which the CFHost object is scheduled; this is set in `start()` |
/// and cleared when the query stops (either via `cancel()` or by completing). |
@property (nonatomic, strong, readwrite, nullable) NSRunLoop * targetRunLoop; |
@end |
NS_ASSUME_NONNULL_END |
@implementation QHostNameQuery |
- (instancetype)initWithAddress:(NSData *)address { |
NSParameterAssert(address != nil); |
self = [super init]; |
if (self != nil) { |
self->_address = [address copy]; |
self->_host = CFHostCreateWithAddress(NULL, (__bridge CFDataRef) self->_address); |
} |
return self; |
} |
- (instancetype)init { |
abort(); |
} |
- (void)start { |
NSParameterAssert(self.targetRunLoop == nil); |
self.targetRunLoop = NSRunLoop.currentRunLoop; |
CFHostClientContext context = { |
.info = (void *) CFBridgingRetain(self) |
}; |
BOOL success = CFHostSetClient(self.host, HostClientCallBack, &context) != false; |
assert(success); |
CFHostScheduleWithRunLoop(self.host, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode); |
CFStreamError streamError; |
success = CFHostStartInfoResolution(self.host, kCFHostNames, &streamError) != false; |
if ( ! success ) { |
[self stopWithStreamError:&streamError notify:NO]; |
} |
} |
/// Our CFHost callback function for our host; this just extracts the object from the `info` |
/// pointer and calls methods on it. |
static void HostClientCallBack(CFHostRef theHost, CFHostInfoType typeInfo, const CFStreamError * __nullable error, void * __nullable info) { |
#pragma unused(theHost) |
#pragma unused(typeInfo) |
QHostNameQuery * obj = (__bridge QHostNameQuery *) info; |
assert([obj isKindOfClass:[QHostNameQuery class]]); |
if ( (error == NULL) || ( (error->domain == 0) && (error->error == 0) ) ) { |
[obj stopWithStreamError:NULL notify:YES]; |
} else { |
[obj stopWithStreamError:error notify:YES]; |
} |
} |
/// Stops the query with the supplied error, notifying the delegate if `notify` is true. |
- (void)stopWithStreamError:(nullable const CFStreamError *)streamError notify:(BOOL)notify { |
NSError * error = nil; |
if (streamError != NULL) { |
// Convert a CFStreamError to a NSError. This is less than ideal. I only handle a |
// limited number of error domains, and I can't use a switch statement because |
// some of the `kCFStreamErrorDomainXxx` values are not a constant. Wouldn't it be |
// nice if there was a public API to do this mapping <rdar://problem/5845848> |
// or a CFHost API that used CFError <rdar://problem/6016542>. |
if (streamError->domain == kCFStreamErrorDomainPOSIX) { |
error = [NSError errorWithDomain:NSPOSIXErrorDomain code:streamError->error userInfo:nil]; |
} else if (streamError->domain == kCFStreamErrorDomainMacOSStatus) { |
error = [NSError errorWithDomain:NSOSStatusErrorDomain code:streamError->error userInfo:nil]; |
} else if (streamError->domain == kCFStreamErrorDomainNetServices) { |
error = [NSError errorWithDomain:(__bridge NSString *) kCFErrorDomainCFNetwork code:streamError->error userInfo:nil]; |
} else if (streamError->domain == kCFStreamErrorDomainNetDB) { |
error = [NSError errorWithDomain:(__bridge NSString *) kCFErrorDomainCFNetwork code:kCFHostErrorUnknown userInfo:@{ |
(__bridge NSString *) kCFGetAddrInfoFailureKey: @(streamError->error) |
}]; |
} else { |
// If it's something we don't understand, we just assume it comes from |
// CFNetwork. |
error = [NSError errorWithDomain:(__bridge NSString *) kCFErrorDomainCFNetwork code:streamError->error userInfo:nil]; |
} |
} |
[self stopWithError:error notify:notify]; |
} |
/// Stops the query with the supplied error, notifying the delegate if `notify` is true. |
- (void)stopWithError:(nullable NSError *)error notify:(BOOL)notify { |
NSParameterAssert(NSRunLoop.currentRunLoop == self.targetRunLoop); |
self.targetRunLoop = nil; |
CFHostSetClient(self.host, nil, nil); |
CFHostUnscheduleFromRunLoop(self.host, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode); |
CFHostCancelInfoResolution(self.host, kCFHostAddresses); |
CFRelease( (__bridge CFTypeRef) self ); |
id<QHostNameQueryDelegate> strongDelegate = self.delegate; |
if (notify && (strongDelegate != nil) ) { |
if (error != nil) { |
[strongDelegate hostNameQuery:self didCompleteWithError:error]; |
} else { |
NSArray<NSString *> * names = (__bridge NSArray<NSString *> *) CFHostGetNames(self.host, NULL); |
[strongDelegate hostNameQuery:self didCompleteWithNames:names]; |
} |
} |
} |
- (void)cancel { |
if (self.targetRunLoop != nil) { |
[self stopWithError:[NSError errorWithDomain:NSCocoaErrorDomain code:NSUserCancelledError userInfo:nil] notify:NO]; |
} |
} |
@end |
Copyright © 2017 Apple Inc. All Rights Reserved. Terms of Use | Privacy Policy | Updated: 2017-03-14