这篇我们接着分析类。在iOS开发中我们经常会使用到实例方法和类方法。那实例方法和对象方法是存放在哪里的呢?今天,我们就来一探究竟。
一、属性、成员变量、实例变量
同样使用LPPerson作为演示,分别给LPPerson添加两个属性和实例变量,再利用runtime的API获取LPPerson的成员变量和属性并打印。
void LogObjc_copyIvar_copyProperies(Class pClass){
unsigned int count = 0;
Ivar *ivars = class_copyIvarList(pClass, &count);
for (unsigned int i=0; i < count; i++) {
Ivar const ivar = ivars[i];
//获取实例变量名
const char*cName = ivar_getName(ivar);
NSString *ivarName = [NSString stringWithUTF8String:cName];
NSLog(@"class_copyIvarList:%@",ivarName);
}
free(ivars);
unsigned int pCount = 0;
objc_property_t *properties = class_copyPropertyList(pClass, &pCount);
for (unsigned int i=0; i < pCount; i++) {
objc_property_t const property = properties[i];
//获取属性名
NSString *propertyName = [NSString stringWithUTF8String:property_getName(property)];
//获取属性值
NSLog(@"class_copyProperiesList:%@",propertyName);
}
free(properties);
}
@interface LPPerson : NSObject
{
NSString *hobby;
NSObject *objc;
}
@property (nonatomic, copy) NSString *nickName;
@property (nonatomic, strong) NSString *name;
@end
@implementation LPPerson
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
LPPerson *person = [LPPerson alloc];
Class pClass = object_getClass(person);
LogObjc_copyIvar_copyProperies(pClass);
NSLog(@"Hello, World!");
}
return 0;
}
我们运行,看一下打印结果:
class_copyIvarList:hobby
class_copyIvarList:objc
class_copyIvarList:_nickName
class_copyIvarList:_name
class_copyProperiesList:nickName
class_copyProperiesList:name
通过结果,我们可以发现:
- 结合以前的知识,我们可以知道,属性 = 成员变量 +
setter+getter - 属性的成员变量是带下划线_的,
{}中的成员变量是不带_的
那什么叫实例变量呢:
实例变量本质上就是成员变量,只是实例是针对类而言,实例是指类的声明。{ }中的objc就是实例变量。NSObject 是OC类,所以objc属于实例变量。但是并不是所有的成员变量都是实例变量,OC常用基本数据类型申明的成员变量和NSString类型不是成员变量。
NSString虽然继承于NSObject,但是仍然是常量类型, 因为不能添加属性,如果定义在类中的{}中,是成员变量
也可以理解为:实例变量 + 基本数据类型 + NSString = 成员变量
二、方法的归属
继续使用LPPerson,在它的.h中新增实例方法sayHello和类方法sayHappy,并在.m中实现
@interface LPPerson : NSObject
- (void)sayHello;
+ (void)sayHappy;
@end
@implementation LPPerson
- (void)sayHello{
NSLog(@"LPPerson say : Hello!!!");
}
+ (void)sayHappy{
NSLog(@"LPPerson say : Happy!!!");
}
@end
然后在main.m中实现如下代码:
void LogObjc_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 LpInstanceMethod_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(@"%s - %p-%p-%p-%p",__func__,method1,method2,method3,method4);
}
void LpClassMethod_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));
Method method4 = class_getClassMethod(metaClass, @selector(sayHappy));
NSLog(@"%s-%p-%p-%p-%p",__func__,method1,method2,method3,method4);
}
void LpIMP_classToMetaclass(Class pClass){
const char *className = class_getName(pClass);
Class metaClass = objc_getMetaClass(className);
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__);
}
int main(int argc, const char * argv[]) {
@autoreleasepool {
LPPerson *person = [LPPerson alloc];
Class pClass = object_getClass(person);
LogObjc_copyMethodList(pClass);
LpInstanceMethod_classToMetaclass(pClass);
LpClassMethod_classToMetaclass(pClass);
LpIMP_classToMetaclass(pClass)
NSLog(@"Hello, World!");
}
return 0;
}
看下运行结果:
Method, name: sayHello
lgInstanceMethod_classToMetaclass:------ 0x1000031b0---0x0---0x0---0x100003148
lgClassMethod_classToMetaclass:------ 0x0---0x0---0x100003148---0x100003148
lgIMP_classToMetaclass:------ 0x100001d10---0x7fff6f198580---0x7fff6f198580---0x100001d40
由上篇中isa和superClass走位图我们可以知道:
- 实例对象和其父类的关系是:
类->父类->根类->nil - 元类和其父类的关系是:
元类->根元类->根类->nil
方法查找时也是按照上面的顺序进行查找,如果找到就返回,没有找到则返回NULL
我们根据打印结果来分析下:
LogObjc_copyMethodList
去获取LPPerson的方法列表发现,只有一个实例方法:sayHello
实例方法是存在类中,所以此时只能找实例方法sayHello。
LpInstanceMethod_classToMetaclass
class_getInstanceMethod是用来在类中获取实例方法,如果在传入的类或者类的父类中没有找到指定的实例方法,则返回NULL
- 第一个参数表示在哪个类中获取方法
- 第二个是需要获取的方法
我们再来分析下LpInstanceMethod_classToMetaclass:
在LPPerson和LPPerson的元类中,分别获取sayHello和sayHappy的实例方法
-method1:在LPPerson中获取sayHello实例方法,LPPerson类中有,所以可以获取
-
method2:在LPPerson的元类中获取sayHello实例方法,LPPerson的元类中没有,根元类没有,根类没有,所以返回NULL,地址就为0x0 -
method3:在LPPerson的获取sayHappy实例方法,LPPerson中没有,LPPerson的父类NSObsject没有,所以返回NULL -
method4:在LPPerson元类中的获取sayHappy实例方法,LPPerson中没有,所以去LPPerson的元类中找,而类方法就是元类的是实例方法,所以LPPerson的元类获取到了。
LpClassMethod_classToMetaclass
class_getClassMethod:是用来类中获取类方法,如果在传入的类或者类的父类中没有找到指定的实例方法,则返回NULL
我们再看下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 判断是否是元类,是元类就直接返回,反之,继续找isa指向
Class getMeta() {
if (isMetaClass()) return (Class)this;
else return this->ISA();
}
可以得出class_getClassMethod的实现是获取类的类方法,其本质就是获取元类的实例方法,最终还是会走到class_getInstanceMethod,但是在这里需要注意的一点是:在getMeta源码中,如果判断出cls是元类,那么就不会再继续往下递归查找,会直接返回this,其目的是为了防止元类的无限递归查找
再来分析下LpClassMethod_classToMetaclass:
在LPPerson和LPPerson的元类中,分别获取sayHello和sayHappy的类方法
-
method1:在LPPerson中获取sayHello类方法,LPPerson中没有,LPPerson的父类NSObsject也没有,nil也没有所以返回NULL -
method2:在LPPerson的元类中获取sayHello类方法,LPPerson的元类中没有,根元类没有,根类没有,所以返回NULL,地址就为0x0 -
method3:在LPPerson的获取sayHappy类方法,LPPerson中没有,但是LPPerson元类中找,LPPerson元类存储的实例方法就是LPPerson类方法,所以可以找到 -
method4:在LPPerson元类中的获取sayHappy类方法,当前传入的是LPPerson元类,所以开始在LPPerson元类中去查找实例方法,即class_getInstanceMethod(cls->getMeta(), sel),而LPPerson的类方法,就是LPPerson元类存储的实例方法,所以可以获取到。
lpIMP_classToMetaclass
class_getMethodImplementation:
其大致含义就是:该函数在向类实例发送消息时会被调用,并返回一个指向方法实现函数的指针。这个函数会比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;
}
-
imp1:在LPPerson中查找sayHello实现,因为sayHello是实例方法,存在类中,所以可以找到返回对应的实现 -
imp2:在LPPerson元类中查找sayHello实现,因为LPPerson元类没有,所以进行了消息转发 -
imp3:在LPPerson类中查找sayHappy实现,因为sayHappy是类方法,类方法是存在元类中的,LPPerson类没有,所以进行了消息转发 -
imp4:在LPPerson元类中查找sayHappy实现,因为sayHappy是类方法,类方法是存在元类中的,LPPerson元类中,所以可以找到返回对应的实现
三、经典题目分析:
//-----使用 iskindOfClass & isMemberOfClass 类方法
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);
//------iskindOfClass & isMemberOfClass 实例方法
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);
上面打印结果是什么?各位可以先思考一下,再看下面的结果
结果如下:
re1 :1
re2 :0
re3 :0
re4 :0
re5 :1
re6 :1
re7 :1
re8 :1
在分析结果之前,我们先看一下isKindOfClass和isMemberOfClass的源码:
isKindOfClass源码,分为类方法和实例方法:
//--isKindOfClass---类方法、对象方法
//+ isKindOfClass:第一次比较是 获取类的元类 与 传入类对比,再次之后的对比是获取上次结果的父类 与 传入 类进行对比
+ (BOOL)isKindOfClass:(Class)cls {
// 获取类的元类 vs 传入类
// 根元类 vs 传入类
// 根类 vs 传入类
// 举例:LGPerson vs 元类 (根元类) (NSObject)
for (Class tcls = self->ISA(); tcls; tcls = tcls->superclass) {
if (tcls == cls) return YES;
}
return NO;
}
//- isKindOfClass:第一次是获取对象类 与 传入类对比,如果不相等,后续对比是继续获取上次 类的父类 与传入类进行对比
- (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;
}
isMemberOfClass源码,同样分为类方法和实例方法
//-----类方法
//+ isMemberOfClass : 获取类的元类,与 传入类对比
+ (BOOL)isMemberOfClass:(Class)cls {
return self->ISA() == cls;
}
//-----实例方法
//- isMemberOfClass : 获取对象的类,与 传入类对比
- (BOOL)isMemberOfClass:(Class)cls {
return [self class] == cls;
}
根据源码,我们可以总结如下:
-
isKindOfClass
类方法:元类 --> 根元类 --> 根类 -->nil与 传入类的对比
实例方法:对象的类 --> 父类 --> 根类 -->nil与 传入类的对比 -
isMemberOfClass
类方法: 类的元类 与 传入类 对比
实例方法:对象的父类 与 传入类 对比
接下来,我们再来分析上面题目:
re1:
[NSObject class]执行的是isKindOfClass类方法,所以:
NSObject的元类是根元类,根元类!=NSObject,
继续寻找根元类的父类,即根类,根类即为NSObject。
NSObject==NSObject所以为1。re2:
[NSObject class]执行的是isMemberOfClass类方法,所以:
NSObject的元类是根元类,根元类!=NSObject,所以为0re3:
[LGPerson class]执行的是isKindOfClass类方法,所以:
LGPerson的元类!=LGPerson,
继续LGPerson的元类的父类是根元类,根元类 !=LGPerson,
继续根元类的父类即根类,根类!=LGPerson
根类的父类是nil,nil != LGPerson,
所以为0re4:
[LGPerson class]执行的是isMemberOfClass类方法,所以:
LGPerson的元类 !=LGPerson,所以为0
re5:
[NSObject alloc]执行的是isKindOfClass实例方法,所以:
[NSObject alloc]的类NSObject,NSObject==NSObject,所以为1。re6:
[NSObject alloc]执行的是isMemberOfClass实例方法,所以:
[NSObject alloc]的类NSObject,NSObject==NSObject,所以为1re7:
[LGPerson alloc]执行的是isKindOfClass实例方法,所以:
[LGPerson alloc]的类是LGPerson,LGPerson== LGPerson,所以为1re8:
[LGPerson alloc]执行的是isMemberOfClass实例方法,所以:
[LGPerson alloc]的类是LGPerson,LGPerson == LGPerson,所以为1
觉得不错记得点赞哦!听说看完点赞的人逢考必过,逢奖必中。ღ( ´・ᴗ・` )比心