UI试图
UITableView相关
1.cell的复用
2.数据源同步:
-
并发访问、数据拷贝(缺点:拷贝数据产生内存开销)
image.png -
串行访问 (缺点:子线程操作耗时时,删除会有延迟)
image.png
事件传递&视图响应
UIView提供内容,以及负责处理触摸等事件,参与响应链
CALayer负责显示内容的contents
-
事件传递:
主要方法
-(UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
-(BOOL)pointInside(CGPoint)point withEvent:(UIEvent *)event
流程
image.png -
响应链:
主要方法
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
-(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
-(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
流程
image.png
图像显示原理
CPU和GPU是通过总线连接起来的,CPU输出的是位图,再经由总线在合适的时机上传给GPU,GPU拿到位图后会做图层的渲染、纹理的合成,之后把结果放到帧缓冲区中,由视频控制器根据VSync信号在指定时间之前去提取帧缓冲中的屏幕显示内容,最终显示到屏幕上。
- CPU工作
Layout UI布局、文本计算
Display 绘制
Prepare 图片编解码
Commit 提交位图 - GPU渲染管线
顶点着色
图元装配
光栅化
片段着色
片段处理
UI卡顿、掉帧
原因:1s完成60帧的刷新,在视觉上是流畅的,也就是每个VSync之间有16.7ms,在这16.7ms之间,要CPU和GPU共同完成下一帧画面的合成,不能完成则会导致卡顿(掉帧)
界面滑动优化方案:
- CPU
对象的创建、调整、销毁放到子线程中操作
预排版(布局计算、文本计算)
预渲染(文本等异步绘制,图片编解码等) - GPU
纹理渲染(给layer设置圆角、阴影等会触发离屏渲染)
视图混合(减少层级)
UI绘制原理/异步绘制
调用UIView的setNeedsDisplay方法,并不会立刻发生当前视图的绘制工作,而是先调用layer的setNeedDisplay方法,相当于在当前layer上打上一个脏标记,然后在当前RunLoop将要结束前调用CALayer的display方法,去绘制试图
异步绘制:
layer.delegate实现了displayLayer:方法,进入异步绘制流程中
- 代理负责生成对应的bitmap
-
设置该bitmap作为layer.contents属性的值
异步绘制.png
离屏渲染
在屏渲染:GPU的渲染操作是在当前用于显示的屏幕缓冲区中进行的
离屏渲染:GPU在当前屏幕缓冲区以外新开辟一个缓冲区进行渲染操作。当我们设置UI视图的图层属性时(圆角,遮罩,阴影),如果指定为在未预合之前(无法把渲染结果直接写入帧缓存区,而是暂存在另外的内存区域之后再写入frame buffer)不能用于直接显示的时候,就会触发离屏渲染。
为何要避免:会创建新的渲染缓冲区,GPU会产生额外的开销,很有可能导致CPU和GPU绘制一帧的时间超过16.7ms,会造成卡顿和掉帧
OC语言
分类Category
特点:
1.运行时决议(运行时通过Runtime把分类中添加的内容添加到原类中)
2.可以为系统类添加分类可以添加哪些内容
实例方法、类方法、协议、属性-
结构体
分类结构体.png 程序启动加载调用栈(image为镜像)
_objc_init(runtime初始化)
map_2_images(程序镜像的处理)
map_images_nolock
_read_images(加载可执行文件到内存中)
remethodizeClass(分类加载内容在此)原理
倒叙遍历原类的所有分类(最先访问最后编译的分类),把实例方法、类方法、协议、属性放入不同的二维数组中,获取原类当中的rw数据,其中包含原类的方法列表信息,根据拼接之后的元素总数重新分配内存,通过memmove把原类方法进行内存移动,移动到最后面,memcopy把分类中的方法copy到原类的内存中。所以原类的方法还存在,只是调用顺序问题。
关联对象
可以为分类添加实例变量
objc_getAssociatedObject(id object, const void *key)
objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)
objc_removeAssociatedObjects(id object)
-
本质
关联对象由AssociationsManager管理并在AssociationsHashMap中存储。
所有对象的关联内容都在同一个全局容器中。
@selector(text)-key COPY/RETAIN-Policy
image.png
扩展
扩展Extension
- 格式
@interface XXX ()
//私有属性
//私有方法(如果不实现,编译时会报警,Method definition for 'XXX' not found)
@end
- 使用
声明私有属性
声明私有方法
声明私有成员变量 - 特点
编译时决议
只以声明的形式存在,多数情况下寄生于宿主类的.m中
不能为系统类添加扩展
代理
是一种软件设计模式
传递方式是一对一的
NSNotification
- 特点
是使用观察者设计模式来实现的用于跨层传递消息的机制
传递方式是一对多的 -
如何实现通知机制
NSNotificationCenter内部会维护一个Notification_Map表, notificationName为key,Observers_List为value
image.png
KVO
- KVO:key-value observing
是OC对观察者设计模式的又一实现
Apple使用了isa混写(isa-swizzling)来实现KVO -
isa-swizzling
A在运行时会动态生成一个NSKVONotifying_A类(A的子类),然后将A类的实例对象的isa指针指向NSKVONotifying_A类,
观察A的name属性:
1、重写set方法,并在设置操作的前后分别调用 willChangeValueForKey: 和 didChangeValueForKey方法,这两个方法用于通知系统该 key 的属性值即将和已经变更了
2、重写class方法,返回原来的A类对象
3、重写dealloc方法,再合适的时候销毁这个运行时创建的类
image.png - 通过kvc设置name kvo能否生效
可以。和kvc的实现有关 - 通过成员变量直接赋值name kvo是否生效
不能,直接赋值不会走set方法,可以在赋值前后加上willChangeValueForKey: 和 didChangeValueForKey
KVC
如果没有找到Set方法的话,会先根据accessInstanceVariablesDirectly返回实例变量是否存在,YES会按照_key,_iskey,key,iskey的顺序搜索成员并进行赋值操作,都没有会调用valueForUndefinedKey:
属性关键字
- 读写权限
readonly
readwrite(系统默认) - 原子性
1.atomic(系统默认):可以保证赋值和获取是线程安全的,并不代表操作(如:数组的增加和移除)和访问
2.nonatomic - 引用计数
retain(MAC)/strong(ARC)
assign(基本数据类型、对象类型)/unsafe_assign(MRC中使用频繁)
weak/copy
1、assign和weak的区别:
assign:修饰基本数据类型;修饰对象时,不改变其引用计数;会产生野指针。
weak:不改变被修饰对象的引用计数;所指对象在被释放后会自动置为nil。
2、copy和mutableCopy/深拷贝和浅拷贝
略
Runtime
对象、类对象、元类对象
对象,指向类对象
类对象,指向元类对象
-
整体数据结构
数据结构.png
image.png 类对象(objc_class类型数据结构)
存储实例方法列表等信息元类对象(objc_class类型数据结构)
存储类方法列表等信息,元类对象的isa指针都指向根元类,根元类对象的isa指针也指向根元类。根元类对象的superClass指针指向根类对象如果类方法没有实现,在根类对象里面有同名实例方法,才能调用
消息传递机制
-
缓存查找
根据给定的方法选择器SEL,找到对应的方法实现(通过哈希查找找到对应的bucket_t)
image.png - 当前类中查找
对于已排序好的方法列表。采用二分查找算法查找方法对应执行函数
对于没有排序的方法列表,采用一般遍历查找 -
父类逐级查找
image.png
消息转发流程
- resolveInstanceMethod:/resolveClassMethod:
- forwadingTargetForSelector:
- methodSignatureForSelector:
forwardInvocation:
Method-Swizzling
- method_exchangeImplementations()
动态添加方法
class_addMethod(Class cls, SEL name, IMP imp, const char * types)
内存管理
内存布局
stack:方法调用
heap:通过alloc等分配的对象
-
内存管理方案
1、TaggedPointer (一些小对象,如NSNumber)
2、NONPOINTER_ISA (64位架构下的ios应用程序。在64位架构下,isa指针占64个比特位,但只需要32位就够了,为了提高内存利用率,在剩余的比特位中存储了一些关于内存管理的数据内容)
3、散列表 (SideTables,引用计数、弱引用表)
SideTable结构
ARC
自动引用计数管理内存
ARC是编译器(自动插入retain、release)和Runtime共同协作
ARC中禁止手动调用retain/release/retainCount/dealloc(ARC下可以重写dealloc方法,但是不能显示调用super dealloc)
新增weak、strong属性关键字
MRC
手动引用计数(斜体方法在ARC中调用会编译报错)
alloc:分配对象的内存空间
retain:引用计数+1
release:引用计数-1
retainCount:当前对象的引用计数值
autorelease:autoreleasePool结束时调用release
dealloc:显示调用[super dealloc]
引用计数机制
- alloc实现
经过一系列调用,最终调用C函数的calloc
此时并没有设置引用计数为1。(调用retainCount返回1,和retainCount的实现有关) - retain实现
//通过哈希查找对象引用计数相关的sidetable,从sidetables中找到sidetable
SideTable& table = SidTables()[this];
//获取引用计数map,通过哈希查找从中获取引用计数值
size_t& refcntStorage = table.refcnts[this];
//引用计数+1,实际是加偏移量4,反应出的结果是+1
refcntStorage += SIDE_TABLE_RC_ONE;
- release实现
SideTable& table = SidTables()[this];
RefcountMap::iterator it = table.refcnts.find(this);
//引用计数-1
it->sencond -= SIDE_TABLE_RC_ONE;
- retainCount
SideTable& table = SidTables()[this];
size_t refcnt_result = 1;
RefcountMap::iterator it = table.refcnts.find(this);
//alloc没有给对象进行+1,由于refcnt_result为1,所以调用retainCount为1
refcnt_result += it->second >> SIDE_TABLE_RC_SHIFT;
-
dealloc
image.png
弱引用表
- 添加weak变量
在weak_register_no_lock()函数中进行弱引用变量的添加,通过哈希算法进行位置查找,如果查找到的位置中已经有对应的弱引用数组,把弱引用指针添加到数组中,如果没有会创建弱引用数组,第0个位置上添加最新的weak指针,后面都初始化为nil
objc_initWeak() -> storeWeak() -> weak_register_no_lock()
- 清除weak变量,同时设置指向nil
weak_clear_no_lock()根据当前对象指针查找弱引用表,遍历当前对象的弱引用数组,把弱引用指针置为nil
dealloc() -> clearDeallocating() -> weak_clear_no_lock()
AutoReleasePool
编译器会将@autorealsepool{}改为
void *ctx = objc_autoreleasePoolPush(); (-> AutoreleasePoolPage::push)
{}代码块
//一次pop相当于一次批量的pop操作
objc_autoreleasePoolPop(ctx);(-> AutoreleasePoolPage::pop)
- 结构
是以栈为结点,通过双向链表的形式组合而成的数据结构(双向链表:每个结点都有两个指针,ParentPtr、ChildPtr)
和线程是一一对应的 - AutoreleasePoolPage
id* next;
AutoreleasePoolPage* const parent;
AutoreleasePoolPage* child;
pthread_t const thread; - 自动释放池
在当次runloop将要结束的时候调用AutoreleasePoolPage::pop()
多层嵌套就是多次插入哨兵对象
在for循环中alloc图片数据等内存消耗较大的场景手动插入autoreleasePool(每次循环都会完成一次内存的释放)
循环引用
自循环引用
相互循环引用
多循环引用
- 破除循环引用
避免产生循环应用;在合适的时机手动断环
(__weak;
__block—在MRC下,__block修饰对象不会增加其引用计数,避免了循环引用;在ARC下,__block修饰对象会被强引用,无法避免循环引用,需手动解环;
__unsafe_unretained—和__weak等效,但是被修饰的对象被释放后会产生野指针问题) - NSTimer循环引用问题
调用invalidate,从runloop中移除,设为nil 释放对target对象的强引用
不直接持有,引入中间对象持有两个弱引用,NSTimer和原对象
Block
Block本质-对象
Block是将函数及其执行上下文封装起来的对象
截获变量特性
- 局部变量
基本数据类型:截获其值
对象类型:连同所有权修饰符一起截获(和循环引用有关联) - 静态局部变量:指针形式
- 全局变量:不截获
- 静态全局变量:不截获
__block修饰符的本质
一般情况下,对被截获变量进行赋值操作需要添加__block修饰符(赋值 != 操作)
__block修饰的变量变成了对象
__block int xxx;
struct _Block_byref_XXX_0 {
void *isa;
//栈上的__forwarding指针指向自身
//栈上的block进行copy操作后,栈上的__forwarding指向堆上的block,堆上的__forwarding 指向自身
_Block_byref_XXX_0 *__forwarding;
int __flags;
int __size;
int XXX;
}
Block的内存管理
GlobalBlock(放在已初始化数据区中)
StackBlock(被销毁时,__block修饰的对象和block都会被销毁)
MallocBlock(堆上的block)
-
Block的copy操作
image.png
循环引用
__weak修饰
多线程
GCD
- 同步/异步 和 串行/并发
死锁:队列引起的循环等待 - dispatch_barrier_async
解决多读单写的问题 - dispatch_group
NSOperation/NSOperationQueue
- 添加/移除任务依赖
- 任务执行状态的控制
isReady
isExecuting
isFinished(是通过KVO机制实现的)
isCancelled
如果只重写了main方法,底层控制变更任务执行完成状态,以及任务退出
如果重写了start方法,自行控制任务状态 - 最大并发量
NSThread
- 启动流程
start() -> 创建pthread -> main() -> [target performSelector:selector] -> exit()
- 结合RunLoop实现常驻线程
互斥锁、自旋锁、递归锁等
- @synchronized
一般在创建单例对象的时候使用 - atomic
修饰属性的关键字;
对被修饰对象进行原子操作(不负责使用,对mutableArr.addObject操作不能保证线程安全) - OSSpinLock
自旋锁;循环等待询问,并不释放当前资源
用于轻量级数据访问,简单的int值+1/-1操作 -
NSLock
获取到锁,再获取会导致死锁(可使用递归锁进行解决)
image.png -
NSRecursiveLock
递归锁;可以重入
image.png - dispatch_semaphore_t
信号量 - dispatch_semaphore_create(0);
struct semaphore {
int value;
List <thread>;
}
- dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
{
S.value = S.value - 1;
//阻塞是一个主动行为
if S.value < 0 then Block(S.List)
}
- dispatch_semaphore_signal(semaphore); //发送信号量
{
S.value = S.value + 1;
//唤醒是一个被动行为
if S.value >= 0 then wakeup(S.List);
}
RunLoop
什么是RunLoop
RunLoop是通过内部维护的事件循环来对事件/消息进行管理的一个对象
没有消息处理时,休眠以避免资源占用(用户态,通过系统调用进入内核态)
有消息处理时,立刻被唤醒(内核态 -> 用户态)
不是个简单的while循环,主要是发生了状态切换
数据结构
NSRunLoop是对CFRunLoop的封装,提供了面向对象的API
- CFRunLoop
struct __CFRunLoop {
pthread_t _pthread; //对应的线程 RunLoop和线程一一对应
CFMutableSetRef _commonModes; //多个commonModes <String>
CFMutableSetRef _commonModeItems; //多个Observer,多个Timer,多个Source
CFRunLoopModeRef _currentMode; //当前运行的mode
CFMutableSetRef _modes; //多个modes <CFRunLoopMode>
};
- CFRunLoopMode
struct __CFRunLoopMode {
CFMutableSetRef _sources0; //需要手动唤醒当前线程
CFMutableSetRef _sources1; //具备唤醒线程的能力
CFMutableArrayRef _observers; //
CFMutableArrayRef _timers; //
};
- Source/Timer/Observer
CFRunLoopObserverRef监听runloop的状态
事件循环机制
有事做事,没事做时休息
RunLoop和NSTimer
用户滑动ScrollView时,RunLoop会转到UITrackingRunLoopMode,Mode发生切换,timer不会再生效了
- 解决方案
通过CFRunLoopAddTimer(CFRunLoopRef rl, CFRunLoopTimerRef timer, CFRunLoopMode mode )方法将timer添加到NSRunLoopCommonModes中
RunLoop与线程之间的关系
线程与RunLoop是一一对应的
自己创建的线程默认是没有RunLoop的
常驻线程
1、为当前线程开启一个RunLoop
2、向该RunLoop中添加一个Port/Source等维持RunLoop的时间循环
3、启动该RunLoop
保证子线程数据回来更新UI的时候不打断用户的滑动操作
可以把更新UI放到DefaultMode下,这样不会在TrackingMode下更新UI
网络
HTTP协议
超文本传输协议
- 请求/响应报文
- 连接建立流程
- HTTP特点
HTTPS与网络安全
TCP/UDP
DNS解析
域名到IP地址的映射,DNS解析请求采用UDP数据报,且明文
-
递归查询
我去给你问一下
image.png -
迭代查询
我告诉你谁可能知道
image.png -
DNS解析中常见的问题
1、DNS劫持问题
image.png
2、DNS解析转发问题
-
DNS劫持怎么解决
1、httpDNS:
使用DNS协议向DNS服务器的53端口进行请求(也就是使用HTTP协议向服务器的80端口进行请求,通过ip像服务端进行请求,返回给域名的ip地址)
2、长连接
image.png
Session/Cookie
对HTTP协议无状态特点的补偿
Cookie:保存在客户端,用来记录用户状态
设计模式
六大设计原则
- 单一职责原则
一个类只负责一件事(UIView和CALayer) - 依赖倒置原则
抽象不应该依赖具体实现,具体实现可以依赖于抽象(上层业务只依赖于提供的接口,并不关心内部实现) - 开闭原则
对修改关闭,对扩展开放 - 里氏替换原则
父类可以被子类无缝替换,且原有功能不受影响(KVO) - 接口隔离原则
使用多个专门的协议,而不是一个庞大臃肿的协议(UITableViewDelegate和UITableViewDataSource)
协议中的方法应该尽量少 - 迪米特法则
一个对象对其他对象有尽可能少的了解,高内聚低耦合
责任链
实际需求:对于业务A -> 业务B -> 业务C 变更为 业务C -> 业务B -> 业务A
业务C的nextBusinessObject为业务B,业务B的nextBusinessObject为业务C
桥接
适配器
单例
命令
架构/框架
模块化
分层
解耦
降低代码重合度
图片缓存框架
- 图片读写
以图片URL的单向Hash值作为key
内存方面淘汰策略:
1、队列先进先出
2、LRU算法,如30分钟内未使用过(触发时机每次进行图片查找时,后台切换到前台时)
磁盘设计:
1、存储方式
2、大小限制
3、淘汰策略(7天以上的删除)
网络设计:
1、图片请求的最大并发量
2、请求的超时策略(超时重试)
3、请求优先级
图片解码:
1、应用策略模式对不同图片格式进行解码
阅读时长统计框架
复杂页面架构
MVVM框架思想
-
ReactNative的数据流思想
任何一个子节点是没有权利做自己的变化更新的(打一个标记),必须把自己变化更新的消息传递给根结点,由根结点自顶向下的方式询问哪些结点需要更新
RN数据流思想 系统UIView更新机制的思想
FaceBook的开源框架AsyncDisplayKit关于预排班的设计思想
客户端整体架构
- 业务之间的解耦通信方式
OpenURL
依赖注入(通过中间层解耦)
算法
字符串反转
链表反转
有序数组合并
Hash算法
查找两个子视图的共同父试图
求无序数组当中的中位数
三方库
AFNetworking
SDWebImageView
ReactiveCocoa
- 信号
核心类RACSignal - 订阅