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的区别

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,189评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,577评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,857评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,703评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,705评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,620评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,995评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,656评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,898评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,639评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,720评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,395评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,982评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,953评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,195评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,907评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,472评论 2 342

推荐阅读更多精彩内容