NSURLSession 的最简单实用方法
NSURLSession 的不同之处在于,它把 NSURLConnection 替换为
NSURLSession
NSURLSessionConfiguration
NSURLSessionTask 子类:NSURLSessionDataTask、NSURLSessionUploadTask、NSURLSessionDownloadTask
NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration];
NSOperationQueue *queue = [NSOperationQueue mainQueue];
NSURLSession *session = [NSURLSession sessionWithConfiguration:config delegate:self delegateQueue:queue];
NSURLSessionDownloadTask *task = [session downloadTaskWithURL:[NSURL URLWithString:@"https://www.baidu.com"]];
[task resume];
可以用这个最简单的例子跟 AF 对比下,看看别人怎么写一个库
设置 NSURL 的几种方式
NSURL *baseURL = [NSURL URLWithString:@"http://example.com/v1/"];
// http://example.com/v1/foo
[NSURL URLWithString:@"foo" relativeToURL:baseURL];
// http://example.com/v1/foo?bar=baz
[NSURL URLWithString:@"foo?bar=baz" relativeToURL:baseURL];
NSURLSession 和 NSURLConnection 的区别
与 NSURLConnection 相比,NSURLSession 最直接的改善就是提供了配置每个会话的缓存,协议,Cookie 和证书政策(Credential Policies),甚至跨应用程序共享它们的能力。
NSURLSessionConfiguration 的类型
+ (NSURLSessionConfiguration *)defaultSessionConfiguration;
返回标准配置,这实际上与 NSURLConnection 的网络协议栈是一样的,具有相同的共享 NSHTTPCookieStorage,共享 NSURLCache 和共享 NSURLCredentialStorage。
+ (NSURLSessionConfiguration *)ephemeralSessionConfiguration;
返回一个预设配置,没有持久性存储的缓存,Cookie或证书。这对于实现像“秘密浏览”功能的功能来说,是很理想的。
+ (NSURLSessionConfiguration *)backgroundSessionConfigurationWithIdentifier:(NSString *)identifier NS_AVAILABLE(10_10, 8_0);
独特之处在于,它会创建一个后台会话。后台会话不同于常规的,普通的会话,它甚至可以在应用程序挂起,退出,崩溃的情况下运行上传和下载任务。初始化时指定的标识符,被用于向任何可能在进程外恢复后台传输的守护进程提供上下文。
线程保活
- (void)start {
// NSRecursiveLock 递归锁的说明见 [GCD](http://alicialy.github.io/2016/06/24/Concurrent-Programming-and-Synchronization/)
[self.lock lock];
// ...
[self performSelector:@selector(operationDidStart) onThread:[[self class] networkRequestThread] withObject:nil waitUntilDone:NO modes:[self.runLoopModes allObjects]];
[self.lock unlock];
}
- (void)operationDidStart {
[self.lock lock];
if (![self isCancelled]) {
self.connection = [[NSURLConnection alloc] initWithRequest:self.request delegate:self startImmediately:NO];
// ...
}
[self.lock unlock];
// ...
}
创建一个线程,在线程中创建 NSURLConnection,NSURLConnection 请求后的回调也会是在这个子线程中,如果不使用线程保活方式,线程结束就退出了,无法收到回调信息,线程保活则是在子线程中添加了一个 Runloop
+ (void)networkRequestThreadEntryPoint:(id)__unused object {
@autoreleasepool {
[[NSThread currentThread] setName:@"AFNetworking"];
NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
[runLoop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];
[runLoop run];
}
}
+ (NSThread *)networkRequestThread {
static NSThread *_networkRequestThread = nil;
static dispatch_once_t oncePredicate;
dispatch_once(&oncePredicate, ^{
_networkRequestThread = [[NSThread alloc] initWithTarget:self selector:@selector(networkRequestThreadEntryPoint:) object:nil];
[_networkRequestThread start];
});
return _networkRequestThread;
}
如果每个请求就开一个线程,保活这个线程的话,开销太大,所以使用一个单例
KVO 的手动触发
- (void)setAllowsCellularAccess:(BOOL)allowsCellularAccess {
[self willChangeValueForKey:NSStringFromSelector(@selector(allowsCellularAccess))];
_allowsCellularAccess = allowsCellularAccess;
[self didChangeValueForKey:NSStringFromSelector(@selector(allowsCellularAccess))];
}
KVO 手动触发和自动触发不是互斥的,如果需要控制某些变量手动触发,某些自动触发,需要重载 automaticallyNotifiesObserversForKey 方法
(BOOL)automaticallyNotifiesObserversForKey:(NSString *)theKey {
BOOL automatic = NO;
if ([theKey isEqualToString:@"balance"]) {
automatic = NO;
} else {
automatic = [super automaticallyNotifiesObserversForKey:theKey];
}
return automatic;
}
可变参数
NS_REQUIRES_NIL_TERMINATION :是一个宏,用于编译时非 nil 结尾的检查
- (void)print:(NSString *)firstArg, ... NS_REQUIRES_NIL_TERMINATION {
if (firstArg) {
NSLog(@"%@", firstArg);
va_list args;
NSString *arg;
va_start(args, firstArg);
while ((arg = va_arg(args, NSString *))) {
NSLog(@"%@", arg);
}
va_end(args);
}
}
调用时要以 nil 作为结尾标志 [instance print:@”a”, @”b”, nil];
常量的定义
AF 中找不到任何宏定义的字符串、数字、数组,都是使用静态常量
数组常量的定义方式如下
static NSArray * AFHTTPRequestSerializerObservedKeyPaths() {
static NSArray *_AFHTTPRequestSerializerObservedKeyPaths = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_AFHTTPRequestSerializerObservedKeyPaths = @[NSStringFromSelector(@selector(allowsCellularAccess)), NSStringFromSelector(@selector(cachePolicy)), NSStringFromSelector(@selector(HTTPShouldHandleCookies)), NSStringFromSelector(@selector(HTTPShouldUsePipelining)), NSStringFromSelector(@selector(networkServiceType)), NSStringFromSelector(@selector(timeoutInterval))];
});
return _AFHTTPRequestSerializerObservedKeyPaths;
}
字符串、数字等
static NSString * const kAFMultipartFormCRLF = @"\r\n";
NSUInteger const kAFUploadStream3GSuggestedPacketSize = 1024 * 16;
开闭原则
代码中随处可见的符合开闭原则的 readonly 属性设置,对外只读,对内可读写
// xxx.h
@interface AFHTTPRequestOperation : AFURLConnectionOperation
@property (readonly, nonatomic, strong) id responseObject;
@end
// xxx.m
@interface AFHTTPRequestOperation ()
@property (readwrite, nonatomic, strong) id responseObject;
@end
此外,利用 @dynamic 变量是只读的,则只保留读取方法
@property (readonly, nonatomic, getter = isNetworkActivityIndicatorVisible) BOOL networkActivityIndicatorVisible;
@dynamic networkActivityIndicatorVisible;
- (BOOL)isNetworkActivityIndicatorVisible {
return self.activityCount > 0;
}
另外微信安装包的瘦身也提到过,改用 @dynamic 去掉类成员变量和类属性,安装包减少了2.5M。
断言
参数不能为空的地方不要忘了使用断言
NSParameterAssert(fileURL);
归档
在 iOS6 中,苹果引入了一个新的协议,是基于 NSCoding 的 NSSecureCoding。NSSecureCoding 在解码时要同时指定 key 和要解码的对象的类。能安全地写有关归档的代码,可以确保正在加载的数据文件是安全的。
Tips
- 获取两个 float 数据的最大值 fmaxf(70, 100);
- 三目运算 x ? : y,省略的是 x,等价于 x ? x : y。使用 self.completionQueue ?: dispatch_get_main_queue()
- 常量定义尽量不使用宏,除非需要 ifdef 这种宏,使用静态变量定义数组,定义返回数组的静态函数见
static NSArray * AFHTTPRequestSerializerObservedKeyPaths()
参考文献
忘记NSURLConnection,拥抱NSURLSession吧
KVO Manual Change Notification