共计 8197 个字符,预计需要花费 21 分钟才能阅读完成。
一、objc 对象的 isa 的指针指向什么?有什么作用?
指向他的类对象, 从而能够找到对象上的办法
详解:下图很好的形容了对象,类,元类之间的关系:
图中实线是 super_class
指针,虚线是 isa
指针。
1. Root class (class)
其实就是 NSObject
,NSObject
是没有超类的,所以 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
表中搜寻,找到所有以 a
为键的 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
时,a
和b
指向同一个内存地址,在 b
变nil
时,a
变nil
。此时向 a
发送音讯不会解体:在 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*)
,float
,double
,long 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
和原来的类才会合并到一起。
mememove
,memcpy
:这俩办法是位移、复制,简略了解就是原有的办法挪动到最初,根根新开拓的控件,把后面的地位留给分类,而后分类中的办法,依照倒序顺次插入,能够得出的论断就就是,越晚参加编译的分类,外面的办法才是失效的那个。
二十二、_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= self
。self
就是 son
,调用 class
,所以父类的办法 class
执行 IMP
之后,输入还是 son
,最初输入两个都一样,都是输入 son
。