1、组件化
image.png2、为什么必须在主线程操作UI
UIKit并不是一个 线程安全 的类,UI操作涉及到渲染访问各种View对象的属性,如果异步操作下会存在读写问题,而为其加锁则会耗费大量资源并拖慢运行速度。另一方面因为整个程序的起点UIApplication是在主线程进行初始化,所有的用户事件都是在主线程上进行传递(如点击、拖动),所以view只能在主线程上才能对事件进行响应。而在渲染方面由于图像的渲染需要以60帧的刷新率在屏幕上 同时 更新,在非主线程异步化的情况下无法确定这个处理过程能够实现同步更新。
在UIKit中,很多类中大部分的属性都被修饰为nonatomic,这意味着它们不能在多线程的环境下工作,而对于UIKit这样一个庞大的框架,将其所有属性都设计为线程安全是不现实的,这可不仅仅是简单的将nonatomic改成atomic或者是加锁解锁的操作,还涉及到很多的方面:
假设能够异步设置view的属性,那我们究竟是希望这些改动能够同时生效,还是按照各自runloop的进度去改变这个view的属性呢?
假设UITableView在其他线程去移除了一个cell,而在另一个线程却对这个cell所在的index进行一些操作,这时候可能就会引发crash。
如果在后台线程移除了一个view,这个时候runloop周期还没有完结,用户在主线程点击了这个“将要”消失的view,那么究竟该不该响应事件?在哪条线程进行响应?
仔细思考,似乎能够多线程处理UI并没有给我们开发带来更多的便利,假如你代入了这些情景进行思考,你很容易得出一个结论: “我在一个串行队列对这些事件进行处理就可以了。” 苹果也是这样想的,所以UIKit的所有操作都要放到主线程串行执行。
Thread-Safe Class Design
https://www.objc.io/issues/2-concurrency/thread-safe-class-design/
大意为把UIKit设计成线程安全并不会带来太多的便利,也不会提升太多的性能表现,甚至会因为加锁解锁而耗费大量的时间。事实上并发编程也没有因为UIKit是线程不安全而变得困难,我们所需要做的只是要确保UI操作在主线程进行就可以了。
3、Runloop 与绘图循环
UIApplication在主线程所初始化的Runloop我们称为Main Runloop,它负责处理app存活期间的大部分事件,如用户交互等,它一直处于不断处理事件和休眠的循环之中,以确保能尽快的将用户事件传递给GPU进行渲染,使用户行为能够得到响应,画面之所以能够得到不断刷新也是因为Main Runloop在驱动着。
而每一个view的变化的修改并不是立刻变化,相反的会在当前run loop的结束的时候统一进行重绘,这样设计的目的是为了能够在一个runloop里面处理好所有需要变化的view,包括resize、hide、reposition等等,所有view的改变都能在同一时间生效,这样能够更高效的处理绘制,这个机制被称为绘图循环(View Drawing Cycle)。
4、核心动画
CoreAnimation 的动画执行过程都是在后台操作的,不会阻塞主线程。
CoreAnimation 是直接作用在CALayer 上的,并不是UIView。
5、链式调用
6、iOS的VSync与双缓冲机制
VSync:
VSync(vertical sync)是指垂直同步,在玩游戏的时候在设置的时候应该会看见过这个选项,这个机制能够让显卡和显示器保持在一个相同的刷新率从而避免画面撕裂。在iOS中,屏幕具有60Hz的刷新率,这意味着它每秒需要显示60张不同的图片(帧),但GPU并没有一个确定的刷新率,在某些时候GPU可能被要求更强力的数据输出来确保渲染能力,这时候他们可能比屏幕刷新率(60Hz)更快,就会导致屏幕不能完整的渲染所有GPU给他的数据,因为它不够快,屏幕的上一帧还没渲染完,下一帧就已经到来了,这就导致画面的撕裂。
这个时候我们就要引入VSync了,简单来说它就是让显卡保持他的输出速率不高于屏幕的刷新率,启用了VSync后,GPU不再会给你可怜的60Hz屏幕每秒发送100帧了,它会增加每一帧的发送间隔,确保显示器能够有充足的时间去处理每一帧。
双缓冲机制:
双缓冲机制是用于避免或减少画面闪烁的问题,在单缓冲的情况下,GPU输出了一帧画面,缓冲区就需要马上获取这个画面,并交给显示屏去显示,而这段时间GPU输出的画面就全都丢失了,因为没有缓冲区去承载这些画面,就会造成画面的闪烁。
而在双缓冲机制下有一个Back Frame Buffer和一个Front Frame Buffer,在GPU绘制完成后,它会将图像先保存到Back Frame Buffer中,操作完毕后,会调用一个交换函数,让绘制完成的Back Frame Buffer上的图像交换到Front Frame Buffer上。由于双缓冲利用了更多显存与CPU消耗时间,从而避免了画面的闪烁。
7、NSObject对象, 在内存中就是一个结构体对象
-
思考: 一个OC对象在内存中是如何布局的?
-
NSObject的底层实现如下:
8、常用LLDB指令
print、p: 打印值
po: 打印对象
memory read/数量格式字节数 内存地址: 读取内存, 有缩略写法: x
image.png
读取内存的格式和字节数如下:
格式:
x: 16进制 f: 浮点数 d: 十进制
字节大小:
b: byte: 1字节 h: half word: 2字节
w: word: 4字节 g: giant word: 8字节
指定条件读取内存:
image.png
memory write 内存地址 数值: 修改内存中的值
8、继承结构中的内存
现有如下代码: Person继承自NSObject, 有一个成员变量_age, Student继承自Person, 有一个成员变量_no
@interface Person: NSObject
{
int _age;
}
@end
@implementation Person
@end
@interface Student: Person
{
int _no;
}
@end
@implementation Student
@end
此时底层实现如下:
struct NSObject_IMPL {
Class isa;
};
struct Person_IMPL {
struct NSObject_IMPL NSObject_IVARS;
int _age;
};
struct Student_IMPL {
struct Person_IMPL Person_IVARS;
int _no;
};
可以推测出:
Person的实例占用内存为isa + _age = 12, 小于16, 所以Person的实例占用16字节的内存
Student的实例占用内存为isa + _age + _no = 16, 等于16, 所以Student的实例占用16字节的内存
注:isa是八个字节, 一个int类型占4个字节
class_getInstanceSize获取到的大小, 是内存对齐之后的大小, 即所有成员变量中, 占用内存最大的那个成员变量的倍数, Person内的NSObject_IMPL有一个isa占用8个字节, _age占用4个字节, 所以class_getInstanceSize获取到的是8的倍数, 即16个字节
网友评论