iOS 探索类(下)---方法归属
这篇我们接着分析类。在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
没有,所以返回NUL
L -
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
,所以为0 -
re3
:
[LGPerson class]
执行的是isKindOfClass
类方法,所以:
LGPerson
的元类!=LGPerson
,
继续LGPerson
的元类的父类是根元类,根元类 !=LGPerson
,
继续根元类的父类即根类,根类!=LGPerson
根类的父类是nil
,nil != LGPerson
,
所以为0 -
re4
:
[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
,所以为1 -
re7
:
[LGPerson alloc]
执行的是isKindOfClass
实例方法,所以:
[LGPerson alloc]
的类是LGPerson
,LGPerson== LGPerson
,所以为1 -
re8
:
[LGPerson alloc]
执行的是isMemberOfClass
实例方法,所以:
[LGPerson alloc]
的类是LGPerson
,LGPerson == LGPerson
,所以为1
觉得不错记得点赞哦!听说看完点赞的人逢考必过,逢奖必中。ღ( ´・ᴗ・` )比心