下载&后台下载相关
首先了解NSURLSession
:
NSURLSession
中负责下载策略的URLSessionConfiguration
:
SessionDelegate
中负责下载的DownloadSessionDelegate
:
extension DownloadSessionDelegate: URLSessionDownloadDelegate {
func urlSession(_ session: URLSession, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
let method = challenge.protectionSpace.authenticationMethod
if method == NSURLAuthenticationMethodServerTrust {
let credential = URLCredential.init(trust: challenge.protectionSpace.serverTrust!)
completionHandler(URLSession.AuthChallengeDisposition.useCredential, credential)
}
}
public func urlSession(_ session: URLSession, didBecomeInvalidWithError error: Error?) {
manager?.didBecomeInvalidation(withError: error)
}
public func urlSessionDidFinishEvents(forBackgroundURLSession session: URLSession) {
manager?.didFinishEvents(forBackgroundURLSession: session)
}
public func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didWriteData bytesWritten: Int64, totalBytesWritten: Int64, totalBytesExpectedToWrite: Int64) {
guard let manager = manager,
let currentURL = downloadTask.currentRequest?.url,
let task = manager.fetchTask(currentURL: currentURL)
else { return }
task.didWriteData(bytesWritten: bytesWritten, totalBytesWritten: totalBytesWritten, totalBytesExpectedToWrite: totalBytesExpectedToWrite)
}
public func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL) {
guard let manager = manager,
let currentURL = downloadTask.currentRequest?.url,
let task = manager.fetchTask(currentURL: currentURL)
else { return }
task.didFinishDownloadingTo(location: location)
}
public func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
guard let manager = manager,
let currentURL = task.currentRequest?.url,
let downloadTask = manager.fetchTask(currentURL: currentURL)
else { return }
downloadTask.didComplete(task: task, error: error)
}
}
复制代码
NSURLSessionTask
介绍,以及下载专用的NSURLSessionDownloadTask
NSURLSessionDownloadTask
暂停生成用于断点续传的resumedata
:
后台下载
- iOS 原生级别后台下载详解
- 重点:completionHandler的做用: 处理完事件后,执行参数中的块,以便应用程序能够拍摄用户界面的新快照。通常在urlSessionDidFinishEvents函数中调用completionHandler。
- 准备
- 在 App 启动的时候
AppDelegate的application(_:didFinishLaunchingWithOptions:)
建立Background Sessions,后面会说明缘由。
- 下载过程当中
- 当建立了Background Sessions,系统会把它的identifier记录起来,只要 App 从新启动后,建立对应的Background Sessions,它的代理方法也会继续被调用
- 下载完成
- 在前台
- 跟普通的 downloadTask 同样,调用相关的 session 代理方法
Background Session
,若是是任务被session管理,则下载中的 tmp 格式缓存文件会在沙盒的 caches 文件夹里;
- 若是不被session管理,且能够恢复,则缓存文件会被移动到 Tmp 文件夹里;
- 若是不被Background Session管理,且不能够恢复,则缓存文件会被删除。
- 手动 Kill App 会调用了
cancel(byProducingResumeData:)
或者cancel
,最后会调用urlSession(_:task:didCompleteWithError:)
代理方法,能够在这里作集中处理,管理 downloadTask,把resumeData保存起来。
- 进入后台、crash 或者被系统关闭,系统会有另一个进程对下载任务进行管理,没有开启的任务会自动开启,已经开启的会保持原来的状态(继续运行或者暂停),当 App 从新启动后,建立对应的Background Sessions,可使用
session.getTasksWithCompletionHandler(_:)
方法来获取任务,session 的代理方法也会继续被调用
- 最使人意外的是,只要没有手动 Kill App,就算重启手机,重启完成后原来在运行的下载任务仍是会继续下载,实在牛逼
- 在后台
- 当Background Sessions里面全部的任务(注意是全部任务,不仅仅是下载任务)都完成后,会调用
AppDelegate的application(_:handleEventsForBackgroundURLSession:completionHandler:)
方法,激活 App
- 而后跟在前台时同样,调用相关的 session 代理方法,
didFinishDownloadingTo & didCompleteWithError
- 最后再调用
urlSessionDidFinishEvents(forBackgroundURLSession:)
方法
- crash 或者 App 被系统关闭
- 当Background Sessions里面全部的任务(注意是全部任务,不仅仅是下载任务)都完成后,会自动启动 App,调用
AppDelegate的application(_:didFinishLaunchingWithOptions:)
方法(这就是为何要在这个方法中建立Background Sessions
)
- 而后调用
application(_:handleEventsForBackgroundURLSession:completionHandler:)
方法
- 当建立了对应的
Background Sessions
后,才会跟在前台时同样,调用相关的 session 代理方法,最后再调用urlSessionDidFinishEvents(forBackgroundURLSession:)
方法
- 总结
- 只要不在前台,当全部任务完成后会调用AppDelegate的
application(_:handleEventsForBackgroundURLSession:completionHandler:)
方法
- 只有建立了对应Background Sessions,才会调用对应的 session 代理方法,若是不在前台,还会调用
urlSessionDidFinishEvents(forBackgroundURLSession:)
- 下载错误
- 支持后台下载的 downloadTask 失败的时候,在
urlSession(_:task:didCompleteWithError:)
方法里面的(error as NSError).userInfo
可能会出现一个 key 为NSURLErrorBackgroundTaskCancelledReasonKey
的键值对,由此能够得到只有后台下载任务失败时才有相关的信息
- 最大并发数
- 支持后台下载的URLSession的特性,系统会限制并发任务的数量,以减小资源的开销。同时对于不一样的 host,就算
httpMaximumConnectionsPerHost
设置为 1,也会有多个任务并发下载,因此不能使用httpMaximumConnectionsPerHost
来控制下载任务的并发数。
- 先后台切换
- 在 downloadTask 运行中,App进行先后台切换,会致使
urlSession(_:downloadTask:didWriteData:totalBytesWritten:totalBytesExpectedToWrite:)
方法不调用
- 解决办法:使用通知监听
UIApplication.didBecomeActiveNotification
,延迟 0.1 秒调用suspend方法,再调用resume方法
- 缓存文件
- 前面说了恢复下载依靠的是resumeData,其实还须要对应的缓存文件,在resumeData里能够获得缓存文件的文件名(在 iOS 8 得到的是缓存文件路径),由于以前推荐使用
cancel(byProducingResumeData:)
方法暂停任务,那么缓存文件会被移动到沙盒的 Tmp 文件夹,这个文件夹的数据在某些时候会被系统自动清理掉,因此为了以防万一,最好是额外保存一份。
上传
经过multipart form data上传的机制和原理
NSString *headerString = [NSString stringWithFormat:@"multipart/form-data; boundary=%@", boundary];
[request setValue:headerString forHTTPHeaderField:@"Content-Type"];
bodyStr = [NSString stringWithFormat:@"Content-Disposition: form-data; name=\"%@\"; filename=\"%@\" \r\n", name, fileName];
[data appendData:[bodyStr dataUsingEncoding:NSUTF8StringEncoding]];
[data appendData:[@"Content-Type: image/png\r\n\r\n" dataUsingEncoding:NSUTF8StringEncoding]];
[data appendData:[NSData dataWithContentsOfURL:fileURL]];
[data appendData:[@"\r\n" dataUsingEncoding:NSUTF8StringEncoding]];
复制代码
在multipart基础上实现分片上传
headers = ["Content-Range": "bytes=\(UploadManager.share().offset*model.uploadFileChunk!)-\(model.uploadSecretFileSize!)",
"wnd-token": Defaults.shared.get(for: wndToken) ?? ""]
复制代码
Tiercel源码解析

云盘上传
- 将添加的全部上传任务建立为
DownloadTask
,并保存self.sessionManager.cache.storeUploadTasks(self.waittingUploadArray)
- 检查正在上传的任务数,将可上传任务数补充至3个。
uploadArray
&waittingUploadArray
- 被暂停的任务从新开始(旧任务)
- 获取上传状态
- 1.若是未完成,则执行上传。
- 2.已完成上传的任务,将任务从等待数组中移除,继续添加任务。
- 若是是照片或视频,则文件拷贝到沙盒。
- 检查硬盘空间
- 上传以前,将待上传文件复制到解密区。
- 经过密码中间件计算哈希
- 密钥转保护、文件加密
- 文件预上传
- 将文件分片,读取第一片上传
- 分片上传完成后,判断是否还有分片未上传
- 未上传完,分片加一,从新调用上传
- 所有上传完,继续添加上传任务
后台上传
iOS项目技术还债之路《一》后台下载趟坑html
上传加密流程

下载解密流程

预览流程

文件存储流程

iOS底层-第一章 oc对象本质
- 一个OC对象在内存中是如何布局的?
- 一个NSObject对象占用多少内存?
- 分配16个字节,使用8个字节(isa指针)
- 实际上class_getInstanceSize获取到的大小, 是内存对齐以后的大小, 即全部成员变量中, 占用内存最大的那个成员变量的倍数, Person内的NSObject_IMPL有一个isa占用8个字节, _age占用4个字节, 因此class_getInstanceSize获取到的是8的倍数, 即16个字节
- OC中给实例对象分配空间是按照16的倍数递增的。
- OC中给实例对象分配空间时, 是按照16, 32, 48, 64, 80, 96...按照16的倍数递增的, 因此malloc_size函数获取到的Student实例内存是32
iOS底层-第二章 OC对象的分类
- 如何获取类对象?
- -class
- +class
- object_getClass(实例对象)
- 每一个类在内存中有且只有一个class对象
- 如何获取元类对象?
- object_getClass(类对象)
- 每一个类在内存中有且只有一个meta-class对象
- objc_getClass、object_getClass、-class、+class的区别?
- objc_getClass传入字符串类名,返回对应的类对象
- object_getClass传入instance对象、class对象、meta-class对象,返回class对象、meta-class对象、NSObject(基类)meta-class对象
- -class、+class返回类对象
- -class、+class底层实现?
- return self->isa
- return self
- object_getClass(obj)与[obj class]的区别
iOS底层-第三章 isa和superclass

iOS底层-第四章 kvo
- kvo的本质是什么?
- 利用runtime动态生成一个子类,而且让instance对象的isa指向这个全新的子类。
- 子类命名为NSKVONotifying_xxx,重写set、class、superclass、dealloc方法,新增_isKVOA方法。
- class方法返回原instance对象的类对象。
- superclass方法指向原instance的类对象。
- isKVOA返回true。
- dealloc会增长一些监听的释放。
- set方法新增willChangeValueForKey和didChangeValueForKey,而且在didChangeValueForKey内部会出发监听器Observer的监听方法。
- kvo动态生成的对象结构是什么样的?
- NSKVONotifying_Person的类对象中, 一共有两个指针isa和superclass, 四个方法setAge:, class, dealloc和_isKVOA
- 若是直接修改对象的成员变量,是否会出发监听器?
- 直接修改对象,不会调用set方法,将不会出发观察者。
- 如何手动触发kvo?
- 经过调用willChangeValueForKey和didChangeValueForKey方法,能够手动调用kvo,两个方法必须同时出现。

iOS底层-第五章 kvc
- setValue:forKey的原理?
- 按照setKey、_setkey顺序查找方法
- 没有找到方法,则查看accessInstanceVariablesDirectly方法返回值,默认为true。
- 若为true,按照_key、_isKey、key、isKey顺序查找成员变量,找到了直接赋值。
- 若为false或没有找到成员变量,调用valueForUndefineKey:并跑出异常NSUnknownKeyException

- valueForKey的原理?
- 按照getKey、key、isKey、_key顺序查找方法
- 没有找到方法,则查看accessInstanceVariablesDirectly方法返回值,默认为true。
- 若为true,按照_key、isKey、key、isKey顺序查找成员变量,找到了直接取值。
- 若为false或没有找到成员变量,调用valueForUndefineKey:并跑出异常NSUnknownKeyException

- kvc赋值时,会触发kvo吗?
- 使用KVO给属性或成员变量赋值时, 都会触发KVO, 系统会自动调用willChangeValueForKey:和didChangeValueForKey:两个方法
iOS底层-第六章 category
iOS底层-第七章 load & initialize
- Category中有load方法, load方法会在runtime加载类的时候调用
- 类的load方法调用早于Category中的load方法, 调用子类的load方法以前, 会先调用父类的load方法
- 没有关系的类会根据编译顺序调用load方法, Category会根据编译顺序调用load方法
- 全部的类和分类, load方法只会调用一次
- 当一个类在查找方法的时候, 会先判断当前类是否初始化, 若是没有初始化就会去掉用initialize方法
- 若是这个类的父类没有初始化, 就会先调用父类的initialize方法, 再调用本身的initialize方法
- 若是该类没有实现initialize方法,会执行父类的initialize方法。
- 类在调用initialize时, 使用的是objc_msgSend消息机制调用
- load和initialize的区别是什么?
- load & initialize 调用时机
- load 加载类、分类时调用
- initialize 类第一次接收到消息时调用
- load & initialize 调用方式
- load 根据函数地址直接调用
- initialize 经过objc_msgsend调用
- load & initialize 调用顺序
- load 父类load->子类load->分类load,先编译的类优先调用load
- initialize 父类init->子类init(子类未实现则调用其父类的方法,因此父类的initialize方法可能会调用屡次)
- 经过代码调用load会怎样?
- 会根据消息传递机制经过该实例的类对象查找load函数。可是通常状况下不会主动调用load方法, 都是让系统自动调用。
iOS底层-第八章 关联对象
- Category为何不能存放成员变量?
- 由于category_t的底层结构中,只有存放实例方法、类方法、协议、实例属性、类属性的list,没有存放成员变量的list。
- 如何在category中存放成员变量?
- 能够经过关联对象,关联对象并非存储在被关联的对象内存中,而是存储在全局的统一的一个AssociationsManager中。

iOS底层-第九章 block的底层结构
- block的三种类型?
- GlobalBlock
- 存在于内存的
数据区域
(.data区)
- 内部
没有使用auto类型变量
的block, 就是__NSGlobalBlock__类型
- __NSGlobalBlock__类型的block调用copy后类型不变, 仍是__NSGlobalBlock__类型(还在数据区)
- StackBlock
- 存在于内存的
栈区
- 内部
使用了auto类型变量
的block, 就是__NSStackBlock__类型
- MallocBlock
- 存在于内存的
堆区
- __NSStackBlock__类型的block
调用copy后
就是__NSMallocBlock__类型, 经过copy, 将block从栈区复制到了堆区
- __NSMallocBlock__类型的block调用copy后类型不变, 仍是__NSMallocBlock__类型(不会生成新的block, 原有
引用计数+1
)
- block对auto、static、全局变量捕获方式

全局静态变量
和全局变量
的区别, 全局静态变量
是有做用域的, 只能够被声明的.h .m / .c 中访问到, 全局变量
是没有做用域的,不管在任何地方,引入一下 Test.h,即可以得到并使用全局变量。
内存分布了解一下
-
栈区
:内存管理由系统控制,存储的为非静态的局部变量,例如:函数参数,在函数中生命的对象的指针等。当系统的栈区大小不够分配时,系统会提示栈溢出。算法
-
堆区
:内存管理由程序控制,存储的为malloc , new ,alloc出来的对象。数据库
- 若是程序没有控制释放,那么在程序结束时,由系统释放。但在程序运行过程当中,会出现内存泄露、内存溢出问题。分配方式相似于链表
-
全局存储区(静态存储区)
:全局变量、静态变量会存储在此区域。事实上全局变量也是静态的,所以,也叫全局静态存储区。编程
- 存储方式: 初始化的全局变量跟静态变量放在一片区域,未初始化的全局变量与静态变量放在相邻的另外一片区域。
- 程序结束后由系统释放。
-
文字常量区
:在程序中使用的常量存储在此区域。程序结束后,由系统释放。在程序中使用的常量,都会到文字常量区获取。
-
程序代码区
:存放函数体的二进制代码。
-
ARC环境下, block的类型问题
- __NSStackBlock__类型的block作为
函数返回值
时, 会将返回的block复制到堆区
- 将__NSStackBlock__类型的block赋值给
__strong指针
时, 会将block复制到堆区
- 若是
没有__strong指针
引用__NSStackBlock__类型的block, 那么block的类型依然是__NSStackBlock__
- block做为
Cocoa API
中方法名含有usingBlock的方法参数时, block在堆区
- block做为
GCD API
的方法参数时, block在堆区
-
对象类型的auto变量捕获
- 栈中的block不会将对象类型的auto变量进行retain处理, 只有在将block复制到堆上时, 才会将对象类型的auto变量进行retain处理(引用计数+1)
- 当堆中的block释放时, 会对其中的对象类型的auto变量进行release处理(引用计数-1), 若是此时对象类型的auto变量的引用计数为零, 就会被释放
- 当block被复制到堆上时,会调用__main_block_copy_0函数, 来对捕获的对象类型的auto变量进行强引用
- 当block从堆上移除时, 又会被调用__main_block_dispose_0函数, 对捕获的对象类型的auto变量解除强
-
__weak修饰符
- 当block捕获到的对象类型的auto变量被__weak修饰时, 即使block被复制到了堆上, __main_block_copy_0方法也
不会对被捕获的对象类型的auto变量进行强引用
iOS底层-第十章 __block和block内存管理
iOS底层-第十一章 isa详解
- isa指针的变化
- 在arm64架构以前,isa就是一个普通的指针,存储着Class、Meta-Class对象的内存地址
- 从arm64架构开始,对isa进行了优化,变成了一个共用体(union)结构,还使用位域来存储更多的信息

- bits占用一个字节,而结构体使用了位域, 也只占一个字节, 因此共用体只占用一个字节
- 什么是联合体,位域?
- isa位域解释
iOS底层-第十二章 Class结构
- class的底层结构
- 一开始class_data_bits_t bits;指向ro, 在加载的过程当中建立了rw, 此时的指向顺序是bits->rw->ro

- class_rw_t结构
- class_rw_t里面的methods、properties、protocols是二维数组,是可读可写的,包含了类的初始内容、分类的内容

- method_t结构
- method_t是对方法\函数的封装

- cache_t结构
- Class内部结构中有个方法缓存(cache_t),用散列表(哈希表)来缓存曾经调用过的方法,能够提升方法的查找速度


- 方法查找的逻辑
- 经过isa找到class
- 先会先从cache中查找, 若是有方法, 直接调用
- 若是没有方法, 在自身的class->bits->rw中查找方法
- 若是找到方法直接调动,并将方法缓存到cache中
- 若是没有找到, 会经过superclass找到父类, 从父类->bits->rw中查找方法
- 若是在父类中找到方法, 就直接调用, 同时将方法存到本身(不是父类的)的cache中, 若是一直找不到, 就进入下一个阶段
- 散列表的存储方法
- 一开始, 系统会分配给cache_t->_buckets一段内存, 假设第一次分配了足够存储3个方法的内存

- 此时cache_t的mask等于2, 即_buckets的长度 - 1
- 当存储方法时, 会用SEL & mask, 获取到一个数字, 用这个数字作为索引, 将该方法存储到_buckets中
- 当一个Student实例调用learning方法时, 就会用@selector(learning) & _buckets.mask来获取存储的索引,而后将learning方法存放到Student类对象的cache中
- 假设获取到的索引值为0, 那么散列表的结构相似下图

- 若是Student实例调用父类Person的eat方法时, 根据@selector(eat) & _buckets.mask的结果将becket_t插入到相应位置
- 假设@selector(eat) & _buckets.mask结果是2, 那么散列表结构相似下图

- 即便eat是Person中的方法, 可是Student调用,也会存到Student的cache中
- 当多个数都&一个固定值时, 那么确定就有重复的可能出现
- 此时,存储方法的索引就会-1, 而后在查找-1后所在位置是否空缺, 若是有空缺就会存储
- 若是Student实例调用exercises方法, 会计算@selector(exercises) & _buckets.mask的结果做为索引值
- 假设@selector(exercises) & _buckets.mask的结果是2, 此时由于索引2的位置已经存储eat方法, 因此索引会-1变成1, 而后查看1的位置是否空缺, 若是空缺就会存储, 以下图

- 若是Student实例继续调用dancing方法, 此时cache->buckets已经存储满
- 那么buckets的容量会扩大一倍, 容量变为6,从新计算cache->mask的值为5, 接着清空buckets中以前缓存的全部方法
- 而后计算@selector(dancing) & _buckets.mask的值, 若是此时的结果是3, 那么散列表的结构相似下图

- 由于已经清空过cache中的全部方法, 因此此时只存储dancing方法
iOS底层-第十三章 消息发送
- objc_msgSend
- OC中调用方法, 会使用objc_msgSend函数给消息接收者发送消息,因此OC调用方法的过程也被称为消息机制
- 消息发送流程
- 当消息接收者为空时, 直接返回, 结束objc_msgSend函数的调用
- 当消息接收者有值时, 查看缓存
- 若是方法没有被缓存过, 就会查询方法列表
- 当缓存中没有找到须要调用的方法时, 就会在方法列表中查找, 若是找到就会存到缓存cache中
- 方法存在cls->rw->methods中, 而methods是个二位数组, 因此须要进行遍历查询, 这里先拿到一维数组调用search_method_list函数查询
- 在一维数组中查找方法, 这里有两种状况
- 第一种: 方法列表已经排好序, 会经过findMethodInSortedMethodList函数查找,findMethodInSortedMethodList函数使用的是二分查找的方式查询方法
- 第二种: 方法列表没有排好序, 会一个一个遍历查找
- 当找到方法后, 会先将方法存储到cache中
- 若是在本身的类对象中没有找到须要调用的方法, 就会去查找父类中是否有该方法
- 1.查找时会一层一层遍历全部父类, 只要某个父类中找到方法, 就会结束查找
- 2.先从父类的缓存中找, 若是找到, 会先存到本身的cache中
- 3.若是父类的缓存中没有该方法, 就会从父类的方法列表中查找, 若是找到就会存入到本身的cache中, 并不会存入到父类的cache中
- 4.若是没找到, 就会经过for循环查看父类的父类中有没有方法, 依次类推, 只要找到就会结束查询, 并存到本身的cache中
- 若是最后仍是没找到, 就会进入下一个阶段, 动态解析阶段

iOS底层-第十四章 动态方法解析
- 动态方法解析流程
- 当消息发送过程当中,没有找到要调用的方法时, 就会进入动态方法解析阶段,
- 在动态方法解析过程当中, 会根据类对象和元类对象进行判断, 分别处理
- 类对象调用resolveInstanceMethod:方法
- 元类对象调用resolveClassMethod:方法
- 咱们能够在+ (BOOL)resolveInstanceMethod:(SEL)sel方法中, 使用Runtime添加其余方法的实现


iOS底层-第十五章 消息转发
- 消息转发流程
- 当动态方法解析也没有找到须要调用的方法实现时, 就会进入消息转发阶段。调用的是forwardingTargetForSelector:方法
- 能够在+forwardingTargetForSelector:方法中设置消息转发的对象
- 由于objc_msgSend的原理是给 消息接收者 发送 一条消息, 而这个消息是SEL类型的,而且不分是类方法(+)仍是对象方法(-)
- 因此, 咱们能够将消息接收者设置为实例对象
- 若是不实现+forwardingTargetForSelector:方法, 就会调用+methodSignatureForSelector:方法, 并调用+forwardInvocation:方法


iOS底层-第十六章 super
iOS底层-第十七章 Runloop基本认识
-
runloop与线程的关系
- 每条线程都有惟一的一个与之对应的RunLoop对象
- RunLoop保存在一个全局的Dictionary里, 线程做为key, RunLoop对象作为Value
- 线程刚建立时并无RunLoop对象, RunLoop会在第一次获取它时建立
- RunLoop会在线程结束时销毁
- 主线程的RunLoop已经自动获取(建立), 子线程默认没有开启RunLoop
-
runloop底层结构
typedef struct CF_BRIDGED_MUTABLE_TYPE(id) __CFRunLoop * CFRunLoopRef; struct __CFRunLoop {
pthread_t _pthread;
CFMutableSetRef _commonModes;
CFMutableSetRef _commonModeItems;
CFRunLoopModeRef _currentMode;
CFMutableSetRef _modes;
};
复制代码
- _modes中存放的是CFRunLoopModeRef类型数据, 其中就有_currentMode, 只不过_currentMode是当前使用的mode
- CFRunLoopModeRef 结构
typedef struct __CFRunLoopMode *CFRunLoopModeRef; struct __CFRunLoopMode {
CFStringRef _name;
CFMutableSetRef _sources0;
CFMutableSetRef _sources1;
CFMutableArrayRef _observers;
CFMutableArrayRef _timers;
};
复制代码
-
CFRunLoopModeRef
- CFRunLoopModeRef表明RunLoop的运行模式
- 一个RunLoop包含若干个Mode,每一个Mode又包含若干个Source0/Source1/Timer/Observer
- RunLoop启动时只能选择其中一个Mode,做为currentMode
- 若是须要切换Mode,只能退出当前Loop,再从新选择一个Mode进入
- 不一样组的Source0/Source1/Timer/Observer能分隔开来,互不影响
- 若是Mode里没有任何Source0/Source1/Timer/Observer,RunLoop会立马退出
- 常见的两种CFRunLoopModeRef
- kCFRunLoopDefaultMode(NSDefaultRunLoopMode): App的默认Mode,一般主线程是在这个Mode下运行
- UITrackingRunLoopMode: 界面跟踪 Mode,用于 ScrollView 追踪触摸滑动,保证界面滑动时不受其余 Mode 影响
-
CFRunLoopModeRef各属性做用
- Source0
- 触摸事件处理
- performSelector:onThread:
- Source1
- Timers
- NSTimer
- performSelector:withObject:afterDelay:
- Observers
- 用于监听RunLoop的状态
- UI刷新(BeforeWaiting)
- Autorelease pool(BeforeWaiting)
-
mode的切换
- 滚动以前, 先退出kCFRunLoopDefaultMode, 进入UITrackingRunLoopMode
- 等滚动结束, 会先退出UITrackingRunLoopMode, 进入kCFRunLoopDefaultMode
-
如何保活
- 每一条线程都有与之相对应的惟一一个RunLoop, 只有在主动获取RunLoop时才会建立(主线程中的RunLoop由系统自动建立)
- 固然, 咱们在使用RunLoop对线程进行保活的时候, 不能仅仅运行就好了, 由于RunLoop当前执行的_currentModel中若是没有Sources0, Sources1, Timers, Observers, 那么RunLoop会自动退出
- 因此咱们须要建立一个事件让RunLoop处理, 这样RunLoop才不会退出

-
运行流程
-
runloop休眠和工做的切换
-
RunLoop中的source0、source1
-
iOS Touch Event from the inside out(该文章下有一篇讲解mach机制的文章)
iOS底层-第十八章 多线程的安全隐患(锁)
-
iOS中有哪些锁
- OSSpinLock
- 自旋锁,等待锁的线程会处于忙等(busy-wait)状态,一直占用着CPU资源
- 目前已经再也不安全,可能会出现优先级反转问题
- 可能出现低优先级的线程先加锁,可是CPU更多的执行高优先级线程, 此时就会出现相似死锁的问题
- os_unfair_lock
- 互斥锁,用于取代不安全的OSSpinLock, 从iOS10开始才支持
- 线程会处于休眠状态, 并不是忙等
- pthread_mutex
- mutex叫作互斥锁,等待锁的线程会处于休眠状态
- 设置pthread初始化时的属性类型为PTHREAD_MUTEX_RECURSIVE, 这样pthread就是一把递归锁
- 递归锁容许同一线程内, 对同一把锁进行重复加锁
- dispatch_semaphore
- 信号量
- 使用dispatch_semaphore_t设置信号量为1, 来控制赞成之间只有一条线程能执行
- dispatch_queue(DISPATCH_QUEUE_SERIAL)
- NSLock
- 基于pthread封装的OC对象
- NSLock是基于pthread封装的normal锁
- NSRecursiveLock
- 递归锁
- 基于pthread封装的OC对象
- 基于pthread_mutex封装的递归锁
- NSCondition
- 基于pthread封装的OC对象
- 基于pthread_cond & pthread_mutex封装的条件锁
- NSConditionLock
- 基于pthread封装的OC对象
- NSConditionLock是对NSCondition的进一步封装
- @synchronized
- @synchronized是对mutex递归锁的封装
- @synchronized(obj)内部会生成obj对应的递归锁,而后进行加锁、解锁操做
- 底层是经过os_unfair_recursive_lock封装的锁
-
什么是优先级反转
- 假设经过OSSpinLock给两个线程
thread1
和thread2
加锁
- thread优先级高, thread2优先级低
- 若是thread2先加锁, 可是尚未解锁, 此时CPU切换到
thread1
- 由于
thread1
的优先级高, 因此CPU会更多的给thread1
分配资源, 这样每次thread1
中遇到OSSpinLock
都处于使用状态
- 此时
thread1
就会不停的检测OSSpinLock
是否解锁, 就会长时间的占用CPU
- 这样就会出现相似于死锁的问题
-
iOS锁的性能
- 性能从高到低排序
- os_unfair_lock
- OSSpinLock
- dispatch_semaphore
- pthread_mutex
- dispatch_queue(DISPATCH_QUEUE_SERIAL)
- NSLock
- NSCondition
- pthread_mutex(recursive)
- NSRecursiveLock
- NSConditionLock
- @synchronized
-
自旋锁、互斥锁比较
- 什么状况使用自旋锁比较划算?
- 预计线程等待锁的时间很短
- 加锁的代码(临界区)常常被调用,但竞争状况不多发生
- CPU资源不紧张
- 多核处理器
- 什么状况使用互斥锁比较划算?
- 预计线程等待锁的时间较长
- 单核处理器
- 临界区有IO操做
- 临界区代码复杂或者循环量大
- 临界区竞争很是激烈
iOS底层-第十九章 atomic
- 基础概念
- atomic用于保证属性setter、getter的原子性操做,至关于在getter和setter内部加了线程同步的锁
- 它并不能保证使用属性的过程是线程安全的
- atomic底层使用的是os_unfair_lock(性能最高)
iOS底层-第二十章 文件的读写安全
- 多读单写方案
- pthread_rwlock
- dispatch_barrier_async
- 这个函数传入的并发队列必须是本身经过dispatch_queue_cretate建立的
- 若是传入的是一个串行或是一个全局的并发队列,那这个函数便等同于dispatch_async函数的效果
- 为何读用dispatch_sync,写用dispatch_async

iOS底层-第二十一章 内存管理-定时器
- CADisplayLink
- NSTimer
- NSTimer依赖于RunLoop,若是RunLoop的任务过于繁重,可能会致使NSTimer不许时
- GCD定时器
- GCD定时器不依赖于RunLoop, 会更加的准时
iOS底层-第二十二章 Tagged Pointer
iOS - 老生常谈内存管理(五):Tagged Pointer
iOS 内存管理之 Tagged Pointer
聊聊伪指针 Tagged Pointer
- iOS程序的内存布局
- Tagged Pointer
- 从64bit开始,iOS引入了Tagged Pointer技术,用于优化NSNumber、NSDate、NSString等小对象的存储
- 在没有使用Tagged Pointer以前, NSNumber等对象须要动态分配内存、维护引用计数等,NSNumber指针存储的是堆中NSNumber对象的地址值
- 使用Tagged Pointer以后,NSNumber指针里面存储的数据变成了: Tag + Data,也就是将数据直接存储在了指针中
- 当指针不够存储数据时,才会使用动态分配内存的方式来存储数据
- objc_msgSend能识别Tagged Pointer,好比NSNumber的intValue方法,直接从指针提取数据,节省了之前的调用开销
iOS底层-第二十三章 OC对象的内存管理
iOS SideTable
iOS开发-weak引用以及sidetable表
- 引用计数
- 一个新建立的OC对象引用计数默认是1,当引用计数减为0,OC对象就会销毁,释放其占用的内存空间
- 调用retain会让OC对象的引用计数+1,调用release会让OC对象的引用计数-1
- 当调用alloc、new、copy、mutableCopy方法返回了一个对象,在不须要这个对象时,要调用release或者autorelease来释放它
- 想拥有某个对象,就让它的引用计数+1;不想再拥有某个对象,就让它的引用计数-1
- MRC下的setter方法
- (void)setName:(NSString *)name {
if (_name != name) {
[_name release];
_name = [name retain];
}
}
复制代码
- copy和mutableCopy
- 深拷贝: 产生一个新的副本, 与源对象相互独立
- 浅拷贝: 指针拷贝, 指向源对象
- 自定义对象的拷贝须要实现NSCopying协议
- 引用计数的存储
- 若是指针是Tagged Pointer, 那么直接返回, 不然进入下一步
- 判断isa是否优化过
- 若是优化过, 那么最后isa的最后19位存储的是引用计数
- 若是isa没有优化过, 那么就会进入sidetable_retainCount函数, 获取sidetable中的引用计数
- sidetable 使用的是spinLock_t
- 若是最后19位不足以存储, 那么多余的引用计数会存储到sidetable中, 同时将倒数第20位的值置为1, 就是has_sidetable_rc的值为1
- 若是has_sidetable_rc的值为1, 就会从sidetable_getExtraRC_nolock函数中取出sidetable中存储的引用计数
- dealloc函数原理
- isa是优化过的指针, 对象没有被弱引用, 没有关联对象, 没有c++析构函数, 没有将引用计数存到Sidetable中, 就会当即释放
- 不然调用object_dispose函数
- 进入object_dispose函数, 能够看到调用了objc_destructInstance函数
- 进入objc_destructInstance函数, 能够看到对objc的处理, 是在clearDeallocating函数中将弱指针置为nil的
- 进入clearDeallocating函数, 又能够看到两种状况
- 对象的isa没有优化过
- 当isa没有被优化过, 进入sidetable_clearDeallocating函数, 能够看到weak引用是存放到SideTable中的
- 存放在了SideTable的weak_table_t中
- 查看weak_table_t, 即weak会被存放到一个全局的散列表中
- 会经过weak_clear_no_lock函数, 对弱指针置为nil, 同时移除删列表中的weak记录
- 和优化过, 而且被弱指针引用 或者 将引用计数存放到了Sidetable中
- 若是isa被优化过, 而且对象被弱引用或者将引用计数存到Sidetable中, 就会调用clearDeallocating_slow函数
- 进入clearDeallocating_slow函数, 能够看到在函数中, 调用了weak_clear_no_lock函数, 并清空了引用计数
retainCount方法
- 在arm64以前,isa不是nonpointer。对象的引用计数全都存储在SideTable中,retainCount方法返回的是对象自己的引用计数值 1,加上SideTable中存储的值;
- 从arm64开始,isa是nonpointer。对象的引用计数先存储到它的isa中的extra_rc中,若是 19 位的extra_rc不够存储,那么溢出的部分再存储到SideTable中,retainCount方法返回的是对象自己的引用计数值 1,加上isa中的extra_rc存储的值,加上SideTable中存储的值。
- 因此,其实咱们经过retainCount方法打印alloc建立的对象的引用计数为 1,这是retainCount方法的功劳,alloc方法并无设置对象的引用计数。
retain方法
- 若是isa不是nonpointer,那么就对Sidetable中的引用计数进行 +1;
- 若是isa是nonpointer,就将isa中的extra_rc存储的引用计数进行 +1,若是溢出,就将extra_rc中RC_HALF(extra_rc满值的一半)个引用计数转移到sidetable中存储。
- 从rootRetain函数中咱们能够看到,若是extra_rc溢出,设置它的值为RC_HALF,这时候又对sidetable中的refcnt增长引用计数RC_HALF。extra_rc是19位,而RC_HALF宏是(1ULL<<18),实际上相等于进行了 +1 操做。
release方法
- 若是isa不是nonpointer,那么就对Sidetable中的引用计数进行 -1,若是引用计数 =0,就dealloc对象;
- 若是isa是nonpointer,就将isa中的extra_rc存储的引用计数进行 -1。若是下溢,即extra_rc中的引用计数已经为 0,判断has_sidetable_rc是否为true便是否有使用Sidetable存储。若是有的话就申请从Sidetable中申请RC_HALF个引用计数转移到extra_rc中存储,若是不足RC_HALF就有多少申请多少,而后将Sidetable中的引用计数值减去RC_HALF(或是小于RC_HALF的实际值),将实际申请到的引用计数值 -1 后存储到extra_rc中。若是extra_rc中引用计数为 0 且has_sidetable_rc为false或者Sidetable中的引用计数也为 0 了,那就dealloc对象。
- 为何须要这么作呢?直接先从Sidetable中对引用计数进行 -1 操做不行吗?我想应该是为了性能吧,毕竟访问对象的isa更快。
dealloc方法
- ① 判断 5 个条件(1.isa为nonpointer;2.没有弱引用;3.没有关联对象;4.没有C++的析构函数;5.没有额外采用SideTabel进行引用计数存储),若是这 5 个条件都成立,直接调用free函数销毁对象,不然调用object_dispose作一些释放对象前的处理;
- ②
- 1.若是有C++的析构函数,调用object_cxxDestruct;
- 2.若是有关联对象,调用_object_remove_assocations函数,移除关联对象;
* 3.调用weak_clear_no_lock将指向该对象的弱引用指针置为nil; * 4.调用table.refcnts.erase从引用计数表中擦除该对象的引用计数(若是isa为nonpointer,还要先判断isa.has_sidetable_rc)
-
③ 调用free函数销毁对象。
-
根据dealloc过程,__weak修饰符的变量在对象被dealloc时,会将该__weak置为nil。可见,若是大量使用__weak变量的话,则会消耗相应的 CPU 资源,因此建议只在须要避免循环引用的时候使用__weak修饰符。
-
在《iOS - 老生常谈内存管理(三):ARC 面世 —— 全部权修饰符》章节中提到,__weak对性能会有必定的消耗,当一个对象dealloc时,须要遍历对象的weak表,把表里的全部weak指针变量值置为nil,指向对象的weak指针越多,性能消耗就越多。因此__unsafe_unretained比__weak快。当明确知道对象的生命周期时,选择__unsafe_unretained会有一些性能提高。
清除weak
- 当一个对象被销毁时,在dealloc方法内部通过一系列的函数调用栈,经过两次哈希查找,第一次根据对象的地址找到它所在的Sidetable,第二次根据对象的地址在Sidetable的weak_table中找到它的弱引用表。弱引用表中存储的是对象的地址(做为key)和weak指针地址的数组(做为value)的映射。weak_clear_no_lock函数中遍历弱引用数组,将指向对象的地址的weak变量全都置为nil。
添加weak
- 一个被标记为__weak的指针,在通过编译以后会调用objc_initWeak函数,objc_initWeak函数中初始化weak变量后调用storeWeak。添加weak的过程以下:通过一系列的函数调用栈,最终在weak_register_no_lock()函数当中,进行弱引用变量的添加,具体添加的位置是经过哈希算法来查找的。若是对应位置已经存在当前对象的弱引用表(数组),那就把弱引用变量添加进去;若是不存在的话,就建立一个弱引用表,而后将弱引用变量添加进去。
iOS底层-第二十四章 @autoreleasepool
-
autoreleasepool的结构 
- 每一个AutoreleasePoolPage对象占用4096字节内存,除了用来存放它内部的成员变量,剩下的空间用来存放autorelease对象的地址

- AutoreleasePoolPage中除了一开始的56个字节用来存储成员变量, 其余的全部内存空间都是用来存储被autorelease对象的地址
- 当一个AutoreleasePoolPage不够存储autorelease对象地址时, 就会在建立一个AutoreleasePoolPage
- 全部的AutoreleasePoolPage对象经过双向链表的形式链接在一块儿

-
autoreleasepool的运行机制
- 调用push方法会将一个POOL_BOUNDARY入栈,而且返回其存放的内存地址
- 调用pop方法时传入一个POOL_BOUNDARY的内存地址,会从最后一个入栈的对象开始发送release消息,直到遇到这个POOL_BOUNDARY
- id *next指向了下一个能存放autorelease对象地址的区域
-
autoreleasepool的嵌套
-
Runloop和Autorelease
- iOS在主线程的Runloop中注册了2个Observer
- 第1个Observer
- 监听了kCFRunLoopEntry事件,会调用objc_autoreleasePoolPush()
- 第2个Observer
- 监听了kCFRunLoopBeforeWaiting事件,会调用objc_autoreleasePoolPop()、objc_autoreleasePoolPush()
- 监听了kCFRunLoopBeforeExit事件,会调用objc_autoreleasePoolPop()
高手课 - 02 App启动速度怎么作优化与监控?
虚拟内存的那点事儿
- 动态库共享缓存(dyld shared cache)

- 动态库的加载
- 在iOS中是使用了dyld来加载动态库
- dyld(dynamic loader),动态加载器
- mach-o
- 是mach object的缩写,是iOS上用于存储程序、库的标准格式。
- 常见的mach-o文件
- mh_object
- 目标文件(.o):可执行文件和代码之间的中间产物。
- 静态库文件(.a):静态库其实就是N个.o合并在一块儿。
- mh_execute
- mh_dylib
- 动态库文件
- .dylib
- .framework/xx
- mh_dsym
- mh_dylikner
- Universal Binary
- 通用二进制文件
- 包含了多种不一样架构的独立二进制文件
- 能够经过命令瘦身
- mach-o的基础结构
- Header
- Load commands
- Rwa segment data
- 在Load commands中定义的Segment的原始数据
- dyld和Mach-O的关系
- dyld负责加载mh_execute、mh_dylib、mh_bundle类型的mach-o文件。
小码哥底层 app的启动
深刻理解iOS App的启动过程
抖音品质建设 - iOS启动优化《原理篇》
iOS 启动优化 + 监控实践
iOS App启动优化(一):检测启动时间
iOS 优化篇 - 启动优化之Clang插桩实现二进制重排
高手课 - 03 AutoLayout是怎么进行自动布局的
深刻理解 Autolayout 与列表性能 -- 背锅的 Cassowary 和偷懒的 CPU
高手课 - 05 符号是怎么绑定到地址上的
-
LLVM 编译过程
- 预处理
- 词法分析
- 语法分析
- 生成 AST
- 由于 Swift 在编译时就完成了方法绑定直接经过地址调用属于强类型语言,方法调用再也不是像 Objective-C 那样的消息转发,这样编译就能够得到更多的信息用在后面的后端优化上。所以咱们能够在 SIL 上对 Swift 作针对性的优化,而这些优化是 LLVM IR 所没法实现的。
- 强类型和弱类型的语言有什么区别
- 静态分析
- 生成 LLVM IR
- 编译器优化
- Bitcode (可选)
- 生成汇编
- 生成目标文件
- 连接器作符号和地址绑定
- 连接器还要把项目中的多个 Mach-O 文件合并成一个
- 生成可执行文件
-
源码到可执行文件流程
- 编译器Clang会将源码XXX.m编译为目标文件XXX.o
- 连接器会将目标文件连接打包进最终的可执行文件Mach-O中
- 点击App ICON时,动态连接器dyld会加载可执行文件以及依赖的动态库,并最终执行到main.m里,至此App启动完成

-
iOS 编译知识小结
-
iOS底层学习 - 从编译到启动的奇幻旅程(一)
高手课 - 06 App如何经过注入动态库的方式实现极速编译调试
- 建立监听SimpleSocket,经过File Watcher监听观察文件改动
- 修改代码,保存后从新编译修改的类文件,修改后的文件被编译为了.dylib动态库
- 而后经过writestring给咱们的App发"INJECT"消息,通知App更新代码
- 经过SwiftEval.instance.loadAndInject方法dlopen加载.dylib动态库
- 而后经过OC runtime 的class_replaceMethod把整个类的实现方法都替换
- 而后再调SwiftInjected.injected咱们的类收到消息开始重绘UI
高手课 - 07 咱们应该使用谁来作静态分析?
- OCLint/SwiftLint
- Infer
- Clang 静态分析器
高手课 - 08 如何利用Clang为App提质?
iOS 查漏补缺 - LLVM & Clang
高手课 - 09 无侵入的埋点方案如何实现
静下心来读源码之Aspects iOS AOP 框架 - Aspects 源码解读
高手课 - 10 包大小:如何从资源和代码层面实现全方位瘦身?
- 官方 App Thinning
- 无用图片资源
- 图片资源压缩
- 经过 AppCode 找出无用代码
高手课 - 12 iOS 崩溃千奇百怪,如何全面监控?
iOS Crash防御
iOS中常见Crash总结
- 可捕获崩溃
- KVO 问题、NSNotification 线程问题、数组越界、野指针等崩溃信息
- EXC_BAD_ACCESS 这个异常会经过 SIGSEGV 信号发现有问题的线程。虽然信号的种类有不少,可是均可以经过注册 signalHandler 来捕获到。
- oc本身也有定义一些并没深刻到内核的一些exception,这些是经过注册exceptionhandle来进行捕获的
- 对各类信号都进行了注册,捕获到异常信号后,在处理方法 handleSignalException 里经过 backtrace_symbols 方法就能获取到当前的堆栈信息。堆栈信息能够先保存在本地,下次启动时再上传到崩溃监控服务器就能够了。
- 不可捕获崩溃
- 后台任务超时、内存被打爆、主线程卡顿超阈值等信息
- 设置阀值,保存堆栈信息。
- 僵尸对象原理
- 开启Zombie Objects后,dealloc将会被hook,被hook后执行dealloc,内存并不会真正释放,系统会修改对象的isa指针,指向_NSZombie_前缀名称的僵尸类,将该对象变为僵尸对象。
- 僵尸类作的事情比较单一,就是响应全部的方法:抛出异常,打印一条包含消息内容及其接收者的消息,而后终止程序。
Crash
Unrecoginzed Selector Crash
- hook NSObject的 -(id)forwardingTargetForSelector:(SEL)aSelector 方法启动消息转发
KVO Crash
- 找一个 Proxy 用来作转发, 真正的观察者是 Proxy,被观察者出现了通知信息,由 Proxy 作分发。因此 Proxy 里面要保存一个数据结构 {keypath : [observer1, observer2,...]} 。
- Hook NSObject的KVO相关方法
-
- (void)addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(void *)context;
-
- (void)removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath;
- hook dealloc在dealloc中遍历删除proxy中的数对象。
数组 Crash
- hook 常见的方法加入检测功能而且捕获堆栈信息上报。
- 不过要注意容器类是类簇,直接hook容器类是没法成功的,须要hook对外隐藏的实际起做用的类,好比NSMutableArray的实际类名为__NSArrayM。
NSNotification Crash
- 相似 KVO 中间加上 Proxy 层,使用 weak 指针来持有对象
- 在 dealloc 的时候将未被移除的观察者移除
NSNull Crash
- 一旦对 NSNull 这个类型调用任何方法都会出现 unrecongized selector 错误
野指针 Crash
- 借用系统的NSZombies对象的设计
- 创建白名单机制,因为系统的类基本不会出现野指针,并且 hook 全部的类开销较大。因此咱们只过滤开发者自定义的类。
- hook dealloc 方法 这些须要保护的类咱们并不让其释放,而是调用
objc_desctructInstance
方法释放实例内部所持有属性的引用和关联对象。
- 利用 object_setClass(id,Class) 修改 isa 指针将其指向一个Proxy 对象(类比�系统的 KVO 实现),此 Proxy 实现了一个和前面所说的智能转发类同样的 return 0的函数。
- 在 Proxy 对象内的 - (void)forwardInvocation:(NSInvocation *)anInvocation 中收集 Crash 信息。
- 缓存的对象是有成本的,咱们在缓存对象到达必定数量时候将其释放(object_dispose)。
- 建议使用的时候若是近期没有野指针的Crash能够没必要开启,若是野指针类型的Crash忽然增多,能够考虑在 hot Patch 中开启野指针防御,待收取异常信息以后,再关闭此开关。
高手课 - 13 如何利用RunLoop原理去监控卡顿
天罗地网? iOS卡顿监控实战(开源)
iOS卡顿监测方案总结
使用RunLoop检测卡顿
- 一旦发现进入睡眠前的 kCFRunLoopBeforeSources 状态,或者唤醒后的状态 kCFRunLoopAfterWaiting,在设置的时间阈值内一直没有变化,便可断定为卡顿。接下来,咱们就能够 dump 出堆栈的信息,从而进一步分析出具体是哪一个方法的执行时间过长。
高手课 - 14 临近 OOM,如何获取详细内存分配信息,分析内存问题?
iOS 性能优化实践:头条抖音如何实现 OOM 崩溃率降低50%+
如何断定发生了OOM
分析字节跳动解决OOM的在线Memory Graph技术实现
高手课 - 15 日志监控:怎样获取 App 中的全量日志?
CocoaLumberjack源码
高手课 - 17 临近 OOM,如何获取详细内存分配信息,分析内存问题?
这个问题的根源在于 AFNetworking 2.0 使用的是 NSURLConnection,而 NSURLConnection 的设计上存在些缺陷。NSURLConnection 发起请求后,所在的线程须要一直存活,以等待接收 NSURLConnectionDelegate 回调方法。可是,网络返回的时间不肯定,因此这个线程就须要一直常驻在内存中。AFNetworking 在 3.0 版本时,使用苹果公司新推出的 NSURLSession 替换了 NSURLConnection,从而避免了常驻线程这个坑。NSURLSession 能够指定回调 NSOperationQueue,这样请求就不须要让线程一直常驻在内存里去等待回调了。
- 相似数据库这种须要频繁读写磁盘操做的任务,尽可能使用串行队列来管理,避免由于多线程并发而出现内存问题
新浪 - 1 UI视图相关面试问题
iOS触摸事件全家桶

- 事件响应
- 若是响应视图没法处理响应事件,则响应事件会经过响应链传递给父视图尝试处理,直到传递给UIApplication。
- 图像显示原理
- CPU和GPU是经过事件总线连接在一块儿的。
- CPU输出的位图,在适当时机由事件总线上传给GPU。
- GPU会对位图进行渲染,而后将结果放入帧缓冲区。
- 视频控制器经过Vsync信号,在指定时间(16.7ms)以前,从帧缓冲区中提取屏幕显示内容,而后显示在显示器上。

- 卡顿&掉帧的缘由
- 若是CPU和GPU的工做时长超过16.7ms,那么当Vsync信号来临时,没法提供这一帧的画面,就会出现掉帧现象。
- 绘制原理

- 异步绘制

新浪 - 2 Objective-C语言特性相关面试问题
- 分类和扩展的区别
- 如何手动实现Notification

- MRC手动修饰变量的setter

加固
- 加壳
- 利用特殊的算法,对可执行文件的编码进行改变(好比压缩,加密),以达到保护程序代码的目的。

- 脱壳

新浪 - 4 内存管理相关面试问题


- sideTable算法
- 经过对象地址 与Hash表的count取模,获取目标值下标索引。
- 弱引用表结构



新浪 - 6 多线程相关面试
新浪 - 8 网络相关面试问题
HTTP 响应代码
- GET和POST的区别?
- get请求参数以?分隔拼接到URL后面,post请求参数在Body内部。
- get参数长度显示2048个字符,post通常没有该限制。
- get请求不安全,post请求比较安全。
- 链接创建流程

-
HTTP的特色
- 无链接
- HTTP持久链接方案可解决该问题
- 开启持久链接须要设置的头部字段
- Connection: keep-alive 须要开启持久链接
- time: 20 持续时间
- max: 10 持久链接最多可发起的网络请求次数
- 无状态
-
Cookie
- 状态保存在客户端
- 客户端发送的cookie在http请求报文的Cookie首部字段中。
- 服务器端设置http响应报文的Set-Cookie首部字段。
-
Session
- 状态存放在服务器端
- Seesion须要依赖于Cookie机制实现。
-
怎样判断一个请求是否结束?
- Content-length: 1024根据所接受数据是否达到Content-length来判断。
- chunkedpost请求会有屡次返回,最后一次返回会有一个空的chunked。
-
Charles抓包原理
- 当客户端和服务器创建链接时,Charles会拦截到服务器返回的证书(服务器公钥)
- 而后动态生成一张伪造证书(Charles公钥/假公钥)发送给客户端
- 客户端收到Charles证书后,进行验证;由于以前咱们手机设置了信任,因此验证经过;(只要手机不信任这种证书,HTTPS仍是能确保安全的)
- 客户端生成会话密钥,使用Charles证书对会话密钥进行加密再传输给服务器
- Charles拦截到客户端传输的数据,使用本身的Charles私钥进行解密获得会话密钥
- 链接成功后,客户端和服务器通讯,客户端对传输的数据使用会话密钥加密并使用公钥对数据摘要进行数字签名,一同传输给服务器;
- Charles拦截到通讯的数据,使用以前得到的会话密钥解密就能获得原始数据;
- Charles一样也能篡改通讯的数据:将篡改后的数据从新加密并从新生成摘要并使用以前得到的公钥进行数字签名,替换本来的签名,再传输给服务器;
- 服务器收取到数据,按正常流程解密验证;
- 服务器返回响应数据时,Charles也是相似拦截过程
-
HTTPS连接创建流程是怎样的? 
- 客户端向服务器发送一段报文,报文内容包括三部分,客户端支持的TLS协议版本,客户端支持的加密算法,一段随机数C。
- 服务器返回客户端一段握手的报文消息,内容也包括三部分,服务器从客户端上报的多种加密算法中选择的加密算法,一段随机数S,服务器证书。
- 客户端对服务器返回的证书进行验证,判断服务器是不是合法的服务器,即对服务器公钥进行验证。
- 客户端经过预主密钥、一段随机数C、一段随机数S,组装会话密钥。
- 客户端经过服务器的公钥对预主密钥进行加密传输。
- 服务器经过私钥解密获得预主密钥。
- 服务器经过预主密钥、一段随机数C、一段随机数S,组装会话密钥。
- 客户端和服务器相互发送加密的握手消息,验证握手是否完成。

-
UDP

-
TCP如何作到可靠传输 
-
滑动窗口协议 
- 接收方有接收缓存,若是发送速度过快,可能形成溢出。
- 接收方能够动态调整发送方的发送窗口,达到动态调整发送速率的目的。
- 发送窗口和接收窗口是两个字段,位于TCP报文的首部。
-
拥塞控制 
-
为何要进行三次握手而不是两次? 
-
DNS解析

新浪 - 9 设计模式、架构、三方库
设计模式
原型模式
工厂模式

- 工厂方法模式
- 在工厂方法模式中,咱们再也不提供一个统一的工厂类来建立全部的产品对象,而是针对不一样的产品提供不一样的工厂

- 抽象工厂模式
- 在工厂方法模式中一个具体工厂只生产一种具体产品, 可是有时候咱们须要一个工厂可以生产多个具体产品对象, 而不是一个单一的具体对象,这就引入了抽象工厂模式。

- 三种工厂方法关系
- 当抽象工厂模式中每个具体工厂类只建立一个产品对象,也就是只存在一个产品等级结构时,抽象工厂模式退化成工厂方法模式;
- 当工厂方法模式中抽象工厂与具体工厂合并,提供一个统一的工厂来建立产品对象,并将建立对象的工厂方法设计为静态方法时,工厂方法模式退化成简单工厂模式。
单例模式
适配器模式
- 将一个类接口转化为客户代码须要的另外一个接口。适配器使本来因为兼容性而不能协同工做的类能够工做在一块儿,消除了客户代码和目标对象的类之间的耦合性。
组合模式
- 组合模式为树形结构的面向对象提供了一种灵活的解决方案
- view的结构使用组合模式
装饰模式
- 用于替代继承的技术, 无需定义子类就能够给原来的类增长新的功能, 使用对象关联关系替代继承。
- 最终执行的是装饰器接入的基本组件中的函数。
- OC中是category
- swift中是extension
外观模式
- 一个客户类要和多个业务类交互, 而这些交互的业务类常常做为一个总体出现, 这个时候可使用外观模式, 为客户端提供一个简化的入口, 简化客户类和业务类的交互.
- 相似不少三方库中的manager,用于衔接其余工具类。

代理模式
责任链模式
命令模式
- NSInvocation
- NSInvocation类的实例用于封装Objective-C消息。一个调用对象中含有一个目标对象、一个方法选择器、以及方法参数。
- 您能够动态地改变调用对象中消息的目标及其参数,一旦消息被执行,您就能够从该对象获得返回值。经过一个调用对象能够屡次调用目标或参数不一样的消息。
- 建立NSInvocation对象须要使用NSMethodSignature对象,该对象负责封装与方法参数和返回值有关系的信息。NSMethodSignature对象的建立又须要用到一个方法选择器。
- Target&Action
- 当您用Interface Builder构建程序的用户界面时,能够对控件的动做和目标进行设置。您所以可让控件具备定制的行为,而又没必要为控件自己书写任何的代码。动做选择器和目标链接被归档在nib文件中,并在nib文件被解档时复活。您也能够经过向控件或它的单元对象发送setTarget:和setAction:消息来动态地改变目标和动做。
解释器模式
- 好比判断邮件地址、电话号码、证件号码是不是正确的正则表达式,就是应用了解释器模式。
迭代器模式
- 这种模式提供一种顺序访问聚合对象(也就是一个集合)中的元素,而又没必要暴露潜在表示的方法。迭代器模式将访问和遍历集合元素的责任从集合对象转移到迭代器对象。迭代器定义一个访问集合元素的接口,并对当前元素进行跟踪。不一样的迭代器能够执行不一样的遍历策略。
- Foundation框架中的NSEnumerator类实现了迭代器模式。
中介者模式
- MVC模式是中介者模式的一种表现形式,Comtroller(中介者)承担两个同事类(View和Modle)之间的中转和协调做用。
备忘录模式
- 这种模式在不破坏封装的状况下,捕捉和外部化对象的内部状态,使对象在以后能够回复到该状态。备忘录模式使关键对象的重要状态外部化,同时保持对象的内聚性。
- 经过NSCoder对象能够执行编解码操做,在编解码过程当中最好使用键化的归档技术(须要调用NSKeyedArchiver和NSKeyedUnarchiver类的方法)。被编解码的对象必须遵循NSCoding协议;该协议的方法在归档过程当中会被调用。
观察者模式
状态模式
- 同一操做在不一样状态,执行不一样的方案。
- 代码中包含大量与对象状态有关的条件语句: 一个操做中含有庞大的多分支的条件(if else(或switch case)语句,且这些分支依赖于该对象的状态。
- 这个状态一般用一个或多个枚举常量表示。一般, 有多个操做包含这一相同的条件结构。
- State模式将每个条件分支放入一个独立的类中。这使得你能够根据对象自身的状况将对象的状态做为一个对象,这一对象能够不依赖于其余对象而独立变化。
策略模式
iOS设计模式之策略模式
模版方法模式

不常使用
《Objective-C 高级编程》
《Objective-C 高级编程》干货三部曲(一):引用计数篇
《Objective-C 高级编程》干货三部曲(二):Blocks篇
《Objective-C 高级编程》干货三部曲(三):GCD篇
《Effective Objective-C》
《Effective Objective-C》干货三部曲(一):概念篇
《Effective Objective-C》干货三部曲(二):规范篇
《Effective Objective-C》干货三部曲(三):技巧篇
网络协议
小码哥《网络协议从入门到底层原理》笔记(1、二):基本概念、集线器、网桥、交换机、路由器
小码哥《网络协议从入门到底层原理》笔记(三):MAC地址、IP地址
小码哥《网络协议从入门到底层原理》笔记(四):路由、区域网、NAT
小码哥《网络协议从入门到底层原理》笔记(五):物理层、数据链路层
小码哥《网络协议从入门到底层原理》笔记(六):网络层
小码哥《网络协议从入门到底层原理》笔记(七):传输层、UDP、TCP可靠传输
小码哥《网络协议从入门到底层原理》笔记(八):TCP可靠传输、流量控制、拥塞控制
小码哥《网络协议从入门到底层原理》笔记(九):TCP序号、确认号、创建链接、释放链接
小码哥《网络协议从入门到底层原理》笔记(十):应用层、域名、DNS解析
小码哥《网络协议从入门到底层原理》笔记(十一):HTTP、报文、请求头、状态码、form
知识盲区面试题
- 为何不能在异步线程中更新页面,介绍缘由
- 网络优化、弱网优化方案
- 网络深度优化的点
- NSCache缓存、Last-Modified、ETag
- 失败重发、缓存请求有网发送
- DNS解析
- 数据压缩:protobuf,WebP
- 弱网:2G、3G、4G、wifi下设置不一样的超时时间
- TCP对头阻塞:GOOGLE提出QUIC协议,至关于在UDP协议之上再定义一套可靠传输协议
- 正常一条网络请求须要通过的流程是这样:
- DNS 解析,请求DNS服务器,获取域名对应的 IP 地址。
- 与服务端创建链接,包括 tcp 三次握手,安全协议同步流程。
- 链接创建完成,发送和接收数据,解码数据。
- 这里有明显的三个优化点:
- 直接使用 IP 地址,去除 DNS 解析步骤。
- 不要每次请求都从新创建链接,复用链接或一直使用同一条链接(长链接)。
- 压缩头部、压缩数据,减少传输的数据大小。
- 弱网
- 提高链接成功率
- 制定最合适的超时时间
- 调优TCP参数,使用TCP优化算法
- http2.0与http1.x的区别
- 静态库与动态库
- Cookie与Session的区别-总结很好的文章