iOS面试 - 方法归属&isKindOfClass
本篇文章将从几个面试题出发,探究方法的归属以及isa与Superclass。
objc_object 与对象的关系,objc_object 与 NSObject的关系
- 所有
对象
在底层都是以objc_object
为模版继承
来的。 - 所有
对象
都是继承自NSObject
(根类
),而在底层中NSObject
是一个objc_object
(C/C++
)结构体。
所以结论:objc_object
与对象
之间是继承
关系。
属性、成员变量、实例变量
-
属性
:带下划线
的成员变量 +setter
+getter
,以@property
开头定义的变量 -
成员变量
:定义在类.h文件的{}
中的,不带下划线的变量 -
实例变量
:特殊的成员变量,经过实例化的成员变量对象,例如UIButton
、UILabel
等 -
成员变量
中除去基本数据类型及{}中定义的NSString
类型变量,剩下的都是实例化
后的实例变量,实例变量可理解为是拥有属性
的对象
。
方法的归属分析
下面通过一个例子来探究实例方法及类方法的归属问题:
#import <Foundation/Foundation.h>
@interface LGPerson : NSObject
- (void)sayHello;
+ (void)sayHappy;
@end
#import "LGPerson.h"
@implementation LGPerson
- (void)sayHello{
NSLog(@"LGPerson say : Hello!!!");
}
+ (void)sayHappy{
NSLog(@"LGPerson say : Happy!!!");
}
@end
定义继承于NSObject的LGPerson类,并添加一个实例方法sayHello,一个类方法sayHappy,下面通过几个打印情况来分析:
int main(int argc, const char * argv[]) {
@autoreleasepool {
// insert code here...
// 0x0000000100000000
// LGTeacher *teacher = [LGTeacher alloc];
LGPerson *person = [LGPerson alloc];
Class pClass = object_getClass(person);
lgObjc_copyMethodList(pClass);
lgInstanceMethod_classToMetaclass(pClass);
lgClassMethod_classToMetaclass(pClass);
lgIMP_classToMetaclass(pClass);
NSLog(@"Hello, World!");
}
return 0;
}
方法从上而下的定义如下面:
#ifdef DEBUG
#define LGLog(format, ...) printf("%s\n", [[NSString stringWithFormat:format, ## __VA_ARGS__] UTF8String]);
#else
#define LGLog(format, ...);
#endif
void lgObjc_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));
LGLog(@"Method, name: %@", key);
}
free(methods);
}
void lgInstanceMethod_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));
LGLog(@"%s - %p-%p-%p-%p",__func__,method1,method2,method3,method4);
}
void lgClassMethod_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));
LGLog(@"%s-%p-%p-%p-%p",__func__,method1,method2,method3,method4);
}
void lgIMP_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(@"%p-%p-%p-%p",imp1,imp2,imp3,imp4);
NSLog(@"%s",__func__);
}
下面我们先给出打印结果,然后逐个分析:
方法归属结果打印
lgObjc_copyMethodList
object_getClass
获取当前实例对象person
的类对象
。
此方法参数为Class
类型的pClass
,一个类对象
,class_copyMethodList
获取当前类对象中的方法列表,遍历此类对象所有的实例方法,根据打印情况只打印sayHello
实例方法,可以验证类对象里面存放实例方法
,类方法不存放在类对象中。
lgInstanceMethod_classToMetaclass
方法中的metaClass
为当前方法参数类对象pClass
的元类,由objc_getMetaClass
根据类获取到元类,class_getInstanceMethod 获取实例方法的源码分析后可知,在传入类及传入类的父类一级一级找下去直到找到返回,没找到返回null,下面分别分析几个打印情况:
- method1:
0x1000031b0
因为
sayHello
是实例方法,当前pClass
刚好也是存放sayHello
的LGPerson
类,所以能找到方法并打印
- method2:
0x0
因为
metaClass
为元类,元类
(LGPerson
)--->根元类
(NSObject
)--->NSObject
--->nil
,发现找不到当前的sayHello
方法,说明返回null
,打印为0x0
- method3:
0x0
当前需要找方法
sayHappy
,类
(LGPerson
)--->根类
(NSObject
)--->nil
,没找到sayHappy
方法,所以返回null
,打印0x0
- method4:
0x100003148
寻找类方法
sayHappy
,metaClass
为当前类的元类,类方法存在类的元类中,所以此时在metaClass
中就找到sayHppy
方法,即可返回并打印0x100003148
lgClassMethod_classToMetaclass
跟上面一样,方法参数pClass
类对象,metaClass
为当前类的元类
,class_getClassMethod
获取类方法源代码如下:
Method class_getClassMethod(Class cls, SEL sel)
{
if (!cls || !sel) return nil;
return class_getInstanceMethod(cls->getMeta(), sel);
}
Class getMeta() {
if (isMetaClass()) return (Class)this;
else return this->ISA();
}
class_getClassMethod
源码可见,获取类的类方法
就是去获取当前类的元类的实例方法
,getMeta()
就是返回当前类的元类(如果getMeta()
方法当前调用者为元类
时,直接返回自己
,不是的话就返回当前调用者的元类
),下面分别分析打印情况:
- method1:
0x0
sayhello
实例方法,class_getClassMethod
当前传入为类,不是元类
,拿到元类
,然后class_getInstanceMethod
方法寻找,元类
--->根元类
--->NSObject
--->nil
,没找到sayHello
,返回null
,打印0x0
- method2:
0x0
class_getClassMethod
当前传入为metaClass
元类,直接进行class_getInstanceMethod
方法查找当前元类
--->根元类
--->NSObject
--->nil
,没找到实例方法sayHello
方法,所以返回null
,打印0x0
- method3:
0x100003148
当前查找方法为类方法
sayHappy
,在pClass
类中寻找,所以class_getClassMethod
中要拿到元类
,然后进行查找,发现在元类中找到类方法sayHappy
,所以能打印方法地址0x100003148
- method4:
0x100003148
class_getClassMethod
传入即为类的元类,所以直接进行class_getInstanceMethod
方法查找,在当前元类找到了sayHappy
类方法,所以返回找到的方法
lgIMP_classToMetaclass
class_getMethodImplementation
返回方法的具体实现
:
IMP class_getMethodImplementation(Class cls, SEL sel)
{
IMP imp;
if (!cls || !sel) return nil;
imp = lookUpImpOrNil(nil, sel, cls, LOOKUP_INITIALIZE | LOOKUP_RESOLVER);
// Translate forwarding function to C-callable external version
if (!imp) {
return _objc_msgForward;
}
return imp;
}
如果在当前类对象里没找到对应的方法实现,就会触发底层的消息转发,所以打印结果是四个都有值,而不是0x0
- method1:
0x100001d10
sayHello
实例方法,在当前pClass
类中能找到当前方法,所以method1
返回的是sayHello
方法地址指针
- method2:
0x7fff6cd17580
在
metaClass
元类中寻找实例方法sayHello
,是找不到的,所以进行了底层的消息转发
,返回的不是sayHello
的实现地址
- method3:
0x7fff6cd17580
sayHappy
类方法,因为类方法是存储在元类里的,所以在类pClass
中找不到sayHappy
方法实现地址,所以触发了底层消息转发
- method4:
0x100001d40
在
metaClass
元类 中找到了sayHappy
方法实现,所以返回的就是真正的sayHappy
实现地址
isKindOfClass & isMemberOfClass
同样,利用上面的类LGPerson
进行下面探究:
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)[LGPerson class] isKindOfClass:[LGPerson class]]; //
BOOL re4 = [(id)[LGPerson class] isMemberOfClass:[LGPerson class]]; //
NSLog(@" 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)[LGPerson alloc] isKindOfClass:[LGPerson class]]; //
BOOL re8 = [(id)[LGPerson alloc] isMemberOfClass:[LGPerson class]]; //
NSLog(@" re5 :%hhd\n re6 :%hhd\n re7 :%hhd\n re8 :%hhd\n",re5,re6,re7,re8);
}
return 0;
}
isKind&isMember结果
首先理清一下isKindOfClass
方法及isMemberOfClass
方法:
-
isKindOfClass
方法:+ (BOOL)isKindOfClass:(Class)cls { // 类 vs 元类 // 根元类 vs NSObject // NSObject vs NSObject // LGPerson vs 元类 (根元类) (NSObject) for (Class tcls = self->ISA(); 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; }
+isKindOfClass
比较线路:元类
--->根元类
--->NSObject
--->nil
与当前cls
类比较。实例方法-isKindOfClass
比较路线:对象的类
--->父类
--->根类
--->nil
与cls
比较。
注意(有坑!!!)
你以为就是这样了?当然不是,isKindOfClass
方法在llvm
编译器在编译期做了编译优化,isKindOfClass
在底层走的方法是objc_opt_isKindOfClass
,打开源码,搜索objc_opt_isKindOfClass
打断点调试,发现调用isKindOfClass
类方法及实例方法,走的都是这个方法:
// Calls [obj 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);
}
可见,无论是实例方法还是类方法调用,比较的对象链:元类
---> 根元类
---> NSObject
---> nil
,也就是说isKindOfClass
就是遵循isa
走向的规则
- isMemberOfClass方法:
+ (BOOL)isMemberOfClass:(Class)cls { return self->ISA() == cls; }
类方法- (BOOL)isMemberOfClass:(Class)cls { return [self class] == cls; }
+isMemberOfClass
判断的是当前类
是否是元类
,实例方法-isMemberOfClass
判断是否是当前对象的类
。
类方法:re1
、re2
、re3
、re4
实例方法:re5
、re6
、re7
、re8
-
re1
:1
首先拿到前者
NSObject
的元类(根元类
),与后者NSObject
类比较,不相等
,找根元类的父类,也就是NSObject
类,比较结果为相等,所以返回YES
,打印1
-
re2
:0
NSObject
元类与NSObject
类当然不相等,所以返回NO
-
re3
:0
LGPerson
类 与LGPerson
元类不想等,与LGPerson
根元类不想等,与NSObject
类不想等,与nil
不想等,所以返回NO
-
re4
:0
LGPerson
类与LGPerson
元类不相等,返回NO
-
re5
:1
NSObject
实例对象的类NSObject
当然与NSObject
类相等,返回YES
-
re6
:1
NSObject
实例对象的类NSObject
当然与NSObject
类相等
-
re7
:1
LGPerson
实例对象的类LGPerson
与LGPerson
类相等
-
re8
:1
LGPerson
实例对象的类LGPerson
与LGPerson
类相等