内存管理与多线程

1.zone的作用是为了防止内存出现碎片化,p14
2.类的引用计数统一存在哈希表里,以对象内存为key, p19
3.自动释放池的实现原理:p26
pool内部维护一个数组,持有所有自动释放对象,当pool被drain时release所有对象
嵌套pool同栈,先进后出

4.Block原理
· block本质也是OC对象,转化为C语言代码,block会转化为结构体,结构体里包含block实现和block描述,block实现结构体里包含isa、block代码块的函数指针、flage等,block主体代码块转化为C语言函数,参数为block结构体,当执行block时会通过函数指针执行该C语言函数,并将结构体作为参数传进去。如果block里有使用外部参数,参数会以成员变量的形式被copy到结构体内部,执行函数时获取结构体的成员变量进行访问。
· block类型分三种:栈、堆、全局
· 由于block获取外面变量使用的是copy,所以内部无法修改外部变量(静态、全局变量除外),如果需要修改外部变量,需要添加__block修饰符,添加该修饰符时结构体内部不再是copy外部变量指针,而是将__block变量转化为结构体,保存结构体的指针,从而通过结构体里的value可以修改外部变量
· 被__block修饰的变量在C语言层面也是一个结构体实例,结构体里包含isa、forwarding、具体值等内容,其中forwarding指针是为了保证在栈和堆访问时能拿到同一对象,栈上指针指向堆上对象(有被复制到堆上),堆上对象指向自己。
· 当外部变量出了作用域,内部指针将变为野指针,所以需要将栈上的block及__block变量复制到堆上,用于实现当变量出了作用域还能正常访问;copy和dispose用于维护引用计数。

5.通过dispatch_queue_create创建的队列需要手动调用dispatch_release释放 p150
6.通过dispatch_async将block追加到队列中,block会持有队列知道block执行完毕 p150
7.dispatch_after不是在指定时间后执行代码,而是指定时间后将block加入到队列中,具体执行时机受队列状态影响
8.dispatch_group 在队列多个操作都执行完毕后执行某些操作
9.dispatch_barrier_async 等待队列中所有异步操作执行完毕后再执行
10.dispatch_sync会阻塞当前线程,dispatch_async不阻塞当前线程
11.dispatch_apply将某些操作重复的追加到队列中,并等待所有操作执行完毕后返回(阻塞当前线程)
12.信号量
dispatch_semaphore_create(1):创建初始值为1的信号量
dispatch_semaphore_wait():等待信号量的值大于等于1,等到后信号量减一并返回
dispathc_semaphore_signal():将信号量加1
13.dispatch_once 保证代码只执行一次
dispatch_queue是通过链表实现的FIFO队列,被加入队列的block封装在一个结构体里,结构体里包含block执行的上下文
dispatch_queue执行block的过程:
queue从链表里拿出要执行block的结构体,同自身信息及符合该优先级的workqueue当做参数传给pthread_workqueue_additem_up函数,该函数通知XNU内核的workqueue执行该项目,XNU内核会根据当前情况判断是否需要增加线程,然后执行该block,执行完block通知group等,然后执行回调通知queue执行下一个任务。

14.@syncthesize指定实例变量的名字
15.@dynamic让编译器不自动生成getter/setter方法
16.atomic原子性、nonatomic非原子性,原子性是保证该操作的完整性,无法保证线程安全,如果两个线程同时读写同一属性,能够保证读到完整的数据,但无法保证读到的是正确的数据。
17.内存管理语义
MRC
retain(引用加1,同strong)、assign(引用不加1,释放后不置为nil)、copy
ARC
strong、weak(引用不加1,释放后置为nil)、copy、assign(只修饰纯量类型)
18.关联对象
objc_setAssociatedObject()
objc_getAssociatedObject()
原理:
全局AssociationsManager维护一个hashmap,当给一个对象添加关联对象时,会以当前对象的地址为key(进行过位运算),value为当前对象所有关联对象的map,该map以关联对象的key为key,以关联对象的value及policy为value。
19.所有消息传递最终都转化为C语言函数调用,调用objc_msgSend,objc_msgSend会将匹配到的方法缓存在map里,每个类有一个缓存objc_cache,OC类的方法也后会转化为C语言函数,SEL为key,函数指针为value存在哈希表。
20.消息转发机制
1).调用resolveInstanceMethod:方法,在该方法可以动态添加方法
2).调用forwardingTargetForSeletor:,该方法返回消息接受者,可以将消息转发给其他对象
3).调用forwordInvecation:,NSInvocation将消息的接受者、SEL、参数等封装转发
21.OC只在极其罕见的情况下抛出异常,并在异常抛出后退出App
22.僵尸对象实现原理
创建一个新类,类名为_NSZombie_XXX,原理同KVO原理
23.GCD与NSOperation的区别
1).NSOperation可以取消队列里的任务,仅局限于未开始执行的任务
2).NSOperation面向对象,可以更方便控制一个任务及KVO观察任务的状态
3).NSOperation可以指定每个任务的优先级
24.NSCache与NSDictionary
NSCache在系统内存耗尽时会自动删减,NSCache不会Copy Key,而是持有Key,NSCache是线程安全
25.load方法不会在继承链上找,如果一个类不实现load方法,不会去执行父类的load方法,每个类在加载时都会调用load方法,但initialize方法只会在类第一次使用时才调用,不使用不会调用
26.id 就是实例对象的结构体指针
27.对象也是转化为结构体,里面包含成员变量isa。
28.KVO实现原理
当一个对象被监测,系统会创建该类的一个新子类,名为KVONotification_XXX,并改写该对象的isa指针改为新创建的类,并重写被监测属性的setter方法和class方法,当调用setter方法时通知observer
29.category
分类结构体里包含分类名、类、实例方法、类方法、协议、实例属性等,当分类被加载时会调用remethodizeClass重新整合类,将实例方法整合到类里,类方法整合到元类里,协议整合到类和元类里。
30.类和分类里都有load,会先调用所有类的load,再调用分类的load,initialize机制同普通方法
31.UIButton->UIControl->UIView->UIResponser
UIControl实现了touchesBegan系列方法,就算UIControl上没有添加手势也能响应事件,而UIView没有实现上述方法,必须添加手势才能响应事件
32.事件传递与响应流程
1).产生事件后系统会放在UIApplication的事件队列里,然后派发给UIWindow
2).UIWindow递归查询最合适的View,调用hitTest,判断交互是否开启、是否透明、是否隐藏以及事件是否在View内部(pointInSide),如果满足就遍历自己子视图,调用convertPoint后递归调用hitTest,返回最合适的View
3).找到最合适的View后自上而下查找能响应的响应者,先查看当前是否添加手势,有手势直接响应,没有添加手势判断是否有实现touchesBegan系列方法,有的话直接调用该系列方法。
4).查找响应者的顺序:子View->父View,View->ViewController
33.子线程需要启动runloop的情况:
1).使线程常驻,定期处理事情
2).在子线程中使用定时器
3).使用NSPort或者自定义输入源与其它线程通信
34.runloop
所有runloop存在全局的字典里,以线程为key,runloop为value,在第一次获取runloop时会创建;
runloop是内部处理事件循环的一个对象,通过状态切换来使App长时间运行(不要说运行循环,说是用户态到内核态,内核态到用户态的转换。)。
kCFRunLoopCommonModes是一种标记,包括kCFRunLoopDefaultMode和UITrackingRunLoopMode。定时器设置在这种mode,就可以在两种mode下都有效。
source0:需要手动唤醒线程,用于用户手动触发的事件
source1:基于port,可以主动唤醒线程,通过内核与其他线程进行通信
CommonMode模式,runloop在切换mode的同时,也把timer的事件也带走了,所以无论切到哪种mode下,timer的事件都是可以处理的。

35.GCD定时器为什么不需要runloop
GCD定时器使用的是dispatch_source_t实现的,用的GCD纯C语言代码,在实现中没有涉及到runloop,底层使用I/O多路复用的SELECT函数实现
dispatch_after只是封装调用了dispatch source定时器,然后在回调函数中执行定义的block。
36.临界区:一块对公共资源进行读写的代码块
37.锁
自旋锁:OSSpinLock,定义一个全局变量判断是否加锁,会出现忙等状态。
信号量:判断信号量是否大于1,不大于1的时候线程会睡眠,让出时间片。
互斥锁:pthread_mutex、NSLock、NSRecursiveLock(递归锁),内部都是用pthread_mutex_lock实现,只是类型不同
条件锁:NSConditionLock
@synchronized:内部实现递归锁,锁和对象存储在结构体里

  • NSMutableArray实现原理
    使用环形缓冲区,内部维护_offset,删除和移动元素时只移动数量较小那边的数据,不会整体移动

  • 设计模式

  • 渲染机制
    1.CPU对视图进行Frame布局、Layout布局,将布局后的数据及视图层级结构打包,通过进程间通讯(IPC)将数据发送给RenderService。
    2.RenderService将视图数据交给OpenGL,OpenGL将视图数据进行着色等处理,然后生成前后帧缓存,将渲染指令交给GPU。
    3.GPU根据渲染指令对数据进行渲染并合成,最终将渲染的数据存在缓存里,交给屏幕显示。

  • 离屏渲染
    圆角等属性无法和视图在同一缓存渲染,需要为这些属性创建新的缓存,渲染完后再讲多个缓存合成显示,导致多余的缓存区创建和上下文切换。
    解决方案:可通过CoreGraphics将效果生成位图,避免离屏渲染。

  • AOP无痕埋点

  • 启动优化
    pre-main:
    动态库、类数量、load方法
    didFinishLaunchingWithOptions:
    将业务分三级(启动主线程、启动子线程、完全显示后),非必须及时启动的业务挪到相应的时机启动
    首页渲染优化:
    减少首页VC、首页不用xib(去掉侧滑手势)、减少不必要的耗时(去掉首页截图方法)
    闪屏优化:
    使用GCD定时器、动画多进程
    首页业务优化:
    费时操作放在didAppear

  • 性能工具
    CPU
    线程是CPU调度和分配的基本单位,统计CPU使用率只需统计该进程下所有线程的使用率,Mach层提供了接口task_thread可以获取该进程下所有线程,同时Mach层还提供里线程信息的结构体,结构体里包含CPU使用率,通过获取所有线程的使用率统计出总得CPU使用率

内存使用量
Mach层task_info可以获取当前虚拟内存信息,获取内存信息结构体里包含当前App所占的物理内存

FPS(屏幕每秒传输帧数)
屏幕每次刷新都会通过CADisplayLink回调,监听CADisplayLink回调,统计一秒内屏幕的刷新次数(基于runloop,不一定准确)

卡顿检测
开辟一条子线程,专门检测主线程的runloop的before和waitting状态,当连续5次超过50ms认为卡顿,卡顿后可获取当前堆栈信息上报,可用信号量超时做定时器

获取堆栈信息
使用系统的backtrace API 或第三方框架

内存泄漏检测(MLeakFinder)
hook VC的didDisappear、popViewController等接口,当页面要消失时调用willDealloc,在willDealloc里添加计时器,使用week,2秒后week指针不为nil即可判定泄漏

  • 收集崩溃日志
    崩溃分为2种:NSException异常和Signal信号异常
    NSException异常为OC代码导致的异常,可通过注册Handler监听异常
    Signal异常是Mach层异常,可通过sigaction函数注册Handler
  • 组件化

  • 第三方库 (AFN、SD、YYModel、Aspects)

  • DNS

  • 包签名
    使用非对称双重加密,苹果服务器存在私钥,设备上存在公钥,Mac生成公钥私钥,公钥上传到后台,对公钥进行哈希生成摘要,用苹果服务器私钥对摘要加密,生成证书,将证书和udid、appid用苹果密钥加密,生成描述文件,将描述文件打包进App,App信息用本地私钥解密。
    验证:用设备公钥解密,验证设备udid和bundleid,用设备公钥解密获取开发者公钥,用开发者公钥解密,验证App签名是否正确。

  • WebView

  • MVC、MVVM、MVP
    [http://www.ruanyifeng.com/blog/2015/02/mvcmvp_mvvm.html]

KVO的实现方式

通过runtime创建一个受监听的子类,重写该子类的setter方法,然后修改受监听对象的isa指针,将对象的类改为新创建的类,当setter方法调用时通知监听者。

KVC的实现方式

KVC使用runtime去执行对应的getter、setter方法。

autoreleasepool的实现方法

autoreleasepool内部实现是一个结构体,当runloop即将进入时创建,当runloop即将进入休眠时释放并创建新的自动释放池。当结构体初始化时会去初始化一个autoleasepoolpage,然后压入哨兵对象,每个autoreleasepool包含多个page,各个page之间使用双链表的形式存储(每个page都有一个parent和child指针)。当将一个对象加入自动释放池时会将该对象加入到当前page的堆栈中,page空间满后会往下创建新的page。当自动释放池释放时会向page发送pop消息,然后page向page内的每一个对象发送release消息。

类的实现方式

类的本质也是一个对象,内部存储是一个结构体,内部存有isa指针,指向对象的类(即元类,元类的isa指向根元类,根元类isa指向自己)。superclass指针指向该对象的父类,NSObject的superclass指向nil,根元类的superclass指向NSObject。method_list存着改对象的方法列表,静态方法存着元类的method_list。ivas存着该对象的成员变量列表。

category的实现方式

category内部也是结构体,存有category_name和class_name及实例方法列表、类方法列表和协议列表,在类初始化时会加载category,将category里的内容添加到类里。

runloop

runloop本质也是一个结构体,内部存有commodModes、commondModeItems、currentMode、modes等;一个runloop内部包含多个runloopMode,每个runloopMode包含多个runloopModeItem(source、timer、observer)。runloop执行方法内部是一个do-while循环,直到手动停止或者超时退出。

runloop和线程一一对应,保存在一个以线程为key、runloop为value的字典里;

runloop并不会主动创建,在第一次获取时创建,在线程销毁时销毁。

事件传递和响应机制

1.由硬件接收事件,传给runloop,最后加到UIApplication的事件队列,然后传给keywindow。

2.keywindow->view->最适合的view(hitTest,pointInside)

3.最适合的view接收事件,如果能处理事件就处理,处理不了将事件沿响应链往上传。

性能检测工具

1.FPS 使用CADisplayLink 监听屏幕刷新率 监听最后一秒刷新了几次

2.内存使用量 设置定时器,每隔一秒去获取系统端口任务信息,可以获取当然进程占用的内存

3.CPU使用量 设置定时器,每隔一秒获取当前程序的线程,后期每个线程占用的CPU占用。

4.卡顿检测 在子线程创建一个runloop监听,去监听主线程的runloop状态,当主线程runloop响应时间超过2s或者2次1s即认为发生卡顿,上报收集平台

5.App启动时间 在main函数直接记录当前时间,当appdelegate执行完finish之后计算启动时间。

APP启动过程

1.加载系统库和二进制文件

2.加载程序的类(class load)

3.加载info.plist 显示启动页

4.main函数执行UIApplicationMain

创建UIApplication单例对象

设置UIAppdelegate

创建UIWindows

开始RunLoop

AFNetWorking中常驻线程的作用

线程保活,网络请求是在子线程的,子线程没有自动开始runloop,当任务执行结束后就销毁子线程;而网络请求是异步的,等请求回来后线程已经销毁,没有机会触发回调,所以需要开启runloop来使子线程长期不被销毁。

为什么说oc是动态语言

1.动态类型 一个对象的类型要在运行时才能确定,isa指针有可能被改

2.动态加载 一个对象的方法在运行时才能确定,可以动态的给一个对象添加方法

动态库与静态库

静态库:链接时会被完整的复制到可执行文件中,所以如果两个程序都用了某个静态库,那么每个二进制可执行文件里面其实都含有这份静态库的代码。

动态库: 链接时不复制,在程序启动后用动态加载,然后再决议符号,所以理论上动态库只用存在一份,好多个程序都可以动态链接到这个动态库上面,达到了节省内存(不是磁盘是内存中只有一份动态库),还有另外一个好处,由于动态库并不绑定到可执行程序上,所以我们想升级这个动态库就很容易,windows和linux上面一般插件和模块机制都是这样实现的。

对比一下静态和动态库的优缺点

库类型优点缺点

静态库

  1. 目标程序没有外部依赖,直接就可以运行。2. 效率教动态库高。

  2. 会使用目标程序的体积增大。

动态库

  1. 不需要拷贝到目标程序中,不会影响目标程序的体积。

  2. 同一份库可以被多个程序使用(因为这个原因,动态库也被称作共享库)。

  3. 编译时才载入的特性,也可以让我们随时对库进行替换,而不需要重新编译代码。实现动态更新

  4. 动态载入会带来一部分性能损失(可以忽略不计)

  5. 动态库也会使得程序依赖于外部环境。如果环境缺少动态库或者库的版本不正确,就会导致程序无法运行(Linux lib not found 错误)。

iOS平台上规定不允许存在动态库。由于iOS主App需要和Extension共享代码,Swift语言机制也需要动态库,于是苹果后来提出了Embedded Framework,这种动态库允许APP和APP Extension共享代码,但是这份动态库的生命被限定在一个APP进程内。系统的 Framework 不需要拷贝到目标程序中,我们自己做出来的 动态库(Embedded Framework) 哪怕是动态的,最后也还是要拷贝到 App 中(App 和 Extension 的 Bundle 是共享的)。所以苹果没有直接把这种Embedded Framework称作动态库而是叫Embedded Framework。

  • TCP为什么是面向连接的
    通过TCP连接传输报文,会保证报文正确的到达接收方。使用TCP连接之前需要三次握手确认连接,发送方和接收方通过一些状态量保证连接状态。

  • Mac地址
    Mac地址就像人的身份证,全球唯一,无法修改,但是可以造假,常说的修改Mac地址只是修改操作系统层存储的地址,网卡上的地址无法修改。

  • IP 地址
    IP 地址是一个网卡在网络世界的通讯地址,相当于我们现实世界的门牌号码

3.将子网掩码和 IP 地址按位计算 AND,就可得到网络号

  • MAC层主要是用来处理多路访问的拥堵问题

  • ARP协议通过广播来获取MAC地址,获取到后会缓存起来

  • 在 IP 头里面有个 8 位协议,这里会存放,数据里面到底是 TCP 还是 UDP

  • TCP如何保证数据的可靠传输

  1. 校验和
    通过将各个字节相加取反得到的校验和存入报文段里,可用于校验报文的bit是否错乱
  2. 序号
    通过对各个字节流编号,使用累积确认来保证数据的有序传输
  3. 确认号
    通过确认号确保数据被接收方正确接收
  4. 超时重传
    当TCP发出报文段时启动一个定时器,当超时后还未收到确认报文时重传报文段
  5. 流量控制
    通过滑动窗口调整当前可接收的最大数据
  6. 拥塞控制
    通过慢启动、拥塞避免、快速恢复控制拥塞
    判断当前是否拥塞:当超时或收到三个冗余ACK时认定发生拥塞
  • HTTPS工作流程
  1. 服务端生成一对非对称加密密钥,将公钥提交到CA结构生成CA证书
  2. 客户端发起连接请求,发送加密算法等及随机数。
  3. 服务器选择加密算法,并返回一个随机数,同时将CA证书发送给客户端。
  4. 客户端校验CA证书,拿到公钥。
  5. 客户端生成随机数,并用公钥加密,将加密的数据发给服务端。
  6. 服务端和客户端用三个随机数生成相同的对称密钥。
  7. 客户端和服务端通过该对称密钥进行加密,通过密文进行传输。
  • Charles抓包HTTPS的原理
    1.客户端向服务端请求证书时,Charles拦截服务端的证书,并拿出证书的公钥保存。
    2.Charles自己生成一个CA证书,里面包含自己的公钥,将该证书发给客户端。
    3.客户端信任该CA证书,拿出证书里的公钥,并将生成的对称密钥通过该公钥加密发送给服务端。
    4.Charles拦截请求,通过自己的私钥拿到对称密钥,并用服务端的公钥加密对称密钥,发送给服务端。
    5.客户端和服务端的交互Charles都可以通过对称密钥解密。

  • HTTP2.0和HTTP1.X相比的新特性
    1.HTTP2.0使用二进制格式,HTTP1.1使用文本格式,二进制格式更不易出错
    2.HTTP2.0压缩请求头,并且发送方和接收方各有请求头缓存
    3.HTTP2.0 可以主动推送资源给客户端
    4.HTTP2.0 使用多路复用,连接共享,让多个数据流共用一个连接

  • GCD实现原理
    C语言层面通过结构体和链表实现了FIFO的队列,当block被加入到队列时,block及block所属的group等上下文会被封装在dispatch_continuation_t结构体里,该结构体会加入到队列中,当执行该block时,从队列中取出block对应的结构体,同时通知内核执行,内核根据当前系统状态从线程池里取出执行的线程,线程池会维护所有线程,当没有使用时销毁,取出线程后在线程里执行block,执行完成后通知group及队列取出下个block执行

  • weak 实现原理
    runtime维护了一张全局的weak哈希表,weak表里以对象地址为key,引用该对象的地址数组为value。当一个对象声明为weak时,会调用store_weak,将该对象及weak对象加到哈希表里。当对象调用release时会判断引用计数是否为0,当引用计数为0时调用dealloc,dealloc最终后调用objc_clear_deallocting,该方法会从哈希表里获取引用该对象的所有对象,将这些对象地址设为nil,并将这条记录从哈希表里移除。

  • 死锁
    产生的条件:
    互斥:资源只能同时被一个进程使用
    不可剥夺:进程所获得的资源在未使用完毕之前,不能被其他进程强行夺走
    请求与保持:进程已经持有一个资源,但又提出新的资源请求,该请求被其他进程持有,此时请求进程被阻塞,但对自己已获得的资源保持不放
    循环与等待:进程间形成首尾相接的循环等待

预防:
破坏请求和保持条件:1.一次性的申请所有资源。之后不在申请资源,如果不满足资源条件则得不到资源分配。2.只获得初期资源运行,之后将运行完的资源释放,请求新的资源。
破坏不可抢占条件:当一个进程获得某种不可抢占资源,提出新的资源申请,若不能满足,则释放所有资源,以后需要,再次重新申请。
破坏循环等待条件:对资源进行排号,按照序号递增的顺序请求资源。若进程获得序号高的资源想要获取序号低的资源,就需要先释放序号高的资源。
死锁的解除办法:
1、抢占资源。从一个或多个进程中抢占足够数量的资源,分配给死锁进程,以解除死锁状态。
2、终止(撤销)进程:将一个或多个思索进程终止(撤销),直至打破循环环路,使系统从死锁状态解脱。

你可能感兴趣的:(内存管理与多线程)