AFNetworking 2.6.3 源碼解析

到了現在大部分項目使用的AFNetworking應該是3.0以上版本,源碼分析基本上都是都是3.0的文章介紹,關于2.0的介紹只有bang神的博客.本文分析以AFNetworking 2.6.3為基礎進行分析,如果大家已經看過了類似文章,可以忽略我.

AFNetworking主要由NSURLConnction,NSURLSession,Security,Reachability和Serialization五個文件夾組成,AFNetworking對常見的UIKit組件進行擴展,這一部分就不介紹了,項目中實際使用的不多.

NSURLConnection

AFURLConnectionOperation是NSOperation的子類,實現的協議如下:
<pre><code>@interface AFURLConnectionOperation : NSOperation <NSURLConnectionDelegate, NSURLConnectionDataDelegate, NSSecureCoding, NSCopying></code></pre>

相對于日常開發中常用的NSCoding協議NSSecureCoding更加安全,可以防止信息被篡改,如果解檔的類和要求的類類型不一致的時候會拋出異常,詳情見參考鏈接.

單例請求的隊列queue和隊列組group,queue的命名堪稱典范:
<pre><code>`static dispatch_group_t url_request_operation_completion_group() {
static dispatch_group_t af_url_request_operation_completion_group;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
af_url_request_operation_completion_group = dispatch_group_create();
});

return af_url_request_operation_completion_group;

}

static dispatch_queue_t url_request_operation_completion_queue() {
static dispatch_queue_t af_url_request_operation_completion_queue;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
af_url_request_operation_completion_queue = dispatch_queue_create("com.alamofire.networking.operation.queue", DISPATCH_QUEUE_CONCURRENT );
});

return af_url_request_operation_completion_queue;

}`</code></pre>

靜態內聯函數,static inline可以理解為static函數加入了inline屬性,編譯的時候會將其展開編譯而不是為函數生成獨立的匯編碼.
<pre><code>`static inline NSString * AFKeyPathFromOperationState(AFOperationState state) {
switch (state) {
case AFOperationReadyState:
return @"isReady";
case AFOperationExecutingState:
return @"isExecuting";
case AFOperationFinishedState:
return @"isFinished";
case AFOperationPausedState:
return @"isPaused";
default: {

pragma clang diagnostic push

pragma clang diagnostic ignored "-Wunreachable-code"

        return @"state";

pragma clang diagnostic pop

    }
}

}`</code></pre>

創建線程默認是主線的Runloop下,Runloop模式默認為NSDefaultRunLoopMode模式,如果用戶操作UI滑動屏幕,會切換到UITrackingRunLoopMode模式下,導致請求失效.AFNetworking創建一條常駐線程:
<pre><code>`+ (void)networkRequestThreadEntryPoint:(id)__unused object {
@autoreleasepool {
[[NSThread currentThread] setName:@"AFNetworking"];

    NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
    [runLoop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];
    [runLoop run];
}

}

  • (NSThread *)networkRequestThread {
    static NSThread *_networkRequestThread = nil;
    static dispatch_once_t oncePredicate;
    dispatch_once(&oncePredicate, ^{
    _networkRequestThread = [[NSThread alloc] initWithTarget:self selector:@selector(networkRequestThreadEntryPoint:) object:nil];
    [_networkRequestThread start];
    });

    return _networkRequestThread;
    }`</code></pre>

批量請求的代碼,線程的異步轉同步,線程之間的依賴寫的很??:
<pre><code>`+ (NSArray *)batchOfRequestOperations:(NSArray *)operations
progressBlock:(void (^)(NSUInteger numberOfFinishedOperations, NSUInteger totalNumberOfOperations))progressBlock
completionBlock:(void (^)(NSArray *operations))completionBlock
{
if (!operations || [operations count] == 0) {
return @[[NSBlockOperation blockOperationWithBlock:^{
dispatch_async(dispatch_get_main_queue(), ^{
if (completionBlock) {
completionBlock(@[]);
}
});
}]];
}

__block dispatch_group_t group = dispatch_group_create();
NSBlockOperation *batchedOperation = [NSBlockOperation blockOperationWithBlock:^{
    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        if (completionBlock) {
            completionBlock(operations);
        }
    });
}];

for (AFURLConnectionOperation *operation in operations) {
    operation.completionGroup = group;
    void (^originalCompletionBlock)(void) = [operation.completionBlock copy];
    __weak __typeof(operation)weakOperation = operation;
    operation.completionBlock = ^{
        __strong __typeof(weakOperation)strongOperation = weakOperation;

pragma clang diagnostic push

pragma clang diagnostic ignored "-Wgnu"

        dispatch_queue_t queue = strongOperation.completionQueue ?: dispatch_get_main_queue();

pragma clang diagnostic pop

        dispatch_group_async(group, queue, ^{
            if (originalCompletionBlock) {
                originalCompletionBlock();
            }

            NSUInteger numberOfFinishedOperations = [[operations indexesOfObjectsPassingTest:^BOOL(id op, NSUInteger __unused idx,  BOOL __unused *stop) {
                return [op isFinished];
            }] count];

            if (progressBlock) {
                progressBlock(numberOfFinishedOperations, [operations count]);
            }

            dispatch_group_leave(group);
        });
    };

    dispatch_group_enter(group);
    [batchedOperation addDependency:operation];
}

return [operations arrayByAddingObject:batchedOperation];

}`</code></pre>

AFHTTPRequestOperation繼承自AFURLConnectionOperation,其中的暫停的操作對文件流的操作代碼如下:
<pre><code>`- (void)pause {
[super pause];

u_int64_t offset = 0;
if ([self.outputStream propertyForKey:NSStreamFileCurrentOffsetKey]) {
    offset = [(NSNumber *)[self.outputStream propertyForKey:NSStreamFileCurrentOffsetKey] unsignedLongLongValue];
} else {
    offset = [(NSData *)[self.outputStream propertyForKey:NSStreamDataWrittenToMemoryStreamKey] length];
}

NSMutableURLRequest *mutableURLRequest = [self.request mutableCopy];
if ([self.response respondsToSelector:@selector(allHeaderFields)] && [[self.response allHeaderFields] valueForKey:@"ETag"]) {
    [mutableURLRequest setValue:[[self.response allHeaderFields] valueForKey:@"ETag"] forHTTPHeaderField:@"If-Range"];
}
[mutableURLRequest setValue:[NSString stringWithFormat:@"bytes=%llu-", offset] forHTTPHeaderField:@"Range"];
self.request = mutableURLRequest;

}
`</code></pre>

AFHTTPRequestOperationManager對所有的Operation進行管理,通過初始化就覆蓋了所有的核心類文件,序列化,安全協議和網絡請求管理文件.
<pre><code>`- (instancetype)initWithBaseURL:(NSURL *)url {
self = [super init];
if (!self) {
return nil;
}

// Ensure terminal slash for baseURL path, so that NSURL +URLWithString:relativeToURL: works as expected
if ([[url path] length] > 0 && ![[url absoluteString] hasSuffix:@"/"]) {
    url = [url URLByAppendingPathComponent:@""];
}

self.baseURL = url;

self.requestSerializer = [AFHTTPRequestSerializer serializer];
self.responseSerializer = [AFJSONResponseSerializer serializer];

self.securityPolicy = [AFSecurityPolicy defaultPolicy];

self.reachabilityManager = [AFNetworkReachabilityManager sharedManager];

self.operationQueue = [[NSOperationQueue alloc] init];

self.shouldUseCredentialStorage = YES;

return self;

}
`</code></pre>

提供http請求的六種方式GET,HEAD,POST,PUT,PATCH,DELETE.
<pre><code>`- (nullable AFHTTPRequestOperation *)GET:(NSString *)URLString
parameters:(nullable id)parameters
success:(nullable void (^)(AFHTTPRequestOperation *operation, id responseObject))success
failure:(nullable void (^)(AFHTTPRequestOperation * __nullable operation, NSError *error))failure;

/**
Creates and runs an AFHTTPRequestOperation with a HEAD request.

@param URLString The URL string used to create the request URL.
@param parameters The parameters to be encoded according to the client request serializer.
@param success A block object to be executed when the request operation finishes successfully. This block has no return value and takes a single arguments: the request operation.
@param failure A block object to be executed when the request operation finishes unsuccessfully, or that finishes successfully, but encountered an error while parsing the response data. This block has no return value and takes a two arguments: the request operation and the error describing the network or parsing error that occurred.

@see -HTTPRequestOperationWithRequest:success:failure:
*/

  • (nullable AFHTTPRequestOperation *)HEAD:(NSString *)URLString
    parameters:(nullable id)parameters
    success:(nullable void (^)(AFHTTPRequestOperation *operation))success
    failure:(nullable void (^)(AFHTTPRequestOperation * __nullable operation, NSError *error))failure;

/**
Creates and runs an AFHTTPRequestOperation with a POST request.

@param URLString The URL string used to create the request URL.
@param parameters The parameters to be encoded according to the client request serializer.
@param success A block object to be executed when the request operation finishes successfully. This block has no return value and takes two arguments: the request operation, and the response object created by the client response serializer.
@param failure A block object to be executed when the request operation finishes unsuccessfully, or that finishes successfully, but encountered an error while parsing the response data. This block has no return value and takes a two arguments: the request operation and the error describing the network or parsing error that occurred.

@see -HTTPRequestOperationWithRequest:success:failure:
*/

  • (nullable AFHTTPRequestOperation *)POST:(NSString *)URLString
    parameters:(nullable id)parameters
    success:(nullable void (^)(AFHTTPRequestOperation *operation, id responseObject))success
    failure:(nullable void (^)(AFHTTPRequestOperation * __nullable operation, NSError *error))failure;

/**
Creates and runs an AFHTTPRequestOperation with a multipart POST request.

@param URLString The URL string used to create the request URL.
@param parameters The parameters to be encoded according to the client request serializer.
@param block A block that takes a single argument and appends data to the HTTP body. The block argument is an object adopting the AFMultipartFormData protocol.
@param success A block object to be executed when the request operation finishes successfully. This block has no return value and takes two arguments: the request operation, and the response object created by the client response serializer.
@param failure A block object to be executed when the request operation finishes unsuccessfully, or that finishes successfully, but encountered an error while parsing the response data. This block has no return value and takes a two arguments: the request operation and the error describing the network or parsing error that occurred.

@see -HTTPRequestOperationWithRequest:success:failure:
*/

  • (nullable AFHTTPRequestOperation *)POST:(NSString *)URLString
    parameters:(nullable id)parameters
    constructingBodyWithBlock:(nullable void (^)(id <AFMultipartFormData> formData))block
    success:(nullable void (^)(AFHTTPRequestOperation *operation, id responseObject))success
    failure:(nullable void (^)(AFHTTPRequestOperation * __nullable operation, NSError *error))failure;

/**
Creates and runs an AFHTTPRequestOperation with a PUT request.

@param URLString The URL string used to create the request URL.
@param parameters The parameters to be encoded according to the client request serializer.
@param success A block object to be executed when the request operation finishes successfully. This block has no return value and takes two arguments: the request operation, and the response object created by the client response serializer.
@param failure A block object to be executed when the request operation finishes unsuccessfully, or that finishes successfully, but encountered an error while parsing the response data. This block has no return value and takes a two arguments: the request operation and the error describing the network or parsing error that occurred.

@see -HTTPRequestOperationWithRequest:success:failure:
*/

  • (nullable AFHTTPRequestOperation *)PUT:(NSString *)URLString
    parameters:(nullable id)parameters
    success:(nullable void (^)(AFHTTPRequestOperation *operation, id responseObject))success
    failure:(nullable void (^)(AFHTTPRequestOperation * __nullable operation, NSError *error))failure;

/**
Creates and runs an AFHTTPRequestOperation with a PATCH request.

@param URLString The URL string used to create the request URL.
@param parameters The parameters to be encoded according to the client request serializer.
@param success A block object to be executed when the request operation finishes successfully. This block has no return value and takes two arguments: the request operation, and the response object created by the client response serializer.
@param failure A block object to be executed when the request operation finishes unsuccessfully, or that finishes successfully, but encountered an error while parsing the response data. This block has no return value and takes a two arguments: the request operation and the error describing the network or parsing error that occurred.

@see -HTTPRequestOperationWithRequest:success:failure:
*/

  • (nullable AFHTTPRequestOperation *)PATCH:(NSString *)URLString
    parameters:(nullable id)parameters
    success:(nullable void (^)(AFHTTPRequestOperation *operation, id responseObject))success
    failure:(nullable void (^)(AFHTTPRequestOperation * __nullable operation, NSError *error))failure;

/**
Creates and runs an AFHTTPRequestOperation with a DELETE request.

@param URLString The URL string used to create the request URL.
@param parameters The parameters to be encoded according to the client request serializer.
@param success A block object to be executed when the request operation finishes successfully. This block has no return value and takes two arguments: the request operation, and the response object created by the client response serializer.
@param failure A block object to be executed when the request operation finishes unsuccessfully, or that finishes successfully, but encountered an error while parsing the response data. This block has no return value and takes a two arguments: the request operation and the error describing the network or parsing error that occurred.

@see -HTTPRequestOperationWithRequest:success:failure:
*/

  • (nullable AFHTTPRequestOperation *)DELETE:(NSString *)URLString
    parameters:(nullable id)parameters
    success:(nullable void (^)(AFHTTPRequestOperation *operation, id responseObject))success
    failure:(nullable void (^)(AFHTTPRequestOperation * __nullable operation, NSError *error))failure;`</code></pre>

整個網絡請求回調順序:

AFNetworking.png

NSURLSession

NSURLSession文件夾下有兩個文件AFURLSessionManager和AFHTTPSessionManager.
<pre><code>@interface AFURLSessionManager : NSObject <NSURLSessionDelegate, NSURLSessionTaskDelegate, NSURLSessionDataDelegate, NSURLSessionDownloadDelegate, NSSecureCoding, NSCopying></code></pre>

AFURLSessionManager有一個私有類_AFURLSessionTaskSwizzling,交換NSURLSessionTask中的暫停和繼續方法.

交換函數:

<pre><code>`static inline void af_swizzleSelector(Class theClass, SEL originalSelector, SEL swizzledSelector) {
Method originalMethod = class_getInstanceMethod(theClass, originalSelector);
Method swizzledMethod = class_getInstanceMethod(theClass, swizzledSelector);
method_exchangeImplementations(originalMethod, swizzledMethod);
}

static inline BOOL af_addMethod(Class theClass, SEL selector, Method method) {
return class_addMethod(theClass, selector, method_getImplementation(method), method_getTypeEncoding(method));
}
`</code></pre>

交換過程:

<pre><code>`

  • (void)load {
    /**
    WARNING: Trouble Ahead
    https://github.com/AFNetworking/AFNetworking/pull/2702
    */

    if (NSClassFromString(@"NSURLSessionTask")) {
    /**
    iOS 7 and iOS 8 differ in NSURLSessionTask implementation, which makes the next bit of code a bit tricky.
    Many Unit Tests have been built to validate as much of this behavior has possible.
    Here is what we know:
    - NSURLSessionTasks are implemented with class clusters, meaning the class you request from the API isn't actually the type of class you will get back.
    - Simply referencing [NSURLSessionTask class] will not work. You need to ask an NSURLSession to actually create an object, and grab the class from there.
    - On iOS 7, localDataTask is a __NSCFLocalDataTask, which inherits from __NSCFLocalSessionTask, which inherits from __NSCFURLSessionTask.
    - On iOS 8, localDataTask is a __NSCFLocalDataTask, which inherits from __NSCFLocalSessionTask, which inherits from NSURLSessionTask.
    - On iOS 7, __NSCFLocalSessionTask and __NSCFURLSessionTask are the only two classes that have their own implementations of resume and suspend, and __NSCFLocalSessionTask DOES NOT CALL SUPER. This means both classes need to be swizzled.
    - On iOS 8, NSURLSessionTask is the only class that implements resume and suspend. This means this is the only class that needs to be swizzled.
    - Because NSURLSessionTask is not involved in the class hierarchy for every version of iOS, its easier to add the swizzled methods to a dummy class and manage them there.

       Some Assumptions:
          - No implementations of `resume` or `suspend` call super. If this were to change in a future version of iOS, we'd need to handle it.
          - No background task classes override `resume` or `suspend`
       
       The current solution:
          1) Grab an instance of `__NSCFLocalDataTask` by asking an instance of `NSURLSession` for a data task.
          2) Grab a pointer to the original implementation of `af_resume`
          3) Check to see if the current class has an implementation of resume. If so, continue to step 4.
          4) Grab the super class of the current class.
          5) Grab a pointer for the current class to the current implementation of `resume`.
          6) Grab a pointer for the super class to the current implementation of `resume`.
          7) If the current class implementation of `resume` is not equal to the super class implementation of `resume` AND the current implementation of `resume` is not equal to the original implementation of `af_resume`, THEN swizzle the methods
          8) Set the current class to the super class, and repeat steps 3-8
       */
      NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration ephemeralSessionConfiguration];
      NSURLSession * session = [NSURLSession sessionWithConfiguration:configuration];
    

pragma GCC diagnostic push

pragma GCC diagnostic ignored "-Wnonnull"

    NSURLSessionDataTask *localDataTask = [session dataTaskWithURL:nil];

pragma clang diagnostic pop

    IMP originalAFResumeIMP = method_getImplementation(class_getInstanceMethod([self class], @selector(af_resume)));
    Class currentClass = [localDataTask class];
    
    while (class_getInstanceMethod(currentClass, @selector(resume))) {
        Class superClass = [currentClass superclass];
        IMP classResumeIMP = method_getImplementation(class_getInstanceMethod(currentClass, @selector(resume)));
        IMP superclassResumeIMP = method_getImplementation(class_getInstanceMethod(superClass, @selector(resume)));
        if (classResumeIMP != superclassResumeIMP &&
            originalAFResumeIMP != classResumeIMP) {
            [self swizzleResumeAndSuspendMethodForClass:currentClass];
        }
        currentClass = [currentClass superclass];
    }
    
    [localDataTask cancel];
    [session finishTasksAndInvalidate];
}

}

  • (void)swizzleResumeAndSuspendMethodForClass:(Class)theClass {
    Method afResumeMethod = class_getInstanceMethod(self, @selector(af_resume));
    Method afSuspendMethod = class_getInstanceMethod(self, @selector(af_suspend));

    if (af_addMethod(theClass, @selector(af_resume), afResumeMethod)) {
    af_swizzleSelector(theClass, @selector(resume), @selector(af_resume));
    }

    if (af_addMethod(theClass, @selector(af_suspend), afSuspendMethod)) {
    af_swizzleSelector(theClass, @selector(suspend), @selector(af_suspend));
    }
    }`</code></pre>

初始化過程與AFURLConnectionOperation的常駐線程處理方式不一樣,設置最大并發數量為1,想當于是串行隊列.
<pre><code>`- (instancetype)initWithSessionConfiguration:(NSURLSessionConfiguration *)configuration {
self = [super init];
if (!self) {
return nil;
}

if (!configuration) {
    configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
}

self.sessionConfiguration = configuration;

self.operationQueue = [[NSOperationQueue alloc] init];
self.operationQueue.maxConcurrentOperationCount = 1;

self.session = [NSURLSession sessionWithConfiguration:self.sessionConfiguration delegate:self delegateQueue:self.operationQueue];

self.responseSerializer = [AFJSONResponseSerializer serializer];

self.securityPolicy = [AFSecurityPolicy defaultPolicy];

if !TARGET_OS_WATCH

self.reachabilityManager = [AFNetworkReachabilityManager sharedManager];

endif

self.mutableTaskDelegatesKeyedByTaskIdentifier = [[NSMutableDictionary alloc] init];

self.lock = [[NSLock alloc] init];
self.lock.name = AFURLSessionManagerLockName;

[self.session getTasksWithCompletionHandler:^(NSArray *dataTasks, NSArray *uploadTasks, NSArray *downloadTasks) {
    for (NSURLSessionDataTask *task in dataTasks) {
        [self addDelegateForDataTask:task completionHandler:nil];
    }

    for (NSURLSessionUploadTask *uploadTask in uploadTasks) {
        [self addDelegateForUploadTask:uploadTask progress:nil completionHandler:nil];
    }

    for (NSURLSessionDownloadTask *downloadTask in downloadTasks) {
        [self addDelegateForDownloadTask:downloadTask progress:nil destination:nil completionHandler:nil];
    }
}];

[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(taskDidResume:) name:AFNSURLSessionTaskDidResumeNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(taskDidSuspend:) name:AFNSURLSessionTaskDidSuspendNotification object:nil];

return self;

}`</code></pre>

任務的暫停和繼續通過通知來執行:
<pre><code>`- (void)taskDidResume:(NSNotification *)notification {
NSURLSessionTask *task = notification.object;
if ([task respondsToSelector:@selector(taskDescription)]) {
if ([task.taskDescription isEqualToString:self.taskDescriptionForSessionTasks]) {
dispatch_async(dispatch_get_main_queue(), ^{
[[NSNotificationCenter defaultCenter] postNotificationName:AFNetworkingTaskDidResumeNotification object:task];
});
}
}
}

  • (void)taskDidSuspend:(NSNotification *)notification {
    NSURLSessionTask *task = notification.object;
    if ([task respondsToSelector:@selector(taskDescription)]) {
    if ([task.taskDescription isEqualToString:self.taskDescriptionForSessionTasks]) {
    dispatch_async(dispatch_get_main_queue(), ^{
    [[NSNotificationCenter defaultCenter] postNotificationName:AFNetworkingTaskDidSuspendNotification object:task];
    });
    }
    }
    }`</code></pre>

進度管理通過AFURLSessionManagerTaskDelegate來實現:
<pre><code>@interface AFURLSessionManagerTaskDelegate : NSObject <NSURLSessionTaskDelegate, NSURLSessionDataDelegate, NSURLSessionDownloadDelegate> @property (nonatomic, weak) AFURLSessionManager *manager; @property (nonatomic, strong) NSMutableData *mutableData; @property (nonatomic, strong) NSProgress *progress; @property (nonatomic, copy) NSURL *downloadFileURL; @property (nonatomic, copy) AFURLSessionDownloadTaskDidFinishDownloadingBlock downloadTaskDidFinishDownloading; @property (nonatomic, copy) AFURLSessionTaskCompletionHandler completionHandler; @end</code></pre>

異步轉同步的另外一種實現方式:
<pre><code>`- (NSArray *)tasksForKeyPath:(NSString *)keyPath {
__block NSArray *tasks = nil;
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
[self.session getTasksWithCompletionHandler:^(NSArray *dataTasks, NSArray *uploadTasks, NSArray *downloadTasks) {
if ([keyPath isEqualToString:NSStringFromSelector(@selector(dataTasks))]) {
tasks = dataTasks;
} else if ([keyPath isEqualToString:NSStringFromSelector(@selector(uploadTasks))]) {
tasks = uploadTasks;
} else if ([keyPath isEqualToString:NSStringFromSelector(@selector(downloadTasks))]) {
tasks = downloadTasks;
} else if ([keyPath isEqualToString:NSStringFromSelector(@selector(tasks))]) {
tasks = [@[dataTasks, uploadTasks, downloadTasks] valueForKeyPath:@"@unionOfArrays.self"];
}

    dispatch_semaphore_signal(semaphore);
}];

dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);

return tasks;

}

  • (NSArray *)tasks {
    return [self tasksForKeyPath:NSStringFromSelector(_cmd)];
    }
    `</code></pre>

AFHTTPSessionManger是AFURLSessionManager的子類,提供了GET,HEAD,POST,PUT,PATCH和DELETE六種訪問方式.
<pre><code>`- (nullable NSURLSessionDataTask *)GET:(NSString *)URLString
parameters:(nullable id)parameters
success:(nullable void (^)(NSURLSessionDataTask *task, id responseObject))success
failure:(nullable void (^)(NSURLSessionDataTask * __nullable task, NSError *error))failure;

/**
Creates and runs an NSURLSessionDataTask with a HEAD request.

@param URLString The URL string used to create the request URL.
@param parameters The parameters to be encoded according to the client request serializer.
@param success A block object to be executed when the task finishes successfully. This block has no return value and takes a single arguments: the data task.
@param failure A block object to be executed when the task finishes unsuccessfully, or that finishes successfully, but encountered an error while parsing the response data. This block has no return value and takes a two arguments: the data task and the error describing the network or parsing error that occurred.

@see -dataTaskWithRequest:completionHandler:
*/

  • (nullable NSURLSessionDataTask *)HEAD:(NSString *)URLString
    parameters:(nullable id)parameters
    success:(nullable void (^)(NSURLSessionDataTask *task))success
    failure:(nullable void (^)(NSURLSessionDataTask * __nullable task, NSError *error))failure;

/**
Creates and runs an NSURLSessionDataTask with a POST request.

@param URLString The URL string used to create the request URL.
@param parameters The parameters to be encoded according to the client request serializer.
@param success A block object to be executed when the task finishes successfully. This block has no return value and takes two arguments: the data task, and the response object created by the client response serializer.
@param failure A block object to be executed when the task finishes unsuccessfully, or that finishes successfully, but encountered an error while parsing the response data. This block has no return value and takes a two arguments: the data task and the error describing the network or parsing error that occurred.

@see -dataTaskWithRequest:completionHandler:
*/

  • (nullable NSURLSessionDataTask *)POST:(NSString *)URLString
    parameters:(nullable id)parameters
    success:(nullable void (^)(NSURLSessionDataTask *task, id responseObject))success
    failure:(nullable void (^)(NSURLSessionDataTask * __nullable task, NSError *error))failure;

/**
Creates and runs an NSURLSessionDataTask with a multipart POST request.

@param URLString The URL string used to create the request URL.
@param parameters The parameters to be encoded according to the client request serializer.
@param block A block that takes a single argument and appends data to the HTTP body. The block argument is an object adopting the AFMultipartFormData protocol.
@param success A block object to be executed when the task finishes successfully. This block has no return value and takes two arguments: the data task, and the response object created by the client response serializer.
@param failure A block object to be executed when the task finishes unsuccessfully, or that finishes successfully, but encountered an error while parsing the response data. This block has no return value and takes a two arguments: the data task and the error describing the network or parsing error that occurred.

@see -dataTaskWithRequest:completionHandler:
*/

  • (nullable NSURLSessionDataTask *)POST:(NSString *)URLString
    parameters:(nullable id)parameters
    constructingBodyWithBlock:(nullable void (^)(id <AFMultipartFormData> formData))block
    success:(nullable void (^)(NSURLSessionDataTask *task, id responseObject))success
    failure:(nullable void (^)(NSURLSessionDataTask * __nullable task, NSError *error))failure;

/**
Creates and runs an NSURLSessionDataTask with a PUT request.

@param URLString The URL string used to create the request URL.
@param parameters The parameters to be encoded according to the client request serializer.
@param success A block object to be executed when the task finishes successfully. This block has no return value and takes two arguments: the data task, and the response object created by the client response serializer.
@param failure A block object to be executed when the task finishes unsuccessfully, or that finishes successfully, but encountered an error while parsing the response data. This block has no return value and takes a two arguments: the data task and the error describing the network or parsing error that occurred.

@see -dataTaskWithRequest:completionHandler:
*/

  • (nullable NSURLSessionDataTask *)PUT:(NSString *)URLString
    parameters:(nullable id)parameters
    success:(nullable void (^)(NSURLSessionDataTask *task, id responseObject))success
    failure:(nullable void (^)(NSURLSessionDataTask * __nullable task, NSError *error))failure;

/**
Creates and runs an NSURLSessionDataTask with a PATCH request.

@param URLString The URL string used to create the request URL.
@param parameters The parameters to be encoded according to the client request serializer.
@param success A block object to be executed when the task finishes successfully. This block has no return value and takes two arguments: the data task, and the response object created by the client response serializer.
@param failure A block object to be executed when the task finishes unsuccessfully, or that finishes successfully, but encountered an error while parsing the response data. This block has no return value and takes a two arguments: the data task and the error describing the network or parsing error that occurred.

@see -dataTaskWithRequest:completionHandler:
*/

  • (nullable NSURLSessionDataTask *)PATCH:(NSString *)URLString
    parameters:(nullable id)parameters
    success:(nullable void (^)(NSURLSessionDataTask *task, id responseObject))success
    failure:(nullable void (^)(NSURLSessionDataTask * __nullable task, NSError *error))failure;

/**
Creates and runs an NSURLSessionDataTask with a DELETE request.

@param URLString The URL string used to create the request URL.
@param parameters The parameters to be encoded according to the client request serializer.
@param success A block object to be executed when the task finishes successfully. This block has no return value and takes two arguments: the data task, and the response object created by the client response serializer.
@param failure A block object to be executed when the task finishes unsuccessfully, or that finishes successfully, but encountered an error while parsing the response data. This block has no return value and takes a two arguments: the data task and the error describing the network or parsing error that occurred.

@see -dataTaskWithRequest:completionHandler:
*/

  • (nullable NSURLSessionDataTask *)DELETE:(NSString *)URLString
    parameters:(nullable id)parameters
    success:(nullable void (^)(NSURLSessionDataTask *task, id responseObject))success
    failure:(nullable void (^)(NSURLSessionDataTask * __nullable task, NSError *error))failure;
    `</code></pre>

Security

AFSecurityPolicy主要用于驗證HTTPS請求的證書,驗證的方式有三種.

<pre><code>typedef NS_ENUM(NSUInteger, AFSSLPinningMode) { AFSSLPinningModeNone, AFSSLPinningModePublicKey, AFSSLPinningModeCertificate, };</code></pre>

AFSSLPinningModeNone:直接在系統信任的證書列表中查找,只有系統信任機構簽發的證書才能通過.

AFSSLPinningModePublicKey:客戶端保存服務端證書的拷貝,檢驗證書的公鑰和服務端返回證書的公鑰是否一致;

AFSSLPinningModeCertificate:客戶端保存服務端證書的拷貝,先驗證證書的域名有效期等信息,然后對比客戶端的證書和服務端的正式是否一致;

安全校驗代碼:
<pre><code>`

  • (BOOL)evaluateServerTrust:(SecTrustRef)serverTrust {
    return [self evaluateServerTrust:serverTrust forDomain:nil];
    }

  • (BOOL)evaluateServerTrust:(SecTrustRef)serverTrust
    forDomain:(NSString *)domain
    {
    if (domain && self.allowInvalidCertificates && self.validatesDomainName && (self.SSLPinningMode == AFSSLPinningModeNone || [self.pinnedCertificates count] == 0)) {
    // https://developer.apple.com/library/mac/documentation/NetworkingInternet/Conceptual/NetworkingTopics/Articles/OverridingSSLChainValidationCorrectly.html
    // According to the docs, you should only trust your provided certs for evaluation.
    // Pinned certificates are added to the trust. Without pinned certificates,
    // there is nothing to evaluate against.
    //
    // From Apple Docs:
    // "Do not implicitly trust self-signed certificates as anchors (kSecTrustOptionImplicitAnchors).
    // Instead, add your own (self-signed) CA certificate to the list of trusted anchors."
    NSLog(@"In order to validate a domain name for self signed certificates, you MUST use pinning.");
    return NO;
    }

    NSMutableArray *policies = [NSMutableArray array];
    if (self.validatesDomainName) {
    [policies addObject:(__bridge_transfer id)SecPolicyCreateSSL(true, (__bridge CFStringRef)domain)];
    } else {
    [policies addObject:(__bridge_transfer id)SecPolicyCreateBasicX509()];
    }

    SecTrustSetPolicies(serverTrust, (__bridge CFArrayRef)policies);

    if (self.SSLPinningMode == AFSSLPinningModeNone) {
    return self.allowInvalidCertificates || AFServerTrustIsValid(serverTrust);
    } else if (!AFServerTrustIsValid(serverTrust) && !self.allowInvalidCertificates) {
    return NO;
    }

    NSArray *serverCertificates = AFCertificateTrustChainForServerTrust(serverTrust);
    switch (self.SSLPinningMode) {
    case AFSSLPinningModeNone:
    default:
    return NO;
    case AFSSLPinningModeCertificate: {
    NSMutableArray *pinnedCertificates = [NSMutableArray array];
    for (NSData *certificateData in self.pinnedCertificates) {
    [pinnedCertificates addObject:(__bridge_transfer id)SecCertificateCreateWithData(NULL, (__bridge CFDataRef)certificateData)];
    }
    SecTrustSetAnchorCertificates(serverTrust, (__bridge CFArrayRef)pinnedCertificates);

          if (!AFServerTrustIsValid(serverTrust)) {
              return NO;
          }
    
          NSUInteger trustedCertificateCount = 0;
          for (NSData *trustChainCertificate in serverCertificates) {
              if ([self.pinnedCertificates containsObject:trustChainCertificate]) {
                  trustedCertificateCount++;
              }
          }
          return trustedCertificateCount > 0;
      }
      case AFSSLPinningModePublicKey: {
          NSUInteger trustedPublicKeyCount = 0;
          NSArray *publicKeys = AFPublicKeyTrustChainForServerTrust(serverTrust);
    
          for (id trustChainPublicKey in publicKeys) {
              for (id pinnedPublicKey in self.pinnedPublicKeys) {
                  if (AFSecKeyIsEqualToKey((__bridge SecKeyRef)trustChainPublicKey, (__bridge SecKeyRef)pinnedPublicKey)) {
                      trustedPublicKeyCount += 1;
                  }
              }
          }
          return trustedPublicKeyCount > 0;
      }
    

    }

    return NO;
    }`</code></pre>

最終會通過C語言函數進行信任校驗:
<pre><code>`static NSArray * AFCertificateTrustChainForServerTrust(SecTrustRef serverTrust) {
CFIndex certificateCount = SecTrustGetCertificateCount(serverTrust);
NSMutableArray *trustChain = [NSMutableArray arrayWithCapacity:(NSUInteger)certificateCount];

for (CFIndex i = 0; i < certificateCount; i++) {
    SecCertificateRef certificate = SecTrustGetCertificateAtIndex(serverTrust, i);
    [trustChain addObject:(__bridge_transfer NSData *)SecCertificateCopyData(certificate)];
}

return [NSArray arrayWithArray:trustChain];

}

static NSArray * AFPublicKeyTrustChainForServerTrust(SecTrustRef serverTrust) {
SecPolicyRef policy = SecPolicyCreateBasicX509();
CFIndex certificateCount = SecTrustGetCertificateCount(serverTrust);
NSMutableArray *trustChain = [NSMutableArray arrayWithCapacity:(NSUInteger)certificateCount];
for (CFIndex i = 0; i < certificateCount; i++) {
SecCertificateRef certificate = SecTrustGetCertificateAtIndex(serverTrust, i);

    SecCertificateRef someCertificates[] = {certificate};
    CFArrayRef certificates = CFArrayCreate(NULL, (const void **)someCertificates, 1, NULL);

    SecTrustRef trust;
    __Require_noErr_Quiet(SecTrustCreateWithCertificates(certificates, policy, &trust), _out);

    SecTrustResultType result;
    __Require_noErr_Quiet(SecTrustEvaluate(trust, &result), _out);

    [trustChain addObject:(__bridge_transfer id)SecTrustCopyPublicKey(trust)];

_out:
    if (trust) {
        CFRelease(trust);
    }

    if (certificates) {
        CFRelease(certificates);
    }

    continue;
}
CFRelease(policy);

return [NSArray arrayWithArray:trustChain];

}`</code></pre>

Reachability

監聽當前App的網絡請求狀態,有網,無網,4G還是wifi.
<pre><code>typedef NS_ENUM(NSInteger, AFNetworkReachabilityStatus) { AFNetworkReachabilityStatusUnknown = -1, AFNetworkReachabilityStatusNotReachable = 0, AFNetworkReachabilityStatusReachableViaWWAN = 1, AFNetworkReachabilityStatusReachableViaWiFi = 2, };</code></pre>

經典的單例模式又出現了:
<pre><code>`+ (instancetype)sharedManager {
static AFNetworkReachabilityManager *_sharedManager = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
struct sockaddr_in address;
bzero(&address, sizeof(address));
address.sin_len = sizeof(address);
address.sin_family = AF_INET;

    _sharedManager = [self managerForAddress:&address];
});

return _sharedManager;

}`</code></pre>

代碼中的sockaddr_in用來處理網絡通信的地址:
<pre><code>struct sockaddr_in { __uint8_t sin_len; sa_family_t sin_family; in_port_t sin_port; struct in_addr sin_addr; char sin_zero[8]; };</code></pre>

sin_family表示協議族,sin_port表示端口,sin_addr表示網絡地址,sin_zero填充字節.

sockaddr_in的比較對象是sockaddr,sockaddr是給操作系統用的,sockaddr的sa_data字段被sockaddr_in擴展成了三個字段.
<pre><code>struct sockaddr { __uint8_t sa_len; /* total length */ sa_family_t sa_family; /* [XSI] address family */ char sa_data[14]; /* [XSI] addr value (actually larger) */ };</code></pre>

通過SCNetworkReachabilityContext來監控網絡變化:
<pre><code>`- (void)startMonitoring {
[self stopMonitoring];

if (!self.networkReachability) {
    return;
}

__weak __typeof(self)weakSelf = self;
AFNetworkReachabilityStatusBlock callback = ^(AFNetworkReachabilityStatus status) {
    __strong __typeof(weakSelf)strongSelf = weakSelf;

    strongSelf.networkReachabilityStatus = status;
    if (strongSelf.networkReachabilityStatusBlock) {
        strongSelf.networkReachabilityStatusBlock(status);
    }

};

id networkReachability = self.networkReachability;
SCNetworkReachabilityContext context = {0, (__bridge void *)callback, AFNetworkReachabilityRetainCallback, AFNetworkReachabilityReleaseCallback, NULL};
SCNetworkReachabilitySetCallback((__bridge SCNetworkReachabilityRef)networkReachability, AFNetworkReachabilityCallback, &context);
SCNetworkReachabilityScheduleWithRunLoop((__bridge SCNetworkReachabilityRef)networkReachability, CFRunLoopGetMain(), kCFRunLoopCommonModes);

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0),^{
    SCNetworkReachabilityFlags flags;
    if (SCNetworkReachabilityGetFlags((__bridge SCNetworkReachabilityRef)networkReachability, &flags)) {
        AFPostReachabilityStatusChange(flags, callback);
    }
});

}

  • (void)stopMonitoring {
    if (!self.networkReachability) {
    return;
    }

    SCNetworkReachabilityUnscheduleFromRunLoop((__bridge SCNetworkReachabilityRef)self.networkReachability, CFRunLoopGetMain(), kCFRunLoopCommonModes);
    }`</code></pre>

Serialization

序列化數據有兩個主要的類AFURLRequestSerialization和AFURLResponseSerialization.

查詢字符串百分比編碼,經典如下:
<pre><code>`static NSString * AFPercentEscapedStringFromString(NSString string) {
static NSString * const kAFCharactersGeneralDelimitersToEncode = @":#[]@"; // does not include "?" or "/" due to RFC 3986 - Section 3.4
static NSString * const kAFCharactersSubDelimitersToEncode = @"!$&'()
+,;=";

NSMutableCharacterSet * allowedCharacterSet = [[NSCharacterSet URLQueryAllowedCharacterSet] mutableCopy];
[allowedCharacterSet removeCharactersInString:[kAFCharactersGeneralDelimitersToEncode stringByAppendingString:kAFCharactersSubDelimitersToEncode]];

// FIXME: https://github.com/AFNetworking/AFNetworking/pull/3028
// return [string stringByAddingPercentEncodingWithAllowedCharacters:allowedCharacterSet];

static NSUInteger const batchSize = 50;

NSUInteger index = 0;
NSMutableString *escaped = @"".mutableCopy;

while (index < string.length) {

pragma GCC diagnostic push

pragma GCC diagnostic ignored "-Wgnu"

    NSUInteger length = MIN(string.length - index, batchSize);

pragma GCC diagnostic pop

    NSRange range = NSMakeRange(index, length);

    // To avoid breaking up character sequences such as ????????
    range = [string rangeOfComposedCharacterSequencesForRange:range];

    NSString *substring = [string substringWithRange:range];
    NSString *encoded = [substring stringByAddingPercentEncodingWithAllowedCharacters:allowedCharacterSet];
    [escaped appendString:encoded];

    index += range.length;
}

return escaped;

}
`</code></pre>

AFNetworking文件上傳的封裝的非常巧妙:
<pre><code>`- (NSMutableURLRequest *)multipartFormRequestWithMethod:(NSString *)method
URLString:(NSString *)URLString
parameters:(NSDictionary *)parameters
constructingBodyWithBlock:(void (^)(id <AFMultipartFormData> formData))block
{
return [self multipartFormRequestWithMethod:method URLString:URLString parameters:parameters constructingBodyWithBlock:block error:nil];
}

  • (NSMutableURLRequest *)multipartFormRequestWithMethod:(NSString *)method
    URLString:(NSString *)URLString
    parameters:(NSDictionary *)parameters
    constructingBodyWithBlock:(void (^)(id <AFMultipartFormData> formData))block
    error:(NSError *__autoreleasing *)error
    {
    NSParameterAssert(method);
    NSParameterAssert(![method isEqualToString:@"GET"] && ![method isEqualToString:@"HEAD"]);

    NSMutableURLRequest *mutableRequest = [self requestWithMethod:method URLString:URLString parameters:nil error:error];

    __block AFStreamingMultipartFormData *formData = [[AFStreamingMultipartFormData alloc] initWithURLRequest:mutableRequest stringEncoding:NSUTF8StringEncoding];

    if (parameters) {
    for (AFQueryStringPair *pair in AFQueryStringPairsFromDictionary(parameters)) {
    NSData *data = nil;
    if ([pair.value isKindOfClass:[NSData class]]) {
    data = pair.value;
    } else if ([pair.value isEqual:[NSNull null]]) {
    data = [NSData data];
    } else {
    data = [[pair.value description] dataUsingEncoding:self.stringEncoding];
    }

          if (data) {
              [formData appendPartWithFormData:data name:[pair.field description]];
          }
      }
    

    }

    if (block) {
    block(formData);
    }

    return [formData requestByFinalizingMultipartFormData];
    }`</code></pre>

文件上傳請求的流程圖如下:

AFURLRequestSerialization.png

AFHTTPBodyPart封裝了各部分數據的組裝和讀取,一個AFHTTPBodyPart就是一個數據塊.FileURL/NSData/NSInputStream的數據在AFHTTPBodyPart都轉成NSInputStream,讀取數據時只需讀這個inputStream.inputStream只保存了數據的實體,沒有包括分隔符和頭部,AFHTTPBodyPart邊讀取邊上傳,每次讀取的大小有限制,通過狀態機判斷數據讀取進度,讀取進度方法如下:

<pre><code>`- (NSInteger)read:(uint8_t *)buffer
maxLength:(NSUInteger)length
{
if ([self streamStatus] == NSStreamStatusClosed) {
return 0;
}

NSInteger totalNumberOfBytesRead = 0;

pragma clang diagnostic push

pragma clang diagnostic ignored "-Wgnu"

while ((NSUInteger)totalNumberOfBytesRead < MIN(length, self.numberOfBytesInPacket)) {
    if (!self.currentHTTPBodyPart || ![self.currentHTTPBodyPart hasBytesAvailable]) {
        if (!(self.currentHTTPBodyPart = [self.HTTPBodyPartEnumerator nextObject])) {
            break;
        }
    } else {
        NSUInteger maxLength = length - (NSUInteger)totalNumberOfBytesRead;
        NSInteger numberOfBytesRead = [self.currentHTTPBodyPart read:&buffer[totalNumberOfBytesRead] maxLength:maxLength];
        if (numberOfBytesRead == -1) {
            self.streamError = self.currentHTTPBodyPart.inputStream.streamError;
            break;
        } else {
            totalNumberOfBytesRead += numberOfBytesRead;

            if (self.delay > 0.0f) {
                [NSThread sleepForTimeInterval:self.delay];
            }
        }
    }
}

pragma clang diagnostic pop

return totalNumberOfBytesRead;

}`</code></pre>

AFURLResponseSerialization響應序列化:
<pre><code>@protocol AFURLResponseSerialization <NSObject, NSSecureCoding, NSCopying></code></pre>

初始化設置了可接受的成功狀態碼:
<pre><code>`- (instancetype)init {
self = [super init];
if (!self) {
return nil;
}

self.stringEncoding = NSUTF8StringEncoding;

self.acceptableStatusCodes = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(200, 100)];
self.acceptableContentTypes = nil;

return self;

}`</code></pre>

AFImageResponseSerializer:接收的圖片序列化主流圖片格式都有判斷.
<pre><code>`@implementation AFImageResponseSerializer

  • (instancetype)init {
    self = [super init];
    if (!self) {
    return nil;
    }

    self.acceptableContentTypes = [[NSSet alloc] initWithObjects:@"image/tiff", @"image/jpeg", @"image/gif", @"image/png", @"image/ico", @"image/x-icon", @"image/bmp", @"image/x-bmp", @"image/x-xbitmap", @"image/x-win-bitmap", nil];

if TARGET_OS_IOS

self.imageScale = [[UIScreen mainScreen] scale];
self.automaticallyInflatesResponseImage = YES;

elif TARGET_OS_WATCH

self.imageScale = [[WKInterfaceDevice currentDevice] screenScale];
self.automaticallyInflatesResponseImage = YES;

endif

return self;

}`</code></pre>

刪除返回對象中的null值的輔助函數:
<pre><code>`static id AFJSONObjectByRemovingKeysWithNullValues(id JSONObject, NSJSONReadingOptions readingOptions) {
if ([JSONObject isKindOfClass:[NSArray class]]) {
NSMutableArray *mutableArray = [NSMutableArray arrayWithCapacity:[(NSArray *)JSONObject count]];
for (id value in (NSArray *)JSONObject) {
[mutableArray addObject:AFJSONObjectByRemovingKeysWithNullValues(value, readingOptions)];
}

    return (readingOptions & NSJSONReadingMutableContainers) ? mutableArray : [NSArray arrayWithArray:mutableArray];
} else if ([JSONObject isKindOfClass:[NSDictionary class]]) {
    NSMutableDictionary *mutableDictionary = [NSMutableDictionary dictionaryWithDictionary:JSONObject];
    for (id <NSCopying> key in [(NSDictionary *)JSONObject allKeys]) {
        id value = (NSDictionary *)JSONObject[key];
        if (!value || [value isEqual:[NSNull null]]) {
            [mutableDictionary removeObjectForKey:key];
        } else if ([value isKindOfClass:[NSArray class]] || [value isKindOfClass:[NSDictionary class]]) {
            mutableDictionary[key] = AFJSONObjectByRemovingKeysWithNullValues(value, readingOptions);
        }
    }

    return (readingOptions & NSJSONReadingMutableContainers) ? mutableDictionary : [NSDictionary dictionaryWithDictionary:mutableDictionary];
}

return JSONObject;

}`</code></pre>

特別感謝bang神的博客三年前的博客,現在讀起來依然是干貨十足.文中關鍵代碼并沒有中文注釋,代碼塊都不長,正常讀起來應該都沒問題,如有問題歡迎評論區探討.

AFNetWorking是如何避免循環引用的?
項目開中為了避免控制器或者視圖不釋放,經常會檢查Block在調用的過程是否會發生循環引用,當發起網絡請求的的時候,self持有opration,operation如果直接持有self,肯定會出現循環引用,但是開始中我們可以直接寫self.

AFNetWorking在block后調用[strongSelf setCompletionBlock:nil]把completionBlock設成nil,手動釋放self(NSOperation對象)持有的completionBlock對象,打破循環引用.
<pre><code>`- (void)setCompletionBlock:(void (^)(void))block {
[self.lock lock];
if (!block) {
[super setCompletionBlock:nil];
} else {
__weak __typeof(self)weakSelf = self;
[super setCompletionBlock:^ {
__strong __typeof(weakSelf)strongSelf = weakSelf;

pragma clang diagnostic push

pragma clang diagnostic ignored "-Wgnu"

        dispatch_group_t group = strongSelf.completionGroup ?: url_request_operation_completion_group();
        dispatch_queue_t queue = strongSelf.completionQueue ?: dispatch_get_main_queue();

pragma clang diagnostic pop

        dispatch_group_async(group, queue, ^{
            block();
        });

        dispatch_group_notify(group, url_request_operation_completion_queue(), ^{
            [strongSelf setCompletionBlock:nil];
        });
    }];
}
[self.lock unlock];

}`</code></pre>

參考資料:
AFNetworking2.0源碼解析<一>
使用NSSecureCoding協議進行對象編解碼
https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/Streams/Articles/CocoaStreamsOverview.html#//apple_ref/doc/uid/20002272-BABJFBBB
sockaddr和sockaddr_in的區別

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容