关于objective-c:iOS开发面试只需知道这些技术基本通关Runtime篇

42次阅读

共计 8197 个字符,预计需要花费 21 分钟才能阅读完成。

一、objc 对象的 isa 的指针指向什么?有什么作用?

指向他的类对象, 从而能够找到对象上的办法

详解:下图很好的形容了对象,类,元类之间的关系:

图中实线是 super_class 指针,虚线是 isa 指针。

1. Root class (class)其实就是 NSObjectNSObject 是没有超类的,所以 Root class(class)superclass指向 nil

2. 每个 Class 都有一个 isa 指针指向惟一的 Meta class

3. Root class(meta) superclass 指向 Root class(class),也就是 NSObject,造成一个回路。

4. 每个 Meta class 的 isa 指针都指向 Root class (meta)

二、一个 NSObject 对象占用多少内存空间?

受限于内存调配的机制,一个 NSObject对象都会调配 16byte 的内存空间。

然而实际上在 64 位 下,只应用了 8byte; 在 32 位下,只应用了 4byte

一个 NSObject 实例对象成员变量所占的大小,实际上是 8 字节

实质是

获取 Obj-C 指针所指向的内存的大小,实际上是 16 字节

对象在分配内存空间时,会进行内存对齐,所以在 iOS 中,分配内存空间都是 16 字节 的倍数。能够通过以下网址:openSource.apple.com/tarballs 来查看源代码。

三、说一下对 class_rw_t 的了解?

rw 代表可读可写。

ObjC 类中的属性、办法还有遵循的协定等信息都保留在 class_rw_t  中:

四、说一下对 class_ro_t 的了解?

存储了以后类在编译期就曾经确定的属性、办法以及遵循的协定。

五、说一下对 isa 指针的了解

说一下对 isa 指针的了解,对象的 isa  指针指向哪里?isa 指针有哪两种类型?

isa等价于 is kind of

· 实例对象 isa  指向类对象

· 类对象指 isa  向元类对象

· 元类对象的 isa  指向元类的基类

isa 有两种类型

· 纯指针,指向内存地址

· NON_POINTER_ISA,除了内存地址,还存有一些其余信息

isa 源码剖析

Runtime源码查看isa_t 是共用体。简化构造如下:

六、说一下 Runtime 的办法缓存?存储的模式、数据结构以及查找的过程?

首先作为一个开发者,有一个学习的气氛跟一个交换圈子特地重要,这是一个我的 iOS 开发公众号:编程大鑫,不论你是小白还是大牛都欢送入驻,让咱们一起提高,独特倒退!(群内会收费提供一些群主珍藏的收费学习书籍材料以及整顿好的几百道面试题和答案文档!)

cache_t 增量扩大的哈希表构造。哈希表外部存储的 bucket_t.

bucket_t 中存储的是 SEL IMP 的键值对。

如果是有序办法列表,采纳二分查找

如果是无序办法列表,间接遍历查找

cache_t 构造体

散列表查找过程,在 objc-cache.mm 文件中

下面是查问散列表函数,其中 cache_hash(k, m) 是动态内联办法,将传入的 key mask 进行 & 操作返回 uint32_t 索引值。do-while循环查找过程,当发生冲突 cache_next 办法将索引值减 1。

七、应用 runtime Associate 办法关联的对象,须要在主对象 dealloc 的时候开释么?

无论在 MRC 下还是 ARC 下均不须要,被关联的对象在生命周期内要比对象自身开释的晚很多,它们会在被 NSObject -dealloc调用的 object_dispose()办法中开释。

详解:

八、实例对象的数据结构?

具体能够参看 Runtime源代码,在文件 objc-private.h  的第  127-232 行。

实质上 objc_object  的公有属性只有一个  isa  指针。指向 类对象 的内存地址。

九、什么是 method swizzling(俗称黑魔法)

简略说就是进行办法替换

Objective-C 中调用一个办法,其实是向一个对象发送音讯,查找音讯的惟一根据是 selector 的名字。利用 Objective-C 的动静个性,能够实现在运行时偷换 selector  对应的办法实现,达到给办法挂钩的目标。每个类都有一个办法列表,寄存着办法的名字和办法实现的映射关系 selector 的实质其实就是办法名 IMP 有点相似函数指针,指向具体的 Method 实现,通过 selector 就能够找到对应的 IMP

换办法的几种实现形式

· 利用 method_exchangeImplementations 替换两个办法的实现

· 利用 class_replaceMethod  替换办法的实现

· 利用 method_setImplementation 来间接设置某个办法的 IMP

十、什么时候会报 unrecognized selector 的异样?

objc 在向一个对象发送音讯时,runtime库会依据对象的 isa指针找到该对象理论所属的类,而后在该类中的办法列表以及其父类办法列表中寻找办法运行,如果,在最顶层的父类中仍然找不到相应的办法时,会进入音讯转发阶段,如果音讯三次转发流程仍未实现,则程序在运行时会挂掉并抛出异样 unrecognized selector sent to XXX

十一、如何给 Category 增加属性?关联对象以什么模式进行存储?

查看的是 关联对象 的知识点。具体的说一下 关联对象。

关联对象 以哈希表的格局,存储在一个全局的单例中。

十二、是否向编译后失去的类中减少实例变量?是否向运行时创立的类中增加实例变量?为什么?

不能向编译后失去的类中减少实例变量;能向运行时创立的类中增加实例变量;

1. 因为编译后的类曾经注册在 runtime 中, 类构造体中的 objc_ivar_list 实例变量的链表和 instance_size 实例变量的内存大小曾经确定,同时 runtime  会调用 class_setvarlayout class_setWeaklvarLayout 来解决 strong weak 援用. 所以不能向存在的类中增加实例变量。

2. 运行时创立的类是能够增加实例变量,调用 class_addIvar 函数. 然而的在调用 `objc_allocateClassPair
之后,objc_registerClassPair` 之前, 起因同上.

十三、类对象的数据结构?

具体能够参看 Runtime源代码。类对象就是 objc_class

它的构造绝对丰盛一些。继承自 objc_object  构造体,所以蕴含 isa 指针

· isa:指向元类

· superClass: 指向父类

· Cache: 办法的缓存列表

· data: 顾名思义,就是数据。是一个被封装好的 class_rw_t 

十四、runtime 如何通过 selector 找到对应的 IMP 地址?

每一个类对象中都一个办法列表, 办法列表中记录着办法的名称, 办法实现, 以及参数类型, 其实 selector 实质就是办法名称, 通过这个办法名称就能够在办法列表中找到对应的办法实现.

十五、runtime 如何实现 weak 变量的主动置 nil?晓得 SideTable 吗?

runtime 对注册的类会进行布局,对于  weak 润饰的对象会放入一个  hash  表中。用  weak指向的对象内存地址作为 key,当此对象的援用计数为 0 的时候会 dealloc,如果 weak 指向的对象内存地址是 a,那么就会以 a 为键,在这个 weak表中搜寻,找到所有以 为键的  weak 对象,从而设置为 nil

更细一点的答复:

1. 初始化时:runtime会调用 objc_initWeak函数,初始化一个新的 weak  指针指向对象的地址。

2. 增加援用时:objc_initWeak 函数会调用 objc_storeWeak() 函数,objc_storeWeak()的作用是更新指针指向,创立对应的弱援用表。

3. 开释时, 调用 clearDeallocating 函数。clearDeallocating函数首先依据对象地址获取所有 weak指针地址的数组,而后遍历这个数组把其中的数据设为 nil,最初把这个 entry weak 表中删除,最初清理对象的记录。

SideTable 构造体是负责管理类的援用计数表和 weak 表,

详解:参考自《Objective-C 高级编程》一书

1. 初始化时:runtime 会调用 objc_initWeak 函数,初始化一个新的 weak 指针指向对象的地址。

当咱们初始化一个 weak 变量时,runtime会调用 NSObject.mm中的 objc_initWeak 函数。

通过 objc_initWeak 函数初始化“附有 weak 修饰符的变量(obj1)”,在变量作用域完结时通过

objc_destoryWeak 函数开释该变量(obj1)

2. 增加援用时:objc_initWeak 函数会调用 objc_storeWeak()函数,objc_storeWeak()的作用是更新指针指向,创立对应的弱援用表。

objc_initWeak函数将“附有 weak修饰符的变量(obj1)”初始化为 0(nil) 后,会将“赋值对象”(obj) 作为参数,调用 objc_storeWeak 函数。

也就是说

weak 润饰的指针默认值是 nil (在 Objective-C 中向 nil 发送音讯是平安的)而后 obj_destroyWeak 函数将 0(nil)作为参数,调用 objc_storeWeak  函数。

后面的源代码与下列源代码雷同。

objc_storeWeak 函数把第二个参数的赋值对象 (obj) 的内存地址作为键值,将第一个参数 weak  润饰的属性变量 (obj1) 的内存地址注册到 weak 表中。如果第二个参数 (obj)0(nil),那么把变量(obj1) 的地址从 weak 表中删除。

因为一个对象可同时赋值给多个附有weak 修饰符的变量中,所以对于一个键值,可注册多个变量的地址。

能够把 objc_storeWeak(&a, b)了解为:objc_storeWeak(value, key),并且当 key nil,将 value 置 nil。在b 非nil 时,b 指向同一个内存地址,在 b 变nil 时,nil。此时向 发送音讯不会解体:在 Objective-C 中向 nil 发送音讯是平安的。

3. 开释时, 调用 clearDeallocating 函数。clearDeallocating 函数首先依据对象地址获取所有 weak 指针地址的数组,而后遍历这个数组把其中的数据设为 nil,最初把这个 entry 从 weak 表中删除,最初清理对象的记录。

weak 援用指向的对象被开释时,又是如何去解决 weak  指针的呢?当开释对象时,其根本流程如下:

1. 调用 objc_release

2. 因为对象的援用计数为 0,所以执行 dealloc

3. 在 dealloc 中,调用了_objc_rootDealloc 函数

4. 在 _objc_rootDealloc  中,调用了 object_dispose 函数

5. 调用 objc_destructInstance

6. 最初调用 objc_clear_deallocating

对象被开释时调用的 objc_clear_deallocating 函数:

1. 从 weak  表中获取废除对象的地址为键值的记录

2. 将蕴含在记录中的所有附有 weak 修饰符变量的地址,赋值为 nil

3. 将 weak 表中该记录删除

4. 从援用计数表中删除废除对象的地址为键值的记录

总结:

其实 Weak 表是一个 hash(哈希)表,Key weak 所指对象的地址,Value  weak 指针的地址(这个地址的值是所指对象指针的地址)数组。

十六、objc中向一个 nil 对象发送音讯将会产生什么?

如果向一个 nil对象发送音讯,首先在寻找对象的 isa 指针时就是 0 地址返回了,所以不会呈现任何谬误。也不会解体。

详解:

如果一个办法返回值是一个对象,那么发送给 nil  的音讯将返回 0(nil)

如果办法返回值为指针类型,其指针大小为小于或者等于 sizeof(void*) floatdoublelong double 或者 long long  的整型标量,发送给 nil 的音讯将返回 0;

如果办法返回值为构造体, 发送给 nil  的音讯将返回 0。构造体中各个字段的值将都是 0;

如果办法的返回值不是上述提到的几种状况,那么发送给 nil 的音讯的返回值将是未定义的。

十七、objc 在向一个对象发送音讯时,产生了什么?

objc 在向一个对象发送音讯时,runtime会依据对象的 isa 指针找到该对象理论所属的类,而后在该类中的办法列表以及其父类办法列表中寻找办法运行,如果始终到根类还没找到,转向拦挡调用,走音讯转发机制,一旦找到,就去执行它的实现 IMP

十八、isKindOfClass 与 isMemberOfClass

上面代码输入什么?

答案:1000

详解:

isKindOfClass 中有一个循环,先判断 class  是否等于 meta class,不等就持续循环判断是否等于meta class super class,不等再持续取super class,如此循环上来。

[NSObject class]执行完之后调用 isKindOfClass,第一次判断先判断 NSObject 和  NSObject 的meta class 是否相等,之前讲到 meta class 的时候放了一张很具体的图,从图上咱们也能够看出,NSObject 的 metaclass  与自身不等。
接着第二次循环判断 NSObject 与 meta class  superclass 是否相等。还是从那张图下面咱们能够看到:Root class(meta) 的 superclass 就是 Root class(class),也就是 NSObject  自身。所以第二次循环相等,于是第一行 res1 输入应该为 YES

同理,[Sark class]执行完之后调用 isKindOfClass,第一次 for 循环,Sark 的Meta Class [Sark class]不等,第二次 for 循环,Sark Meta Class super class 指向的是 NSObject Meta Class,和 Sark Class 不相等。第三次 for  循环,NSObject Meta Class 的 super class  指向的是 NSObject Class,和 Sark Class 不相等。第四次循环,NSObject Class super class 指向  nil,和 Sark Class  不相等。第四次循环之后,退出循环,所以第三行的 res3  输入为NO

isMemberOfClass 的源码实现是拿到本人的 isa 指针和本人比拟,是否相等。

第二行 isa 指向 NSObject  Meta Class,所以和 NSObject Class  不相等。第四行,isa 指向 Sark 的 Meta Class,和 Sark Class  也不等,所以第二行 res2 和第四行 res4  都输入 NO

十九、Category 在编译过后,是在什么机会与原有的类合并到一起的?

1. 程序启动后,通过编译之后,Runtime 会进行初始化,调用 _objc_init

2. 而后会 map_images

3. 接下来调用 map_images_nolock

4. 再而后就是 read_images,这个办法会读取所有的类的相干信息。

5. 最初是调用 reMethodizeClass:,这个办法是从新办法化的意思。

6. 在 reMethodizeClass: 办法外部会调用 attachCategories:,这个办法会传入 Class Category ,会将办法列表,协定列表等与原有的类合并。最初退出到 class_rw_t 构造体中。

二十、Category 有哪些用处?

· 给零碎类增加办法、属性(须要关联对象)。

· 对某个类大量的办法,能够实现依照不同的名称归类。

二十一、Category 的实现原理?

被增加在了 class_rw_t 的对应构造里。

Category 实际上是 Category_t  的构造体,在运行时,新增加的办法,都被以倒序插入到原有办法列表的最后面,所以不同的 Category,增加了同一个办法,执行的实际上是最初一个。

拿办法列表举例,实际上是一个二维的数组。

Category 如果翻看源码的话就会晓得实际上是一个 _catrgory_t 的构造体。

例如咱们在程序中写了一个  Nsobject+Tools 的分类,那么被编译为 C++  之后,实际上是:

Category 在刚刚编译完的时候,和原来的类是离开的,只有在程序运行起来后,通过 Runtime 

Category 和原来的类才会合并到一起。

mememovememcpy:这俩办法是位移、复制,简略了解就是原有的办法挪动到最初,根根新开拓的控件,把后面的地位留给分类,而后分类中的办法,依照倒序顺次插入,能够得出的论断就就是,越晚参加编译的分类,外面的办法才是失效的那个。

二十二、_objc_msgForward 函数是做什么的,间接调用它将会产生什么?

_objc_msgForward 是 IMP 类型,用于音讯转发的:当向一个对象发送一条音讯,但它并没有实现的时候,

_objc_msgForward 会尝试做音讯转发。

详解:_objc_msgForward 在进行音讯转发的过程中会波及以下这几个办法:

1. List itemresolveInstanceMethod: 办法 (或 resolveClassMethod:)。

2.List itemforwardingTargetForSelector: 办法

3. List itemmethodSignatureForSelector: 办法

4. List itemforwardInvocation: 办法

5. List itemdoesNotRecognizeSelector: 方 法

具体请见:请看 Runtime  在工作中的使用 第三章 Runtime 办法调用流程;

二十三、[self class] 与 [super class]

上面的代码输入什么?

NSStringFromClass([self class]) = Son
NSStringFromClass([super class]) = Son

详解:这个题目次要是考查对于 Objective-C  中对 self  super  的了解。

self 是类的暗藏参数,指向以后调用办法的这个类的实例;

super 实质是一个编译器标示符,和  self 是指向的同一个音讯接受者。不同点在于:super 会通知编译器,当调用办法时,去调用父类的办法,而不是本类中的办法。

当应用 self 调用办法时,会从以后类的办法列表中开始找,如果没有,就从父类中再找;而当应用 super时,则从父类的办法列表中开始找。而后调用父类的这个办法。

在调用 [super class] 的时候,runtime 会去调用 objc_msgSendSuper  办法,而不是 objc_msgSend

objc_msgSendSuper 办法中,第一个参数是一个objc_super  的构造体,这个构造体外面有两个变量,一个是接管音讯的 receiver,一个是以后类的父类super_class

objc_msgSendSuper 的工作原理应该是这样的:

objc_super 构造体指向的 superClass  父类的办法列表开始查找 selector,找到后以 objc->receiver 去调用父类的这个 selector。留神,最初的调用者是 objc->receiver,而不是 super_class

那么 objc_msgSendSuper 最初就转变成:

因为找到了父类 NSObject  外面的 class 办法的 IMP,又因为传入的入参 objc_super->receiver= selfself 就是 son,调用 class,所以父类的办法 class 执行 IMP之后,输入还是 son,最初输入两个都一样,都是输入 son

正文完
 0