Runtime Asssociate方法关联的对象,是否需要在dealloc中释放?
不需要释放
分析
我们知道当一个对象销毁的时候会调用dealloc
方法,那么我们先看下dealloc
都进行了哪些操作。
dealloc
函数调用了_objc_rootDealloc
函数_objc_rootDealloc
函数调用rootDealloc
函数
void
_objc_rootDealloc(id obj)
{
ASSERT(obj);
obj->rootDealloc();
}
rootDealloc
函数查看
inline void
objc_object::rootDealloc()
{
if (isTaggedPointer()) return; // fixme necessary?
if (fastpath(isa.nonpointer &&
!isa.weakly_referenced &&
!isa.has_assoc &&
!isa.has_cxx_dtor &&
!isa.has_sidetable_rc))
{
assert(!sidetable_present());
free(this);
}
else {
object_dispose((id)this);
}
}
从rootDealloc
函数中我们看到了判断isa相关属性的地方,实际上当一个对象存在会进入else
中,即object_dispose
函数
object_dispose
函数查看
id
object_dispose(id obj)
{
if (!obj) return nil;
objc_destructInstance(obj);
free(obj);
return nil;
}
通过objc_destructInstance
函数找到对象,然后free
,我们看objc_destructInstance
函数
objc_destructInstance
函数 重点查看_object_remove_assocations
函数_object_remove_assocations
函数分析
类、分类方法同名时调用顺序是怎样的?
当非+load
方法同名时,分类的方法在类的方法前面(注意不是覆盖
),因为分类的方法是在类realize之后 attach进去的
,所以 优先分类,其次类
当+load
方法同名时,优先类,其次分类
分类与类的扩展
分类
- 专门用来给类添加新的方法
- 不能添加属性,但是可以通过runtime动态添加属性(因为我们在前面的篇章中分析过,分类底层代码中有属性列表)
- 分类中
@property
定义的变量只会生成setter
以及getter
方法的声明,但是不会生成对应的方法实现以及带有下划线的成员变量
类的扩展
- 可以添加给类添加属性,但是属于私有变量,比如ViewController的.m文件中
@property
定义的变量只能是这个ViewController的.m文件使用 - 添加的方法也是私有方法
什么是Runtime?
runtime是由C和C++汇编实现的一套API,为OC语言添加了面向对象和运行时功能。
- 运行时:将数据类型的确定由编译阶段推迟到了运行阶段。我们平时所写的OC代码,最终转换为runtime的C语言代码。
方法的本质是什么?SEL、IMP是什么?两者之间的关系是什么?
方法的本质
方法的本质是消息的发送
,涉及到消息发送的流程有
- 快速查找:
objc_msgSend
~cache_t中查找 - 慢速查找:递归自己以及父类查找,即
lookUpImpOrForward
- 动态解析:当查找不到消息时执行动态解析,即
resolveInstanceMethod
- 消息快速转发:当动态解析也没有找到消息,则进行消息快速转发,即
forwardingTargetForSelector
- 消息慢速转发:消息快速转发没有找到消息,则进行消息慢速转发,即
mesthodSignatureForSelector & forwardInvocation
- 以上流程均没有找到消息则crash
SEL、IMP
- sel:方法编号,类比一本书的目录
- imp:方法函数指针地址,类比一本书的页数
- sel与imp关系:sel是方法编号,通过sel找到imp的函数指针地址,通过imp就能找到函数的实现
能否向编译后的类中添加实例变量?能否向运行时创建的类添加实例变量?
- 不能向编译后的类中添加实例变量,因为
编译后实例变量存储到 ro 中,一旦编译完成,内存结构就完全确定了,无法再次修改
- 只要类还没有注册到内存还是可以添加的
- 可以添加属性与方法
[self class] 与 [super class]的区别
我们先看以下如下代码打印结果,其中self是LGTeacher类,LGTeacher继承于LGPerson,LGPerson继承于NSObject从打印结果中我们看到无论是[self class]
还是[super class]
的结果是一样的,为什么呢?
分析
- 我们知道任何方法调用都会隐藏两个参数,即
(id self , sel _cmd)
,其中self
是消息接收者。对于[self class]
来说,它的消息接收者是自身LGTeacher
没什么可说的,所以打印的是LGTeacher
。 - 首先我们要知道
super
只是关键字,它意思是说从父类调用方法,因此[super class]
就是直接调用的就是父类的class
方法,它的本质是objc_msgSendSuper
,只是objc_msgSendSuper
速度更快,直接跳过self
。但需要注意的是,[super class]
的消息接受者依然是LGTeacher
,所以最终打印的是LGTeacher
。
内存偏移相关问题
我们先准备代码,定义IFPerson
类,代码如下我们再看ViewController代码
从上述代码中我们延伸出两个问题:代码是否崩溃
、doSomething打印结果是什么
。先不回答这两个问题,我们运行代码看结果如何,运行结果如下图
从运行结果中我们可以看出代码不会崩溃且运行结果也出来了
.
[(__bridge id)kc doSomething]
为什么不会崩溃?
首先我们知道对于一个对象,它的指针地址指向的是isa
,同时isa
地址指向当前的class
,所以kc
指向的是IFPerson
的isa
,而person
的指针指向的也是isa
,这样它们都是isa
从cache_t
中查找doSomething
方法,因此不会崩溃。
为什么[(__bridge id)kc doSomething]
打印的结果是ViewController
?
- 从打印结果中
[person doSomething]
打印出出来shifx
是没有什么问题的,毕竟给person.name赋值shifx
,但是[(__bridge id)kc doSomething]
打印的结果是ViewController
呢?要解决这个问题首先我们需要知道person
能够找到name
是指针从isa内存平移了8个字节
移动到了name
。那么对于kc
来说,它也需要指针平移,但是为什么平移后的结果是viewController
呢?这就需要明白栈地址是从高到低存储的,且是先进后出
,由于前面先调用了[super viewDidLoad]
方法,且viewDidLoad
的隐藏参数是(id self, SEL _cmd)
,所以self
会先入栈,其次是cls
->kc
->person
,出栈的顺序刚好相反,由于[(__bridge id)kc doSomething]
时需要指针平移,自然指向了self(即ViewController)
,所以打印的结果是ViewController
。 - 为了验证我们上面分析是否正确,我们修改代码位置,将声明
IFPerson *person = [[IFPerson alloc] init]
放在[super viewDidLoad]
之后,即 此时我们按照我们上面的分析self
会先入栈,其次是person
->cls
->kc
,猜测[(__bridge id)kc doSomething]
打印结果应该是IFPerson (person的isa指向其Class)
,我们运行代码结果可以看出我们的分析是正确的。