iOS-OC底层原理

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。

©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

  • Swift1> Swift和OC的区别1.1> Swift没有地址/指针的概念1.2> 泛型1.3> 类型严谨 对...
    cosWriter阅读 11,833评论 1 32
  • 面试题的答案都是抛砖引玉,具体细节还得深入了解iOS底层原理复制代码 1、一个NSObject对象占用多少内存? ...
    大豆豆_小豆豆阅读 456评论 0 0
  • 1、Objective-C的本质 Objective-C代码,底层实现其实都是C\C++代码。Objective-...
    学习天亦阅读 726评论 0 0
  • ————“勇气读书会”第三季第12篇 第八章第四节,胜可知不可为。 意思是善于打仗的人,先要做到自己...
    潇湘雨默阅读 231评论 0 4
  • 前几日读秋大的一篇文章,里面提到了这个观点,确实得以深思,今日就说说关于生存与生活的那些事! 生存的概念是什...
    阿毅阅读 401评论 0 1

友情链接更多精彩内容