iOS最新面试题解答最全-2023-01

一、多线程间通信

本地的进程间通信(IPC)有很多种方式,但可以总结为下面 4 类:
消息传递(管道、FIFO、消息队列) 同步(互斥量、条件变量、读写锁、文件和写记录锁、信号量) 共享内存(匿名的和具名的)远程过程调用(Solaris 门和 Sun RPC)
在面试中,经常被面试官问到线程间是如何通讯的,很多童鞋会回答在子线程获取数据,切换回主线程刷新UI,那么请你回家等消息。苹果的官方文档给我们列出了线程间通讯的几种方式

image.png

上图的表格是按照技术复杂度由低到高顺序排列的,其中后两种只能在OS X中使用。

Direct messaging:这是大家非常熟悉的-performSelector:系列。
Global variables...:直接通过全局变量、共享内存等方式,但这种方式会造成资源抢夺,涉及到线程安全问题。
Conditions:一种特殊的锁--条件锁,当使用条件锁使一个线程等待(wait)时,该线程会被阻塞并进入休眠状态,在另一个线程中对同一个条件锁发送信号(single),则等待中的线程会被唤醒继续执行任务。

//构建条件锁,条件值为1
    NSConditionLock *lock = [[NSConditionLock alloc]initWithCondition:1];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        //线程1 传入9,与条件值不相等,无法正确进行加锁操作,会阻塞线程
        [lock lockWhenCondition:9];
        NSLog(@"11111");
        sleep(1);
        [lock unlock];
    });
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        sleep(1);
        //传入1,如果与条件的值相等,返回YES;如果不相等则返回NO; 不会阻塞当前线程
        if ([lock tryLockWhenCondition:1]) {
            NSLog(@"22222");
            NSLog(@"2加锁");
            //解锁,并把条件值修改为9
            [lock unlockWithCondition:9];
        //   [lock unlock];
 
        }else{
            NSLog(@"2加锁失败");
        }
    });

Run loop sources:通过自定义Run loop sources来实现,后面的文章会单独研究Run loop。
Ports and sockets:通过端口和套接字来实现线程间通讯。

// AvatarDownloader.h
extern NSString * const AvatarDownloaderUrlKey;

extern NSString * const AvatarDownloaderPortKey;

@interface AvatarDownloader : NSObject

- (void)downloadAvatarInfo:(NSDictionary *)info;

@end

// AvatarDownloader.m
NSString * const AvatarDownloaderUrlKey = @"Url";

NSString * const AvatarDownloaderPortKey = @"Port";

@interface AvatarDownloader ()<NSMachPortDelegate>

@property (nonatomic, strong) NSPort *completePort;

@property (nonatomic, strong) NSMachPort *downloaderPort;

@end

@implementation AvatarDownloader

- (instancetype)init {
    if (self = [super init]) {
        self.downloaderPort = [[NSMachPort alloc] init];
        self.downloaderPort.delegate = self;
    }
    return self;
}

- (void)downloadAvatarInfo:(NSDictionary *)info {
    @autoreleasepool {
        NSLog(@"download thread: %@", [NSThread currentThread]);
        NSString *url = info[AvatarDownloaderUrlKey];
        NSLog(@"download url: %@", url);
        self.completePort = info[AvatarDownloaderPortKey];

        // 模拟下载
        sleep(2);
        UIImage *img = [UIImage imageNamed:@"avatar.jpg"];
        NSData *data = UIImageJPEGRepresentation(img, 1);
        NSLog(@"download complete");

        NSMutableArray *components = @[data].mutableCopy;
        [self.completePort sendBeforeDate:[NSDate date]
                                    msgid:1
                               components:components
                                     from:self.downloaderPort
                                 reserved:0];
    }
}

#pragma mark - NSMachPortDelegate
- (void)handlePortMessage:(NSPortMessage *)message {
    NSLog(@"downloader handlePortMessage: %@", [NSThread mainThread]);
    NSArray *components = [(id)message valueForKey:@"components"];
    NSData *data = components[0];
    NSString *msg = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
    NSLog(@"response msg from receiver: %@", msg);
}
// ViewController.m
#import "ViewController.h"
#import "AvatarDownloader.h"

NSString * const AVATAR_URL = @"http://img3.imgtn.bdimg.com/it/u=1559309274,2399850183&fm=26&gp=0.jpg";

@interface RootViewController ()<NSMachPortDelegate>

@property (weak, nonatomic) IBOutlet UIImageView *imageView;

@property (nonatomic, strong) NSMachPort *mainPort;

@property (nonatomic, strong) AvatarDownloader *downloader;

@end

@implementation RootViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // 创建Port对象,并添加到主线程的Runloop中
    self.mainPort = [[NSMachPort alloc] init];
    self.mainPort.delegate = self;
    [[NSRunLoop currentRunLoop] addPort:self.mainPort forMode:NSDefaultRunLoopMode];

    NSDictionary *info = @{AvatarDownloaderUrlKey : AVATAR_URL,
                           AvatarDownloaderPortKey : self.mainPort};

    self.downloader = [[AvatarDownloader alloc] init];
    [NSThread detachNewThreadSelector:@selector(downloadAvatarInfo:)
                             toTarget:self.downloader
                           withObject:info];
}

#pragma mark - NSPortDelegate
- (void)handlePortMessage:(NSPortMessage *)message {
    NSLog(@"handlePortMessage: %@", [NSThread currentThread]);
    NSArray *array = [(id)message valueForKey:@"components"];
    NSData *data = array[0];
    UIImage *avatar = [UIImage imageWithData:data];
    self.imageView.image = avatar;

    NSData *responseMsg = [@"头像已收到" dataUsingEncoding:NSUTF8StringEncoding];
    NSMutableArray *components = @[responseMsg].mutableCopy;
    NSPort *remotePort = [(id)message valueForKey:@"remotePort"];

    // downloader线程已销毁,因此要给remotePort发消息,就得把它添加到存活的runloop中
    [[NSRunLoop currentRunLoop] addPort:remotePort forMode:NSDefaultRunLoopMode];

    [remotePort sendBeforeDate:[NSDate date]
                         msgid:2
                    components:components
                          from:self.mainPort
                      reserved:0];
}

@end

NSPort的使用要点:
NSPort对象必须添加到要接收消息的线程的Runloop中

2020-02-23 00:11:43.448999+0800 TestObjC[3140:208871] download thread: <NSThread: 0x600001e1e740>{number = 6, name = (null)}
2020-02-23 00:11:43.449342+0800 TestObjC[3140:208871] download url: http://img3.imgtn.bdimg.com/it/u=1559309274,2399850183&fm=26&gp=0.jpg
2020-02-23 00:11:45.486259+0800 TestObjC[3140:208871] download complete
2020-02-23 00:11:45.486600+0800 TestObjC[3140:208701] handlePortMessage: <NSThread: 0x600001e49e00>{number = 1, name = main}
2020-02-23 00:11:45.492472+0800 TestObjC[3140:208701] downloader handlePortMessage: <NSThread: 0x600001e49e00>{number = 1, name = main}
2020-02-23 00:11:45.492666+0800 TestObjC[3140:208701] response msg from receiver: 头像已收到

代码中首先将self.mainPort添加到主线程的Runloop中,然后起新线程下载头像,下载完成后通过mainPort发送消息,此时并没有手动切换线程,但是controller中的回调却是在主线程中的,如此便完成了线程间的通讯。

接收消息的对象实现NSPortDelegate协议的-handlePortMessage:方法来获取消息内容
使用的方式为接收线程中注册的NSMachPort,在另外的线程中使用此port发送消息,则被注册线程会收到相应消息,最终在主线程里调用某个回调函数。

NSPort是描述通信通道的抽象类。在两个NSPort对象之间通过- (BOOL)sendBeforeDate:(NSDate *)limitDate msgid:(NSUInteger)msgID components:(nullable NSMutableArray *)components from:(nullable NSPort *)receivePort reserved:(NSUInteger)headerSpaceReserved;发送消息和handlePortMessage:代理接受NSPortMessage对象,来实现线程(或进程或应用)之间的通信。
NSPort对象必须作为输入源添加到NSRunLoop对象中,来实现线程不退出。
NSPort有3个子类NSMachPort、NSMessagePort和NSSocketPort。
CFRunLoopSource中source1事件源采用port,来监听系统端口和通过内核和其他线程发送消息。

//发送消息
- (BOOL)sendBeforeDate:(NSDate *)limitDate 
                 msgid:(NSUInteger)msgID 
            components:(NSMutableArray *)components 
                  from:(NSPort *)receivePort 
              reserved:(NSUInteger)headerSpaceReserved;有关这个方法解释一下,
limitDate:消息可能被发送的最后时刻;
msgID:消息ID
components:消息组件
receivePort:接收端口
headerSpaceReserved:为报头保留的字节数。
//接收消息就是NSMachPortDelegate - (void)handlePortMessage:(NSPortMessage *)message方法
通过 NSPort *remotePort = [(id)message valueForKey:@"remotePort"];可以取出receivePort的port,用port发送消息实现线程通信
上面示例没有体现出来真正不同线程之间通信,应该在主线程又把本来是子线程的port加入到主线程runloop当中了。接下来我们把上面代码改一下
- (void)viewDidLoad {
    [super viewDidLoad];
    self.view.backgroundColor = [UIColor blueColor];
    self.imageView = [[UIImageView alloc]initWithFrame:CGRectMake(20, 60, 80, 80)];
    [self.view addSubview:self.imageView];
    self.mainPort = [[NSMachPort alloc] init];
       self.mainPort.delegate = self;
       [[NSRunLoop currentRunLoop] addPort:self.mainPort forMode:NSDefaultRunLoopMode];

    self.thread = [[NSThread alloc] initWithTarget:self selector:@selector(addThreadToRunloop) object:nil];
          [self.thread start];

}
- (void)addThreadToRunloop {
    NSDictionary *info = @{AvatarDownloaderUrlKey : AVATAR_URL,
                           AvatarDownloaderPortKey : self.mainPort};

    self.downloader = [[AvatarDownloader alloc] init];
    [[NSRunLoop currentRunLoop] addPort:self.downloader.downloaderPort forMode:NSDefaultRunLoopMode];
    
    [self.downloader downloadAvatarInfo:info];
     [[NSRunLoop currentRunLoop] run];
}
#pragma mark - NSPortDelegate
- (void)handlePortMessage:(NSPortMessage *)message {
    NSLog(@"handlePortMessage: %@", [NSThread currentThread]);
    NSArray *array = [(id)message valueForKey:@"components"];
    NSData *data = array[0];
    UIImage *avatar = [UIImage imageWithData:data];
    self.imageView.image = avatar;

    NSData *responseMsg = [@"头像已收到" dataUsingEncoding:NSUTF8StringEncoding];
    NSMutableArray *components = @[responseMsg].mutableCopy;
    NSPort *remotePort = [(id)message valueForKey:@"remotePort"];

    // downloader线程已销毁,因此要给remotePort发消息,就得把它添加到存活的runloop中
  //  [[NSRunLoop currentRunLoop] addPort:remotePort forMode:NSDefaultRunLoopMode];
//<NSMachPort: 0x600001986780>
    [remotePort sendBeforeDate:[NSDate date]
                         msgid:2
                    components:components
                          from:self.mainPort
                      reserved:0];
}
这样改才是真正上子线程与主线程相互通信了

总结:创建了NSMachPort 加入runloop上,port通过- (BOOL)sendBeforeDate:(NSDate *)limitDate msgid:(NSUInteger)msgID components:(nullable NSMutableArray *)components from:(nullable NSPort *)receivePort reserved:(NSUInteger)headerSpaceReserved;发送消息和代理方法- (void)handlePortMessage:(NSPortMessage *)message 接收消息两个方法实现线程之间的通信,有一点注意的是子线程需要调用[[NSRunLoop currentRunLoop] run];而且调用时机不同,就决定是否子线程能够通过NSPort接受到主线程的消息。也就是必须将[[NSRunLoop currentRunLoop] run];一定要放到[self downloadAvatarInfo];之后。

二、内存优化

性能优化点:
不要在启动时花几百ms来做logging,不要为同样的数据做多次查询
事后做优化:异步加载、懒加载
事先做优化:对于昂贵的计算,要进行事先计算。iCal中的重复事件,是预先计算出来的,并保存到数据库中。
事先计算并缓存一些对象,可能会占用大量的内存。注意不要将这些对象声明为static并常驻内存。
基础

这些技巧你要总是想着实现在你开发的App中。

  1. 用ARC去管理内存(Use ARC to Manage Memory)

2.适当的地方使用reuseIdentifier(Use a reuseIdentifier Where Appropriate)

3.尽可能设置视图为不透明(Set View as Opaque When Possible)

4.避免臃肿的XIBs文件(Avoid Fat XiBs)

5.不要阻塞主进程(Don't Block the Main Thread)

6.调整图像视图中的图像尺寸(Size Images to Image Views)

7.选择正确集合(Choose the Correct Collection)
这是一个最常见的集合类型的快速简介:

Arrays:有序的值的列表,用index快速查找,通过值查找慢,insert/delete操作慢。

Dictionaries:存储键/值对.用index快速查找。

Sets: 无序的值列表。通过值快速查找,insert/delete快。

8.启用Gzip压缩(Enable GZIP Compression)
中级
这些技巧是当你遇到更复杂的情况的时候使用。
9. 重用和延迟加载视图(Reuse and Lazy Load Views)
10.缓存,缓存,缓存(Cache,Cache,Cache)

11.考虑绘图(Consider Drawing)

12.处理内存警告(Handle Memory Warnings)

13.重用大开销对象(Reuse Expensive Objects)
有的对象的初始化非常慢--NSDateFormatter 和 NSCalendar是两个例子,但是你不能避免使用它们,当你从 JSON/XML响应中解析日期时。
避免使用这些对象时的性能瓶颈,试着尽可能的重用这些对象。你可以加入你的类中成为一个属性,也可以创建为静态变量。
14.使用精灵表(Use Sprite Sheets )

15.避免重复处理数据(Avoid Re-Processing Data)

16.选择正确的数据格式(Choose the Right Data Format)

17.适当的设置背景图片(Set Background Images Appropriately)

18.减少你的网络占用(Reduce Your Web Footprint)

19.设置阴影路径(Set the Shadow Path )
这里有个替代方法让系统更好的渲染,设置阴影路径:
view.layer.shadowPath = [[UIBezierPath bezierPathWithRect:view.bounds] CGPath];
imageNamed的优点在于可以缓存已经加载的图片
imageWithContentsOfFile比较合适——系统不会浪费内存来缓存图片。
20.你的表格视图Optimize Your Table Views)
通过正确的reuseIdentifier重用cells

尽量多的设置views 为不透明,包括cell本身。

避免渐变,图像缩放,屏幕以外的绘制。

如果行高不总是一样,缓存它们。

如果cell显示的内容来自网络,确保异步和缓存。

使用shadowPath来建立阴影。

减少子视图的数目。

cellForRowAtIndexPath:中做尽量少的工作,如果需要做相同的工作,那么只做一次并缓存结果。

使用适当的数据结构存储你要的信息,不同的结构有对于不同的操作有不同的代价。

使用rowHeight,sectionFooterHeight,sectionHeaderHeight为常数,而不是询问代理。
21.选择正确的数据存储方式(Choose Correct Data Storage Option)
高级

这些技巧你应该只在你很积极认为它们能解决这个问题,而且你觉得用它们很舒适的时候使用。

22.加速启动时间(Speed up Launch Time )

23.使用自动释放池(Use AutoRelease Pool)

24.缓存图像(Cache Images-Or not )
UIImage *img = [UIImage imageWithContentsOfFile:@"myImage"]; // no caching

如果你加载只使用一次大图片,那就不需要缓存。这种情况imageWithContendsOfFile会非常好,这种方式不会浪费内存来缓存图片。什么时候使用哪一种呢?

然而,imageNamed 对于要重用的图片来说是更好的选择,这种方法节约了经常的从磁盘加载图片的时间。
25.尽可能避免日期格式化器(Avoid Date Formatters Where Possible)

UITableView 如何优化

提前计算并缓存好高度(布局),因为 heightForRowAtIndexPath:是调
用最频繁的方法;
异步绘制,遇到复杂界面,遇到性能瓶颈时,可能就是突破口;
滑动时按需加载,这个在大量图片展示,网络加载的时候很管用! (SDWebImage 已经实现异步加载,配合这条性能杠杠的)。
正确使用 reuseIdentifier 来重用 Cells
尽量使所有的 view opaque,包括 Cell 自身
尽量少用或不用透明图层
如果 Cell 内现实的内容来自 web,使用异步加载,缓存请求结果
减少 subviews 的数量
在 heightForRowAtIndexPath:中尽量不使用 cellForRowAtIndexPath:, 如果你需要用到它,只用一次然后缓存结果
尽量少用 addView 给 Cell 动态添加 View,可以初始化时就添加,然 后通过 hide 来控制是否显示

启动优化

总结来说,main()方法调用前,启动过程大体分为如下步骤:

1、内核加载可执行文件

2、load dylibs image (加载程序所需的动态库镜像文件)

3、Rebase image /  Bind image (由于ASLR(address space layout randomization)的存在,可执行文件和动态链接库在虚拟内  存中的加载地址每次启动都不固定,所以需要修复镜像中的资源指针)

4、Objc setup (注册Objc类、将Category中的方法插入方法列表)

5、initializers (调用Objc类的+load()方法、调用C++类的构造函数)

针对上边各个启动过程,我们可以做的优化有:

1、减少动态库的引用,将项目中不使用的Framework及时删除,将Xcode配置中General -> Linked Frameworks and Libraries中使用不到的系统库不再引用。

2、合并动态库。

3、尽量不使用内嵌(embedded)的dylib,加载内嵌dylib性能开销较大。

4、清理项目中冗余的类、category。对于同一个类有多个category的,建议进行合并。

5、将不必须在+load方法中做的事情延迟到+initialize中。

6、尽量不要用C++虚函数(创建虚函数表有开销),不要在C++构造函数中做大量耗时操作。
四、main()方法调用之后过程的解析:

 main()方法调用之后,主要是didFinishLaunchingWithOptions方法中初始化必要的服务,显示首页内容等操作。这时候我们可以做的事情主要有:

 1、将一些不影响首页展示的服务放到其他线程中去处理,或者延时处理和懒加载。延时处理可以监听Runloop的状态,当进入kCFRunLoopBeforeWaiting(即将休眠状态)再去处理任务,最大限度的利用CPU等系统资源。

 2、使用Xcode的Instruments的Time Profiler工具,分析启动过程中比较耗时的方法和操作,然后,进行具体的优化。

3、重点关注TabBarController和首页的性能,保证尽快的能展示出来。这两个控制器及里边的view尽量用代码进行布局,不使用storyboard和xib,如果在布局上想更进一步的优化,那就连autolayout(Massonry)都不要使用,直接使用frame进行布局。

4、本地缓存。首页的数据离线化,优先展示本地缓存数据,等待网络数据返回之后更新缓存并展示。

三、项目架构

image.png

MVVM
model — view — view-model
MVVM框架是在MVC的基础上演化而来,MVVM想要解决的问题是尽可能地减少Controller的任务。

  • Model:程序中要操纵的实际对象的抽象
  • View(ViewController):MVVM中的View不再是UIView的子类,而变成了UIViewController的子类。这里的View实际上就是MVC中剥离了处理呈现View逻辑部分的Controller,因此它仍然有各种UIView的属性,仍然有ViewController的声明周期的各种方法,但是这里的Controller不再负责数据的请求以及处理逻辑,因此不再臃肿。
  • ViewModel:MVVM中,ViewModel代替了MVC中的Controller成为了协调者的角色,ViewModel被View(ViewController)持有,同时持有Model。数据请求以及处理逻辑都放在ViewModel中,View(ViewController)就瘦了下来。

四、网络框架

AFNetworking是封装的NSURLSession的网络请求,由五个模块组成:分别由NSURLSession,Security,Reachability,Serialization,UIKit五部分组成
1、NSURLSession:网络通信模块(核心模块) 对应 AFNetworking中的AFURLSessionManager和对HTTP协议进行特化处理的AFHTTPSessionManager,AFHTTPSessionManager是继承于AFURLSessionmanager的

2、Security:网络通讯安全策略模块 对应 AFSecurityPolicy

3、Reachability:网络状态监听模块 对应AFNetworkReachabilityManager

4、Seriaalization:网络通信信息序列化、反序列化模块 对应 AFURLResponseSerialization

5、UIKit:对于iOS UIKit的扩展库

AFN调用流程分析:

1)AFHTTPSessionManager: 发起网络请求(例如GET);
- (AFHTTPSessionManager *)sessionManager
{
    if (_sessionManager == nil) {

        NSString *cerPath = [[NSBundle mainBundle] pathForResource:@"mdmiciticnet" ofType:@"cer"];
        NSData * certData =[NSData dataWithContentsOfFile:cerPath];
        NSSet * certSet = [[NSSet alloc] initWithObjects:certData, nil];
        AFSecurityPolicy *securityPolicy = [AFSecurityPolicy policyWithPinningMode:AFSSLPinningModePublicKey];
        // 是否允许,NO-- 不允许无效的证书
        [securityPolicy setAllowInvalidCertificates:YES];
        // 设置证书
       // [securityPolicy setPinnedCertificates:certSet];
        [securityPolicy setValidatesDomainName:YES];
        
        _sessionManager = [AFHTTPSessionManager manager];
        _sessionManager.securityPolicy = securityPolicy;
        _sessionManager.responseSerializer = [AFHTTPResponseSerializer serializer];
        //这里是设置网络请求队列,如果不传 就默认主线程回到回来
//        dispatch_queue_t queue = dispatch_queue_create("moapiproxyNEWWEORK", DISPATCH_QUEUE_CONCURRENT);
//        _sessionManager.completionQueue =  queue;
    }
    return _sessionManager;
}
- (NSURLSessionDataTask *)GET:(NSString *)URLString
                   parameters:(id)parameters
                     progress:(void (^)(NSProgress * _Nonnull))downloadProgress
                      success:(void (^)(NSURLSessionDataTask * _Nonnull, id _Nullable))success
                      failure:(void (^)(NSURLSessionDataTask * _Nullable, NSError * _Nonnull))failure
{

    NSURLSessionDataTask *dataTask = [self dataTaskWithHTTPMethod:@"GET"
                                                        URLString:URLString
                                                       parameters:parameters
                                                   uploadProgress:nil
                                                 downloadProgress:downloadProgress
                                                          success:success
                                                          failure:failure];

    [dataTask resume];

    return dataTask;
}
2)AFHTTPSessionManager内部调用dataTaskWithHTTPMethod:方法(内部处理requestSerializer);
- (NSURLSessionDataTask *)dataTaskWithHTTPMethod:(NSString *)method
                                       URLString:(NSString *)URLString
                                      parameters:(id)parameters
                                  uploadProgress:(nullable void (^)(NSProgress *uploadProgress)) uploadProgress
                                downloadProgress:(nullable void (^)(NSProgress *downloadProgress)) downloadProgress
                                         success:(void (^)(NSURLSessionDataTask *, id))success
                                         failure:(void (^)(NSURLSessionDataTask *, NSError *))failure
{
    NSError *serializationError = nil;
    NSMutableURLRequest *request = [self.requestSerializer requestWithMethod:method URLString:[[NSURL URLWithString:URLString relativeToURL:self.baseURL] absoluteString] parameters:parameters error:&serializationError];
    if (serializationError) {
        if (failure) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wgnu"
            dispatch_async(self.completionQueue ?: dispatch_get_main_queue(), ^{
                failure(nil, serializationError);
            });
#pragma clang diagnostic pop
        }

        return nil;
    }

    __block NSURLSessionDataTask *dataTask = nil;
    dataTask = [self dataTaskWithRequest:request
                          uploadProgress:uploadProgress
                        downloadProgress:downloadProgress
                       completionHandler:^(NSURLResponse * __unused response, id responseObject, NSError *error) {
        if (error) {
            if (failure) {
                failure(dataTask, error);
            }
        } else {
            if (success) {
                success(dataTask, responseObject);
            }
        }
    }];

    return dataTask;
}

3)dataTaskWithHTTPMethod内部调用父类AFURLSessionManager的dataTaskWithRequest: uploadProgress: downloadProgress: completionHandler方法;
- (NSURLSessionDataTask *)dataTaskWithRequest:(NSURLRequest *)request
                               uploadProgress:(nullable void (^)(NSProgress *uploadProgress)) uploadProgressBlock
                             downloadProgress:(nullable void (^)(NSProgress *downloadProgress)) downloadProgressBlock
                            completionHandler:(nullable void (^)(NSURLResponse *response, id _Nullable responseObject,  NSError * _Nullable error))completionHandler {

    __block NSURLSessionDataTask *dataTask = nil;
    url_session_manager_create_task_safely(^{
        //使用NSURLSession生成一个Task
        dataTask = [self.session dataTaskWithRequest:request];
    });

    [self addDelegateForDataTask:dataTask uploadProgress:uploadProgressBlock downloadProgress:downloadProgressBlock completionHandler:completionHandler];

    return dataTask;
}
4)AFURLSessionManager中的dataTaskWithRequest方法内部设置全局session和创建task;
- (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 uploadProgress:nil downloadProgress:nil 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];
        }
    }];

    return self;
}
  url_session_manager_create_task_safely(^{
        //使用NSURLSession生成一个Task
        dataTask = [self.session dataTaskWithRequest:request];
    });
5)AFURLSessionManager中的dataTaskWithRequest方法内部给task设置delegate(AFURLSessionManagerTaskDelegate);
[self addDelegateForDataTask:dataTask uploadProgress:uploadProgressBlock downloadProgress:downloadProgressBlock completionHandler:completionHandler];
6)taskDelegate代理的初始化: 绑定task / 存储task下载的数据 / 下载或上传进度 / 进度与task同步(KVO)
- (void)addDelegateForDataTask:(NSURLSessionDataTask *)dataTask
                uploadProgress:(nullable void (^)(NSProgress *uploadProgress)) uploadProgressBlock
              downloadProgress:(nullable void (^)(NSProgress *downloadProgress)) downloadProgressBlock
             completionHandler:(void (^)(NSURLResponse *response, id responseObject, NSError *error))completionHandler
{
    AFURLSessionManagerTaskDelegate *delegate = [[AFURLSessionManagerTaskDelegate alloc] init];
    delegate.manager = self;
    delegate.completionHandler = completionHandler;

    dataTask.taskDescription = self.taskDescriptionForSessionTasks;
    [self setDelegate:delegate forTask:dataTask];

    delegate.uploadProgressBlock = uploadProgressBlock;
    delegate.downloadProgressBlock = downloadProgressBlock;
}
- (void)setDelegate:(AFURLSessionManagerTaskDelegate *)delegate
            forTask:(NSURLSessionTask *)task
{
    NSParameterAssert(task);
    NSParameterAssert(delegate);

    [self.lock lock];
    self.mutableTaskDelegatesKeyedByTaskIdentifier[@(task.taskIdentifier)] = delegate;
    [delegate setupProgressForTask:task];
    [self addNotificationObserverForTask:task];
    [self.lock unlock];
}

7)task对应的AFURLSessionManagerTaskDelegate实现对进度处理、Block调用、Task完成返回数据的拼装的功能等;
#pragma mark -

#pragma mark - AFURLSessionManagerTaskDelegate
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context {
    if ([object isKindOfClass:[NSURLSessionTask class]]) {
        if ([keyPath isEqualToString:NSStringFromSelector(@selector(countOfBytesReceived))]) {
            self.downloadProgress.completedUnitCount = [change[@"new"] longLongValue];
        } else if ([keyPath isEqualToString:NSStringFromSelector(@selector(countOfBytesExpectedToReceive))]) {
            self.downloadProgress.totalUnitCount = [change[@"new"] longLongValue];
        } else if ([keyPath isEqualToString:NSStringFromSelector(@selector(countOfBytesSent))]) {
            self.uploadProgress.completedUnitCount = [change[@"new"] longLongValue];
        } else if ([keyPath isEqualToString:NSStringFromSelector(@selector(countOfBytesExpectedToSend))]) {
            self.uploadProgress.totalUnitCount = [change[@"new"] longLongValue];
        }
    }
    else if ([object isEqual:self.downloadProgress]) {
        if (self.downloadProgressBlock) {
            self.downloadProgressBlock(object);
        }
    }
    else if ([object isEqual:self.uploadProgress]) {
        if (self.uploadProgressBlock) {
            self.uploadProgressBlock(object);
        }
    }
}
#pragma mark - NSProgress Tracking

- (void)setupProgressForTask:(NSURLSessionTask *)task {
    __weak __typeof__(task) weakTask = task;

    self.uploadProgress.totalUnitCount = task.countOfBytesExpectedToSend;
    self.downloadProgress.totalUnitCount = task.countOfBytesExpectedToReceive;
    [self.uploadProgress setCancellable:YES];
    [self.uploadProgress setCancellationHandler:^{
        __typeof__(weakTask) strongTask = weakTask;
        [strongTask cancel];
    }];
    [self.uploadProgress setPausable:YES];
    [self.uploadProgress setPausingHandler:^{
        __typeof__(weakTask) strongTask = weakTask;
        [strongTask suspend];
    }];
    if ([self.uploadProgress respondsToSelector:@selector(setResumingHandler:)]) {
        [self.uploadProgress setResumingHandler:^{
            __typeof__(weakTask) strongTask = weakTask;
            [strongTask resume];
        }];
    }

    [self.downloadProgress setCancellable:YES];
    [self.downloadProgress setCancellationHandler:^{
        __typeof__(weakTask) strongTask = weakTask;
        [strongTask cancel];
    }];
    [self.downloadProgress setPausable:YES];
    [self.downloadProgress setPausingHandler:^{
        __typeof__(weakTask) strongTask = weakTask;
        [strongTask suspend];
    }];

    if ([self.downloadProgress respondsToSelector:@selector(setResumingHandler:)]) {
        [self.downloadProgress setResumingHandler:^{
            __typeof__(weakTask) strongTask = weakTask;
            [strongTask resume];
        }];
    }

    [task addObserver:self
           forKeyPath:NSStringFromSelector(@selector(countOfBytesReceived))
              options:NSKeyValueObservingOptionNew
              context:NULL];
    [task addObserver:self
           forKeyPath:NSStringFromSelector(@selector(countOfBytesExpectedToReceive))
              options:NSKeyValueObservingOptionNew
              context:NULL];

    [task addObserver:self
           forKeyPath:NSStringFromSelector(@selector(countOfBytesSent))
              options:NSKeyValueObservingOptionNew
              context:NULL];
    [task addObserver:self
           forKeyPath:NSStringFromSelector(@selector(countOfBytesExpectedToSend))
              options:NSKeyValueObservingOptionNew
              context:NULL];

    [self.downloadProgress addObserver:self
                            forKeyPath:NSStringFromSelector(@selector(fractionCompleted))
                               options:NSKeyValueObservingOptionNew
                               context:NULL];
    [self.uploadProgress addObserver:self
                          forKeyPath:NSStringFromSelector(@selector(fractionCompleted))
                             options:NSKeyValueObservingOptionNew
                             context:NULL];
}
#pragma mark - NSURLSessionTaskDelegate

- (void)URLSession:(__unused NSURLSession *)session
              task:(NSURLSessionTask *)task
didCompleteWithError:(NSError *)error
{
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wgnu"
    __strong AFURLSessionManager *manager = self.manager;

    __block id responseObject = nil;

    __block NSMutableDictionary *userInfo = [NSMutableDictionary dictionary];
    userInfo[AFNetworkingTaskDidCompleteResponseSerializerKey] = manager.responseSerializer;

    //Performance Improvement from #2672
    NSData *data = nil;
    if (self.mutableData) {
        data = [self.mutableData copy];
        //We no longer need the reference, so nil it out to gain back some memory.
        self.mutableData = nil;
    }

    if (self.downloadFileURL) {
        userInfo[AFNetworkingTaskDidCompleteAssetPathKey] = self.downloadFileURL;
    } else if (data) {
        userInfo[AFNetworkingTaskDidCompleteResponseDataKey] = data;
    }

    if (error) {
        userInfo[AFNetworkingTaskDidCompleteErrorKey] = error;

        dispatch_group_async(manager.completionGroup ?: url_session_manager_completion_group(), manager.completionQueue ?: dispatch_get_main_queue(), ^{
            if (self.completionHandler) {
                self.completionHandler(task.response, responseObject, error);
            }

            dispatch_async(dispatch_get_main_queue(), ^{
                [[NSNotificationCenter defaultCenter] postNotificationName:AFNetworkingTaskDidCompleteNotification object:task userInfo:userInfo];
            });
        });
    } else {
        dispatch_async(url_session_manager_processing_queue(), ^{
            NSError *serializationError = nil;
            responseObject = [manager.responseSerializer responseObjectForResponse:task.response data:data error:&serializationError];

            if (self.downloadFileURL) {
                responseObject = self.downloadFileURL;
            }

            if (responseObject) {
                userInfo[AFNetworkingTaskDidCompleteSerializedResponseKey] = responseObject;
            }

            if (serializationError) {
                userInfo[AFNetworkingTaskDidCompleteErrorKey] = serializationError;
            }

            dispatch_group_async(manager.completionGroup ?: url_session_manager_completion_group(), manager.completionQueue ?: dispatch_get_main_queue(), ^{
                if (self.completionHandler) {
                    self.completionHandler(task.response, responseObject, serializationError);
                }

                dispatch_async(dispatch_get_main_queue(), ^{
                    [[NSNotificationCenter defaultCenter] postNotificationName:AFNetworkingTaskDidCompleteNotification object:task userInfo:userInfo];
                });
            });
        });
    }
#pragma clang diagnostic pop
}
#pragma mark - NSURLSessionDataTaskDelegate

- (void)URLSession:(__unused NSURLSession *)session
          dataTask:(__unused NSURLSessionDataTask *)dataTask
    didReceiveData:(NSData *)data
{
    [self.mutableData appendData:data];
}

8)setDelegate: forTask: 加锁设置通过一个字典处理Task与之代理方法关联; 添加对Task开始、重启、挂起状态的通知的接收.
- (void)setDelegate:(AFURLSessionManagerTaskDelegate *)delegate
            forTask:(NSURLSessionTask *)task
{
    NSParameterAssert(task);
    NSParameterAssert(delegate);

    [self.lock lock];
    self.mutableTaskDelegatesKeyedByTaskIdentifier[@(task.taskIdentifier)] = delegate;
    [delegate setupProgressForTask:task];
    [self addNotificationObserverForTask:task];
    [self.lock unlock];
}
 [delegate setupProgressForTask:task];
9)[downloadTask resume]后执行开始, 走代理回调方法(内部其实是NSURLSession的各种代理的实现);
/** 客户端已收到服务器返回的部分数据
 * @param data 自上次调用以来收到的数据
 * 该方法可能被多次调用,并且每次调用只提供自上次调用以来收到的数据;因此 NSData 通常是由许多不同的data拼凑在一起的,所以尽量使用 [NSData enumerateByteRangesUsingBlock:] 方法迭代数据,而非 [NSData getBytes]
 */
- (void)URLSession:(NSURLSession *)session
          dataTask:(NSURLSessionDataTask *)dataTask
    didReceiveData:(NSData *)data
{

    AFURLSessionManagerTaskDelegate *delegate = [self delegateForTask:dataTask];
    [delegate URLSession:session dataTask:dataTask didReceiveData:data];

    if (self.dataTaskDidReceiveData) {
        self.dataTaskDidReceiveData(session, dataTask, data);
    }
}
10)task完成后走URLSession: task: didCompleteWithError: 回调对返回的数据进行封装;
/** 已完成传输数据的任务,调用该方法
 * @param error 客户端错误,例如无法解析主机名或连接到主机;
 *              服务器错误不会在此处显示;
 *              为 nil 表示没有发生错误,此任务已成功完成
 */
- (void)URLSession:(NSURLSession *)session
              task:(NSURLSessionTask *)task
didCompleteWithError:(NSError *)error
{
    AFURLSessionManagerTaskDelegate *delegate = [self delegateForTask:task];

    // delegate may be nil when completing a task in the background
    if (delegate) {
        [delegate URLSession:session task:task didCompleteWithError:error];

        [self removeDelegateForTask:task];
    }

    if (self.taskDidComplete) {
        self.taskDidComplete(session, task, error);
    }
}
11)同时移除对应的task; removeDelegateForTask: 加锁移除8)中的字典和通知;

- (void)removeDelegateForTask:(NSURLSessionTask *)task {
    NSParameterAssert(task);

    AFURLSessionManagerTaskDelegate *delegate = [self delegateForTask:task];
    [self.lock lock];
    [delegate cleanUpProgressForTask:task];
    [self removeNotificationObserverForTask:task];
    [self.mutableTaskDelegatesKeyedByTaskIdentifier removeObjectForKey:@(task.taskIdentifier)];
    [self.lock unlock];
}

AFN请求过程梳理
首先我们是初始化了AFHTTPSessionManager类(往往创建单例)初始化时候指定请求回调的代理是父类(AFURLSessionManager)。之后当我们发出一个请求后,先创建一个AFURLSessionManagerTaskDelegate(与NSURLsessionTask是一一对应关系,返回数据通过AFURLSessionManagerTaskDelegate遵循协议方法返回对应task数据给外界)对象来保存请求结果回调。并把该对象放到一个全局字典中来保存(以task.taskIdentifier为key),再启动请求。当AFURLSessionManager类收到了请求结果后根据task.taskIdentifier( AFURLSessionManagerTaskDelegate *delegate = [self delegateForTask:task])从全局字典中取出当前请求的AFURLSessionManagerTaskDelegate对象。然后调用AFURLSessionManagerTaskDelegate的对象方法处理请求,完成回调。之后再从全局字典中移除该AFURLSessionManagerTaskDelegate对象。

AFURLSessionManager方法
- (AFURLSessionManagerTaskDelegate *)delegateForTask:(NSURLSessionTask *)task {
    NSParameterAssert(task);

    AFURLSessionManagerTaskDelegate *delegate = nil;
    [self.lock lock];
    delegate = self.mutableTaskDelegatesKeyedByTaskIdentifier[@(task.taskIdentifier)];
    [self.lock unlock];

    return delegate;
}

AFN是怎样来解决循环引用的

首先我们用AFN时候往往是用单例,因此调用类不会直接持有该AFHTTPSessionManager对象。

该AFHTTPSessionManager对象持有block,该AFHTTPSessionManager对象持有全局字典,该全局字典持有AFURLSessionManagerTaskDelegate对象,该AFURLSessionManagerTaskDelegate对象持有block,这是一个循环引用。

当AFURLSessionManagerTaskDelegate对象block进行回调后,从全局字典中移除该对象。从而打破引用环。

1 生成NSMutableURLRequest 对象
AFHTTPSessionManager 实例对象种自动创建一个 AFHTTPRequestSerializer类型的requestSerializer 对像, 如下图

AFHTTPSessionManager
- (instancetype)initWithBaseURL:(NSURL *)url
           sessionConfiguration:(NSURLSessionConfiguration *)configuration
{
    self = [super initWithSessionConfiguration:configuration];
    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];

    return self;
}
- (NSURLSessionDataTask *)dataTaskWithHTTPMethod:(NSString *)method
                                       URLString:(NSString *)URLString
                                      parameters:(id)parameters
                                  uploadProgress:(nullable void (^)(NSProgress *uploadProgress)) uploadProgress
                                downloadProgress:(nullable void (^)(NSProgress *downloadProgress)) downloadProgress
                                         success:(void (^)(NSURLSessionDataTask *, id))success
                                         failure:(void (^)(NSURLSessionDataTask *, NSError *))failure
{
    NSError *serializationError = nil;
    NSMutableURLRequest *request = [self.requestSerializer requestWithMethod:method URLString:[[NSURL URLWithString:URLString relativeToURL:self.baseURL] absoluteString] parameters:parameters error:&serializationError];
    if (serializationError) {
        if (failure) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wgnu"
            dispatch_async(self.completionQueue ?: dispatch_get_main_queue(), ^{
                failure(nil, serializationError);
            });
#pragma clang diagnostic pop
        }

        return nil;
    }

    __block NSURLSessionDataTask *dataTask = nil;
    dataTask = [self dataTaskWithRequest:request
                          uploadProgress:uploadProgress
                        downloadProgress:downloadProgress
                       completionHandler:^(NSURLResponse * __unused response, id responseObject, NSError *error) {
        if (error) {
            if (failure) {
                failure(dataTask, error);
            }
        } else {
            if (success) {
                success(dataTask, responseObject);
            }
        }
    }];

    return dataTask;
}

2 用requestSerializer 生成一个NSMutableRequest对象

AFHTTPRequestSerializer
#pragma mark -

- (NSMutableURLRequest *)requestWithMethod:(NSString *)method
                                 URLString:(NSString *)URLString
                                parameters:(id)parameters
                                     error:(NSError *__autoreleasing *)error
{
    NSParameterAssert(method);
    NSParameterAssert(URLString);
    
    NSURL *url = [NSURL URLWithString:URLString];
    
    NSParameterAssert(url);
    
    NSMutableURLRequest *mutableRequest = [[NSMutableURLRequest alloc] initWithURL:url];
    mutableRequest.HTTPMethod = method;
    
    for (NSString *keyPath in AFHTTPRequestSerializerObservedKeyPaths()) {
        if ([self.mutableObservedChangedKeyPaths containsObject:keyPath]) {
            [mutableRequest setValue:[self valueForKeyPath:keyPath] forKey:keyPath];
        }
    }
    
    mutableRequest = [[self requestBySerializingRequest:mutableRequest withParameters:parameters error:error] mutableCopy];
    
    return mutableRequest;
}
生成MutableRequest的过程中,会将requestSerializer 的 请求头字典赋值给MutableRequest 的请求头self.HTTPRequestHeaders
#pragma mark - AFURLRequestSerialization

- (NSURLRequest *)requestBySerializingRequest:(NSURLRequest *)request
                               withParameters:(id)parameters
                                        error:(NSError *__autoreleasing *)error
{
    NSParameterAssert(request);
    
    NSMutableURLRequest *mutableRequest = [request mutableCopy];
    
    [self.HTTPRequestHeaders enumerateKeysAndObjectsUsingBlock:^(id field, id value, BOOL * __unused stop) {
        if (![request valueForHTTPHeaderField:field]) {
            [mutableRequest setValue:value forHTTPHeaderField:field];
        }
    }];
    
    NSString *query = nil;
    if (parameters) {
        if (self.queryStringSerialization) {
            NSError *serializationError;
            query = self.queryStringSerialization(request, parameters, &serializationError);
            
            if (serializationError) {
                if (error) {
                    *error = serializationError;
                }
                
                return nil;
            }
        } else {
            switch (self.queryStringSerializationStyle) {
                case AFHTTPRequestQueryStringDefaultStyle:
                    query = AFQueryStringFromParameters(parameters);
                    break;
            }
        }
    }
    
    if ([self.HTTPMethodsEncodingParametersInURI containsObject:[[request HTTPMethod] uppercaseString]]) {
        if (query) {
            mutableRequest.URL = [NSURL URLWithString:[[mutableRequest.URL absoluteString] stringByAppendingFormat:mutableRequest.URL.query ? @"&%@" : @"?%@", query]];
        }
    } else {
        // #2864: an empty string is a valid x-www-form-urlencoded payload
        if (!query) {
            query = @"";
        }
        if (![mutableRequest valueForHTTPHeaderField:@"Content-Type"]) {
            [mutableRequest setValue:@"application/x-www-form-urlencoded" forHTTPHeaderField:@"Content-Type"];
        }
        [mutableRequest setHTTPBody:[query dataUsingEncoding:self.stringEncoding]];
    }
    
    return mutableRequest;
}

并且创建请求Request之后,如果从外面传来了请求头参数,会再次给
request的请求头赋值的


image.png

3 AFHTTPSessionManager 使用MutableRequest 生成一个 请求任务 NSURLSessionDataTask


image.png
- (NSURLSessionDataTask *)dataTaskWithRequest:(NSURLRequest *)request
                               uploadProgress:(nullable void (^)(NSProgress *uploadProgress)) uploadProgressBlock
                             downloadProgress:(nullable void (^)(NSProgress *downloadProgress)) downloadProgressBlock
                            completionHandler:(nullable void (^)(NSURLResponse *response, id _Nullable responseObject,  NSError * _Nullable error))completionHandler {

    __block NSURLSessionDataTask *dataTask = nil;
    url_session_manager_create_task_safely(^{
        //使用NSURLSession生成一个Task
        dataTask = [self.session dataTaskWithRequest:request];
    });

    [self addDelegateForDataTask:dataTask uploadProgress:uploadProgressBlock downloadProgress:downloadProgressBlock completionHandler:completionHandler];

    return dataTask;
}

AFHTTPSessionManager 继承于AFURLSessionManager


image.png

AFURLSessionManager 默认持有一个NSURLSession 对象


image.png

NSURLSession

Session翻译为中文意思是会话,我们知道,在七层网络协议中有物理层->数据链路层->网络层->传输层->会话层->表示层->应用层,那我们可以将NSURLSession类理解为会话层,用于管理网络接口的创建、维护、删除等等工作,我们要做的工作也只是会话层之后的层即可,底层的工作NSURLSession已经帮我们封装好了。
HTTP/2 通过引入多播,解决 HTTP1.1 中要求响应有序返回导致的 HOL 问题。

NSURLSession 使用 HTTP2.0,与NSURLConnection一样,它实际代表的也是一组相互关联的类,NSURLRequest、NSURLResponse、NSURLSessionTask、NSURLProtocol、NSURLCache、NSHTTPCookieStorage、NSURLCredentialStorage 、NSURLSessionConfiguration 和同名类 NSURLSession。在 NSURLSession 架构中,可以针对每个 session 配置缓存、协议、cookie和证书策略,多个 session 可以配置不同的选项,提升移动应用的性能。


image.png

NSURLsessionTask 是一个抽象类,其下有 3 个实体子类可以直接使用:NSURLSessionDataTask、NSURLSessionUploadTask、NSURLSessionDownloadTask。这 3 个子类封装了现代程序三个最基本的网络任务:获取数据,比如 JSON 或者 XML,上传文件和下载文件


image.png

image.png

不同于直接使用 alloc-init 初始化方法,task 是由一个 NSURLSession 创建的。每个 task 的构造方法都对应有或者没有 completionHandler 这个 block 的两个版本。
  1. 代理

针对NSURLsessionTask的代理,根代理为NSURLSessionDelegate,其它的代理直接或者间接继承自改代理,如:NSURLSessionTaskDelegate、NSURLSessionDataDelegate、NSURLSessionDownloadDelegate。其中根代理NSURLSessionDelegate主要处理鉴权、后台下载任务完成通知等等,NSURLSessionTaskDelegate主要处理收到鉴权响应、任务结束(无论是正常还是异常),NSURLSessionDataDelegate处理数据的接收、dataTask转downloadTask、缓存等,NSURLSessionDownloadDelegate主要处理数据下载、数据进度通知等。


image.png

NSURLSessionDataTask 发送 POST 请求

//确定请求路径
   NSURL *url = [NSURL URLWithString:@"http://120.25.226.186:32812/login"];
   //创建可变请求对象
   NSMutableURLRequest *requestM = [NSMutableURLRequest requestWithURL:url];
   //修改请求方法
   requestM.HTTPMethod = @"POST";
   //设置请求体
   requestM.HTTPBody = [@"username=520&pwd=520&type=JSON" dataUsingEncoding:NSUTF8StringEncoding];
   //创建会话对象
   NSURLSession *session = [NSURLSession sharedSession];
   //创建请求 Task
   NSURLSessionDataTask *dataTask = [session dataTaskWithRequest:requestM completionHandler:
            ^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
       //解析返回的数据
       NSLog(@"%@", [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]);
   }];
   //发送请求
   [dataTask resume];

NSURLSessionDataTask 设置代理发送请求

 //确定请求路径
     NSURL *url = [NSURL URLWithString:@"http://120.25.226.186:32812/login"];
     //创建可变请求对象
     NSMutableURLRequest *requestM = [NSMutableURLRequest requestWithURL:url];
     //设置请求方法
     requestM.HTTPMethod = @"POST";
     //设置请求体
     requestM.HTTPBody = [@"username=520&pwd=520&type=JSON" dataUsingEncoding:NSUTF8StringEncoding];
     //创建会话对象,设置代理
 /**
  第一个参数:配置信息
  第二个参数:设置代理
  第三个参数:队列,如果该参数传递nil 那么默认在子线程中执行
  */
     NSURLSession *session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration]
                              delegate:self delegateQueue:nil];
     //创建请求 Task
     NSURLSessionDataTask *dataTask = [session dataTaskWithRequest:requestM];
     //发送请求
     [dataTask resume];

代理方法:

-(void)URLSession:(NSURLSession *)session dataTask:(nonnull NSURLSessionDataTask *)dataTask
didReceiveResponse:(nonnull NSURLResponse *)response
completionHandler:(nonnull void (^)(NSURLSessionResponseDisposition))completionHandler {
     //子线程中执行
     NSLog(@"接收到服务器响应的时候调用 -- %@", [NSThread currentThread]);
     self.dataM = [NSMutableData data];
     //默认情况下不接收数据
     //必须告诉系统是否接收服务器返回的数据
     completionHandler(NSURLSessionResponseAllow);
}
-(void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data {
     NSLog(@"接受到服务器返回数据的时候调用,可能被调用多次");
     //拼接服务器返回的数据
     [self.dataM appendData:data];
}
-(void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error {
     NSLog(@"请求完成或者是失败的时候调用");
     //解析服务器返回数据
     NSLog(@"%@", [[NSString alloc] initWithData:self.dataM encoding:NSUTF8StringEncoding]);
}

设置代理之后的强引用问题

NSURLSession 对象在使用的时候,如果设置了代理,那么 session 会对代理对象保持一个强引用,在合适的时候应该主动进行释放(@property (nullable, readonly, retain) id <NSURLSessionDelegate> delegate;)
可以在控制器调用 viewDidDisappear 方法的时候来进行处理,通过调用 invalidateAndCancel 方法或者是 finishTasksAndInvalidate 方法来释放对代理对象的强引用。
其中,invalidateAndCancel是直接取消请求然后释放代理对象,而finishTasksAndInvalidate是等请求完成之后释放代理对象。
总结:

1,创建会话NSURLSession
*第一种方式是使用静态的sharedSession方法,该类使用共享的会话,该会话使用全局的Cache,Cookie和证书。
*/
+ (NSURLSession *)sharedSession;
*第二种方式是通过sessionWithConfiguration:方法创建对象,也就是创
建对应配置的会话,与NSURLSessionConfiguration合作使用。
*/
+ (NSURLSession *)sessionWithConfiguration:(NSURLSessionConfiguration *)configuration;  

NSURLSessionConfiguration
用于配置会话的属性,可以通过该类配置会话的工作模式
2,创建请求任务NSURLSessionTask
NSURLSessionTask是一个抽象子类,它的子类:NSURLSessionDataTask,NSURLSessionUploadTask和NSURLSessionDownloadTask

NSURLRequest

NSURLRequest及其分类和NSMutableURLRequest及其分类提供了相应的API供开发者配置HTTP请求;类似请求的缓存策略NSURLRequestCachePolicy、请求超时时间timeoutInterval、设置请求的Head filed、设置HTTPMethod(GET/POST)、使用POST请求时设置HTTPBody等


最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念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

推荐阅读更多精彩内容