iOS知识点

291 阅读9分钟

分类

  • 运行时决议
  • 可以为系统类添加分类
  • 分类添加的方法可以"覆盖"原类方法
  • 同名分类方法谁能生效取决于编译顺序,最后编译的方法会被实现
  • 名字相同的分类会引起编译报错

分类可以添加哪些内容

  • 实例方法
  • 类方法
  • 协议
  • 属性(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>

    1. 如果有其中任意一个setter方法,则直接设置属性的value(注意:key是指成员变量名,首字符大小写需要符合KVC的命名规范)
    2. 如果没有则进第二步
  • 如果没有第一步中的三个简单的setter方法,则查找accessInstanceVariablesDirectly是否返回YES

    1. 如果返回YES,则查找间接访问的实例变量进行赋值,查找顺序为:_<key>->_is<key>-><key>->is<key>

    ** 如果找到其中任意一个实例变量,则赋值

    ** 如果没有,则进入第三步 2. 如果返回NO,则进入第三步

  • 如果setter方法 或者 实例变量都没有找到,系统会执行该对象的 setValue:forUndefinedKey:方法,默认抛出NSUndefinedKeyException类型的异常

属性关键字

  • 读写权限

    1. readonly
    2. readwrite(默认)
  • 原子性

    1. atomic(默认),对属性直接赋值,获取是线程安全的,但操作和访问不是线程安全的,比如给数组添加元素,移除元素等

    2. nonatomic

    • 引用计数

    1. retain/strong
    2. assign/unsafe_unretained
    3. weak
    4. 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
    1. MRC下,__block修饰的对象不会增加其引用计数,避免了循环引用
    2. ARC下,__block修饰对象会被强引用,无法避免循环引用,需手动解环
  • __unsafe_unretained
  1. 修饰对象不会增加其引用计数,避免了循环引用

  2. 如果被修饰对象在某一时机被释放,会产生悬垂指针

NSTimer的循环引用问题

  1. 使用系统ios10以后系统API,block形式
  2. 引入一个对象proxy,proxy弱引用 self,然后 proxy 传入NSTimer。即self 强引用NSTimer,NSTimer强引用 proxy,proxy 弱引用 self,这样通过弱引用来解决了相互引用,此时不会形成环 处。

block

Block是将函数及其执行上下文封装起来的对象

截获变量

  • 对于基本数据类型的局部变量截获其值
  • 对于对象类型的局部变量连同所有权修饰符一起截获
  • 指针形式截获局部静态变量
  • 不截获全局变量,静态全局变量

__block修饰符

  • 一般情况下,对被截获变量进行赋值操作需添加__block修改符
  • __block修改的变量变成了对象

__forwarding指针是用来干什么的?

多线程

同步和异步的区别

  • 是否等待队列的任务执行结束,以及是否具备开启新线程的能力
  • 异步执行虽然具有开始新线程的能力,但是并不一定开始新线程,这跟任务所指定的队列类型有关

死锁的原因

队列引起的循环等待

怎么利用GCD实现多读单写?

  • 读者,读者并发
  • 读者,写者互斥
  • 写者,写者互斥

dispatch_barrier_async()

NSOperation实现多线程方案的特点和优势

  • 添加任务依赖

  • 任务执行状态控制

    1. isReady
    2. isExecuting
    3. isFinished
    4. isCancelled

    如果只重写了main方法,底层控制变更任务执行完成状态,以及任务退出

    如果重写了start方法,自行控制任务状态

系统是怎样移除一个isFinished=YES的NSOperation的?

通过KVO

  • 最大并发量

**iOS中都有哪些锁? **

  • @synchronized

    一般在创建单例对象的时候使用

  • atomic

    1. 修饰属性的关键字
    2. 对被修饰对象进行原子操作(不负责使用)
  • OSSpinLock(自旋锁)

    1. 循环等待询问,不释放当前资源
    2. 用于轻量级数据访问,简单的int值+1/-1操作
  • NSRecursiceLock(递归锁)

    1. 可以重入
  • NSLock

  • dispatch_semaphore_t

RunLoop

RunLoop是通过内部维护的事件循环来对事件/消息进行管理的一个对象

什么是事件循环?

  • 没有消息需要处理时,休眠以避免资源占用
    1. 用户态 -> 内核态
  • 有消息需要处理时,立刻被唤醒
    1. 内核态 -> 用户态

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
  1. 内存
  2. 磁盘
  3. 网络
  • Code Manager
  1. 图片解码
  2. 图片压缩/解压缩

内存设计

  • 存储的Size
  1. 10k以下50个
  2. 100K以下20个
  3. 100K以上10个
  • 淘汰策略
  1. 队列,先进先出模式
  2. LRU(最近最久未使用策略)

磁盘设计

  • 存储方式
  • 大小限制
  • 淘汰策略(如某一图片存储时间距今已超过7天)

网络设计

  • 图片请求最大并发量
  • 请求超时策略
  • 请求优先级

图片解码

内存