runtime:打印类属性、方法信息

2019-11-06  本文已影响0人  四月_Hsu

前言

这篇文章专注打印一个类的方法,属性等信息。以及实践过程中遇到的问题的思考和探索。

正文

分条列举说明。

测试类 Person 头文件定义:

#import <Foundation/Foundation.h>
#include <objc/message.h>

NS_ASSUME_NONNULL_BEGIN

@protocol HumanInfo <NSObject>
-(void)setSomeOneCountry;
@end

@interface Person : NSObject<HumanInfo>

/// 类属性
@property(nonatomic, strong, class) NSString *address;
/// 实例属性
@property(nonatomic, strong) NSString *phone;

/// 类方法
+(NSInteger)howOldAreYou;
/// 实例方法
-(void)whatName:(NSString * _Nullable)name;
-(NSString *_Nullable)fullName;

// MARK:- runtime
/// 获取协议方法
-(void)getProtocolMethods;
/// 属性的获取。并且动态修改属性信息
-(void)getIvarAndChange;
/// 获取实例方法
-(void)getInstanceMethod;
/// 获取类方法
-(void)getClassMethod;

@end

NS_ASSUME_NONNULL_END

获取协议方法

/// 获取协议及协议方法
- (void)getProtocolMethods {
    unsigned int count;
    // 获取协议列表
    __unsafe_unretained Protocol **protocolList = class_copyProtocolList([self class], &count);
    for (unsigned int i = 0; i < count; i++) {
        Protocol *pro = protocolList[i];
        const char *protoclName = protocol_getName(pro);
        NSLog(@"协议名称:%@", [NSString stringWithUTF8String:protoclName]);
        // 获取协议方法名
        NSArray *names = [self methodListWithProtocol:pro];
        for (NSString *name in names) {
            NSLog(@"%@ 协议方法: %@", [NSString stringWithUTF8String:protoclName], name);
        }
    }
}

/// 获取协议方法
-(NSArray<NSString *>*)methodListWithProtocol: (Protocol *)protocol {
    unsigned int count = 0;
    NSMutableArray<NSString *> *methodList = @[].mutableCopy;
    struct objc_method_description *methods = protocol_copyMethodDescriptionList(protocol, YES, YES, &count);
    for (unsigned int i = 0; i < count; i++) {
        struct objc_method_description method = methods[i];
        NSString *name = NSStringFromSelector(method.name);
        [methodList addObject:name];
    }
    free(methods);
    return methodList;
}

/// 协议方法
- (void)setSomeOneCountry {
    NSLog(@"设置所属国家");
}

测试:

Person *p = [[Person alloc] init];
[p getProtocolMethods];

结果:

 协议名称:HumanInfo
 HumanInfo 协议方法: setSomeOneCountry

获取属性列表并动态更改属性值

/// 属性的获取。并且动态修改属性信息
-(void)getIvarAndChange {
    NSLog(@"修改前: %@", self.phone);
    unsigned int count;
    Ivar *vars = class_copyIvarList([self class], &count);
    for (unsigned int i = 0; i < count; i++) {
        Ivar oneVar = vars[i];
        // 获取名字和类型
        const char *memberName = ivar_getName(oneVar);
        const char *memberType = ivar_getTypeEncoding(oneVar);
        // 一次打印属性名称和属性类型
        NSLog(@"属性信息  %s: %s", memberName, memberType);
        
        // 修改属性
        if (strcmp(memberName, "phone") == 0) {
            // 修改前
            NSString *name = (NSString *)object_getIvar(self, oneVar);
            NSLog(@"开始修改手机属性  phone: %@", name);
            // 修改后
            object_setIvar(self, oneVar, @"telphoneNumber");
            // 修改后获取
            NSString *newName = (NSString *)object_getIvar(self, oneVar);
            NSLog(@"修改后的手机属性  %@", newName);
        }
    }
    free(vars);
    NSLog(@"方法列表获取结束,修改后属性信息 phone: %@", self.phone);
}

测试:

[p getIvarAndChange];

结果:

2019-11-05 17:34:13.838345+0800 CateAndExt[51413:735475] 修改前: (null)
2019-11-05 17:34:13.838493+0800 CateAndExt[51413:735475] 属性信息  phone: @"NSString"
2019-11-05 17:34:13.838639+0800 CateAndExt[51413:735475] 开始修改手机属性  phone: (null)
2019-11-05 17:34:13.838763+0800 CateAndExt[51413:735475] 修改后的手机属性  telphoneNumber
2019-11-05 17:34:13.838888+0800 CateAndExt[51413:735475] 属性信息  _delegate: @"<HumanInfo>"
2019-11-05 17:34:13.838994+0800 CateAndExt[51413:735475] 方法列表获取结束,修改后属性信息 phone: telphoneNumber

获取实例方法列表

/// 获取实例方法
/// 获取实例方法
-(void)getInstanceMethod {
    unsigned int count;
    Method *methodList = class_copyMethodList([self class], &count);
    for (unsigned int i = 0; i < count; i++) {
        Method oneMethod = methodList[i];
        // 获取方法名
        NSString *methodName = NSStringFromSelector(method_getName(oneMethod));
        // 获取返回类型
        char retName[512] = {};
        method_getReturnType(oneMethod, retName, 512);
        // 获取参数个数
        unsigned int argCount = method_getNumberOfArguments(oneMethod);
        // 方法指针地址
        IMP methodImp = method_getImplementation(oneMethod);
        NSLog(@"MethodName: %@ 😎 retName: %s 😎 argCount: %u 😎 methodImp: %p",methodName, retName, argCount, methodImp);
    }
}

测试:

[p getInstanceMethod];

结果:

2019-11-05 18:04:33.361437+0800 CateAndExt[52474:754299] MethodName: getInstanceMethod 😎 retName: v 😎 argCount: 2 😎 methodImp: 0x10ec3c100
2019-11-05 18:04:33.361690+0800 CateAndExt[52474:754299] MethodName: methodListWithProtocol: 😎 retName: @ 😎 argCount: 3 😎 methodImp: 0x10ec3bd90
2019-11-05 18:04:33.361835+0800 CateAndExt[52474:754299] MethodName: setSomeOneCountry 😎 retName: v 😎 argCount: 2 😎 methodImp: 0x10ec3bed0
2019-11-05 18:04:33.361971+0800 CateAndExt[52474:754299] MethodName: whatName: 😎 retName: v 😎 argCount: 3 😎 methodImp: 0x10ec3b900
2019-11-05 18:04:33.362098+0800 CateAndExt[52474:754299] MethodName: getProtocolMethods 😎 retName: v 😎 argCount: 2 😎 methodImp: 0x10ec3ba10
2019-11-05 18:04:33.362222+0800 CateAndExt[52474:754299] MethodName: getIvarAndChange 😎 retName: v 😎 argCount: 2 😎 methodImp: 0x10ec3bf00
2019-11-05 18:04:33.362369+0800 CateAndExt[52474:754299] MethodName: getClassMethod 😎 retName: v 😎 argCount: 2 😎 methodImp: 0x10ec3c2a0
2019-11-05 18:04:33.362543+0800 CateAndExt[52474:754299] MethodName: .cxx_destruct 😎 retName: v 😎 argCount: 2 😎 methodImp: 0x10ec3c350
2019-11-05 18:04:33.364629+0800 CateAndExt[52474:754299] MethodName: delegate 😎 retName: @ 😎 argCount: 2 😎 methodImp: 0x10ec3c310
2019-11-05 18:04:33.364777+0800 CateAndExt[52474:754299] MethodName: setDelegate: 😎 retName: v 😎 argCount: 3 😎 methodImp: 0x10ec3c330
2019-11-05 18:04:33.364899+0800 CateAndExt[52474:754299] MethodName: phone 😎 retName: @ 😎 argCount: 2 😎 methodImp: 0x10ec3c2b0
2019-11-05 18:04:33.365038+0800 CateAndExt[52474:754299] MethodName: fullName 😎 retName: @ 😎 argCount: 2 😎 methodImp: 0x10ec3b960
2019-11-05 18:04:33.365172+0800 CateAndExt[52474:754299] MethodName: nickName 😎 retName: @ 😎 argCount: 2 😎 methodImp: 0x10ec3b6a0
2019-11-05 18:04:33.365297+0800 CateAndExt[52474:754299] MethodName: setNickName: 😎 retName: v 😎 argCount: 3 😎 methodImp: 0x10ec3b6d0
2019-11-05 18:04:33.365419+0800 CateAndExt[52474:754299] MethodName: setPhone: 😎 retName: v 😎 argCount: 3 😎 methodImp: 0x10ec3c2d0

获取类方法

/// 获取类方法
-(void)getClassMethod {
    unsigned int count;
    Class metaclass = object_getClass([self class]);
    Method *methodList = class_copyMethodList(metaclass, &count);
    for (unsigned int i = 0; i < count; i++) {
        Method oneMethod = methodList[i];
        // 获取方法名
        NSString *methodName = NSStringFromSelector(method_getName(oneMethod));
        // 获取返回类型
        char retName[512] = {};
        method_getReturnType(oneMethod, retName, 512);
        // 获取参数个数
        unsigned int argCount = method_getNumberOfArguments(oneMethod);
        // 方法指针地址
        IMP methodImp = method_getImplementation(oneMethod);
        NSLog(@"类方法MethodName: %@ 🧐 retName: %s 🧐 argCount: %u 🧐 methodImp: %p",methodName, retName, argCount, methodImp);
    }
}

修改分类:

#import "Person.h"

NS_ASSUME_NONNULL_BEGIN

@interface Person (Man)
@property(nonatomic, strong) NSString *nickName;

+(NSInteger)howOldAreYou;
+(NSInteger)howOldAreYouoneMore;


@end

NS_ASSUME_NONNULL_END

测试:

 [p getClassMethod];

结果:

2019-11-05 18:34:19.045761+0800 CateAndExt[53499:774413] 类方法MethodName: howOldAreYou 🧐 retName: q 🧐 argCount: 2 🧐 methodImp: 0x10204e3b0
2019-11-05 18:34:19.045947+0800 CateAndExt[53499:774413] 类方法MethodName: howOldAreYou 🧐 retName: q 🧐 argCount: 2 🧐 methodImp: 0x10204e5c0
2019-11-05 18:34:19.047406+0800 CateAndExt[53499:774413] 类方法MethodName: howOldAreYouoneMore 🧐 retName: q 🧐 argCount: 2 🧐 methodImp: 0x10204e3d0
2019-11-05 18:34:19.047614+0800 CateAndExt[53499:774413] 类方法MethodName: address 🧐 retName: @ 🧐 argCount: 2 🧐 methodImp: 0x10204e660
2019-11-05 18:34:19.048768+0800 CateAndExt[53499:774413] 类方法MethodName: setAddress: 🧐 retName: v 🧐 argCount: 3 🧐 methodImp: 0x10204e680

由此联想,是否可以通过 Class metaclass = object_getClass([self class]); 获取类属性。

/// 获取类属性
-(void)getClassIvar {
    unsigned int count;
    Class metaclass = object_getClass([self class]);
    objc_property_t *properties = class_copyPropertyList(metaclass, &count);
    for (unsigned int i = 0; i < count; i++) {
        objc_property_t oneP = properties[i];
        // 获取名字和类型
        const char *memberName = property_getName(oneP);
        // 一次打印属性名称和属性类型
        NSLog(@"类属性信息  %s", memberName);
    }
    free(properties);
}

打印结果:

2019-11-05 18:34:19.050084+0800 CateAndExt[53499:774413] 类属性信息  address

以下引用自 class_copyPropertyList与class_copyIvarList区别:

class_copyPropertyList返回的仅仅是对象类的属性(@property申明的属性),而class_copyIvarList返回类的所有属性和变量(包括在@interface大括号中声明的变量)。

原作者是写的测试函数,我在项目中直接测试,和文中结果不太一样。

Person 添加属性

@interface Person : NSObject<HumanInfo>

{
    NSInteger height;
    NSString *workName;
}

/// 类属性
@property(nonatomic, strong, class) NSString *address;
/// 实例属性
@property(nonatomic, strong) NSString *phone;
@property(nonatomic, assign) id <HumanInfo> delegate;

添加一个新的属性打印方法:

/// 另一个获取属性的方法
-(void)getProperties {
    unsigned int count;
    unsigned int pCount;
    Ivar *vars = class_copyIvarList([self class], &count);
    objc_property_t *properties = class_copyPropertyList([self class], &pCount);
    
    for (unsigned int i = 0; i < count; i++) {
        Ivar oneVar = vars[i];
        // 获取名字和类型
        const char *memberName = ivar_getName(oneVar);
        // 一次打印属性名称和属性类型
        NSLog(@"class_copyIvarList 属性  %s", memberName);
    }
    
    for (unsigned int i = 0; i < pCount; i++) {
        objc_property_t oneP = properties[i];
        // 获取名字和类型
        const char *memberName = property_getName(oneP);
        // 一次打印属性名称和属性类型
        NSLog(@"class_copyPropertyList 属性  %s", memberName);
    }
    
    free(vars);
    free(properties);
}

打印结果:

2019-11-05 18:51:52.544776+0800 CateAndExt[54040:784325] class_copyIvarList 属性  height
2019-11-05 18:51:52.545474+0800 CateAndExt[54040:784325] class_copyIvarList 属性  workName
2019-11-05 18:51:52.546048+0800 CateAndExt[54040:784325] class_copyIvarList 属性  phone
2019-11-05 18:51:52.546394+0800 CateAndExt[54040:784325] class_copyIvarList 属性  _delegate
2019-11-05 18:51:52.546694+0800 CateAndExt[54040:784325] class_copyPropertyList 属性  nickName
2019-11-05 18:51:52.547136+0800 CateAndExt[54040:784325] class_copyPropertyList 属性  phone
2019-11-05 18:51:52.547538+0800 CateAndExt[54040:784325] class_copyPropertyList 属性  delegate
2019-11-05 18:51:52.547878+0800 CateAndExt[54040:784325] class_copyPropertyList 属性  hash
2019-11-05 18:51:52.548443+0800 CateAndExt[54040:784325] class_copyPropertyList 属性  superclass
2019-11-05 18:51:52.549367+0800 CateAndExt[54040:784325] class_copyPropertyList 属性  description
2019-11-05 18:51:52.549822+0800 CateAndExt[54040:784325] class_copyPropertyList 属性  debugDescription

后记

虽然这些仅仅是 runtime 的冰山一角,但是关于方法、属性的查询、打印应该做了比较详细的分析,后续关于此类的问题,也会在这里补全。

参考文章:

class_copyPropertyList与class_copyIvarList区别

iOS 获取类的属性,实例方法,类方法等

上一篇 下一篇

猜你喜欢

热点阅读