1.UI视图
UITableView的重用机制
UITableView的数据源同步解决方案
1.并发访问,数据拷贝(主线程标记删除的数据,子线程中删除一次)
2.串行访问 子线程数据回来时 同步数据删除
UIView和CALayer区别
UIView 提供内容 负责处理触摸等事件参与响应链
CALayer 负责显示内容contents
事件传递机制
从父视图到子视图
递归调用、倒序遍历
事件响应链
从子视图到父视图最终到UIApplicationDelegate,如果UIApplicationDelegate不响应,就什么都不做
图像显示原理
UI卡顿、掉帧的原因?
在规定的16.7ms内,CPU和GPU并没有完成下一帧画面的合成
滑动优化方案?
CPU
对象的创建、调整、销毁
预排版(布局计算、文本计算)
预渲染(文本等异步绘制,图片编解码)
GPU
纹理渲染 减少阴影圆角
视图混合 减少视图层级
UIView绘制原理
[UIView setNeedsDisplay]->[view.layer setNeedsDisplay]->[CALyer display]->layer.delegate respondsTo@selector(displayLayer)->NO->系统绘制流程
YES->异步绘制入口
系统绘制流程
异步绘制[layer.delegate displayLayer]
代理负责生成对应的bitmap
设置该bitmap为layer.contents属性的值
GPU渲染发生在哪里的问题
在屏渲染:GPU的渲染操作是在当前显示的屏幕缓冲区进行
离屏渲染:GPU的渲染操作是在当前显示的屏幕缓冲区外新开辟一个缓冲区中进行
何时会触发?
圆角maskToBounds同时设置时
图层蒙版
阴影
光栅化
为何要避免?
离屏渲染会增加GPU的工作量,会导致CPU和GPU的总耗时超过16.7ms,就会导致UI的卡顿和掉帧
2.OC语言
你用分类做了哪些事情?
声明私有方法
分解体积庞大的类文件
把Framework的私有方法公开
分类的特点?
运行时决议
有声明有实现
可以为系统类添加分类
分类中都可以添加哪些内容?实力方法、类方法、协议、属性(只是生成了setter、getter方法并没有生成实例变量)
分类的实现原理?
分类有多个的情况下,每个分类中都有一个同名的分类方法,最终哪个会生效?最后被编译的方法会生效
分类方法覆盖宿主类方法的原因?分类方法靠前然后是宿主类方法
名字相同的分类会引起编译报错
如何给分类添加成员变量?可以使用关联对象的技术为分类添加
使用关联对象的技术为分类添加的成员变量呗添加到哪里了?放在了同一个全局容器中,不同类的关联对象都放在了同一个全局容器字典中
怎样删除某个类的一个关联对象?objc_setAssociateObjects(nil)设置关联对象的值为nil
一般用扩展做什么?
声明私有属性、成员变量、方法
扩展的特点?
编译时决议
内部的私有声明,实现是写在宿主类中的
不能为系统类添加扩展
代理是什么?软件设计模式
代理和通知的区别?
代理是用代理模式实现的,传递方式是一对一
通知是使用观察者模式来实现的用于跨层传递消息的机制,传递方式是一对多
如何实现通知机制?
NSNotificationCenter中维护一个字典
{
notificationName:[observer1,observer2,...]
}
KVO是什么?
KVO是Key-Value observing的缩写,KVO是OC对观察者设计模式的又一体现,Apple使用了isa混写来实现KVO
KVO实现原理:调用了observeValueForKeyPath时,系统会动态创建NSKVONotifying_A这个类,并将原类的isa指针指向新创建的类,并重写新类的setter方法
- (void)setValue:(id)oj {
[self willChangeValueForKey:@"value"];
[super setValue:obj];//调用父类的实现,也即原类的实现
[self didChangeValueForKey:@"value"];
}
使用setter方法改变值KVO才会生效
通过KVC设置value能否生效?调用了setter方法,所以能生效
通过成员变量直接赋值value能否生效?不能生效,可以在前后手动添加willChangeValueForKey、didChangeValueForKey就可以生效
KVC?
setValaue:forKey:
valueForKey:
使用KVC是否会破坏面向对象的思想?会
valueForKey实现原理?valueForKey与setValaue:forKey:实现原理相似
Accessor Method is exist?-> no: accessInstanceVariablesDirectly ->no:valueForUndefinedKey:->NSUndefinedKeyException
KVC Accessor Method调用规则: 调用相似的访问器方法 <getKey> <key> <isKey>;
instance var调用规则:调用相似的实例变量_key,_isKey,key,isKey
属性关键字有哪些?
原子性:atomic,只保证赋值安全,不保证使用安全 nonatomic
引用计数:retain/strong、 assign/unsafe_unretained、weak、copy
asign和weak的区别?
assign 修饰基本数据类型,修饰对象类型时不改变其引用计数,修饰对象时被释放后仍指向原对象地址这时继续访问会产生悬垂指针即程序异常
weak 不改变修饰对象的引用计数,所指对象在被释放之后会自动置为nil
weak所指对象在被释放之后为什么会自动置为nil?
@property (copy) NSMutableArray *array;会产生什么问题?会导致崩溃
如果赋值过来的是NSMutaleArray,copy之后是NSArray,如果赋值过来的是NSArray,copy之后是NSArray,如果此时调用addObject:会崩溃,NSArray实例没有addObject这个方法
重在特点
浅拷贝:是对内存地址的复制,让目标对象指针和原对象指针指向同一片内存空间。 特点:会增加被拷贝对象的引用计数,无开辟新的内存空间
深拷贝:让目标对象指针和源对象指针指向两片内容相同的内存空间。 特点:不会增加被拷贝对象的引用计数,开辟了新的内存空间
copy
可变对象的拷贝都是深拷贝 包括copy和mutableCopy
不可变对象的copy是浅拷贝,mutableCopy是深拷贝
copy返回的都是不可变对象
MRC下如何重写retain修饰变量的retain方法?
@property (nonatomic,retain) id obj;
- (void)setObj:(id)obbj {
if (_obj != obj) {
[_obj release];
_obj = [_obj retain]; //如果无if判断此行会出现异常,因为上一行已经release调了
}
}
3.runtime
主要是消息传递和转发先关的
id=objc_object:isa操作、弱引用、关联对象、内存管理相关
class=objc_class:superClass、cache_t(哈希表理解成数组[bucket_t(SEL,IMP),...])、class_data_bits_t类的基本信息(属性、成员变量、方法列表、分类的属性、成员变量)。
class_data_bits_t封装了class_rw_t; class_rw_t:class_ro_t,methods,properties,protocols;
class_ro_t:name,methodList,ivars,properties,protocols
class是什么?class是对象,因为objc继承自objc_object,
isa是什么?
分为指针型的isa,isa的值代表class的地址 和 非指针型的isa,isa的值部分代表class的地址
函数四要素:名称 -> SEL name
返回值
参数 2个-> const char *types 3个-> struct method_t实际是堆函数四要素的封装
函数体 -> IMP imp
-(void)aMethod; -> v@: v -> void @ -> id即self : -> SEL
类对象、元类对象是什么?
类对象存储实例方法列表等信息,元类对象存储类方法列表等信息
对象、类对象、元类对象关系图?
关系图注意点:
Root class (class) superClass:nil
Root class (meta) superClass: Root class (class)
Subclass (meta)、 Superclass (meta)、 Root class (meta) isa:Root class (meta)
如果我们调用的类方法没有对应的实现,但是有同名的实例方法的实现,这个时候会不会发生崩溃、会不会产生实际的调用?不会崩溃,会产生实际调用
#import "Mobile.h"
@interface Phone:Mobile
@end
@interface Phone
- (id)init {
self = [super init];
if (self) {
NSLog(@"%@",NSStringFromClass([self class])); //消息的接受者是当前对象self,从当前对象的 类对象中 开始查找方法
NSLog(@"%@",NSStringFromClass([super class]));//消息的接受者是当前对象self,从当前对象的 父类对象中 开始查找方法
}
return self;
}
@end
打印的都是当前对象:Phone
消息传递流程是什么?
查找缓存 -> 查找当前类方法列表 -> 父类逐级查找 -> 消息转发流程
查找缓存方法:哈希查找,给定SEL查找bucket_t中的IMP
查找当前类方法列表:对于已排序好的列表,采用二分查找方法IMP ,对于没有排序的列表,采用一般遍历查找方法IMP
父类逐级查找:查找缓存 -> 查找当前类方法列表 ,直到NSObject的父类是nil结束
消息转发流程是什么?
resolveInstanceMethod -> 返回NO forwardingTargetForSelector -> 返回nil methodSignatureForSelector -> 返回方法签名 ->forwardInvocation -> unrecognized selectorb崩溃
-> 返回nil ->unrecognized selectorb崩溃
YES话都是消息已处理
你是否使用过performSelector这个方法?主要考察Runtime动态添加方法
resolveInstanceMethod中添加class_addMethod(self,@selector(test),testImp,"v@:")
void testImp (void) {
NSLog(@"test Invoke");
}
@dynamic是什么?
不需要编译器在编译时生成属性的getter、setter方法的具体实现,而是在运行时具体调用了getter、setter方法再去为它添加具体的实现
动态运行时语言将函数决议推迟到运行时 编译时语言在编译期进行函数决议
[obj foo]和objc_msgSend()函数之间有什么关系? [obj foo]被编译成objc_msgSend(obj,@selector(foo))
Runtime如何通过Selector找到对应的IMP地址的?消息传递流程
能否向编译后的类中增加实例变量?因为编译之前定义了这个类已经完成了实例变量的布局,class_ro_t没办法修改,所以不能
能否向动态添加的类中增加实例变量?在动态添加类的过程中,在注册方法之前完成实例变量的添加是能的
4.内存
内存布局是怎样的?
栈区:存放局部变量、函数参数 地址从高到低使用,栈底是高地址,栈顶是低地址,后入先出
堆区:存放对象 地址从低到高使用
静态(全局)区
常量区
代码区:编译后形成的二进制文件
iOS操作系统是怎样对内存进行管理的?iOS针对不同的场景会应用不同的内存管理方案
TaggedPointer, 小对象如NSNumber使用
NONPOINTER_ISA,非指针型的ISA
SideTabbles,散列表(本质是哈希表)包括弱引用表、引用计数表
SideTabbles结构 由多个 SideTabble组成,SideTabble包含:自旋锁spinlock_t,引用计数表RefcountMap,弱引用表weak_table_t
为什么不是一个SideTable?会导致效率问题,采用分离锁解决
我们通过一个对象的指针如何快速的定位到它属于哪张表中?哈希查找不用遍历,提高效率
spinlock_t:是忙等的锁,适用于轻量访问
RefcountMap:理解成字典,通过指针哈希查找引用计数
weak_table_t:理解成字典,通过指针哈希查找弱引用指针
什么是MRC?是通过手动引用计数来进行的内存管理
什么是ARC?是通过自动引用计数来进行的内存管理,是LLVM编译器和Runtime协作的结果,ARC下禁止手动调用retain/release/retainCount/dealloc,ARC中新增weak、strong关键字
引用计数管理
alloc实现:通过一系列调用,最终调用可C函数的calloc,此时并没有设置引用计数为1,原因?新alloc出来的对象引用计数表中是没有对应的值,所以这个值默认为0,然后向右偏移并+1
retain实现:
SideTable &table = SideTables()[this];
size_t &refcntStorage = table.refcnts[this];//size_t unsigned long
refcntStorage += SIDE_TABLE_RC_ONE;//SIDE_TABLE_RC_ONE 偏移量4,前两个位置不是存的引用计数
我们进行retain操作时系统是怎样查找它对应的引用计数的?是通过2次哈希查找到对应的引用计数然后进行+1操作
release实现:和retain相反的
SideTable &table = SideTables()[this];
RefcountMap::iterator it = table.refcnts.find(this);
it->second -= SIDE_TABLE_RC_ONE;
retainCount实现:
SideTable &table = SideTables()[this];
size_t refcnt_result = 1;
Refcountmap::iterator it = table.refcnts.find(this);
refcnt_result += it->second >> SIDE_TABLE_RC_SHIFT;//向右偏移并+1
dealloc实现:???
开始 ->_objc_rootDealloc()->rootDealloc()->nonpointer_isa、weakly_referenced、has_assoc、has_cxx_dtor、has_sideTable_rc是否可以释放 -> NO object_dispose() -> 结束
-> YES C函数free() -> 结束
object_dispose()实现:
开始 -> objc_destructInstance() -> C函数free() -> 结束
objc_destructInstance()实现:
开始 -> has_cxx_dtor? -> YES objct_cxxDestruct() / NO -> has_assoc? -> YES _object_remove_associstions() / NO -> clearDeallocating() -> 结束
clearDeallocating()实现:
开始 -> sidetable_clearDeallocating() -> weak_clear_no_lock() -> table_refcnt_erase() -> 结束
weak_clear_no_lock():指向该对象的弱引用指针置为nil, table_refcnt_erase():从引用计数表中擦除该对象引用计数
我们通过关联对象的技术为一个类添加了一些实例变量,在对象的dealloc方法中是否有必要对它的关联对象进行移除操作呢?在系统的dealloc内部实现当中会自动判断当前对象是否有关联对象,如果有系统内部会把关联对象一并移除掉。
为什么weak指针指向的对象在废弃之后会被自动置为nil?简:因为在dealloc内部实现中有做关于它相关的弱引用指针置为nil的操作的。
具:当对象被废弃之后,dealloc的内部实现当中会调用一个清除弱引用的方法,在清除弱引用的方法当中会通过哈希算法来查找被废弃对象在弱引用表中的位置,来提取它所对应的弱引用列表数组,然后进行for循环遍历把每一个weak指针都置为nil.
自动释放池
viewDidLoad中 NSMutableArray *array = [NSMutableArray array]; array是在什么时候被释放的?
在当次runloop将要结束的时候调用AutoreleasePoolPage::Pop(),把对应的array对象调用release方法释放
AutoreleasePool的实现原理是怎样的?什么是自动释放池?
是以栈为结点通过双向链表的形式组合而成,是和线程一一对应的.
AutoreleasePool为何可以嵌套使用?多次嵌套就是多次插入哨兵对象。
什么情况下需要我们手动创建AutoreleasePool呢?在for循环中alloc图片数据等内存消耗较大的场景手动插入AutoreleasePool。
AutoreleasePoolPage数据结构
id *next;
AutoreleasePoolPage *const parent;
AutoreleasePoolPage *child;
pthread_t const thread;
[obj autorelease]内部实现?
开始 -> next==栈顶? -> YES 增加一个栈结点到链表上 / NO -> 在新栈上add(obj) -> 结束
AutoreleasePoolPage::pop实现原理?
根据传入的哨兵对象找到对应位置,给上次push操作之后添加的对象依次发送release消息,回退next指针到正确位置。
三种循环引用:自循环引用、相互循环引用、多循环引用
考点:代理、Block重点、NSTimer重点、大环引用
如何破除循环引用?避免产生循环引用,在合适的时机手动断环
破除循环引用的具体方案有哪些?__weak、__block、__unsafe_unretained(与__weak效果等效)
__block破解注意:
MRC下,__block修饰对象不会增加引用计数,避免了循环引用;
ARC下,__block修饰对象会被强引用,无法避免循引用,需要手动解环
__unsafe_unretained破解:
修饰对象不会增加引用计数,避免了循环引用,如果被修饰对象在某一时机被释放会产生悬垂指针异常
你在平时开发过程中是否遇到过循环引用,你又是如何解决的?
NSTimer示例,创建一个中间对象持有2个弱引用变量NSTimer和原对象(原NSTimer的target),NSTimer的回调方法是在中间对象中实现的,然后在中间对象实现的NSTimer回调方法中对其所持有的target进行值的判断,如果当前值存在直接把NSTimer回调给原对象,如果当前对象已经被释放了设置NSTimer为无效状态,然后就可以解除当前线程的runloop对NSTimer的强引用以及NSTimer对中间对象的强引用。
Block示例,有2方面,当前block对当前对象的某一成员变量进行截获的话,这个block会对对应变量进行强引用,而当前对象又对当前block有强引用,就产生了自循环方式的引用,可以声明其为__weak变量变量来进行循环引用的消除。如果我们定义了__block说明符的话也会产生大环引用,但是是区分场景的,一种是在ARC下回产生循环引用,而在MRC不会,同时在ARC下可以通过断环的方式去解除对应循环引用但是有一个弊端,如果这个block一直不调用则没办法解除。
5.Block
什么是Block?
block是将(函数)及其(执行上下文)封装起来的(对象)。
什么是block的调用?block的调用就是函数的调用。
截获变量
基本数据类型的局部变量:截获其值
对象类型的局部变量:连同其所有权修饰符一起截获
静态局部变量:以指针形式截获
全局变量、静态全局变量:不截获
{
int multiplier = 6;
int(^Block)(int) = ^int(int num) {
return num * multiplier;
};
multiplier = 4;
NSLog(@"result is %d",Block(2));//12
}
{
static int multiplier = 6;
int(^Block)(int) = ^int(int num) {
return num * multiplier;
};
multiplier = 4;
NSLog(@"result is %d",Block(2));//8
}
什么情况下使用__block修饰符?
一般情况下,对被截获的(局部变量)进行(赋值)操作需添加__block修饰符.赋值 不等于 使用
{
NSMutableArray *array = [NSMutableArray array];
void(^Block)(void) = ^{
[array addObject:@123];//对array的操作是使用
};
Block();
}
{
NSMutableArray *array = nil;//需要在array声明处添加__block修饰符即: __block NSMutableArray *array = nil;
void(^Block)(void) = ^{
array = [NSMutableArray array];//对array的操作是赋值
};
Block();
}
什么情况下不使用__block修饰符?
对被截获的(静态局部变量、全局变量、静态全局变量)进行(赋值)操作不需添加__block修饰符.
{
__block int multiplier = 6;
int(^Block)(int) = ^int(int num) {
return num * multiplier;
};
multiplier = 4;//(multiplier.__forwarding->multiplier) = 4;即栈上的__forwarding指针指向的是堆上的__block变量内部的成员值
NSLog(@"result is %d",Block(2));//8
}
__forwarding指针存在的意义?
不论在任何内存位置,都可以顺利的访问同一个__block变量
Block的内存管理
Block的copy操作
栈Block的copy结果:堆block;
全局Block的copy结果:什么也不做;
堆Block的copy结果:增加引用计数;
何时需要对block进行copy操作?
声明一个对象的成员变量是block,在栈上创建一个block同时赋值给成员变量的block,如果成员变量的block没有使用copy而是assign,这时用成员变量去访问对应的block,可能由于栈对应的函数退出之后在内存当中就销毁掉了,这个时候再继续访问可能就会引起崩溃。
对栈上的block进行copy后,随着变量作用域的结束,栈上的block和__block变量会随之销毁,堆上的block和__block变量仍然存在,堆上的block在MRC下会产生内存泄漏
//__forwarding的理解
{
__block int multiplier = 10;//栈区的__block修饰就变成了对象
_blk = ^int(int num) { //将栈区的block copy到堆区
return num *multiplier;
};
multiplier = 6; //如果没有进行copy操作修改的就是栈上的multiplier变量。如果进行了copy操作,这句代码就是通过栈区的multiplier.__forwarding指针找到堆上所对应的multiplier对象的成员变量的multiplier的copy副本,对其副本进行修改改成6即 此处修改的是堆区的__block变量。
[self excuteBlock];
}
- (void)excuteBlock {
int result = _blk(4);
NSLog(@"result is %d %@",result,[_blk class]);//result is 24 __NSMallocBlock__
}
Block的循环引用
Block的循环引用 解除1 避免
_array = [NSMutableArray arrayWithObject:@"block"];
__weak NSArray *weakArray = _array;
_strBlk = ^ NSString *(NSString *num) {
//为什么加上__weak就可以避免自循环引用 对象类型的局部变量 block会连同其修饰符一同截获
return [NSString stringWithFormat:@"%@_%@",num,weakArray[0]];
};
NSLog(@"%@",_strBlk(@"hello"));//hello_block
Block的循环引用 解除2 断环
{
//这段代码会导致循环引用
// __block MCBlock *blockSelf = self;
// _blk = ^int(int num) {
// return num * blockSelf.var;//var = 2
// };
// NSLog(@"%d",_blk(3));//6
//断环 解除循环引用,弊端:如果这个block一直不调用则没办法解除
//__Block 在MRC不会产生循环引用 在ARC会产生循环引用 !!!!!!!!!!!!!!!!!!
__block MCBlock *blockSelf = self;
_blk = ^int(int num) {
int result = num * blockSelf.var;
blockSelf = nil;
return result;
};
NSLog(@"%d",_blk(3));//6
}
6.多线程
重要 重要 重要 只要是以同步方式去提交任务 不管是串行还是并行队列 都是在当前线程执行
//1.同步串行 结果产生死锁 线程内一行都不会执行 死锁原因:队列引起的循环等待
// dispatch_sync(dispatch_get_main_queue(), ^{
// [self doSomething];
// });
//可以正常运行,如果viewDidLoad也在serialQueue中执行就死锁了,跟第一种一样
dispatch_queue_t serialQueue = dispatch_queue_create("serial", DISPATCH_QUEUE_SERIAL);
dispatch_sync(serialQueue, ^{
[self doSomething];
});
//2.同步并发 打印12345
NSLog(@"1");
dispatch_queue_t global_queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_sync(global_queue, ^{
NSLog(@"2");
dispatch_sync(global_queue, ^{
NSLog(@"3");
});
NSLog(@"4");
});
NSLog(@"5");
//3.异步串行 打印 doSomething
dispatch_async(dispatch_get_main_queue(), ^{
[self doSomething];
});
//4.异步并发 打印13
dispatch_async(global_queue, ^{//异步分派到全局队列的block会在GCD底层线程池里边所维护的某个线程中去执行,而GCD底层所分派线程中的runloop默认是不开启的
NSLog(@"1");
//performSelector正常执行的前提是其所在当前线程runloop是开启的
[self performSelector:@selector(printLog) withObject:nil afterDelay:0];
NSLog(@"3");
});
- (void)doSomething {
NSLog(@"doSomething");
}
- (void)printLog {
NSLog(@"2");
}
怎样实现多读单写?
- (id)objectForKey:(NSString *)key {
__weak typeof(userCenterDic) weakUserCenterDic = userCenterDic;
__block id obj;
//同步读取指定数据 concurrent_queue特点就是 允许提交的任务并发执行
dispatch_sync(concurrent_queue, ^{
obj = [weakUserCenterDic objectForKey:key];
});
return obj;
}
- (void)setObject:(id)obj forKey:(NSString *)key {
__weak typeof(userCenterDic) weakUserCenterDic = userCenterDic;
//异步栅栏调用设置数据
dispatch_barrier_async(concurrent_queue, ^{;
[weakUserCenterDic setObject:obj forKey:key];
});
}
如何实现A、B、C三个任务并发,完成后执行任务D?
- (void)handle {
//创建一个group
dispatch_group_t group = dispatch_group_create();
//for循环遍历各个元素执行操作
for (NSURL *url in arrayURLs) {
//异步组分派到并发队列当中
dispatch_group_async(group, concurrent_queue, ^{
//根据url去下载图片
NSLog(@"url is %@",url);
});
}
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
//添加到数组中的所有任务执行完成之后会调用该Block
NSLog(@"所有图片已全部下载完成");
});
}
NSOperation有什么优势?
添加任务依赖,任务执行状态控制,最大并发量
任务状态的控制有哪些?
isReady、isExecuting、isFinished、isCanceled
我们应该怎样控制NSOperation状态? 看源码解读得到
如果只重写main方法,底层控制变更任务执行完成状态以及任务退出,如果重写了start方法,系统的任务状态控制都写在start里,所以要自行控制任务状态
系统是怎样移除一个isFinished=YES的NSOperation的?KVO
NSThread的实现原理?看源码解读得到
start() -> 创建pthread -> main() -> [target performSelector:selector] -> exit()
你在日常开发中都使用过那些琐?你又是如何使用的?没用不要随便说结合NSLock,NSRecursiveLock说即可
@synchronized 创建单例对象 保证多线程创建的对象唯一
atomic 赋值保证线程安全 使用不保证线程安全
@property (atomic) NSMutableArray *array;
self.array = [NSMutableArray array]; //赋值保证线程安全
self.array = [self.array addObject:obj];//使用不保证线程安全
OSSpinLock 自旋锁 循环等待询问,不释放当前资源,用于轻量级数据访问,简单的int值+1/-1操作
NSLock 重入易导致死锁,解决方案递归锁
- (void)methodA {
[lock lock];
[self methodB];
[lock unlock];
}
- (void)methodB {
[lock lock];
//操作逻辑
[lock unlock];
}
NSRecursiveLock 递归锁:可以重入
- (void)methodA {
[recursiveLock lock];
[self methodB];
[recursiveLock unlock];
}
- (void)methodB {
[recursiveLock lock];
//操作逻辑
[recursiveLock unlock];
}
dispatch_semaphore_t 信号量
调用信号量阻塞了是主动行为还是被动行为?是主动行为
唤醒是一个被动行为
iOS系统为我们提供的几种多线程技术各自的特点是怎样的?
提供了GCD、NSOperation、NSThread3种。GCD用来实现一些简单的线程同步,子线程的分派,多读单写。NSOperation AFN和SD里用到添加移除依赖、控制状态。NSThread用来实现常驻线程。
7.Runloop
什么是Runloop?
Runloop是通过内部维护的事件循环来对事件/消息进行管理的一个对象。
事件循环是什么?状态切换
没有消息需要处理时,休眠以避免资源占用 用户态 -> 内核态,有消息需要处理时,立刻被唤醒 内核态 -> 用户态。
runloop是如何做到有事做事,没事休息的?
我们在调用CFRunloopRun方法后,会调用系统的mach_msg函数,同时发生了用户态到核心态的切换,然后当前线程处于休眠状态,所以做到做到有事做事,没事休息。
数据结构
NSRunloop是对CFRunloop的封装,提供了面向对象的API.
CFRunloop:
pthread,//一一对应
currentMode ,//CFRunloopMode
modes ,//NSMutabbleSet <CFRunloopMode *>
commonModes ,//NSMutabbleSet <NSString *>
comonModeItems //Source/Timer/Observer
CFRunloopMode:
name //NSDefaultRunloopMode,
sources0,//NSMutableSet
sources1,//NSMutableSet
observers,//NSMutableArray
timers. //NSMutableArray
CFRunloopSource
source0和source1有什么区别?
source0 需要手动唤醒线程
source1 具备唤醒线程的能力
CFRunloopTimer 基于事件的定时器,和NSTimer是toll-free Bridged的。
CFRunloopObserver
我们可以监测Runloop的哪些时间点呢?
kCFRunloopEntry、kCFRunloopBeforeTimers、kCFRunloopBeforeSources、
kCFRunloopBeforeWaiting用户态到内核态、kCFRunloopAfterWaiting内核态到用户态、kCFRunloopExit
各个数据结构之间的关系?
-> Source(m)
Runloop(1) -> Model(n) -> Timer(m)
-> Observer(m)
Runloop为什么会有多个Model?
隔离source、timer、observer
NSRunloopCommonModes有使用过吗?你是怎么理解的?
不是实际存在的一种mode,是同步Source/Timer/Oberver到多个Mode中的一种技术方案。
事件循环的实现机制
如何唤醒一个处于休眠状态的runloop?Source1,Timer事件,外部手动唤醒
APP从点击一个图标到程序启动运行到最终退出过程中,系统都做了什么?
我们调用了mian()函数之后会调用UIApplicationMain,在这个函数内部会启动主线程的runloop,然后经过一系列的处理最终我们主线程的runloop处于休眠状态,若此时点击一个屏幕会产生一个MachPort,基于MachPort最终会转成source1可以把主线程唤醒运行处理,之后把程序杀死实际上就会发生Runloop的退出,这时会发送一个通知runloop即将退出,runloop退出后线程就销毁掉了。
滑动TableView的时候我们的定时器还会生效吗?不会,可以将NSTimer添加到commonMode,即将NSTimer同步到多个mode中。
Runloop与多线程有什么关系?
Runloop与线程是一一对应的,自己创建的线程默认是没有Runloop的。
怎样实现一个常驻线程?
为当前线程开启一个Runloop,向该Runloop中添加一个Port/Source等维持Runloop的事件循环,启动该Runloop。
怎样保证子线程数据回来更新UI的时候不被打断用户的滑动操作?UI更新加到Default模式下。
把子线程抛回给主线程进行UI更新的这块逻辑包装起来提交到主线程的Default模式下面,这样抛回来的任务处于tracking模式下面就不会执行,当我们手停止滑动操作之后当前线程就切换到了Default的模式下面了,这时才会处理子线程抛回给主线程的任务
8.网络
HTTP的请求方式都有哪些
get post head put delete options
get post 的区别
get 获取资源 安全的 幂等的 可缓存的
post 处理资源 非安全的 非幂等的 不可缓存的
安全的:不应该引起Server端的任何状态变化
幂等的:同一个请求方法执行一次和执行多次的效果完全相同
可缓存的:多次请求可能获取到的是一个缓存结果
状态码:
1xx
2xx 请求成功
3xx 网络重定向
4xx 客户端异常
5xx 服务端异常
HTTP请求过程:
三次握手->http请求报文->http响应报文->四次挥手
http的特点:
无连接:http的持久连接 不用重新经历三次握手和四次挥手
持久连接头部字段 Connection:keep-alive是否是持久连接 time:20持续时间 max:10最大次数
无状态(用户端通过HTTP再次请求服务端添加购物车时,服务端无法记住用户):Cookie/Session
持久连接怎样判断一个请求是否结束的
响应报文头部字段 Content-length 1024
chunked 最后会有一个空的chunked
charles抓包原理? 中间人攻击
https和http区别
https=http+ssl/tls
ssl/tls在应用层和传输层之间
补充:七层模型:应用层、表示层、会话层、传输层、网络层、数据链路层、物理层
https建立流程是怎样的?
商定加密算法->非对称加密加密对称秘钥->对称加密
HTTPS都使用了哪些加密手段 为什么
连接建立过程使用非对称加密
后续通信使用对称加密
UDP特点:无连接、尽最大努力交付(不变保证可靠传输)、面向报文的(既不合并也不拆分,小了不合并大了不拆分)
功能:复用、分用、差错检测
TCP特点:面向连接、可靠传输、面向字节流、流量控制、拥塞控制
为什么要进行三次握手?
为了避免多次建立连接
可靠传输:无差错、不丢失、不重复、按序到达(滑动窗口协议解决 接收窗口未按序到达的那部分数据不能提交给应用层)
可靠传输是通过停止等待协议来实现的,通过四方面来理解无差错情况、超时重传(客户端发送报文丢了)、确认丢失(服务端确认报文丢了 服务端要丢弃重传报文重新发送确认报文)、确认迟到(服务端确认报文迟到了 客户端收下迟到的确认但什么也不做)
面向字节流:TCP可以自己决定一次发几个字节的数据
流量控制:滑动窗口协议解决
拥塞控制:
慢开始、拥塞避免 只发送一个报文->指数规律增长->门限初始值->线性规律增长->网络拥塞 ->只发送一个报文->指数规律增长->新的门限值(网络拥塞值的1\2)
快恢复、快重传 变化为新的门限值(网络拥塞值的1\2) 而不是原来的门限值
了解DNS解析吗?
域名到IP的地址的映射,DNS解析请求采用UDP数据报且明文
DNS解析查询方式
递归查询:我去给你问一下
迭代查询:我告诉你谁可能知道
DNS解析存在哪些常见问题?
DNS劫持问题:篡改
DNS解析转发问题:跨网访问请求缓慢
DNS劫持与HTTP的关系是怎样的?
是完全没有关系的,因为DNS解析是发生在HTTP建立连接之前
怎样解决DNS劫持?
httpDNS
使用DNS协议向DNS服务器的53端口进行请求->使用HTTP协议向DNS服务器的80端口进行请求
长连接:用户端和长连Server建立长连接,长连Server和DNS Server是内网专线
Session/Cookie:HTTP协议无状态特点的补偿
Cookie是什么?
用来记录用户状态且保存在客户端,区分用户
服务端是如何将Cookie传给客户端的呢?
服务端设置HTTP响应报文的Set-Cookie首部字段
客户端发送的Cookie在HTTP请求报文的Cookie首部字段中
怎样修改Cookie?
新Cookie覆盖旧Cookie。覆盖规则:name、path、domian等需要与原Cookie一致
怎样删除Cookie? expire maxAge
新Cookie覆盖旧Cookie。覆盖规则:name、path、domian等需要与原Cookie一致
设置Cookie的expire=过去的一个时间点或者maxAge=0
怎样保证Cookie的安全?
对Cookie进行加密处理
只在HTTPS上携带Cookie
设置Cookie为httpOnly,防止跨站脚本攻击
Session是什么?
用来记录用户状态且保存在服务端,区分用户
Session和Cookie的关系是怎样的?记住sessionId这个例子
Session需要依赖于Cookie机制
9.设计模式
你都知道哪些设计原则,请谈谈你的理解?
单一职责原则 CALayer 和 UIView
接口隔离原则 UITaleViewDelegate UITaleViewDataSourceDelegate
里氏替换原则 KVO实现原理 即父类可以被子类无缝替换,且原有功能不受任何影响
开闭原则 对修改关闭 拓展开放
依赖倒置原则 抽象不必依赖具体实现 具体实现依赖抽象 增删改查不依赖里边具体存储方案
迪米特法则 一个对象应该对其他对象尽可能少的了解
责任链模式
一个关于需求变更的问题
业务A -> 业务B -> 业务C 变成更成 业务C -> 业务B -> 业务A
@interface BusinessObject : NSObject
@property (nonatomic,strong) BusinessObject *nextBusiness;//责任链模式构成的关键
@end
桥接模式
一个关于业务解耦的问题
列表数据来源于网络数据B1、B2、B3数据需要并存。桥接模式可以解除列表和多套数据耦合的问题。
能否用一幅图简单的表示桥接模式的主体结构?
抽象类 ClassB是抽象类 ClassA 的属性,A1、A2、A3是ClassA的子类,B1、B2、B3是ClassB的子类。
适配器模式:说白了就是再在被适配类上边封装一层,分为对象适配器、类适配器
一个现有类需要适应变化的问题
//适配对象
@interface CoolTarget : NSObject
//被适配对象
@property (nonatomic,strong) Target *target;
//对原有方法包装
- (void)request;
@end
单例模式
请你手写一个单利模式?
+ (id)shareInstance {
static Mooc *instance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
instance = [[super allocWithZone:NULL]init];//注意点1
});
return instance;
}
//重写方法【必不可少】注意点2
+ (id)allocWithZone:(struct _NSZone *)zone {
return [self shareInstance];
}
//重写方法【必不可少】注意点3
- (id)copyWithZone:(struct _NSZone *)zone {
return self;
}
什么是命令模式?它是用来做行为参数化的。行为例如转发评论赞
命令模式有什么作用? 降低代码重合度
UI事件传递机制是怎样实现的?你对其中运用到的设计模式是怎么理解的?
UI事件传递机制是用责任链模式实现的,一个类的成员变量是当前类的类型。
10.架构/框架
框架和机构是为了解决什么问题?
模块化,分层,解耦,降低代码重合度
怎样设计一个图片缓存框架?
Manager,内存,磁盘,网络,code manager,图片解码,图片压缩/解压缩
图片通过什么方式进行读写,过程是怎么样的?
以图片URL的单向Hash值作为key存储在对应框架中,内存 -> 磁盘 ->网络下载 (这里使用了多级缓存来提高查找效率)
设计图片缓存框架中内存设计上需要考虑哪些问题?存储size,淘汰策略
存储size通过什么数据结构去设计?队列10kb以下50个,队列100kb以下20个,队列100kb以上10个。
具体怎么去设计淘汰策略?以队列先进先出的方式进行淘汰;LRU算法即最近最久未使用算法(如30min之内是否使用过)
定时检察,提高检查触发频率,每次进行读写时,前后台切换时,注意开销。
磁盘设计需要考虑哪些问题?
存储方式,大小限制,淘汰策略(如某一图片存储时间距今已超过7天)。
网络部分的设计需要考虑哪些问题?
图片请求的最大并发量,请求超时策略(两次都失败就不请求了),请求的优先级(当前请求的图片是都是用户最需要的)。
对不同格式的图片,解码采用什么方式来做?策略模式
在哪个阶段做图片解码处理?
磁盘读取后,网络请求返回后
整体图片缓存框架的工作流程?内存 -> 磁盘 -> 网络
怎样设计一个时长统计框架?
记录器:页面式,流式如头条新闻,自定义如微博横滑式的新闻;
记录管理者:记录缓存,磁盘存储,上传器
为何要有不同类型的记录器,你的考虑是什么?
基于不同分类场景提供关于记录的封装、适配。
记录的数据会由于某种原因丢失,你是怎样处理的?问的是如何降低丢失率而不是保证不丢失
定时写磁盘,如每满50条进行一次写入可以降低丢失率;
限定内存缓存条数(如10条),超过该条数,即写磁盘。
记录上传器关于延时上传的具体场景有哪些?
前后台切换,从无网到有网的变化,通过轻量级的接口稍带
记录上传器上传时机是怎样把控的?
立刻上传,延时上传,定时上传
复杂页面架构如微博APP正文页
整体架构 MVVM相似模式 view、viewController、viewModel <->桥接模式 Engine、Model
视图层(View&ViewController)
view:控件初始化、设置数据、交互实践代理
viewController:视图的创建、组合,协调逻辑,事件的回调处理
业务逻辑层(ViewModel)
业务逻辑处理预排版,数据增删改查封装者,线程安全处理
数据层(Engine&Model)
网络请求,数据解析,增删改查,本地处理逻辑(对server数据的适配)
数据流
网络数据 -Engine-> 业务数据 -ViewModel-> UI数据 ->ViewController-> View
数据及数据关系
网络数据(网络接口数据):一级评论,二级评论,转评赞,广告、推荐等其他数据;
业务数据:int type ,id data根据type知道是一级评论还是二级评论;
UI数据:imageObj,labelObj(frame value)value是text的值
网络数据、UI数据是业务数据的成员变量,UI数据右对业务数据有一个弱引用。
反向更新
复杂页面架构借鉴的四个思想不能生搬硬套:
MVVM框架思想,ReactNative的数据流思想,系统UIView更新机制的思想,Faceook开源框架AsyncDisplayKit关于预排版的设计思想
MVVM框架思想理解?
View ViewModel Model,View包括:view和viewControler(是视图层因为我们添加的大多数控件都是在viewControler添加的)
ReactNative的数据流思想?
视图组合可以看成一个多叉树
任何一个子节点是没有权利做自己的变化更新的,它必须得把这种变化更新的消息传递给根节点,由根节点自顶向下的去询问哪些需要更新,相当于一个主动行为变成了一个被动行为。
你所在公司的客户端整体架构是怎么样的?原则?
独立于APP的通用层:时长统计框架、崩溃统计、网络三方库 独立于当前APP
通用业务层:当下公司的自定义的路径控件、特殊UIImage的封装
中间层:实现业务ABCD协调和解耦的作用
业务A、业务B、业务C、业务D。
原则是上层可以依赖下层,下层不可以依赖上层。
各个业务之间是如何解耦的呢?
OpenURL,
依赖注入:业务A需要使用业务C的某个方法,可以通过中间层来做业务A和C解耦,业务C可以通过注入的方式把自己注入到中间层,业务A去中间层获取它所依赖的方法成员变量。例如业务C要在中间层中注册一个协议,同时需要实现代理方法返回给中间层具体的实例对象,业务A使用的时候可以事先和业务C的同学商量好协议从中间层中根据某一方法获取遵循某一协议的实例,在业务A中可以把这个实例当做一个完全遵循这个协议的透明对象使用,这样就解除了业务A和业务C的耦合。
11.算法
->能不能答出来决定了要不要你
1.字符串反转->头尾指针向中间移动的思想 注意理解算法思想
void char_reverse(char *cha) {
//指向第一个字符
char *begin = cha;
//指向最后一个字符
char *end = cha + strlen(cha) - 1;
while (begin < end) {
//交换前后两个字符,同时移动指针
char temp = *begin;
*(begin++) = *end;
*(end--) = temp;
}
}
char ch[] = "hello,world";
char_reverse(ch);
printf("reverse result is %s \n",ch); //reverse result is dlrow,olleh
2.单链表反转->头插法思想实现!!!!!!!
//定义一个链表
struct Node {
int data;
struct Node *next;
};
//链表反转 头插法
truct Node *reverseList(struct Node *head) {
//定义遍历指针,初始化为头结点
struct Node *p = head;
//反转后的链表头部
struct Node *newH = NULL;
//遍历链表
while (p != NULL) {
//记录下一个结点
struct Node *temp = p->next;
//当前结点的next指向新链表的头部
p->next = newH;
//更改新链表头部为当前结点
newH = p;
//移动p指针
p = temp;
}
//返回反转后的链表头结点
return newH;
}
//构造一个链表
struct Node *constructList(void) {
//头结点定义
struct Node *head = NULL;
//记录当前尾节点
struct Node *cur = NULL;
for (int i = 1; i < 5; i ++) {
struct Node *node = malloc(sizeof(struct Node));
node->data = i;
//头结点为空,新结点即为头结点
if (head == NULL) {
head = node;
}
else {
//当前结点的next为新结点
cur->next = node;
}
//设置当前结点为新结点
cur = node;
}
return head;
}
//打印链表中的数据
void printList(struct Node *head) {
struct Node *temp = head;
while (temp != NULL) {
printf("node is %d \n",temp->data);
temp = temp->next;
}
}
struct Node *head = constructList();
printList(head);
printf("--------\n");
struct Node * newHead = reverseList(head);
printList(newHead);
printf("--------\n");
node is 1
node is 2
node is 3
node is 4
--------
node is 4
node is 3
node is 2
node is 1
--------
3.有序数组合并,之后仍然是有序的
void mergeList(int a[], int aLen, int b[], int bLen, int result[]) {
int p = 0; //遍历数组a的指针
int q = 0; //遍历数组b的指针
int i = 0; //记录当前存储位置
while (p < aLen && q < bLen) {//任一数组没有达到边界则进行遍历
if (a[p] <= b[q]) {//如果a数组对应位置的值小于b数组对应位置的值
result[i] = a[p];//存储a数组的值
p++;//移动a数组的遍历指针
}
else {
result[i] = b[q];//存储b数组的值
q++;//移动b数组的遍历指针
}
i++;//指向合并结果的下一个存储位置
}
while (p < aLen) {//如果a数组有剩余
result[i] = a[p++];//将a数组剩余部分拼接到合并结果的后面
i++;
}
while (q <bLen) {//如果b数组有剩余
result[i] = b[q++];//将b数组剩余部分拼接到合并结果后面
i++;
}
}
int a[5] = {1,4,6,7,9};
int b[8] = {2,3,5,6,8,10,11,12};
int result[13];//用于存储归并结果
mergeList(a, 5, b, 8, result);//归并操作
printf("merge result is ");
for (int i = 0; i < 13; i ++) {
printf("%d ",result[i]); //merge result is 1 2 3 4 5 6 6 7 8 9 10 11 12
}
4.Hash算法 //具体场景:查找第一个只出现一次的字符
算法思路:说白了数组下标是ASCII码值,内容是字符出现次数
字符(char)是一个长度为8的数据类型,因此总共有256种可能;
每个字母根据其ASCII码值作为数组的下标对应数组的一个数字;
数组中存储的是每个字符出现的次数。
例如:给定值是字母a,对应ASCII值是97,数组索引下标为97,对应数组内存储字母a出现的次数.
存储和查找都通过该函数,可有效提高查找效率。
char findFirstChar(char *cha) {
char result = '\0';
int array[256];//定义一个数组 用来存储各个字母出现次数
for (int i = 0; i < 256; i ++) {
array[i] = 0;
}
char *p = cha;//定义一个指针 指向当前字符串头部
while (*p != '\0') {//遍历每个字符
array[*(p++)]++;//在字母对应存储位置 进行出现次数+1操作
}
p = cha;//将p指针重新指向字符串头部
while (*p != '\0') {//遍历每个字母的出现次数
if(array[*p] == 1) {//遇到第一个出现次数为1的字符,打印结果
result = *p;
break;
}
p++;//反之继续向后遍历
}
return result;
}
char cha[] = "abaccdeff";
char fc = findFirstChar(cha);
printf("\nthis char is %c \n",fc); //this char is b
5.查找两个子视图的共同父视图!!!!!!!!!!!!!!
- (NSArray *)findCommonSuperView:(UIView *)view other:(UIView *)viewOther {
NSMutableArray *result = [NSMutableArray array];
NSArray *arrayOne = [self findSuperViews:view];//查找第一个视图的所有的父视图
NSArray *arrayOther = [self findSuperViews:viewOther];//查找第二个视图的所有的父视图
int i = 0;
while (i < MIN((int)arrayOne.count, (int)arrayOther.count)) {//越界限制条件
UIView *superOne = [arrayOne objectAtIndex:arrayOne.count - i - 1];//倒叙方式获取各个视图的父视图
UIView *superOther = [arrayOther objectAtIndex:arrayOther.count - i - 1];
if (superOne == superOther) {//比较如果相等 则为共同父视图
[result addObject:superOne];
i++;
}
else {
break;//如果不相等,则结束遍历
}
}
return result;
}
- (NSArray <UIView *> *)findSuperViews:(UIView *)view {
UIView *temp = view.superview;//初始化第一个父视图
NSMutableArray *result = [NSMutableArray array];//保存结果的数组
while (temp) {
[result addObject:temp];
temp = temp.superview;//顺着superView指针一直向上查找
}
return result;
}
6.求无序数组当中的中位数
排序算法(冒泡、快排、堆)+中位数(n为奇数时,中位数是中间一个数(n+1)/2,n为奇数时,中位数是中间2个数((n/2) + (n/2+1))/2);
利用快排思想:选取关键字,高低交替扫描。
+ (double)findMiddle:(NSMutableArray *)arr {
[self quickSort:arr low:0 high:(int)arr.count - 1];
double valueMid = 0;
if (arr.count % 2 == 0) {//n为偶数
valueMid = ([arr[arr.count / 2] intValue] + [arr[arr.count / 2 - 1] intValue]) / 2.0;
}
else {//n为奇数
valueMid = [arr[(arr.count - 1) / 2] intValue];
}
return valueMid;
}
//快排
+ (void)quickSort:(NSMutableArray *)arr low:(int)low high:(int)high {
if (arr == nil || arr.count == 0) {
return;
}
if (low >= high) {
return;
}
int middle = low + (high - low) / 2;
NSNumber *prmt = arr[middle];
int i = low;
int j = high;
while (i <= j) {
while ([arr[i] intValue] < [prmt intValue]) {
i ++;
}
while ([arr[j] intValue] > [prmt intValue]) {
j --;
}
if (i <= j) {
[arr exchangeObjectAtIndex:i withObjectAtIndex:j];
i ++;
j --;
}
}
if (low < j) {
[self quickSort:arr low:low high:j];
}
if (i < high) {
[self quickSort:arr low:i high:high];
}
}
NSMutableArray *list1 = @[@3,@10,@8,@6,@7,@11,@13,@9].mutableCopy;//3 6 7 8 9 10 11 13
double median1 = [MedianFind findMiddle:list1];
printf("the median is %f \n",median1);//the median is 8.500000
12.第三方
1.AFN
AFN的整体框架是什么样的?
会话NSURLSession、网络监听模块、网络安全模块;请求序列化、响应序列化;UIKit集成模块(分类控件);
主要类的关系图?
AFURLSessionManager:NSURLSession、AFSecurityPolicy、AFNetworkRechabilityManager
子类AFHTTPSessionManager:AFURLRequestSerialization、AFURLResponseSerialization
AFURLSessionManager都负责哪些工作?
创建和管理NSURLSession、NSURLSessionTask(网络请求);
实现NSURLSessionDelegate等协议的代理方法如重定向;
引入AFSecurityPolicy保证请求安全;
引入AFNetworkRechabilityManager监控网络状态;
2.SDW一个异步下载图片并且支持缓存的框架
框架是什么样的?
UIImageView+WebCache;
SDWebImageManager;
SDImageCache(管理内存缓存和磁盘缓存)、SDWebImageDownloader
加载图片的流程?
内存缓存 -> 磁盘缓存 -> 网络下载
3.Reactive Cocoa 函数响应式编程框架
信号:Reactive Cocoa中的核心类RACSignal
RACStream -子类-> RACSignal -子类-> RACDynamicSignal、RACReturnSignal、RACEmptySignal、RACErrorSignal
对于信号我们应该怎么理解?
信号代表一连串的状态(,在状态改变时,对应的订阅者RACSubscriber就会收到通知执行响应的指令。)
订阅
订阅的流程?解读源码得到的
start -> RACSignal -> -subscribeNext: -> RACSubscriber -> -sendNext: -> -sendCompleted
4.AsyncDisplayKit提升iOS界面渲染性能的一个框架
主要做了什么?减轻主线程的压力,把更多的事情能挪到子线程去做就都挪到子线程去做,主要是3方面,UI布局,渲染,对象。
layout(解决布局的耗时运算):文本宽高计算、视图布局计算;
rendering(渲染):文本渲染、图片解码、图形绘制;
UIKit Objects(对象):对象创建、调整、销毁。
AsyncDisplayKit基本原理?
说白了就是对UIView进行了一层包装,再把耗时操作放在后台线程。
ASNode -.view->UIView -.layer-> CALayer
<-.node- <-.delegate->
后台线程 主线程
针对ASNode的修改和提交,会对其进行封装并提交到一个全局容器中,ASDK也在RunLoop中注册了一个Observer,当RunLoop进入休眠前,ASDK执行该loop内提交的所有任务。
网友评论