和谐学习!不急不躁!!我是你们的老朋友小青龙~
在上篇文章isa分析之类的探究(上),我们遗留了两个问题:成员变量和类方法存储在哪儿呢? 在解答这个问题之前,我们有必要先了解一下iOS app中,内存的分类:Clean Memory、Dirty Memory、Compressed Memory。
- Clean Memory
为了节省内存空间,将一部分低优先级的数据存放到Clean Memory。
有人称之为是能够进行 Page Out 的部分。
- Dirty Memory
指那些被 App 写入过数据的内存。
- Compressed Memory
当内存不够用的时候,系统会将不使用的内存进行压缩,直到下一次访问的时候进行解压。
例如,当我们使用 Dictionary 去缓存数据的时候,假设现在已经使用了 3 页内存,
当不访问的时候可能会被压缩为 1 页,再次使用到时候又会解压成 3 页。
这里我们主要讨论的是Clean Memor和Dirty Memory。 上篇文章,我们在研究类的objc_class的时候,提到了class_rw_t,而class_rw_t里又包含了class_ro_t。
- class_ro_t:只读。
包含了类在编译时期就确定了的属性、方法、协议。
- 提到了class_rw_t:可读可写。
包含了运行时添加的方法,比如分类添加方法。
我们再来看一下class_rw_t的结构:
struct class_rw_t {
...
const class_ro_t *ro() const {
...
}
void set_ro(const class_ro_t *ro) {
...
}
const method_array_t methods() const {
...
}
const property_array_t properties() const {
...
}
const protocol_array_t protocols() const {
...
}
};
我们之前是通过访问properties来获取属性,但是最终没发现成员变量。根据上面的叙述,class_ro_t是包含了类在编译时期就确定的属性,那么我们的成员变量会不会就存在ro里呢?我们打印一下看看:
至此,我们就找到了成员变量。
那么类方法又在何处呢?
在回答这个问题之前,我们先来分析一下,我们之前打印DirectionChild类的方法都是实例方法,实例-》类 这一层关系是不是有点上下层的概念,所以我们猜想:既然实例方法放在类里面,那么类方法是不是放在类的上一层元类里呢?纸上谈兵不如实际操作,我们直接撸代码:
第一种验证方式:- - - - - - - - - - - - lldb调试
至此,我们也找到了类方法。
那么为什么苹果要把类方法放在元类
里,而不是跟实例方法一样放在类里面呢?因为对于底层来说,不关实例方法还是类方法,都是对象方法
(类也是一个对象),都是按照符号
(方法名)来找,如果定义两个名字一样实例方法和类方法:
-(void)runAgain;
+(void)runAgain;
这样在调用runAgain方法的时候,系统就没法区分是调用实例方法的还是调用类方法。所以苹果设计了元类
这么一个东西,把类方法都丢进去,这样就完美的解决了这个问题。
第二种验证方式 - - - - - - - - - - - - 遍历->类及元类的实例方法列表
其实我们还有一种方法可以验证类方法在元类里,就是通过api的形式去获取方法列表,然后将方法名遍历打印。我们这边要主要是遍历类的方法以及元类的方法
,元类也是类,所以我们提取公用部分的代码写成一个方法:
///打印类方法名
///参数:Class类
void logClassMethod(Class cla)
{
unsigned int methodsCount = 0;
Method *me = class_copyMethodList(cla, &methodsCount);
for (unsigned int i = 0 ; i < methodsCount; i++) {
Method methodI = me[i];
SEL sel = method_getName(methodI);
NSString *name = NSStringFromSelector(sel);
NSLog(@"method name - %@",name);
}
///这里不要忘记释放内存
free(me);
}
///打印元类方法名
///参数:Class类
void logClassMetaMethod(Class cla)
{
const char *className = class_getName(cla);
Class metaClass = objc_getMetaClass(className);
///将元类作为参数
logClassMethod(metaClass);
}
int main(int argc, const char * argv[]) {
...
///下面打印类方法、元类方法
NSLog(@"打印 类方法-》");
logClassMethod(DirectionChild.class);
NSLog(@"打印 元类方法-》");
logClassMetaMethod(DirectionChild.class);
...
return 0;
}
为了方便对照,我直接把DirectionChild
代码贴出来
@interface DirectionChild:Direction
{
NSString *bgColorValue;
}
@property (nonatomic,strong) NSString *hobby;
@property (nonatomic,assign) NSInteger runSpeed;
@property (nonatomic,copy) NSString *indexZ;
///方法
-(void)runSpeedNonTime;
+(void)runSpeedWithTime:(NSString *)time;
-(void)eatSomethingNonTime;
+(void)eatSomethingWithTime:(NSString *)time;
@end
@implementation DirectionChild
-(void)runSpeedNonTime{}
+(void)runSpeedWithTime:(NSString *)time{}
-(void)eatSomethingNonTime{}
+(void)eatSomethingWithTime:(NSString *)time{}
@end
打印结果:
第三种验证方法: - - - - - - - - - - - - 打印实例方法地址
#pragma mark -- 第三种
void logInstanceMethodP(Class cla)
{
NSLog(@"\n\n第三种 验证方式-》实例方法");
/**打印DirectionChild - 类 是否包含runSpeedNonTime的函数地址 */
Method runM_class = class_getInstanceMethod(cla, @selector(runSpeedNonTime));
NSLog(@"class -> runSpeedNonTime -> %p",runM_class);
/**打印DirectionChild - 元类 是否包含runSpeedNonTime的函数地址 */
Class metaClass = objc_getMetaClass(class_getName(cla));
Method runM_metaClass = class_getInstanceMethod(metaClass, @selector(runSpeedNonTime));
NSLog(@"metaClass -> runSpeedNonTime -> %p",runM_metaClass);
/**打印DirectionChild - 类 是否包含runSpeedWithTime:的函数地址 */
Method runM_class2 = class_getInstanceMethod(cla, @selector(runSpeedWithTime:));
NSLog(@"class -> runSpeedWithTime: -> %p",runM_class2);
/**打印DirectionChild - 元类 是否包含runSpeedWithTime:的函数地址 */
Class metaClass2 = objc_getMetaClass(class_getName(cla));
Method runM_metaClass2 = class_getInstanceMethod(metaClass2, @selector(runSpeedWithTime:));
NSLog(@"metaClass -> runSpeedWithTime: -> %p",runM_metaClass2);
}
打印结果:
第四种验证方法: - - - - - - - - - - - - 打印类方法地址
#pragma mark -- 第四种
void logClassMethodP(Class cla)
{
NSLog(@"\n\n第四种 验证方式-》类方法");
/**打印DirectionChild - 类 是否包含runSpeedNonTime的函数地址 */
Method runM_class = class_getClassMethod(cla, @selector(runSpeedNonTime));
NSLog(@"class -> runSpeedNonTime -> %p",runM_class);
/**打印DirectionChild - 元类 是否包含runSpeedNonTime的函数地址 */
Class metaClass = objc_getMetaClass(class_getName(cla));
Method runM_metaClass = class_getClassMethod(metaClass, @selector(runSpeedNonTime));
NSLog(@"metaClass -> runSpeedNonTime -> %p",runM_metaClass);
/**打印DirectionChild - 类 是否包含runSpeedWithTime:的函数地址 */
Method runM_class2 = class_getClassMethod(cla, @selector(runSpeedWithTime:));
NSLog(@"class -> runSpeedWithTime: -> %p",runM_class2);
/**打印DirectionChild - 元类 是否包含runSpeedWithTime:的函数地址 */
Class metaClass2 = objc_getMetaClass(class_getName(cla));
Method runM_metaClass2 = class_getClassMethod(metaClass2, @selector(runSpeedWithTime:));
NSLog(@"metaClass -> runSpeedWithTime: -> %p",runM_metaClass2);
}
打印结果:
如图所示,类对应的这一行竟然打印出了runSpeedWithTime:方法的地址,按照前面的理解,
类的类方法应该存放在元类的实例方法列表里
才对,可是为什么在Class类这一层就打印了呢?我们Command+单机class_getClassMethod
进去看一下里面的实现:
Method class_getClassMethod(Class cls, SEL sel)
{
if (!cls || !sel) return nil;
return class_getInstanceMethod(cls->getMeta(), sel);
}
/**翻译:
1、Class或SEL为空,就返回nil;
2、class_getClassMethod方法在底层的实现是class_getInstanceMethod,并且第一个参数是cls的元类。
3、这里就解释通了,为什么图片上关于runSpeedWithTime:的打印,类和元类是打印的地址一样。
在底层没有类方法的概念,万物皆对象,只有对方方法的概念。
*/
第五种验证方法: - - - - - - - - - - - - 打印IMP地址
#pragma mark -- 第五种
void logIMP(Class cla)
{
NSLog(@"\n\n第五种 验证方式-》IMP");
/**打印DirectionChild - 类 是否包含runSpeedNonTime的函数地址 */
IMP runM_class = class_getMethodImplementation(cla, @selector(runSpeedNonTime));
NSLog(@"class -> runSpeedNonTime -> %p",runM_class);
/**打印DirectionChild - 元类 是否包含runSpeedNonTime的函数地址 */
Class metaClass = objc_getMetaClass(class_getName(cla));
IMP runM_metaClass = class_getMethodImplementation(metaClass, @selector(runSpeedNonTime));
NSLog(@"metaClass -> runSpeedNonTime -> %p",runM_metaClass);
/**打印DirectionChild - 类 是否包含runSpeedWithTime:的函数地址 */
IMP runM_class2 = class_getMethodImplementation(cla, @selector(runSpeedWithTime:));
NSLog(@"class -> runSpeedWithTime: -> %p",runM_class2);
/**打印DirectionChild - 元类 是否包含runSpeedWithTime:的函数地址 */
Class metaClass2 = objc_getMetaClass(class_getName(cla));
IMP runM_metaClass2 = class_getMethodImplementation(metaClass2, @selector(runSpeedWithTime:));
NSLog(@"metaClass -> runSpeedWithTime: -> %p",runM_metaClass2);
}
打印结果:
图片上圈起来的两个地方,按照我们前面的理解,
IMP
应该是nil
,这里应该是显示0x0,可是为什么会有地址呢?
带着这个疑问,我们点击进入class_getMethodImplementation
去看看里面的实现:
IMP class_getMethodImplementation(Class cls, SEL sel)
{
IMP imp;
if (!cls || !sel) return nil;
lockdebug_assert_no_locks_locked_except({ &loadMethodLock });
imp = lookUpImpOrNilTryCache(nil, sel, cls, LOOKUP_INITIALIZE | LOOKUP_RESOLVER);
// Translate forwarding function to C-callable external version
if (!imp) {
return _objc_msgForward;
}
return imp;
}
我们发现,当imp
为nil的时候,会return一个默认的_objc_msgForward
地址,这也就解释了为什么上面IMP的地址打印都不为空了。
百度网盘:
链接:pan.baidu.com/s/1qWbB4c7l… 密码:vkh4