美文网首页
第一部分: OC语法

第一部分: OC语法

作者: 大风先生bigWind | 来源:发表于2020-01-16 16:19 被阅读0次

    前言:

    我们直接用面试题的方式来回答和解读oc语法的底层原理。看下面的面试题:

    有关oc面相对象的:

    1,面向对象编程OOP是什么意思?谈谈你对OOP的理解。
    2,一个nsobject占用多少内存空间?
    3,对象的isa指针和superclass指向哪里?
    4,oc对象的类信息存放在哪?
    5,oc对象的本质是什么?
    6,谈谈你对isa指针的了解?

    KVO有关的:

    1,oc是如何实现对一个对象的KVO?
    2,如何手动触发KVO?
    3,直接修改成员变量会触发KVO吗?

    KVC相关的:

    1,KVC的实现原理是怎样的?
    2,通过KVC修改属性会触发KVO吗?

    Category分类相关的:

    1,Caregory是什么?什么场合使用?
    2,Caregory的实现原理?
    3,Catefory和Class Extension的区别是什么?
    4,Category中有load方法吗?load方法和intialize方法的区别是什么?

    关联对象相关的 :

    1,关联对象常用的api有哪些?
    2,关联对象的底层是怎么实现的?

    Block相关(写了另一篇来讲了)

    链接:https://www.jianshu.com/p/eed13c33c8bc

    答案和详解

    一, OC对象的本质

    面试题1:面向对象编程OOP是什么意思?谈谈你对OOP的理解。
    面试题2: 一个nsobject对象占用多少内存空间?

    答: 系统给nsobject分配了16个字节的空间(可以通过#import<malloc/malloc.h>框架下的malloc_size()方法来获取);
    但实际上在64bit环境下nsobject对象内部只使用了8字节的空间(可以通过#import<objc/runtime.h>的class_getInstanceSize()方法来获取).
    以上是一个没有属性和成员变量的对象所占用的字节, 通过上面两个方法, 我们还可以发现, 对于拥有多个成员变量和属性的对象, 系统给其分配的内存空间是以16的倍数增长的, 而实际内部占用的空间则不一定是16的倍数.

    面试题3: oc对象的分类有几种? isa指针是怎样的?

    答: 三种, 分别是:
    instance对象(实例对象);
    class对象(类对象);
    meta-class对象(元类对象).
    下面是他们的isa指针指向和superclass指针的指向。其中需要注意的是:基类元对象的isa指针指向自己;基类元对象的superclass指针指向基类的class对象,而基类对象的superclass指针指向nil。


    isa指向.png
    面试题4:oc对象的本质是什么?

    OC对象的本质是一个结构体。通过将OC的文件编译成cpp文件之后可以看到,一个OC的类编译之后就是一个cpp的结构体,结构是这样的:


    类的本质.png

    对于自定义的类,例如GQStudent类,编译后的结构体跟上面是一样的,只是编译后的结构体的名称一般都是student_IMPL{}.

    面试题5:oc对象的类信息存放在哪?

    根据上面的两个题,那么这个问题就很好回答了。类对象的信息是存放在objc_class结构体的bits结构体中;并且通过FAST_DATA_MASK掩码可以得到bits结构体地址,在bits中的ro结构体里面存放着oc对象的类信息。ro包括的信息有:instanceSize表示对象占用内存空间大小,name类名,ivars成员变量列表等等。

    面试题6:谈谈你对isa指针的了解?

    在64bits之前,isa指针指示单纯的指针,里面存放着所指向的对象地址。在64bits之后,isa指针被优化成tagged pointer,里面不仅能存放地址,也能存放相对较小的number string等对象,而不需要为这样的对象开辟新的内存地址,用来减小内存开销和调用效率。并且在64bit之后,需要通过ISA_MASK掩码才能获得真正的isa地址

    二, KVO有关的

    面试题1: oc是如何实现对一个对象的KVO?

    当我们对一个instance对象进行KVO的时候,runtime的API会动态生成一个全新的中间对象,当修改instance中的属性时,会调用Fundation的NSSet**ValueAndNotify{
    [self willChangeValueForkey:@“”];
    [原来的instance的setter方法实现];
    [self didChageValueForKey:@“”];
    },并且会触发内部的observe监听器的监听方法(observeValueForKeyPath:ofObject:change:context:)以实现对对象的监听。
    以MJPerson为例,如图所示:
    原来的MJPerson的instance对象和class对象之间新建一个中间对象NSKVONotifying_MJPerson对象,并且把instance的isa指向NSKVONotifying_MJPerson,把NSKVONotifying_MJPerson的superclass指针指向原来的class对象。NSKVONotifying_MJPerson中对setter方法进行了重写,重写的原理是在原来的setter方法赋值之前调用[self willChangeValueForKey:@""]和赋值之后调用[self didChangeValueForKey:@""]. 并且重写后的方法会触发NSFundation_NSSet
    ValueAndNotify方法发出通知。所以这是实现的原理。

    KVO原理.png
    面试题 2:如何手动触发KVO?

    手动的调用willChangeValueForKey:或者didChangeValueForKey:方法都能触发KVO。

    面试题 3:直接接修改成员变量会触发KVO吗?

    不会,因为不经过上面1说的那个过程。

    三, KVC相关的:

    面试题 1:KVC的实现原理是怎样的?

    KVC全程是Key_Value Coding也就是俗称的“键值编码”,可以通过key来访问某个属性。常见的KVC的API接口有:


    kvc接口.png

    当我们调用setValue:forKey:方法时,会先按照次序调用setKey:和_setKey:方法修改属性值,而如果调用不成功则会调用accessInstanceVariableDirectly:方法,如果返回YES则按照次序调用_key:和 _isKey: 和 key:和 _isKey:方法获取属性并直接赋值;而如果accessInstanceVariableDirectly:返回YES后调用了前面四个方法之后没找到属性值,或者accessInstanceVariableDirectly返回了NO,则调用setValue:forUndefineKey:方法并抛出异常NSunknownKeyException。下图是调用的过程图:


    kvc赋值.png
    当我们调用valueForKey:方法时候,过程也是类似的。会首先依次调用getKey:→key:→isKey:→_key方法,找到了则直接调用获取属性;找不到则会调用accessInstanceVariablesDirectly如果返回了YES,则依次调用_key:→ _isKey:→ key:→ isKey:方法找到了则直接调用取值。如果上面找不到或者accessInstanceVariablesDirectly直接返回NO,则调用setValue:forUndefineKey:方法并抛出异常NSunknownKeyException。下图是调用的过程图:
    kvc取值.png
    面试题 2:通过KVC修改属性会触发KVO吗?

    会。因为KVO调用过程中会调用setter方法,进而触发KVO。

    四,Category(也叫分类)相关的:

    面试题 1:Caregory是什么?什么场合使用?

    category也叫分类,是runtime支持实现的对类进行动态添加拓展, 是oc语法的一种。
    分类使用的场合主要有:
    1,降低单个类的体积,降低耦合性,可以多人开发;
    2,可以对系统的类进行拓展;
    3,可以模拟多继承;
    4,可以用来对静态库方法进行公开。

    面试题 2:Caregory的实现原理?

    category在编译后是一个结构体category_t,里面存放着分类的类方法、方法、协议信息等,但是这个结构体里并没有属性列表,所以不能往分类里添加属性。在程序运行时候,runtime会将一个类的分类动态的合并到类信息里面(包括类对象和元类对象)。并且后编译的category会排在class的methods列表前面,所以当一个类有多个category时,后面编译的category的类方法和方法等会优先被调用。但这不是覆盖,而是优先调用。

    面试题 3:Catefory和Class Extension的区别是什么?

    category和class extension是两个不同的概念,都是oc语法中的一种,只是中文叫法有点类似。
    它们的区别主要有:
    1,category一般只能用来对类进行方法的扩展,而不能添加成员变量属性变量;而class extension可以添加属性和方法并且会自动生成setter和getter方法。
    2,category中的方法是运行时合并到原来的类信息之中,而class extension中的属性和方法等是编译时候就添加到原来的类信息之中了。所以当category中声明的方法没有实现时候,编译时候也不会被提醒,而class extension就会报警。
    3,class extension没有独立的.m文件,是需要在原来的类的.m文件中进行实现的,也就是需要有原来的类代码,需要依托源代码实现。而category有独立的.m文件,可以在.m中实现方法。

    面试题 4:Category中有load方法吗?load方法什么时候调用?load方法可以被继承吗?load方法和intialize方法的区别是什么?

    category中是有load方法的。+load方法是在runtime加载类和分类的时候调用,并且在整个程序运行过程中知知调用一次。load方法可以被继承但实际开发中我们都最好不要主动调用laod方法,让系统自动调用。而且调用的次序分别是:
    先调用类的+load方法(其中,是按照编译顺序来调用的,先编译先调用,而且调用子类的load方法之前会先调用父类的load);
    然后再调用分类的laod(其中,分类是按照编译顺序来调用的,先编译的会先被调用)。
    而且由上面的顺序可以看出,load方法是通过方法地址直接调用的,而不是通过objc_msgSend方法来调用。

    intialize方法是在类第一次接收到信息时候调用。并且是先调用父类的intialize方法再调用子类的intialize方法(其实就是:先初始化父类在初始化子类,并且每个类只会初始化一次)。
    intialize方法跟load方法最大的区别是调用不同,intialize方法是通过消息objc_msgSend方法调用的,而load方法是通过内存地址直接调用的。所以他们有以下各自的特点:
    当子类的intialize方法没有实现的时候,就会调用父类的intialize方法(所以父类的intialize方法有可能会被多次调用);
    如果分类中实现了intialize方法,则会覆盖父类的intialize方法的调用。

    面试题 5:如何给Category添加属性?

    category的底层结构限制了无法给分类添加属性。但是可以通过关联对象来间接地实现给分类添加属性。

    五,关联对象(associatedObject)相关的:

    面试题 1:关联对象常用的api有哪些?

    常用的有三个接口:


    image.png

    其中,对于key,除了可以使用属性名来作为key以外,还可以是使用方法签名来作为key这样可以较好的保证其唯一性。例如可以像下面这样设置key:


    image.png
    然后,关于objc_AssociationPolicy可以参考下面的表格:
    image.png
    面试题 1:关联对象的实现原理?

    关联对象技术的核心对象有以下四个:
    AssosiationManager;
    AssosiationHashMap;
    ObjectAssosiationMap;
    ObjectAssosiation.
    他们的之间的关系是这样的:


    image.png

    原理是这样的:关联对象并不是存储在被关联对象本身的内存中,而是存储在一个由runtime来保持的全局的统一的AssosiationManager对象中;这个对象持有一个AssosiationHashMap,在AssosiationHashMap里面存储着被关联对象的key以及被关联的属性ObjectAssosiationMap,而ObjectAssosiationMap中就存储着要关联的属性。
    所以,当我们往一个对象里添加关联对象的时候,AssosiationManager就会往自己的AssosiationHashMap中添加一个键值对,这个键值对里就存着需要添加的属性信息。当我们要移除关联对象时候,除了可以调用移除方法以外,还可以通过设置nil来移除。

    相关文章

      网友评论

          本文标题:第一部分: OC语法

          本文链接:https://www.haomeiwen.com/subject/vypczctx.html