张东轩的博客

合抱之木,生于毫末;九层之台,起于累土;千里之行,始于足下。

0%

Objective-C 中对象的分类

一、对象的分类

instance对象

实例对象就是通过alloc出来的对象,每次调用alloc都会产生新的instance对象;

实例对象的存储信息

  1. isa指针
  2. 其他成员变量

class对象

每个类的内存中有且只有一个类对象

类对象的存储信息

  • isa指针
  • superClass指针
  • 类的属性信息(@property),类的对象方法信息(method),类的协议信息(protocol),类的成员变量信息(ivar)

类对象是由程序员定义并在运行时由编译器创建的,它没有自己的实例变量,这里需要注意的是类的成员变量和实例方法列表是属于实例对象的,但其存储于类对象当中的。

meta-class对象

每个类的内存中有且只有一个元类对象

元类的存储信息

  • isa指针
  • superClass指针
  • 类的属性信息(@property),类的对象方法信息(method),类的协议信息(protocol),类的成员变量信息(ivar)

元类和类的存储结构是一样的,但是用途不一样

二、类及相应meta-class类的继承体系

instance的isa指向class对象(类对象),当调用对象方法时,通过instance的isa找到class对象(类对象),最后找到对象方法的实现进行调用;

class的isa指向meta-class,当调用类方法时,通过class的isa找到meta-class,最后找到类方法的实现进行调用;

objective-c源码中对对象的定义是这样的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
//类的实例
typedef struct objc_object *id;

/// Represents an instance of a class.
struct objc_object {
// 这里的isa指针指向当前实例的类对象
Class isa OBJC_ISA_AVAILABILITY;
};
----------------------------------------------------

/// An opaque type that represents an Objective-C class.
typedef struct objc_class *Class;

struct objc_class {
Class isa OBJC_ISA_AVAILABILITY; //
#if !__OBJC2__
Class super_class OBJC2_UNAVAILABLE;//父类
const char *name OBJC2_UNAVAILABLE;//类名
long version OBJC2_UNAVAILABLE;
long info OBJC2_UNAVAILABLE;
long instance_size OBJC2_UNAVAILABLE;//实例大小
struct objc_ivar_list *ivars OBJC2_UNAVAILABLE;//成员变量列表
struct objc_method_list **methodLists OBJC2_UNAVAILABLE;//方法列表
struct objc_cache *cache OBJC2_UNAVAILABLE;//方法缓存
struct objc_protocol_list *protocols OBJC2_UNAVAILABLE;//协议列表
#endif

} OBJC2_UNAVAILABLE;
/* Use `Class` instead of `struct objc_class *` */

这里也是有个isa 指针,其实这个isa 指向的就是 **元类**
----------------------------------------------------

这里介绍一下元类,介绍之前先理解以下概念

类对象 == object_getClass(实例对象) == [实例对象 class] == [类对象 class]
object_getClass(类对象) == 类对象的isa == 元类
object_getClass(类对象) != [类对象 class]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41

@interface Test : NSObject
@property(nonatomic, assign) int p1;
@end

@implementation Test
@end

int main(int argc, const char * argv[]) {
@autoreleasepool {
Test *test = [[Test alloc] init];


// Test的类对象
Class cls1 = test.class; //获取当前的类的对象,就是isa指向的类对象
// Test的类对象
Class cls2 = object_getClass(test);
// Test的类对象
Class cls3 = Test.class;
// 还是Test的类对象
Class cls4 = [cls1 class]; //类对象的isa指向的类对象本身

NSLog(@"cls1:%p cls2:%p cls3:%p cls4:%p", cls1, cls2, cls3, cls4);
// cls1:0x100008188 cls2:0x100008188 cls3:0x100008188 cls4:0x100008188

// Test的Class的元类
Class cls5 = object_getClass(cls1);
// Test的Class的元类
Class cls6 = objc_getMetaClass(object_getClassName(test));
NSLog(@"cls5:%p cls6:%p", cls5, cls6);
// cls5:0x100008160 cls6:0x100008160

const char *cls2_name = object_getClassName(cls2);
const char *cls6_name = object_getClassName(cls6);
NSLog(@"cls2_name:%s cls6_name:%s", cls2_name, cls6_name);
// cls2_name:Test cls6_name:NSObject

}
return 0;
}

很奇怪,为什么cls6_nameNSObject,通过object_getClassName的源码可以知道,这个方法获取的是isa指向的名称。

1
2
3
4
5
const char *object_getClassName(id obj)
{
return class_getName(obj ? obj->getIsa() : nil);
}

总结一下实例对象,类对象以及元类对象之间的isa指向和继承关系的规则为:
规则一: 实例对象的isa指向该类,类的isa指向元类(metaClass)
规则二: 类的superClass指向其父类,如果该类为根类则值为nil
规则三: 元类的isa指向根元类,如果该元类是根元类则指向自身
规则四: 元类的superClass指向父元类,若根元类则指向该根类

三、实践

实践一

考虑以下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@interface Sark : NSObject
@end

@implementation Sark
@end

int main(int argc, const char * argv[]) {
@autoreleasepool {
BOOL res1 = [(id)[NSObject class] isKindOfClass:[NSObject class]];
BOOL res2 = [(id)[NSObject class] isMemberOfClass:[NSObject class]];
BOOL res3 = [(id)[Sark class] isKindOfClass:[Sark class]];
BOOL res4 = [(id)[Sark class] isMemberOfClass:[Sark class]];

NSLog(@"%d %d %d %d", res1, res2, res3, res4);
}
return 0;
}

先来分析一下源码这两个函数的对象实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
+ (Class)class {
return self;
}

- (Class)class {
return object_getClass(self);
}

Class object_getClass(id obj)
{
if (obj) return obj->getIsa();
else return Nil;
}

inline Class
objc_object::getIsa()
{
if (isTaggedPointer()) {
uintptr_t slot = ((uintptr_t)this >> TAG_SLOT_SHIFT) & TAG_SLOT_MASK;
return objc_tag_classes[slot];
}
return ISA();
}

inline Class
objc_object::ISA()
{
assert(!isTaggedPointer());
return (Class)(isa.bits & ISA_MASK);
}

+ (BOOL)isKindOfClass:(Class)cls {
for (Class tcls = object_getClass((id)self); tcls; tcls = tcls->superclass) {
if (tcls == cls) return YES;
}
return NO;
}

- (BOOL)isKindOfClass:(Class)cls {
for (Class tcls = [self class]; tcls; tcls = tcls->superclass) {
if (tcls == cls) return YES;
}
return NO;
}

+ (BOOL)isMemberOfClass:(Class)cls {
return object_getClass((id)self) == cls;
}

- (BOOL)isMemberOfClass:(Class)cls {
return [self class] == cls;
}

首先题目中NSObject 和 Sark分别调用了class方法。

+ (BOOL)isKindOfClass:(Class)cls方法内部,会先去获得object_getClass的类,而object_getClass的源码实现是去调用当前类的obj->getIsa(),最后在ISA()方法中获得meta class的指针。

接着在isKindOfClass中有一个循环,先判断class是否等于meta class,不等就继续循环判断是否等于super class,不等再继续取super class,如此循环下去。

[NSObject class]执行完之后调用isKindOfClass,第一次判断先判断NSObjectNSObjectmeta class是否相等,之前讲到meta class的时候放了一张很详细的图,从图上我们也可以看出,NSObjectmeta class与本身不等。接着第二次循环判断NSObject与meta classsuperclass是否相等。还是从那张图上面我们可以看到:Root class(meta)superclass 就是 Root class(class),也就是NSObject本身。所以第二次循环相等,于是第一行res1输出应该为YES。

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

如果把这里的Sark改成它的实例对象,[sark isKindOfClass:[Sark class],那么此时就应该输出YES了。因为在isKindOfClass函数中,判断sarkisa指向是否是自己的类Sark,第一次for循环就能输出YES了。

isMemberOfClass的源码实现是拿到自己的isa指针和自己比较,是否相等。
第二行isa 指向NSObjectMeta Class,所以和 NSObject Class不相等。第四行,isa指向Sark的Meta Class,和Sark Class也不等,所以第二行res2和第四行res4都输出NO。

实践二

考虑以下代码是否可以正常运行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
@interface Sark : NSObject
@property (nonatomic, copy) NSString *name;
- (void)speak;
@end

@implementation Sark
- (void)speak {
NSLog(@"my name is aaa");
}
@end


int main(int argc, const char * argv[]) {
@autoreleasepool {
id cls = [Sark class];
void *obj = &cls;
id obj_new = (__bridge id)obj;
[obj_new speak];

size_t cls_size = class_getInstanceSize(Sark.class);
size_t obj_size = malloc_size(obj);
NSLog(@"cls_size:%zu, obj_size:%zu", cls_size, obj_size);
}
return 0;
}

答案是可以的,因为obj是指向类对象Sark的指针,将obj转为iOS对象obj_new,因为iOS对象的前8个字节是isa指针,指向类对象Sark,这里obj_new就相当于在内存中只占用8个字节的栈对象,所以是可以进行方法调用的,但是由于是栈对象,所以obj_size的值是0;

所以输出的结果如下

2021-04-12 00:06:13.596537+0800 ClassTest[6501:1363097] my name is aaa
2021-04-12 00:06:13.596620+0800 ClassTest[6501:1363097] cls_size:16, obj_size:0

参考
https://halfrost.com/objc_runtime_isa_class/