1.Runloop和线程的关系
1、一一对应,主线程的 runloop 已经创建,子线程的必须手动创建。
2、runloop 在在第一次获取时创建,在线程结束时销毁
系统默认注册 5 个 Mode:
kCFRunLoopDefaultMode、UITrackingRunLoopMode、NSRunLoopCommonModes
2.自动释放池什么时候释放?
第一次创建:启动 runloop 时候
最后一次销毁:runloop 退出的时候
其他时候的创建和销毁:当 runloop 即将睡眠时销毁之前的释放池,重新创建一个新的。
3.什么情况下使用 weak 关键字,和 assign 的区别?
1、ARC中,循环引用的地方
2、自定义 IBOutlet 控件属性
区别:weak 非持有关系 必须用于OC对象,assign 用于基本数据类型
4.怎么用 copy 关键字
1、NSString、NSArray、NSDictionary经常使用 copy 关键字,为了确保他们的可变类型之间进行赋值时,对象中字符串值不会随意变动,设置新属性时拷贝一份。
2、block 也使用 copy
5.@property (copy) NSMutableArray *array; 这种写法会出什么问题?
1、添加,删除数组内元素时崩溃,因为 copy 复制了一个不可变的 NSArray
2、默认使用了 atomic 属性 严重影响性能
6.如何让自己的类用 copy 修饰符,即让对象具备拷贝功能
具体步骤:
1、让该类遵从 NSCopying 或 NSMutableCopying 协议
2、实现 NSCopying 协议,该协议只有一个方法:- (id)copyWithZone:(NSZone *)zone;
7.@property 的本质是什么?ivar、setter、getter如何生成并添加到类中的?
本质:实例变量+getter方法+setter方法
在编译期自动生成 getter 和 setter,还自动向类中添加适当类型的实例变量
8.多线程
1、NSThread线程的生命周期:线程任务执行完毕之后被释放。
2、线程安全(加互斥锁):防止多线程抢夺资源造成的数据安全问题,但是耗费 CPU 性能
- atomic:原子属性 为 setter 加锁,线程安全 耗资源
- nonatomic:非原子属性,不会给 setter 加锁,非线程安全
3、线程间通信
- NSThread 实现
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(nullable id)arg waitUntilDone:(BOOL)wait; - (void)performSelector:(SEL)aSelectoronThread:(NSThread *)thr withObject:(nullable id)arg waitUntilDone:(BOOL)wait;
- GCD 实现
主线程和子线程相互切换 - NSOperationQueue 实现
4、GCD(任务、队列)
- 同步异步的区别
同步:只能在当前线程执行任务,不能开启新线程
异步:可在新的线程中执行任务,可以开启新线程 - 队列
并发队列:可多个任务并发(同时)执行,只在异步函数下有效
串行队列:一个任务执行完毕后,再执行下一个任务
主队列:主队列的任务都在主线程中执行
runloop 的应用:- 自动释放池
- performSelector
- 常驻线程
9.@protocol 和 category 中如何使用@property?
1、在 protocol 中使用 property 只会生成 getter 和 setter 的方法声明,目的是为了遵循该协议的对象实现该属性。
2、category 也只是生成方法声明,如果真要增加属性实现需要借助两个函数:
- objc_setAssociatedObject
- objc_getAssociatedObject
10.@property 中有哪些属性关键字?
1、原子性:nonatomic 特质
2、读/写权限: readwrite(读写)、readonly(只读)
3、内存管理语义:assign、strong、weak、copy、unsafe_unretained
4、方法名:getter=<name>、setter=<name>
11.weak 属性需要在 delloc 中置 nil 吗?
不需要,ARC会帮我们处理,即使编译器不帮忙,runtime 内部已经实现了
12.@synthesize 和 @dynamic 分别有什么作用?
@property 对应这两个,没写就默认@synthesis var = _var
1、@synthesize 语义就是编译器自动帮你实现 getter、setter
2、@dynamic 就是告诉编译器不要自动生成,自己要手动实现 getter、setter
13.ARC下,不显式指定属性关键字,默认的关键字有哪些?
1、基本数据类型:atomic、readwrite、assign
2、普通 OC 对象:atomic、readwrite、strong
14.用@property 修饰的 NSString(或NSArray、NSDictionary)经常使用 copy 关键字,为什么?换成 strong 会怎样?
1、因为父类指针可以指向子类对象,使用的 copy 的目的是为了对象的属性不受外界影响,不管你传入的是可变还是不可变对象,我本身持有的是一个不可变的副本。
2、copy 和 mutableCopy
- [immutableObject copy] // 浅拷贝
- [immutableObject mutableCopy] // 深复制
- [mutableObject copy] // 深复制
- [mutableObject mutableCopy] // 深复制
15.@synthesize 合成实例变量的规则是什么?
规则:
1、如果指定了成员变量的名称,会生成一个指定名称的成员变量
2、如果这个成员已经存在了就不在生成
3、如果 @synthesize foo;还会生成同名的成员变量,如果 @synthesize foo = _foo; 就不会再生成了。
16.除了自动合成属性实例变量外,@synthesize 还有那些使用场景
1、重写只读属性的 getter 时
2、使用了 @dynamic 时
3、在 @protocol 中定义的所有属性
4、在 category 中定义的所有属性
17.objc 中向一个 nil 发送消息会发送什么?
在 Objective-C 中向 nil 对象发送消息是完全有效的,只是在运行时没有任何作用
18.objc 中向一个对象发送消息 [obj foo] 和 obj_msgSend()函数之间有什么关系
[obj foo];
在 objc 动态编译时,每个方法在运行时都会被动态的转为消息发送,即为: objc_msgSend(obj, @selector(foo));
19.什么时候会报 unrecognized selector 的异常?
当调用对象上的某个方法时,而发现对象没有实现这个方法
objc 在向一个对象发送消息时,runtime 库会根据对象的 isa指针找到该对象所属的类,然后在该类的方法列表和其父类方法列表中寻找方法运行,如果在最顶层的父类也没有找到对应方法,程序就会在运行时挂掉并抛出异常 unrecognized selector
20.一个 objc 对象如何进行内存布局?
1、所有父类的成员变量和自己的成员变量都会存放在该对象所对应的存储空间中。
2、每一个对象内部都有一个 isa 指针,指向它的类对象
3、类对象中存放着本对象的:
- 对象方法列表
- 成员变量列表
- 属性列表
- 类对象内部还有个 isa 指针,指向它的元对象(meta class),元对象内部存放着类方法列表
- 类对象内部还有个 superclass 指针,指向它的父类对象
21.一个 objc 对象的 isa 指针指向什么?有什么作用?
指向它的类对象,从而找到对象上的方法
22.runtime 如何通过 selector找到对应的 IMP 地址?
每个类对象都有一个方法列表,方法列表上记录着方法名称、方法实现、参数类型,selector 本质上就是方法名称,通过这个方法名称就可以在方法列表上找到对应的方法实现。
23.使用 runtime Associate 方法关联的对象,需要在主对象 dealloc 的时候释放吗?
无论在 ARC 还是 MRC 下均不需要
被关联的对象在生命周期内要比对象本身释放的晚很多,他们会在 NSObject -dealloc 调用的 objc_dispose() 方法中释放。
24.objc 中的类方法和实例方法有什么本质区别和联系?
1、类方法
- 类方法是属于类对象的
- 类方法只能通过类对象调用
- 类方法中不能访问成员变量
- 类方法可以调用其他类方法
- 类方法中不能直接调用对象方法
2、实例方法
- 实例方法是属于实例对象
- 实例方法只能通过实例对象调用
- 实例方法中可以访问成员变量
- 实例方法中直接调用实例方法
- 实例方法中也可以调用类方法(通过类名)
25._objc_msgForward 函数是做什么的,直接调用它会发生什么?
_objc_msgForward 是一个函数指针(和 IMP 类型一样),用于消息转发的:当向一个对象发送一条消息,它并没有实现的时候,_objc_msgForward 就会尝试做消息转发。
26.runtime 如何实现 weak 变量的自动置 nil ?
runtime 对注册的类会进行布局,对于 weak 对象会放入一个 hash 表中。用 weak 指向的对象内存地址作为 key,当此对象的引用计数变为 0 时候会 dealloc,此时用这个 key 在 hash 表找到 weak 对象置 nil。
27.能否向编译后得到的类中增加实例变量?能否在运行时创建的类中添加实例变量?为什么?
1、不能向编译后得到的类中增加实例变量
2、能向运行时创建的类中添加实例变量
3、因为编译后的类已经在 runtime 中注册,实例变量的内存大小已经确定
4、运行时创建的类可以调用 class_addIvar 函数来添加实例变量
28.runloop 和线程有什么关系
runloop 是线程的基础架构部分,每个线程包括程序主线程都有与之对应的 runloop 对象。
1、主线程的 runloop 默认是启动的
2、其他线程的 runloop 需要手动配置和启动
3、当前线程的 runloop 可以NSRunLoop *runloop = [NSRunLoop currentRunLoop];
获取
29.runloop 的 mode 作用是什么?
mode 主要是用来指定事件在运行循环中的优先级的,分为:
- NSDefaultRunLoopMode(kCFRunLoopDefaultMode ):默认 空闲状态
- UITrackingRunLoopMode:ScrollView滑动
- UIInitializationRunLoopMode:启动时
- NSRunLoopCommonModes(kCFRunLoopCommonModes):mode 集合
苹果公开提供的有:
- NSDefaultRunLoopMode(kCFRunLoopDefaultMode)
- NSRunLoopCommonModes(kCFRunLoopCommonModes)
30.以 scheduledTimerWithTimeInterval 的方式触发的 Timer,在滑动页面列表时,timer 会暂停回调,为什么?如何处理?
1、runloop 只能运行在一种 mode 下,NSTimer 在主线程运行,处于的 mode 是 NSDefaultRunLoopMode,滑动页面是 需要切换到 UITrackingRunLoopMode 保证滑动流畅,这就导致 NSTimer 不被继续调度。
2、可以讲 timer 添加到 NSRunLoopCommonModes 来解决问题
31.猜想 runloop 内部是如何实现的?
一般来讲,一个线程只执行一个任务,执行完成后线程就会退出。如果需要一个机制,让线程随时处理任务但并不退出,通常的代码逻辑就是 do while 循环,只要传递的消息不为 nil,就一直循环下去。
32.objc 使用什么机制管理对象内存?
通过 retainCount 机制来决定对象是否需要释放。每次 runloop 的时候,都会检查对象的 retainCount,为0的话说明没地方使用需要使用个对象了,可以释放了
33.ARC 通过什么方式帮助开发者管理内存?
主要在编译期和运行期两部分共同帮助开发者管理内存
1、编译期,ARC 用的是更底层的 C接口实现的 retain、release、autorelease
2、ARC 也包含运行期组件,这个地方做的优化比较复杂,但也不能忽略
34.BAD_ACCESS 在什么情况下出现?
访问了野指针,
- 对一个已经释放的对象执行 release
- 访问已经释放了的对象的成员变量或发消息
- 死循环
35.使用 block 时,什么情况会出现循环引用,如何解决?
一个对象强引用了 block,block 又强引用了对象,就会发生循环引用。
解决方法就是使用 __weak
或者 __block
修饰符修饰之后再在 block 中使用。
36.在 block 内如何修改 block 外部变量?
block 不允许修改外部变量的值(栈中指针的内存地址),__block
只要观察到该变量被 block 所持有,就将“外部变量” 栈中内存地址放到堆中,block 内部变量会被 copy 到堆区,进而在 block 内部可以修改外部变量的值。
37.苹果是如何实现 autoreleasepool 的?
autoreleasepool 是以一个队列数组的方式实现,主要通过下列3个函数:
- objc_autoreleasepoolPush
- objc_autoreleasepoolPop
- objc_autorelease
看函数名就知道,分别对 autoreleasepool 执行 push,和 pop 操作,销毁对象时执行 release 操作。
38.不手动指定 autoreleasepool 的情况下,一个 autorelease 对象什么时候释放?
分两种情况:手动干预释放时机、系统自动去释放
1、手动干预释放时机:当前作用域大括号结束时释放
2、系统自动去释放:autorelease 对象出了作用域之后,会被添加到最近一次创建的自动释放池中,并会在当前 runloop 迭代结束时释放。
39.使用系统的某些 block API 时,是否也要考虑循环引用?
UIView 的 block版本写动画时不需要考虑,因为 循环引用是双向的强引用,而他们是单向强引用。
GCD 和 NSNotificationCenter 时需要小心,比如 GCD 内部引用了 self
40. GCD 的队列(dispatch_queue_t)分哪两种类型?
1、串行队列( Serial Dispatch Queue)
2、并行队列(Concurrent Dispatch Queue)
41.如何用 GCD 同步若干个异步操作?
异步加载多张图片,然后在都下载完成后合成一张整图
使用 Dispatch Group 追加 block 到 Dispatch Group queue 中
,这些 block 如果都执行完毕,就会执行 Main Dispatch Queue 中的 结果处理的block
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, queue, ^{ /*加载图片1*/ });
dispatch_group_async(group, queue, ^{ /*加载图片2*/);
dispatch_group_async(group, queue, ^{ /*加载图片3*/});
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
// 合并图片
});
42.dispatch_barrier_async 的作用是什么?
在并行队列中,barrier 能够保持某些任务的顺序,避免数据竞争等问题。
注意:barrier 只能搭配自定义并行队列,不能使用 global_queue,否则 dispatch_barrier_async 和 dispatch_async 作用一样。
43.苹果为何废弃 dispatch_get_current_queue?
dispatch_get_current_queue 容易造成死锁
44.addObserver:forKeyPath:options:context:各个参数的作用分别是什么?observer 需要实现哪个方法才能获得 KVO 回调?
// 添加键值观察
/*
1.观察者,负者监听事件的对象
2.观察的属性
3.观察的选项
4.上下文
*/
[self.person addObserver:self
forKeyPath:@"name"
options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld
context:@"Person Name"];
observer 中需要实现一下方法:
// 所有的 kvo 监听到事件都会调用此方法
/*
1.观察的属性
2.观察的选项
3.change 属性变化字典(新 / 旧)
4.上下文,与监听的时候传递的一致
*/
- (void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary *)change
context:(void *)context;
45.如何手动触发一个 value 的 kvo?
46.如果一个类有成员变量 NSString *_foo,调用 setValue:forKey: 时,可以以 foo 还是 _foo 作为 key ?
都可以
47.KVC 的 keyPath 中集合运算符如何使用?
1、必须在集合对象上或普通对象的集合属性上
2、简单集合运算符有:@avg、@min、@max、@sum
3、格式:@"@sum.age" 或者 @"集合属性.@max.age"
48.KVC 和 KVO 的 keyPath 一定是属性吗?
- KVC 支持实例变量;
- 如果将一个对象设置成属性,这个属性是自动支持 KVO 的;
- 如果这个对象是一个实例变量,这个 KVO 需要手动实现
49.如何关闭默认的 KVO 的默认实现,并进入自定义的 KVO 实现?
利用 runtime 动态创建类、实现 KVO
50.苹果用什么方式实现对一个对象的KVO?
Apple 使用了 isa 混写(isa-swizzling)来实现 KVO
51.IBOutlet 连出来的视图属性为什么可以被设置成 weak?
因为有外链那么视图在 xib 或 storyboard 中肯定存在,视图已经对它有一个强引用了。
52.IB 中的 User Defined Runtime Attributes 如何使用?
它能够通过 KVC 的方式配置一些你在 interface builder 中不能配置的属性。通过他可以编写更轻量级的 viewController
53.如何调试 BAD_ACCESS 错误?
54.lldb(gdb)常用的调试命令?
- breakpoint 设置断点定位到某一个函数
- n 断点指针下一步
- po 打印对象
![](https://img.haomeiwen.com/i8918169/b7bc4001f05f9c5d.png)
55.iOS 容易引起“循环引用”的几种场景
最简单的理解:对象A持有对象B,对象B又持有对象A,就会造成循环引用
1、parent-child 相互持有、委托模式
2、block:隐式(间接)循环引用,A 持有 B,B 持有 block。那么在 block 中调用 A 的 self 会造成间接循环引用。显式循环引用,编译器会报警告,在 block 中引用 self 的时候最好使用 weak-strong dance 技术
3、NSTimer:它会持有对象,所以删除对象前要执行 [time invalidate]
4、把 self 加入到 Array 中,也会造成循环引用。
5、使用类别添加属性:给一个类A动态添加属性P,P中又引用类A,容易造成循环引用
56.类方法 load 和 initialize 的区别
iOS 会在运行期提前并且自动调用这两个方法。
que | +(void)load | +(void)initialize |
---|---|---|
执行时机 | 在程序运行后立即执行 | 在类的方法第一次被调时执行 |
是否沿用父类的方法 | 否 | 是 |
类别中的定义 | 全都执行,但后于类中的方法 | 覆盖类中的方法,只执行一个 |
当类对象被引入项目时,runtime 会向每一个类对象发送 load 消息。它会在被引入时仅调用一次,顺序是父类优先于子类,子类优先于类别, 而且 load 方法不会被类自动继承
57.HTTP 和 HTTPS
![](https://img.haomeiwen.com/i8918169/b34733ce827656f1.png)
![](https://img.haomeiwen.com/i8918169/cfeed5aa1ad8af0a.png)
区别:
1、HTTPS 是加密传输协议,需要 SSL证书;HTTP 是明文传输协议,不需要
2、HTTPS 的标准端口443,HTTP 的标准端口80
3、HTTPS 基于传输层,HTTP 基于应用层
58.常见的 Exception Type
1、EXC_BAD_ACCESS
访问了不该访问的内存导致
- SIGSEGV:重复释放对象导致
- SIGABRT:收到 Abort信号退出,例如插入 nil 到数组中会导致
- SEGV:无效内存地址,例如空指针、栈溢出、未初始化指针
- SIGBUS:总线错误(地址对齐问题)
- SIGILL:尝试执行非法的指令,可能不被识别或者没有权限
2、EXC_BAD_INSTRUCTION
此类异常通常是线程执行非法指令导致的
3、EXC_ARITHMETIC
除零错误会抛出此类异常
59.Category 和 Extension
Category 为原始类添加方法,注意不要去重写已经存在的方法
Extension 管理类的私有方法
60.响应者链(respond chain)
![](https://img.haomeiwen.com/i8918169/9b0bd1b5d363cc3d.png)
如上图,响应者链有以下特点:
- 通常是由 initial view 开始
- UIView 的 nextResponder 是它的 superview
- UIWindow 的 contentView 指向 UIApplication҅,将其作为 nextResponder;
- UIApplication 是一个响应者链的终点,它的 nextResponder 指向 nil
事件的链有两条:
- 响应链:由离用户最近的的 view 向系统传递。 initial view –> super view –> ….. –> view controller –> window –> Application –> AppDelegate
- Hit-Testing链:由系统向离用户最近的 view 传递。 UIKit –> active app's event queue –> window –> root view –> …… –>lowest view
61.UITableView 优化
1、重用 cell
2、缓存行高
3、加载网络数据,下载图片,使用异步加载,并缓存
4、使用局部刷新,尽量不要使用 reloadData
5、渲染,尽量少用或不用透明图层
6、少用 addSubView 给 Cell动态添加 子View,初始化直接设置好,用 hidden 控制显隐
7、使用异步加载 web 内容,缓存请求结果
8、按需加载 cell,cell 滚动很快时,只加载范围内的 cell
9、遇到复杂界面,像朋友圈涉及图文混排,需要异步绘制
62.离屏渲染(OffScreen-Renderd)
引发离屏渲染的情况和操作:
- 给图层设置了遮罩(
layer.mask
) - 对图层进行了圆角处理(
layer.masksToBounds
和layer.cornerRadius
) - 给图层设置了光栅化(
layer.shouldRasterize=true
) - 给图层设置了阴影(
layer.shadow *
) - 使用 CGContext 在 drawRect:方法中绘制,大概率会导致
优化方案:
- 圆角优化
- 使用 CAShapeLayer 和 UIBezierPath 设置圆角
- 直接覆盖一张中间为原型透明的图片(推荐使用)
- shadow 优化,使用 ShadowPath 指定 layer 阴影效果路径,优化性能
- 减少复杂图层合成,尽量设置 layer 的大小为整型值
- 尽量使用不包含透明(alpha)通道的图片资源
63.UIView 和 CALayer
联系:
- UIView 是 iOS 中所有界面元素的根类,它本身是由 CoreAnimation 来实现的
- UIView 内部默认关联着一个 layer,真正的绘图部分,由 CALayer 类管理
- UIView 有个 layer 属性,返回它的主CALayer 实例
区别:
- UIView 继承自 UIResponder,能接收并响应事件,负者显示内容的管理。CALayer 继承自 NSObject,不能响应事件,负者显示内容的绘制。
- UIView 侧重于展示内容,而 CALayer 则侧重于图形和界面的绘制。
- view 的内容展示依赖CALayer对内容的绘制,UIView 的 Frame也是由内部的 CALayer 进行绘制
64.TCP 和 UDP
TCP:传输控制协议,提供的是面向连接、可靠的字节流服务。
UDP:用户数据包协议,是面向数据报的运输层协议。
tcp协议和udp协议的差别:
- tcp 面向连接,udp 面向非连接
- tcp 传输可靠,udp 传输不可靠
- tcp 传输大量数据,udp 少量数据
- tcp 速度慢,udp 快
TCP 的三次握手:
- 第一步:客户端发送 syn包(syn=j ) 到服务器,并进入 SYN_SEND 状态
- 第二步:服务器收到 syn包(ack=j+1) 并确认后,自己也发送一个 syn包(syn=k),即 SYN+ACK 包,进入 SYN_RECV 状态
- 第三步:客户端接收到 SYN+ACK 包后,向服务器发送确认包 ACK(ack=k+1),此包发送完毕,客户端与服务器建立 ESTABLISHED 连接,完成三次握手。
65.socket 和 http
socket连接和http连接的区别:
http 是基于 socket 之上的,socket 是一套完成 tcp,udp 协议的接口。
HTTP协议:简单对象访问协议,对应于应用层,HTTP协议是基于TCP连接的。
TCP协议:对应于传输层
IP协议:对应于网络层
TCP/IP是传输层协议,主要解决数据如何在网络中传输;而HTTP是应用层协议,主要解决如何包装数据。
Socket是对 TCP/IP协议的封装,Socket 本身并不是协议,而是一个调用接口,通过Socket 我们才能使用 TCP/IP 协议。
http连接:短连接,请求一次完成响应后会主动释放连接即断开
Socket连接:长连接,理论上客户端与服务器一旦建立连接将不会主动断开。但由于各种因素可能会断开: 服务器或主机挂了、网络故障、防火墙切断等,所以需要发送心跳消息。
66.远程推送 APNS
远程推送的基本过程:
1、客户端的 app 用 UDID 和 bundleID 向 apns服务器请求 device Token
2、app 获得到 device Token 后上传到公司服务器
3、需要推送通知时,将 推送内容和device Token一起发送给 apns服务器
4、apns 再将推送内容推送到相应的客户端app上
67.iOS 应用程序生命周期
![](https://img.haomeiwen.com/i8918169/c8ad53702e67078a.png)
![](https://img.haomeiwen.com/i8918169/49858c4205349608.png)
1、应用程序的状态
状态 | 注释 | 说明 |
---|---|---|
Not running | 未运行 | 程序没有启动 |
Inactive | 未激活 | 程序在前台运行,但未接收事件 |
Active | 激活 | 程序在前台运行且接收到了事件 |
Backgroud | 后台 | 程序在后台运行且能执行代码 |
Suspended | 挂起 | 程序在后台不能执行代码 |
2、UIApplicationMain
UIApplicationMain(int argc, char *argv[], NSString *principalClassName, NSString *delegateClassName);
/*
argc:系统或者用户传入的参数个数
argv:系统或用户传入的实际参数
argc、argv:直接传递给 UIApplicationMain 进行相关处理即可。
principalClassName:指定应用程序类名(app的象征)
delegateClassName:创建并设置 UIApplicationMain 对象的代理,指定应用程序的代理类
接着会建立应用的 Main runloop, 处理与用户交互产生的事件
*/
68.View 视图生命周期
阶段 | 方法调用 |
---|---|
视图创建 | viewDidLoad() |
视图即将可见 | viewWillAppear() |
视图已经可见 | viewDidAppear() |
视图即将不可见 | viewWillDisappear() |
视图已经不可见 | viewDidDisappear() |
系统低内存 | didReceiveMemory() 和 viewDidUnload() |
69.autorelease pool(事件循环)
1、autorelease 的原理是什么?
autorelease 实际上只是把 release 的调用延迟了,系统只是把 autorelease 对象放入了当前 autorelease pool 中,当 pool 在释放时,会通知 pool 内所有对象调用 release。
2、autorelease 的对象何时释放?
- 对于autorelease pool 本身,会在以下情况释放:
- 手动释放 Autorelease pool
- Runloop 结束后自动释放
- 对于 autorelease pool 内部的对象在引用计数等于 0 的时候释放。release 和 autorelease pool 的 drain 都会触发引用计数减一。
3、NSAutoreleasePool 是什么?
NSAutoreleasePool 实际上是一个对象引用计数自动处理器,在官方文档被称为一个类。
每一个 autoreleasepool 只对应一个线程,每个线程都会维护自己的 autorelease pool 堆栈。
4、NSAutoreleasePool 和 autoreleasepool 的区别?
pool | 作用时间 | ARC |
---|---|---|
NSAutoreleasePool | 作用于运行时 | 不可用 |
autoreleasepool | 作用于编译时 | @autoreleasepool |
5、autorelease 作用:
- 对象执行 autorelease 方法会将对象添加到自动释放池中。
- 对象执行 autorelease 方法后自身引用计数不会改变,而且返回对象本身
- 当自动释放池销毁时内部所有对象做 release 操作
- autorelease 将对象的 release 调用延迟,不要关注它的引用计数,交给系统来做
6、@autoreleasepool
自己开启一个线程,需要创建自己的自动释放池块。当你的应用或者线程长时间存活,并可能产生大量的自动释放对象,此时应该手动创建自动释放池块。
在遇到需要大量创建对象的地方使用 autoreleasepool 可以加速对象的释放。
70.view layout
相关方法:
- (CGSize)sizeThatFits:(CGSize)size
- (void)sizeToFit
——————-
- (void)layoutSubviews
- (void)layoutIfNeeded
- (void)setNeedsLayout
——————–
- (void)setNeedsDisplay
- (void)drawRect
layoutSubviews 在以下情况会被调用:
layoutSubviews | 调用时机 |
---|---|
1 | addSubview 时 |
2 | 设置 view 的 Frame 时 |
3 | 滚动 UIScrollView 时 |
4 | 旋转 Screen会触发父View上的layoutSubviews |
5 | 改变一个UIView大小时也会触发父View的 |
刷新子对象布局:
刷新子对象布局 | 做什么 |
---|---|
layoutSubviews | 默认什么都不做,需要子类进行重写 |
setNeedsLayout | 标记为需要重新布局,异步调用layoutIfNeeded,不立即刷新 |
layoutIfNeeded | 如果有需要刷新的标记,立即调用 layoutSubviews 刷新布局 |
重绘:
重绘 | 作用 |
---|---|
drawRect:(CGRect)rect: | 重写此方法,执行重绘任务 |
setNeedsDisplay | 标记为需要重绘,异步调用drawRect |
setNeedsDisplayInRect:(CGRect)invalidRect | 标记为需要局部重绘 |
sizeToFit | 会自动调用 sizeThatFits |
sizeThatFits | 传入的参数是 receiver 当前的 size |
layoutSubviews | 对 subviews 重新布局,优先于 drawRect |
drawRect | 是对 receiver 的重绘,能获得 context |
setNeedsDisplay | 该方法在调用时,会自动调用 drawRect 来画图。 |
总结 | 说明 |
---|---|
当需要刷新布局时 | 用 setNeedsLayOut 方法 |
当需要重新绘画时 | 调用 setNeedsDisplay 方法 |
71.应用程序的架构MVC、MVVM
MVC
遵循 Model-View-Controller 的架构
MVC | 负责 |
---|---|
Model | 存储数据和处理业务数据 |
View | 显示数据和用户交互 |
Controller | 协调 Model 和 View 协作 |
他们的通讯规则如下:
![](https://img.haomeiwen.com/i8918169/2deb4d6bed248f7e.png)
![](https://img.haomeiwen.com/i8918169/d143ae4930cb9712.png)
![](https://img.haomeiwen.com/i8918169/a6c4a1d81125bacf.png)
![](https://img.haomeiwen.com/i8918169/32f45d66f41edc16.png)
MVVM
MVVM 就是在 MVC 的基础上分离出业务处理的逻辑到 ViewModel 层
MVVM | 负责 |
---|---|
Model | API请求的原始数据,数据持久化 |
View | 视图展示,由 viewController 来控制 |
viewModel | 负责网络请求、业务处理和数据转化 |
简单来说,就是 API 请求玩数据之后,解析成 Model,之后在 viewModel 中转换成能够直接被视图层使用的数据,交付给前段。
经过 viewModel 转化的数据由 viewModel 保存,与数据相关的处理都将在 viewModel 中进行。viewModel 返回给 view层。
view层是由 Controller 控制的。view层只做展示,不做业务处理。view层的数据由 viewModel 提供。
![](https://img.haomeiwen.com/i8918169/fc2ea34ef2d314f1.png)
![](https://img.haomeiwen.com/i8918169/da5fb6232d5a996e.png)
![](https://img.haomeiwen.com/i8918169/4b548d997bb93494.png)
72.FMDB、SQLite
FMDatabaseQueue 用于在多线程执行多个查询和更新,它是线程安全的。
FMDatabase 这个类是线程不安全的,如果在多个线程中同时使用一个 FMDatabase 实例,会造成数据混乱等问题。
操作 | 语法 |
---|---|
增表 | create table t_student (id integer, name text, age inetger, score real); |
删表 | drop table t_student; |
插入数据 | insert into t_student (name, age) values ('mj', 10); |
更新数据 | update t_student set name = ‘jack’, age = 20; |
删除数据 | delete from t_student; |
73.简述内存分区情况
分区 | 情况 |
---|---|
代码区 | 存放函数二进制代码 |
数据区 | 存放全局变量、静态变量、常量 |
堆区 | 通过malloc或new动态申请,需要手动释放 |
栈区 | 存放局部变量、函数参数,函数模块内申请,函数结束时自动释放 |
74.各属性作用
属性 | 作用 |
---|---|
readwrite | 可读可写,需要生成 getter、setter 方法 |
readonly | 只读,只生成 getter,不会生成 setter,不希望属性在类外部被更改 |
assign | 赋值,setter 方法将传入的参数赋值给实例变量;仅设置变量时, assign 用于基本数据类型 |
retain | 表示持有特性,setter 将传入的参数先保留,再赋值,传入参数的引用计数会加1 |
copy | 表示赋值特性,setter 方法将传入的对象复制一份 |
nonatomic | 非原子操作,决定编译器生成的 getter setter 是否是原子操作 |
atomic | 原子操作,表示多线程安全,耗费资源,一般用 nonatomic |
75.简述 Notification、KVC、KVO、delegate 的区别
方式 | 说明 |
---|---|
Notification | 观察者模式,发送者和接受者的关系是间接的,多对多的 |
delegate | 发送者和接受者的关系是直接的,一对一的 |
KVO | 一对多,观察者模式,键值观察机制,提供了观察某一属性变化的方法,极大地简化代码 |
KVC | 键值编码,一个对象在调用 setValue |
KVC | 一个对象在调用 setValue |
---|---|
1 | 检查是否存在相应 key 的 set 方法,存在就调用 set 方法 |
2 | set 方法不存在,就查找 _key 的成员变量是否存在,存在就直接赋值 |
3 | 如果 _key 没有找到,就查找相同名称的 key,存在就赋值 |
4 | 如果都没有找到,就调用 valueForUndefinedKey 和 setValue:forUndefinedKey |
区别 | 区别 |
---|---|
1 | delegate 的效率比 NOtification 高 |
2 | delegate 比 Notification 更直接,需要关注返回值,常常带有 should 关键字 |
3 | Notification 不关注结果,常常带有 did 关键字 |
4 | 两个模块之间的联系不是很紧密,就用 Notification 传值,比如多线程之间的传值 |
5 | KVO容易实现两个对象的同步,比如 Model 和 View 的同步 |
76. id 和 nil 代表什么?
- id 类型的指针可以指向任意对象
- nil 代表空值(空指针的值,0)
77.nil、Nil、Null、NSNull
OC 中
- nil、Nil、Null 都表示 (void *)0
- NSNull 继承于 NSObject,很特殊的类,表示空,什么也不存储,但它却是一个占位对象。使用场景:比如服务器接口让传空,
NSDictionary *params = @{@“arg1” : @“value1”, @“arg2” : arg2.isEmpty ? [NSNull null] : arg2}
区别 | 区别 |
---|---|
Null | 是宏,是对于 C 语言指针而使用的,表示空指针 |
nil | 是宏,是对于 OC 中的对象而使用的, 表示对象为空 |
Nil | 是宏,是对于OC 中的类而使用的,表示类指向空 |
NSNull | 是类类型,是用于表示空的占位对象,与 JS 或服务端的 null 类似 |
78.向一个 nil 对象发送消息会发送什么?
- 向 nil 对象发送消息是完全有效的,只是在运行时不会有任何作用
返回类型 | 发送 nil 消息的返回值 |
---|---|
对象 | 0(nil) |
指针 | 指针大小小于等于 float,double 时,返回0 |
结构体 | 0,结构体中的各个字段也是0 |
79.self. 和 self-> 的区别
- self. 是调用 getter 或者 setter 方法
- self 是当前本身,是指向当前对象的指针
- self-> 是直接访问成员变量
80.如何访问并修改一个类的私有属性?
- 通过 KVC
- 通过 runtime 访问并修改
81.如何为 class 定义一个对外只读,对内可读写的属性?
在头文件 .h 中将属性定义为 readonly,在 .m 中重新将属性定义为 readwrite
82.OC 中 meta_class 指的是什么?
meta-class 是 class 对象的类,为这个 class 存储类方法,当向一个类发送信息时,就去这个类的 meta-class 中查找那个消息,
83.NSString 用 copy 和 strong 的区别
用 copy 会判断是不是不可变字符串:
类型 | 是否分配 |
---|---|
是不可变字符串 | 不分配空间 |
是可变字符串 | 分配空间 |
如果程序中用到的 NSString 特别多,这样的判断会很耗费性能,所以选择用 strong 修饰不可变字符串
84.创建一个对象的步骤
1、开辟内存空间
2、初始化参数
3、返回内存地址值
85.setter 和 getter
- setter 方法:对外界提供一个设置成员变量的方法,
- setter 好处:可以不让数据暴露在外,保证数据安全性;对设置的数据过滤
- getter 方法:为调用者返回对象内部的成员变量
- 点语法的本质是对 getter 或 setter 方法的调用
86.id 和 instancetype 是什么?有什么区别?
- id:万能指针,能作为参数、方法的返回类型
- instancetype:只能作为方法的返回类型,并且返回类型是当前定义类的类类型
87.内存管理
ARC 所做的是在代码编译期自动在合适的位置插入 release 或 autorelease,只要没有强指针指向对象,对象就会被释放。ARC 中不能手动使用 NSZone,也不能调用父类的 dealloc。
1、调用对象的 release 方法会销毁对象吗?
不会,调用 release 只是将对象的引用计数-1,当对象的引用计数=0时,调用对象的 delloc 方法才能释放内存。
2、objc 通过什么机制管理内存?
- 通过引用计数器(retainCount)的机制来决定对象是否需要释放。每次 runloop 完成一个循环的时候,都会检查对象的 retainCount,如果 retainCount = 0,说明该对象没有地方继续使用了,可以释放了。
- ARC 的判断准则:只要没有强指针指向对象,对象就会释放。
3、内存管理的范围
管理所有继承自 NSObject 的对象,对基本数据类型无效。是因为对象和其他基本数据类型在系统中的存储空间不一样,其他局部变量主要存储在栈区,对象主要存储在堆中。代码块结束。栈自动清空,指向对象的指针也被回收,没有指针指向的对象依然存在于堆中造成内存泄漏
4、内存管理研究的对象
- 野指针:指针变量没有进行初始化或指向的空间已经被释放
- 使用野指针调用对象,会报异常,程序崩溃
- 在调用完 release 后,将保存对象指针的地址清空,赋值为 nil
- 空指针:指针赋值为空,nil
- 内存泄漏
- 如
Person *p = [Person new];
对象 提前赋值 nil 或清空,在栈区的 p 已经被释放,而堆区 new 产生的对象还没有被释放,就会造成内存泄漏 - MRC 造成内存泄漏的情况:
- 没有配对释放,不符合内存管理原则
- 对象提前赋值 nil 或清空,导致 release 不起作用
- 如
- 僵尸对象:堆中已经被释放的对象(retainCount=0)
操作 | 引用计数 |
---|---|
alloc̵、allocWithZone̵、new | 引用计数 +1 |
retain | 手动为该对象引用计数+1 |
copy | 深拷贝对象引用计数为1,浅拷贝引用计数+1 |
release | 手动为该对象引用计数-1 |
autorelease | 放入自动释放池,池子释放时,给对象引用计数-1 |
autorelease | 多次调用对象的 autorelease 方法会导致:野指针异常 |
88.KVC 的底层实现
当一个对象在调用 setValue 时,方法内部会做以下操作:
步骤 | 操作 |
---|---|
1 | 检查是否存在相应 key 的 set 方法,存在就调用 set 方法 |
2 | 如果 set 方法不存在,就查找 _key 的成员变量是否存在,存在就直接赋值 |
3 | 如果 _key 没有找到,就查找相同名称的 key,存在就赋值 |
4 | 如果都没有找到,就调用valueForUndefinedKey 和 setValue:forUndefinedKey
|
89.block 的内存管理
无论 ARC 还是 MRC,只要 block 没有访问外部变量,block 就始终在全局区
- MRC 下
- block 如果访问外部变量,block 在栈区
- 不能对 block 使用 retain,否则 block 不能保存在堆区
- 只有使用 copy 才能放到堆区
- ARC 下
- block 如果访问外部变量,block 在栈区
- block 是一个对象,可以使用 strong 和 copy,最好使用 copy
90.app 的启动过程,从 main 说起
app 启动分两类: 有 storyboard 、无storyboard
有 storyboard 情况下:
1、main 函数
2、UIApplicationMain
- 创建 UIApplicationMain 对象
- 创建 UIAPPlicationMain 的 delegate 对象
3、根据 info.plist 加载 Main. storyboard
- 创建 UIWindow
- 创建和设置 UIWindow 的 rootViewController
- 显示窗口 window
无 storyboard 情况下:
1、main 函数
2、UIApplicationMain
- 创建 UIApplicationMain 对象
- 创建 UIAPPlicationMain 的 delegate 对象
3、delegate 对象开始处理(监听)系统事件
- 程序启动完毕时,调用
didFinishLaunching
方法 -
didFinishLaunching
方法中创建 UIWindow 设置 rootViewController 并显示窗口 window
91.tableView 的 cell 中如何嵌套 collectionView?
用自定义的继承自 UITableViewCell 的类,在 initWithFrame
中初始化自定义的继承自 UICollectionView 的类。
92.awakeFromNib 和 viewDidLoad 的区别
awakeFromNib:通过 .nib 文件创建的 view 对象会执行 awakeFromNib
viewDidLoad:当 view 被加载到内存就会执行,不过通过 nib 还是代码形式
93.常见的 Crash 场景
- 数组越界
- 访问了不存在的方法
- 访问了僵尸对象
- 在定时器下一次回调前,将定时器释放
94.AFN 断点续传
- 检查服务端文件信息
- 检查本地文件
- 如果本地文件小,利用 HTTP 请求头的 Range 实现断点续传
- 如果本地文件比服务端大,重新下载
- 如果和服务端文件一样,下载完成
95.客户端的缓存机制
缓存分为:内存数据缓存,数据库缓存、文件缓存
- 每次想获取数据的时候,先检测内存中有无缓存
- 再检测本地有无缓存(数据库、文件)
- 都没有则发送网络请求
- 将服务端返回的数据进行缓存(内存、数据库、文件)
96.数据存储方式
4种数据持久化:属性列表(plist)、对象归档、SQLite、Core Data
NSUserDefaults 用于存储配置信息
使用 keychain 存储用户的敏感信息,如 token,需要导入 Security 框架
使用NSUserDefaults 时,如何处理布尔的默认值?
if ([[NSUserDefaults standardUserDefaults] objectForKey:@"key"] == nil) {
NSLog(@"没有设置过");
}
97.App 需要加载超大量的数据时,给服务器发送请求,但服务器卡住了,如何解决?
- 设置请求超时
- 给用户提示请求超时
- 根据用户操作再次请求数据
98.网络图片处理时如何避免相同的网络地址重复请求?
利用字典(图片地址为 key,下载操作为 value)
99.NSOperation、GCD、NSThread 的区别
NSOperation 和 GCD 的区别:
GCD:
- GCD是ios4.0推出的,纯c
- GCD是将任务(block) 添加到队列(串行、并行、全局、主队列),并且以同步/异步的方式执
行任务的函数 - GCD 提供NSOperation不具备的功能:
- 次性执行、延迟执行、调度组
- GCD是严格的队列,先进先出FIFO
NSOperation:
- NSOperation是i0s2 . 0推出的,i0s4.0 以后又重写的
- NSOperation是将操作(异步任务)添加到队列(并发队列),就会执行指定的函数
- NSOperation 提供的方便操作:
- 最大并发数
- 队列的暂停和继续
- 取消所有的操作
- 指定操作之间的依赖关系,可以让异步任务同步执行
- 可以利用KVO 监听一个operation是否完成
- 可以设置operation 的优先级,能使同-一个并行队列中的任务区分先后地执行
- 对NSoperation 继承,在这之.上添加成员变量和成员方法,提高代码的复用度
GCD 和 NSThread 的区别:
- NSThread 使用@selector 指定要执行的方法,代码分散
- GCD通过block 指定要执行的方法,代码集中
- GCD不用管理线程的生命周期(创建、销毁、复用)
- 如果要开多个线程NSThread 必须实例化多个线程对象
- NSThread通过performselector 方法实现线程间通信
为什么要取消/恢复队列?
- 一般内存警告后取消队列中的操作
- 为了保证scrollview 在滚动的时候流畅,通常在滚动开始时,暂停队列中的所有操作,滚动结
束后,恢复操作
100.是否可以把比较耗时的操作放在 NSNotificationCenter 中?
- 如果在异步线程发的通知,可以执行耗时操作
- 如果在主线程发的通知,则不可以执行耗时操作
网友评论