1.OC对象的本质
1.1一个NSObject对象占用多少内存?
系统默认分配16个字节给NSObject对象(通过malloc_size函数获得),但NSObject对象只使用了8个字节的空间(64bit环境下,通过class_getInstaneSize获得),8个字节用来存放isa指针变量,在编译是oc对象会转换成C语言中的结构体。
1.2OC对象的分类
OC对象分为实例对象(instance)、类对象(class)、元类对象(meta class),
实例对象是通过alloc init生成的对象,存储isa指针和成员变量的值。类对象通过[object1 class]、object_getClass(object1)、[NSObject class]获取的对象,存储isa指针、superClass指针、对象方法、成员变量的信息、类的属性信息(@property)、协议信息。元类对象是通过类对象调用object_getClass(object1)获取得到的,主要存储类的 存储isa指针、superClass指针、类方法等信息。
1.3 Class objc_getClass(const char *name) 和Class object_getClass(obj)函数的区别
Class objc_getClass(const char *name) 传入字符串类名,返回对应的类对象。
Class object_getClass(obj)传入的可能是instance、class、metaClass对象,返回值如果传入的是instance对象,返回class对象;如果传入的是class对象,返回metaClass对象;如果传入的是metaClass对象,返回NSObject根源类(基类)的metaClass对象。
1.4isa和superClass的总结
instance的isa指向class对象
class对象的isa指向metaClass对象
metaClass对象的isa指向基类对象
class的superClass指向父类的class,如果没有父类,superClass则为nil
metaClass的superClass指向父类的metaClass,父类的metaClass指向基类的metaClass,基类的metaClass的superClass指向基类的class对象
instance调用对象方法的轨迹:instance通过isa找到自己的class对象,如果在class对象方法列表找不到对象方法,就通过superClass查找父类的类对象方法,如果都找不到,会报错:unrecognized selector send to XXX。
class调用类方法的轨迹:class对象通过isa寻找自己的metaClass,如果在metaClass的类对象方法列表中找不到对应的类方法,就通过superClass找父类的metaClass的类方法,如果都找不到,会报错:unrecognized selector send to XXX。
2.1KVO实现原理
2.1iOS用什么方式实现对一个对象的KVO?(KVO的本质是什么?)
利用Runtime动态生成一个继承自该对象的子类,并且让instance对象的isa指向这个全新的子类。当修改该对象的属性时(调用成员变量的set方法),会调用Foundation的NSSetXXXValueAndNotify函数。
通过调用willChangeValueForKey: ,父类原来的setter,didChangeValueForKey:函数,触发内部会触发监听器(Oberser)的监听方法(observeValueForKeyPath:ofObject:change:context:)
2.2如何手动触发KVO?
手动调用willChangeValueForKey:和didChangeValueForKey会触发KVO
2.3直接修改成员变量会触发KVO么?
在不进行手动触发KVO的前提下,直接修改成员变量,不会触发KVO
2.4通过KVC修改属性会触发KVO么?
在通过KVO直接监听instance对象时,不管通过setValueForKey:还是直接修改对象的成员变量,都会触发KVO,原因是KVC内部会调用willChangeForKey和didChangeForKey方法。
2.5KVC的赋值和取值过程是怎样的?原理是什么?
setValue:forKey:的原理
首先会查找setKey:、_setKey: (按顺序查找),如果有直接调用,如果没有,先查看accessInstanceVariablesDirectly方法,如果可以访问会按照 _key、_isKey、key、iskey的顺序查找成员变量,找到直接赋值,未找到报错NSUnkonwKeyException错误。
valueForKey:的原理
kvc取值按照 getKey、key、iskey、_key 顺序查找方法,存在直接调用,没找到同样,先查看accessInstanceVariablesDirectly方法,如果可以访问会按照 _key、_isKey、key、iskey的顺序查找成员变量,找到直接赋值,未找到报错NSUnkonwKeyException错误
3.1 Category
3.1Category的使用场合是什么?
1.需求变化,需要添加新的方法
2.原有类代码结构复杂,通过category分化模块
3.多人协作开发,用分类区分代码模块
4.扩充系统基础类,比如给NSString添加新方法
5.覆盖原有类方法
3.2Category的实现原理
category编译之后底层是struct category_t结构体结构,里面存储分类的对象方法、类方法、属性、协议信息等。在程序运行时,runtime会将category的数据,合并到原有类的信息中,实例方法会存储在对应的类对象中,类方法会存储在元类对象中。注意:多个category和原有类同时存在同名函数,category会覆盖原有类的同名方法,不同category之间调用方法取决于文件编译顺序,最后进行编译的文件的方法,优先级最高,会优先调用该文件的同名函数方法。
3.3Category和Class Extension的区别是什么?
Class Extension在编译的时候,类扩展的就已经包含在类信息中(编译时)
category是在运行时,利用runtime机制,才会将数据合并到类信息中(运行时)
3.4load方法的加载原理
load方法在类、分类加载进内存的时候调用(运行时),在程序运行的过程中只调用一次。调用顺序:先调用类方法的load,再调用分类的load,不同类、分类文件之间加载load方法的顺序按照文件编译的顺序调用。在加载子类的load方法时,必须先调用父类的load方法。
load方法是根据方法地址直接调用,并不是经过objc_msgsend方法来调用。
3.4-2 initialize方法的调用原理
initialize方法在类第一次接收消息的时候调用,如果这个类在程序运行时没有被创建过,则不会被调用。调用顺序:先调用父类的initialize,再调用子类的initialize,每个类只会被初始化一次。如果子类没有实现initialize方法,通过消息机制,会调用父类实现的initialize方法。+initialize和+load的很大区别是,+initialize是通过objc_msgSend进行调用的,所以有以下特点
如果子类没有实现+initialize,会调用父类的+initialize(所以父类的+initialize可能会被调用多次)
如果分类实现了+initialize,就覆盖类本身的+initialize调用。
3.4.1Category中有load方法吗?load方法是什么时候调用的?load 方法能继承吗?
有load方法
load方法在runtime加载类、分类的时候调用
load方法可以继承,但是一般情况下不会主动去调用load方法,都是让系统自动调用
3.4.2load、initialize方法的区别什么?它们在category中的调用的顺序?以及出现继承时他们之间的调用过程?
load、initialize方法的区别什么?
1.调用方式
1> load是根据函数地址直接调用
2> initialize是通过objc_msgSend调用
2.调用时刻
1> load是runtime加载类、分类的时候调用(只会调用1次)
2> initialize是类第一次接收到消息的时候调用,每一个类只会initialize一次(父类的initialize方法可能会被调用多次)
load、initialize的调用顺序?
1.load
1> 先调用类的load
a) 先编译的类,优先调用load
b) 调用子类的load之前,会先调用父类的load
2> 再调用分类的load
a) 先编译的分类,优先调用load
2.initialize
1> 先初始化父类
2> 再初始化子类(可能最终调用的是父类的initialize方法)
4.block的原理是怎样的?本质是什么?
封装了函数调用以及调用环境的OC对象,本质还是一个OC对象。
4.1block的变量捕获
为了保证block能访问外部变量,所以block有个变量捕获机制。
1、局部变量:对于auto类型的变量,block捕获的变量的方式是值传递。对于static修饰的变量,因为static修饰的变量在内存中只有一份,内存地址不会发生变化,所以访问变量的方式是指针传递。
2、全局变量,因为在任何地方都可以访问全局变量,所以不需要捕获变量,直接进行访问就行。
4.2 block的类型
block分为NSGlobalBlock、NSStackBlock、NSMallocBlock三类。
应用程序的内存分成:代码区、数据区、堆、栈四个部分。
NSGlobalBlock是存储在数据区的block,没有访问auto类型的变量。NSStackBlock是存储在栈区的block,访问了auto类型的变量。NSMallocBlock是存储在堆区的block,将NSStackBlock进行copy操作就变成了NSMallocBlock。
4.1__block的作用是什么?有什么使用注意点?
__block可以用于解决block内部无法修改auto变量值的问题,其本质是编译器会将__block变量包装成一个对象。注意点:__block不能修饰全局变量、静态变量(static)。
4.2 block的属性修饰词为什么是copy?使用block有哪些使用注意?
block一旦没有进行copy操作,就不会在堆上,在ARC下,使用strong和copy本质上没有什么区别,使用strong也可以将block copy到堆上。
使用注意:循环引用问题
4.3如何解决block的循环引用问题?
在ARC下:
1、可以使用__weak typeof(self) weakSelf = self,将对象进行弱引用。
2.使用unsife_unretain修饰词进行弱引用。
相同点是__weak、unsife_unretain都可以改变强引用变成弱引用,不同点是unsafe_unretain是不安全的,用__weak进行修饰,变量被释放后指针会自动被修改成nil,unsafe_unretain则不会,再次访问该对象会报 野指针错误。
3.可以使用__block解决循环引用,核心操作是 在block内部 将对象置成nil,person=nil,并且需要调用该block,block()。缺点是:如果不主动调用block(),则该对象不会被释放,造成内存泄漏。
综上:在ARC下推荐使用___weak。
在MRC下:可以使用unsife_unretain和block解决循环引用。
4.4 block在修改NSMutableArray,需不需要添加__block?
不需要,如果是对NSMutableArray 进行addObject等增删操作,不需要调用__block。如果要进行array=nil,则需要添加__block。