前言
这篇文章专注打印一个类的方法,属性等信息。以及实践过程中遇到的问题的思考和探索。
正文
分条列举说明。
测试类 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
- 该方法不能获取到类方法。
- 方法列表包括属性的
setter
和getter
方法。 - 尽管该方法写在
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_copyIvarList
和class_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
的冰山一角,但是关于方法、属性的查询、打印应该做了比较详细的分析,后续关于此类的问题,也会在这里补全。
参考文章: