本打算这个系列结束的,但是由于疫情影响在家的这几天有空闲时间,想着把这块也整理下,今天这篇文章打算带大家熟悉下Runtime API.其这部分和Runtime重要的数据结构紧密相关,通过objc/runtime.h无非就是获得类,对象,Method,SEL,IMP,Ivar,Property,Protocal相关类以及对应的属性。这块还是蛮有规律的,最早接触Runtime还是蛮抗拒的,后面才慢慢觉得它的强大。废话不多说这篇博客主要分两大部分,一部分介绍上面提到的这些数据结构相关的数据,另一部分介绍十分重要的类型编码和属性编码。搞定这两部分估计大家在使用上就会游刃有余了,但是还是建立在理解Runtime各个环节的基础上。
这里推荐大家用Cooci可编译苹果官方源码objc来写demo这样可以比较方便得调试整个过程,这也是最近才发现的一个很棒的开源库,之前研究底层代码的时候要是早点发现这个就不会那么纠结了。好东西不私藏。https://github.com/LGCooci/objc4_debug:
在开始之前建议大家通读下我的《iOS Runtime源码分析系列》的博客,当然在这篇博客介绍的时候还会简要提一下相关内容。好了我们开始我们的API之旅:
首先是类相关的,这些都以****class_****开头:
类相关:
OBJC_EXPORT const char * _Nonnull class_getName(Class _Nullable cls)
OBJC_EXPORT Class _Nullable object_getClass(id _Nullable obj)
OBJC_EXPORT Class _Nullable class_getSuperclass(Class _Nullable cls)
获取实例变量的信息 1.实例变量是指变量不是属性.每个属性都有对应的实例变量,比如下面例子中包含属性name,那么它对应的实例变量为_name 2.这个方法可以获取属性的变量,也可以获取私有变量 3.如果没有找到对应的实例变量,就会返回nil 4.返回的列表不包含父类的实例变量 Ivar class_getClassVariable(Class cls, const char *name)
获取属性: 这个方法只能获得属性,不能获得变量 objc_property_t class_getProperty(Class cls, const char *name)
获取实例方法信息 OBJC_EXPORT Method _Nullable class_getInstanceMethod(Class _Nullable cls, SEL _Nonnull name);
获取类方法信息 OBJC_EXPORT Method _Nullable class_getClassMethod(Class _Nullable cls, SEL _Nonnull name)
获取SEL对应的实现体,也就是IMP IMP class_getMethodImplementation(Class cls, SEL name)
|
通过这部分接口可以获得类名,父类,实例变量,属性,实例方法,类方法和IMP,注意这里只是获取单个的并不是全部的数据。如果要获得全部的就需要class_copyXXXX了。
获取数据列表类:
OBJC_EXPORT Ivar _Nonnull * _Nullable class_copyIvarList(Class _Nullable cls, unsigned int * _Nullable outCount);
OBJC_EXPORT Method _Nonnull * _Nullable class_copyMethodList(Class _Nullable cls, unsigned int * _Nullable outCount)
OBJC_EXPORT Protocol * __unsafe_unretained _Nonnull * _Nullable class_copyProtocolList(Class _Nullable cls, unsigned int * _Nullable outCount)
OBJC_EXPORT objc_property_t _Nonnull * _Nullable class_copyPropertyList(Class _Nullable cls, unsigned int * _Nullable outCount)
|
这些方法都需要注意最后调用free释放掉存放copy来的空间
添加方法 OBJC_EXPORT BOOL class_addMethod(Class _Nullable cls, SEL _Nonnull name, IMP _Nonnull imp, const char * _Nullable types)
添加实例变量
OBJC_EXPORT BOOL class_addIvar(Class _Nullable cls, const char * _Nonnull name, size_t size, uint8_t alignment, const char * _Nullable types)
添加协议 OBJC_EXPORT BOOL class_addProtocol(Class _Nullable cls, Protocol * _Nonnull protocol)
OBJC_EXPORT BOOL class_addProperty(Class _Nullable cls, const char * _Nonnull name, const objc_property_attribute_t * _Nullable attributes, unsigned int attributeCount)
|
替换方法 OBJC_EXPORT IMP _Nullable class_replaceMethod(Class _Nullable cls, SEL _Nonnull name, IMP _Nonnull imp, const char * _Nullable types)
替换属性 OBJC_EXPORT void class_replaceProperty(Class _Nullable cls, const char * _Nonnull name, const objc_property_attribute_t * _Nullable attributes, unsigned int attributeCount)
|
创建实例 OBJC_EXPORT id _Nullable class_createInstance(Class _Nullable cls, size_t extraBytes)
遵循协议 OBJC_EXPORT BOOL class_conformsToProtocol(Class _Nullable cls, Protocol * _Nullable protocol)
判断是否响应某个SEL OBJC_EXPORT BOOL class_respondsToSelector(Class _Nullable cls, SEL _Nonnull sel)
|
下面是简要的实例:
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface IDLTestObject : NSObject { NSString *privateInstanceVar; }
@property(nonatomic, strong, readwrite) NSString *name;
@property(nonatomic, assign, readwrite) NSInteger age;
- (void)testInstanceMethod;
@end
NS_ASSUME_NONNULL_END
|
#import "IDLTestObject.h"
@implementation IDLTestObject
- (void)testInstanceMethod { NSLog(@"run testInstanceMethod"); }
+ (void)testClassMethod { NSLog(@"testclassMethod"); }
@end
|
const char *className = class_getName([IDLTestObject class]); NSLog(@"className : %@",[NSString stringWithUTF8String:className]);
Class superClass = class_getSuperclass([IDLTestObject class]); NSLog(@"superClass : %@", NSStringFromClass(superClass));
size_t instanceSize = class_getInstanceSize([IDLTestObject class]); NSLog(@"instanceSize = %zu",instanceSize);
Ivar nameVar = class_getInstanceVariable([IDLTestObject class], [@"_name" UTF8String]); NSLog(@"InstanceVariable :%@",[NSString stringWithUTF8String:ivar_getName(nameVar)]); NSLog(@"Instance Variable TypeEncoding:%@",[NSString stringWithUTF8String:ivar_getTypeEncoding(nameVar)]); NSLog(@"Instance Variable Offset:%td",ivar_getOffset(nameVar));
unsigned int outCount = 0; Ivar *varList = class_copyIvarList([IDLTestObject class], &outCount); for (unsigned int index = 0 ;index < outCount; index++) { NSLog(@"var %u = %@",index,[NSString stringWithUTF8String:ivar_getName(varList[index])]); } free(varList);
objc_property_t property = class_getProperty([IDLTestObject class], [@"age" UTF8String]); NSLog(@"Instance Property Name :%@",[NSString stringWithUTF8String:property_getName(property)]); NSLog(@"Instance Property Attribute:%@",[NSString stringWithUTF8String:property_getAttributes(property)]);
Method method = class_getInstanceMethod([IDLTestObject class], @selector(testInstanceMethod)); NSLog(@"get InstanceMethod %@",NSStringFromSelector(method_getName(method)));
Method *methodList = class_copyMethodList([IDLTestObject class], &outCount); for (unsigned int index = 0; index < outCount; index++) { NSLog(@"method %u = %@",index,NSStringFromSelector(method_getName(methodList[index]))); } free(methodList);
Method classMethod = class_getClassMethod([IDLTestObject class], @selector(testClassMethod)); NSLog(@"get classMethod %@",NSStringFromSelector(method_getName(classMethod)));
IMP testMethod = class_getMethodImplementation([IDLTestObject class], @selector(testInstanceMethod)); testMethod();
class_addMethod([IDLTestObject class], @selector(addedMethod), (IMP)addedMethodIMP, "v@:");
[[IDLTestObject new] performSelector:@selector(addedMethod)];
objc_property_attribute_t type = { "T", "@\"NSString\"" }; objc_property_attribute_t ownership = { "C", "" }; objc_property_attribute_t nonatomic = { "N", "" }; objc_property_attribute_t ivar = { "V", "_addedMethod" }; objc_property_attribute_t attributs[] = { type, ownership,nonatomic, ivar }; class_addProperty([IDLTestObject class], "addedMethod", attributs, 4);
unsigned int count; objc_property_t *propertyList = class_copyPropertyList([IDLTestObject class], &count); for (unsigned int i = 0; i< count; i++) { const char *name = property_getName(propertyList[i]); NSLog(@"属性%@ 属性信息%@",[NSString stringWithUTF8String:name], [NSString stringWithUTF8String:property_getAttributes(propertyList[i])]); } free(propertyList);
class_replaceMethod([IDLTestObject class], @selector(testInstanceMethod), (IMP)addedMethodIMP, "v@:"); [[[IDLTestObject alloc] init] testInstanceMethod];
IDLTestObject *testClass = class_createInstance([IDLTestObject class], 0); [testClass performSelector:@selector(testInstanceMethod)];
Class DynamicClass = objc_allocateClassPair([NSObject class], "DynamicClass", 0); class_addIvar(DynamicClass, "_attribute0", sizeof(NSString *), log(sizeof(NSString *)), "i"); Ivar ivar = class_getInstanceVariable(DynamicClass, "_attribute0"); NSLog(@"DynamicClass var:%@",[NSString stringWithUTF8String:ivar_getName(ivar)]); objc_registerClassPair(DynamicClass);
|
输出结果如下:
2020-02-08 19:05:51.294372+0800 objc-debug[43702:387877] className : IDLTestObject 2020-02-08 19:05:51.296220+0800 objc-debug[43702:387877] superClass : NSObject 2020-02-08 19:05:51.297028+0800 objc-debug[43702:387877] instanceSize = 32 2020-02-08 19:05:51.299444+0800 objc-debug[43702:387877] InstanceVariable :_name 2020-02-08 19:05:51.306411+0800 objc-debug[43702:387877] Instance Variable TypeEncoding:@"NSString" 2020-02-08 19:05:51.306766+0800 objc-debug[43702:387877] Instance Variable Offset:16 2020-02-08 19:05:51.307020+0800 objc-debug[43702:387877] var 0 = privateInstanceVar 2020-02-08 19:05:51.307322+0800 objc-debug[43702:387877] var 1 = _name 2020-02-08 19:05:51.307559+0800 objc-debug[43702:387877] var 2 = _age 2020-02-08 19:05:51.308250+0800 objc-debug[43702:387877] Instance Property Name :age 2020-02-08 19:05:51.308553+0800 objc-debug[43702:387877] Instance Property Attribute:Tq,N,V_age 2020-02-08 19:05:51.308878+0800 objc-debug[43702:387877] get InstanceMethod testInstanceMethod 2020-02-08 19:05:51.309351+0800 objc-debug[43702:387877] method 0 = testInstanceMethod 2020-02-08 19:05:51.309624+0800 objc-debug[43702:387877] method 1 = .cxx_destruct 2020-02-08 19:05:51.334783+0800 objc-debug[43702:387877] method 2 = name 2020-02-08 19:05:51.335325+0800 objc-debug[43702:387877] method 3 = setName: 2020-02-08 19:05:51.335856+0800 objc-debug[43702:387877] method 4 = age 2020-02-08 19:05:51.336313+0800 objc-debug[43702:387877] method 5 = setAge: 2020-02-08 19:05:51.336745+0800 objc-debug[43702:387877] get classMethod testClassMethod 2020-02-08 19:05:51.336909+0800 objc-debug[43702:387877] run testInstanceMethod 2020-02-08 19:05:51.340473+0800 objc-debug[43702:387877] run addedMethodIMP 2020-02-08 19:05:51.351977+0800 objc-debug[43702:387877] 属性addedMethod 属性信息T@"NSString",C,N,V_addedMethod 2020-02-08 19:05:51.352879+0800 objc-debug[43702:387877] 属性name 属性信息T@"NSString",&,N,V_name 2020-02-08 19:05:51.353429+0800 objc-debug[43702:387877] 属性age 属性信息Tq,N,V_age 2020-02-08 19:05:51.353906+0800 objc-debug[43702:387877] run addedMethodIMP 2020-02-08 19:05:51.354370+0800 objc-debug[43702:387877] run addedMethodIMP 2020-02-08 19:05:51.354378+0800 objc-debug[71958:585781] DynamicClass var:_attribute0
|
Ivar实例变量相关:
OBJC_EXPORT const char * _Nullable ivar_getName(Ivar _Nonnull v)
OBJC_EXPORT const char * _Nullable ivar_getTypeEncoding(Ivar _Nonnull v)
OBJC_EXPORT ptrdiff_t ivar_getOffset(Ivar _Nonnull v)
|
上面的三个方法基本上对应于IVar的数据结构的各个字段,后面会单独介绍TypeEncoding和属性编码。
Property属性相关:
OBJC_EXPORT const char * _Nonnull property_getName(objc_property_t _Nonnull property)
OBJC_EXPORT const char * _Nullable property_getAttributes(objc_property_t _Nonnull property)
OBJC_EXPORT objc_property_attribute_t * _Nullable property_copyAttributeList(objc_property_t _Nonnull property, unsigned int * _Nullable outCount)
OBJC_EXPORT char * _Nullable property_copyAttributeValue(objc_property_t _Nonnull property, const char * _Nonnull attributeName)
|
Method方法相关:
OBJC_EXPORT SEL _Nonnull method_getName(Method _Nonnull m)
//获得方法的实现 OBJC_EXPORT IMP _Nonnull method_getImplementation(Method _Nonnull m) OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);
OBJC_EXPORT const char * _Nullable method_getTypeEncoding(Method _Nonnull m)
//获得参数数量 OBJC_EXPORT unsigned int method_getNumberOfArguments(Method _Nonnull m)
//获得返回类型 OBJC_EXPORT char * _Nonnull method_copyReturnType(Method _Nonnull m)
//获得参数类型 OBJC_EXPORT char * _Nullable method_copyArgumentType(Method _Nonnull m, unsigned int index)
//获得返回类型,指定存储空间 OBJC_EXPORT void method_getReturnType(Method _Nonnull m, char * _Nonnull dst, size_t dst_len)
//获得参数类型,指定存储空间 OBJC_EXPORT void method_getArgumentType(Method _Nonnull m, unsigned int index, char * _Nullable dst, size_t dst_len)
//获得方法描述 OBJC_EXPORT struct objc_method_description * _Nonnull method_getDescription(Method _Nonnull m)
设置方法实现 OBJC_EXPORT IMP _Nonnull method_setImplementation(Method _Nonnull m, IMP _Nonnull imp)
//交换两个方法的实现 OBJC_EXPORT void method_exchangeImplementations(Method _Nonnull m1, Method _Nonnull m2)
|
之前博客已经介绍过,方法主要有方法名SEL,包含方法参数,返回值类型的编码,以及实现体IMP.这里最重要的是类型编码在后面统一介绍。这里还增加了可以分开获取方法参数和返回值类型编码的方法,以及方法参数数量。
SEL相关:
OBJC_EXPORT const char * _Nonnull sel_getName(SEL _Nonnull sel)
OBJC_EXPORT SEL _Nonnull sel_registerName(const char * _Nonnull str)
OBJC_EXPORT BOOL sel_isEqual(SEL _Nonnull lhs, SEL _Nonnull rhs)
|
协议Protocal相关:
OBJC_EXPORT Protocol * _Nullable objc_getProtocol(const char * _Nonnull name);
OBJC_EXPORT Protocol * __unsafe_unretained _Nonnull * _Nullable objc_copyProtocolList(unsigned int * _Nullable outCount);
OBJC_EXPORT BOOL protocol_conformsToProtocol(Protocol * _Nullable proto, Protocol * _Nullable other)
OBJC_EXPORT BOOL protocol_isEqual(Protocol * _Nullable proto, Protocol * _Nullable other)
OBJC_EXPORT const char * _Nonnull protocol_getName(Protocol * _Nonnull proto)
OBJC_EXPORT struct objc_method_description protocol_getMethodDescription(Protocol * _Nonnull proto, SEL _Nonnull aSel, BOOL isRequiredMethod, BOOL isInstanceMethod)
OBJC_EXPORT struct objc_method_description * _Nullable protocol_copyMethodDescriptionList(Protocol * _Nonnull proto, BOOL isRequiredMethod, BOOL isInstanceMethod, unsigned int * _Nullable outCount)
OBJC_EXPORT objc_property_t _Nullable protocol_getProperty(Protocol * _Nonnull proto, const char * _Nonnull name, BOOL isRequiredProperty, BOOL isInstanceProperty)
OBJC_EXPORT objc_property_t _Nonnull * _Nullable protocol_copyPropertyList(Protocol * _Nonnull proto, unsigned int * _Nullable outCount)
OBJC_EXPORT objc_property_t _Nonnull * _Nullable protocol_copyPropertyList2(Protocol * _Nonnull proto, unsigned int * _Nullable outCount, BOOL isRequiredProperty, BOOL isInstanceProperty)
OBJC_EXPORT Protocol * __unsafe_unretained _Nonnull * _Nullable protocol_copyProtocolList(Protocol * _Nonnull proto, unsigned int * _Nullable outCount)
|
动态创建协议
OBJC_EXPORT Protocol * _Nullable objc_allocateProtocol(const char * _Nonnull name)
OBJC_EXPORT void objc_registerProtocol(Protocol * _Nonnull proto) OBJC_AVAILABLE(10.7, 4.3, 9.0, 1.0, 2.0);
OBJC_EXPORT void protocol_addMethodDescription(Protocol * _Nonnull proto, SEL _Nonnull name, const char * _Nullable types, BOOL isRequiredMethod, BOOL isInstanceMethod)
OBJC_EXPORT void protocol_addProtocol(Protocol * _Nonnull proto, Protocol * _Nonnull addition) OBJC_AVAILABLE(10.7, 4.3, 9.0, 1.0, 2.0);
给协议添加属性 OBJC_EXPORT void protocol_addProperty(Protocol * _Nonnull proto, const char * _Nonnull name, const objc_property_attribute_t * _Nullable attributes, unsigned int attributeCount, BOOL isRequiredProperty, BOOL isInstanceProperty)
|
类型编码与属性编码
编译器将每个方法的返回值和参数类型编码为一个字符串,并将其与方法的selector关联在一起。同时每个属性的类型也进行了编码,下面是这两部分的官方文档,供大家查阅使用。
我们给出一个例子,下面的例子会将方法的返回值以及参数编码打印出来:
这个方法如下所示:
+ (IDLReturnType *)testTypeEncoding:(NSString *)strValue intValue:(NSInteger)intValue { return [IDLReturnType new]; }
|
Method testMethods = class_getClassMethod([IDLTestObject class], @selector(testTypeEncoding:intValue:)); NSLog(@"Type Encoding : %@",[NSString stringWithUTF8String:method_getTypeEncoding(testMethods)]); char *returnType = method_copyReturnType(testMethods); NSLog(@"Return Type: %@",[NSString stringWithUTF8String:returnType]); free(returnType);
NSInteger numberOfArguments = method_getNumberOfArguments(testMethods); char *argument = NULL; for (unsigned int index = 0; index < numberOfArguments; index++) { argument = method_copyArgumentType(testMethods, index); NSLog(@"Index %d Return Type: %@", index,[NSString stringWithUTF8String:argument]); } free(argument);
|
输出内容为:
2020-02-09 10:21:22.231254+0800 objc-debug[84523:1305673] Type Encoding : @32@0:8@16q24 2020-02-09 10:21:22.231343+0800 objc-debug[84523:1305673] Return Type: @ 2020-02-09 10:21:22.231425+0800 objc-debug[84523:1305673] Index 0 Return Type: @ 2020-02-09 10:21:22.231503+0800 objc-debug[84523:1305673] Index 1 Return Type: : 2020-02-09 10:21:22.231579+0800 objc-debug[84523:1305673] Index 2 Return Type: @ 2020-02-09 10:21:22.231653+0800 objc-debug[84523:1305673] Index 3 Return Type: q
|
我们看****@32@0:8@16q24****这实际上就是方法的类型编码。我们先看下类型编码表:
这里每个部分都包含两位,前面一个表示编码,后面表示这个数据对应的偏移量,返回的类型数据会将返回值放在最后面,所以****@32@0:8@16q24****对应部分的意义如下:
@32 返回值编码为@,也就是id类型,所处的偏移量为32。也就是q24的后面 @0 第一个参数的编码为@,也就是id类型,所处的偏移量为0,由于第一个参数恒为方法的接收者,所以是id类型。 :8 第二个参数的编码为: 也就是SEL类型,所处的偏移量为8,由于第二个参数恒为_cmd所以是SEL类型。 @16 第三个参数的编码为@ 也就是id类型,所处的偏移量为16,上面第三个参数为NSString* q24 第四个参数的编码为q 也就是long long类型,由于用的是64位机器所以对得上。
|
Runtime为了我们方便,为我们提供了@encode编译器指令来获取它,当给定一个类型时,@encode返回这个类型的字符串编码,如下所示:
NSLog(@"char : %s, %lu", @encode(char), sizeof(char)); NSLog(@"short : %s, %lu", @encode(short), sizeof(short)); NSLog(@"int : %s, %lu", @encode(int), sizeof(int)); NSLog(@"long : %s, %lu", @encode(long), sizeof(long)); NSLog(@"long long: %s, %lu", @encode(long long), sizeof(long long)); NSLog(@"float : %s, %lu", @encode(float), sizeof(float)); NSLog(@"double : %s, %lu", @encode(double), sizeof(double)); NSLog(@"NSInteger: %s, %lu", @encode(NSInteger), sizeof(NSInteger)); NSLog(@"CGFloat : %s, %lu", @encode(CGFloat), sizeof(CGFloat)); NSLog(@"int32_t : %s, %lu", @encode(int32_t), sizeof(int32_t)); NSLog(@"int64_t : %s, %lu", @encode(int64_t), sizeof(int64_t))
|
2020-02-09 11:04:28.116484+0800 objc-debug[91476:1363542] char : c, 1 2020-02-09 11:04:28.116991+0800 objc-debug[91476:1363542] short : s, 2 2020-02-09 11:04:28.117745+0800 objc-debug[91476:1363542] int : i, 4 2020-02-09 11:04:28.118195+0800 objc-debug[91476:1363542] long : q, 8 2020-02-09 11:04:28.118943+0800 objc-debug[91476:1363542] long long: q, 8 2020-02-09 11:04:28.121076+0800 objc-debug[91476:1363542] float : f, 4 2020-02-09 11:04:28.123510+0800 objc-debug[91476:1363542] double : d, 8 2020-02-09 11:04:28.124016+0800 objc-debug[91476:1363542] NSInteger: q, 8 2020-02-09 11:04:28.124132+0800 objc-debug[91476:1363542] CGFloat : d, 8 2020-02-09 11:04:28.124211+0800 objc-debug[91476:1363542] int32_t : i, 4 2020-02-09 11:04:28.124283+0800 objc-debug[91476:1363542] int64_t : q, 8
|
一般我们会使用:
method_copyArgumentType(testMethods, index);
|
这种方式,避免涉及到偏移量等数据,直接获取参数类型。
前面提到一个例子:
objc_property_t property = class_getProperty([IDLTestObject class], [@"age" UTF8String]); NSLog(@"Instance Property Name :%@",[NSString stringWithUTF8String:property_getName(property)]); NSLog(@"Instance Property Attribute:%@",[NSString stringWithUTF8String:property_getAttributes(property)]);
|
age属性的声明如下所示:
@property(nonatomic, copy, readonly,getter=ageValue,setter=setAageValue:) NSString *age;
|
输出结果为:
2020-02-09 11:23:38.356033+0800 objc-debug[94653:1391447] Instance Property Attribute:T@"NSString",R,C,N,GageValue,SsetAageValue:,V_age
|
我们对照上表给大家分析:
属性编码是通过逗号进行分隔开的,T@”NSString”,R,C,N,GageValue,SsetAageValue:,V_age 表示的意义如下:
* T@"NSString" 属性类型为NSString * R 属性类型为ReadOnly类型 * C 属性存储属性为Copy * N 属性为nonatomic * GageValue 属性指定getter方法为ageValue * SsetAageValue: 属性指定setter方法为setAageValue: * V_age 属性对应的实例变量为_age
|
对于类型编码和属性编码大致就这些。大家可以通过下面的表格熟悉下属性编码: