问:
编译型语言与OC这种动态编译语言的区别有哪些?
消息传递与函数调用有怎样的区别?
为了很好的回答以上问题,我们开始一起学习
数据结构
我们主要学习四种数据结构:
objc_object
objc_class
isa指针
method_t
typedef struct objc_class *Class;
typedef struct objc_object *id;
objc_object
在源码文件中,截取了部分内容:
struct objc_object {
private:
isa_t isa;
public:
//关于isa的操作
Class ISA();
Class getIsa();
void initIsa(Class cls /*nonpointer=false*/);
void initClassIsa(Class cls /*nonpointer=maybe*/);
//弱引用相关
bool isWeaklyReferenced();
void setWeaklyReferenced_nolock();
//关联对象相关
bool hasAssociatedObjects();
void setHasAssociatedObjects();
//内存管理相关
id retain();
void release();
id autorelease();
....
}
objc_class
在源码文件中,截取了部分内容:
struct objc_class : objc_object {
// Class ISA;
Class superclass;
cache_t cache; // formerly cache pointer and vtable
class_data_bits_t bits; // class_rw_t * plus custom rr/alloc flags
......
}
isa指针
isa_t isa;
isa是isa_t类型的
在源码文件中,截取了部分内容:
union isa_t
{
isa_t() { }
isa_t(uintptr_t value) : bits(value) { }
Class cls;
uintptr_t bits;
......
}
struct cache_t {
struct bucket_t *_buckets;
mask_t _mask;
mask_t _occupied;
......
}
struct class_data_bits_t {
public:
class_rw_t* data() {
return (class_rw_t *)(bits & FAST_DATA_MASK);
}
void setData(class_rw_t *newData)
{
assert(!data() || (newData->flags & (RW_REALIZING | RW_FUTURE)));
uintptr_t newBits = (bits & ~FAST_DATA_MASK) | (uintptr_t)newData;
atomic_thread_fence(memory_order_release);
bits = newBits;
}
......
}
struct class_rw_t {
const class_ro_t *ro;
method_array_t methods;
property_array_t properties;
protocol_array_t protocols;
......
}
其中:
class method_array_t :
public list_array_tt<method_t, method_list_t>
{
typedef list_array_tt<method_t, method_list_t> Super;
public:
method_list_t **beginCategoryMethodLists() {
return beginLists();
}
method_list_t **endCategoryMethodLists(Class cls);
method_array_t duplicate() {
return Super::duplicate<method_array_t>();
}
};
struct class_ro_t {
const char * name;
method_list_t * baseMethodList;
protocol_list_t * baseProtocols;
const ivar_list_t * ivars;
const uint8_t * weakIvarLayout;
property_list_t *baseProperties;
method_list_t *baseMethods() const {
return baseMethodList;
}
};
method_t
struct method_t {
SEL name;
const char *types;
IMP imp;
};
数据类型总结
这个图是真好
类对象与元类对象
实例对象、类对象、元类对象
类对象存储实例方法列表等信息
元类对象存储类方法列表等信息
需要注意的地方:
元类对象的isa指针,都是指向根元类对象。包括根元类对象本身,也是指向根元类对象
根元类对象的superclass指针,指向的是根类对象
问:元类对象的isa指针指向哪里?
元类对象的isa指针,都是指向根元类对象。包括根元类对象本身,也是指向根元类对象
问:如果一个类方法没有实现,但是有同名的对象方法实现,会崩溃?还是会调用?
对象方法存储在类对象里面;
类方法存储在元类对象里面;
这个问题应该这样看:
如果一个类方法没有实现,根据superclass指针,会去父类里面找同名类方法,直至到根元类对象里面去查找;
如果中间找到了对应的同名类方法,则会调用。
如果中间没有找到对应的同名类方法,则根元类对象的superclass指向的是根类对象
如果根类对象里面有对应的同名实例方法(类对象(包括根类对象)只能存储实例方法),则会调用。
如果根类对象里面没有对应的同名实例方法,则根据消息传递原则,进入动态方法解析阶段。
举个例子:
@interface YZPerson : NSObject
+ (void)run;
- (void)run;
@end
#import "YZPerson.h"
@implementation YZPerson
//+ (void)run
//{
// NSLog(@"类方法-run");
//}
- (void)run
{
NSLog(@"实例方法-run");
}
@end
- (void)viewDidLoad {
[super viewDidLoad];
[YZPerson run];
}
结果:崩溃
也就是说,person里面虽然有同名的对象方法-(void)test;,但是,不好意思,superclass指针最多也就到达根类对象里面,到达不了类对象person里面,所以,还是找不到-(void)test;
换句话说,如果类方法没有实现,只有在根类对象里面有同名实例方法,才能调用。
根类对象一般指的是NSObject,如果需要在根类对象添加,也就是给NSObject添加自定义实例方法,这就需要分类了。分类可以给系统添加方法。
@interface YZPerson : NSObject
+ (void)run;
- (void)run;
@end
#import "YZPerson.h"
@implementation YZPerson
//+ (void)run
//{
// NSLog(@"类方法-run");
//}
- (void)run
{
NSLog(@"实例方法-run");
}
@end
NSObject+test.h文件
@interface NSObject (test)
- (void)run;
@end
NSObject+test.m文件
#import "NSObject+test.h"
@implementation NSObject (test)
- (void)run
{
NSLog(@"NSObject-实例方法-run");
}
@end
- (void)viewDidLoad {
[super viewDidLoad];
[YZPerson run];
}
打印结果:
NSObject-实例方法-run
堪称是完美
消息传递
消息传递
void objc_msgSend(void /* id self, SEL op, ... */ )
里面是两个默认参数,self和SEL。
[self class]经过编译器转换为objc_msgSend(self, @selector(class));
void objc_msgSendSuper(void /* struct objc_super *super, SEL op, ... */ )
里面是两个默认参数,super和SEL。
[super class]经过编译器转换为objc_msgSendSuper(super, @selector(class));
而,其中struct objc_super的数据结构是:
struct objc_super {
/// Specifies an instance of a class.
__unsafe_unretained _Nonnull id receiver;
#if !defined(__cplusplus) && !__OBJC2__
/* For compatibility with old objc-runtime.h header */
__unsafe_unretained _Nonnull Class class;
#else
__unsafe_unretained _Nonnull Class super_class;
#endif
/* super_class is the first class to search */
};
里面有句话:super_class is the first class to search
也就是,搜索方法,是从super_class开始的
objc_msgSendSuper({self, super_class}, @selector(class));
receiver是当前对象,也就是self,才是真正的消息接收者
也就是,不论是[self class]还是[super class],消息接收者都是当前对象self。
因此,上面的图片中,打印结果是:
Phone
Phone
老师的消息传递图中,在父类查找到方法后直接调用,没有缓存,这是不对的。
通过对源码的解读,在原类缓存中无对应方法的时候,去原类或父类中查找:
// Try this class's cache.
imp = cache_getImp(cls, sel);
if (imp) goto done;
// Try this class's method lists.
{
//去查找:有序二分查找,无序遍历查找
Method meth = getMethodNoSuper_nolock(cls, sel);
if (meth) {//找到方法
//缓存
log_and_fill_cache(cls, meth->imp, sel, inst, cls);
imp = meth->imp;//取出方法的IMP
goto done;//调用
}
}
// Try superclass caches and method lists.
{
unsigned attempts = unreasonableClassCount();
//遍历父类
for (Class curClass = cls->superclass;
curClass != nil;
curClass = curClass->superclass)
{
// Halt if there is a cycle in the superclass chain.
if (--attempts == 0) {
_objc_fatal("Memory corruption in class list.");
}
// Superclass cache.
//从父类缓存中找
imp = cache_getImp(curClass, sel);
if (imp) {
if (imp != (IMP)_objc_msgForward_impcache) {
// Found the method in a superclass. Cache it in this class.
log_and_fill_cache(cls, imp, sel, inst, curClass);//cls是当前类,也就是缓存到原类
goto done;//调用
}
else {
// Found a forward:: entry in a superclass.
// Stop searching, but don't cache yet; call method
// resolver for this class first.
break;
}
}
// Superclass method list.
//从父类方法列表中找
Method meth = getMethodNoSuper_nolock(curClass, sel);
if (meth) {
log_and_fill_cache(cls, meth->imp, sel, inst, curClass);//cls代表原类,因此找到后,缓存到原类
imp = meth->imp;
goto done;
}
}
}
其中,log_and_fill_cache里面的部分调用
bucket_t *bucket = cache->find(key, receiver);
if (bucket->key() == 0) cache->incrementOccupied();
bucket->set(key, imp);
可以看出,在调用前,会先进行缓存操作。
缓存操作有两种可能:
1.缓存到receiver的cache_t的bucket_t中
2.缓存到父类的cache_t的bucket_t中
首先,肯定是参数cls的
那么,参数cls是谁?
注释倒是有一句:
cls is the method whose cache should be filled.
跟没说一样
从上面代码可以看出,父类中缓存中有或者方法列表中有,则将方法缓存到原类中的cache_t的bucket_t中。
正确流程图:
1.当前缓存中查找
2.当前类中查找
3.父类缓存中查找
4.父类类中查找
1.当前缓存中查找
根据给定的值key,找到bucket_t里面的IMP
是一个hash查找
2.当前类中查找
对于已经排序好的列表,采用二分查找算法查找对应的执行函数
对于没有排序好的列表,采用一般遍历查找方法对应的执行函数
3.父类缓存中查找
4.父类中查找
消息转发
动态添加方法
动态添加方法
BOOL class_addMethod(Class cls, SEL name, IMP imp, const char *types)
Method_Swizzing方法交换
void method_exchangeImplementations(Method m1, Method m2)
更多学习请参考:
iOS-探究Runtime
动态方法解析
@dynamic
这句话写上,编译器将不再生成属性值的setter和getter方法
也不会生成成员变量
然后,我们可以在运行时+ (BOOL)resolveInstanceMethod:(SEL)sel方中,动态的实现setter和getter方法
在MJ课程中,动态方法解析指的是runtime中的第二个流程+ (BOOL)resolveInstanceMethod:(SEL)sel
在于海本节课程中,动态方法解析是一个具体例子@dynamic,动态实现setter和getter方法
都是指的+ (BOOL)resolveInstanceMethod:(SEL)sel的方法调用
动态运行时语言将函数决议推迟到运行时
编译时语言在编译期进行函数决议
问:[obj foo]与objc_msgSend()函数之间有什么关系?
[obj foo]编译后,就转换为了objc_msgSend()类型
问:runtime如何通过selector找到对应的IMP的?
其实就是objc_msgSend()的消息传递流程
问:能否向编译后的类中增加实例变量?
实例变量就是ivar
(成员变量 = 实例变量 + 基本数据类型的变量)
不能
可以向动态添加的类中增加实例变量
只需在注册之前加就可以