0%

OC类与元类

实例对象、类与元类

实例对象的类称为类(类对象),类(类对象)的类称为元类(元类对象)。他们都是对象。

OC中对象、类和元类的关系如下图:

总结下:

  1. 实例对象的类是类对象,类对象的类是元类对象.元类对象的类是根元类,根元类对象的类就是自己本身.这里形成了一个闭环.

  2. 类对象里面保存的是实例方法.

  3. 元类对象里面保存的是类方法.

  4. 根类对象的superClass是nil,根类对象的类是根元类对象,根元类对象的superClass是根类对象,这里也形成了一个闭环.

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
- (void)test_class_metaClass {
//根类对象
NSLog(@"根类对象[NSObject class]:%@,地址:%p", [NSObject class], [NSObject class]);
//根类对象的父类
NSLog(@"根类对象的父类[[NSObject class] superclass]:%@,地址%p", [[NSObject class] superclass], [[NSObject class] superclass]);
const char *nsobjectClassChar = [NSStringFromClass([NSObject class]) UTF8String];
Class nsobjectMetaClass = objc_getMetaClass(nsobjectClassChar);
Class nsobjectMetaClass1 = object_getClass(NSObject.class); //nsobjectMetaClass与nsobjectMetaClass1相等。
//根类对象的类即根元类对象
NSLog(@"根类对象的类即根元类对象(NSObject metaClass):%@,地址:%p", nsobjectMetaClass, nsobjectMetaClass);
//根元类对象的父类
NSLog(@"根元类对象的父类(NSObject metaClass superclass):%@,地址:%p", [nsobjectMetaClass superclass], [nsobjectMetaClass superclass]);
//根元类对象的类
Class root = objc_getMetaClass([NSStringFromClass(nsobjectMetaClass) UTF8String]);
NSLog(@"根元类对象的类:%@,地址:%p", root, root);
}

打印:

1
2
3
4
5
2023-05-07 14:28:18.507417+0800 runtime方法使用Demo[20896:7290701] 根类对象[NSObject class]:NSObject,地址:0x1b9e44148
2023-05-07 14:28:18.507511+0800 runtime方法使用Demo[20896:7290701] 根类对象的父类[[NSObject class] superclass]:(null),地址0x0
2023-05-07 14:28:18.507599+0800 runtime方法使用Demo[20896:7290701] 根类对象的类即根元类对象(NSObject metaClass):NSObject,地址:0x1b9e440f8
2023-05-07 14:28:18.507663+0800 runtime方法使用Demo[20896:7290701] 根元类对象的父类(NSObject metaClass superclass):NSObject,地址:0x1b9e44148
2023-05-07 14:28:18.507762+0800 runtime方法使用Demo[20896:7290701] 根元类对象的类:NSObject,地址:0x1b9e440f8

可以看到根类对象的类是根元类对象0x1b9e440f8。根元类对象的父类是根类对象0x1b9e44148。根元类对象的类是它本身0x1b9e440f8。

类对象响应根类对象的实例方法

不知道大家有没有发现,给类对象发送根类对象的实例方法,编译器是不会报错的,并且执行时也不会崩溃。这是怎么回事呢?

还是看上图,因为根元类对象的superClass是根类对象,当给类对象发送实例方法时,根据方法的查找顺序,查找到根元类对象时,由于根元类对象里也没有该实例方法,所以继续往上查找,于是查找到根类这里,根类里保存的是实例方法并且包含该实例方法,于是进行调用。因此类是可以响应根类的实例方法的。

我们可以写一些代码验证一下:给ViewController发送NSObject的一些实例方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
- (void)test_class_metaClass1 {
Class vcCls = [ViewController copy];
//copy是根类NSObject的实例方法,但是类对象ViewController依然能够“响应”.
NSLog(@"vcCls:%p,self.class:%p", vcCls, [self class]); //打印的地址一样,是同一个对象.

if ([ViewController respondsToSelector:@selector(viewDidLoad)]) { //不能响应
NSLog(@"ViewController respondsToSelector -viewDidLoad");
}
//- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector OBJC_SWIFT_UNAVAILABLE("");
NSMethodSignature *msig = [ViewController methodSignatureForSelector:@selector(viewDidLoad)];
NSLog(@"msig:%@", msig);
NSMethodSignature *msig1 = [self methodSignatureForSelector:@selector(viewDidLoad)];
NSLog(@"msig1:%@", msig1);
//- (IMP)methodForSelector:(SEL)aSelector;
IMP imp = [ViewController methodForSelector:@selector(viewDidLoad)]; //这里为啥会有值?不懂。估计走了消息转发。
NSLog(@"imp:%p", imp); //(IMP) imp = 0x000000018002f440 (libobjc.A.dylib`_objc_msgForward)
IMP imp1 = [self methodForSelector:@selector(viewDidLoad)];
NSLog(@"imp1:%p", imp1);
}

打印:

1
2
3
4
5
2023-05-07 14:18:44.308901+0800 runtime方法使用Demo[20472:7278793] vcCls:0x104d452d8,self.class:0x104d452d8
2023-05-07 14:18:44.309072+0800 runtime方法使用Demo[20472:7278793] msig:(null)
2023-05-07 14:18:44.309131+0800 runtime方法使用Demo[20472:7278793] msig1:<NSMethodSignature: 0xa1d3a9acc0fd100f>
2023-05-07 14:18:44.309171+0800 runtime方法使用Demo[20472:7278793] imp:0x18002f440
2023-05-07 14:18:44.309209+0800 runtime方法使用Demo[20472:7278793] imp1:0x104d3a940

可以看到,类ViewController都可以响应。不过NSObject根类是比较特殊的,事实上NSObject所有暴露的实例方法都有对应的一份类方法实现,只不过大部分这样的类方法都没有暴露出来。所以根元类里面是有这些类方法的,根据方法的查找顺序,最终会在根元类这里找到而不会继续往根类里找,于是调用相应类方法。这在外部看起来像是类响应了一个实例方法,实际上只是因为NSObject没有暴露对应的类方法。

可以看NSObject的源码实现:performSelector,copy等都只暴露了实例方法接口,但实际上内部有对应的类方法接口。

1
2
3
4
5
6
7
8
9
10
- (id)performSelector:(SEL)aSelector;
- (id)performSelector:(SEL)aSelector withObject:(id)object;
- (id)performSelector:(SEL)aSelector withObject:(id)object1 withObject:(id)object2;

- (id)copy;
- (id)mutableCopy;

- (id)forwardingTargetForSelector:(SEL)aSelector OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);
- (void)forwardInvocation:(NSInvocation *)anInvocation OBJC_SWIFT_UNAVAILABLE("");
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector OBJC_SWIFT_UNAVAILABLE("");

内部实现:

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
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
//performSelector相关
+ (id)performSelector:(SEL)sel {
if (!sel) [self doesNotRecognizeSelector:sel];
return ((id(*)(id, SEL))objc_msgSend)((id)self, sel);
}

+ (id)performSelector:(SEL)sel withObject:(id)obj {
if (!sel) [self doesNotRecognizeSelector:sel];
return ((id(*)(id, SEL, id))objc_msgSend)((id)self, sel, obj);
}

+ (id)performSelector:(SEL)sel withObject:(id)obj1 withObject:(id)obj2 {
if (!sel) [self doesNotRecognizeSelector:sel];
return ((id(*)(id, SEL, id, id))objc_msgSend)((id)self, sel, obj1, obj2);
}

- (id)performSelector:(SEL)sel {
if (!sel) [self doesNotRecognizeSelector:sel];
return ((id(*)(id, SEL))objc_msgSend)(self, sel);
}

- (id)performSelector:(SEL)sel withObject:(id)obj {
if (!sel) [self doesNotRecognizeSelector:sel];
return ((id(*)(id, SEL, id))objc_msgSend)(self, sel, obj);
}

- (id)performSelector:(SEL)sel withObject:(id)obj1 withObject:(id)obj2 {
if (!sel) [self doesNotRecognizeSelector:sel];
return ((id(*)(id, SEL, id, id))objc_msgSend)(self, sel, obj1, obj2);
}

//copy相关
+ (id)copy {
return (id)self;
}

+ (id)copyWithZone:(struct _NSZone *)zone {
return (id)self;
}

- (id)copy {
return [(id)self copyWithZone:nil];
}

+ (id)mutableCopy {
return (id)self;
}

+ (id)mutableCopyWithZone:(struct _NSZone *)zone {
return (id)self;
}

- (id)mutableCopy {
return [(id)self mutableCopyWithZone:nil];
}

//消息转发相关
// Replaced by CF (returns an NSMethodSignature)
+ (NSMethodSignature *)instanceMethodSignatureForSelector:(SEL)sel {
_objc_fatal("+[NSObject instanceMethodSignatureForSelector:] "
"not available without CoreFoundation");
}

// Replaced by CF (returns an NSMethodSignature)
+ (NSMethodSignature *)methodSignatureForSelector:(SEL)sel {
_objc_fatal("+[NSObject methodSignatureForSelector:] "
"not available without CoreFoundation");
}

// Replaced by CF (returns an NSMethodSignature)
- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel {
_objc_fatal("-[NSObject methodSignatureForSelector:] "
"not available without CoreFoundation");
}

+ (void)forwardInvocation:(NSInvocation *)invocation {
[self doesNotRecognizeSelector:(invocation ? [invocation selector] : 0)];
}

- (void)forwardInvocation:(NSInvocation *)invocation {
[self doesNotRecognizeSelector:(invocation ? [invocation selector] : 0)];
}

+ (id)forwardingTargetForSelector:(SEL)sel {
return nil;
}

- (id)forwardingTargetForSelector:(SEL)sel {
return nil;
}

为了避免上述影响,我们可以通过类别给NSObject添加一个实例方法,这样就可以保证根元类里没有对应的类方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@interface NSObject (XQPrint)

- (void)printSomething;

@end

@implementation NSObject (XQPrint)

- (void)printSomething {
NSLog(@"printSomething:%@", self);

if ([self isKindOfClass:ViewController.class]) { //根类里的方法如果不进行downcast作用很有限。
ViewController *vc = (ViewController *)self;
[vc handleBtnClicked];
} else {
NSLog(@"not work");
}

// ViewController *vc = (ViewController *)self;
// [vc handleBtnClicked]; //如果self是类对象这里会直接崩溃。
}

@end

调用printSomething方法

1
2
3
- (void)test_class_respond_instance_method {
[ViewController printSomething];
}

打印:

1
2
2023-05-08 12:12:58.795626+0800 runtime方法使用Demo[45319:7748354] printSomething:ViewController
2023-05-08 12:12:58.795700+0800 runtime方法使用Demo[45319:7748354] not work

神奇的事情来了,实例方法printSomething还是被调用了,需要注意的是虽然在实例方法里面但self却是一个类对象。到此为止我们就验证了类可以响应根类的实例方法。

注意1:类对象只能响应根类的实例方法,不能响应其父类的实例方法.

举例:isBeingPresented是UIViewController的实例方法。现在让ViewController执行isBeingPresented,会崩溃。

1
[ViewController performSelector:@selector(isBeingPresented)];//崩溃.

注意2:不能给实例对象发送类方法。编译器会报错,即使使用下面的performSelector规避报错最终还是会崩溃。

1
[obj performSelector:@selector(someClassMethod)]; //崩溃

这是因为给实例对象发送消息是沿着类对象的继承链查找的,而NSObject的父类是nil,最终肯定是找不到这个类方法的,因此会崩溃。

总结:

1.类可以响应根类的实例方法。不过不建议这么做,因为没什么意义。所以最好还是类对象发送类方法,实例对象发送实例方法。

2.不能给实例对象发送类方法。

问题

Q:为什么根元类的父类要设置为根类?是为了解决什么问题吗,还是啥原因?

A:就是为了让类可以响应根类的实例方法?

参考

objc explain: Classes and metaclasses 远古大神

觉得文章有帮助可以打赏一下哦!