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

前言

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

正文

分条列举说明。

测试类 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
  • 输入 protocol_ ,根据快捷提示,可以看到很多为协议专门准备的方法,包括添加属性、获取属性列表、方法列表等。
  • __unsafe_unretained Protocol **protocolList 为什么要这么定义,通过查看 class_copyProtocolList 方法即可:
    OBJC_EXPORT Protocol * __unsafe_unretained _Nonnull * _Nullable 
    class_copyProtocolList(Class _Nullable cls, unsigned int * _Nullable outCount)
    OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);
    

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

/// 属性的获取。并且动态修改属性信息
-(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
  • 不能获取到类的类属性。
  • 测试时建了 Person 的分类并添加属性,不能获取到分类中的属性信息。
    #import "Person.h"
    
    NS_ASSUME_NONNULL_BEGIN
    
    @interface Person (Man)
    @property(nonatomic, strong) NSString *nickName;
    
    @end
    
    NS_ASSUME_NONNULL_END
    
  • 其他暂时没问题。

获取实例方法列表

/// 获取实例方法
/// 获取实例方法
-(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

  • 该方法不能获取到类方法。
  • 方法列表包括属性的 settergetter 方法。
  • 尽管该方法写在 Person 中,但是获取到了分类中的方法列表。
  • 方法返回类型 @v 分表代表什么意思,为什么参数个数会比看到的多了两个,参考文章 runtime Method精讲

获取类方法

/// 获取类方法
-(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
  • 仅仅能获取类方法,获取不到实例方法。
  • 类方法、以及类属性,都能获取到。
  • 分类中的类方法也能获取到。分类写了和主类完全一致的方法时,两个方法同时存在。调用重复的方法时,优先调用子类方法。当然,可以通过runtime强制调用主类方法,这里不在讨论。
  • 通过方法指针可确定,分配和主类重复的方法时实质上是两个不同的方法。

由此联想,是否可以通过 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_copyIvarList 不能获取到类方法,但是使用 class_copyPropertyList 则可以。引发另一个问题:class_copyIvarListclass_copyPropertyList 有什么区别。

以下引用自 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
  • class_copyIvarList 只能找到当前类的所有属性,包括 @property 修饰的以及大括号修饰的变量。
  • class_copyPropertyList 对于当前类只能打印 @property 修饰的属性,但是,还能找到父类、分类中的属性。
  • 饰的以及大括号修饰的变量。即当前类所有的属性。
  • class_copyPropertyList 能找到类属性。

后记

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

参考文章:

class_copyPropertyList与class_copyIvarList区别

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

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容