分类
- 运行时决议
- 可以为系统类添加分类
- 分类添加的方法可以"覆盖"原类方法
- 同名分类方法谁能生效取决于编译顺序,最后编译的方法会被实现
- 名字相同的分类会引起编译报错
分类可以添加哪些内容
- 实例方法
- 类方法
- 协议
- 属性(get/set方法.没有添加实例变量)
扩展
一般扩展做什么
- 声明私有属性
- 声明私有方法
- 声明私有成员变量
特点
- 编译时决议
- 只以声明的形式存在,多数情况下寄生于宿主类的.m中
- 不能为系统类添加扩展
代理
- 是一种软件设计模式
- 传递方式一对一
- iOS当中以@protocol形式体现
一般声明为weak以规避循环引用
通知
- 是使用观察者模式来实现的用于跨层传递消息的机制
- 传递方式为一对多
UIView和CALayer
- UIView为其提供内容,以及负责处理触摸等事件,参与响应链
- CALayer负责显示内容contents
事件传递与视图响应链
事件传递
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event - (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event
UI绘制原理
CPU工作
- UI布局
- 文本计算
- 绘制
- 图片编解码
- 提交位图
GPU工作
- 顶点着色
- 图元装配
- 光栅化
- 片段着色
- 片段处理
KVO
- KVO是key-value observing的缩写
- KVO是Objective-C对观察者设计模式的又一实现
- Apple使用了isa混写(isa-swizzling)来实现KVO
调用addobser方法之后在运行时动态创建一个NSKVONotifying_A类,
又会将原来类A的isa指针指向新创建的NSKVONotifying_A类,在NSKVONotifying_A中重写setter方法
重写的setter添加的方法里调用以下方法
-
-(void)willChangeValueForKey:(NSString *)key -
-(void)didChangeValueForKey:(NSString *)key
-(void)setValue:(id)obj
{
[self willChangeValueForKey:@"keyPath"];
[super setValue:obj];
[self didChangeValueForKey:@"keyPath"];
}
问题
通过KVC设置value能否生效?
能生效,能调用到set方法
通过成员变量直接赋值value能否生效?
不能,因为其不会走set方法
总结
- 使用setter方法改变值KVO才会生效
- 使用setValue:forKey:改变值KVO才会生效
- 成员变量直接修改需手动添加KVO才会生效
KVC
当调用setValue:forKey:设置属性value时,其底层的执行流程为:
-
首先寻找是否有这三种的setter方法,按照查找顺序为
set<key>:->_set<key>->setIs<key>- 如果有其中任意一个setter方法,则直接设置属性的value(注意:key是指成员变量名,首字符大小写需要符合KVC的命名规范)
- 如果没有则进第二步
-
如果没有第一步中的三个简单的setter方法,则查找
accessInstanceVariablesDirectly是否返回YES- 如果返回YES,则查找间接访问的实例变量进行赋值,查找顺序为:
_<key>->_is<key>-><key>->is<key>
** 如果找到其中任意一个实例变量,则赋值
** 如果没有,则进入第三步 2. 如果返回NO,则进入第三步
- 如果返回YES,则查找间接访问的实例变量进行赋值,查找顺序为:
-
如果setter方法 或者 实例变量都没有找到,系统会执行该对象的
setValue:forUndefinedKey:方法,默认抛出NSUndefinedKeyException类型的异常
属性关键字
-
读写权限
- readonly
- readwrite(默认)
-
原子性
-
atomic(默认),对属性直接赋值,获取是线程安全的,但操作和访问不是线程安全的,比如给数组添加元素,移除元素等
-
nonatomic
-
引用计数
- retain/strong
- assign/unsafe_unretained
- weak
- copy
-
assign
- 修饰基本数据类型,如int,BOOL等
- 修饰对象类型时,不改变其引用计数
- 会产生悬垂指针,所指对象释放以后,assign指针仍然指向原对象地址,如果继续访问原对象,会导致内存泄漏
weak
- 不改变被修饰对象的引用计数
- 所指对象在被释放之后会自动置为nil
浅拷贝
浅拷贝就是对内存地址的赋值,让目标对象指针和源对象指向同一片内存空间
深拷贝
深拷贝让目标对象指针和源对象指针指向两片内容相同的内存空间
- 可变对象的copy和mutableCopy都是深拷贝
- 不可变对象copy是浅拷贝,mutableCopy是深拷贝
- copy方法返回的都是不可变对象
Runtime
isa指针
指针型isa: isa的值代表Class的地址
非指针型isa:isa的值部分代表Class的地址
cacha_t
- 用于快速查找方法执行函数
- 是可增量扩展的哈希表结构
- 是局部性原理的最佳应用
对象,类对象,元类对象
- 类对象存储实例方法列表等信息
- 元类对象存储类方法列表等信息
self代表从当前类开始查找方法列表
super代表从父类开始查找方法
缓存查找
例:给定值是SEL,目标值是对应bucket_t中的IMP
给定的key,通过hash函数计算出对应的索引,找到bucket_t
当前类中查找
- 对于已排序好的列表,采用二分查找算法查找方法对应执行函数
- 对于没有排序好的列表,采用一般遍历查找查找方法对应执行函数
消息转发
首先会调用+ (BOOL)resolveInstanceMethod:(SEL)sel方法来判断消息是否已处理,如果没处理调用- (id)forwardingTargetForSelector:(SEL)aSelector返回转发目标,如果没有返回转发目标,会调用- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector 返回方法签名,则调用- (void)forwardInvocation:(NSInvocation *)anInvocation, 设置调用目标
动态方法解析
@dynamic不需要在编译时生成get,set方法
- 动态运行时语言将函数决议推迟到运行时
- 编译时语言在编译器进行函数决议
内存管理方案
- TaggedPointer 小对象,比如NSNumber
- NONPOINTER_ISA(64bit)
- 散列表(引用计数表,弱引用表)
slidTables(), slidTables里面存在多个slidTable,如果只是使用一个slidTable,会存在效率问题,而slidTables采用分离锁的方案,对不同对象的引用计数管理可以并发访问
怎么实现快速分流?
slidTables本质是一张Hash表
锁的应用(需查找)
自旋锁Spinlock_t
-
Spinlock_t是"忙等"的锁(当前锁已被其他线程获取,当前线程就不不断的探测锁是否被释放,如果已释放,则使用)
-
适用于轻量访问
自动释放池
- 是以栈为结点通过双向链表的形式组合而成
- 是和线程一一对应的
- 在当次runloop将要结束的时候调用AutoreleasePoolPage::pop()
- 多层嵌套就是多次插入哨兵对象
- 在for循环中alloc图片数据等内存消耗较大的场景手动插入autoreleasePool
循环引用
- 自循环引用
- 相互循环引用
- 多循环引用
如何破除循环引用?
- 避免产生循环引用
- 在合适的时机手动断环
具体的解决方案有哪些?
- __weak
- __block
- MRC下,__block修饰的对象不会增加其引用计数,避免了循环引用
- ARC下,__block修饰对象会被强引用,无法避免循环引用,需手动解环
- __unsafe_unretained
-
修饰对象不会增加其引用计数,避免了循环引用
-
如果被修饰对象在某一时机被释放,会产生悬垂指针
NSTimer的循环引用问题
- 使用系统ios10以后系统API,block形式
- 引入一个对象proxy,proxy弱引用 self,然后 proxy 传入NSTimer。即self 强引用NSTimer,NSTimer强引用 proxy,proxy 弱引用 self,这样通过弱引用来解决了相互引用,此时不会形成环
处。
block
Block是将函数及其执行上下文封装起来的对象
截获变量
- 对于基本数据类型的局部变量截获其值
- 对于对象类型的局部变量连同所有权修饰符一起截获
- 以指针形式截获局部静态变量
- 不截获全局变量,静态全局变量
__block修饰符
- 一般情况下,对被截获变量进行赋值操作需添加__block修改符
- __block修改的变量变成了对象
__forwarding指针是用来干什么的?
多线程
同步和异步的区别
- 是否等待队列的任务执行结束,以及是否具备开启新线程的能力
- 异步执行虽然具有开始新线程的能力,但是并不一定开始新线程,这跟任务所指定的队列类型有关
死锁的原因
队列引起的循环等待
怎么利用GCD实现多读单写?
- 读者,读者并发
- 读者,写者互斥
- 写者,写者互斥
dispatch_barrier_async()
NSOperation实现多线程方案的特点和优势
-
添加任务依赖
-
任务执行状态控制
- isReady
- isExecuting
- isFinished
- isCancelled
如果只重写了
main方法,底层控制变更任务执行完成状态,以及任务退出如果重写了
start方法,自行控制任务状态
系统是怎样移除一个isFinished=YES的NSOperation的?
通过KVO
- 最大并发量
锁
**iOS中都有哪些锁? **
-
@synchronized
一般在创建单例对象的时候使用
-
atomic
- 修饰属性的关键字
- 对被修饰对象进行原子操作(不负责使用)
-
OSSpinLock(自旋锁)
- 循环等待询问,不释放当前资源
- 用于轻量级数据访问,简单的int值+1/-1操作
-
NSRecursiceLock(递归锁)
- 可以重入
-
NSLock
-
dispatch_semaphore_t
RunLoop
RunLoop是通过内部维护的事件循环来对事件/消息进行管理的一个对象
什么是事件循环?
- 没有消息需要处理时,休眠以避免资源占用
- 用户态 -> 内核态
- 有消息需要处理时,立刻被唤醒
- 内核态 -> 用户态
main函数为什么能保持不退出?
main函数中会启动一个运行循环,而runloop又是对事件循环的一种维护机制 NSRunLoop是对CFRunLoop的封装,提供了面向对象的API
- CFRunLoop
- CFRunLoopMode
- Source/Timer/Observer
NSRunLoopCommonModes
- CommonMode不是实际存在的一种mode
- 是同步Source/Timer/Observer到多个mode的一种技术方案
怎样开启一条常驻线程?
- 为当前线程开启一个RunLoop
- 向该RunLoop中添加一个port/Source等维持RunLoop的事件循环
- 启动该RunLoop
RunLoop与线程的关系?
- 线程与RunLoop是一一对应的
- 线程默认是没有RunLoop的,需要手动创建
图片缓存
- Manager
- 内存
- 磁盘
- 网络
- Code Manager
- 图片解码
- 图片压缩/解压缩
内存设计
- 存储的Size
- 10k以下50个
- 100K以下20个
- 100K以上10个
- 淘汰策略
- 队列,先进先出模式
- LRU(最近最久未使用策略)
磁盘设计
- 存储方式
- 大小限制
- 淘汰策略(如某一图片存储时间距今已超过7天)
网络设计
- 图片请求最大并发量
- 请求超时策略
- 请求优先级