iOS 面试总结

iOS 面试总结

四月 10, 2020

1、UI视图

1.1、常见面试问题
  • UITableView 重用机制
  • UITableView数据源同步
  • 事件传递&视图响应
  • 图像显示原理
  • UI卡顿、掉帧
  • UI绘制原理、异步绘制
  • 离屏渲染
1.2、UITableView重用机制

UITableView.png

2、事件传递

1
2
3
4
// 返回最终哪个UIView相应点击事件
open func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView?
// 返回某一个点击的位置是否在视图范围内
open func point(inside point: CGPoint, with event: UIEvent?) -> Bool

3、UIView 和 CALayer

  • UIView提供内容以及负责处理触摸等事件,参与事件响应链
  • CALayer负责显示内容contents
  • 体现了系统设置上的单一性原则

4、图像绘制原理

4.1、UIView绘制原理
4.2、离屏渲染
4.2.1 什么是离屏渲染
  • On- Screen Rendering 当前屏幕渲染,指的是GPU的渲染操作是在当前用于显示的屏幕缓冲区中进行
  • Off-Screen Rendering 离屏渲染,指的是GPU在当前屏幕缓冲区以外新开辟一个缓冲区进行渲染操作
4.2.2 什么时候会出发离屏渲染
  • 圆角和maskToBounds一起使用
  • 图层蒙版
  • 阴影
  • 光栅化
4.2.3 为什么要避免离屏渲染
  • 创建新的渲染缓冲区

  • 在触发离屏渲染时会增加GPU的工作量,很可能造成掉帧和卡顿

4.3 异步绘制
4.4、滑动优化方案
  • GPU

    • 纹理渲染
    • 视图混合
  • CPU

    • 对象创建、调整、销毁
    • 预排版、(布局计算、文本计算)
    • 预渲染(文本异步绘制、图片编解码)

5、分类

5.0、常见面试问题
  • 分类
  • 关联对象
  • 扩展、代理
  • KVC、KVO
  • NSNotification(通知)的原理是什么
  • 属性关键字
5.1、分类做了那些事?
  • 申明私有方法
  • 分解体积庞大的类文件
  • 把Framework的私有方法公开
  • 系统方法做一个扩展
5.2、分类的特点
  • 运行时决议,在运行时添加到宿主类上
  • 可以为系统类添加分类
5.3、分类可以添加哪些内容
  • 实例方法
  • 类方法
  • 协议
  • 属性(只添加了get方法,和set方法)
  • 分类可以通过runtime关联对象的方式添加实例变量
5.4、分类方法
  • 分类添加的方法可以覆盖原类方法
  • 同名分类方法谁能生效取决于编译顺序
  • 名字相同的分类会引起编译报错
5.5、怎样为分类添加成员变量
  • 不能在分类申明时添加成员变量,只能用关联对象的方法添加成员变量
1
objc_getAssociatedObject(id _Nonnull object, const void * _Nonnull key)
1
2
objc_setAssociatedObject(id _Nonnull object, const void * _Nonnull key,
id _Nullable value, objc_AssociationPolicy policy)
1
objc_removeAssociatedObjects(id _Nonnull object)
5.6、分类添加的对象被添加到哪了?
  • 关联对象由AssicucationsManager管理并在AssociationsHashMap存储。
  • 所有对象的关联内容都在同一个全局容器中。

6、扩展

6.1、一般用扩展做什么
  • 申明私有属性
  • 申明私有方法
  • 申明私有成员变量
.2、扩展的
  • 编译时决议
  • 只以申明的形式存在,多数情况下寄生于宿主类的.m中
  • 不能为系统类添加扩展

7、代理

7.1、什么是代理
  • 代理是一种软件设计模式
  • iOS中以@protocol形式体现
  • 代理是一对一的
7.2、通知
  • 是使用观察者模式来实现的用于跨层传递消息机制
  • 通知是一对多的
7.3、KVO
  • KVO 是观察者模式的一种实现方式
  • Apple使用了isa混写(isa-swizzling)来实现KVO
  • 使用setter方法改变值KVO才会生效
  • 使用setValue:forKey:改变值KVO才会生效
  • 成员变量直接修改需手动添加KVO才会生效

8、关键字

  • 读写权限 (readonly,)
8.1、weak
  • 不改变被修饰对象的引用计数
  • 所指对象在被释放之后会自动置为nil
  • 多数用于解决循环引用计数
8.2 原子性
  • atomic 对赋值和获取保证线程安全,
  • nonatomic 不保证线程安全
8.3 引用计数
  • retain 在MRC中使用

  • strong 在ARC中使用

  • assign 修饰基本数据类型,如Int

    • 修饰基本数据类型,
    • 修饰对象类型是,不改变其引用计数
    • 会产生垂直指针
  • unsafe_unretained 在MRC中使用

    8.4 weak 和 assign的区别和相同

区别:

  • weak只能修饰对象类型,assign既能修饰对象类型又能修饰基本数据类型
  • weak修饰的对象被释放后自动置为nil, assign所修饰的对象在被释放后assign指针依旧指向这个对象的地址

相同:

  • 在修饰对象时,weak 和 assign 都不改变对象的引用计数
8.5 copy
源对象类型 拷贝方式 目标对象类型 拷贝类型(深/浅)
mutable对象 copy 不可变 深copy
mutable对象 mutableCopy 可变 深copy
immutable对象 copy 不可变 浅copy
immutable对象 mutableCopy 可变 深copy
  • 可变对象的copy和mutableCopy都是深copy
  • 不可变对象的copy是浅copy,mutableCopy是深copy
  • copy方法返回的都是不可变对象

9、RunTime

常见面试问题:

  • 消息发送流程
  • 消息转发流程
  • 类方法和实例方法动态解析
  • 类的本质
  • 分类初始化
  • 分类方法覆盖原类方法
  • KVO和KVC的原理
  • Method Swizzling原理,解决Crash问题
  • 如何使用RunTime动态创建类
  • 如何使用RunTime进行hook
  • Method Swizzling使用
9.1、objc_object

iOS中所有的对象都是的最终父类都是id类型,在RunTime中最终都转换成了objc_object结构体,该结构体主要包含四个部分

  • isa_t共用体
  • 关于isa操作相关
  • 弱引用相关,如该对象是否被弱引用过
  • 关联对象相关,如是否关联属性
  • 内存管理相关方法的实现
9.2、objc_class

iOS中所有的Class对象在RunTime中最终都转换成了objc-class结构体,objc_class继承自objc_objectobjc_class包含以下内容:

  • Class superClass,是一个Class类型的类对象,指向其父类对象
  • cache_t cache方法缓存
  • class_data_bits_t bits定义的属性
9.2、isa指针
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
union isa_t 
{
isa_t() { }
isa_t(uintptr_t value) : bits(value) { }

Class cls;
uintptr_t bits;
struct {
uintptr_t nonpointer : 1; // 是否Tagged Poniter
uintptr_t has_assoc : 1; // 是否有关联引用
uintptr_t has_cxx_dtor : 1; // 是否有析构器
uintptr_t shiftcls : 33; // 类的指针 MACH_VM_MAX_ADDRESS 0x1000000000
uintptr_t magic : 6; // 是否初始化完成
uintptr_t weakly_referenced : 1; // 是否被弱引用
uintptr_t deallocating : 1; // 是否正在释放内存
uintptr_t has_sidetable_rc : 1; // 引用计数是否过大
uintptr_t extra_rc : 19; // 引用计数
};
}
  • isa_t分为指针型isa和非指针型isa
  • 对象的isa指针指向其类对象
  • 类对象的isa指针指向其元类对象
9.3、cache_t
  • 用于快速查找方法执行函数
  • 是可增量扩展的哈希表结构
  • 是局部性原理的最佳应用
  • cache_t数组中存储的数据是keyIMP,我们可以通过查找key的方法实现来查找IMP的方法实现
9.4、class_data_bits_t bits
  • class_data_bits_t主要是对class_rw_t的封装
  • class_rw_t代表了类相关的读写信息,对class_ro_t的封装
  • class_ro_t代表了类相关的只读信息
9.5、Tagged Pointer原理

10、多线程相关

10.0、常见面试问题
  • GCD
  • NSOperation / NSOperationQueue
  • Thred
  • 线程同步、资源共享
  • 互斥锁、自旋锁、递归锁
10.1、iOS系统提供了几种多线程技术各自的特点是怎样的
  • GCD 实现一些简单的线程同步
  • Thread + RunLoop = 常驻线程

一般常用于线程保活,分为三步:

1、在线程的Block中取当前RunLoop

2、为RunLoop添加NSMachPort或者 Source

3、启动这个RunLoop

1
2
3
4
5
6
7
8
9
10
11
12
let thread = Thread { [weak self] in
guard let self = `self` else { return }
self.configThread()
}
thread.start()

func configThread() {
print("--------------1")
let runLoop = RunLoop.current
runLoop.add(NSMachPort(), forMode: .common)
runLoop.run()
}
  • Operation和OperationQueue,可以方便对任务的状态的控制,所以一些常见的第三方框架,SDWebImage,AFNetworking
10.2 、怎样用GCD实现多读单写
  • 写: 开启多个任务去修改数据,保证资源部被抢占,比如卖票系统,此处使用异步栅栏任务
  • 读: 允许多个任务同时加入队列,但要保证一个一个执行,此处使用同步并行
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 并发队列
let queue = DispatchQueue.init(label: "com.tsn.readWriteQueue", attributes: .concurrent)
// 数据源 一般是file
lazy var dataArray: [String] = [String]()
// 读数据
func getData(_ index: Int) -> String? {
var result: String? = nil
// 并发同步读取数据
queue.sync {
result = dataArray[index]
}
return result
}
func addData(_ data: String) {
queue.async(execute: DispatchWorkItem(qos: .default, flags: .barrier, block: {
self.dataArray.append(data)
print("======\(self.dataArray)")
}))
}
10.3、Operation对象在Finished之后是怎样从queue当中移除的

通过KVO的方式达到对Operation移除的目的。

10.4、你都使用过哪些锁?结合实际谈谈你是怎样使用的

递归锁解决死锁问题

10.5、线程死锁
  • 队列引起的循环等待
  • 主线程中+同步造成死锁
1
2
3
DispatchQueue.main.sync { 

}
10.6、ABC三个任务并发,完成后执行任务D
1
2
3
4
5
6
7
8
9
10
11
12
13
14
let queue = DispatchQueue.init(label: "com.tsn.groupQueue")

init() {
let group = DispatchGroup()
let array = ["A","B","C"]
for index in 0..<array.count {
queue.async(group: group) {
print("---------\(array[index])")
}
}
group.notify(queue: .main) {
print("---------D")
}
}
10.7、Operation

需要和OperationQueue配合使用来实现多线程方案,

  • 添加任务依赖或移除依赖 这是GCD和Thread所不具备的
  • 任务执行状态控制
    • isReady是否可执行,一般用于异步的情况下
    • isExexuting标记Operation是否正在执行中
    • isFinished标记Operation是否已经执行完成了,一般用于异步
    • isCancelled标记Operation是否已经cancel
  • 可以控制最大并发量
  • 如果只重写main方法,底层控制变更任务执行完成状态,以及任务退出
  • 如果重写了start方法,自行控制任务状态

11、内存管理

11.0、常见面试问题:
  • ARC
  • MRC
  • 引用计数机制
  • 弱引用表
  • AutoReleasePool
  • 循环引用
11.1、内存布局
  • stack: 栈内存, 方法调用
  • heap: 堆内存,通过alloc等分配的对象
  • bss: 未初始化的全局变量
  • data:已初始化的全局变量等
  • txt: 程序代码
11.2、内存管理方案

iOS系统会针对不同情况下使用不同的内存管理方法:

  • 对于一些小的对象,如Number,通过TaggedPointer来进行内存管理
  • 对于64位的NONPOINTER_ISA ,非指针型的ISA
  • 散列表 是一种复杂的数据结构,其中包含了弱引用表 和 散列引用计数表
11.3 引用计数
  • ARC是LLVMRuntime协作的结果
  • ARC中禁止手动调用retain/release/retainCount/dealloc
  • ARC中新增了weak 、strong属性关键字
11.4 weak
  • 添加weak时实现

一个被申明为__weak的对象,经过编译器的编译之后回调用objc_initWeak()方法,然后经过一系列的函数调用栈,最终在weak_reginster_no_lock()函数中进行弱引用的添加,具体添加的位置通过hash算法来添加或查找的,如果查找位置中已经存在了一个弱引用数组,那么就把这个新的变量添加到这个弱引用数组中,如果没有弱引用数组那么就创建一个,并把第零个设置这个变量的weak指针,后面都添加为nil.

  • 清除weak变量,同时设置指向为nil

当一个对象调用dealloc()方法中,在dealloc()实现中会调用弱引用清除的相关函数。在这个函数的内部实现中会根据当前对象指针查找弱引用表,把当前对象相对应的弱引用数组取出,然后遍历数组中所有的弱引用指针,分别置为nil

11.5、自动释放池(@autoreleasepool)
  • @autoreleasepool的实现结构

    • 是以栈为节点通过双向链表的形式组合而成
    • 是和线程一一对应的
  • @autoreleasepool的实现原理是怎样的?

以栈为节点通过双向链表的形式组成的数据结构

  • @autoreleasepool为何可以嵌套使用?

  • 在for循环中alloc图片数据等内存消耗较大场景手动插入**@autoreleasepool**

11.6、循环引用
  • 自循环引用
  • 相互循环引用
  • 多循环引用
11.6.1、产生循环引用的条件
  • 代理
  • NSTimer
  • Block
  • 大环引用
11.6.2、如破除循环引用
  • 避免产生循环引用
  • 在合适的时机解除循环引用
11.6.3、具体的方案有哪些?
  • __weak
  • __block
  • __unsafe_unretained 由这个关键字修饰的关键字也没有增加引用计数
    • 修饰对象不会增加其引用计数,避免了循环引用
    • 如果被修饰对象在某一时机被释放会产生垂直指针
11.6.4、NSTimer 产生循环引用问题

iOS 10以后通过Block方式,这时要注意的是和Block产生的循环引用;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@property (strong, nonatomic) NSTimer *timer;
if (@available(iOS 10.0, *)) {
__weak typeof(self) weakSelf = self;
self.timer = [NSTimer scheduledTimerWithTimeInterval:2 repeats:true block:^(NSTimer * _Nonnull timer) {
[weakSelf timePrint];
}];
}

- (void)timePrint {
NSLog(@"-------------------");
}

- (void)dealloc {
NSLog(@"------------------dealloc");
[self.timer invalidate];
_timer = nil;
}

一般要处理的是iOS 10之前的系统,方法有多种,可以

  • 模仿系统的采用Block方式

    新建NSTimer分类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#import "NSTimer+Category.h"

@interface NSTimer (Category)
+ (NSTimer *)RP_ScheduledTimerWithTimeInterval:(NSTimeInterval)timeInterval repeats:(BOOL)repeats block:(void(^)(void))timerBlock;
@end

@implementation NSTimer (Category)
+ (NSTimer *)RP_ScheduledTimerWithTimeInterval:(NSTimeInterval)timeInterval repeats:(BOOL)repeats block:(void(^)(void))timerBlock {
return [self scheduledTimerWithTimeInterval:timeInterval
target:self
selector:@selector(RP_TimerHandle:)
userInfo:[timerBlock copy] //注意copy
repeats:repeats];
}
+ (void)RP_TimerHandle:(NSTimer *)timer {
void(^block)(void) = timer.userInfo;
if (block) {
block();
}
}
@end

调用方式:

1
2
3
4
5
6
7
8
__weak typeof(self) weakSelf = self;
self.timer = [NSTimer RP_ScheduledTimerWithTimeInterval:2 repeats:true block:^{
[weakSelf timePrint];
}];

- (void)timePrint {
NSLog(@"-------------------");
}

Swift代码:

1
2
3
4
5
6
7
8
9
10
11
extension Timer {
class func rp_scheduledTimer(timeInterval ti: TimeInterval, repeats yesOrNo: Bool, closure: @escaping (Timer) -> Void) -> Timer {
return self.scheduledTimer(timeInterval: ti, target: self, selector: #selector(RP_TimerHandle(timer:)), userInfo: closure, repeats: yesOrNo)
}

@objc class func RP_TimerHandle(timer: Timer) {
var handleClosure = { }
handleClosure = timer.userInfo as! () -> ()
handleClosure()
}
}
1
2
3
4
5
6
7
8
9
10
11
if #available(iOS 10.0, *) {
time = Timer.scheduledTimer(withTimeInterval: 2, repeats: true, block: { [weak self] (timer) in
guard let `self` = self else { return }
self.timePrint()
})
} else {
time = Timer.rp_scheduledTimer(timeInterval: 2, repeats: true, closure: { [weak self] (timer) in
guard let `self` = self else { return }
self.timePrint()
})
}
  • 采用中间件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#import <objc/runtime.h>

@property (nonatomic, strong) id target;

_target = [NSObject new];
class_addMethod([_target class], @selector(timePrint), (IMP)timerIMP, "v@:");
// self换成_target就没有了循环引用了
self.timer = [NSTimer scheduledTimerWithTimeInterval:2 target:_target selector:@selector(timePrint) userInfo:nil repeats:true];

void timerIMP(id self, SEL _cmd) {
NSLog(@"-------------------");
}

- (void)timePrint {
NSLog(@"-------------------");
}

12、Block

12.0、常见面试问题
  • Block的本质(什么是Block)
  • 为什么Block会产生循环引用
  • __block修饰符的本质
  • Block的内存管理
  • 怎样理解Block截获变量的特性
    • 截获基本数据类型的变量,是截获其值
    • 截获对象类型是截获其修饰符和变量
    • 截获静态变量是截获其指针
  • 你都遇到过哪些循环引用?你是怎么解决的?
    • Block本身是对象的变量,Block又捕获了这个对象的变量,会产生循环引用
12.1、什么是Block
  • Block是将函数及其执行上下文封装起来的对象
  • Block调用即是函数调用
  • Block的底层结构:
1
2
3
4
5
6
struct __block_impl {
void *isa;// isa指针,Block是对象的标志
int Flags;
int Reserved;
void *FuncPtr;// 函数指针
}
12.2、Block如何截获变量
1
2
3
4
5
6
7
8
int multiplier = 6;
int (^ Block)(int) = ^ int(int num) {
return num * multiplier;
};
multiplier = 4;
NSLog(@"===========%d",Block(2));
// 打印结果
// ===========12
12.2.2、被截获变量的类型分类:
  • 局部变量
    • 基本数据类型
    • 对象类型
  • 静态局部变量
  • 全局变量
  • 静态全局变量

12.2.3、截获变量

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

使用命令将OC代码转成C++代码:

1
clang -rewrite-objc -fobjc-arc ***.m
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
// 全局变量
int gloabal_var = 4;
// 全局静态变量
static int static_global_var = 5;

@implementation BlockTest

- (instancetype)init {
self = [super init];
if (self) {
[self testMethod];
}
return self;
}

- (void)testMethod {
// 基本数据类型的局部变量
int var_num = 10086;
// 对象类型的局部变量
__unsafe_unretained id unsafa_obj = nil;
__strong id strong_obj = nil;
// 局部静态变量
static int static_var_num = 1008611;

void (^Block)(void) = ^{
NSLog(@"局部变量<基本数据类型> %d",var_num);
NSLog(@"对象类型的局部变量:%@,%@",unsafa_obj,strong_obj);
NSLog(@"局部静态变量:%d",static_var_num);
NSLog(@"全局变量:%d",gloabal_var);
NSLog(@"全局静态变量:%d",static_global_var);
};
Block();
}
@end

编译结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
struct __BlockTest__testMethod_block_impl_0 {
struct __block_impl impl;
struct __BlockTest__testMethod_block_desc_0* Desc;
int var_num;
__unsafe_unretained id unsafa_obj;
__strong id strong_obj;
int *static_var_num;
__BlockTest__testMethod_block_impl_0(void *fp, struct __BlockTest__testMethod_block_desc_0 *desc, int _var_num, __unsafe_unretained id _unsafa_obj, __strong id _strong_obj, int *_static_var_num, int flags=0) : var_num(_var_num), unsafa_obj(_unsafa_obj), strong_obj(_strong_obj), static_var_num(_static_var_num) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
12.2.5、__block修饰符
1
2
3
4
5
6
7
8
__block int multiplier = 6;
int (^ Block)(int) = ^ int(int num) {
return num * multiplier;
};
multiplier = 4;
NSLog(@"===========%d",Block(2));
// 打印结果
// ===========8
  • __block修饰的变量变成了对象
12.3、Block内存管理
12.3.1、Block的分类
  • 全局类型 _NSConcreteGloabalBlock 存储于已初始化内存区中
  • 堆类型 _NSConcreateStackBlock
  • 栈类型 _NSConcreateMallocBlock
12.3.2、Block的copy操作
Block类别 Copy结果
堆类型 _NSConcreateStackBlock
栈类型 _NSConcreateMallocBlock 数据区 什么也不做
全局类型 _NSConcreteGloabalBlock 增加引用计数
12.3.3 解决循环引用问题
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@property (strong, nonatomic) NSArray <NSString *> *array;
@property (copy, nonatomic) NSString *(^strBlock)(NSString *);

- (instancetype)init {
self = [super init];
if (self) {

_array = [NSMutableArray arrayWithObject:@"block"];
_strBlock = ^NSString *(NSString *num) {
return _array[0]; // Capturing 'self' strongly in this block is likely to lead to a retain cycle
};

}
return self;
}

解决方法:

1
2
3
4
__weak NSArray <NSString *> *weakArray = _array;
_strBlock = ^NSString *(NSString *num) {
return weakArray[0];
};
12.3.4、__block造成循环引用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@property (copy, nonatomic) NSString *varNum;
@property (copy, nonatomic) int(^numBlock)(int);

- (instancetype)init {
self = [super init];
if (self) {
[self testMethod];

_varNum = @"3";
__block BlockTest *blockSelf = self;
_numBlock = ^int(int num) {
return num * [blockSelf.varNum intValue];
};
int blockNum = _numBlock(10);
NSLog(@"blockNum=========%d",blockNum);

}
return self;
}
  • 在MRC下不会产生循环引用

  • 在ARC下,会产生循环引用引起内存泄漏

1
2
3
4
5
_numBlock = ^int(int num) {
int result = num * [blockSelf.varNum intValue];
blockSelf = nil;
return result;
};

13、RunTime

13.1、常见面试问题
  • 对象、类对象、元类对象
  • 消息传递机制
  • 消息转发流程
  • 方法缓存
  • Method-Swizzling
  • 动态添加方法

14、RunLoop

14.0、常见面试问题
  • RunLoop的概念
  • Mode/Spurce/Timer/Observer
  • 事件循环机制
  • RunLoop与Timer的关系
  • RunLoop和线程之间的关系
  • 常驻线程
14.1、RunLoop的概念

RunLoop是通过内部的事件循环来对事件/消息进行管理的一个对象。RunLoop可以不断的接收消息,如点击屏幕、滑动列表,接收到消息之后会对事件进行处理,处理完成之后会进入内核态等待。这里的等待并不是一个简单的等待或者循环,重点是状态的切换。

14.2、事件循环机制
  • 没有消息需要处理时,休眠以避免资源占用 (用户态 –>转换时调用mach_msg()方法–> 内核态)
  • 有消息需要处理时,立即被唤醒 (内核态 –> 转换时调用mach_msg()方法–> 用户态 )
14.2.1、为什么main函数不会退出

在main函数中维护着一个RunLoop,

14.3、RunLoop与NSTimer

滑动TableView的时候我们的定时器还会生效吗?

  • 正常情况下,Timer是运行在RunLoopDefaultMode模式下,当UITableView滑动时会发生一个RunLoop的Mode的切换,会切换到UITrackingRunLoopMode
  • 可以使用addTimer()同步添加到多个Mode中
1
2
3
4
5
let timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { (time) in
print("------------time")
}
let runLoop = RunLoop.current
runLoop.add(timer, forMode: .common)
14.4、RunLoop的Mode

RunLoop有多个Mode,运行到不同的Mode上时,只能接收到当前Mode上的事件,例如UI Mode下只能接收到屏幕点击,上下滑动的事件。一个Timer 要想加入到多个Mode当中呢,

  • CommonMode是一个特殊的Mode,CommonMode不是实际存在的一种Mode,
  • CommonMode是同步到Timer/Source/Observer到多个Mode的一种技术方案
14.5、RunLoop与多线程
  • 线程和RunLoop是一一对应的关系。
  • 线程中的RunLoop并没有创建需要手动创建,此处引出常驻线程。
    • 为当前线程开启一个RunLoop
    • 向当前RunLoop中添加一个Port/Source等位置RunLoop的事件循环
    • 启动该RunLoop
14.6、RunLoop数据结构
  • RunLoop是CGRunLoop的封装,提供了面向对象的API。
  • CFRunLoop
    • pthread 线程相关,RunLoop和线程是一一对应的关系
    • currentMode
    • modes
    • commonModes
    • commonModeItems
  • CGRunLoopMode
  • Source/Timer/Observer