iOS进阶补完计划--通读runtime(.h)

说到runtime。所有iOS的开发者无不知晓。运行时、swizzle、黑魔法等等。
不过用的时候是copy代码、还是真正理解了runtime以及OC中类、对象、方法的本质结构。
起码就我而言、很长一段时间(以年来计算)。都是前者。

所以这篇文章不属于教学贴。希望借此能在runtime、以及OC的本质方面更深一步。

这里有一篇很不错的入门文章、内部对runtime进行了蛮详细的归纳总结。并且还列举了很多runtime的实用帖子链接。
iOS 模块详解—「Runtime面试、工作」看我就 🐒 了 _

目录

  • 类和对象
    • 实例对象(id)
    • 类对象(Class)
    • 元类(Meta Class)
    • 类与对象的总结
  • runtime正题
  • runtime方法的前缀
  • 对象操作方法(object_)
  • 全局操作方法(objc_)
  • 类操作方法(class_)
  • 动态创建类和对象
    • 动态创建类
    • 动态创建对象
  • 方法操作(method_)
  • 实例变量操作(ivar_)
    • 通过偏移量ivar_offset、获取/修改任意一个成员变量。
  • 属性操作(property_)
    • 属性的动态添加与获取
  • 协议操作(protocol_)
    • 动态的创建一个协议
  • 库操作
  • 选择器操作(sel_)
  • 语言特性
  • 关联策略
  • 编码类型

类和对象

为什么runtime的博客、要聊究类和对象?
或许写出一段swizzle代码更直观

- (void)runtime_swizzle_test {
    Method method1 = class_getInstanceMethod([self class], @selector(func1));
    Method method2 = class_getInstanceMethod([self class], @selector(func2));
    method_exchangeImplementations(method1, method2);
}

所有的runtime方法、只要与方法操作相关。
无一例外都需要使用Method或者其结构体内部的IMP、SEL等指针。
而这个Method恰恰存放在的结构体中。

除此之外:

  • class_getInstanceMethod(获取Method)、class_copyIvarList(获取属性列表)。也都是建立在class_也就是类操作之下。
  • object_setIvar(属性赋值)。则是建立在object_、也就是对象操作之下。

所以、大概可以知道了解类和对象对理解runtime有多么大的帮助了。

我们每天都在创建类、或者类的实例对象。但类和实例对象到底是什么?

[XXX new]或者[[XXX alloc]init]就是实例对象?
说得对、但是这浅显了。相信如果面试官如此提问、你绝壁不敢这么回答的。

  • 实例对象(id)
id obj = self;

这就是我们的OC对象。

/// A pointer to an instance of a class.
/// 指向一个实例的指针
typedef struct objc_object *id;


/// Represents an instance of a class.
/// 表示类的实例。
struct objc_object {
    Class _Nonnull isa  OBJC_ISA_AVAILABILITY;
};
  • 实例对象在被创建的过程中会拷贝实例所属的类的成员变量、但并不拷贝类定义的方法
  • isa
    整个id对象的结构体内部、只有一个isa指针、指向了其所属的Class
  • 调用实例方法时。
    系统会根据实例的isa指针去类的方法列表及父类的方法列表中寻找与消息对应的selector指向的方法。
  • 类对象(Class)
Class class = [self class];

这就是我们每天都在使用的Class、也就是类。点进去

#import <objc/objc.h>
/// An opaque type that represents an Objective-C class.
typedef struct objc_class *Class;

可以看到Class本质是一个objc_class的结构体。继续
这里为了看着舒服、我把一些标注的宏删掉了。

struct objc_class {
    Class  isa ;  // 指向所属类的指针(_Nonnull)
    Class super_class;  // 父类(_Nullable)
    const char *  name; // 类名(_Nonnull)
    long version;  // 类的版本信息(默认为0)
    long info;  // 类信息(供运行期使用的一些位标识)
    long instance_size;  // 该类的实例变量大小
    struct objc_ivar_list * ivars;  // 该类的成员变量链表(_Nullable)
    struct objc_method_list ** methodLists ;  // 方法定义的链表(_Nullable)
    struct objc_cache * cache  // 方法缓存(_Nonnull)
    struct objc_protocol_list * protocols;  // 协议链表(_Nullable)
} ;
  • isa
    Class的结构体中也有isa指针、但和对象不同的是。Classisa指向的是Class所属的类、即元类。
  • 调用类方法时
    系统会通过该isa指针从元类中寻找方法对应的函数指针。
  • super_class
    父类指针、如果该类已经是最顶层的根类(如NSObject或NSProxy)、则 super_class为NULL。
  • ivars
    objc_ivar_list类型的指针,用来存储这个类中所有成员变量的信息。
//变量列表
struct objc_ivar_list {
    int ivar_count ; 
#ifdef __LP64__
    int space ;
#endif
    /* variable length structure */
    struct objc_ivar ivar_list[1] ;
}

//单个变量信息
struct objc_ivar {
    char * _Nullable ivar_name ;
    char * _Nullable ivar_type ;
    int ivar_offset ; //基地址偏移字节
#ifdef __LP64__
    int space ;
#endif
}             
  • methodLists
//方法列表
struct objc_method_list {
    struct objc_method_list * _Nullable obsolete ;

    int method_count  ;
#ifdef __LP64__
    int space ;
#endif
    /* variable length structure */
    struct objc_method method_list[1]  
}   
//单个方法的信息
struct objc_method {
    SEL _Nonnull method_name ;
    char * _Nullable method_types ;
    IMP _Nonnull method_imp  ;
} 
  • objc_cache
struct objc_cache {
    unsigned int mask /* total = mask + 1 */                 OBJC2_UNAVAILABLE;
    unsigned int occupied                                    OBJC2_UNAVAILABLE;
    Method buckets[1]                                        OBJC2_UNAVAILABLE;
};

用于缓存最近使用的方法。系统在调用方法时会先去cache中查找、在没有查找到时才会去methodLists中遍历获取需要的方法。

  • 元类(Meta Class)

上面我们看到了。在objc_class中也有一个isa指针、这说明Class类本身也是一个对象。为了处理类和对象的关系、Runtime 库创建了一种叫做Meta Class(元类) 的东西、类对象所属的类就叫做元类。Meta Class表述了类对象本身所具备的元数据。

  • 元类用来储存类方法
  • 元类也是一个对象、所以才能调用他的方法。
  • 元类的元类、为根元类。
  • 根元类的元类、是其本身。

更多的关于元类的就暂且打住~毕竟对于runtime。了解这些元类和类的关系已经够用了。

  • 类与对象的总结
类与对象

还有一张特别经典的图


类关系示意图
  • 规则一: 实例对象的isa指向该类、类的isa指向元类(metaClass)。

  • 规则二: 类的superClass指向其父类、如果该类为根类则值为nil。

  • 规则三: 元类的isa指向根元类、如果该元类是根元类则指向自身。

  • 规则四: 元类的superClass指向父元类、若根元类则指向该根类。


runtime正题

runtime方法的前缀

runtime方法有很多、最方便归纳的方式就是通过前缀来确定其作用对象。

对象操作方法(object_)

包含了对对象的所有操作。
拷贝、释放、获取所属类(isa)、所属成员变量的操作(初始化的时候会copy成员变量列表)

/** 
 * 返回给定对象的副本。
 */
id object_copy(id _Nullable obj, size_t size)
/** 
 * 释放对象内存
 */
id object_dispose(id _Nullable obj)
/** 
 * 返回对象的类--也就是上文说的isa指针所指的类
 */
Class object_getClass(id _Nullable obj) 
/** 
 * 为对象设置一个新的类
 * 其实就等于 NSArray  *arr = [NSMutableArray new];
 * 这个arr依旧是_M类型、但是已经不能使用addobj方法了、起码在编译阶段是。
 */
Class object_setClass(id _Nullable obj, Class _Nonnull cls) 
/** 
 * 判断一个对象是否为Class类型的对象
 */
BOOL object_isClass(id _Nullable obj)
/** 
 * 返回一个对象中指定实例变量(Ivar)的值
 */
id object_getIvar(id _Nullable obj, Ivar _Nonnull ivar) 
/*
* 为对象设置一个实例变量的值
*/
void object_setIvar(id _Nullable obj, Ivar _Nonnull ivar, id _Nullable value) 
/*
*  10.0以后多了一个方法
*/
void object_setIvarWithStrongDefault(id _Nullable obj, Ivar _Nonnull ivar,
                                id _Nullable value) 
/*
* 非ARC下的两个set方法
*/
void object_setInstanceVariable(id _Nullable obj, const char * _Nonnull name,
                           void * _Nullable value);
void object_setInstanceVariableWithStrongDefault(id _Nullable obj,
                                            const char * _Nonnull name,
                                            void * _Nullable value)
/*
* 非ARC下的get方法
*/
Ivar 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;
其中有比较特殊的:
  • Class object_getClass(id _Nullable obj)
    这个方法可以获得对象结构体内部isa指针所指、也就是对象所属的类。(具体是啥可以翻回去看)。
    但需要注意的是、这个和我们平时用的[self class]方法、并不完全等价。
+ (Class)class {
    return self; // 返回自身指针
}
- (Class)class {
    return object_getClass(self); // 调用'object_getClass'返回isa指针
}

也就是说、通过[Class object_getClass]是可以获取元类的、但是通过[Class class]则不行。

  • void object_setIvar(id _Nullable obj, Ivar _Nonnull ivar, id _Nullable value)
    为对象设置一个实例变量的值
    但依旧不能为不存在的变量赋值
    当获取一个不存在实例变量时、Ivar将等于0x0000000000000000。自然没办法对其进行赋值。

全局操作方法(objc_)

这里的方法并不基于对象、也不需要以对象作为参数。
基本都是一些类相关的获取。

/*
* 返回指定的Class
* 如果未注册命名类的定义,则该函数调用类处理程序。
* 然后再次检查是否注册了类。
*/
Class  objc_getClass(const char * _Nonnull name)
/*
* 返回指定的元类Class
*/
Class objc_getMetaClass(const char * _Nonnull name)
/*
* 返回指定的Class
* 不调用类处理程序回调。
*/
Class objc_lookUpClass(const char * _Nonnull name)
/*
* 返回指定的Class
* 如果没有找到类,就会杀死进程。
*/
Class objc_getRequiredClass(const char * _Nonnull name)
/*
* 获取所有已经注册类的列表
*/
int objc_getClassList(Class _Nonnull * _Nullable buffer, int bufferCount)
/*
* 获取所有已经注册类的实例列表
*/
Class *objc_copyClassList(unsigned int * _Nullable outCount)
其中有比较特殊的:
  • 获取所有已经注册的类--数量.
int objc_getClassList(Class _Nonnull * _Nullable buffer, int bufferCount)
int numClasses = 0, newNumClasses = objc_getClassList(NULL, 0);
Class *classes = NULL;
while (numClasses < newNumClasses) {
    numClasses = newNumClasses;
    classes = (Class *)realloc(classes, sizeof(Class) * numClasses);
    newNumClasses = objc_getClassList(classes, numClasses);
    
    for (int i = 0; i < numClasses; i++) {
        const char *className = class_getName(classes[i]);
        if (class_getSuperclass(classes[i]) == [UIScrollView class]) {
            //打印所有UIScrollView的子类
            NSLog(@"subclass of UIScrollView : %s", className);
        }
    }
}
free(classes);
  • 获取所有已经注册的类--实例列表.
Class *objc_copyClassList(unsigned int * _Nullable outCount)
unsigned int outCount;
Class *classes = objc_copyClassList(&outCount);
for (int i = 0; i < outCount; i++) {
    if (class_getSuperclass(classes[i]) == [UIScrollView class]) {
        //打印所有UIScrollView的子类
        NSLog(@"subclass of UIScrollView : %s", class_getName(classes[i]));
    }
}
free(classes);

类对象操作方法(class_)

这里的所有操作都针对类对象

/** 
 * 返回类的名称
 */
char * class_getName(Class _Nullable cls) 
/** 
 * 判断该类对象是否为元类
 */
BOOL class_isMetaClass(Class _Nullable cls) 
/** 
 * 获取结构体中的super_class、也就是父类对象指针
 */
Class class_getSuperclass(Class _Nullable cls) 
/** 
 * 给一个类对象设置新的父类指针
 * 返回旧的父类
 * 这个方法已经被弃用并且apple明确指出不应该使用。
 */
Class class_setSuperclass(Class _Nonnull cls, Class _Nonnull newSuper) 
/** 
 * 获取类对象的版本号
 * 没发现啥用处、不过测试起来
 * 所有的元类都为7
 * 普通类NSDateFormatter最高(41)、并且大于5的只有它一个
 */
int class_getVersion(Class _Nullable cls)
/** 
 * 为一个类设置版本号
 */
void class_setVersion(Class _Nullable cls, int version)
/** 
 * 获取一个类的实例大小
 */
size_t class_getInstanceSize(Class _Nullable cls)
/** 
 * 获取类中指定名称实例成员变量的信息(Ivar)
 */
Ivar class_getInstanceVariable(Class _Nullable cls, const char * _Nonnull name)
/** 
 * 获取类"成员变量"的信息(Ivar)
 * 但是好像OC中没有这种东西
 */
Ivar class_getClassVariable(Class _Nullable cls, const char * _Nonnull name) 
/** 
 * 获取全部成员变量列表
 * 但是不包括父类
 */
Ivar * class_copyIvarList(Class _Nullable cls, unsigned int * _Nullable outCount)
/** 
 * 查找某个实例方法
 */
Method class_getInstanceMethod(Class _Nullable cls, SEL _Nonnull name)
/** 
 * 查找某个类方法
 */
Method class_getClassMethod(Class _Nullable cls, SEL _Nonnull name)
/** 
 * 查找某个实例方法的实现(IMP)
 */
IMP class_getMethodImplementation(Class _Nullable cls, SEL _Nonnull name) 
/** 
 * 查找某个实例方法的实现(IMP)
 * 不过在“ arm64”也就是现在的机器上报报红
 */
IMP class_getMethodImplementation_stret(Class _Nullable cls, SEL _Nonnull name) 
/** 
 * 查找某个实例方法的实现(IMP)
 * 不过在“ arm64”也就是现在的机器上报报红
 */
BOOL class_respondsToSelector(Class _Nullable cls, SEL _Nonnull sel) 
/** 
 * 返回该类所有方法(结构体中的objc_method_list)
 */
Method * class_copyMethodList(Class _Nullable cls, unsigned int * _Nullable outCount)
/** 
 * 判断类是否实现了某个协议
 * 其实就是[self conformsToProtocol:@protocol(UITableViewDelegate)]的底层
 */
BOOL class_conformsToProtocol(Class _Nullable cls, Protocol * _Nullable protocol) 
/** 
 * 返回该类的协议列表(结构体中的objc_protocol_list)
 */
Protocol * * class_copyProtocolList(Class _Nullable cls, unsigned int * _Nullable outCount)
/** 
 * 返回该类的指定属性(注意不是成员变量)
 */
objc_property_t class_getProperty(Class _Nullable cls, const char * _Nonnull name)
/** 
 * 返回该类的属性列表
 */
objc_property_t * class_copyPropertyList(Class _Nullable cls, unsigned int * _Nullable outCount)
/** 
* 返回该类成员变量的布局 
*/
uint8_t * class_getIvarLayout(Class _Nullable cls)
/** 
* 返回该类weak成员变量的布局 
*/
uint8_t * class_getWeakIvarLayout(Class _Nullable cls)
/** 
* 添加方法 如果返回YES则添加成功
*/
BOOL class_addMethod(Class _Nullable cls, SEL _Nonnull name, IMP _Nonnull imp, 
                const char * _Nullable types) 
/** 
* 替换方法实现(IMP)
* 如果之前存在该方法--直接替换
* 如果之前不存在该方法--添加方法
*/
IMP class_replaceMethod(Class _Nullable cls, SEL _Nonnull name, IMP _Nonnull imp, 
                    const char * _Nullable types) 
/** 
* 添加成员变量
* 这个函数只能在objc_allocateClassPair和objc_registerClassPair之前调用。
* 也间接的说明、已经注册的类不能动态的添加成员变量
*/
BOOL class_addIvar(Class _Nullable cls, const char * _Nonnull name, size_t size, 
              uint8_t alignment, const char * _Nullable types) 
/** 
* 添加协议
*/
BOOL class_addProtocol(Class _Nullable cls, Protocol * _Nonnull protocol) 
/** 
* 添加属性
* 注意这里属性的意义。和Category中的一样、并不包含成员变量和setget方法
*/
BOOL class_addProperty(Class _Nullable cls, const char * _Nonnull name,
                  const objc_property_attribute_t * _Nullable attributes,
                  unsigned int attributeCount)
/** 
* 替换属性
* 注意这里属性的意义。和Category中的一样、并不包含成员变量和setget方法
*/
void class_replaceProperty(Class _Nullable cls, const char * _Nonnull name,
                      const objc_property_attribute_t * _Nullable attributes,
                      unsigned int attributeCount)
/** 
* 设置变量布局
*/
void class_setIvarLayout(Class _Nullable cls, const uint8_t * _Nullable layout)
/** 
* 设置weak变量布局
*/
void class_setWeakIvarLayout(Class _Nullable cls, const uint8_t * _Nullable layout)
/** 
* 由CoreFoundation的toll-free桥接使用。
* 不能主动调用
*/
Class objc_getFutureClass(const char * _Nonnull name) 
其中有比较特殊的:
  • class_getSuperclass[self superclass]
    class_getSuperclass等价于[self superclass]都直接获取指向父类对象的指针。
    并且由之前的结构图(类关系示意图)可知
    1、元类的元类为NSObject
    2、元类的父类为正常结构的父类
    可以验证一下
NSLog(@"类对象--%@",[self class]);
NSLog(@"父类对象--%@",[self superclass]);
NSLog(@"父类对象--%@",[[self superclass] superclass]);
NSLog(@"父类对象--%@",[[[self superclass] superclass] superclass]);
NSLog(@"父类对象--%@",[[[[self superclass] superclass] superclass] superclass]);

//获取self的类 ==》 ViewController(类)  再获取其元类 ViewController(元类)
Class class = object_getClass(object_getClass(self));
NSLog(@"元类对象--%@",class);
NSLog(@"如果再继续获取元类---%@",object_getClass(class));
NSLog(@"父元类对象--%@",class_getSuperclass(class));
NSLog(@"父元类对象--%@",class_getSuperclass(class_getSuperclass(class)));
NSLog(@"父元类对象--%@",class_getSuperclass(class_getSuperclass(class_getSuperclass(class))));
NSLog(@"父类对象--%@",class_getSuperclass(class_getSuperclass(class_getSuperclass(class_getSuperclass(class)))));
NSLog(@"父类对象--%@",class_getSuperclass(class_getSuperclass(class_getSuperclass(class_getSuperclass(class_getSuperclass(class))))));

打印结果:

类对象--ViewController
父类对象--UIViewController
父类对象--UIResponder
父类对象--NSObject
父类对象--(null)
元类对象--ViewController
如果再继续获取元类---NSObject
父元类对象--UIViewController
父元类对象--UIResponder
父元类对象--NSObject
父类对象--NSObject
父类对象--(null)

动态创建类和对象

  • 动态创建类
/** 
*  创建一个新的类、以及其元类
*/
Class objc_allocateClassPair(Class _Nullable superclass, const char * _Nonnull name, 
                       size_t extraBytes) 
/** 
*  注册一个类
*/
void objc_registerClassPair(Class _Nonnull cls) 
/** 
*  用于KVO
*  禁止主动调用
*/
Class objc_duplicateClass(Class _Nonnull original, const char * _Nonnull name,
                    size_t extraBytes)
/** 
*  销毁一个类
*  如果程序运行中还存在类或其子类的实例,则不能调用针对类调用该方法
*/
void objc_disposeClassPair(Class _Nonnull cls) 

objc_allocateClassPair函数:如果我们要创建一个根类,则superclass指定为Nil。extraBytes通常指定为0,该参数是分配给类和元类对象尾部的索引ivars的字节数。
为了创建一个新类,我们需要调用objc_allocateClassPair
然后使用诸如class_addMethod,class_addIvar等函数来为新创建的类添加方法、实例变量和属性等。
完成这些后,我们需要调用objc_registerClassPair函数来注册类,之后这个新类就可以在程序中使用了。
有两点需要注意:

  • 实例方法和实例变量应该添加到类自身上,而类方法应该添加到类的元类上。
  • class_addIvar必须使用在类生成后objc_allocateClassPair、注册前objc_registerClassPair。因为程序不允许一个已经注册、分配好内存的类进行扩容。
    ※※※※这也是为什么类别、添加了属性却不能添加成员变量的原因

举个例子:

Class cls = objc_allocateClassPair(MyClass.class, "MySubClass", 0);
class_addMethod(cls, @selector(submethod1), (IMP)imp_submethod1, "v@:");
class_replaceMethod(cls, @selector(method1), (IMP)imp_submethod1, "v@:");
class_addIvar(cls, "_ivar1", sizeof(NSString *), log(sizeof(NSString *)), "i");
 
objc_property_attribute_t type = {"T", "@\"NSString\""};
objc_property_attribute_t ownership = { "C", "" };
objc_property_attribute_t backingivar = { "V", "_ivar1"};
objc_property_attribute_t attrs[] = {type, ownership, backingivar};
 
class_addProperty(cls, "property2", attrs, 3);
objc_registerClassPair(cls);
 
id instance = [[cls alloc] init];
[instance performSelector:@selector(submethod1)];
[instance performSelector:@selector(method1)];
  • 动态创建对象
/** 
* 实例化一个类对象
* 基本等价于[XXX new]
* 第二个参数表示分配的额外字节数。这些额外的字节可用于存储在类定义中所定义的实例变量之外的实例变量。
*/
id class_createInstance(Class _Nullable cls, size_t extraBytes)
/** 
* 在指定位置实例化一个类对象
*/
id objc_constructInstance(Class _Nullable cls, void * _Nullable bytes) 
/** 
* 销毁一个类的实例
* 但不会释放并移除任何与其相关的引用
*/
void * _Nullable objc_destructInstance(id _Nullable obj) 

方法操作(method_)

大概我们应该先了解一下Method

struct objc_method {
    SEL _Nonnull method_name;  //方法名、借此才能从方法列表锁定单个Method
    char * _Nullable method_types; //方法返回值以及参数的描述
    IMP _Nonnull method_imp; //方法实现.(方法实现所在的位置)
} 

Method包含了一个方法所需要的全部要素。并且可以自由获取任意其一。

/** 
* 返回方法的名称
*/
SEL method_getName(Method _Nonnull m) 
/** 
* 返回方法的实现(IMP)
*/
IMP method_getImplementation(Method _Nonnull m) 
/** 
* 返回描述方法参数和返回类型的字符串
*/
char * method_getTypeEncoding(Method _Nonnull m) 
/** 
* 返回方法参数个数
* 参数最低为两个(每个方法都有self以及_cmd两个隐性参数)
*/
int method_getNumberOfArguments(Method _Nonnull m)
/** 
* 返回方法返回类型
* 类型会传递给第二个参数"dst"
*/
void method_getReturnType(Method _Nonnull m, char * _Nonnull dst, size_t dst_len)
/** 
* 返回方法返回类型
* 第二个参数index为参数位置、类型会传递给第三个参数"dst"(越位会返回'\0')
*/
void method_getArgumentType(Method _Nonnull m, unsigned int index, 
                       char * _Nullable dst, size_t dst_len) 
/** 
* 为一个Method设置具体实现(IMP)
*/
IMP method_setImplementation(Method _Nonnull m, IMP _Nonnull imp) 
/** 
* ※※※大名鼎鼎的swizzle※※※
*/
void method_exchangeImplementations(Method _Nonnull m1, Method _Nonnull m2) 

能说的不多:

  • swizzle的本质?

method_exchangeImplementations(Method _Nonnull m1, Method _Nonnull m2)的注释里说很明白了.

 *  IMP imp1 = method_getImplementation(m1);
 *  IMP imp2 = method_getImplementation(m2);
 *  method_setImplementation(m1, imp2);
 *  method_setImplementation(m2, imp1);

本质就是交换了两个方法的实现。
所以说用method_setImplementation设置IMP的话、也可以实现一个IMP实现对应多个SEL。

实例变量操作(ivar_)

Method一样。我们也来看看ivar的结构体、看看ivar能做什么。


/// An opaque type that represents an instance variable.
typedef struct objc_ivar *Ivar;

struct objc_ivar {
    char * _Nullable ivar_name; //变量名
    char * _Nullable ivar_type; //变量类型
    int ivar_offset  //基地址偏移字节
#ifdef __LP64__
    int space ; //占用空间
#endif
}       

实例变量操作的API特别少(因为获取/设置变量的值都由ojbect_进行)

/** 
* 返回实例变量的名称
*/
char * _Nullable ivar_getName(Ivar _Nonnull v) 
/** 
* 返回实例变量的类型
*/
char * _Nullable ivar_getTypeEncoding(Ivar _Nonnull v) 
/** 
* 返回实例变量的偏移量
*/
ptrdiff_t ivar_getOffset(Ivar _Nonnull v) 
  • 基地址偏移字节ivar_offset
    • 先聊聊类的布局
      在编译我们的类时、编译器生成了一个 ivar布局、显示了在类中从哪可以访问我们的 ivars 。如下图:

      我们增加了父类的ivar、这个时候布局就出错了。

      虽然runtime可以在类加载的时候通过Non Fragile ivars进行重新布局。
但是!!!、对已经加载完成(注册好)的类、增加成员变量一样会导致布局出错。

这也是为什么类别不能添加属性的根本原因。

  • 通过偏移量ivar_offset、获取/修改任意一个成员变量。

我们可以通过对象地址 + ivar偏移字节的方法、获取任意一个成员变量。

@interface Person : NSObject
{
    @private int age_int;
    @private NSString * age_str;
    @public int age_public;
}
@end

@implementation Person
- (instancetype)init
{
    self = [super init];
    if (self) {
        age_public = 1;
        age_int = 1;
        age_str = @"1";
    }
    return self;
}

- (NSString *)description
{
    NSLog(@"person pointer==%p", self);
    NSLog(@"age_int pointer==%p", &age_int);
    return [NSString stringWithFormat:@"age_int==%d", age_int];
}
@end

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // insert code here...
        //私有成员变量--基本类型、可以使用偏移量
        Person * person = [Person new];
        Ivar age_int_ivar = class_getInstanceVariable(object_getClass(person), "age_int");
        int *age_int_pointer = (int *)((__bridge void *)(person) + ivar_getOffset(age_int_ivar));
        
        
        NSLog(@"age_int offset==%td", ivar_getOffset(age_int_ivar));
        
        * age_int_pointer = 10;
        NSLog(@"%@",person);
    }
    return 0;
}

打印结果:

age_int offset==8
person pointer==0x10061b070
age_int pointer==0x10061b078
age_int==10

属性操作(property_)

/** 
 * 返回属性名
 */
char * _Nonnull property_getName(objc_property_t _Nonnull property) 
/** 
 * 返回属性描述(也就是属性的属性)
 * @property (nonatomic ,readonly ,strong) NSObject * obj;
 * "T@\"NSObject\",R,N,V_obj"
 */
char * _Nullable property_getAttributes(objc_property_t _Nonnull property) 
/** 
 * 返回属性描述数组
 */
objc_property_attribute_t * _Nullable
property_copyAttributeList(objc_property_t _Nonnull property,
                           unsigned int * _Nullable outCount)
/** 
 * 返回属性描述(也就是属性的属性)
 */
char * _Nullable property_copyAttributeValue(objc_property_t _Nonnull property,
                            const char * _Nonnull attributeName)
  • 属性的动态添加与获取
    注意这里的添加和category一样、有名无实。
objc_property_attribute_t type = { "T", "@\"NSString\"" };//属性类型为NSString
objc_property_attribute_t ownership = { "C", "copy" }; // C = copy
objc_property_attribute_t backingivar  = { "V", "_littleName" };//_littleName为Person类的全局变量,这里是让新属性与之关联
objc_property_attribute_t attrs[] = { type, ownership, backingivar };
class_addProperty([Person class], "littleName", attrs, 3);
//验证是否添加成功
objc_property_t pt =  class_getProperty([Person class], "littleName");
NSLog(@"property's name: %s", property_getName(pt));
NSLog(@"property'attribute:%s",property_getAttributes(pt));
NSLog(@"property'release:%s",property_copyAttributeValue(pt, "C"));
NSLog(@"property'type:%s",property_copyAttributeValue(pt, "T"));
NSLog(@"property'value:%s",property_copyAttributeValue(pt, "V"));

打印结果:

property's name: littleName
property'attribute:T@"NSString",Ccopy,V_littleName
property'release:copy
property'type:@"NSString"
property'value:_littleName

协议操作(protocol_)

/** 
 * 返回一个协议
 */
Protocol * _Nullable objc_getProtocol(const char * _Nonnull name)
/** 
 * 返回runtime已知的所有协议的数组
 */
Protocol ** _Nullable objc_copyProtocolList(unsigned int * _Nullable outCount)
/** 
 * 判断一个协议是否遵循了另一个协议
 */
BOOL protocol_conformsToProtocol(Protocol * _Nullable proto,
                            Protocol * _Nullable other)
/** 
 * 判断两个协议是否相同
 */
BOOL protocol_isEqual(Protocol * _Nullable proto, Protocol * _Nullable other)
/** 
 * 返回协议的名称
 */
char * _Nonnull protocol_getName(Protocol * _Nonnull proto)
/** 
 * 返回协议中指定方法的描述
 */
struct objc_method_description protocol_getMethodDescription(Protocol * _Nonnull proto, SEL _Nonnull aSel,
                              BOOL isRequiredMethod, BOOL isInstanceMethod)
/** 
 * 返回协议中指定规则的所有方法描述
 */
struct objc_method_description * _Nullable protocol_copyMethodDescriptionList(Protocol * _Nonnull proto,
                                   BOOL isRequiredMethod,
                                   BOOL isInstanceMethod,
                                   unsigned int * _Nullable outCount)
/** 
 * 返回协议的指定属性
 */
objc_property_t protocol_getProperty(Protocol * _Nonnull proto,
                     const char * _Nonnull name,
                     BOOL isRequiredProperty, BOOL isInstanceProperty)
/** 
 * 返回协议中所有属性的列表
 */
objc_property_t * protocol_copyPropertyList(Protocol * _Nonnull proto,
                          unsigned int * _Nullable outCount)
/** 
 * 返回协议中所有指定规则属性的列表
 */
objc_property_t * protocol_copyPropertyList2(Protocol * _Nonnull proto,
                           unsigned int * _Nullable outCount,
                           BOOL isRequiredProperty, BOOL isInstanceProperty)
/** 
 * 返回协议所遵循的协议列表(比如`NSPortDelegate`遵循了`NSObject`)
 */
Protocol ** protocol_copyProtocolList(Protocol * _Nonnull proto,
                          unsigned int * _Nullable outCount)
/** 
 * 创建一个协议(未注册)
 */
Protocol * objc_allocateProtocol(const char * _Nonnull name) 
/** 
 * 向协议中添加方法
 */
void protocol_addMethodDescription(Protocol * _Nonnull proto, SEL _Nonnull name,
                              const char * _Nullable types,
                              BOOL isRequiredMethod, BOOL isInstanceMethod) 
/** 
 * 向协议中添加依赖协议
 */
void protocol_addProtocol(Protocol * _Nonnull proto, Protocol * _Nonnull addition) 
/** 
 * 向协议中添属性
 */
void protocol_addProperty(Protocol * _Nonnull proto, const char * _Nonnull name,
                     const objc_property_attribute_t * _Nullable attributes,
                     unsigned int attributeCount,
                     BOOL isRequiredProperty, BOOL isInstanceProperty)
/** 
 * 注册一个协议
 */
void objc_registerProtocol(Protocol * _Nonnull proto) 
  • 动态的创建一个协议
Protocol * protocol = objc_getProtocol("KTDelegate");
NSLog(@"%@",protocol);

if (!protocol) {
    //创建
    protocol = objc_allocateProtocol("KTDelegate");
    protocol_addMethodDescription(protocol, @selector(protocol_func), "desc", YES, YES);
    protocol_addProtocol(protocol, objc_getProtocol("NSObject"));
    
    
    objc_property_attribute_t type = { "T", "@\"NSString\"" };//属性类型为NSString
    objc_property_attribute_t ownership = { "C", "copy" }; // C = copy
    objc_property_attribute_t backingivar  = { "V", "_littleName" };
    objc_property_attribute_t attrs[] = { type, ownership, backingivar };
    
    protocol_addProperty(protocol, "_littleName", attrs, 3, YES, YES);
    //注册
    objc_registerProtocol(protocol);
    NSLog(@"register_end");
}

protocol = objc_getProtocol("KTDelegate");
NSLog(@"protocol == %@",protocol);
NSLog(@"protocol_name == %s",protocol_getName(protocol));
NSLog(@"protocol_desc == %s",protocol_getMethodDescription(protocol, @selector(protocol_func), YES, YES));
NSLog(@"protocol_property == %s",property_getName(protocol_getProperty(protocol, "_littleName", YES, YES)));

打印结果:

(null)
register_end
protocol == <Protocol: 0x100462f80>
protocol_name == KTDelegate
protocol_desc == protocol_func
protocol_property == _littleName

库操作

/** 
 * 返回所有加载在Objective-C框架和动态库上的名称
 * char ** arr = objc_copyImageNames(&num);
 */
char ** objc_copyImageNames(unsigned int * _Nullable outCount) 
/** 
 * 返回一个类所属的动态库名称
 */
char * class_getImageName(Class _Nullable cls) 
/** 
 * 返回一个库中所有类的名称
 */
char ** objc_copyClassNamesForImage(const char * _Nonnull image,
                            unsigned int * _Nullable outCount) 

简单举个例子

char * libName = class_getImageName(objc_getClass("NSString"));
int num = 0;
char ** libarr = objc_copyClassNamesForImage(libName, &num);
for (int i = 0; i < num; i ++) {
    NSLog(@"%s",libarr[i]);
}

选择器操作(sel_)

/** 
 * 返回一个选择器所指定的方法名称
 */
char * sel_getName(SEL _Nonnull sel)
/** 
 * 注册一个方法选择器
 */
SEL sel_registerName(const char * _Nonnull str)
/** 
 * 比较两个选择器
 * SEL sel1 = @selector(fun1:);
 * SEL sel2 = sel_registerName("fun1:");
 * BOOL b = sel_isEqual(sel1, sel2);
 */
BOOL sel_isEqual(SEL _Nonnull lhs, SEL _Nonnull rhs) 

语言特性

/** 
 * 当一个obj突变发生时,这个函数会抛出异常
 */
void objc_enumerationMutation(id _Nonnull obj) 
/** 
 * 突变处理回调
 */
void objc_setEnumerationMutationHandler(void (*_Nullable handler)(id _Nonnull )) 
/** 
 * objc_msgForward方法的调用
 */
void objc_setForwardHandler(void * _Nonnull fwd, void * _Nonnull fwd_stret) 
/** 
 * 就是直接把一个block作为IMP
 */
IMP imp_implementationWithBlock(id _Nonnull block)
/** 
 * 返回(使用imp_implementationWithBlock创建的)IMP对应的block
 * 举个例子:
    void (^block)(id ,SEL) = ^(id self ,SEL _sel){
         NSLog(@"%s",sel_getName(_sel));
    };
    IMP imp = imp_implementationWithBlock(block);
    class_addMethod([Person class], @selector(imp_fun), imp, "v@:@");
    [[Person new] performSelector:@selector(imp_fun)];
    void (^block2)(id ,SEL)= imp_getBlock(imp);
    block2([Person new],@selector(fun_block2));

    输出:
    imp_fun
    fun_block2
 */
id imp_getBlock(IMP _Nonnull anImp);
/** 
 *   移除(使用imp_implementationWithBlock创建的)IMP对应的block
 */
BOOL imp_removeBlock(IMP _Nonnull anImp)
/** 
 *   在表达式中使用__weak变量时将自动使用该函数
 *   该函数加载一个弱指针引用的对象,并在对其做retain和autoreleasing操作后返回它。这样,对象就可以在调用者使用它时保持足够长的生命周期
    UIView * view = [UIView new];
    __weak UIView * w_view = view;
    NSLog(@"retain  count = %ld\n",CFGetRetainCount((__bridge  CFTypeRef)(w_view)));
    objc_loadWeak(&w_view);
    NSLog(@"retain  count = %ld\n",CFGetRetainCount((__bridge  CFTypeRef)(w_view)));
    __strong UIView * s_view = w_view;
    __weak UIView * ww_view = w_view;
    NSLog(@"retain  count = %ld\n",CFGetRetainCount((__bridge  CFTypeRef)(w_view)));
 */
id objc_loadWeak(id _Nullable * _Nonnull location)
/** 
 *   可以用来为__weak变量赋值
 */
id objc_storeWeak(id _Nullable * _Nonnull location, id _Nullable obj) 

关联策略

// 关联策略
typedef OBJC_ENUM(uintptr_t, objc_AssociationPolicy) {

    OBJC_ASSOCIATION_ASSIGN = 0,           //@property(assign)。
    OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1, //@property(strong, nonatomic)
    OBJC_ASSOCIATION_COPY_NONATOMIC = 3,  //@property(copy, nonatomic)
    OBJC_ASSOCIATION_RETAIN = 01401,       //@property(strong,atomic)
    OBJC_ASSOCIATION_COPY = 01403         //@property(copy, atomic)
};

/** 
 *   将一个key&&value关联到另一个对象上、并且设置关联策略
 */
void objc_setAssociatedObject(id _Nonnull object, const void * _Nonnull key,
                         id _Nullable value, objc_AssociationPolicy policy)
/** 
 *   将关联的对象取出
 */
id _Nullable objc_getAssociatedObject(id _Nonnull object, const void * _Nonnull key)
/** 
 *   移除关联所有对象
 */
void objc_removeAssociatedObjects(id _Nonnull object)

这个就是我们经常用的、给类别绑定属性的

static char *key = "key";
objc_setAssociatedObject(self, &key, @"hello world", OBJC_ASSOCIATION_COPY_NONATOMIC);
NSString *associated = objc_getAssociatedObject(self, &key);
NSLog(@"objc_getAssociatedObject===%@",associated);

编码类型

#define _C_ID       '@' // 代表对象类型
#define _C_CLASS    '#' // 代表类对象 (Class)
#define _C_SEL      ':' // 代表方法selector (SEL)
#define _C_CHR      'c' // 代表char类型
#define _C_UCHR     'C' // 代表unsigned char类型
#define _C_SHT      's' // 代表short类型
#define _C_USHT     'S' // 代表unsigned short类型
#define _C_INT      'i' // 代表int类型
#define _C_UINT     'I' // 代表unsigned int类型
#define _C_LNG      'l' // 代表long类型,在64位处理器上也是按照32位处理
#define _C_ULNG     'L' // 代表unsigned long类型
#define _C_LNG_LNG  'q' // 代表long long类型
#define _C_ULNG_LNG 'Q' // 代表unsigned long long类型
#define _C_FLT      'f' // 代表float类型
#define _C_DBL      'd' // 代表double类型
#define _C_BFLD     'b' //
#define _C_BOOL     'B' // 代表C++中的bool或者C99中的_Bool
#define _C_VOID     'v' // 代表void类型
#define _C_UNDEF    '?' // 代表未知类型
#define _C_PTR      '^' // 代表指针类型
#define _C_CHARPTR  '*' // 代表字符串类型 (char *)
#define _C_ATOM     '%' //
#define _C_ARY_B    '[' // 代表array
#define _C_ARY_E    ']'
#define _C_UNION_B  '(' // 代表UNION
#define _C_UNION_E  ')'
#define _C_STRUCT_B '{' // 代表结构体
#define _C_STRUCT_E '}'
#define _C_VECTOR   '!' // 代表矢量类型
#define _C_CONST    'r' // 代表常量类型

最后

本文主要是自己的学习与总结。如果文内存在纰漏、万望留言斧正。如果不吝赐教小弟更加感谢。

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

推荐阅读更多精彩内容