一、多线程间通信
本地的进程间通信(IPC)有很多种方式,但可以总结为下面 4 类:
消息传递(管道、FIFO、消息队列) 同步(互斥量、条件变量、读写锁、文件和写记录锁、信号量) 共享内存(匿名的和具名的)远程过程调用(Solaris 门和 Sun RPC)
在面试中,经常被面试官问到线程间是如何通讯的,很多童鞋会回答在子线程获取数据,切换回主线程刷新UI
,那么请你回家等消息。苹果的官方文档给我们列出了线程间通讯的几种方式
上图的表格是按照技术复杂度由低到高顺序排列的,其中后两种只能在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中。
- 用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、本地缓存。首页的数据离线化,优先展示本地缓存数据,等待网络数据返回之后更新缓存并展示。
三、项目架构
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的请求头赋值的
3 AFHTTPSessionManager 使用MutableRequest 生成一个 请求任务 NSURLSessionDataTask
- (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
AFURLSessionManager 默认持有一个NSURLSession 对象
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 可以配置不同的选项,提升移动应用的性能。
NSURLsessionTask 是一个抽象类,其下有 3 个实体子类可以直接使用:NSURLSessionDataTask、NSURLSessionUploadTask、NSURLSessionDownloadTask。这 3 个子类封装了现代程序三个最基本的网络任务:获取数据,比如 JSON 或者 XML,上传文件和下载文件
不同于直接使用 alloc-init 初始化方法,task 是由一个 NSURLSession 创建的。每个 task 的构造方法都对应有或者没有 completionHandler 这个 block 的两个版本。
- 代理
针对NSURLsessionTask的代理,根代理为NSURLSessionDelegate,其它的代理直接或者间接继承自改代理,如:NSURLSessionTaskDelegate、NSURLSessionDataDelegate、NSURLSessionDownloadDelegate。其中根代理NSURLSessionDelegate主要处理鉴权、后台下载任务完成通知等等,NSURLSessionTaskDelegate主要处理收到鉴权响应、任务结束(无论是正常还是异常),NSURLSessionDataDelegate处理数据的接收、dataTask转downloadTask、缓存等,NSURLSessionDownloadDelegate主要处理数据下载、数据进度通知等。
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等