- 关键是理解isa指针指向,参考经典的图
假设有个Person类继承自NSObject:
@interterface Person : NSObject
- (void)eat();
@end
...
Person *person = [[Person alloc] init];
[person eat];
解释如下:
person 对象的isa指向Person类(类也是对象)
Person 类的isa指向Person的元类
Person 元类isa指向NSObject的元类(根元类)
- 理解这几个对象分别存了啥
类对象存对象方法
元类对象存类方法
根元类存根元类的类方法
2.1 peson对象的isa指向Person类对象,它就这么一个参数,如下
typedef struct objc_object {
Class isa;
} *id;
2.2 Person类对象比较丰富,如下:
struct objc_class {
Class _Nonnull isa OBJC_ISA_AVAILABILITY;
#if !__OBJC2__
Class _Nullable super_class OBJC2_UNAVAILABLE;
const char * _Nonnull name OBJC2_UNAVAILABLE;
long version OBJC2_UNAVAILABLE;
long info OBJC2_UNAVAILABLE;
long instance_size OBJC2_UNAVAILABLE;
struct objc_ivar_list * _Nullable ivars OBJC2_UNAVAILABLE;
struct objc_method_list * _Nullable * _Nullable methodLists OBJC2_UNAVAILABLE;
struct objc_cache * _Nonnull cache OBJC2_UNAVAILABLE;
struct objc_protocol_list * _Nullable protocols OBJC2_UNAVAILABLE;
#endif
} OBJC2_UNAVAILABLE;
- isa 指向元类对象
- super_class 指向基类对象
- name 表示类名 version 版本号 instance_size:大小 我记得是48Bytes
- ivars 表示变量列表
- methodLists 方法列表
- cache 已调用方法的缓存
- protocols 协议列表
我们调用定义的eat方法时,runtime会将该方法换成objc_msgSend(id self, SEL s, param1, param2...)方法发消息,isa作用体现在查找eat方法过程:
person对象的isa->拿到Person类对象,找cache中是否缓存的有,有则立即调用,没有就从methodLists中找该方法,有则调用,没有则通过super_class重复该过程-> 若eat是类方法,则通过Person类对象的isa拿到元类,在元类中重复上步过程(元类的结构跟类对象的结构定义相似)-> 若没有则通过元类的isa拿到根元类,在根元类中重复上述过程->根元类的isa指向自己,向上追溯过程就结束了
- 理解元类和根元类
参考:Objective-C 中的元类(meta class)是什么?
元类是类对象的类,里边存类方法
根元类是元类的类,所有的元类指向同一个根元类
元类打印的类型跟类对象的类型一致,但内存地址是不一样的,元类可以通过object_getClass()传入类对象拿到
贴一段代码:
- (void)_test_metaClass2{
Class newClass =
objc_allocateClassPair([NSError class], "RuntimeErrorSubclass", 0);
class_addMethod(newClass, @selector(report), (IMP)ReportFunction, "v@:");
objc_registerClassPair(newClass);
Class metaClass = [newClass class];
id instanceOfNewClass =
[[newClass alloc] initWithDomain:@"someDomain" code:0 userInfo:nil];
[instanceOfNewClass performSelector:@selector(report)];
}
void ReportFunction(id self, SEL _cmd)
{
NSLog(@"This object is %p.", self);
NSLog(@"Class is %@, and super is %@.", [self class], [self superclass]);
Class currentClass = [self class];
for (int i = 1; i < 4; i++)
{
NSLog(@"Following the isa pointer %d times gives %p", i, currentClass);
currentClass = object_getClass(currentClass);
}
NSLog(@"NSObject's class is %p", [NSObject class]);
NSLog(@"NSObject's meta class is %p", object_getClass([NSObject class]));
}
输出结果如下:
2018-04-01 19:40:14.265682+0800 iOSLearnigDemo[7206:426669] This object is 0x604000453f20.
2018-04-01 19:40:14.265950+0800 iOSLearnigDemo[7206:426669] Class is RuntimeErrorSubclass, and super is NSError.
2018-04-01 19:40:14.266073+0800 iOSLearnigDemo[7206:426669] Following the isa pointer 1 times gives 0x604000453710 // 类对象
2018-04-01 19:40:14.266179+0800 iOSLearnigDemo[7206:426669] Following the isa pointer 2 times gives 0x604000453d70 // 元类对象
2018-04-01 19:40:14.266321+0800 iOSLearnigDemo[7206:426669] Following the isa pointer 3 times gives 0x104b0de58 //本次打印的就是根元类
2018-04-01 19:40:14.266452+0800 iOSLearnigDemo[7206:426669] NSObject's class is 0x104b0dea8
2018-04-01 19:40:14.266651+0800 iOSLearnigDemo[7206:426669] NSObject's meta class is 0x104b0de58 //根元类
2. 理解类对象结构
参考:Objective-C Runtime
所有定义在runtime.h中可以看到
- ivars
struct objc_ivar {
char * _Nullable ivar_name OBJC2_UNAVAILABLE; //变量名
char * _Nullable ivar_type OBJC2_UNAVAILABLE; //变量类型
int ivar_offset OBJC2_UNAVAILABLE; //偏移量
#ifdef __LP64__
int space OBJC2_UNAVAILABLE; //空间大小
#endif
} OBJC2_UNAVAILABLE;
struct objc_ivar_list {
int ivar_count OBJC2_UNAVAILABLE;
#ifdef __LP64__
int space OBJC2_UNAVAILABLE;
#endif
/* variable length structure */
struct objc_ivar ivar_list[1] OBJC2_UNAVAILABLE; //是一个变长结构体,存放所有的变量
}
- methodLists 结构,命名都很清晰
struct objc_method {
SEL _Nonnull method_name OBJC2_UNAVAILABLE;
char * _Nullable method_types OBJC2_UNAVAILABLE;
IMP _Nonnull method_imp OBJC2_UNAVAILABLE; //方法实现,是一个函数指针,指向真正的方法地址
} OBJC2_UNAVAILABLE;
struct objc_method_list {
struct objc_method_list * _Nullable obsolete OBJC2_UNAVAILABLE;
int method_count OBJC2_UNAVAILABLE;
#ifdef __LP64__
int space OBJC2_UNAVAILABLE;
#endif
/* variable length structure */
struct objc_method method_list[1] OBJC2_UNAVAILABLE;
}
- protocolLists
struct objc_protocol_list {
struct objc_protocol_list * _Nullable next; //下个协议列表,这结构看起来像链表
long count;
__unsafe_unretained Protocol * _Nullable list[1]; //Protocol的定义如下文
};
#ifdef __OBJC__
@class Protocol; //在objc下,protocol其实也是个类
#else
typedef struct objc_object Protocol;
#endif
- cache
struct objc_cache {
unsigned int mask /* total = mask + 1 */ OBJC2_UNAVAILABLE;
unsigned int occupied OBJC2_UNAVAILABLE;
Method _Nullable buckets[1] OBJC2_UNAVAILABLE; //此处的method就是objc_method,因为有如下定义
};
typedef struct objc_method *Method;
3.上边一些结构的理解
- ivars 存所有的实例变量,包括property+exetensions中定义的实例变量,可以用代码验证;ivars 中获取的property名字是带_下划线的(exstensions中定义的直接打印原名)
@interface Person2 : NSObject
@property (nonatomic, strong) NSArray *array;
@end
@interface Person2(){
int _age;
}
@end
@implementation Person2
@end
...
- (void)_test_ivarList{
int outCount = 0;
Ivar *varList = class_copyIvarList(objc_getClass("Person2"), &outCount); //获取所有实例变量
int outCount2 = 0;
objc_property_t *propList = class_copyPropertyList(objc_getClass("Person2"), &outCount2); //获取所有属性
for(int i = 0 ;i < outCount;i++){
Ivar ivar = varList[i];
char *name = ivar_getName(ivar);
NSLog(@"ivar: %s\n",name);
}
for(int i = 0 ;i < outCount2;i++){
objc_property_t pro = propList[i];
char *name = property_getName(pro);
NSLog(@"prop:%s\n",name);
}
free(varList);
free(propList);
}
输出结果如下:
2018-04-04 14:59:11.340351+0800 iOSLearnigDemo[7044:716357] ivar: _age
2018-04-04 14:59:11.341156+0800 iOSLearnigDemo[7044:716357] ivar: _array
2018-04-04 14:59:11.341385+0800 iOSLearnigDemo[7044:716357] prop:array
关于ivar或者property的所有操作方法,基本都以class_开头,这也很好理解,他们属于class的范畴
- ivar 理解在类创建之后就无法再修改
简单得说,ivar的内存布局在编译时已经确定,无法直接修改;在运行时调用class_setIvarLayout 和 class_setWeakIvarLayout 重新设置 Ivar Layout无效;可以有其他trick的方式
具体参考:ObjC如何通过runtime修改Ivar的内存管理方式 - ivar 可以在runtime新建类时添加或修改
Class TestObjectClass = objc_allocateClassPair(NSClassFromString(@"NSObject"), "TestObject", sizeof(48));
BOOL flag = class_addIvar(TestObjectClass, "prop", sizeof(int), 0, @encode(int));
if(flag){
NSLog(@"%@ add ivar:prop success",TestObjectClass);
}else{
NSLog(@"%@ add ivar:prop success",TestObjectClass);
}
objc_registerClassPair(TestObjectClass);
//打印一下
[self _test_printf_classIvarList:TestObjectClass];
输出结果:
2018-04-04 15:35:41.217178+0800 iOSLearnigDemo[7590:741638] TestObject add ivar:prop success
2018-04-04 15:35:41.217615+0800 iOSLearnigDemo[7590:741638] TestObject ivar: prop
- methodLists中,每一个method包装一个SEL和一个IMP,SEL是方法选择器(也叫选择子),IMP是一个函数指针,指向方法的具体实现;熟悉了这个结构,可以比较熟练的玩method-swizzing了,主要用method这个结构体来操作;
// 1.拿到selector对应的method
Method swizzMethod = class_getInstanceMethod([self class], @selector(_test_person_replace_method));
Method originalMethod = class_getInstanceMethod([Person2 class], @selector(test));
// 2.尝试为要替换的类添加一个method
BOOL success = class_addMethod([Person2 class], @selector(test), method_getImplementation(swizzMethod), method_getTypeEncoding(swizzMethod));
// 2.1 添加成功;说明替换类中没有test方法,则说明父类有实现,将替换方法实现改为父类的实现
if(success){
class_replaceMethod([Person2 class], @selector(_test_person_replace_method), method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod));
}else{
// 2.2 未添加成功,直接交换method的imp
method_exchangeImplementations(originalMethod, swizzMethod); //直接替换两个实现
}
[[Person2 new] test];
- 方法替换主要用method结构体来操作
- SEL转method,SEL转imp都可以通过class来操作
4. 补充一些结构
4.1 SEL
- 方法选择器,是objc_msgSend()的第二个参数,可以理解成区分方法的id,这个id得结构如下:
参考:objc/objc.h头文件中声明
/// An opaque type that represents a method selector.
typedef struct objc_selector *SEL; //objc_selector未看到具体声明
- 有三种方式获取SEL
SEL selector1 = @selector(_test_runtime_method);
SEL selector2 = sel_registerName("_test_runtime_method");
SEL selector3 = NSSelectorFromString(@"_test_runtime_method");
NSLog(@"1:%p,2:%p,3:%p",selector1,selector2,selector3);
日志输出:
2018-04-04 17:07:16.538913+0800 iOSLearnigDemo[8950:800180] 1:0x10f436a24,2:0x10f436a24,3:0x10f436a24 //获取的都是一样的
-
不同类中的相同名字的方法对应的SEL是一样的[经过我测试,打印两个不同类相同名称的Selector,得到的结果是不一样的,暂时有个疑惑],即使方法的参数类型不一样也无效;(有参数和无参数肯定是不同的)
-
实际上函数执行前,就是先根据SEL找IMP,具体我们在objc_msgSend()方法学习时再展开
-
在当前函数中打印_cmd获取到的也是它,每一个方法都有两个隐藏参数self和_cmd
4.2 IMP 实际上是一个函数指针,指向函数的地址
/// A pointer to the function of a method implementation.
#if !OBJC_OLD_DISPATCH_PROTOTYPES
typedef void (*IMP)(void /* id, SEL, ... */ );
#else
typedef id _Nullable (*IMP)(id _Nonnull, SEL _Nonnull, ...);
#endif
网友评论