AFHTTPRequestOperationManager
// 创建
AFHTTPRequestOperationManager *mgr = [AFHTTPRequestOperationManager manager];
// GET
- (AFHTTPRequestOperation *)GET:(NSString *)URLString
parameters:(id)parameters
success:(void (^)(AFHTTPRequestOperation *operation, id responseObject))success
failure:(void (^)(AFHTTPRequestOperation *operation, NSError *error))failure
// POST
- (AFHTTPRequestOperation *)POST:(NSString *)URLString
parameters:(id)parameters
success:(void (^)(AFHTTPRequestOperation *operation, id responseObject))success
failure:(void (^)(AFHTTPRequestOperation *operation, NSError *error))failure
// 上传
- (AFHTTPRequestOperation *)POST:(NSString *)URLString
parameters:(id)parameters
constructingBodyWithBlock:(void (^)(id <AFMultipartFormData> formData))block
success:(void (^)(AFHTTPRequestOperation *operation, id responseObject))success
failure:(void (^)(AFHTTPRequestOperation *operation, NSError *error))failure
// 监控
AFNetworkReachabilityManager *manager = [AFNetworkReachabilityManager sharedManager];
[manager startMonitoring];//监控网络连接状态必须先调startMonitoring方法
[manager setReachabilityStatusChangeBlock:^(AFNetworkReachabilityStatus status) {
NSLog(@"%d", status);
}];
网络传输协议UDP、TCP、Http、Socket、XMPP
1、应用层http?iOS1发信息给服务器,iOS2如果想要知道iOS1发的信息是什么,只有iOS2客户端向服务器发送请求,服务器才会将服务器的数据返回给客户端。因此iOS2需要以轮询的方式不断向服务器发送请求查看是否有其它客服端给自己发送信息。就像收快递的时候,你只知道快递要来,但是并不会知道具体什么时候快递会到。所以一会去看一下,一会儿又去看一下
2、应用层Socket?Socket协议的服务器会主动将消息发送给客户端。当iOS1将消息发送给服务器后,服务器会马上将消息转发到iOS2,服务器可以和客户端主动交互,相对于http协议而言,转发速度比较快。XMPP是基于Socket的封装,环信是XMPP的封装。
4、传输层TCP?控制传输协议,三次握手,四次挥手。类似于打电话,先接通对方后再一句话一句话地口述内容传输给对方。可以有效地保证对方能够及时接收到传递的信息
5、传输层UDP?用户数据报协议。协议通信的前提是知道服务器的ip号和端口号。UDP协议传输跟发邮件类似。UDP只负责发送,而且每次发送都需要对方的IP号和端口号。所以有时候发送邮件对方接受不到,同样别人给我发邮件,但是我并没有接受到
6、TCP与UDP区别?发文档两种方式,后者是邮件,并不能保证你对方一定接受到。前者则是先打电话,接通后建立连接成功,不是通过邮件,而是直接通过口述直接把内容传输给对方,三次握手(打电话,接通,建立连接)
OAuth2.0原理
用户身份验证和授权方式。也就是这么说,我们想要提取到新浪API服务器里的信息,但是不能使用用户的登录名和密码去访问,因为从根本上来说都不会让第三方应用获取到用户名和登陆密码。那又通过什么方式来请求登陆用户的信息呢?自然通过在用户使用新浪的授权服务器进行用户名和密码的第三方应用授权登陆成功后。当然前提是必须这个第三方应用已经在新浪的开发平台上已经创建了引用且获取到了AppKey和相关的秘钥已经审核通过。那么这时候新浪的授权服务器会返回给第三方引用一个Access Token,也就是一段字符串,第三方应用可以将授权服务器返回回来的字符串结合在请求地址中,这时候就能够获取到用户的新浪API信息了。为了防止抓包的人去获取到这个字符串,新浪对返回回来的字符串还设置了生命周期。新浪避免用户信息泄露到其他客户端的服务器上,新浪里面有一个专门对用户做授权的服务器,目的是记录用户是否授权第三方应用登陆微博。授权服务器在接收到验证授权请求时,会按照OAuth2.0协议对本请求的请求头部、请求参数进行检验,若请求不合法或验证未通过,授权服务器还会返回相应的错误信息。
GET请求VsPOST请求
方便和安全——除了往服务器发送请求头以外,其它任何请求体都不会发送。可以很好地保护服务器的封装性。GET对于服务器安全,POST对于用户安全。而且POST向服务器请求数据或者发送数据时使用起来比较灵活。
GET:
·当访问的时候,参数暴露在外面,任何人都可以看见参数信息,从这种角度来讲,相对POST来说不安全
·通过GET方式拼接参数向服务器发送请求时,浏览器里面网址的长度有上限
POST:
·使用相对来说没有GET便捷
·POST请求过程中,除了往服务器发送请求头信息以外,还需要给服务器发送请求体,从这个角度来讲,破坏了服务器的封装性,POST相对GET是不安全的
YTKNetwork基础用法网络配置类YTKNetworkConfig的功能使用方法
1、配置所有网络请求的主机地址和CDN地址2、与url过滤类一起来统一对所有的url进行更改 3、本质是一个单例类,所以直接实例化对象,设置对象的baseUrl和cdnUrl属性
网络请求类YTKRequest
这相当于我们的BaseRequest,以后每一个网络请求对象都将继承于它,一个网络请求一个对象。使用就是通过覆盖父类的方法来建立特定的网络请求对象,主要就是覆盖requestUrl、requestMethod、requestArgument等方法,需要特别注意的是因为详细网址已经设置在YTKNetworkConfig类的baseUrl属性里,因此这里的requestUrl就不用再写服务器的主机地址了。请求数据之前先判断已有的JSON数据是否无效的属性:jsonValidator检查网络请求到的JSON与已有的JSON是否一致:statusCodeValidator属性如果我们要POST文件就需要用到属性:constructingBodyBlock
如何打破Block回调中的循环引用在block里直接用self
就是在网络请求结束之后,调用将Block置空的方法来防止循环引用,这样就可以少些很多的typeof self weafSelf;使用方法?pod ‘YTKNetwork’。将每一个网络请求都封装成对象,每一个请求都需要继承YTKRequest类,通过覆盖父类的一些方法来构造制定的网络请求。就是在网络请求结束之后,调用将Block置空的方法来防止循环引用,这样就可以少些很多的__weak typeof (self) weafSelf = self;
网络封装的适用场景
1、需要缓存2、网络请求之间存在依赖,即B网络请求是否开始与A网络请求的结果直接相关3、依赖特定的版本号来判断缓存的内容已过期4、不仅可以实现Block回调,更是可以通过设置delegate属性等于self,同时调用start方法进行委托回调。按时间缓存网络请求内容?意思是说按一定的时间规律来进行缓存还是说超过了一定时间后已经缓存的内容失效,其实两者并不矛盾,正是因为缓存的内容在一段时间之后失效,所以需要重新请求数据,进行缓存。什么叫按版本号缓存网络请求地址?网络请求中统一设置CDN地址,什么是CDN地址?全称content delivery Network,内容分发网络。通过在网络各处放置节点服务器实现在现有的服务器基础上补充一层智能虚拟网络,智能引导用户到最近的服务器上,是内容传输的更快、更稳定。相互依赖的网络请求的发送?就是一个网络请求的启动开关取决于另一个网络请求的请求结果。
验证JSON的合法性
因为从服务器获得的JSON数据不一定总是可信赖的,如果数据是从有故障的服务器返回了一个错误的格式,就非常容易造成客户端崩溃。就加入说正常情况下,我们登录个人中心,返回的昵称总是字符串,用户年龄都是数字,可是假如返回的文件格式出现问题,那么客户端必崩无疑。所以验证的方法就是重写父类基础网络请求YTKRequest类的- (id)jsonValidator方法。返回的是一个字典,这个字典里面的键就是字符串形参,键值对的值就是【NSString class】。
在YTKNetworkConfig的对象里同时设置了baseUrl和cdnUrl属性?
1、默认使用baseUrl2、如果想要使用cdnUrl,就必须重写基础网络请求YTKRequest类的-(BOOL)useCDN方法并返回YES
实现断点续传下载?
重写基础网络请求YTKRequest类的resumableDownloadPath方法,直接返回一个文件路径字符串。这里面就涉及到库路径Lib Path。
缓存路径cachePath如何实现缓存数据的存取?
首先就是通过重写基础网络请求YTKRequest类的- (NSInteger的)cacheTimeInSeconds方法设定高速缓存数据的有效秒数,如果缓存的数据没有到期,在缓存期内调用start方法或是startWithCompletionBlockWithSuccess方法时实际上并不会发送真正的请求,而是直接返回当初高速缓存的数据,只有在缓存过期时,才会真正地发送网络请求。
高级用法
批量编辑网络请求路径
首先分析url参数过滤的类的实现原理,其实本质就是通过一个全新的自定义类来整合基础的url和新增的参数字典,然后介绍url参数过滤的类的使用方法。1、新建一个url参数过滤的类,这个类的.h方法里面有一个协议,协议里面主要实现了两个方法,第一个方法输入参数是一个字典,返回一个自定义类的对象;第二个方法就是输入参数一为原始url的字符串,参数二为基础网络请求类的对象,返回参数是一个全新的url字符串。2、.m方法里面首先包括一个名为参数arguments的全局变量字典。前面说过返回的参数为url参数过滤的类的对象本身,恰好这个url参数过滤的类的对象的初始化就需要传入argument字典。让我奇怪的是为什么是url参数过滤的类生成类时的self对象来调用alloc来开辟内存空间。而且又返回一个url参数过滤的类的对象,好奇怪。3、在输入原始url和增加的request两个参数之后返回一个全新的url字符串。这里面务必注意这里是通过网络请求私有类来调用一个方法拼接原始的url和新增的参数字典,最后返回一个字符串,反而当初传进来的request网络请求类对象并没有使用。
把客户端的版本号添加到所有的网络请求url之中
其实就是在设置根控制器为可见的方法里使用两个类来实现网络参数的统一添加,所有的url都变成全新的url,当务之急还是先通过url参数过滤的类调用类方法传入参数实例化一个url参数过滤的类的对象。然后再通过网络配置这个单例类对象调用添加url参数过滤的类的对象的方法,其目的就是将添加后的参数推广到所有网络请求url。
同一时间发送批量的网络请求并统一处理同时请求成功时的回调
1、实例化4个Api对象2、把这4个对象以数组的元素的形式传入初始化YTKBatchRequest对象的数组形参里3、YTKBatchRequest对象调用startWithCompletionBlockWithSuccess方法4、在4个Api对象全部请求成功的Block回调里实例化一个临时数组并赋值为YTKBatchRequest对象的requestArray属性5、再各自使用4个Api类实例四个Api对象,一一对应requestArray的每一个元素,但然需要强转6、现在就跟一个Api请求成功获得的request没有什么区别了。
加载缓存数据的高级用法
由于加载慢,所以先调用方法[api cacheJson]显示上次缓存的内容,加载成功后,再用最新的内容替换上次的内容,如是断网状态也先显示上次缓存中的内容。当初不是还需要Api先来判断缓存的数据是否在有效时间内,然后再决定是否是真的请求数据。而现在就是直接调用- (id)cacheJson方法获得上次缓存的内容。前提必须重写父类设定缓存有效时间的方法- (NSInteger)cacheTimeInSeconds返回一个大于0的值,这样才能开启基础网络请求类的缓存功能。因为默认情况下缓存数据的有效时间为0秒。
上传文件获得一个访问文件的Api
关键就是重写基础网络请求类的- (AFConstructingBlock)constructingBodyBlock方法,在这个方法里直return一个Block代码块,返回参数为void直接忽略没写,输入参数为一个满足协议的formData值,数据类型自然不确定是id啦。这就相当于直接return一个NSString *tempString = @“”,只不过这里的tempString是一个^(idformData)输入参数为fromData的Block,而且这个地方直接给Block赋值了,真的是既声明又赋值。赋值的代码里面首先就是压缩图片成NSData数据。最后通过formData调用appendPartWithFileData方法正式上传图片。尤其注意这里需要传入三个参数,其一是文件的二进制数据,其二是文件名字,其三是文件夹的名字,其四很关键,需要传入文件类型,图片通常都是image/jpeg。另外文件名字和文件夹的名字可以相同,而且通常来说就是image。
统一设置网络请求Api的请求头HeaderField
重写覆盖基础网络请求类的-(NSDictionnary*)requestHeaderFieldValueDictionary方法返回一个请求头的键值对的字典。务必注意,请求头字典的键和值都必须是NSString类型。
自定义网络请求类对象
重写最最基础网络请求YTKBaseRequest类的- (NSURLRequest *)buildCustomUrlRequest
方法,返回一个基础网络请求类的对象。尤其注意的是,只要返回的基础网络请求类的对象非nil不为空,那么会忽略其它一切自定义request的方法。
按时间缓存网络请求内容
意思是说按一定的时间规律来进行缓存还是说超过了一定时间后已经缓存的内容失效,其实两者并不矛盾,正是因为缓存的内容在一段时间之后失效,所以需要重新请求数据,进行缓存。
按版本号缓存网络请求地址
网络请求中统一设置CDN地址,什么是CDN地址?
全称content delivery Network,内容分发网络。通过在网络各处放置节点服务器实现在现有的服务器基础上补充一层智能虚拟网络,智能引导用户到最近的服务器上,是内容传输的更快、更稳定。
相互依赖的网络请求的发送
就是一个网络请求的启动开关取决于另一个网络请求的请求结果。
一次性更改所有的网络请求路径
首先分析url参数过滤的类的实现原理,其实本质就是通过一个全新的自定义类来整合基础的url和新增的参数字典,然后介绍url参数过滤的类的使用方法。
1、新建一个url参数过滤的类,这个类的.h方法里面有一个协议,协议里面主要实现了两个方法,第一个方法输入参数是一个字典,返回一个自定义类的对象;第二个方法就是输入参数一为原始url的字符串,参数二为基础网络请求类的对象,返回参数是一个全新的url字符串。
2、.m方法里面首先包括一个名为参数arguments的全局变量字典。前面说过返回的参数为url参数过滤的类的对象本身,恰好这个url参数过滤的类的对象的初始化就需要传入argument字典。让我奇怪的是为什么是url参数过滤的类生成类时的self对象来调用alloc来开辟内存空间。而且又返回一个url参数过滤的类的对象,好奇怪。
3、在输入原始url和增加的request两个参数之后返回一个全新的url字符串。这里面务必注意这里是通过网络请求私有类来调用一个方法拼接原始的url和新增的参数字典,最后返回一个字符串,反而当初传进来的request网络请求类对象并没有使用。
get请求参数会暴露在外面,从这个角度来讲,get相对不安全,但是post请求将请求参数封装到请求体中上传到服务器,上传的数据可能会有问题,会对服务器进行攻击,从这个角度来讲,post相对服务器而言并不安全,而且get请求的请求参数长度受限,但post的请求参数由于事先封装,所以并不限制长度。
ASI网络请求
当然再进行对ASI的进一步封装使用之前,必须先要明白的就是将ASI导入到工程中所需要做的准备。CFNetwork.framework/SystemConfiguration.framework/MobileCoreServices.framework/CoreGraphics.framework/libz.dylib/libxml2.dylib。最后libxml2还需要设置连接选项:点击build setting搜索usr点击Header Seacher Path添加路径(/usr/include/libxml2)。而且可能会出现一个问题就是: 如果Xcode版本高于6.3,增添依赖框架时搜libz.dylib和libxml2.dylib会有问题,而且在点击build setting搜索usr也会有问题!当然还必须考虑到ASI是以手动管理为主,因此必须添加编译混编(-fno-objc-arc).
//通过POST下载数据
- (void)postWetherData {
// 1.接口
NSString *path = @"http://www.webxml.com.cn/WebServices/WeatherWebService.asmx/getWeatherbyCityName";
// 2.请求体-直接转码
NSString *httpBody = [@"theCityName=北京" URLEncodedString];
// 3.将接口转换成URL
NSURL *url = [NSURL URLWithString:path];
// 4.创建请求
ASIFormDataRequest *request = [ASIFormDataRequest requestWithURL:url];
// (1)设置请求方式
[request setRequestMethod:@"POST"];
// (2)设置来自上传流信息
[request addRequestHeader:@"Content-Type" value:@"application/x-www-form-urlencoded"];
[request addRequestHeader:@"Content-Length" value:[NSString stringWithFormat:@"%ld",(long)httpBody.length]];
// (3)设置请求体
[request appendPostData:[httpBody dataUsingEncoding:NSUTF8StringEncoding]];
// (4)设置tag值
request.tag = 2000;
[self setRequest:request andRequestType:Enum_PostWeatherData];
// 5.将线程添加到队列里面
[_netWorkQueue addOperation:request];
}
- (void)getWeatherData {
// 1.创建线程
NSString *path = [@"http://www.webxml.com.cn/WebServices/WeatherWebService.asmx/getWeatherbyCityName?theCityName=北京" URLEncodedString];
NSURL *url = [NSURL URLWithString:path];
ASIHTTPRequest *asiHttpRequest = [ASIHTTPRequest requestWithURL:url];
asiHttpRequest.tag = 1000;
[self setRequest:asiHttpRequest andRequestType:Enum_GetWeatherData];
// 2.添加线程到队列
[_netWorkQueue addOperation:asiHttpRequest];
}
ASI网络库封装:ASI作为AFN出来之前广为人知的网络请求库,封装ASI变得尤为重要。现在谈谈我对于ASI封装的理解,到底是以讴歌怎样的逻辑,首先我们必须知道一点就是ASI里面三个很重要的类。分别是:ASIHTTPRequest/ASIFormDataRequest/ASINetworkQueue这三个类。可是我们需要这三个类到底做些什么呢?现在来说,我知道的就是线程的问题,这么说吧!ASIHTTPRequest继承于NSOperation,ASINetworkQueue继承于NSOperationQueue,而ASIFormDataRequest继承于ASIHTTPRequest,主要用于网络的Post请求,当然也具备get请求的功能。现在来说,我们从线程和对列的角度来分析如何封装这个ASI库的出发点是什么呢?首先,我们希望通过ASINetworkQueue创建一个队列的单例用于管理添加到队列里的任务或者说是线程。当然话都说到这一步肯定就是表明ASINetworkQueue创建的单例队列是一个并行队列了。这个队列用于管理所有添加到这个队列里的任务。既然说到管理,那就必须谈到具体管理什么?当然是要管理队列里的每一个任务。问题就来了,为什么必须将ASI封装成以一个单例管理器。首先就是需要明白到底为什么要封装ASI的使用,原因其实在做项目的时候就已经体现出来价值了,就是在一个控制器中,如果视图里的一个控件有一个网络接口,也就是说,一个控制器需要多个网络接口的数据才能得到实现。如果对于同步请求来说还比较容易,只是显得代码比较冗杂而已,但是如果是异步请求还会有许多的委托协议里的方法,这样不仅会造成控制器里的代码超多,而且会增加很多很多的委托方法,设想一下,如果TableView需要ASI从网络请求数据,上面哪一个WebView也需要从不同于TableView的网络接口获取数据,而且两个都必须是异步操作,这样问题就很复杂了,因为每一个异步请求都需要三个委托的协议方法,而且协议里的方法居然还那么一致,这样就会有六个协议方法呀!而且还两两重复。必须紊乱呀!因此这对于ASI式的网络请求来说,必须通过封装来避免这样的情况,可是,为什么封装ASI就能避免这样的情况呢?我的初步想法就是,控制器里根本不应该下载数据和处理数据,需要的仅仅是将已经下载完成的且分析处理后存到数据模型里并用可变数组接收后的一个数组或字典传递给控制器,这就是封装的意义所在,你或许会问,如何才能将第三方库下载完成并处理好的数据传递到控制器里呢�?当然首先想到的就是协议代理啦?但是后来你会发现这会带来一个全新的问题?一个不知不觉就崩溃的问题,原因就是对象的委托指向了一个可能随时被销毁的控制器对象。
好吧,现在先说一说,为什么必须通过单例式管理器来对ASI的功能进行调用呢?首先来说吧!创建一个单例管理器。这一类继承于NSObject,首先就是导入ASIHTTPRequest.h这个头文件。然后在.h文件声明一个创建ASI单例管理器对象的类方法。前面已经说过,单例的声明方法通常格式为shared或default开头,然后加上管理器类名。现在就是在.m文件里实现单例对象的具体创建了。那么问题来了,首先不明白的就是为什么对一个第三方库进行使用前的第二次封装,又或者说为第三方库添加一个管理器。为什么说到这儿,又产生一个疑问?为什么给第三方库添加了管理器就可以解决在控制器里直接重复使用第三方库的功能所带来的不利影响呢?
继续回归主题谈一下为什么给第三方库添加一个管理器单例,必须将第三方库实例化的一个对象设置成单例管理器类的一个属性呢?而且还是强引用!当然,这么说好像不对,因为管理ASI这个第三方库的单例类的属性并不是ASIHTTPRequest类实例化的对象,反而是继承于NSOperationQueue 的ASINetworkQueue类实例化的一个队列对象。其目的是想要通过单例类实例化的属性通过队列这个属性来管理添加到队列里面的所有线程。好像说起来很玄乎哈?为什么通过一个单例类实例化的单例对象的一个队列属性就能管理这个队列里所有的线程呢?听起来就很神奇到不可思议呀?这样问为什么很难找到合理的解释,只能说问问题的角度不对?那应该怎么问呢?
首先考虑这个创建ASI的单例管理类就已经很无语。当然我也不是什么都不知道,我对于创建一个单例类对象所知道的就是:1、单例类对象都是通过类方法创建 2、在.m文件里实现的这个类方法本质上还是一个开辟空间的过程。但是需要注意:单例对象的内存空间只会被开辟一次,主要通过static来保证已经创建的单例对象不会因为重复调用创建单例对象的代码或就重复执行包含创建单例对象的方法造成多次开辟空间的不良后果。同时考虑到分线程1和分线程2可能调用这个创建单例对象的方法,造成多次开辟内存空间,因此为了防止受到分线程的影响,有几种上锁方式,最常用的方式就是通过static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{})方法进行上锁。3、第一次创建单例类对象时,自然内存状态为static HttpManager *httpManager = nil空,所以需要开辟内存空间。这里我就有问题了,按理说开辟内存应该使用类名加上alloc的方式来开辟内存空间,为什么这里竟然使用[super allocWithZone:NULL]来开辟内存空间呢?4、当然我也明白就是开辟内存空间的时候也就涉及到了一个init的初始化问题。当然上次我已经说明白了,初始化init的具体代码是什么关键还是取决于所继承的类到底是什么?这个初始化方法的意义在于,只要有对象被初始化就会调用这个在父类初始化方法基础上增加的一些初始化设置。5、这个时候就完全被init里增添的初始化设置代码搞凌乱了,最关键的还是因为我根本不知道所创建的单例对象的列队属性竟然会有这么多的方法可以设置。换句话说,这个属性的初始化赋值有点复杂。问题的根本在于这个属性根本不同于以往的什么字符串或是什么长整型呀!根本不能直接用等号进行赋值嘛!原因就是这个属性本质上是常规意义上的对象,而且是必须通过类方法开辟空间的对象。不开辟空间就无法正常工作的对象。主要是想在初始化方法里为单例对象的队列对象属性设置几个方法,分别是:setRequestDidStartSelector、setRequestDidFinishSelector、setRequestDidFailSelector、setQueueDidFinishSelector。最后最关键的就是还需要添加一个[_netWorkQueue go]启动队列的方法。
现在管理类的单例类对象已经创建完成,现在的问题就是围绕着怎么添加线程任务到这个单例类对象的队列对象属性里。当然无论你添加的线程任务请求是什么,有一点必须注意,就是都少不了添加线程任务请求到队列中的[_netWorkQueue addOperation:request]方法 。1、首先我不明白的就是为什么创建Http请求时要新建一个方法,然后在这个方法里面创建线程,并且将线程对象添加到队列中,这时候CPU会对添加的线程进行时间片的分配管理,数据下载成功与失败都会调用相关的回调方法。当然这些方法作为队列对象属性的方法,早已经在实例化单例对象的时候就已经在初始化方法里为队列对象属性设置了这些回调方法。2、因为队列中的任何一个线程任务在进行结束时都会调用线程结束时的方法。那么我如果想要通过这个线程任务结束时回调的方法来传递线程任务下载完成大数据到控制器,有一个很大的问题需要解决,就是需要准确地知道到底是哪一个任务线程调用了回调方法!只有准确的知道是哪一个任务线程现在完成了任务,才能够知道该把任务线程下载完成的数据往需要他的控制器传递呀!首先想到的解决方案就是通过tag属性来标记线程任务(其实这些所说的线程任务通常都是任务请求request)。但是你会发现虽然我们打印Tag号就能能够知道正在调用队列回调方法的任务到底是哪一个,但是随着任务的越来越多,你会发现就算你知道任务tag号,然后再根据tag号寻找到相对应的任务也是一个巨大的工作量呀!造成代码的可读性极差!如果能有一种标识直接就是任务的名称就好了,而且你会发现,如果能够直接获取到任务的名称对于以后使用通知来分发信息也是一个极大的方便。所以,解决方法就是通过枚举直接通过任务的标识找到任务名。换句话说吧!就是给每一个线程任务都绑定一个包含线程任务自身名称的字典。如何实现,当然是创建一个单例类对象可以调用的方法来为线程任务绑定一个包含自身信息的字典。那么我就又不明白了,为什么必须要通过线程请求任务的userInfo属性来作为线程任务的标识符呢?这还得从开始来说起,要想知道现在调用队列对象属性里回调方法的线程任务请求到底是哪一个?唯一的办法就是将线程任务请求的标识符存储在线程任务请求的属性里,然后通过判断线程任务请求的属性就可以知道现在是那一个线程任务请求了,所以必须有一个线程任务请求的属性存储东西来区分现在到底哪一个线程任务请求在调用队列的回调方法。能够肩负起这项任务的属性就只有Tag和userInfo了。前者是一个数字不够直观,后者是一个字典需要用到枚举。对啊?此时我就迷茫了,如何将线程任务请求的标识符赋值到请求的userInfo属性里呢?也就是说如何将信息存入字典,当然就是通常来说的字典赋值方法并不难吧!就是先初始化创建一个字典,然后把字典赋值给userInfo属性就可以了!可是我就又不明白了,这跟枚举有毛关系呀!对于字典来说,不就是一个键对应一个值么?这个键就是线程任务请求类型,键所对应的值就是线程任务请求的名字!所以根本不需要使用到枚举嘛!换句话说,使用枚举的意义到底是什么?当然使用宏定义我还可以理解,就是因为宏定义的名称写的时候有提示就很方便不用当我们使用的时候再去拷贝嘛!这就是宏定义的意义和价值。可是我还是不明白使用枚举的意义在哪儿?其实枚举的本质上也就是一个个键值对的集合嘛,那么我不明白的就是如何根据枚举的一个键或是说一个值找到相应地一个值或是一个键!原来使用枚举竟然也是出于方便比较的考虑,试想一下,如果不用枚举,那么很明显通过userInfo字典的@[@"requestTypeKey"]提取出来的值对应的肯定是线程任务请求名称的字符串。当然现在已经存入了userInfo字典,当我们使用线程任务请求的userInfo属性提取出字典然后使用@[@"requestTypeKey”]提取出线程任务请求名称的字符串时怎么作对比又成了一个难题。其实话说到这儿,好像并不会因为不使用枚举就变得不方便,而且如果你说写枚举的作用仅仅是想要在写线程任务请求名称的时候有提示那么我想说通过宏定义也可以做到。但是如果不是userInfo属性而是使用的是tag属性,那么枚举就变得非常有必要了。所以更加有必要捋一捋枚举的思路,方便以后直接使用tag属性时使用,可是枚举的意义竟然不大?暂且放一放!
当然还有一个最重要的问题就是在创建任务请求的时候必须能够准确地理解get请求和post请求的区别!换句话说就是必须知道怎么使用ASI进行get请求和post请求!这个后面有说明!
第二个问题就是为什么不直接在线程任务请求里将包含线程任务名称的字典赋值给请求的userInfo属性!而是通过这个类实例化的对象来调用一个给请求的userInfo字典属性赋值的方法。这样做又是意义何在?如果你说这样做的目的是想将创建包含线程任务请求名称的字典的创建方法封装起来,那么我会问你,为何不直接将@{}内容赋值给userInfo属性呢?当然这纯属娱乐,我们要时刻谨记一点就是,我这句话想要实现一个什么功能?以后是否还会再用?那么接下来就需要思考需要传递哪些参数才能保证功能的实现,封装加封装接着就是调用方法!
现在就是更加深刻理解哪些回调方法的时候了?为什么我们这么强调必须准确的知道现在到底是哪一个线程任务请求在调用队列初始化时就形成的回调方法!因为我们只有知道了到底是哪一个线程任务请求才能进行下一步操作!下一步操作通常都是说在一个线程任务进行结束后会调用的setRequestDidFinishSelector方法。这就要求我们必须通过switch或者说if else(else if)来准确地判读此时此刻进入setRequestDidFinishSelector方法的线程任务请求的名称。这样就能根据线程任务请求的名称将下载完成的数据传递到需要的地方!
当然这个时候问题又来了,就是我虽然已经知道线程任务请求结束后,主要有responseString/responseData这两个属性来保存任务下载完成后的数据。当然也可能还有其它的种类哈!现在首先的疑问就是为什么不直接用NSString或是NSData将responseString/responseData属性里的值进行接收传递出去,非要将这些属性的值以键值对的形式传递出去呢?而且还会在这个键值对的基础上添加一个键值对,一个说明是否下载成功的键值对!所以如此说来,这个需要被传递的字典总共有两个键值对,一个是说明是否下载成功的键值对,一个是以将会被传递的下载数据为值的键值对。如何将这个包含下载数据的字典传递传递出去呢?当然是通过协议代理来进行传值。还记得当初大明湖畔前的协议代理嘛?要想把值传出去,就必须调用一个协议方法。那么想要调用协议方法,首先得有这个方法的声明吧!然后必须加上一个可以执行这个方法的协议属性呀!只要具备了这三件套就可以将对象传递出去了,当然我们知道使用一个协议总共分7步,已经执行了3步,还剩4步必须写在接收对象的那个控制器里,主要是:调入包含协议的头文件、引用协议、创建委托代理、用协议方法接收传递过来的对象!这样就可以直接将下载数据的过程分理出控制器了!通过一个封装就可以实现下载过程,更同时通过协议代理将下载完成后的数据传递到控制器,而且的而且还可以对下载完成的数据先进行数据模型的封装再传值到控制器!
现在已经完美地将网络请求的过程脱离控制器,同时可以将网络请求完成的数据通过协议代理将整理后的数据传递到控制里!可是通过协议传值必须有一个前提,就是通过委托所指向的对象,必须在调用哪个协议方法的时候存在呀!如果说同步操作或是在主线程进行所有的事情才能不用考虑这个问题,那么异步操作很容易发生的崩溃的一个场景就是,当我从控制器1跳到控制器2时,控制器2里面设置了一个协议代理用于接收从网络封装库传递过来的数据,我们知道接收到从网络封装类获取到下载数据的前提是必须将控制器类对象指向网络封装库的实例化对象的委托属性!也就是说网络请求类的对象的委托属性现在指向了控制器的对象,只要网络请求类的对象的委托属性指向了控制器对象,那个用于接收从网络请求类的下载数据的方法将会被激活!但是哟!但是你要记住,就是本来说如果是主线程,通常写到这个位置就已经可以完美地保证传值成功了,但是如果遇到分线程处理就的另有意外了!因为完全有这么一种情况就是加入我刚刚从控制器1调到控制器2,那么自然执行控制器2里面的代码,建立了委托协议的链接,这样将会启动协议代理传值的方法?这时候我就有一个问题了,就是当我跳到控制器2时肯定会马上启动协议代理传值方法,那么这是就分两种情况了,第一种情况是网络请求类已经数据下载完成直接就调用协议里的方法,第二种情况就是只有当我调到控制器2的时候,只有当创建了协议代理委托之后,网络请求类才会开始添加分线程任务到队列里。那么这时候就会出现一个常见的问题就是,当跳到控制器2的时候,这是添加了一个分线程的请求任务,这需要时间呀!而且大天朝的网这么慢,如果用户这时候选择不等带加载数据,直接销毁控制器回到控制器1,这样会发生一个什么情况呢?崩溃,而且是毫不犹豫的崩溃!原因就是是控制器2启动了分线程下载数据的方法,当分线程任务请求数据成功之后本来想要通过协议代理将下载完成的数据传到控制里的代理方法时,却发现整个代理都不存在了!自然就崩溃了,其实崩溃的原因不在这儿,真正的原因应该从指针的角度去考虑,就是最初跳到控制器2的时候,网络请求类的对象的委托属性指针指向了控制器2的这片内存空间,当控制器2被销毁时内存被回收自然发现委托属性指针指向的内存空间竟然没有了,所以发生了崩溃,但是这也还是无法解释!原因就是我分线程任务请求结束之后在调用协议代理传值的方法前有过判断呀?这个判断就已经包括了判断网络请求类对象的委托属性是否为真,而且也判断了这个协议代理方法已经被实现才开始调用协议代理的方法进行传值,所以按理说是不会发生崩溃的呀!而且要记得当初的解释,就是说我们的前提认为是当我们调到控制器2的时候才开始进行网络请求这个分线程任务。但是我们也必须知道在控制器调用添加网络请求任务线程到队列里必须时在控制器里通过网络请求类的实例化对象进行调用这个创建网络请求任务的过程呀!相当于一个例子,你有本事创建了一个网络请求,你却没有耐心等到网络请求结束将数据传回来!所以我就更疑惑了,如果我在网络请求任务结束后调用协议代理传值方法前判断网络请求类的对象的委托属性是否为真和协议代理的方法是否被实现,按理说就不会发生崩溃了呀!就算你跳到控制器2又马上注销控制器2返回到控制器1也不会造成程序的崩溃呀!一切都因为网络请求类的对象在调用协议代理传值方法前都会首先判断是否网络请求类的对象的委托属性是否为空以及协议代理的传值方法是否已经被实现。这样就完全可以避免因为指针指向空内存或调用不存在的方法而引起的崩溃!但是你会发现尽管现在程序不会发生崩溃,但会因为控制器的跳转带来一个全新的问题,就是:当我在控制器1里创建了一个网络请求任务添加到网络请求类的队列中,这时候就已经开始控制器1页面的数据的请求。当然因为是分线程嘛!所以主线程继续前进,分线程进行下载数据的操作,我们也知道,分线程在全网络请求任务结束之后会通过协议代理将下载完成的数据传递到使用协议代理的控制器里。也就是说,当分线程网络请求任务结束之后,网络请求类的对象的委托属性指向哪一个控制器,那么下载完成的数据就传递到哪一个控制器里的协议代理方法里。可是呀可是,网络请求类是一个单例类,自然网路请求类的对象也是一个单例对象,换句话说,网络请求类的对象的委托属性可以在任何一个控制器里被修改。如果控制器1创建了一个网络请求任务添加到网络请求类的对象的队列属性中,控制器2也创建了一个网络请求任务添加到网络请求类的对象的队列属性中,也就是说,两个控制器都实例化了网络请求类的单例对象,而且都创建了单例对象的委托属性指向了控制器本身!其目的就是通过将委托属性指向自身控制器,从而调用控制器本身里接收下载完成的数据的协议代理的方法!如此就会产生一个问题就是,依然当我从控制器1调到控制器2时,因为网络请求类的对象是一个单例,与因此在控制器1里面创建的请求还未结束呢?那个控制器1里面的委托属性的指向已经从控制器1变成了控制器2 . 因此你会发现本该传递到控制器1里面的数据竟然现在传递到了控制器2里面,一切都是因为单例类的委托属性发生了改变!于是问题说到这儿,你就肯定会问了,为什么非要把网络请求类设置成单例类呢?这真是一个好问题,可以先换一个角度来想一想,如果不使用单例会造成什么不良后果!首当其冲的就是单例对象初始化方法里对队列属性的赋值就会出现问题,因为对列必须作为网络请求类的对象的属性,那么如果不把网络请求类的对象设置成单例类对象,那么队列作为一个属性也会被多次初始化,这样造成不必要的内存消耗呀!本来一个队列就可以管理所有的网络请求任务,为什么要设置这么多队列且多次为队列开辟内存空间造成不必要的消耗!而且网络请求类实例化的对象如果是一个单例还具备诸多优点,比如说:方便管理,节省内存,创建方便等优点!那么如何解决那个单例类对象的委托属性被更改的问题呢?说了这么多,终于引出解决方案了,按照老师的话说:一个高手写代码,必然要懂架构,最底层是持久层,中间为业务逻辑层,最上面是表示层。但是我不明白的是为什么中间的业务逻辑层必须要写成单例,而且持久层通过协议代理设置持久层的对象的委托属性始终指向业务逻辑层的单例对象,将所下载完成的数据全部传递给了业务逻辑层。整体的大概思路就是将中间的业务逻辑层设置成单例类,然后将网络请求类也就是持久层的数据全部存储到中间的业务逻辑层,暂且叫做网络请求管理类吧!将网络请求类的所有数据(通常就是一个字典)和此时此刻的线程任务名称标识符传递到网络请求管理类。当我发现我所看的内容完全超出我的理解范畴的时候!我能做的就是找到我已经知道的信息。现在先列一下发生改变的地方:1、协议里的回调方法发生了改变,输入参数不仅仅包括下载完成处理后的数据,也包括调用次协议代理方法的线程请求任务的名称。2、网络请求类的初始化函数发生了改变,初始化函数增添了一个输入参数:- (instancetype)initWithDelegate:(id) delegate; 3、网络请求类不再是一个单例,4、原本实例化一个网络请求类的对象是在控制器里实现,而现在则变成了在网络请求管理类开辟网络请求类的对象空间。5、原本是把下载完成的数据通过协议代理的回调方法传递到控制器,而现在则是通过协议代理的回调方法传递到网络请求管理类,继而结合传递到网络请求管理类的线程任务名称以发送通知的形式发送到对应的控制器里。
现在需要再整理一下那个网络请求管理类里面究竟都有哪些东西:1、首先这是一个单例,肯定有创建网络请求管理类单例对象的类方法,static,开辟内存空间的方法,改写父类的init初始化方法 ,在改写的初始化方法里为全局的网络请求类对象开辟了内存空间,同时将网络请求类对象的委托属性指向了网络请求管理类对象(即self) 2、有通过网路请求管理类直接调用的网络请求任务方法,相当于一个中介 3、在.m文件里创建了一个网络请求类对象的全局变量。4、引入了网络请求类中用于传值的协议 5、实现了.h文件中声明的添加网络请求线程任务到队列中的方法,其实这个方法本质上在网络请求类就已经实现。现在只不过多封装了一次,相当于原本在控制器里通过网络请求类的对象去调用添加网络请求任务到队列中的这个方法,而现在虽然同样是在控制器里调用添加网络请求任务到队列的方法,只不过现在这个对象已经不是网络请求类的对象,而是变成网络请求管理类的对象了,所以需要重写一下调用方法。5、控制器里面不在设置代理委托,而是注册通知,当然当网络请求类将下载完成的数据和线程网络请求任务的标识符(线程任务名字)通过回调方法传递到网络请求管理类的方法时,进一步操作就是通过switch根据传过来的线程任务名字判断到底通知的名字是什么!传值成功!
那么现在问题来了,我确实知道网络请求类的是持久层,就是干事情的那一个人,网络请求管理类只是一个中介而已,也就是网络请求类就相当于具备一个功能就是的当控制器想要创建网络数据请求时,通过网络请求类的实例化对象调用创建网络请求的方法,再在网络请求管理类通过网络请求类的实例化对象调用最本质的那个添加网络请求任务到网络请求类的实例化的对象的队列属性里。也就是说那个通过网络请求管理类对象调用的开始网络请求任务的方法其实是一个伪方法!最根本的方法还是那个在网络请求管理类里通过网络请求类的对象调用的创建网络请求的方法!但我不明白的是:为什么现在的网络请求类变成了非单例,而网络请求管理类又变成了单例呢?到底什么时候用单例,什么时候又不用单例,简直愁死我了!我能目前能想到就是单例嘛!自然只能开辟一次内存空间,所以当我们需要覆盖单例对象的属性的时候,自然选择使用单例。可是呀可是,我难道只是因为一个勉强的理由就是更改和刷新属性就创建单例对象么?当然不是!我将网路请求管理类设置成单例的根本原因还是在于:方便管理,那么如何理解这个方便管理呢?首先分析一下这个网路请求管理类的作用到底是啥呀!当然你会说这还用问吗?就是一个中介嘛!控制器通过网络请求管理类调用网络请求类的的创建网络请求任务的方法,而且网络请求管理类通过协议代理的回调方法接收从网络请求类接收过来的数据。那么问题来了,如果不把网络请求管理类设置成单例会发生什么事呢?自然我们知道网络请求管理类设置了一个网络请求类对象的全局变量。那么这就必须先考虑一个问题就是:属性和全局变量的区别是什么?又在什么时候使用属性?又在什么时候使用全局变量?全局变量没有像属性那样的内存管理修饰符,那么全局变量的内存管理又怎么计算呢?先说区别吧?全局变量的使用是通过下划线加属性名称进行调用,不能使用self方法,因为self方法就相当于变量不仅是全局的,更是已创建了get和set方法。属性比较全局变量的优势就是在于不仅是全局变量,更是已经初始化默认了set和get方法!那么如果你不需要set和get方法,也就是说不需要再这个类以外给这个变量赋值或取值,那么自然就是使用全局变量,而不是属性啦!因为只要写了属性就默认这个变量是能够在除这个类本身以外被调用到!而且可以进行存储赋值和get取值!那么现在网络请求管理类里面有一个关于网络请求类的全局对象变量。通过这个全局对象变量来调用网络请求类里面创建网络请求的方法。但是这与为什么网络请求管理类是个单例并没有关系呀?但是当我看到网络请求管理类的单例对象的初始化方法后,我好像明白了什么!同样在改写父类的初始化方法的时候,我们发现这里面为网络请求类的对象的全局变量开辟了内存空间。要知道,既然是对象型的全局变量,就必须开辟内存空间才能使用呀?可是每当我把话题说到这儿,我总是忘不了:这种逻辑对于那种字典或数组来说,确实每次创建这种对象类型的全局变量时,总是需要为字典呀!数组呀!自定义类的对象呀!开辟空间,但是心中总有一个声音就是,字符串也是一个对象,对吧?那为什么我创建字符串或长整形类型的数据的时候从来没有开辟内存空间这种印象呢?每次都是直接就使用了!原因就是字符串和长整型这些类型的对象的空间一直存在于常量区,也就是说,字符串的内存空间一直存在,字符串对象里的内容不就是哪些已经存在的固定字符存在的嘛!把字符串对象的内容比作英语,那么那片内存空间就是那26个字母。所以它不同于字典和数组那样内容有很多的不确定性。所以不用象字典和数组对象以及自定义类的对象那样需要开辟内存空间了!现在的问题就是,我们明白给网络请求类对象全局变量开辟内存空间的方法写在网络请求管理类的对象创建时的初始化方法里是希望只有在创建一个新的网络请求管理类对象的时才实例化一个网络请求类对象,可是问题就在于多次创建网络请求类对象不行么?到底行不行?我应该怎么去判断?
假设法?假设网路请求管理类不是一个单例类,那么每当一个控制器调通过网络请求管理类的对象调用创建网络请求任务的方法的时候,都会实例化一个网络请求管理类的对象。然后网络请求管理类对象的初始化又会创建一个网络请求类的对象。要知道创建一个对象就会开辟一次内存空间呀!就算不开率这样是否会出现神秘差错,就算可以完美地实现功能,这些内存空间的开辟就是一个很大的浪费呀!因为你只要一创建网络请求,就创建了一个对象,如果说创建对象的目的仅仅是想要临时调用到所创建对象里面的方法,为什么不把这个对象设置成一个单例对象,这要就可以完美地避免在控制器里,只要一调用创建网络请求就开辟至少两个内存空间。所以总结一点,就是当控制器创建一个对象只是为了调用对象里实现的方法时,那么通常都是把这个对象设置成一个单例对象。现在想明白了如果在控制器里想多次调用其它类的方法通常都是用类方法或者说创建单例对象来调用了吧!因为省内存。那么还有最后一个问题就是,为什么原本没有创建网络请求管理类的时候,网络请求类是一个单例,当网络请求管理类被创建的时候,网络请求管理类作为一个单例类可以理解,毕竟是控制器会调用到这个网络请求管理类的方法。因此必须采用单例嘛!只是不明白现在那个网络请求类从单例变成了非单例,我想知道的是,如果不将网络请求类从单例类变成非单例类会发生什么事呢?按理我这网络请求管理类是一个单例类,每当控制器调用网络请求管理类的方法时。网络请求管理类也会创建一个网络请求类的对象来调用网络请求类里面的方法。既然是网络请求管理类是一个单例类,那么就不可能多次创建网络请求类对象!问题就是弄清楚我们当初没有创建网络请求管理类的时候,将网络请求类设置成单例。其目的不仅仅有利于控制器里面调用网络请求类里面的方法的时候不会开辟太多的内存空间。而且还有一个原因就是网络请求类的对象里的对列属性只能是一个!将所有线程请求任务全部添加到这一个队列中!当然给对象的队列属性赋值的程序只会存在改写网络请求类的重写初始化方法里。现在同样将队列属性赋值的过程写在了网络请求类对象的初始化方法里!而这个网络请求类的对象的初始化方法只会被执行一次,所以当然不需要网络请求类是一个单例了。答案还是很勉强,最合理的解释我想还是因为单例本身存在的价值是因为需要多次创建对象来调用对象里的方法,而现在的问题就是我根本不需要多次创建对象就可以调用到方法呀!举个例子吧?就是控制器1调用网络请求的方法时需要创建一个网络请求类对象,当控制器2又需要调用网络请求的方法时又需要创建一个网络请求类对象,一般在这种情况下,将网络请求类对象设置为单例对象。这样就可以避免内存浪费,重复开辟内存空间了!但是现在是在网络请求管理类调用创建网络请求的方法,也就是需要网络请求类的对象才能调用的方法!但是,在网络请求管理类调用创建网络请求类的方法,就相当于是只有一个控制器来多次调用网络请求类的方法,这样就无须多次创建网络请求类的对象呀!既然都只是创建一次网络请求类的对象,那还有啥将网络请求类变成单例类的必要呢?其实本质上来讲,网络请求管理类对于网络请求类就相当于网络请求类对于NSOpertionQueue队列类!
ASI网络请求封装?
将ASI导入工程就是一件比较麻烦的事情,需要导入6个框架和1个路径设置。ASI作为AFN出来之前广为人知的网络请求库,封装ASI变得尤为重要。现在谈谈我对于ASI封装的理解,到底是以讴歌怎样的逻辑,
首先我们必须知道的就是ASI里面有三个很重要的类?
分别是:ASIHTTPRequest/ASIFormDataRequest/ASINetworkQueue这三个类。
这三个类的关系和功能到底是什么呢?
ASIHTTPRequest继承于NSOperation,ASINetworkQueue继承于NSOperationQueue,而ASIFormDataRequest继承于ASIHTTPRequest,主要用于网络的Post请求,当然也具备父类get请求的功能。
对这个ASI库进行二次封装的出发点是什么?
经常会出现的情况就是,在一个View Contorller里,不止一次使用到网络请求,如果不进行封装就会两次使用到网络请求,如果网络请求到的数据是以协议回调方法的方式来返回,势必造成控制器里的代码非常冗杂。因此封装特别有必要,想要达到的想过仅仅就是一个调用天剑网络请求任务和返回网络请求结果的两句必不可少的代码。本质就是:View展示的数据不止来源于一个接口。控制器不下载数据也不处理数据,需要的仅仅是将已经下载完成的且分析处理后存到数据模型里并用可变数组接收后的一个数组或字典传递给控制器,这就是封装的意义所在。
问题1:通过协议代理的方法来回调下载完成并处理好的数据可能导致程序的崩溃?
原因就在于下载请求类在请求数据结束后直接通过协议代理的回调方法将下载完成并处理好的值返回到了控制器。假如控制器1跳转到控制器2又马上销毁控制器2,就会发生应为网络请求类的单例对象的委托属性指向了一个空的控制器对象而发生崩溃。解决方法就是在将数据通过回调方法传回控制器之前首先判断网络请求类对象的委托属性值是否为空和协议代理的回调方法是否已经存在。
问题2:当我从控制器1跳转到控制器2,将控制器2的网络请求任务添加到网络请求类的队列属性中,进行分线程下载,再下载还为完成就销毁控制器2回到控制器1,会发现本该本该回调到控制器2的数据现在传递到了控制器1,为什么?
原因就是网络请求类是一个单例类,单例类的委托属性可以随时被修改,当初这个单例类的委托属性指向的是控制器2的对象,而现在则指向了控制器1,所以控制器2里面添加的网络请求的任务在网络请求类分线程下载处理完成后开始调用协议方法传值。回调哪一个传值方法就是根据网络请求类对象的委托属性指向哪一个控制器来决定,现在单例类对象的委托属性已经变成的控制器1,自然调用控制器1里面的回调方法,所以自然把控制器2里面的值传递到了控制器1里面。
问题3:为什么网络请求类是一个单例类?
要知道我们封装网络请求类的本质就在于将所有的网络请求任务添加到一个并行队列中去管理,而这个并行队列是以一种属性的方式存在于网络请求类,如果不把网络请求类设置成单例类,那么并行队列属性将会被多次创建,这就无法达到通过并行队列管理所有添加的网络请求任务的目的。而且没添加一个网络请求任务就需要创建一个网络请求对象,就会创建一次队列,这样会造成多大的内存消耗呀!
问题4:如何解决网络请求类的委托属性被修改的问题?
重新架构封装结构,最底层是持久层也就时网络请求类,中间为业务逻辑层也就是网络请求管理类,最上面是表示层也就说我们说的控制器1和控制器2。中间业务逻辑层也就是网络请求管理类现在写成了单例类,网络请求类继承与普通的NSObject,网络数据下载完成后首先通过协议代理的回调方法将数据全部传递到网络请求管理类,继而继而结合传递到网络请求管理类的线程任务名称以发送通知的形式发送到对应的控制器里。
也就是说,现在控制器表示层不再设置代理委托,而是注册通知,当然当网络请求类将下载完成的数据和线程网络请求任务的标识符(线程任务名字)通过回调方法传递到网络请求管理类的方法时,进一步操作就是通过switch根据传过来的线程任务名字判断到底通知的名字是什么!传值成功!