底层原理一: (OC 本质、KVC、KVO、Category、Block)
底层原理二: (Runtime、Runloop)
底层原理三 : (多线程、内存管理)
底层原理四 : (性能优化、架构)
底层原理五 : (面试题目整理)
1. 一个OC对象占用多少内存
系统分配了16个字节给NSObject对象(通过malloc_size函数获得)
但NSObject对象内部只使用了8个字节的空间(64bit环境下,可以通过class_getInstanceSize函数获得)
2. 对象的isa指针指向哪里?
instance对象的isa指向class对象
class对象的isa指向meta-class对象
meta-class对象的isa指向基类的meta-class对象
3.OC的类信息存放在哪里?
对象方法、属性、成员变量、协议信息,存放在class对象中
类方法,存放在meta-class对象中
成员变量的具体值,存放在instance对象
4.iOS用什么方式实现对一个对象的KVO?(KVO的本质是什么?)
- 利用RuntimeAPI动态生成一个子类,并且让instance对象的isa指向这个全新的子类
- 当修改instance对象的属性时,会调用Foundation的_NSSetXXXValueAndNotify函数
willChangeValueForKey:
父类原来的setter
didChangeValueForKey:
- 内部会触发监听器(Oberser)的监听方法(observeValueForKeyPath:ofObject:change:context:)
5.如何手动触发KVO?
手动调用willChangeValueForKey:和didChangeValueForKey:
//
- (void)viewDidLoad {
[super viewDidLoad];
Person *person = [[Person alloc]init];;
[p addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:nil];
[p willChangeValueForKey:@"name"];
[p didChangeValueForKey:@"name"];
}
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{
NSLog(@"被观测对象:%@, 被观测的属性:%@, 值的改变: %@\n, 携带信息:%@", object, keyPath, change, context);
}
6.直接修改成员变量会触发KVO么?
不会触发KVO
7.通过KVC修改属性会触发KVO么?
会触发KVO
KVC在赋值时候,内部会触发监听器(Oberser)的监听方法(observeValueForKeyPath:ofObject:change:context:) 发送通知
8.KVC的赋值和取值过程是怎样的?原理是什么?
KVC的全称是Key-Value Coding,俗称“键值编码”,可以通过一个key来访问某个属性
调用 setValue:forKey:
setKey,_setKey ->找到了则进行赋值,未找到调用 accessInstanceVarlableDirctly 是否运行 修改值,返回YES
调用_key, _isKey, key, isKey 进行赋值
9.Category的使用场合是什么?
- 在不修改原有类代码的情况下,为类添对象方法或者类方法
- 或者为类关联新的属性
- 分解庞大的类文件
使用场合:
- 添加实例方法
- 添加类方法
- 添加协议
- 添加属性
- 关联成员变量
10.Category的实现原理
Category编译之后的底层结构是struct category_t,里面存储着分类的对象方法、类方法、属性、协议信息
在程序运行的时候,runtime会将Category的数据,合并到类信息中(类对象、元类对象中)
11.Category和Class Extension的区别是什么?
Class Extension在编译的时候,它的数据就已经包含在类信息中
Category是在运行时,才会将数据合并到类信息中
12.Category中有load方法吗?load方法是什么时候调用的?load 方法能继承吗?
- 有load方法
- load方法在runtime加载类、分类的时候调用
- load方法可以继承,但是一般情况下不会主动去调用load方法,都是让系统自动调用
13. initialize方法如何调用,以及调用时机
- 当类第一次收到消息的时候会调用类的initialize方法
- 是通过 runtime 的消息机制 objc_msgSend(obj,@selector()) 进行调用的
- 优先调用分类的 initialize, 如果没有分类会调用 子类的,如果子类未实现则调用 父类的
13. load、initialize方法的区别什么?它们在category中的调用的顺序?以及出现继承时他们之间的调用过程?
- load 是类加载到内存时候调用, 优先父类->子类->分类
- initialize 是类第一次收到消息时候调用,优先分类->子类->父类
- 同级别和编译顺序有关系
- load 方法是在 main 函数之前调用的
14. Category能否添加成员变量?如果可以,如何给Category添加成员变量?
不能直接给Category添加成员变量,但是可以间接实现Category有成员变量的效果
Category是发生在运行时,编译完毕,类的内存布局已经确定,无法添加成员变量(Category的底层数据结构也没有成员变量的结构)
可以通过 runtime 动态的关联属性
15. block的原理是怎样的?本质是什么?
block 本质其实是OC对象
block 内部封装了函数调用以及调用环境
16. __block的作用是什么?有什么使用注意点?
如果需要在 block 内部修改外部的 局部变量的值,就需要使用__block 修饰(全局变量和静态变量不需要加__block 可以修改)
__block 修饰以后,局部变量的数据结构就会发生改变,底层会变成一个结构体的对象,结构内部会声明 一个 __block修饰变量的成员, 并且将 __block修饰变量的地址保存到堆内存中. 后面如果修改 这个变量的值,可以通过 isa 指针找到这个结构体,进来修改 这个变量的值;
可以在 block 内部修改 变量的值
17. block的属性修饰词为什么是copy?使用block有哪些使用注意?
block 一旦没有进行copy操作,就不会在堆上
使用注意:循环引用问题 (外部使用__weak 解决)
17. block在修改NSMutableArray,需不需要添加__block?
如果是操作 NSMutableArray 对象不需要,因为 block 内部拷贝了 NSMutableArray对象的内存地址,实际是通过内存地址操作的
如果 NSMutableArray 对象要重新赋值,就需要加__block
18. Block 内部为什么不能修改局部变量,需要加__block
通过查看Block 源码,可以发现, block 内部如果单纯使用 外部变量, 会在 block 内部创建同样的一个变量,并且将 外部变量的值引用过来..(只是将外部变量值拷贝到 block 内部), 内部这个变量和外部 实际已经没关系了
从另一方面分析,block 本质也是一个 函数指针, 外部的变量也是一个局部变量,很有可能 block 在使用这个变量时候,外部变量已经释放了,会造成错误
加了__block 以后, 会将外部变量的内存拷贝到堆中, 内存由 block 去管理.
19.讲一下 OC 的消息机制
OC中的方法调用其实都是转成了objc_msgSend函数的调用,给receiver(方法调用者)发送了一条消息(selector方法名)
objc_msgSend底层有3大阶段
消息发送(当前类、父类中查找)、动态方法解析、消息转发
19.1 消息发送流程
当我们的一个 receiver(实例对象)收到消息的时候, 会通过 isa 指针找到 他的类对象, 然后在类对象方法列表中查找 对应的方法实现,如果 未找到,则会通过 superClass 指针找到其父类的类对象, 找到则返回,未找打则会一级一级往上查到,最终到NSObject 对象, 如果还是未找到就会进行动态方法解析
类方法调用同上,只不过 isa 指针找到元类对象;
19.1 动态方法解析机制
当我们发送消息未找到方法实现,就会进入第二步,动态方法解析: 代码实现如下
// 动态方法绑定- 实例法法调用
+ (BOOL)resolveInstanceMethod:(SEL)sel{
if (sel == @selector(run)) {
Method method = class_getInstanceMethod(self, @selector(test));
class_addMethod(self, sel, method_getImplementation(method), method_getTypeEncoding(method));
return YES;
}
return [super resolveInstanceMethod:sel];
}
// 类方法调用
+(BOOL) resolveClassMethod:(SEL)sel....
20.消息转发机制流程
未找到动态方法绑定,就会进行消息转发阶段
// 快速消息转发- 指定消息处理对象
- (id)forwardingTargetForSelector:(SEL)aSelector{
if (aSelector == @selector(run)) {
return [Student new];
}
return [super forwardingTargetForSelector:aSelector];
}
// 标准消息转发-消息签名
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
if(aSelector == @selector(run))
{
return [NSMethodSignature signatureWithObjCTypes:"v@:"];
}
return [super methodSignatureForSelector:aSelector];
}
- (void)forwardInvocation:(NSInvocation *)anInvocation{
//内部逻辑自己处理
}
21.什么是Runtime?平时项目中有用过么?
Objective-C runtime是一个`运行时`库,它为Objective-C语言的动态特性提供支持,我们所写的OC代码在运行时都转成了runtime相关的代码,类转换成C语言对应的结构体,方法转化为C语言对应的函数,发消息转成了C语言对应的函数调用。通过了解runtime以及源码,可以更加深入的了解OC其特性和原理
OC是一门动态性比较强的编程语言,允许很多操作推迟到程序运行时再进行
OC的动态性就是由Runtime来支撑和实现的,Runtime是一套C语言的API,封装了很多动态性相关的函数
平时编写的OC代码,底层都是转换成了Runtime API进行调用
22.runtime具体应用
利用关联对象(AssociatedObject)给分类添加属性
遍历类的所有成员变量(修改textfield的占位文字颜色、字典转模型、自动归档解档)
交换方法实现(交换系统的方法)
利用消息转发机制解决方法找不到的异常问题
23.打印结果分别是什么?
1.png[self class] 和 [super class] 都是给当前类返送消息,spuer 表示在父类中查找
[self superClass] 和 [super superclass] 也是也当前类发消息,返回父类
第一个打印:
MJStudent / MJStudent/ MJerson / MJPerson
isKindOfClass 表示对象是否为当前类或者子类的 类型
isMemberOfClass 表示是否为当前类的的类型
isMemberOfClass 分为- 对象方法 和+ 类方法2中
- (bool)isMemberOfClass; 比较的是类对象
+ (bool)isMemberOfClass; 比较的是元类
第二个打印:
1 ,0, 0, 0
24.以下代码能不能执行成功?如果可以,打印结果是什么?
2.png打印结果: <ViewController: 0x7f9396c16300>
25.讲讲 RunLoop,项目中有用到吗?
runloop运行循环,保证程序一直运行,主线程默认开启
用于处理线程上的各种事件,定时器等
可以提高程序性能,节约CPU资源,有事情做就做,没事情做就让线程休眠
应用范畴:
定时器,事件响应,手势识别,界面刷新,以及autoreleasePool 等等
26.runloop内部实现逻辑?
3.png实际上 RunLoop 就是这样一个函数,其内部是一个 do-while 循环。当你调用 CFRunLoopRun() 时,线程就会一直停留在这个循环里;直到超时或被手动停止,该函数才会返回。
27.runloop和线程的关系?
每条线程都有唯一的一个与之对应的RunLoop对象
RunLoop保存在一个全局的Dictionary里,线程作为key,RunLoop作为value
线程刚创建时并没有RunLoop对象,RunLoop会在第一次获取它时创建
RunLoop会在线程结束时销毁
主线程的RunLoop已经自动获取(创建),子线程默认没有开启RunLoop
28.timer 与 runloop 的关系?
timer 定时器,是基于 runloop 来实现的, runloop 在运行循环当中,监听到了定制器 就会执行;所以 timer 需要添加到 runloop 中去, 注意子线程的 runloop 默认是不开启的,如果在子线程执行 timer 需要手动开启 runloop
29.程序中添加每3秒响应一次的NSTimer,当拖动tableview时timer可能无法响应要怎么解决?
将 timer 对象添加到 runloop 中,并修改 runloop 的运行 mode
NSTimer *timer = [NSTimer timerWithTimeInterval:1 repeats:YES block:nil];
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
30.runloop 是怎么响应用户操作的, 具体流程是什么样的?
不明白问题想问什么?
31.说说runLoop的几种状态
添加Observer监听RunLoop的所有状态
4.png32.runloop的mode作用是什么?
runloop 只能在一种 mode 下运行, 做不同的事情,runloop 会切换到对应的 model 下来执行,默认是 kCFRunLoopDefaultMode 如果视图滑动再回切换到 UITrackingRunLoopMode,如果需要在多种 mode 下运行则需要手动设置 kCFRunLoopCommonModes;
1. kCFRunLoopDefaultMode:App的默认Mode,通常主线程是在这个Mode下运行
2. UITrackingRunLoopMode:界面跟踪 Mode,用于 ScrollView 追踪触摸滑动,保证界面滑动时不受其他 Mode 影响
3. UIInitializationRunLoopMode: 在刚启动 App 时第进入的第一个 Mode,启动完成后就不再使用,会切换到kCFRunLoopDefaultMode
4. GSEventReceiveRunLoopMode: 接受系统事件的内部 Mode,通常用不到
5. kCFRunLoopCommonModes: 这是一个占位用的Mode,作为标记kCFRunLoopDefaultMode和UITrackingRunLoopMode用,并不是一种真正的Mode
33.你理解的多线程?
同一时间,CPU 只能处理理一条线程, 只有一条线程在⼯工作 多线程并发执行,其实是 CPU 快速的在多条线程之间调度(切换) 如果 CPU 调度线程的时间⾜足够快, 就造成了多线程并发执⾏的假象
优势
充分发挥多核处理器的优势,将不同线程任务分配给不同的处理器,真正进入“⾏行 计算”状态
弊端
新线程会消耗内存控件和cpu时间,线程太多会降低系统行性能。
34.iOS的多线程方案有哪几种?你更倾向于哪一种?
5.png倾向于GCD ,简单灵活,使用方便
35.你在项目中用过 GCD 吗?
使用过
GCD中有2个用来执行任务的函数
用同步的方式执行任务
dispatch_sync(dispatch_queue_t queue, dispatch_block_t block);
queue:队列
block:任务
用异步的方式执行任务
dispatch_async(dispatch_queue_t queue, dispatch_block_t block);
36.GCD 的队列类型
GCD的队列可以分为2大类型
并发队列(Concurrent Dispatch Queue)
可以让多个任务并发(同时)执行(自动开启多个线程同时执行任务)
并发功能只有在异步(dispatch_async)函数下才有效
串行队列(Serial Dispatch Queue)
让任务一个接着一个地执行(一个任务执行完毕后,再执行下一个任务)
37.说一下 OperationQueue 和 GCD 的区别,以及各自的优势
1> GCD是纯C语⾔言的API,NSOperationQueue是基于GCD的OC版本封装
2> GCD只⽀支持FIFO的队列列,NSOperationQueue可以很⽅方便便地调整执⾏行行顺 序、设 置最⼤大并发数量量
3> NSOperationQueue可以在轻松在Operation间设置依赖关系,⽽而GCD 需要写很 多的代码才能实现
4> NSOperationQueue⽀支持KVO,可以监测operation是否正在执⾏行行 (isExecuted)、 是否结束(isFinished),是否取消(isCanceld)
5> GCD的执⾏行行速度⽐比NSOperationQueue快 任务之间不不太互相依赖:GCD 任务之间 有依赖\或者要监听任务的执⾏行行情况:NSOperationQueue
38.线程安全的处理手段有哪些?
1.加锁
2.同步执行
39.OC你了解的锁有哪些?在你回答基础上进行二次提问;
os_unfair_lock ios10 开始
OSSpinLock ios10 废弃
dispatch_semaphore 建议使用,性能也比较好
dispatch_mutex
dispatch_queue 串行
NSLock 对 mutex 封装
@synchronized 性能最差
40.追问一:自旋和互斥对比?
什么情况使用自旋锁比较划算?
预计线程等待锁的时间很短
加锁的代码(临界区)经常被调用,但竞争情况很少发生
CPU资源不紧张
多核处理器
什么情况使用互斥锁比较划算?
预计线程等待锁的时间较长
单核处理器
临界区有IO操作
临界区代码复杂或者循环量大
临界区竞争非常激烈
41.追问二:使用以上锁需要注意哪些?
注意死锁
在串行队列使用同步,容易造成死锁
42.追问三:用C/OC/C++,任选其一,实现自旋或互斥?口述即可!
两种锁的加锁原理:
互斥锁:线程会从sleep(加锁)——>running(解锁),过程中有上下文的切换,cpu的抢占,信号的发送等开销。
自旋锁:线程一直是running(加锁——>解锁),死循环检测锁的标志位,
43.请问下面代码的打印结果是什么?
6.png打印 1,3
performSelector after 是基于 timer 定制器,定时器又是基于 runloop 实现的
任务2在子线程中,子线程默认 runloop 是不开启的,所以不执行2
44.请问下面代码的打印结果是什么?
7.png打印1
start 执行完,线程就销毁了.任务 test 没法执行了
45.使用CADisplayLink、NSTimer有什么注意点?
CADisplayLink 保证调用频率和刷帧频率一直,60FPS, 不用设置时间间隔,每秒钟60次
可以使用 proxy 代理解决循环引用
CADisplayLink、NSTimer会对target产生强引用,如果target又对它们产生强引用,那么就会引发循环引用
46.介绍下内存的几大区域
低地址-> 高地址
保留->代码段->数据段(字符串常量,已初始化全局数据,未初始化数据)>堆->栈内存-> 内核区域
代码段: 编译之后的代码
数据段: 字符串常量,已经初始化的全局变量,或者静态变量,未初始化的全局变量,静态变量
堆 (低>高) 通过 alloc malloc calloc 动态分配的内存
栈 (高地址 从 低地址) 函数调用开销()
47.讲一下你对 iOS 内存管理的理解
在iOS中,使用引用计数来管理OC对象的内存
一个新创建的OC对象引用计数默认是1,当引用计数减为0,OC对象就会销毁,释放其占用的内存空间
调用retain会让OC对象的引用计数+1,调用release会让OC对象的引用计数-1
内存管理的经验总结
当调用alloc、new、copy、mutableCopy方法返回了一个对象,在不需要这个对象时,要调用release或者autorelease来释放它
想拥有某个对象,就让它的引用计数+1;不想再拥有某个对象,就让它的引用计数-1
可以通过以下私有函数来查看自动释放池的情况
extern void _objc_autoreleasePoolPrint(void);
48.ARC 都帮我们做了什么?
LLVM + Runtime 会为我们代码自动插入 retain 和 release 以及 autorelease等代码,不需要我们手动管理
49.weak指针的实现原理
Runtime维护了一个weak表,用于存储指向某个对象的所有weak指针。weak表其实是一个hash(哈希)表,Key是所指对象的地址,Value是weak指针的地址(这个地址的值是所指对象的地址)数组。
runtime对注册的类, 会进行布局,对于weak对象会放入一个hash表中。 用weak指向的对象内存地址作为key,当此对象的引用计数为0的时候会dealloc,假如weak指向的对象内存地址是a,那么就会以a为键, 在这个weak表中搜索,找到所有以a为键的weak对象,从而设置为nil。
50.autorelease对象在什么时机会被调用release
iOS在主线程的Runloop中注册了2个Observer
-第1个Observer监听了kCFRunLoopEntry事件,会调用objc_autoreleasePoolPush()
-第2个Observer
监听了kCFRunLoopBeforeWaiting事件,会调用objc_autoreleasePoolPop()、objc_autoreleasePoolPush()
监听了kCFRunLoopBeforeExit事件,会调用objc_autoreleasePoolPop()
objc_autoreleasePoolPop()调用时候回给 pool 中的对象发送一次 release 消息
51.方法里有局部对象, 出了方法后会立即释放吗
如果是普通的 局部对象 会立即释放
如果是放在了 autoreleasePool 自动释放吃,则会等runloop 循环,进入休眠前释放
52.思考以下2段代码能发生什么事?有什么区别?
8.png第一个内存会暴涨,self.name 会不停的创建
第二个内存固定,会使用 Tagged Pointer 将值存在地址中
53.你在项目中是怎么优化内存的?
内存优化可以从 内存泄漏 和 内存开销 2方面入口
- 减少内存泄露
可以使用静态分析以及instruments的leaks 分析
注意 NStimer 以及 block ,delegate 等的使用,避免循环引用
- 降低内存使用峰值
1. 关于图片加载占用内存问题:imageNamed: 方法会在内存中缓存图片,用于常用的图片。
imageWithContentsOfFile: 方法在视图销毁的时候会释放图片占用的内存,适合不常用的大图等。
2.tableView cell 尽量使用重用机制,减少额外的开销
3.tableView 列表图片展示尽量使用缩略图
4.延迟加载 对象,节约内存开销
5.避免短时间大量创建对象,配合 autoreleasePool 减少内存峰值
6.重用大开销对象,比如: NSDateFormatter和NSCalendar
7.加载 html 尽量使用 wkwebView
8.单例使用不易过多
9.线程最大并发数
54.优化你是从哪几方面着手?
卡顿优化
启动优化
耗电量优化
app 瘦身
CPU 占用率、 内存使用情况、网络状况监控、启动时闪退、卡顿、FPS、使用时崩溃、耗电量监控、流量监控....
55.列表卡顿的原因可能有哪些?你平时是怎么优化的?
1.最常用的就是cell的重用, 注册重用标识符
如果不重用cell时,每当一个cell显示到屏幕上时,就会重新创建一个新的cell;
如果有很多数据的时候,就会堆积很多cell。
如果重用cell,为cell创建一个ID,每当需要显示cell 的时候,都会先去缓冲池中寻找可循环利用的cell,如果没有再重新创建cell
2.避免cell的重新布局
cell的布局填充等操作 比较耗时,一般创建时就布局好
如可以将cell单独放到一个自定义类,初始化时就布局好
3.提前计算并缓存cell的属性及内容
当我们创建cell的数据源方法时,编译器并不是先创建cell 再定cell的高度
而是先根据内容一次确定每一个cell的高度,高度确定后,再创建要显示的cell,滚动时,每当cell进入凭虚都会计算高度,提前估算高度告诉编译器,编译器知道高度后,紧接着就会创建cell,这时再调用高度的具体计算方法,这样可以方式浪费时间去计算显示以外的cell
4.减少cell中控件的数量
尽量使cell得布局大致相同,不同风格的cell可以使用不用的重用标识符,初始化时添加控件,
不适用的可以先隐藏
5.不要使用ClearColor,无背景色,透明度也不要设置为0
渲染耗时比较长
6.使用局部更新
如果只是更新某组的话,使用reloadSection进行局部更新
7.加载网络数据,下载图片,使用异步加载,并缓存
8.少使用addView 给cell动态添加view
9.按需加载cell,cell滚动很快时,只加载范围内的cell
10.不要实现无用的代理方法,tableView只遵守两个协议
11.缓存行高:estimatedHeightForRow不能和HeightForRow里面的layoutIfNeed同时存在,这两者同时存在才会出现“窜动”的bug。所以我的建议是:只要是固定行高就写预估行高来减少行高调用次数提升性能。如果是动态行高就不要写预估方法了,用一个行高的缓存字典来减少代码的调用次数即可
12.不要做多余的绘制工作。在实现drawRect:的时候,它的rect参数就是需要绘制的区域,这个区域之外的不需要进行绘制。例如上例中,就可以用CGRectIntersectsRect、CGRectIntersection或CGRectContainsRect判断是否需要绘制image和text,然后再调用绘制方法。
13.预渲染图像。当新的图像出现时,仍然会有短暂的停顿现象。解决的办法就是在bitmap context里先将其画一遍,导出成UIImage对象,然后再绘制到屏幕;
14.使用正确的数据结构来存储数据。
56.app 启动优化
1\. pre-main 之前
* 排查无用的动态库(定期清理)
* 减少ObjC类(项目中不适用的的库,废弃的代码等)、方法(selector)、分类(category)的数量、无用的库
* 少在类的+load方法里做事情,尽量把这些事情推迟到+initiailize1.
2\. main 函数之后的 didFinishLaunchingWithOptions 加载完之前
* 不影响用户体验的操作,做延迟加载,不要全部放在 didFinishLaunchingWithOptions中去做
* 版本更新,一些三方初始化,不需要在 didFinishLaunchingWithOptions 初始化的放到,界面展示完以后再初始化
* 一些网络请求延迟 请求..
* 一些业务逻辑延迟 加载
* 初始化第三方 SDK
* 配置 APP 运行需要的环境
* 自己的一些工具类的初始化
57.app 耗电量优化
1.不要频繁的刷新页面,能刷新1行cell最好只刷新一行,尽量不要使用reloadData.
2.选择正确的集合
NSArray,使用index来查找很快(插入和删除很慢)
字典,使用键来查找很快
NSSets,是无序的,用键查找很快,插入/删除很快
3.少用运算获得圆角,必须要用圆角的话,不如把图片本身就做成圆角
4.懒加载,不要一次性创建所有的subview,而是需要时才创建.
5.重用机制
6.图片处理
图片与imageView相同大小,避免多余运算
可以使用整副的图片,增加应用体积,但是节省CPU
可调大小的图片,可以省去一些不必要的空间
CALayer,CoreGraphics,甚至OpenGL来绘制,消耗CPU
7.cache,cache,cache(缓存所有需要的)
服务器相应结果的缓存(图片)
复杂计算结果的缓存(UITableView的行高)
8.尽量少用透明或半透明,会产生额外的运算.
9.使用ARC减少内存失误,dealloc需要重写并对属性置为nil
10.避免庞大的xib,storyBoard,尽量使用纯代码开发
CPU层面
1.Timer的时间间隔不宜太短,满足需求即可
2.线程适量,不宜过多,不要阻塞主线程
3.优化算法,减少循环次数
4.定位和蓝牙按需取用,定位之后要关闭或降低定位频率
5.一些硬件的使用,不使用就关掉
58.app 的包瘦身
9.png59.讲讲 MVC、MVVM、MVP,以及你在项目里具体是怎么写的?
MVC Model-view-controller 数据-视图-控制器
一般控制器用于管理数据和视图, 数据和视图交互都是通过控制器来进行的.视图和数据进行了解耦, 但是我们日常使用经常会将模型绑定给视图.模型封装在视图内部,外部不用管理视图内部业务逻辑,这数据 mvc 的变种, 控制器只给视图模型数据就好了. 缺点是视图和 模型有耦合;
MVVM Model-view-viewModel 模型-视图-视图模型
view 和 model 的交互通过viewmodel 来进行交互,实现数据的双向绑定
MVP Model-view - Presenter 模型-视图-主持人
view 和 model 的交互通过Presenter,controller通过Presenter来管理 model 和 View
60.你自己用过哪些设计模式?
结合自己项目来讲吧
61.一般开始做一个项目,你的架构是如何思考的?
根据模块,使用 mvc 功能划分..结合自己项目讲比较容易
涉及到东西也比较多,比较杂,大到整个项目架构,小到一个 view 的架构;没具体的答案
网友评论