实例对象、类与元类 实例对象的类称为类(类对象),类(类对象)的类称为元类(元类对象)。他们都是对象。
OC中对象、类和元类的关系如下图:
总结下:
实例对象的类是类对象,类对象的类是元类对象.元类对象的类是根元类,根元类对象的类就是自己本身.这里形成了一个闭环.
类对象里面保存的是实例方法.
元类对象里面保存的是类方法.
根类对象的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); 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,地址:0 x1b9e441482023 -05 -07 14 :28 :18 .507511 +0800 runtime方法使用Demo[20896 :7290701 ] 根类对象的父类[[NSObject class] superclass]:(null),地址0 x02023 -05 -07 14 :28 :18 .507599 +0800 runtime方法使用Demo[20896 :7290701 ] 根类对象的类即根元类对象(NSObject metaClass):NSObject,地址:0 x1b9e440f82023 -05 -07 14 :28 :18 .507663 +0800 runtime方法使用Demo[20896 :7290701 ] 根元类对象的父类(NSObject metaClass superclass):NSObject,地址:0 x1b9e441482023 -05 -07 14 :28 :18 .507762 +0800 runtime方法使用Demo[20896 :7290701 ] 根元类对象的类:NSObject,地址:0 x1b9e440f8
可以看到根类对象的类是根元类对象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 ]; NSLog (@"vcCls:%p,self.class:%p" , vcCls, [self class ]); if ([ViewController respondsToSelector:@selector (viewDidLoad)]) { NSLog (@"ViewController respondsToSelector -viewDidLoad" ); } NSMethodSignature *msig = [ViewController methodSignatureForSelector:@selector (viewDidLoad)]; NSLog (@"msig:%@" , msig); NSMethodSignature *msig1 = [self methodSignatureForSelector:@selector (viewDidLoad)]; NSLog (@"msig1:%@" , msig1); IMP imp = [ViewController methodForSelector:@selector (viewDidLoad)]; NSLog (@"imp:%p" , imp); 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:0 x104d452d8,self.class:0 x104d452d82023 -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: 0 xa1d3a9acc0fd100f>2023 -05 -07 14 :18 :44 .309171 +0800 runtime方法使用Demo[20472 :7278793 ] imp:0 x18002f4402023 -05 -07 14 :18 :44 .309209 +0800 runtime方法使用Demo[20472 :7278793 ] imp1:0 x104d3a940
可以看到,类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 + (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); } + (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 ]; } + (NSMethodSignature *)instanceMethodSignatureForSelector:(SEL)sel { _objc_fatal("+[NSObject instanceMethodSignatureForSelector:] " "not available without CoreFoundation" ); } + (NSMethodSignature *)methodSignatureForSelector:(SEL)sel { _objc_fatal("+[NSObject methodSignatureForSelector:] " "not available without CoreFoundation" ); } - (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]) { ViewController *vc = (ViewController *)self ; [vc handleBtnClicked]; } else { NSLog (@"not work" ); } } @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:ViewController2023 -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 远古大神