通过之前几篇文章的学习,我们已经对类
做了很多的探索了,现在我们看两道面试题
,巩固一下。
【面试题】类方法归属分析
我们知道实例方法
存储在类
中,类方法
存储在元类
中,我们下面通过一些方法来验证这个结果.
准备工作
创建ZGPerson
对象,分别有一个类方法
和实例方法
@interface ZGPerson : NSObject
- (void)sayHello;
+ (void)sayHappy;
@end
@implementation ZGPerson
- (void)sayHello{
NSLog(@"ZGPerson say : Hello!!!");
}
+ (void)sayHappy{
NSLog(@"ZGPerson say : Happy!!!");
}
@end
下面我们通过几个方法分析一下方法的归属问题
void objc_copyMethodList(Class pClass){
unsigned int count = 0;
Method *methods = class_copyMethodList(pClass, &count);
for (unsigned int i=0; i < count; i++) {
Method const method = methods[I];
//获取方法名
NSString *key = NSStringFromSelector(method_getName(method));
NSLog(@"Method, name: %@", key);
}
free(methods);
}
void instanceMethod_classToMetaclass(Class pClass){
const char *className = class_getName(pClass);
Class metaClass = objc_getMetaClass(className);
Method method1 = class_getInstanceMethod(pClass, @selector(sayHello));
Method method2 = class_getInstanceMethod(metaClass, @selector(sayHello));
Method method3 = class_getInstanceMethod(pClass, @selector(sayHappy));
Method method4 = class_getInstanceMethod(metaClass, @selector(sayHappy));
NSLog(@"方法名__func__:%s\n method1地址:%p\n method2地址:%p\n method3地址:%p\n method4地址:%p",__func__,method1,method2,method3,method4);
}
void classMethod_classToMetaclass(Class pClass){
const char *className = class_getName(pClass);
Class metaClass = objc_getMetaClass(className);
Method method1 = class_getClassMethod(pClass, @selector(sayHello));
Method method2 = class_getClassMethod(metaClass, @selector(sayHello));
Method method3 = class_getClassMethod(pClass, @selector(sayHappy));
// 元类 为什么有 sayHappy 类方法 0 1
//
Method method4 = class_getClassMethod(metaClass, @selector(sayHappy));
NSLog(@"方法名__func__:%s\n method1地址:%p\n method2地址:%p\n method3地址:%p\n method4地址:%p",__func__,method1,method2,method3,method4);
}
void IMP_classToMetaclass(Class pClass){
const char *className = class_getName(pClass);
Class metaClass = objc_getMetaClass(className);
// - (void)sayHello;
// + (void)sayHappy;
IMP imp1 = class_getMethodImplementation(pClass, @selector(sayHello));
IMP imp2 = class_getMethodImplementation(metaClass, @selector(sayHello));
IMP imp3 = class_getMethodImplementation(pClass, @selector(sayHappy));
IMP imp4 = class_getMethodImplementation(metaClass, @selector(sayHappy));
NSLog(@"方法名__func__:%s\n imp1:%p\n imp2:%p\n imp3:%p\n imp4:%p",__func__,imp1,imp2,imp3,imp4);
}
int main(int argc, const char * argv[]) {
@autoreleasepool {
// insert code here...
// 0x0000000100000000
ZGPerson *person = [ZGPerson alloc];
Class pClass = object_getClass(person);
objc_copyMethodList(pClass);
instanceMethod_classToMetaclass(pClass);
classMethod_classToMetaclass(pClass);
iMP_classToMetaclass(pClass);
NSLog(@"Hello, World!");
}
return 0;
}
思考一下打印结果分别是什么?
结果如下:
我们挨个分析一下其中的打印:
objc_copyMethodList分析
我们点进去看一下方法的解释
* @param cls The class you want to inspect.
* @param outCount On return, contains the length of the returned array.
* If outCount is NULL, the length is not returned.
*
* @return An array of pointers of type Method describing the instance methods
* implemented by the class—any instance methods implemented by superclasses are not included.
* The array contains *outCount pointers followed by a NULL terminator. You must free the array with free().
*
* If cls implements no instance methods, or cls is Nil, returns NULL and *outCount is 0.
*
* @note To get the class methods of a class, use \c class_copyMethodList(object_getClass(cls), &count).
* @note To get the implementations of methods that may be implemented by superclasses,
* use \c class_getInstanceMethod or \c class_getClassMethod.
大意是说:获取类
实现的实例方法
,如果cls
没有实现实例方法,或cls为Nil
,则返回NULL
, outCount
为0。,而且告诉我们如果要获得一个类的类方法
,可以使用class_copyMethodList(object_getClass(cls), &count)
let me try...
将class_copyMethodList(pClass, &count)
改为class_copyMethodList(object_getClass(pClass), &count)
打印结果如下:
果然打印出了我们的类方法
,pClass
是ZGPerson类
,而object_getClass(pClass)
是ZGPerson的元类
,这也正验证了我们的结论:实例方法
存储在类
中,类方法
存储在元类
instanceMethod_classToMetaclass分析
* @param cls The class you want to inspect.
* @param name The selector of the method you want to retrieve.
*
* @return The method that corresponds to the implementation of the selector specified by
* \e name for the class specified by \e cls, or \c NULL if the specified class or its
* superclasses do not contain an instance method with the specified selector.
*
* @note This function searches superclasses for implementations, whereas \c class_copyMethodList does not.
大意是这个方法主要是获取类的实例方法
,如果指定的类或其父类
不包含带有指定选择器的实例方法,则为NULL
打印结果也证明了:实例方法
存储在类
中不在元类
中,类方法
存储在元类
,不在本类
中
0x0
表示无地址,即不存在此方法
有地址存在则存在此方法
classMethod_classToMetaclass分析
* @param cls A pointer to a class definition. Pass the class that contains the method you want to retrieve.
* @param name A pointer of type \c SEL. Pass the selector of the method you want to retrieve.
*
* @return A pointer to the \c Method data structure that corresponds to the implementation of the
* selector specified by aSelector for the class specified by aClass, or NULL if the specified
* class or its superclasses do not contain an instance method with the specified selector.
*
* @note Note that this function searches superclasses for implementations,
* whereas \c class_copyMethodList does not.
大意是该方法主要是用于获取类方法
,如果在传入的类或者类的父类
中没有找到指定的类方法
,则返回NULL
打印结果中类和元类
中都没有发现的sayHellod实例方法
,而却都发现了sayHappy类方法
,这是为什么呢?
我们进入的class_getClassMethod源码
看一下
Method class_getClassMethod(Class cls, SEL sel)
{
if (!cls || !sel) return nil;
return class_getInstanceMethod(cls->getMeta(), sel);
}
// NOT identical to this->ISA when this is a metaclass
Class getMeta() {
if (isMetaClass()) return (Class)this;
else return this->ISA();
}
class_getClassMethod
的实现是获取类的类方法
,其本质就是获取元类的实例方法
,最终还是会走到class_getInstanceMethod
,但是在这里需要注意的一点是:在getMeta
源码中,如果判断出cls
是元类
,那么就不会再继续往下递归查找,会直接返回this
,其目的是为了防止元类的无限递归查找
method3
和method4
都打印了地址,是因为如果找不到都会递归到元类
,而元类中有sayHappy
的实现地址。
IMP_classToMetaclass分析
* @param cls The class you want to inspect.
* @param name A selector.
*
* @return The function pointer that would be called if \c [object name] were called
* with an instance of the class, or \c NULL if \e cls is \c Nil.
*
* @note \c class_getMethodImplementation may be faster than \c method_getImplementation(class_getInstanceMethod(cls, name)).
* @note The function pointer returned may be a function internal to the runtime instead of
* an actual method implementation. For example, if instances of the class do not respond to
* the selector, the function pointer returned will be part of the runtime's message forwarding machinery.
大意是说:该函数在向类实例
发送消息时会被调用,并返回一个指向方法实现函数
的指针。这个函数会比method_getImplementation(class_getInstanceMethod(cls, name))
更快。返回的函数指针可能是一个指向runtime
内部的函数,而不一定是方法的实际实现。如果类实例无法响应selector
,则返回的函数指针将是运行时消息转发机制的一部分,即进行转发。
源码验证:
IMP class_getMethodImplementation(Class cls, SEL sel)
{
IMP imp;
if (!cls || !sel) return nil;
//查找方法实现
imp = lookUpImpOrNil(nil, sel, cls, LOOKUP_INITIALIZE | LOOKUP_RESOLVER);
//如果没有找到,则进行消息转发
if (!imp) {
return _objc_msgForward;
}
return imp;
}
打印结果分析:method1
和method4
分别是类中找到了sayHello实例方法
的实现和元类中找到了sayHappy类方法
的实现,故打印的是imp函数指针
的地址,而method2
和method3
则是没有找到方法的实现
,所以进行了消息转发
.
总结
class_getInstanceMethod
:获取实例方法
,如果指定的类或其父类
不包含带有指定选择器的实例方法,则为NULLclass_getClassMethod
:获取类方法
,如果指定的类或其父类
不包含具有指定选择器的类方法,则为NULL。class_getMethodImplementation
:获取方法的具体实现
,如果未查找到,则进行消息转发
【面试题】iskindOfClass & isMemberOfClass 的理解
请思考以下内容的打印结果:
int main(int argc, const char * argv[]) {
@autoreleasepool {
// insert code here...
BOOL re1 = [(id)[NSObject class] isKindOfClass:[NSObject class]]; //
BOOL re2 = [(id)[NSObject class] isMemberOfClass:[NSObject class]]; //
BOOL re3 = [(id)[ZGPerson class] isKindOfClass:[ZGPerson class]]; //
BOOL re4 = [(id)[ZGPerson class] isMemberOfClass:[ZGPerson class]]; //
NSLog(@" \n re1 :%hhd\n re2 :%hhd\n re3 :%hhd\n re4 :%hhd\n",re1,re2,re3,re4);
BOOL re5 = [(id)[NSObject alloc] isKindOfClass:[NSObject class]]; //
BOOL re6 = [(id)[NSObject alloc] isMemberOfClass:[NSObject class]]; //
BOOL re7 = [(id)[ZGPerson alloc] isKindOfClass:[ZGPerson class]]; //
BOOL re8 = [(id)[ZGPerson alloc] isMemberOfClass:[ZGPerson class]]; //
NSLog(@" \n re5 :%hhd\n re6 :%hhd\n re7 :%hhd\n re8 :%hhd\n",re5,re6,re7,re8);
}
return 0;
}
打印结果如下:
那么,为什么会出现这样的结果呢?我们只能进入isKindOfClass
和isMemberOfClass
的实例方法
和类方法
的源码
中寻找答案了。
(+/-)isKindOfClass
源码:
//第一次比较是 获取类的元类 与 传入类对比,再次之后的对比是获取上次结果的父类 与 传入 类进行对比
+ (BOOL)isKindOfClass:(Class)cls {
// 获取类的元类 vs 传入类
// 根元类 vs 传入类
// 根类 vs 传入类
// 举例:ZGPerson vs 元类 (根元类) (NSObject)
for (Class tcls = self->ISA(); tcls; tcls = tcls->superclass) {
if (tcls == cls) return YES;
}
return NO;
}
//第一次是获取对象本类 与 传入类对比,如果不相等,后续对比是继续获取上次 类的父类 与传入类进行对比
- (BOOL)isKindOfClass:(Class)cls {
// 获取本类 vs 传入的类
// 父类 vs 传入的类
// 根类 vs 传入的类
// nil vs 传入的类
for (Class tcls = [self class]; tcls; tcls = tcls->superclass) {
if (tcls == cls) return YES;
}
return NO;
}
注意:在这里发现了一个坑点
如果你的源码配置选的是
10.15
的话是不会执行上面的代码的(10.14
会执行上面的代码),反而会执行一个叫objc_opt_isKindOfClass
的方法。
objc_opt_isKindOfClass
源码
BOOL
objc_opt_isKindOfClass(id obj, Class otherClass)
{
#if __OBJC2__
if (slowpath(!obj)) return NO;
Class cls = obj->getIsa();
if (fastpath(!cls->hasCustomCore())) {
for (Class tcls = cls; tcls; tcls = tcls->superclass) {
if (tcls == otherClass) return YES;
}
return NO;
}
#endif
return ((BOOL(*)(id, SEL, Class))objc_msgSend)(obj, @selector(isKindOfClass:), otherClass);
}
可以看到这个的代码逻辑基本和isKindOfClass
的代码一样,只是做了一些配置和编译器优化的判断,运行结果都是一样的。
(+/-)isMemberOfClass
源码:
+ (BOOL)isMemberOfClass:(Class)cls {
//获取类的元类,与 传入类对比
return self->ISA() == cls;
}
- (BOOL)isMemberOfClass:(Class)cls {
//获取对象的本类,与 传入类对比
return [self class] == cls;
}
总结
isKindOfClass:
-
类方法
:元类 --> 根元类 --> 根类 --> nil
与传入类
的对比 -
实例方法
:对象的类 --> 父类 --> 根类 --> nil
与传入类
的对比
isMemberOfClass:
-
类方法
:类的元类
与传入类
对比 -
实例方法
:对象的父类
与传入类
对比
有了结论之后下面对打印结果逐个分析:
对于BOOL re1 = [(id)[NSObject class] isKindOfClass:[NSObject class]];
分析:
①:Class tcls = self->ISA()
,NSObject
类的isa
指向NSObject
的根元类
,根元类 != NSObject
,循环继续
②:tcls = tcls->superclass
,根元类的父类
是NSObject
, 此时NSObject == NSObject
,return YES;
对于BOOL re2 = [(id)[NSObject class] isMemberOfClass:[NSObject class]];
分析:
NSObject
类的isa
指向NSObject
的根元类
,根元类 != NSObject
,return NO;
对于BOOL re3 = [(id)[ZGPerson class] isKindOfClass:[ZGPerson class]];
分析:
①:ZGPerson
类的isa
指向ZGPerson
的元类
,ZGPerson 元类 != ZGPerson
,循环继续;
②: 元类ZGPerson
的父类即根元类
,根元类 != ZGPerson
,循环继续;
③:ZGPerson 根元类
指向根类NSObject
,NSObject != ZGPerson
,循环继续;
④:NSObject
的父类为nil
,nil != ZGPerson
,循环结束,return NO;
对于BOOL re4 = [(id)[ZGPerson class] isMemberOfClass:[ZGPerson class]];;
分析:
ZGPerson
类isa
指向ZGPerson
的根元类
,ZGPerson根元类 != ZGPerson
,return NO;
对于BOOL re5 = [(id)[NSObject alloc] isKindOfClass:[NSObject class]];
分析:
NSObject
类本类与NSObject
相比,NSObject == NSObject
,return YES;
对于BOOL re6 = [(id)[NSObject alloc] isMemberOfClass:[NSObject class]];
分析:
NSObject
类本类与NSObject
相比,NSObject == NSObject
,return YES;
对于BOOL re7 = [(id)[ZGPerson alloc] isKindOfClass:[ZGPerson class]];
分析:
ZGPerson
类本类与ZGPerson
相比,ZGPerson == ZGPerson
,return YES;
对于BOOL re8 = [(id)[ZGPerson alloc] isMemberOfClass:[ZGPerson class]];
分析:
ZGPerson
类本类与ZGPerson
相比,ZGPerson == ZGPerson
,return YES;
以上便是今天的全部内容,如有不对之处,还望指正!