1、ViewDidLoad的触发时机(viewDidLoad)
结论:
每次访问UIViewController的view而且view为nil,loadView方法就会被调用。
系统是在loadView方法中创建好UIViewController的view的。
viewDidLoad是在loadView方法创建view完毕后被调用的。
题外:
loadView是干嘛用的?
* loadView是手写ViewController时,生成当前VC视图的惰性方法,如果要自定义View,可以直接复写这个方法。
* 自定义的方法无需调用super方法。
2、View方法的先后顺序
执行顺序:
视图创建:init->viewDidLoad->viewWillAppear->viewDidAppear
视图消失:viewWillDisappear-> viewDidDisappear
当视图A切换到视图B的流程:
1、B视图 viewDidLoad
2、A视图 viewWillDisappear
3、B视图 viewVillAppear
4、A视图 viewDidDisappear
5、B视图 viewDidAppear
3、property
* 一类是表示原子性,有atomic和nonatomic,默认是atomic
* 一类是表示引用计数的,有assign(unsafe_unretained),strong,weak,copy, 默认是assign
* 一类是表示读写权限的,默认是readwrite(可读可写),还有就是readonly
assign与weak:共同点是都不会增加retaincount
区别是
a)assign不仅能用于基础数据类型,也能用于OC对象,但weak只能用于OC对象;
b)weak是弱引用,在其所引用的对象被释放后,会将变量置为nil,不会有野指针的现象。
c)weak的赋值方式是复制引用,而assign是复制数据。
d)weak只能用于ARC;
怎么用 copy 关键字?
* NSString、NSArray、NSDictionary 等等经常使用copy关键字,是因为他们有对应的可变类型:NSMutableString、NSMutableArray、NSMutableDictionary
* block 也经常使用 copy 关键字
weak实现原理:https://www.jianshu.com/p/48044cc54392
4、KVO(KVC)、Delegate、NSNotification
简介:
KVO(键值监听),即Key-Value Observing,它提供一种机制,当指定的对象的属性被修改后,对象就会接受到通知,前提是执行了setter方法、或者使用了 KVC赋值。
KVC(键值编码),即Key-Value Coding,一个非正式的Protocol,使用字符串(键)访问一个对象实例变量的机制。而不是通过调用Setter、Getter方法等 显式的存取方式去访问。
KVO的使用:
注册观察者:addObserver:forKeyPath:options:context:
实现观察者:observeValueForKeyPath:ofObject:change:context:
移除观察者:removeObserver:forKeyPath:(对象销毁,必须移除观察者)
KVO的原理:
当你观察一个对象(称该对象为「被观察对象」)时,一个新的类会动态被创建(NSKVONotifying_XX)。
这个类继承自「被观察对象」所对应类的,并重写该被观察属性的setter方法;针对setter方法的重写无非是在赋值语句前后加上相应的通知(或方法调用);
最后,把「被观察对象」的isa指针(isa指针告诉Runtime系统这个对象的类是什么)指向这个新创建的中间类,对象就神奇变成了新创建类的实例。
虽然被观察对象的isa指针被修改了,但是调用其class方法得到的类信息仍然是它之前所继承类的类信息,而不是这个新创建类的类信息。
友链:https://www.jianshu.com/p/e59bb8f59302
KVO相关crash及防护:
1、对不存在的属性进行kvo观测
2、observe忘记写监听回调方法 observeValueForKeyPath
3、add和remove次数不匹配
4、监听者和被监听者dealloc之前没有remove(其实也原因3,但是监听者和被监听者的生命周期不同)
方案一、
可以让被观察对象持有一个KVO的delegate,所有和KVO相关的操作均通过delegate来进行管理,delegate通过建立一张map来维护KVO整个关系。
中间层delegate的代理工作:
(1)如果出现KVO重复添加观察者或者重复移除观察者(KVO注册观察者与移除观察者不匹配)的情况,delegate可以直接阻止这些非正常的操作。
(2)被观察者dealloc之前,可以通过delegate自动将与自己有关的KVO关系都注销掉,避免了KVO的被观察者dealloc时仍然注册着KVO导致的crash。
方案二、
我们可以让观察者在注册的过程中,将注册信息一同记录下来,然后使用某种方法在对象dealloc时,在记录的信息里找到对应的观察者,注销观察。
此方案在宿主释放过程中嵌入我们自己的对象,使得宿主释放时顺带将我们的对象一起释放掉,从而获取dealloc的时机点。采用构建一个释放通知对象,通过AssociatedObject方式连接到宿主对象,在宿主释放时进行回调,完成注销动作。
Delegate简介:
代理设计模式,是iOS中一种消息传递的方式,由协议、代理对象、委托者组成。只能一对一。
协议(protocol):用来指定代理可以做什么,必须做什么。
代理:根据指定协议,完成委托方需要实现的方法。
委托:根据指定协议,指定代理必须完成和可以完成方法。
NSNotification简介:
NSNotificationCenter 较之于 Delegate 可以实现更大的跨度的通信机制,可以为两个无引用关系的两个对象进行通信。NSNotification是iOS中一个调度消息通知的类,采用单例模式设计。
NSNotification使用:
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(xxx:) name:@"abc" object:nil];
[[NSNotificationCenter defaultCenter] removeObserver:self];
//或者
[[NSNotificationCenter defaultCenter] removeObserver:self name:@"abc" object:nil];
[[NSNotificationCenter defaultCenter] postNotificationName:@"abc" object:nil];
NSNotification相关crash及防护:([https://www.cnblogs.com/Xylophone/p/6394056.html])
在退出A页面的时候没有把自身的通知观察者A给注销,导致通知发过来的时候抛给了一个已经释放的对象A,但该对象仍然被通知中心引用,也就是僵尸对象,从而导致程序崩溃 。(也就是说,在一个页面dealloc的时候,一定要把这个页面在通知中心remove掉,否则这个页面很有可能成为僵尸对象)。
但有两种情况需要注意:
(1)单例里不用dealloc方法,应用会统一管理;
(2)类别里不要用dealloc方法removeObserver,在类别对应的原始类里的dealloc方法removeObserver,因为类别会调用原始类的dealloc方法。(如果在类别里新写dealloc方法,原类里的dealloc方法就不执行了)
方案一、
利用method swizzling hook NSObject的dealloc函数,在对象真正dealloc之前先调用一下[[NSNotificationCenter defaultCenter] removeObserver:self]即可。在其添加observer的时候,对observer动态添加标记flag。
方案二、
在宿主释放过程中嵌入我们自己的对象,使得宿主释放时顺带将我们的对象一起释放掉,从而获取dealloc的时机点。显然AssociatedObject是我们想要的方案。相比Method Swizzling方案,AssociatedObject方案的对工程的影响范围小,而且只有使用自动注销的对象才会产生代价。
5、异常捕获机制
Objective-C 异常机制 :
* 开发者将引发异常的代码放在 @try 代码块中, 程序出现异常 使用 @catch 代码块进行捕捉;
* @try代码块:存放可能出现异常的代码, @catch代码块:异常处理逻辑, @finally代码块:回收资源;
* 一个@try 代码块可以对应多个@catch代码块;@try @catch @finally 的花括号不可省略;
异常处理过程
* 生成异常对象:@try 中出现异常, 系统会生成一个异常对象, 该对象提交到系统中,系统就会抛出异常
* 运行环境接收到异常对象时, 如果存在能处理该异常对象的 @catch 代码块, 就将该异常对象交给 @catch 处理, 该过程就是捕获异常, 如果没有 @catch 代码块处理异常, 程序就会终止
* 运行环境接收到异常对象时, 会依次判断该异常对象类型是否是 @catch 代码块中异常或其子类实例, 如果匹配成功, 被匹配的 @catch 就会处理该异常, 都则就会跟下一个 @catch 代码块对比
try/catch能抓的异常:
NSRangeException(越界)
NSInvalidArgumentException(调用方法出错)
NSInternalInconsistencyException(比如把NSDictionary当做NSMutableDictionary来使用)
NSGenericException(遍历的时候,对集合进行了插入/删除操作)
参考:[https://www.jianshu.com/p/d32778ed9dd3]
结论:
Apple虽然同时提供了错误处理(NSError)和异常处理(exception)两种机制,但是Apple更加提倡开发者使用NSError来处理程序运行中可恢复的错误。而异常被推荐用来处理不可恢复的错误。
Exception容易造成内存管理问题(文档有描述即使是arc下,也不是安全的);
(try代码块中的对象不释放,可以用compileflags的-fobjc-arc-exceptions解决或是修改成.mm文件)
Exception使用block造成额外的开销,效率较低等等。
6、+load、+initialize的区别
* load方法是runtime在加载类后,在main函数之前,向每个类和分类发送的消息,顺序是父类-子类-分类. 通常在这个地方做method swizzlling或者使用Router方式做解耦的时候,通常也会在这里做URL注册;
* initialize方法是一个惰性方法,被调用的前提是,该类有方法被调用,如果该类一直没有被使用过,则这个方法一直不会被调用。
相同点是,两个方法都是线程安全的。
7、NSError设计成二级指针原因
为了方便回传赋值。
指针作为参数传递的时候,指针本身是值传递。
在函数内对参数指针赋值,是不能改变原参数指针指向的值的。
参考:[https://www.jianshu.com/p/6a1cfef75092]
8、动画和前后台
现象:App进入后台时,所有动画会在瞬间全部完成,但是闭包返回finish是false。
处理:
1、在applicationWillEnterForeground代理方法里面重新开始所有的动画。
2、设置animation.removedOnCompletion = NO; (属性解释:当为真时,一旦活动持续时间已过,动画将从呈现树中删除。默认值为YES。)
9、常用关键字 static、const、 extern、define
当static关键字修饰局部变量时,只会初始化一次且在程序中只有一份内存;
关键字static不可以改变局部变量的作用域,但可延长局部变量的生命周期(直到程序结束才销毁)。
当static关键字修饰全局变量时,作用域仅限于当前文件,外部类是不可以访问到该全局变量的(即使在外部使用extern关键字也无法访问)。
优点:不会影响到其他文件, 同理也不受其他文件影响/不会有命名冲突
const 表示常量, 用来修饰右边的基本变量或指针变量, 由变量转为常量, 其不可以被修改, 在编译阶段会执行检查, 其存储区域位于常量区, 常用于配合 static 或 extern 使用。
extern:使用其来声明供外部使用(.h&.m)/使用其来声明引用外部全局变量等。
想要访问全局变量可以使用extern关键字(全局变量定义不能有static修饰)。
define:字符替换, 在预编译期间处理, 使用时系统直接进行的文本替换。可用于修饰数据, 函数, 结构体, 方法等, 系统不会对其做类型检查。
Static和extern的区别:
(1)extern修饰的全局变量默认是有外部链接的,作用域是整个工程,在一个文件内定义的全局变量,在另一个文件中,通过extern全局变量的声明,就可以使用全局变量。
(2)static修饰的全局静态变量,作用域是声明此变量所在的文件。
const 修饰其后面内容:
const NSString * name = @"Jersey";
使 *name 指针地址不可变, 实际指向内容不受影响, 修改指针地址编译器报错。
NSString const * name = @"Jersey";
同上面写法一致
NSString * const name = @"Jersey";
使 *name 指针指向内容不可变, 指针地址不受影响, 修改内容则编译报错。
define 与 const 选择
宏定义是在预编译期间处理, 在使用时系统直接进行的方法替换, 静态变量等则是在编译期间进行的。
宏定义不会系统不会做编译检查, 所以类型错误也能通过编译, const 则会做编译检查。
能显式的声明数据类型, 并且不会出现自己定义的宏被其他人员更换,导致出现难以排查的 Bug。
不过宏不仅能对数据类型进行定义, 还能对函数, 结构体, 方法等进行定义相对比起常量来说作用会更多一些。
编译时刻:宏是预编译, const是编译阶段
编译检查:宏不做检查, 有错误不会提示, const会检查, 有错误会提示
宏的优点:高效,灵活,可用于替换各种 函数,方法,结构体,数据等;
宏的缺点:由于在预编译期间完成, 大量使用宏, 容易造成编译时间久
const优点:编译器通常不为普通 const 常量分配存储空间, 而是将它们保存在符号表中, 这使得它成为一个编译期间的常量, 没有了存储与读内存的操作, 使得它的效率也很高, 相当于宏更加高效, 并且容错率很低。
const缺点:const 能定义的内容非常有限, 只能用于定义常量
宏定义所定义的生命周期与所在的载体的生命周期有关
const修饰具有就近性, 即 const 后面的参数是不可变的, const修饰的参数具有只读性, 如果试图修改, 编译器就会报错
苹果官方不推荐我们使用宏, 推荐使用const常量
10、创建字符串常量的方式
在.h文件中extern声明一些全局的常量,在.m实现
.h
extern NSString * const url;
.m
NSString * const url = @"www.baidu.com"
11、category和extension的区别
区别 | Category | Extension |
---|---|---|
添加成员变量 | 不可以 | 可以 |
添加属性 | 不可以(但通过runtime可以) | 可以 |
添加方法 | 可以 | 可以 |
语法格式 | @interface NSObject (category)/@implementation NSObject(category)//注意:括号里有名字 | @interface ClassName ()/@implementation ClassName()//注意:括号里无名字; |
1、Extension通常定义在主类.m文件中,Extension中声明的方法直接在主类的.m文件中实现。
写在.h文件里,属性、方法公有;
写在.m文件里,属性、方法私有。
2、Extension只声明方法,不实现方法,会报警告:Method definition for '方法名' not found
3、为什么要用Category?
当你已经封装好一个完整的类库,随着需求的变化,你发现需要给类库中某个类的增加新方法,为了防止误调用引起不必要的麻烦,可以通过增加这个类的分类达到同样的效果,方便管理。
4、Extension是Category的一种特殊形式。
5、Category里声明属性,不会自动生成带下划线的成员变量以及getter/setter方法,需要手动添加get/setter方法,通过runtime关联对象绑定,访问不会报错。
Extension是编译时自动生成带下划线的getter/setter方法。
6、Extension中声明的方法没被实现,编译器会报警,但是Category中的方法没实现编译器是不会有任何警告的。这是因为Extension是在编译阶段被添加到类中,而Category是在运行时添加到类中。
7、Extension不能像Category那样拥有独立的实现部分(@implementation部分),也就是说,Extension所声明的方法必须依托对应类的实现部分来实现。
动态库和静态库
(iOS里的动态库和静态库)
静态库:链接时,静态库会被完整地复制到可执行文件中,被多次使用就有多份冗余拷贝(图1所示)
系统动态库:链接时不复制,程序运行时由系统动态加载到内存,供程序调用,系统只加载一次,多个程序共用,节省内存(图2所示)
image