runtime_01

首先感谢祖国,可以无忧无虑的码代码 ~


If I have seen further, it is by standing on the shoudlers of giants.


想这样一个场景

某天亲爱的产品突然有个需求,统计app中所有UIButton的点击次数,由于项目比较庞大,并且使用了超多的UIButton来交互,那么怎么实现呐?
当然我们可以寻找代码里面所有的UIButton的点击事件,然后创建一个全局的计数器来进行统计,这个做法可以解决这个问题,但是之后的维护呐?如果寻找的时候发生了遗漏怎么办?新加入的同事不知道这种做法怎么办?显然这不能算是一个好的做法。

当然我们也可以创建一个UIButton的子类,然后重写它的点击事件,这种策略虽然好一点,但是我们需要寻找代码中的所有UIButton,且改变其父类。但是如果寻找的时候发生遗漏怎么办?如果新同事不知道这样的要求怎么办?如果在代码中使用了UIButton的其他子类的话,我们还要再去为这些子类创建新的子类。显然这也不能算是一个好的做法

其实这个时候我们的runtime就派上了用处
如果各位看官没有想到怎么使用runtime的话,且容笔者留个悬念,后续揭晓 ~

o(* ̄3 ̄)o


Objective-C是一门动态语言,这意味着Objective-C不仅仅需要一个编译器,还需要一个运行时系统来执行编译后的代码,运行时系统让所有的工作正常运行,runtime其实是一个runtime库,绝大部分使用C和汇编码的,正是由于这个库,使得Objective-C具备了面向对象的能力。


runtime 源码下载 o(* ̄3 ̄)o


1. 类与基础数据结构

1.1 Class

OC类是由Class类型表示的,实际上是一个指向objc_class结构体(objc/objc.h)的指针

#if !OBJC_TYPES_DEFINED
/// An opaque type that represents an Objective-C class.
typedef struct objc_class *Class;

而 结构体objc_classobjc/runtime.h)的定义为

struct objc_class {
    Class _Nonnull isa  OBJC_ISA_AVAILABILITY;

#if !__OBJC2__
    Class _Nullable super_class                              OBJC2_UNAVAILABLE;
    const char * _Nonnull name                               OBJC2_UNAVAILABLE;
    long version                                             OBJC2_UNAVAILABLE;
    long info                                                OBJC2_UNAVAILABLE;
    long instance_size                                       OBJC2_UNAVAILABLE;
    struct objc_ivar_list * _Nullable ivars                  OBJC2_UNAVAILABLE;
    struct objc_method_list * _Nullable * _Nullable methodLists                    OBJC2_UNAVAILABLE;
    struct objc_cache * _Nonnull cache                       OBJC2_UNAVAILABLE;
    struct objc_protocol_list * _Nullable protocols          OBJC2_UNAVAILABLE;
#endif

} OBJC2_UNAVAILABLE;
  • isa:在OC中,所有的类本身也是一个对象,这个对象的Class里面有一个指针,指向该类metaClass
  • super_class:指向该类的父类,如果该类已经是顶层根类(NSObjectNSProxy)的话,则为NULL
  • name:类的名字
  • version:使用这个字段提供类的版本信息,默认为0,这对于对象的序列化非常有用,可以识别出 不同 版本类 定义中 实例变量布局 的改变
  • info:提供运行时使用的一些标识,有如下一些位掩码:
    CLS_CLASS (0x1L) 表示该类为普通class,其中包含实例方法和变量
    CLS_META (0x2L) 表示该类为metaclass,其中包含类方法
    CLS_INITIALIZED (0x4L) 表示该类已经被运行时初始化了,这个标识位只被 objc_addClass 所设置
    CLS_POSING (0x8L) 表示该类被pose成其他的类;(poseclassObjC 2.0中被废弃了)
    CLS_MAPPED (0x10L) 为运行时所使用
    CLS_FLUSH_CACHE (0x20L) 为运行时所使用
    CLS_GROW_CACHE (0x40L) 为运行时所使用
    CLS_NEED_BIND (0x80L) 为运行时所使用
    CLS_METHOD_ARRAY (0x100L) 该标志位指示methodLists是指向一个 objc_method_list还是一个包含objc_method_list指针的数组;
  • instance_size:该实例变量的大小,包括从父类继承下来的实例变量
  • ivars:成员变量数组
  • methodList:方法的数组,与info的一些标志位有关,CLS_METHOD_ARRAY标识位决定其指向的是,单个objc_method_list,还是一个objc_method_list指针数组,如果info设置了 CLS_CLASSobjc_method_list存储实例方法,如果设置的是CLS_META则存储类方法
  • cache:用于缓存最近使用到的方法,一个接收者对象接收到消息时候,会依据isa指针去查找可以响应该消息的对象。在实际使用的时候,对象只有部分方法是常用的,这种情况下,如果每次来消息时候都去methodLists中遍历一遍的话,会影响性能,这个时候就用上了cache,每次调用一个方法后,这个方法就会被缓存到cache列表中,下次调用的时候runtime就会优先到cache中查找,如果没有查到的话,才去methodList中查找方法,这样对于经常用到的方法可以提高效率
  • protocols:协议的数组

OC为每个类生成两个objc_class,一个为普通的class,另一个为metaClass。我们可以在运行时创建这两个objc_class的数据结构,然后使用objc_addClass动态地创建新的类

1.2 objc_object 和 id

objc_objectobjc/objc.h)表示一个类的实例结构体

/// Represents an instance of a class.
struct objc_object {
    Class _Nonnull isa  OBJC_ISA_AVAILABILITY;
};

/// A pointer to an instance of a class.
typedef struct objc_object *id;

objc_object只有一个指向其类的指针,当像一个对象发送消息的时候,运行时会依据实例对象的isa指针找到这个实例对象所属的类,运行时会在类方法列表及父类的方法列表中去寻找与消息对应的selector指向的方法,然后执行这个方法

当创建一个特定类的实例变量的时候,分配的内存会包含一个objc_object数据结构,然后是类的实例变量的数据。NSObjectallocallocWithZone:使用函数class_createInstance来创建objc_object数据结构

id类型,是一个objc_object结构体类型的指针,id的存在可以实现类似于C++中泛型的一些操作,有点类似Cvoid *指针类型的作用

1.3 objc_cache
struct objc_cache {
    unsigned int mask /* total = mask + 1 */                 OBJC2_UNAVAILABLE;
    unsigned int occupied                                    OBJC2_UNAVAILABLE;
    Method _Nullable buckets[1]                              OBJC2_UNAVAILABLE;
};

objc_class中的cache,用于缓存用过的方法,这个字段是一个指向objc_cache结构体的指针

  • mask:一个整数,指定分配的buckets的数量,在运行时 查找方法的过程中,使用这个字段来确定 开始线性查找 数组的索引的位置,指向 方法selector的指针 与 该字段做AND位操作(index = mask & selector),可以作为一个简单的hash散列算法
  • occupied:一个整数,指定实际占用的缓存bucket的总数
  • buckets:指向Method数据结构指针的数组,这个数组长度不能超过mask + 1。这个指针也可能是NULL,表示这个buckets没有被占用,被占用的bucket可能是不连续的,这个数组内元素也可能随着时间而增加
1.4 元类 MetaClass

metaClass存储着一个类的所有的类方法,每个类都会有一个单独的metaClass
所有类的自身也是一个对象,可以向这个对象发送消息 即调用类方法
eg:

NSArray *array = [NSArray array];

+ (instancetype)array;

NSArray调用类方法,即发送消息给NSArray
NSArray是一个对象,因此也会有一个objc_object类型的指针,包含一个指向其类的isa指针(参考 1.2),为了调用+ array方法,这个类的isa指针,必须指向一个包含这些类方法的objc_class(参考1.1)的结构体
metaClass是一个类对象的类
当运行时向一个对象发送消息的时候,运行时会在这个对象所属类的方法列表中查找方法
当运行时向一个类对象发送消息的时候,运行时会在这个类的metaClass的方法列表中查找

关于类与metaClass的继承体系如下:

图1.4 类与metaClass的继承体系

我们知道metaClass也是一个类,也可以向其发送消息,那么metaClassisa指针又指向了哪里,为了不让这种结构无限延伸下去,所有的metaClass指向其基类的metaClass作为其所属类,而基类的metaClassisa指针指向其自身,形成闭环。即任何NSObject继承体系下的metaClass都使用NSObjectmetaClass来作为期所属类,基类的metaclass指向其metaclass自身,如上图
对于nsobject的继承体系来讲,其实例方法对于体系中的所有实例、类、metaclass都是有效的;而类方法对与体系内的所有类和metaclass都是有效的
eg:

Class newClass = objc_allocateClassPair([NSString class], "NewClass", 0);
class_addMethod(newClass, @selector(showMetaClass), (IMP)ShowMetaClass, "v@:");
objc_registerClassPair(newClass);

id instance = [[newClass alloc] init];
[instance performSelector:@selector(showMetaClass)];

void ShowMetaClass(id self, SEL _cmd) {
    NSLog(@"this object is --> %p, class is --> %@, superclass is --> %@", self, [self class], [self superclass]);
    Class currentClass = [self class];
    for (int i = 0; i < 5; i++) {
        // i == 0 实例对象 currentClass 的地址和类,实例对象的 isa 指向 当前对象的所属类
        NSLog(@"次数 --> %d, 当前类或者实例所属类 --> %@, isa指针 指向 --> %@ == %p", i, currentClass, object_getClass(currentClass), object_getClass(currentClass));

        currentClass = objc_getMetaClass(object_getClassName(currentClass));
    }
    NSLog(@"NSObject's class is %p", [NSObject class]);
    NSLog(@"NSObject's metaclass is %p", object_getClass(CFBridgingRelease((__bridge void *)[NSObject class])));
    NSLog(@"NSObject's metaclass is %@", objc_getMetaClass(object_getClassName([NSObject class])));
}

// 输出:
this object is --> 0x600000013c80, class is --> NewClass, superclass is --> NSString
次数 --> 0, 当前类或者实例所属类 --> NewClass, isa指针 指向 --> NewClass == 0x60000005ea20
次数 --> 1, 当前类或者实例所属类 --> NewClass, isa指针 指向 --> NSObject == 0x10ac96e38
次数 --> 2, 当前类或者实例所属类 --> NSObject, isa指针 指向 --> NSObject == 0x10ac96e38
次数 --> 3, 当前类或者实例所属类 --> NSObject, isa指针 指向 --> NSObject == 0x10ac96e38
次数 --> 4, 当前类或者实例所属类 --> NSObject, isa指针 指向 --> NSObject == 0x10ac96e38
NSObject's class is 0x10ac96e88
NSObject's metaclass is 0x10ac96e38
NSObject's metaclass is NSObject

这个例子是在运行时,动态的创建一个NSString的子类NewClass,然后为这个类添加一个方法showMetaClass,方法的实现是ShowMetaClass
这里有两个方法:
objc_getClass参数是类名的字符串,返回的是这个类的实例对象
object_getClass参数是id类型,返回的是这个id类型的isa指针所指向的class
我们查看输出:
第一次for循环,currentClass是实例对象,所以其isa指向当前类NewClass
第二次for循环,currentClass变成了类NewClass,所以其isa指向了当前类的元类NewClass
第三次for循环,currentClass变成了元类NewClass,所以其isa指向了元类的父类,即NSObject
之后的循环就是 元类NSObjectisa指向当前元类自身了(可以结合 图1.4)~

2. 类与对象的操作函数

类的操作函数大多以class_为前缀,对象的操作函数大多以objc_或者object_为前缀

2.1 类的操作函数
  • class_getName获取类名
/* Working with Classes */

/** 
 * Returns the name of a class.
 * 
 * @param cls A class object.
 * 
 * @return The name of the class, or the empty string if \e cls is \c Nil.
 */
OBJC_EXPORT const char * _Nonnull
class_getName(Class _Nullable cls) 
    OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);
  • class_getSuperclass获取父类
/** 
 * Returns the superclass of a class.
 * 
 * @param cls A class object.
 * 
 * @return The superclass of the class, or \c Nil if
 *  \e cls is a root class, or \c Nil if \e cls is \c Nil.
 *
 * @note You should usually use \c NSObject's \c superclass method instead of this function.
 */
OBJC_EXPORT Class _Nullable
class_getSuperclass(Class _Nullable cls) 
    OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);
  • class_isMetaClass判断一个类是否是元类
/** 
 * Returns a Boolean value that indicates whether a class object is a metaclass.
 * 
 * @param cls A class object.
 * 
 * @return \c YES if \e cls is a metaclass, \c NO if \e cls is a non-meta class, 
 *  \c NO if \e cls is \c Nil.
 */
OBJC_EXPORT BOOL
class_isMetaClass(Class _Nullable cls) 
    OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);
  • class_getInstanceSize获取实例变量的大小
/** 
 * Returns the size of instances of a class.
 * 
 * @param cls A class object.
 * 
 * @return The size in bytes of instances of the class \e cls, or \c 0 if \e cls is \c Nil.
 */
OBJC_EXPORT size_t
class_getInstanceSize(Class _Nullable cls) 
    OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);

// ---
NSString *str = @"123";
NSLog(@"%zu", class_getInstanceSize([str class]));
// 8
2.2 成员变量和属性

object_class中,所有的成员变量、属性是放在链表ivars中(参考1.1),ivars是一个数组,数组中的每个元素是指向Ivar变量信息的指针

struct objc_ivar {
    char * _Nullable ivar_name                               OBJC2_UNAVAILABLE;
    char * _Nullable ivar_type                               OBJC2_UNAVAILABLE;
    int ivar_offset                                          OBJC2_UNAVAILABLE;
#ifdef __LP64__
    int space                                                OBJC2_UNAVAILABLE;
#endif
}                                                            OBJC2_UNAVAILABLE;
/// An opaque type that represents an instance variable.
typedef struct objc_ivar *Ivar;
2.2.1 成员变量操作函数
  • ivar_getName
    获取成员变量的名字
/** 
 * Returns the name of an instance variable.
 * 
 * @param v The instance variable you want to enquire about.
 * 
 * @return A C string containing the instance variable's name.
 */
OBJC_EXPORT const char * _Nullable
ivar_getName(Ivar _Nonnull v) 
    OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);
  • ivar_getTypeEncoding
    获取成员变量的类型编码
/** 
 * Returns the type string of an instance variable.
 * 
 * @param v The instance variable you want to enquire about.
 * 
 * @return A C string containing the instance variable's type encoding.
 *
 * @note For possible values, see Objective-C Runtime Programming Guide > Type Encodings.
 */
OBJC_EXPORT const char * _Nullable
ivar_getTypeEncoding(Ivar _Nonnull v) 
    OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);
  • ivar_getOffset
    获取成员变量的偏移量
    对于id类型 或 其他对象类型的变量,可以使用object_getIvarobject_setIvar来直接访问成员变量,而不使用偏移量
/** 
 * Returns the offset of an instance variable.
 * 
 * @param v The instance variable you want to enquire about.
 * 
 * @return The offset of \e v.
 * 
 * @note For instance variables of type \c id or other object types, call \c object_getIvar
 *  and \c object_setIvar instead of using this offset to access the instance variable data directly.
 */
OBJC_EXPORT ptrdiff_t
ivar_getOffset(Ivar _Nonnull v) 
    OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);
  • class_getInstanceVariable
    获取类中指定名称的属性的信息,也可以是私有属性,也可以获取父类的
    返回的是一个指向包含指定name的成员变量信息的objc_ivar结构体的指针Ivar
/** 
 * Returns the \c Ivar for a specified instance variable of a given class.
 * 
 * @param cls The class whose instance variable you wish to obtain.
 * @param name The name of the instance variable definition to obtain.
 * 
 * @return A pointer to an \c Ivar data structure containing information about 
 *  the instance variable specified by \e name.
 */
OBJC_EXPORT Ivar _Nullable
class_getInstanceVariable(Class _Nullable cls, const char * _Nonnull name)
    OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0);

eg:
想要获取ffSpringInfo父类中属性@property (nonatomic, copy) NSString *ff_seasonStr;的值

@interface FFSeasonModel ()
@property (nonatomic, copy) NSString *ff_seasonStr; // 初始化为 @"这是父类的私有属性"
@end

// --- 
Ivar strSuperIvar = class_getInstanceVariable([FFSpringModel class], "_ff_seasonStr");
const char * strSuperName = ivar_getName(strSuperIvar);
const char * strSuperEncoding = ivar_getTypeEncoding(strSuperIvar);
ptrdiff_t strSuperOffset = ivar_getOffset(strSuperIvar);
NSLog(@"%s, %s, %td", strSuperName, strSuperEncoding, strSuperOffset);
// _ff_seasonStr, @"NSString", 8

id strSuperValue = object_getIvar(ffSpringInfo, strSuperIvar);
NSLog(@"%@", strSuperValue);
// 这是父类的私有属性

想要获取ffSpringInfo中属性@property (nonatomic, copy) NSString *springStr;的值

FFSpringModel *ffSpringInfo = [FFSpringModel new];
ffSpringInfo.springStr = @"ff_spring";
ffSpringInfo.springBool = false;

Ivar strSpringIvar = class_getInstanceVariable([ffSpringInfo class], "_springStr");
const char * strName = ivar_getName(strSpringIvar);
const char * strEncoding = ivar_getTypeEncoding(strSpringIvar);
ptrdiff_t strOffset = ivar_getOffset(strSpringIvar);
NSLog(@"%s, %s, %td", strName, strEncoding, strOffset);
// _springStr, @"NSString", 16

id strValue = object_getIvar(ffSpringInfo, strSpringIvar);
NSLog(@"%@", strValue);
// ff_spring

Ivar boolSpringIvar = class_getInstanceVariable([ffSpringInfo class], "_springBool");
const char * boolName = ivar_getName(boolSpringIvar);
const char * boolEncoding = ivar_getTypeEncoding(boolSpringIvar);
ptrdiff_t boolOffset = ivar_getOffset(boolSpringIvar);
NSLog(@"%s, %s, %td", boolName, boolEncoding, boolOffset);
// _springBool, B, 8

id boolValue = object_getIvar(ffSpringInfo, boolSpringIvar);
NSLog(@"%@", boolValue);
// (null)

疑问:
这里存在一个问题,object_getIvar方法如果传入值的最终类型是bool的,如果bool的值为true的话 程序会crash,如果为false返回null,这里没想明白

插入知识点:
iOS6之后LLVM编译器引入了property autosynthesis(属性自动合成),编译器会为每个@property添加@synthesize

@synthesize propertyName = _propertyName;

这行代码会创建一个带下划线前缀的变量名,同时使用这个属性生成settergetter属性

  • class_getClassVariable
    获取指定类 的 指定名字的 类变量 的详细信息,一般认为OC不支持类变量,笔者认为这里应该指的是类的元类,而类的元类只有一个变量为isa
/** 
 * Returns the Ivar for a specified class variable of a given class.
 * 
 * @param cls The class definition whose class variable you wish to obtain.
 * @param name The name of the class variable definition to obtain.
 * 
 * @return A pointer to an \c Ivar data structure containing information about the class variable specified by \e name.
 */
OBJC_EXPORT Ivar _Nullable
class_getClassVariable(Class _Nullable cls, const char * _Nonnull name) 
    OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);

eg:

Ivar springMetaClassIvar = class_getClassVariable([FFSpringModel class], "isa");
const char * mcName = ivar_getName(springMetaClassIvar);
const char * mcEncoding = ivar_getTypeEncoding(springMetaClassIvar);
ptrdiff_t mcOffset = ivar_getOffset(springMetaClassIvar);
NSLog(@"%s, %s, %td", mcName, mcEncoding, mcOffset);
// isa, #, 0
  • class_addIvar
    OC不支持向已经存在类中添加实例变量,因此无论是系统提供的类还是自定义的类都无法动态的添加实例变量
    class_addIvar函数用于在运行时创建一个类,并且向类中添加实例变量
    这个函数只能在objc_allocateClassPairobjc_registerClassPair之间调用
    向一个元类中添加变量也不允许
/** 
 * Adds a new instance variable to a class.
 * 
 * @return YES if the instance variable was added successfully, otherwise NO 
 *         (for example, the class already contains an instance variable with that name).
 *
 * @note This function may only be called after objc_allocateClassPair and before objc_registerClassPair. 
 *       Adding an instance variable to an existing class is not supported.
 * @note The class must not be a metaclass. Adding an instance variable to a metaclass is not supported.
 * @note The instance variable's minimum alignment in bytes is 1<<align. The minimum alignment of an instance 
 *       variable depends on the ivar's type and the machine architecture. 
 *       For variables of any pointer type, pass log2(sizeof(pointer_type)).
 */
OBJC_EXPORT BOOL
class_addIvar(Class _Nullable cls, const char * _Nonnull name, size_t size, 
              uint8_t alignment, const char * _Nullable types) 
    OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);

eg:

Class FFRuntimeClass = objc_allocateClassPair([FFSeasonModel class], "FFRuntimeModel", 0);
BOOL isSucc = class_addIvar(FFRuntimeClass, "ff_runtime_str", sizeof(NSString *), log2(sizeof(NSString *)), @encode(NSString *));
if (isSucc) {
    NSLog(@"添加成功");
}
objc_registerClassPair(FFRuntimeClass);

id runtimeClass = [[FFRuntimeClass alloc] init];
[runtimeClass setValue:@"这是 ff_runtime_str 的值" forKey:@"ff_runtime_str"];
id value = object_getIvar(runtimeClass, class_getInstanceVariable([runtimeClass class], "ff_runtime_str"));
NSLog(@"%@", value);
// 添加成功
// 这是 ff_runtime_str 的值

使用KVO给创建的变量赋值,使用object_getIvar来取值

  • class_copyIvarList
    返回一个纸箱成员变量信息的数组,数组中的每个元素是指向该成员变量信息的objc_ivar结构体的指针,outCount指针返回数组的大小
/** 
 * Describes the instance variables declared by a class.
 * 
 * @param cls The class to inspect.
 * @param outCount On return, contains the length of the returned array. 
 *  If outCount is NULL, the length is not returned.
 * 
 * @return An array of pointers of type Ivar describing the instance variables declared by the class. 
 *  Any instance variables declared by superclasses are not included. The array contains *outCount 
 *  pointers followed by a NULL terminator. You must free the array with free().
 * 
 *  If the class declares no instance variables, or cls is Nil, NULL is returned and *outCount is 0.
 */
OBJC_EXPORT Ivar _Nonnull * _Nullable
class_copyIvarList(Class _Nullable cls, unsigned int * _Nullable outCount) 
    OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);ins

这里有个细节,先看下面栗子:

// 这是定义在 FFSpringModel.h 中的属性
@interface FFSpringModel : FFSeasonModel
@property (nonatomic, copy) NSString *h_p_str;
@property (nonatomic, assign) NSString *h_p_bool;
@property (nonatomic, strong) NSMutableArray *h_p_arr;
@end

// 这是定义在 FFSpringModel.m 中的变量和属性
@interface FFSpringModel() {
    NSArray *m_array;
    NSString *m_str;
    BOOL *m_bool;
}
@property (nonatomic, copy) NSString *m_p_str;
@property (nonatomic, assign) NSString *m_p_bool;
@property (nonatomic, strong) NSMutableArray *m_p_arr;
@end

我们对FFSpringModel分别使用class_copyIvarListclass_copyPropertyList来对比结果

unsigned int i_outCount = 0;
Ivar *ivars = class_copyIvarList([FFSpringModel class], &i_outCount);
for (int i = 0; i < i_outCount; i ++) {
    NSLog(@"class_copyIvarList ---> %s", ivar_getName(ivars[i]));
}
free(ivars);
//    2018-03-24 15:32:30.132 runtime[2906:197694] class_copyIvarList ---> m_array
//    2018-03-24 15:32:30.133 runtime[2906:197694] class_copyIvarList ---> m_str
//    2018-03-24 15:32:30.133 runtime[2906:197694] class_copyIvarList ---> m_bool
//    2018-03-24 15:32:30.134 runtime[2906:197694] class_copyIvarList ---> _h_p_str
//    2018-03-24 15:32:30.134 runtime[2906:197694] class_copyIvarList ---> _h_p_bool
//    2018-03-24 15:32:30.134 runtime[2906:197694] class_copyIvarList ---> _h_p_arr
//    2018-03-24 15:32:30.134 runtime[2906:197694] class_copyIvarList ---> _m_p_str
//    2018-03-24 15:32:30.134 runtime[2906:197694] class_copyIvarList ---> _m_p_bool
//    2018-03-24 15:32:30.135 runtime[2906:197694] class_copyIvarList ---> _m_p_arr

unsigned int p_outCount = 0;
objc_property_t *propertys = class_copyPropertyList([FFSpringModel class], &p_outCount);
for (int i = 0; i < p_outCount; i ++) {
    NSLog(@"class_copyPropertyList ---> %s", property_getName(propertys[i]));
}
free(propertys);
//    2018-03-24 15:32:30.135 runtime[2906:197694] class_copyPropertyList ---> m_p_str
//    2018-03-24 15:32:30.135 runtime[2906:197694] class_copyPropertyList ---> m_p_bool
//    2018-03-24 15:32:30.135 runtime[2906:197694] class_copyPropertyList ---> m_p_arr
//    2018-03-24 15:32:30.136 runtime[2906:197694] class_copyPropertyList ---> h_p_str
//    2018-03-24 15:32:30.136 runtime[2906:197694] class_copyPropertyList ---> h_p_bool
//    2018-03-24 15:32:30.136 runtime[2906:197694] class_copyPropertyList ---> h_p_arr

对比运行结果,会发现,class_copyIvarList会获取属性和变量,而class_copyPropertyList只能获取属性,毕竟从方法名就可以推测

2.2.2 属性操作函数
  • property_getName
    获取属性名
/** 
 * Returns the name of a property.
 * 
 * @param property The property you want to inquire about.
 * 
 * @return A C string containing the property's name.
 */
OBJC_EXPORT const char * _Nonnull
property_getName(objc_property_t _Nonnull property) 
    OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);
  • property_getAttributes
    获取属性特性描述字符串
/** 
 * Returns the attribute string of a property.
 * 
 * @param property A property.
 *
 * @return A C string containing the property's attributes.
 * 
 * @note The format of the attribute string is described in Declared Properties in Objective-C Runtime Programming Guide.
 */
OBJC_EXPORT const char * _Nullable
property_getAttributes(objc_property_t _Nonnull property) 
    OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);
  • property_copyAttributeList
    获取属性的特性列表,使用后应该free()
/** 
 * Returns an array of property attributes for a property. 
 * 
 * @param property The property whose attributes you want copied.
 * @param outCount The number of attributes returned in the array.
 * 
 * @return An array of property attributes; must be free'd() by the caller. 
 */
OBJC_EXPORT objc_property_attribute_t * _Nullable
property_copyAttributeList(objc_property_t _Nonnull property,
                           unsigned int * _Nullable outCount)
    OBJC_AVAILABLE(10.7, 4.3, 9.0, 1.0, 2.0);
  • property_copyAttributeValue
    获取属性中指定的特性
/** 
 * Returns the value of a property attribute given the attribute name.
 * 
 * @param property The property whose attribute value you are interested in.
 * @param attributeName C string representing the attribute name.
 *
 * @return The value string of the attribute \e attributeName if it exists in
 *  \e property, \c nil otherwise. 
 */
OBJC_EXPORT char * _Nullable
property_copyAttributeValue(objc_property_t _Nonnull property,
                            const char * _Nonnull attributeName)
    OBJC_AVAILABLE(10.7, 4.3, 9.0, 1.0, 2.0);
  • class_getProperty
    获取指定属性
/** 
 * Returns a property with a given name of a given class.
 * 
 * @param cls The class you want to inspect.
 * @param name The name of the property you want to inspect.
 * 
 * @return A pointer of type \c objc_property_t describing the property, or
 *  \c NULL if the class does not declare a property with that name, 
 *  or \c NULL if \e cls is \c Nil.
 */
OBJC_EXPORT objc_property_t _Nullable
class_getProperty(Class _Nullable cls, const char * _Nonnull name)
    OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);

eg:

objc_property_t s_property = class_getProperty([FFSpringModel class], "m_p_str");
if (s_property) {
    NSLog(@"%s", property_getName(s_property));
} else {
    NSLog(@"没找到哟~");
}
// m_p_str
  • class_copyPropertyList
    获取属性列表(参考 2.2.1的例子)
/** 
 * Describes the properties declared by a class.
 * 
 * @param cls The class you want to inspect.
 * @param outCount On return, contains the length of the returned array. 
 *  If \e outCount is \c NULL, the length is not returned.        
 * 
 * @return An array of pointers of type \c objc_property_t describing the properties 
 *  declared by the class. Any properties declared by superclasses are not included. 
 *  The array contains \c *outCount pointers followed by a \c NULL terminator. You must free the array with \c free().
 * 
 *  If \e cls declares no properties, or \e cls is \c Nil, returns \c NULL and \c *outCount is \c 0.
 */
OBJC_EXPORT objc_property_t _Nonnull * _Nullable
class_copyPropertyList(Class _Nullable cls, unsigned int * _Nullable outCount)
    OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);
  • class_addProperty
/** 
 * Adds a property to a class.
 * 
 * @param cls The class to modify.
 * @param name The name of the property.
 * @param attributes An array of property attributes.
 * @param attributeCount The number of attributes in \e attributes.
 * 
 * @return \c YES if the property was added successfully, otherwise \c NO
 *  (for example, the class already has that property).
 */
OBJC_EXPORT BOOL
class_addProperty(Class _Nullable cls, const char * _Nonnull name,
                  const objc_property_attribute_t * _Nullable attributes,
                  unsigned int attributeCount)
    OBJC_AVAILABLE(10.7, 4.3, 9.0, 1.0, 2.0);

objc_property_attribute_t

/// Defines a property attribute
typedef struct {
    const char * _Nonnull name;           /**< The name of the attribute */
    const char * _Nonnull value;          /**< The value of the attribute (usually empty) */
} objc_property_attribute_t;

???

  • class_replaceProperty
    替换类的属性
/** 
 * Replace a property of a class. 
 * 
 * @param cls The class to modify.
 * @param name The name of the property.
 * @param attributes An array of property attributes.
 * @param attributeCount The number of attributes in \e attributes. 
 */
OBJC_EXPORT void
class_replaceProperty(Class _Nullable cls, const char * _Nonnull name,
                      const objc_property_attribute_t * _Nullable attributes,
                      unsigned int attributeCount)
    OBJC_AVAILABLE(10.7, 4.3, 9.0, 1.0, 2.0);
2.2.3 方法
struct objc_method {
    SEL _Nonnull method_name                                 OBJC2_UNAVAILABLE;
    char * _Nullable method_types                            OBJC2_UNAVAILABLE;
    IMP _Nonnull method_imp                                  OBJC2_UNAVAILABLE;
}   
  • class_addMethod
    添加方法,会覆盖父类的同名方法,但是不会替换本类中的实现,如果本类中存在一个同名的方法返回NO,如果要修改一个本类中已经存在的方法的话,需要使用method_setImplementation
    imp参数指向的函数至少要包括两个参数,self_cmd
/** 
 * Adds a new method to a class with a given name and implementation.
 * 
 * @param cls The class to which to add a method.
 * @param name A selector that specifies the name of the method being added.
 * @param imp A function which is the implementation of the new method. The function must take at least two arguments—self and _cmd.
 * @param types An array of characters that describe the types of the arguments to the method. 
 * 
 * @return YES if the method was added successfully, otherwise NO 
 *  (for example, the class already contains a method implementation with that name).
 *
 * @note class_addMethod will add an override of a superclass's implementation, 
 *  but will not replace an existing implementation in this class. 
 *  To change an existing implementation, use method_setImplementation.
 */
OBJC_EXPORT BOOL
class_addMethod(Class _Nullable cls, SEL _Nonnull name, IMP _Nonnull imp, 
                const char * _Nullable types) 
    OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);

方法实现示例:

void myFunction(id self, SEL _cmd) {
    // do something
}

关于types,是一个描述传递给方法的参数类型的字符数组,这就涉及到类型编码,见👇
eg:

void SummerRuntimeMethod(id self, SEL _cmd) {
    NSLog(@"这里是 runtime 添加的方法");
}

FFSummerModel *summerInfo = [FFSummerModel new];
class_addMethod([FFSummerModel class], @selector(runtimeMethod), (IMP)SummerRuntimeMethod, "v@:");
[summerInfo performSelector:@selector(runtimeMethod)];
// 这里是 runtime 添加的方法
  • class_getInstanceMethod
    获取实例方法,会搜索父类
/** 
 * Returns a specified instance method for a given class.
 * 
 * @param cls The class you want to inspect.
 * @param name The selector of the method you want to retrieve.
 * 
 * @return The method that corresponds to the implementation of the selector specified by 
 *  \e name for the class specified by \e cls, or \c NULL if the specified class or its 
 *  superclasses do not contain an instance method with the specified selector.
 *
 * @note This function searches superclasses for implementations, whereas \c class_copyMethodList does not.
 */
OBJC_EXPORT Method _Nullable
class_getInstanceMethod(Class _Nullable cls, SEL _Nonnull name)
    OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0);

eg:看下面栗子
这个是获取FFSummerModel类本身的实例方法

Method summerMethod2 = class_getInstanceMethod([FFSummerModel class], @selector(shouldGoSwimming));
if (summerMethod2) {
    method_getImplementation(summerMethod2)();
} else {
    NSLog(@"shouldGoSwimming 没找到");
}
// let us go swimming!

这个是获取FFSummerModel父类的实例方法showSomething

Method summerMethod0 = class_getInstanceMethod([FFSummerModel class], @selector(showSomething));
if (summerMethod0) {
    IMP summerImp0 = method_getImplementation(summerMethod0);
    summerImp0();
} else {
    NSLog(@"showSomething 没找到");
}
// 这是父类 FFSeasonModel 的 showSomething 方法

这里有个小细节,是如何使用IMP来调用带有参数的方法,简单的如下的调用会在运行时crash

Method summerMethod1 = class_getInstanceMethod([FFSummerModel class], @selector(doSomething:));
if (summerMethod1) {
    IMP summerImp1 = method_getImplementation(summerMethod1);
    // 这里是传递参数的样式
    summerImp1(summerInfo, @selector(doSomething:), @"runtime");
} else {
    NSLog(@"showSomething 没找到");
}
// 成功配置后 即可 得到正确的执行结果,👇的是
// 这是父类 FFSeasonModel 的 doSomething 方法,传入的值为:runtime

在这里需要了解什么是IMP
IMP就是implementation的缩写,是一个指向方法实现的指针,每个方法都会对应一个IMP指针,所以可以直接调用方法的IMP指针,来执行方法,而调用IMP的方式和调用普通的C函数相同,比如这样

id returnObjc = someIMP(id self, SEL, parameters);

但前提是项目需要配置一些选项编译器才能识别,为什么呢
首先我们先查看下IMP的定义(objc/objc.h),会发现默认IMP被认定为无参数无返回的函数

/// A pointer to the function of a method implementation. 
#if !OBJC_OLD_DISPATCH_PROTOTYPES
typedef void (*IMP)(void /* id, SEL, ... */ ); 
#else
typedef id _Nullable (*IMP)(id _Nonnull, SEL _Nonnull, ...); 
#endif

如果需要识别参数的话,需要将如下配置设置为NOTARGETS -> Build Settings -> Enable Strict Checking of objc_msgSend Calls),即可得到正确的执行结果

Enable Strict Checking of objc_msgSend Calls

但是,此时又会出现一个比较烦的问题,就是每个IMP的调用都要至少传入两个参数,即someIMP(id self, SEL)
这时,可以使用另外一种解决办法,就是重新定义一个自己的FF_IMP,和有参数的IMP的指针类型相同,在获取到IMP时候强转成我们自己的FF_IMP,这样在不需要传递参数的时候使用默认的IMP,在需要传递参数的时候使用FF_IMP
实际代码中,如果方法 没有返回值 或 有返回值但是不用接收返回值 的话,可以使用类似如下FF_IMPFF_VOID_IMP都可以;但是如果需要接收返回值的话,必须使用FF_IMP
看如下栗子

typedef id (* FF_IMP) (id, SEL, ...); // 有返回值
typedef void (* FF_VOID_IMP) (id, SEL, ...); // 无返回值

Method summerMethod1 = class_getInstanceMethod([FFSummerModel class], @selector(doSomething:));
if (summerMethod1) {
    FF_IMP summerImp1 = (FF_IMP)method_getImplementation(summerMethod1);
    summerImp1(summerInfo, @selector(doSomething:), @"runtime");
} else {
    NSLog(@"doSomething 没找到");
}
// 这是父类 FFSeasonModel 的 doSomething 方法,传入的值为:runtime
  • class_getClassMethod
    获取类方法,会搜索父类
/** 
 * Returns a pointer to the data structure describing a given class method for a given class.
 * 
 * @param cls A pointer to a class definition. Pass the class that contains the method you want to retrieve.
 * @param name A pointer of type \c SEL. Pass the selector of the method you want to retrieve.
 * 
 * @return A pointer to the \c Method data structure that corresponds to the implementation of the 
 *  selector specified by aSelector for the class specified by aClass, or NULL if the specified 
 *  class or its superclasses do not contain an instance method with the specified selector.
 *
 * @note Note that this function searches superclasses for implementations, 
 *  whereas \c class_copyMethodList does not.
 */
OBJC_EXPORT Method _Nullable
class_getClassMethod(Class _Nullable cls, SEL _Nonnull name)
    OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0);

eg:就一个栗子啦,整体和class_getInstanceMethod差不多,可以参考👆

Method summerClassMethod = class_getClassMethod([FFSummerModel class], @selector(seasonInfoClassMethod));
if (summerClassMethod) {
    method_getImplementation(summerClassMethod)();
} else {
    NSLog(@"seasonInfoClassMethod 没找到");
}
// 这是父类的类方法 seasonInfoClassMethod
  • class_copyMethodList
    获取所有方法的数组,不会搜索父类的,使用后需要使用free()
    返回包含所有实例方法的数组,无论是否在OC.h中定义 都可以获取到,但是父类的不会包含
    如果需要获取类方法,是在元类里面,需class_copMethodList(object_getClass(cls), &outCount)
/** 
 * Describes the instance methods implemented by a class.
 * 
 * @param cls The class you want to inspect.
 * @param outCount On return, contains the length of the returned array. 
 *  If outCount is NULL, the length is not returned.
 * 
 * @return An array of pointers of type Method describing the instance methods 
 *  implemented by the class—any instance methods implemented by superclasses are not included. 
 *  The array contains *outCount pointers followed by a NULL terminator. You must free the array with free().
 * 
 *  If cls implements no instance methods, or cls is Nil, returns NULL and *outCount is 0.
 * 
 * @note To get the class methods of a class, use \c class_copyMethodList(object_getClass(cls), &count).
 * @note To get the implementations of methods that may be implemented by superclasses, 
 *  use \c class_getInstanceMethod or \c class_getClassMethod.
 */
OBJC_EXPORT Method _Nonnull * _Nullable
class_copyMethodList(Class _Nullable cls, unsigned int * _Nullable outCount) 
    OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);

eg:

unsigned int outCount = 0;
Method *methodArr = class_copyMethodList([FFSummerModel class], &outCount);
for (int i = 0; i < outCount; i ++) {
    Method method = methodArr[i];
    SEL sel = method_getName(method);
    NSLog(@"class_copyMethodList ---> %s", sel_getName(sel));
}
free(methodArr);
//    2018-03-25 15:44:47.820 runtime[1586:81902] class_copyMethodList ---> runtimeMethod
//    2018-03-25 15:44:47.820 runtime[1586:81902] class_copyMethodList ---> shouldGoSwimming
//    2018-03-25 15:44:47.820 runtime[1586:81902] class_copyMethodList ---> isSummer
//    2018-03-25 15:44:47.820 runtime[1586:81902] class_copyMethodList ---> gogogo
//    2018-03-25 15:44:47.820 runtime[1586:81902] class_copyMethodList ---> init
  • class_replaceMethod
    替换方法的实现
    如果类中不存在指定名字的方法,这个函数功能相当于class_addMethod
    如果类中存在指定名字的方法,这个函数功能相当于method_setImplementation
/** 
 * Replaces the implementation of a method for a given class.
 * 
 * @param cls The class you want to modify.
 * @param name A selector that identifies the method whose implementation you want to replace.
 * @param imp The new implementation for the method identified by name for the class identified by cls.
 * @param types An array of characters that describe the types of the arguments to the method. 
 *  Since the function must take at least two arguments—self and _cmd, the second and third characters
 *  must be “@:” (the first character is the return type).
 * 
 * @return The previous implementation of the method identified by \e name for the class identified by \e cls.
 * 
 * @note This function behaves in two different ways:
 *  - If the method identified by \e name does not yet exist, it is added as if \c class_addMethod were called. 
 *    The type encoding specified by \e types is used as given.
 *  - If the method identified by \e name does exist, its \c IMP is replaced as if \c method_setImplementation were called.
 *    The type encoding specified by \e types is ignored.
 */
OBJC_EXPORT IMP _Nullable
class_replaceMethod(Class _Nullable cls, SEL _Nonnull name, IMP _Nonnull imp, 
                    const char * _Nullable types) 
    OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);

eg:交换下面两个方法的实现

- (void)testReplaceMethod01 {
    NSLog(@"testReplaceMethod01");
}

- (void)testReplaceMethod02 {
    NSLog(@"testReplaceMethod02");
}

// ---

FFSummerModel *summerInfo = [FFSummerModel new];
[summerInfo performSelector:@selector(testReplaceMethod01)];
IMP imp = class_getMethodImplementation([FFSummerModel class], @selector(testReplaceMethod02));
// IMP imp = method_getImplementation(class_getInstanceMethod([FFSummerModel class], @selector(testReplaceMethod02)));
class_replaceMethod([FFSummerModel class], @selector(testReplaceMethod01), imp, "v@:");
[summerInfo performSelector:@selector(testReplaceMethod01)];
// 2018-03-25 16:01:19.444 runtime[1762:100824] testReplaceMethod01
// 2018-03-25 16:01:19.444 runtime[1762:100824] testReplaceMethod02
  • class_getMethodImplementation
    该函数会在向类实例发送消息时候调用,返回指向方法的实现函数的指针,也有可能是运行时内部的函数,因为类实例无法响应方法的时候,返回的函数指针将是运行时消息转发机制的一部分
    class_getMethodImplementationmethod_getImplementation
/** 
 * Returns the function pointer that would be called if a 
 * particular message were sent to an instance of a class.
 * 
 * @param cls The class you want to inspect.
 * @param name A selector.
 * 
 * @return The function pointer that would be called if \c [object name] were called
 *  with an instance of the class, or \c NULL if \e cls is \c Nil.
 *
 * @note \c class_getMethodImplementation may be faster than \c method_getImplementation(class_getInstanceMethod(cls, name)).
 * @note The function pointer returned may be a function internal to the runtime instead of
 *  an actual method implementation. For example, if instances of the class do not respond to
 *  the selector, the function pointer returned will be part of the runtime's message forwarding machinery.
 */
OBJC_EXPORT IMP _Nullable
class_getMethodImplementation(Class _Nullable cls, SEL _Nonnull name) 
    OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);

栗子可以参考上面的

  • class_getMethodImplementation_stret
    返回方法的实现,这个方法与class_getMethodImplementation的区别是,如果类实例无法响应这个方法时候 不会返回运行时消息转发机制中的实现(待验证)
/** 
 * Returns the function pointer that would be called if a particular 
 * message were sent to an instance of a class.
 * 
 * @param cls The class you want to inspect.
 * @param name A selector.
 * 
 * @return The function pointer that would be called if \c [object name] were called
 *  with an instance of the class, or \c NULL if \e cls is \c Nil.
 */
OBJC_EXPORT IMP _Nullable
class_getMethodImplementation_stret(Class _Nullable cls, SEL _Nonnull name) 
    OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0)
    OBJC_ARM64_UNAVAILABLE;
  • class_respondsToSelector
    类的实例对象是否实现了指定的方法
    可以使用NSObject- (BOOL)respondsToSelector:(SEL)aSelector+ (BOOL)instancesRespondToSelector:(SEL)aSelector替代
/** 
 * Returns a Boolean value that indicates whether instances of a class respond to a particular selector.
 * 
 * @param cls The class you want to inspect.
 * @param sel A selector.
 * 
 * @return \c YES if instances of the class respond to the selector, otherwise \c NO.
 * 
 * @note You should usually use \c NSObject's \c respondsToSelector: or \c instancesRespondToSelector: 
 *  methods instead of this function.
 */
OBJC_EXPORT BOOL
class_respondsToSelector(Class _Nullable cls, SEL _Nonnull sel) 
    OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);

eg:

BOOL isResponds = class_respondsToSelector([FFSummerModel class], @selector(shouldGoSwimmingg));
NSString *str = isResponds ? @"实现了" : @"没有实现";
NSLog(@"%@", str);
// 没有实现
2.2.4 协议

协议相关的操作函数

  • class_copyProtocolList
    返回类实现的协议的数组,使用后应该free(),只会返回当前类实现的协议,不会查找父类的
/** 
 * Describes the protocols adopted by a class.
 * 
 * @param cls The class you want to inspect.
 * @param outCount On return, contains the length of the returned array. 
 *  If outCount is NULL, the length is not returned.
 * 
 * @return An array of pointers of type Protocol* describing the protocols adopted 
 *  by the class. Any protocols adopted by superclasses or other protocols are not included. 
 *  The array contains *outCount pointers followed by a NULL terminator. You must free the array with free().
 * 
 *  If cls adopts no protocols, or cls is Nil, returns NULL and *outCount is 0.
 */
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);

eg
创建一个简单的协,并让目标vc实现

@protocol FFAutumnProtocol <NSObject>
@optional
- (void)autumnProtocolMethod;
@end

在目标vc中如下

unsigned int outCount2 = 0;
Protocol * __unsafe_unretained *protocol_arr = class_copyProtocolList([self class], &outCount2);
for (int i = 0; i < outCount2; i ++) {
    Protocol *protocol = protocol_arr[i];
    NSLog(@"class_copyProtocolList --> %s", protocol_getName(protocol));
}
free(protocol_arr);
//  class_copyProtocolList --> FFAutumnProtocol
  • objc_allocateProtocol
    创建新的协议
/** 
 * Creates a new protocol instance that cannot be used until registered with
 * \c objc_registerProtocol()
 * 
 * @param name The name of the protocol to create.
 *
 * @return The Protocol instance on success, \c nil if a protocol
 *  with the same name already exists. 
 * @note There is no dispose method for this. 
 */
OBJC_EXPORT Protocol * _Nullable
objc_allocateProtocol(const char * _Nonnull name) 
    OBJC_AVAILABLE(10.7, 4.3, 9.0, 1.0, 2.0);
  • objc_registerProtocol
    注册动态创建的协议
/** 
 * Registers a newly constructed protocol with the runtime. The protocol
 * will be ready for use and is immutable after this.
 * 
 * @param proto The protocol you want to register.
 */
OBJC_EXPORT void
objc_registerProtocol(Protocol * _Nonnull proto) 
    OBJC_AVAILABLE(10.7, 4.3, 9.0, 1.0, 2.0);
  • protocol_addProperty
    为协议添加属性,只能向未注册的协议添加属性
    参数proto:协议
    参数name:属性名
    参数attributes:属性类型字符串
    参数attributeCount:属性类型数量
    参数isRequiredProperty:是否为必须属性
    参数isInstanceProperty:是否为实例属性
/** 
 * Adds a property to a protocol. The protocol must be under construction. 
 * 
 * @param proto The protocol to add a property to.
 * @param name The name of the property.
 * @param attributes An array of property attributes.
 * @param attributeCount The number of attributes in \e attributes.
 * @param isRequiredProperty YES if the property (accessor methods) is not optional. 
 * @param isInstanceProperty YES if the property (accessor methods) are instance methods. 
 *  This is the only case allowed fo a property, as a result, setting this to NO will 
 *  not add the property to the protocol at all. 
 */
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)
    OBJC_AVAILABLE(10.7, 4.3, 9.0, 1.0, 2.0);
  • protocol_addMethodDescription
    为协议添加方法,只能向未注册的协议添加方法
    参数types:方法类型
/** 
 * Adds a method to a protocol. The protocol must be under construction.
 * 
 * @param proto The protocol to add a method to.
 * @param name The name of the method to add.
 * @param types A C string that represents the method signature.
 * @param isRequiredMethod YES if the method is not an optional method.
 * @param isInstanceMethod YES if the method is an instance method. 
 */
OBJC_EXPORT void
protocol_addMethodDescription(Protocol * _Nonnull proto, SEL _Nonnull name,
                              const char * _Nullable types,
                              BOOL isRequiredMethod, BOOL isInstanceMethod) 
    OBJC_AVAILABLE(10.7, 4.3, 9.0, 1.0, 2.0);
  • protocol_addProtocol
    添加一个已经注册的协议到一个未注册的协议,只能向未注册的协议中添加
/** 
 * Adds an incorporated protocol to another protocol. The protocol being
 * added to must still be under construction, while the additional protocol
 * must be already constructed.
 * 
 * @param proto The protocol you want to add to, it must be under construction.
 * @param addition The protocol you want to incorporate into \e proto, it must be registered.
 */
OBJC_EXPORT void
protocol_addProtocol(Protocol * _Nonnull proto, Protocol * _Nonnull addition) 
    OBJC_AVAILABLE(10.7, 4.3, 9.0, 1.0, 2.0);
  • class_addProtocol
    为类添加协议
/** 
 * Adds a protocol to a class.
 * 
 * @param cls The class to modify.
 * @param protocol The protocol to add to \e cls.
 * 
 * @return \c YES if the method was added successfully, otherwise \c NO 
 *  (for example, the class already conforms to that protocol).
 */
OBJC_EXPORT BOOL
class_addProtocol(Class _Nullable cls, Protocol * _Nonnull protocol) 
    OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);
  • class_conformsToProtocol
    判断类是否实现了指定的协议,通常是使用NSObject+ (BOOL)conformsToProtocol:(Protocol *)protocol;
/** 
 * Returns a Boolean value that indicates whether a class conforms to a given protocol.
 * 
 * @param cls The class you want to inspect.
 * @param protocol A protocol.
 *
 * @return YES if cls conforms to protocol, otherwise NO.
 *
 * @note You should usually use NSObject's conformsToProtocol: method instead of this function.
 */
OBJC_EXPORT BOOL
class_conformsToProtocol(Class _Nullable cls, Protocol * _Nullable protocol) 
    OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);

eg:用到上面的几个函数

if (!class_conformsToProtocol([self class], NSProtocolFromString(@"FFRuntimeProtocol"))) {
    Protocol *runtimeProtocol = objc_allocateProtocol("FFRuntimeProtocol");
    objc_property_attribute_t type = { "T", "@\"NSString\"" };
    objc_property_attribute_t ownership = { "C", "" };
    objc_property_attribute_t backingivar  = { "V", "" };
    objc_property_attribute_t attrs[] = { type, ownership, backingivar };
    protocol_addProperty(runtimeProtocol, "name", attrs, 3, true, true);
    protocol_addProtocol(runtimeProtocol, NSProtocolFromString(@"NSObject"));
    protocol_addMethodDescription(runtimeProtocol, @selector(FFRuntimeProtocolMethod), "v@:", false, true);

    objc_registerProtocol(runtimeProtocol);

    class_addProtocol([self class], runtimeProtocol);
} else {
    NSLog(@"动态创建协议失败");
}
2.2.5 版本
  • class_getVersion
    获取版本号
    可以使用类的版本号,作为该类为其他类提供的接口的版本控制,这对于对象的序列化存储非常有用。在不同类定义版本中,重要的是要识别出实例变量布局的改变。
/** 
 * Returns the version number of a class definition.
 * 
 * @param cls A pointer to a \c Class data structure. Pass
 *  the class definition for which you wish to obtain the version.
 * 
 * @return An integer indicating the version number of the class definition.
 *
 * @see class_setVersion
 */
OBJC_EXPORT int
class_getVersion(Class _Nullable cls)
    OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0);

eg:

int version = class_getVersion([FFSummerModel class]);
NSLog(@"%d", version);
// 0
  • class_setVersion
    设置版本号
/** 
 * Sets the version number of a class definition.
 * 
 * @param cls A pointer to an Class data structure. 
 *  Pass the class definition for which you wish to set the version.
 * @param version An integer. Pass the new version number of the class definition.
 *
 * @note You can use the version number of the class definition to provide versioning of the
 *  interface that your class represents to other classes. This is especially useful for object
 *  serialization (that is, archiving of the object in a flattened form), where it is important to
 *  recognize changes to the layout of the instance variables in different class-definition versions.
 * @note Classes derived from the Foundation framework \c NSObject class can set the class-definition
 *  version number using the \c setVersion: class method, which is implemented using the \c class_setVersion function.
 */
OBJC_EXPORT void
class_setVersion(Class _Nullable cls, int version)
    OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0);

eg:

class_setVersion([FFSummerModel class], 3);
NSLog(@"%d", class_getVersion([FFSummerModel class]));
// 3

3. 动态创建类和对象

runtime的强大之处,在于可以在运行时创建类和对象

3.1 动态创建类

为创建一个新的类,首先需要调用objc_allocateClassPair,然后为新的类添加方法class_addMethod、实例变量class_addIvar、属性class_addProperty、协议class_addProtocol等,最后需要使用objc_registerClassPair来注册类,之后这个类就可以使用啦

  • objc_allocateClassPair
    如果创建一个根类的话,父类设置为nilextraBytes通常设为0,该参数是分配给类和元类对象尾部的索引ivars的字节数
    实例变量和实例方法应该添加在类自身,类方法应该添加在类的元类上
/** 
 * Creates a new class and metaclass.
 * 
 * @param superclass The class to use as the new class's superclass, or \c Nil to create a new root class.
 * @param name The string to use as the new class's name. The string will be copied.
 * @param extraBytes The number of bytes to allocate for indexed ivars at the end of 
 *  the class and metaclass objects. This should usually be \c 0.
 * 
 * @return The new class, or Nil if the class could not be created (for example, the desired name is already in use).
 * 
 * @note You can get a pointer to the new metaclass by calling \c object_getClass(newClass).
 * @note To create a new class, start by calling \c objc_allocateClassPair. 
 *  Then set the class's attributes with functions like \c class_addMethod and \c class_addIvar.
 *  When you are done building the class, call \c objc_registerClassPair. The new class is now ready for use.
 * @note Instance methods and instance variables should be added to the class itself. 
 *  Class methods should be added to the metaclass.
 */
OBJC_EXPORT Class _Nullable
objc_allocateClassPair(Class _Nullable superclass, const char * _Nonnull name, 
                       size_t extraBytes) 
    OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);
  • objc_registerClassPair
    注册由objc_allocateClassPair创建的类,
/** 
 * Registers a class that was allocated using \c objc_allocateClassPair.
 * 
 * @param cls The class you want to register.
 */
OBJC_EXPORT void
objc_registerClassPair(Class _Nonnull cls) 
    OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);
  • objc_disposeClassPair
    函数用于销毁一个类,如果程序中运行的还有这个类或者其子类的实例,则不能对该类调用该方法
/** 
 * Destroy a class and its associated metaclass. 
 * 
 * @param cls The class to be destroyed. It must have been allocated with 
 *  \c objc_allocateClassPair
 * 
 * @warning Do not call if instances of this class or a subclass exist.
 */
OBJC_EXPORT void
objc_disposeClassPair(Class _Nonnull cls) 
    OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);
3.2 动态创建对象
  • class_createInstance
    创建实例对象
    创建实例时候,会在默认的内存区域分配内存。extraBytes参数表示分配的额外字节数,这些额外的字节可以用于存储在类定义中所定义的实例变量之外的实例变量。
    调用class_createInstance+ (instancetype)alloc方法类似
/** 
 * Creates an instance of a class, allocating memory for the class in the 
 * default malloc memory zone.
 * 
 * @param cls The class that you wish to allocate an instance of.
 * @param extraBytes An integer indicating the number of extra bytes to allocate. 
 *  The additional bytes can be used to store additional instance variables beyond 
 *  those defined in the class definition.
 * 
 * @return An instance of the class \e cls.
 */
OBJC_EXPORT id _Nullable
class_createInstance(Class _Nullable cls, size_t extraBytes)
    OBJC_RETURNS_RETAINED
    OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0);

eg:

id obj = class_createInstance([FFSummerModel class], 0);
NSLog(@"%@", object_getClassName(obj));
[obj performSelector:@selector(shouldGoSwimming)];
// FFSummerModel
// let us go swimming!
  • objc_constructInstance
    在指定位置创建类实例
/** 
 * Creates an instance of a class at the specific location provided.
 * 
 * @param cls The class that you wish to allocate an instance of.
 * @param bytes The location at which to allocate an instance of \e cls.
 *  Must point to at least \c class_getInstanceSize(cls) bytes of well-aligned,
 *  zero-filled memory.
 *
 * @return \e bytes on success, \c nil otherwise. (For example, \e cls or \e bytes
 *  might be \c nil)
 *
 * @see class_createInstance
 */
OBJC_EXPORT id _Nullable
objc_constructInstance(Class _Nullable cls, void * _Nullable bytes) 
    OBJC_AVAILABLE(10.6, 3.0, 9.0, 1.0, 2.0)
    OBJC_ARC_UNAVAILABLE;
  • objc_destructInstance
    销毁类实例,但不会移除与实例相关的引用
/** 
 * Destroys an instance of a class without freeing memory and removes any
 * associated references this instance might have had.
 * 
 * @param obj The class instance to destroy.
 * 
 * @return \e obj. Does nothing if \e obj is nil.
 * 
 * @note CF and other clients do call this under GC.
 */
OBJC_EXPORT void * _Nullable objc_destructInstance(id _Nullable obj) 
    OBJC_AVAILABLE(10.6, 3.0, 9.0, 1.0, 2.0)
    OBJC_ARC_UNAVAILABLE;
3.3 实例操作函数
3.3.1 针对整个对象进行操作的函数
  • object_copy
    返回指定对象的一份copyARC下无效
/** 
 * Returns a copy of a given object.
 * 
 * @param obj An Objective-C object.
 * @param size The size of the object \e obj.
 * 
 * @return A copy of \e obj.
 */
OBJC_EXPORT id _Nullable object_copy(id _Nullable obj, size_t size)
    OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0)
    OBJC_ARC_UNAVAILABLE;
  • object_dispose
    释放指定对象占用的内存,ARC下无效
/** 
 * Frees the memory occupied by a given object.
 * 
 * @param obj An Objective-C object.
 * 
 * @return nil
 */
OBJC_EXPORT id _Nullable
object_dispose(id _Nullable obj)
    OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0)
    OBJC_ARC_UNAVAILABLE;

如何关闭ARCautomatic reference counting),使用MRCmanual reference counting)?
TARGETS -> Build Setting -> Objective-C Automatic Reference Counting -> YES
eg:
FFSeasonModelFFSummerModel的父类,子类添加了一些属性和方法来扩展了父类,我们有一个父类的对象,在运行的时候希望将父类对象转换为子类对象,这种情况下是没法直接转换的,因为子类实例对象比父类实例对象更大,没有更多的空间来放置对象
此时,可以使用上面的函数来处理这种情况,参考如下栗子

FFSeasonModel *seasonInfo = [FFSeasonModel new];
id newInfo = object_copy(seasonInfo, class_getInstanceSize([FFSummerModel class]));
object_setClass(newInfo, [FFSummerModel class]);
object_dispose(seasonInfo);
[newInfo performSelector:@selector(shouldGoSwimming)];
// let us go swimming!
3.3.2 针对对象实例变量操作的函数
  • object_setInstanceVariable
    修改类对象实例变量的值,ARC下无效,可以使用object_setIvar
/** 
 * Changes the value of an instance variable of a class instance.
 * 
 * @param obj A pointer to an instance of a class. Pass the object containing
 *  the instance variable whose value you wish to modify.
 * @param name A C string. Pass the name of the instance variable whose value you wish to modify.
 * @param value The new value for the instance variable.
 * 
 * @return A pointer to the \c Ivar data structure that defines the type and 
 *  name of the instance variable specified by \e name.
 *
 * @note Instance variables with known memory management (such as ARC strong and weak)
 *  use that memory management. Instance variables with unknown memory management 
 *  are assigned as if they were unsafe_unretained.
 */
OBJC_EXPORT Ivar _Nullable
object_setInstanceVariable(id _Nullable obj, const char * _Nonnull name,
                           void * _Nullable value)
    OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0)
    OBJC_ARC_UNAVAILABLE;
  • object_getInstanceVariable
    获取对象实例变量的值,ARC下无效,可以使用object_getIvar
/** 
 * Obtains the value of an instance variable of a class instance.
 * 
 * @param obj A pointer to an instance of a class. Pass the object containing
 *  the instance variable whose value you wish to obtain.
 * @param name A C string. Pass the name of the instance variable whose value you wish to obtain.
 * @param outValue On return, contains a pointer to the value of the instance variable.
 * 
 * @return A pointer to the \c Ivar data structure that defines the type and name of
 *  the instance variable specified by \e name.
 */
OBJC_EXPORT Ivar _Nullable
object_getInstanceVariable(id _Nullable obj, const char * _Nonnull name,
                           void * _Nullable * _Nullable outValue)
    OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0)
    OBJC_ARC_UNAVAILABLE;
  • object_getIndexedIvars
    返回 指向给定对象分配的任何额外字节的指针,ARC下无效
/** 
 * Returns a pointer to any extra bytes allocated with an instance given object.
 * 
 * @param obj An Objective-C object.
 * 
 * @return A pointer to any extra bytes allocated with \e obj. If \e obj was
 *   not allocated with any extra bytes, then dereferencing the returned pointer is undefined.
 * 
 * @note This function returns a pointer to any extra bytes allocated with the instance
 *  (as specified by \c class_createInstance with extraBytes>0). This memory follows the
 *  object's ordinary ivars, but may not be adjacent to the last ivar.
 * @note The returned pointer is guaranteed to be pointer-size aligned, even if the area following
 *  the object's last ivar is less aligned than that. Alignment greater than pointer-size is never
 *  guaranteed, even if the area following the object's last ivar is more aligned than that.
 * @note In a garbage-collected environment, the memory is scanned conservatively.
 */
OBJC_EXPORT void * _Nullable object_getIndexedIvars(id _Nullable obj)
    OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0)
    OBJC_ARC_UNAVAILABLE;
  • object_setIvar
    设置对象实例变量的值
/** 
 * Sets the value of an instance variable in an object.
 * 
 * @param obj The object containing the instance variable whose value you want to set.
 * @param ivar The Ivar describing the instance variable whose value you want to set.
 * @param value The new value for the instance variable.
 * 
 * @note Instance variables with known memory management (such as ARC strong and weak)
 *  use that memory management. Instance variables with unknown memory management 
 *  are assigned as if they were unsafe_unretained.
 * @note \c object_setIvar is faster than \c object_setInstanceVariable if the Ivar
 *  for the instance variable is already known.
 */
OBJC_EXPORT void
object_setIvar(id _Nullable obj, Ivar _Nonnull ivar, id _Nullable value) 
    OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);
  • object_getIvar
    返回 对象中实例变量的值
/** 
 * Reads the value of an instance variable in an object.
 * 
 * @param obj The object containing the instance variable whose value you want to read.
 * @param ivar The Ivar describing the instance variable whose value you want to read.
 * 
 * @return The value of the instance variable specified by \e ivar, or \c nil if \e object is \c nil.
 * 
 * @note \c object_getIvar is faster than \c object_getInstanceVariable if the Ivar
 *  for the instance variable is already known.
 */
OBJC_EXPORT id _Nullable
object_getIvar(id _Nullable obj, Ivar _Nonnull ivar) 
    OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);

eg:

FFAutumnModel *autumnInfo = [FFAutumnModel new];
autumnInfo.autumnStr = @"秋天真是太好了~";
Ivar ivar = class_getInstanceVariable([autumnInfo class], "_autumnStr");
id autumn_value = object_getIvar(autumnInfo, ivar);
NSLog(@"%@", autumn_value);
// 秋天真是太好了~

object_setIvar(autumnInfo, ivar, @"秋天真是真是真是太好啦~");
NSLog(@"%@", object_getIvar(autumnInfo, ivar));
// 秋天真是真是真是太好啦~
3.3.3 针对对象所属的类的操作
  • object_getClassName
    返回给定对象类的名字
/** 
 * Returns the class name of a given object.
 * 
 * @param obj An Objective-C object.
 * 
 * @return The name of the class of which \e obj is an instance.
 */
OBJC_EXPORT const char * _Nonnull object_getClassName(id _Nullable obj)
    OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0);

eg:

const char * name = object_getClassName([FFAutumnModel new]);
NSLog(@"%s", name);
// FFAutumnModel
  • object_getClass
    返回对象的类
/** 
 * Returns the class of an object.
 * 
 * @param obj The object you want to inspect.
 * 
 * @return The class object of which \e object is an instance, 
 *  or \c Nil if \e object is \c nil.
 */
OBJC_EXPORT Class _Nullable
object_getClass(id _Nullable obj) 
    OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);

eg:

Class autumnClass =  object_getClass([FFAutumnModel new]);
[autumnClass performSelector:@selector(shouldGoTraveling)];
// let's go traveling
  • object_setClass
    设置对象的类
/** 
 * Sets the class of an object.
 * 
 * @param obj The object to modify.
 * @param cls A class object.
 * 
 * @return The previous value of \e object's class, or \c Nil if \e object is \c nil.
 */
OBJC_EXPORT Class _Nullable
object_setClass(id _Nullable obj, Class _Nonnull cls) 
    OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);

eg:见3.3.1

3.3.4 获取类定义相关的函数

OC运行时会自动注册代码中的类,也可以在运行时创建类的定义并使用objc_addClass添加
运行时也提供了一系列函数来获取类定义的相关信息

  • objc_getClassList
    获取已注册的类的定义列表
    不能假设从该函数获取的类对象是继承自NSObject体系的,在这些类上调用方法时候应该先检测下需要调用的方法是否存在
    参数buffer:已经分配好的内存空间的数组,如果传NULL会获得当前已经注册的所有的类
    参数bufferCount:数组中可以存放的元素的个数,返回的是注册的类的总数,当该值小于已经注册类的总数的时候,获取到的是已经注册类的集合的任意指定数量的集合
/** 
 * Obtains the list of registered class definitions.
 * 
 * @param buffer An array of \c Class values. On output, each \c Class value points to
 *  one class definition, up to either \e bufferCount or the total number of registered classes,
 *  whichever is less. You can pass \c NULL to obtain the total number of registered class
 *  definitions without actually retrieving any class definitions.
 * @param bufferCount An integer value. Pass the number of pointers for which you have allocated space
 *  in \e buffer. On return, this function fills in only this number of elements. If this number is less
 *  than the number of registered classes, this function returns an arbitrary subset of the registered classes.
 * 
 * @return An integer value indicating the total number of registered classes.
 * 
 * @note The Objective-C runtime library automatically registers all the classes defined in your source code.
 *  You can create class definitions at runtime and register them with the \c objc_addClass function.
 * 
 * @warning You cannot assume that class objects you get from this function are classes that inherit from \c NSObject,
 *  so you cannot safely call any methods on such classes without detecting that the method is implemented first.
 */
OBJC_EXPORT int
objc_getClassList(Class _Nonnull * _Nullable buffer, int bufferCount)
    OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0);

???
eg:

int bufferCount = 0;
Class *classes = NULL;
bufferCount = objc_getClassList(NULL, 0);
NSLog(@"objc_getClassList --- %d", bufferCount);
if (bufferCount > 0) {
    classes = (Class *)malloc(sizeof(Class) * bufferCount);
    bufferCount = objc_getClassList(classes, bufferCount);
    NSLog(@"class count --- %d", bufferCount);
    for (int i = 0; i < bufferCount; i ++) {
        Class class = classes[i];
        NSLog(@"class name ---> %s", class_getName(class));
        if (class_getSuperclass(class) == [UITableView class]) {
           NSLog(@"subclass of UITableView ---> %s", class_getName(class));
        }
    }
    free(classes);
}
  • objc_copyClassList
    创建并返回一个指向所有所有已经注册类的指针列表
/** 
 * Creates and returns a list of pointers to all registered class definitions.
 * 
 * @param outCount An integer pointer used to store the number of classes returned by
 *  this function in the list. It can be \c nil.
 * 
 * @return A nil terminated array of classes. It must be freed with \c free().
 * 
 * @see objc_getClassList
 */
OBJC_EXPORT Class _Nonnull * _Nullable
objc_copyClassList(unsigned int * _Nullable outCount)
    OBJC_AVAILABLE(10.7, 3.1, 9.0, 1.0, 2.0);

eg:

unsigned int outCount3 = 0;
Class *classList = objc_copyClassList(&outCount3);
NSLog(@"objc_copyClassList count --- %d", outCount3);
for (int i = 0; i < outCount3; i ++) {
    Class class = classList[i];
    NSLog(@"%s", class_getName(class));
}
  • objc_lookupClass
    返回指定类,如果类在运行时没有定义,返回nil
/** 
 * Returns the class definition of a specified class.
 * 
 * @param name The name of the class to look up.
 * 
 * @return The Class object for the named class, or \c nil if the class
 *  is not registered with the Objective-C runtime.
 * 
 * @note \c objc_getClass is different from this function in that if the class is not
 *  registered, \c objc_getClass calls the class handler callback and then checks a second
 *  time to see whether the class is registered. This function does not call the class handler callback.
 */
OBJC_EXPORT Class _Nullable
objc_lookUpClass(const char * _Nonnull name)
    OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0);
  • objc_getClass
    返回指定类,如果类在运行时未注册,会调用类处理的回调,并再次确认是否注册,如果依然没有注册,返回nil
/** 
 * Returns the class definition of a specified class.
 * 
 * @param name The name of the class to look up.
 * 
 * @return The Class object for the named class, or \c nil
 *  if the class is not registered with the Objective-C runtime.
 * 
 * @note \c objc_getClass is different from \c objc_lookUpClass in that if the class
 *  is not registered, \c objc_getClass calls the class handler callback and then checks
 *  a second time to see whether the class is registered. \c objc_lookUpClass does 
 *  not call the class handler callback.
 * 
 * @warning Earlier implementations of this function (prior to OS X v10.0)
 *  terminate the program if the class does not exist.
 */
OBJC_EXPORT Class _Nullable
objc_getClass(const char * _Nonnull name)
    OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0);
  • objc_getRequiredClass
    返回指定类,如果类在运行时未注册,该函数会调用类处理的回调,并再次确认是否注册,如果依然没有注册,将杀死进程
/** 
 * Returns the class definition of a specified class.
 * 
 * @param name The name of the class to look up.
 * 
 * @return The Class object for the named class.
 * 
 * @note This function is the same as \c objc_getClass, but kills the process if the class is not found.
 * @note This function is used by ZeroLink, where failing to find a class would be a compile-time link error without ZeroLink.
 */
OBJC_EXPORT Class _Nonnull
objc_getRequiredClass(const char * _Nonnull name)
    OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0);
  • objc_getMetaClass
    返回指定类的元类,如果指定的类没有在运行时注册,该函数会调用类处理回调,并再次确认是否注册。但是每个类定义的时候必要有一个有效的元类,所以该函数总会返回一个有效的元类,无论是否有效
/** 
 * Returns the metaclass definition of a specified class.
 * 
 * @param name The name of the class to look up.
 * 
 * @return The \c Class object for the metaclass of the named class, or \c nil if the class
 *  is not registered with the Objective-C runtime.
 * 
 * @note If the definition for the named class is not registered, this function calls the class handler
 *  callback and then checks a second time to see if the class is registered. However, every class
 *  definition must have a valid metaclass definition, and so the metaclass definition is always returned,
 *  whether it’s valid or not.
 */
OBJC_EXPORT Class _Nullable
objc_getMetaClass(const char * _Nonnull name)
    OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0);

4. 类型编码(Type Encoding)

编译器将每个方法的返回值和参数类型编码成一个字符串,并与其方法的selector关联在一起。可以使用@encode()编译器指定获取值。当给定一个类型时候,@encode()返回这个类型的字符串编码,这里的类型既可以是基本数据类型,也可以是结构体、类等类型。其实,任何可以使用sizeof()操作的参数的类型,都可以使用@encode

Objective-C Runtime Programming Guide > Type Encodings

Objective-C type encodings

eg:

long double abc = 1000;
NSLog(@"%Lf", abc);
NSLog(@"%s", @encode(typeof (abc)));
// 1000.000000
// D

float arr[] = {1.0, 2.0, 3.0};
NSLog(@"%s", @encode(typeof(arr)));
// [3f]

NSString *string = @"string";
NSLog(@"%s", @encode(typeof(string)));
// @

typedef struct example {
    int num1;
    double num2;
    char string;
} example;
NSLog(@"%s", @encode(typeof(example)));
// {example=idc}

NSLog(@"%s", @encode(typeof(FFSummerModel)));
// {FFSummerModel=#}

NSLog(@"int        : %s", @encode(int));
// int        : i
NSLog(@"float      : %s", @encode(float));
// float      : f
NSLog(@"float *    : %s", @encode(float*));
// float *    : ^f
NSLog(@"char       : %s", @encode(char));
// char       : c
NSLog(@"char *     : %s", @encode(char *));
// char *     : *
NSLog(@"BOOL       : %s", @encode(BOOL));
// BOOL       : B
NSLog(@"void       : %s", @encode(void));
// void       : v
NSLog(@"void *     : %s", @encode(void *));
// void *     : ^v

NSLog(@"NSObject * : %s", @encode(NSObject *));
// NSObject * : @
NSLog(@"NSObject   : %s", @encode(NSObject));
// NSObject   : {NSObject=#}
NSLog(@"[NSObject] : %s", @encode(typeof([NSObject class])));
// [NSObject] : #
NSLog(@"NSError ** : %s", @encode(typeof(NSError **)));
// NSError ** : ^@

int intArray[5] = {1, 2, 3, 4, 5};
NSLog(@"int[]      : %s", @encode(typeof(intArray)));
// int[]      : [5i]

float floatArray[3] = {0.1f, 0.2f, 0.3f};
NSLog(@"float[]    : %s", @encode(typeof(floatArray)));
// float[]    : [3f]

typedef struct _struct {
    short a;
    long long b;
    unsigned long long c;
} Struct;
NSLog(@"struct     : %s", @encode(typeof(Struct)));
// struct     : {_struct=sqQ}
  • 指针的标准编码是加一个前置的^,而char *拥有自己的编码,因为C的字符串被认为是一个实体,而不是指针

5. 关联对象

通常会把成员变量Ivar放在类声明的头文件中,或者放在类实现的@implementation后面。并且不能在分类中添加成员变量,编译器会报错。
可以使用 关联对象(associated object
主要有下面几个函数

  • objc_setAssociatedObject
/** 
 * Sets an associated value for a given object using a given key and association policy.
 * 
 * @param object The source object for the association.
 * @param key The key for the association.
 * @param value The value to associate with the key key for object. Pass nil to clear an existing association.
 * @param policy The policy for the association. For possible values, see “Associative Object Behaviors.”
 * 
 * @see objc_setAssociatedObject
 * @see objc_removeAssociatedObjects
 */
OBJC_EXPORT void
objc_setAssociatedObject(id _Nonnull object, const void * _Nonnull key,
                         id _Nullable value, objc_AssociationPolicy policy)
    OBJC_AVAILABLE(10.6, 3.1, 9.0, 1.0, 2.0);
  • objc_getAssociatedObject
/** 
 * Returns the value associated with a given object for a given key.
 * 
 * @param object The source object for the association.
 * @param key The key for the association.
 * 
 * @return The value associated with the key \e key for \e object.
 * 
 * @see objc_setAssociatedObject
 */
OBJC_EXPORT id _Nullable
objc_getAssociatedObject(id _Nonnull object, const void * _Nonnull key)
    OBJC_AVAILABLE(10.6, 3.1, 9.0, 1.0, 2.0);
  • objc_removeAssociatedObjects
/** 
 * Removes all associations for a given object.
 * 
 * @param object An object that maintains associated objects.
 * 
 * @note The main purpose of this function is to make it easy to return an object 
 *  to a "pristine state”. You should not use this function for general removal of
 *  associations from objects, since it also removes associations that other clients
 *  may have added to the object. Typically you should use \c objc_setAssociatedObject 
 *  with a nil value to clear an association.
 * 
 * @see objc_setAssociatedObject
 * @see objc_getAssociatedObject
 */
OBJC_EXPORT void
objc_removeAssociatedObjects(id _Nonnull object)
    OBJC_AVAILABLE(10.6, 3.1, 9.0, 1.0, 2.0);
  • 内存管理策略
/**
 * Policies related to associative references.
 * These are options to objc_setAssociatedObject()
 */
typedef OBJC_ENUM(uintptr_t, objc_AssociationPolicy) {
    OBJC_ASSOCIATION_ASSIGN = 0,           /**< Specifies a weak reference to the associated object. */
    OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1, /**< Specifies a strong reference to the associated object. 
                                            *   The association is not made atomically. */
    OBJC_ASSOCIATION_COPY_NONATOMIC = 3,   /**< Specifies that the associated object is copied. 
                                            *   The association is not made atomically. */
    OBJC_ASSOCIATION_RETAIN = 01401,       /**< Specifies a strong reference to the associated object.
                                            *   The association is made atomically. */
    OBJC_ASSOCIATION_COPY = 01403          /**< Specifies that the associated object is copied.
                                            *   The association is made atomically. */
};

我们可以把关联对象的想象成字典存取的过程,字典类似于目标对象,我们通过指定的key使用objc_setAssociatedObject,将需要添加的成员变量关联到对象中;如果需要取出添加的成员变量,需要用存的时候指定的key通过objc_getAssociatedObject来取出
可以参考👇的栗子
eg:
vc中有一个view,想要动态地添加手势到这个view,并且指定点击后的操作 ~
可以动态的创建一个tap关联到view上,动态地创建一个block关联到view上 ~

// 传入指定的view,和后续需要执行的操作
- (void)setTapActionFFView:(FFView *)view WithBlock:(void(^)(void))block {
    // 根据指定key,获取手势,如果没有的话,就创建手势,并关联到view
    UITapGestureRecognizer *tapGR = objc_getAssociatedObject(view, "FFUITapGestureRecognizerKey");
    if (!tapGR) {
        // 这里的 target 需要传 self,因为触发动作后 是当前的 vc 来响应
        tapGR = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(actionOfTapGestureRecognizer:)];
        [view addGestureRecognizer:tapGR];
        objc_setAssociatedObject(view, "FFUITapGestureRecognizerKey", tapGR, OBJC_ASSOCIATION_RETAIN);
    }
    // 关联 block
    objc_setAssociatedObject(view, "FFActionBlockKey", block, OBJC_ASSOCIATION_COPY);
}

- (void)actionOfTapGestureRecognizer:(UITapGestureRecognizer *)tapGR {
    if (tapGR.state == UIGestureRecognizerStateRecognized) {
        // 从 view 中取出关联的 block,如果存在的话就执行
        void (^action)(void) = objc_getAssociatedObject(tapGR.view, "FFActionBlockKey");
        if (action) {
            action();
        }
    }
}

// -----
FFView *ff_view = [[FFView alloc] initWithFrame:CGRectMake(100, 100, 200, 200)];
ff_view.backgroundColor = [UIColor cyanColor];
[self.view addSubview:ff_view];
[self setTapActionFFView:ff_view WithBlock:^{
    // do something ~
}];

不定期更新 不合适的地方 还请指点~ 感激不尽
愿祖国繁荣昌盛~
o(* ̄3 ̄)o

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,053评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,527评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,779评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,685评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,699评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,609评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,989评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,654评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,890评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,634评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,716评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,394评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,976评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,950评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,191评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,849评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,458评论 2 342

推荐阅读更多精彩内容