深入理解OC的运行时(Runtime)

序(吐槽)

  前段时间刚面试了几人, 面试过程中遇到了比较让人尴尬的问题,这里先吐槽一些:

  • 技术层面(部分)

 1. runtime方面,网上的runtime文章基本都是千篇一律的, 很多都是死记硬背为了应付面试, 没有实质性的东西; 其实也不是说听不进去, 针对于个人来说, runtime其实是一种编程思想, 是动态多态的一种实现机制, 所以想听到的更多的是关于自己对语言的独特见解, 个人看来这十分重要, 可能是以后在IT领域所能达到的高度

 2. 设计模式方面, 不会不要紧, 因为这本身就是个经验活, 这里多插一句, 本人看来, 设计模式这个东西脱离不了语言特性, 这可能与大部分人的见解是北道而驰的, 但我不是否定编程思想, 只是有些模式在特定的语言下实现出来更直观, 更方便, 这可能也是写编译器的人本身也考虑过用户层面的便捷性, 这里不多说了

 3. 数据结构方面,基本的排序和查找算法,链表,队列都是必须掌握的,但现实是国内90%的程序都是基于应用层面的, 项目中基本很少自己来搞一套分配器,容器之类的东西, 但作为一名coder比较高深的数据结构还是要知道原理流程的

 4. 网络方面的东西,一般都是常见的网络通信原理,这个我们虽然不是专业搞网络的,但是也必须懂的流程,更何况我们iOS开发的是处于OSI的应用层,必须知道应用层常用的基本协议,Http,Https等,要知道长链接和短链接的区别和运用,还有比较多的是IM的东西,也就是socket,常用的三方IM框架等等。

PS:吐槽完毕,我个人写一点自己对多态的理解, 读完这篇文章后, 你就会找到runtime为什么要定义那些数据结构, 而不是其他runtime文章那样,上来给你说说runtime是什么东西, 然后开篇就给你一坨c的结构, 然后开始一坨理论




技术要求

硬性

 1. 懂OC的语法

 2. 懂ADT相关的概念

 3. 懂面向对象语言的特性和结构化语言的特性

非硬性

 你有耐心看完(有些地方表述可能比较粗鲁)

声明

本人没有去看Runtime的源码, 只是凭着经验表示自己对runtime的理解, 我也不想去看什么源码,很多文章源代码贴出来了, 然后介绍一下方法的参数和返回和大致的事情, 我觉得没有必要, 我的学习方式是从自己开发遇到的问题和经验, 然后反向思考而有所获,注重的是自己的理解然后加以运用.
如果有和大家认为出入的地方也仅代表个人理解有误, 欢迎大家指正




面向对象和面向过程

 面向对象的语言特性我就不说了(继承,封装,多态).面向对象的思想在C语言里也是有的,并不是说非得在面向对象的语言里,只不过在特定的语言里,有更多的编译特性, 比如在c里结构可以称之为类(事实上其他很多语言最后都转换成了这种结构), 在面向对象的环境下, 类与类直接的关系有组合,引用,继承,这里拿cpp来说下,比较典型

struct Person{
  char name[10];  ///名字
  unsign short age;
};

struct Empolyee{
    char company[20]; ////公司
    float income;     ///收入
};

struct Department{
    char name[20]; ////部门的名字
    unsign int memcount; ///部门有多少人
};

上面定义了3个class, cpp中struct和class几乎是等价的,在现实世界里, 要实现这样的关系无非是 员工Empolyee本质是一个人Person, 员工有自己的部门Department, 在面向对象里要实现这种关系:

struct Person{
  char name[10];  ///名字
  unsign short age;
};


struct Empolyee : public Person{ ///继承 isa关系
    char company[20]; ////公司
    float income;     ///收入
    Department depart;   ///复合(has a)一个部门 注意要先声明部门,这里不细谈
};

struct Department{
    char name[20]; ////部门的名字
    unsign int memcount; ///部门有多少人
};

上面典型用到了继承和复合, 在c语言里要实现这种关系如下:

struct Person{
  char name[10];  ///名字
  unsign short age;
};


struct Empolyee {
    struct Person father;  ////这里也是复合,表示Person是构成Empolyee的一部分
    char company[20];
    float income;   
    struct Department depart;   ///复合(has a)一个部门 这里表示的意义理解和father一样
};

struct Department{
    char name[20]; ////部门的名字
    unsign int memcount; ///部门有多少人
};

上面在定义 Employee的时候, 发现 内部的depart不应该是has的关系, 因为现实里部门可能是员工的一个属性,但Employee退休了怎么办, 这个时候如果你初始化一个Employee,那么他内部是相当于有一个depart的部分, 不符合常理, 所以depart不应该作为Employee的一部分, 要用到另一种关系pImpl(pointer to implementation), 将depart的部分设计成一个指针, 这样可以特定的条件下改变指向,比如退休,直接设置成NULL,最后设计出来的关系如下:

struct Person{
  char name[10]; 
  unsign short age;
};


struct Empolyee {
    struct Person father; 
    char company[20]; 
    float income;   
    struct Department* depart;  
 ///引用, 功能交给他指向的东西实现,这样Empolyee不用关心实现方式
///也就是说无论depart指向的东西是什么, 怎么变动都不会影响到Empolyee
///而且这个类型也被抽象成统一的类型,必要的时候可以void*
};

struct Department{
    char name[20];
    unsign int memcount;
};

在oc中,因为所有的类都是指针,所以类与类之间的关系到最后底层的C层次,只有 pImpl这种关系, 上面说了pImpl可以抽象数据类型, 目的是保证当前类型的结构统一,所以说在C语言里面向对象的思想是存在的,而且也代表着对你实力的肯定




静态语言和动态语言

 所谓静态语言,我想大家都知道概念,但还是要提一下, 静态语言最显著的特点是,编译器做的工作最多, 编译器会尽全力使所有的代码都在编译时期确定内存大小(类型),最典型的c++, c++效率高的原因不光是接近底层,还有很大部分原因是静态编译的功劳, 程序跑起来后除开多态的代码,基本都是在编译的时候就确定了大小, 不需要额外的花时间去寻找真正的类型,比如对于这样的代码:

instancePointer->fun1(arg1, arg2, ...);

///如果编译器在编译的时候发现instancePointer的类型已经确定了(没有多态,具体不细谈)
///则直接编译成的C代码:(伪代码)
////注意fun1因为是成员函数, 内部会修改掉本对象的数据,所以指针
fun1(instancePointer, arg1, arg2, ....)

///如果编译的时候,发现fun1是虚函数,或者就说发现此处调用是多态代码, 则编译成C的代码(伪代码)
funReallyPointer = findFunReally(instancePointer, fun1Identify);
if(funReallyPointer) funReallyPointer(instancePointer, arg1, arg2, ...)

上述代码中多态的部分, 就是运行时一种效果,如果发现此处是多态调用, 会先开始查找具备条件真正的函数地址, 然后再执行,所以多态是有性能损耗的, 这种性能损耗在查找函数的时间上面, 可以知道多态的动态性(也就是运行时)是通过附加查找函数来完成功能的, 当然多态的编译实现算法很复杂, 我也不会,咱们只要知道运行时是需要附加函数对数据结构进行额外线外的操作, 然后再回到线上执行代码, 这里的线外线上可以这样理解, 我要做的事情本来是调用 instancePointer的fun1方法, 但是编译器这里发现instancePointer的调用是多态的代码, 就会到线外先去查找真实地址,查找到后再回到线上执行调用

运行时多态

概念

 1. 多态是面向对象语言里最基本的特性, 在继承体系的前提下, 子类重载父类的函数/方法, 由于子类不用声明函数, 在效果上 通过子类去调用 重载的函数/方法, 会优先执行子类的函数,忽略掉父类
&emsp:2. 多态在C语言里要实现类似的效果, 可以用函数指针, 通过设置函数指针, 实现同一个类型的不同变量调用自己的函数指针时表现不同的动作, 只不过这种设置要我们程序员来手动实现
  3. 不同的语言,在多态的效果上有不同的特性

原理(仅代表个人观点)

  1. 具体去看c++虚函数, 其他语言的多态机制原理都差不太多
  2. 个人观点: 编译时期,编译器对多态的语法部分,并不是直接编译成函数调用:

callFunc(instancePointer, arg0, arg1,...)

而是编译成了类似于:

funReallyPointer = find(instancePointer, methodIdentify); funReallyPointer(instance, arg0, arg1,….);

 3. 从这点可以看出多态是有性能损耗的, 这种损耗就体现在查找funReallyPointer的过程里
 4. 多态的基本原理在面向对象语言里实现都差不多,当然实现上并不是如此简单, 而且不同语言有不同的特性


OC的多态(只代表本人理解)

  1. 前面粗略的讲了多态实现的基本原理, 通过理解要实现多态必须借助于指针
  2.当前指针指向的数据结构里 必须有方法列表,以供能查找
  3.当前指针指向的数据结构里 必须有能实现继承体系的方案(能找到父类, 然后在父类里逐级查找方法的实现), 当然这种方案就是再定义一个指针 指向父类的数据结构

PS: 后面讲的Runtime中相关的数据结构, 及其这些数据结构的定义就是为了实现多态的动态机制和下面要讲的反射机制, 当然OC还有许多自己的动态特性

反射

概念

 反射是一种机制, 在多数面向对象的语言里(c++没有),可以在成员运行的过程中, 通过函数调用的方式, 获取类的数据结构信息(成员变量名/类型, 方法等). 或者通过函数调用实现调用某个实例对象的任意函数和修改成员变量(包括私有的,虽然破坏了封装性,但是非常灵活). 这种动态的通过特定的函数去获取类的数据结构和调用对象的任意方法和属性就是反射, 也是运行时的一种

基本原理(仅代表本人理解)

 寻找到类的数据结构信息(成员变量名/类型,方法地址等)

  1. 要实现这种特性, 就要有一张全局的表(Map),这个表相当于一种映射表, 所有的类要通过key-value的形式存进去
  2. key通常是类名的字符串, value的定义可能比较复杂,接下来讲
  3. 程序运行之前 要将所有的类注册到Map中,为了后面能通过key找到value
  4. 程序运行中 通过key找到value, 但是我们的目的是通过key找到类数据结构的信息(成员变量名,成员变量类型, 方法名,方法地址,方法签名等信息), 所以这value是和类对应的
  5. 因为4的原因, 要求value必须是一个实体变量, 这个实体变量肯定是一种类型, 该类型的数据结构必须能查找到 key对应类的成员变量名,方法名,方法地址等信息, 那么找到的value肯定是已经赋值了, 而且这个时候就可以知道,赋值操作应该就是在程序运行前注册到map之前value的初始化操作

 调用对象的方法和属性(包括私有)

  1. 外部调用public的成员变量和方法(intance.member1, instance.pFun(arg1,arg2,…), 这个阶段在编译时期有语法支持的, 编译不会报错
  2. 调用任意的方法和属性(成员变量,OC中成员变量和属性是有区别的,这里先不讲OC)要实现这种特性, 语言必须提供相应的函数调用接口 类似于:

object_call_fun(instancePointer, funIdentify, arg1, arg2, ...);
object_get_value(instancePointer, memIdentify);
object_set_value(instancePointer, memIdentify, newValue)

    2.1 我们在外部调用这些接口的时候,必须要知道 funIdentify和memIdentify 才能合法调用
    2.2 funIdentify可以像上面说的获取类结构信息的时候的步骤一样, 因为成员函数肯定是在编译的时候已经确定了信息的,在注册到Map的时候,函数的信息肯定是有的
    2.3 类似于成员变量也是同样的道理, 在注册到Map的时候, 类型,名称,个数等都应该在value中

PS: 上面只是粗略讲了这种原理过程,当然实现并不是这种简单流程,有很多细节和技巧也不是我探讨的范围, 也没实力去探讨, 而且只是个人理解,理解了上面就可以开始Runtime了


我理解的runtime

要解决的问题

就是上面提到的多态和反射

核心数据结构(逆推方式)

多态(一下本人理解,不代表其他人)

  多态的前提继承, 继承的成员变量是怎么找到的, 这个是编译器的范畴,在编译器继承的语法的时候, 已经重新定义了子类的数据结构, 其中已经囊括了所谓的父类的成员的一部分

  在OC里所有的对象都是指针, 指向了自己的类, 上面说的如果编译器在编译继承语法的部分时, 是重新定义子类的数据结构, 这点是不恰当的或者是没有表达清楚

eg:(这里是伪代码的形式, 不要在意)

class Person : BaseClass{
  string name;
  string funGetName();
}; 
class Man : Person {
  float money;
  float funGetMoney();
};

上面 编译器在编译 class Man : Person的时候, 如果像上面说的重新定义了子类Man的数据结构, 则Man最后成了:

class Man : Person {
  string name;
  string funGetName();
  float money;
  float funGetMoney();
};

我们可以这么去理解, 但是实际上oc的代码都被转换成了runtime的代码, 也就是上面最后都转换成了结构体的定义, 重复之前的步骤:

//BaseClass先不管
        struct Person{
            string name;
            string (*funGetName)(void);
        };
        
        struct Man{
            string name;
            string (*funGetName)(void);
            float money;
            float (*funGetMoney)();
        }

如果最后是被编译成了这种结构, 那么如果Person家族的继承体系超过3层, 甚至3层以上, 那么后面的子类的结构更庞大,那怎么解决这种问题呢? 答案就是 将所有的类(包括继承体系)都抽象成一种格式

抽象上述的数据结构

 1. 抽象成员变量为 成员变量链表指针,这里的链表里的每一个成员变量 最好定义成一个结构 eg:

struct Member{
                    string name;
                    string type;
                    type value;  是什么类型就是什么类型的值
                    struct Member* next;
                }

记录的是当前类所定义的所有的成员变量的名字和类型.当直接初始化一个子类的时候 eg:Man manInstance的时候, 这链表里会有2个成员变量, 其中一个是自己money, 还有一个是Person的name,如果有重名的, 编译器在编译器的时候就该报错, 不该有重名的成员变量

 2. 抽象 方法为 函数指针链表, 思想和上面一样,链表中的item也该是一个种结构, 因为能统一函数, 有的函数返回值,参数不同, 结构如下:

struct FunPoint{
                    string funName; 函数名字 能通过此东西映射出函数
                    void* funImplementaiton; ///函数具体的地址
                    struct FunPoint* next;
                };

PS:上面这种定义虽然全部抽象, 让我们一下多态的伪代码

面向对象的代码

Person* p = new Man;
p.name = “zs”;

编译器

///1 创建对象
struct Person* p = malloc(sizeof(struct BaseClass));
p->members = NULL;
p->funs = NULL;

///2 创建成员变量
struct Member* m = malloc(sizeof(struct Member));
m->name = “name”;   ///变量名
m->type = “string”;    ///类型
m->value = “zs”;       ////赋值
m->next = NULL;

////3添加到p的链表里
addMembers(p, m);  ///里面链表的添加操作

////4创建fun
struct FunPoint* fun = malloc(sizeof(struct FunPoint));
fun->name = “funGetName”;
fun->funImplementaiton = “具体的函数地址”;
fun->next = NULL;

///添加fun
addFun(p, fun);       ////里面链表的添加操作

这样编译之后, 调用成员变量 eg:p.name, 会编译成

///内部会将name去链表里遍历匹配找到对应value
call_get_value(p,name);

函数调用 eg:p.funGetName(void)

///内部会去通过funGetName匹配到对应的地址, 然后调用,会有多态的寻找过程
all_fun(p, “funGetName”),

PS:上面结构定义和数据的操作 目前有3个弊端, 1.是每次定义一个变量后, 都要做重复的 成员变量类型名/类型名字/函数信息等赋值操作, 然后再设置成员变量的值; 2.没有提到之前说的Map映射的利用; 3. 子类已经将所有的东西包括父类的成员和函数都集成到了 对应实体里了,并没有体现面向对象的语言思想. 不要急往下看

反射机制实现

  1.前面讲反射的时候说过, 通过字符串可以找到对应类的数据结构, 提过的全局的Map
  2.通过 key 寻找 对应类的数据结构的时候(成员列表 方法列表等等)
    2.1 在运行程序之前, 要先将所有的类注册到Map中, 则系统做的事情就是:

///定义Map, 然后 Person info就是获取数据结构的信息,也就是value
Map[“Person”] = Person Info;

    2.2 到这里就发现出问题了, 上面定义的Struct Person只是一个结构体, 是类型, 并不是实体, 在没有实体化的时候, 无法获取类型里面的信息, 而实体化又是我们程序员来做的事情!!!! 上面最后的结果是每次初始化建立对象的时候, 都是重复的在变量(对象)里填入类的信息,虽然这是编译器帮我们做的. 但是代码肯定是一坨垃圾, 是个人都知道!!!而且就算有了实体, 也无法通过 字符串"Person"的形式 来获取类名,因为Person Class最后编译成的 strut BaseClass里也没有对应的字段来获取类名
    2.3 先解决类名的问题,在原来的数据结构里加一项 string className, 则修改后的结构:

struct BaseClass{
                            struct Member* members;
                            strcut FunPoint* funs;
                            string className;   ///当前类的类名
                        };

    2.4 对于注册Map时初始化的问题
      2.4.1 这个可以有系统来做, 在程序启动后, 还没进入到我们的代码, 系统自己构建一个实例, 然后自己初始化与类相关的信息包括 1类名; 2成员变量名和类型; 3函数相关的信息(函数在编译的时候就能确定的)
      2.4.2 无法确定成员变量的值, 因为初始化的操作是建立类相关的信息,和实体变量毛的关系没有, 如果单纯的继续将value这个字段放在struct BaseClass的每个Member中,那么系统初始化类信息的时候, 会浪费一个value的空间,因为根本不做事情, 解决办法, 将value字段从strcut Member中踢出去, 换成一个标记,能 正确找到 后面 程序员创建的对象 对应的 成员变量值标记, 结构如下:

strcut Member{
                                stirng name;
                                string type;
                                int idx; ////定义成一个标识, 在别的地方去通过idx找到真实的值
                                strcut Member* next;
                            }

经此, struct BaseClass已经完全抽象了, 即格式得到了统一

    2.5 经过2.4 系统已经可以完美的初始化类的信息,并注册到Map里了, 但是我们上面还有个问题没有处理, 就是一直提到的 程序员创建对象的时候, 重复赋值类信息的事情, 现在struct BaseClass离的strcut Member里已经没有value这个字段了, 我们怎么赋值呢? 而且目前为止程序员创建的对象压根没有用到 Map相关的东西!!! 这里直接说结果了, 就是将对象 和 类 都弄成实例变量, 类struct BaseClass会在程序启动后,系统创建工程里每一个类的实例注册到map里, 然后我们创建的变量要重新定义结构, 仔细想想就用之前讲的 pimpl的方式, 最后我们定义的变量的结构如下:

struct Instance{
  ////里面存放的是 成员变量值 的数组, 根据定义类的成员变量的顺序存放的, 这里没有考虑编译器的内存对齐优化
  void** valueList;  
  ////指向Class, 这样可以获取当前指针类型的数据结构信息
  struct BaseClass* class;  
}

接下来伪代码展示

系统初始化Map的注册阶段

Person* classPerson = malloc(sizeof(struct BaseClass));
///1 赋值类名 反射用
classPerson->className = “Person”; 

///2 这里包不包含父类,在下面会探讨的
classPerson->members = malloc(sizeof(struct Member) * Person的成员变量的个数包括父类);

///2.1 遍历members, 依次按定义成员变量的顺序包括父类, 将名字和类型添加到对应的member结构里, 然后串成链表

///3 这里包不包含父类,在下面会探讨的
classPerson->funs = malloc(sizeof(struct FunPoint) * Person成员函数的个数包括父类)

///3.1 遍历funs, 将所有的函数包括父类, 名字和地址都存放到链表里

程序员创建对象的时候, 面向对象的阶段

Person* p = new Person;
p.name = “zs”;

编译器编译上述代码后的伪代码

Person* p = malloc(sizeof(struct Instance));

////获取系统创建类信息的实例
struct Person* classPerson = Map[@"Person"];

////给p赋值类的信息
p->class = classPerson;

///伪代码, 系统能获取到了 Person这个类里有多少成员变量
///总之就是要和Person注册时,members对应起来
int count = classPerson->members;

p->values = malloc(sizeof(void*) * count );

///给name这个成员变量赋值
/// 1.从classPerson中遍历找到name的member, 这样就可以找到
/// 2.*((type*)p->values[idx]) = “zs”;  idx 和 type

    2.6 针对于私有变量和方法, 在编译阶段是无法调用私有变量和私有方法的, 所以要实现反射的功能, 必须提供对外的接口, 内部去做相应的操作 比如:

struct Member* privateMem = class_get_varName(className, memName);
class_instance_set_varValue(instacne, privateMem)
私有函数就不说了

回过头再探讨当前数据结构和面向对象的关系

 1. 上面的流程已经到了可以解决反射的基本功能了, 但是回过头去看多态的实现, 还是不好, 因为根本没有体现出继承的思想在里面

 2. 针对1提出的问题, 我们必须要做到层次化, 必须要想面向对象靠拢, 要做的工作就是再次修改数据结构, 在数据结构格式统一的前提下还要体现继承的机制

 3. 既然要做到层次化, 那么 BaseClass中的结构必须要抽离父类的部分, 做到子类的东西子类管理, 父类的东西父类管理, 要实现这样的效果,必须再次引入指针, 这个指针是指向父类的, 数据结构如下:

struct BaseClass{
                            struct Member* members;
                            strcut FunPoint* funs;
                            string className;   ///当前类的类名
                            struct BaseClass superClass; ///父类
                        };  

 4. 在探讨多态和反射的时候, 其中有一点我是一直纠结的, 就是Member* 和 FunPoint* 这2个链表中是不是包含了父类的部分
  4.1 在没有进行到反射的最后的时候, 问题的存在很大隐患
  4.2 如果包含了父类的部分, 就没有体现继承的思想在里面
  4.3 如果没包含, 那么在初始化 subInstance的时候, 岂不是还要先实例化一个父类实例, 子类还要引入一个指针他妈的指向这个父类的实例, 这样导致BaseClass的统一的结构被破坏掉, 所以当时一直想的是 Member中必须有所有的成员包含父类, funs中也是一样的道理
  4.4 但是现在必须改过来, 而且之前的想法也不怎么对, 在进行到反射最后完成了, 衍生出struct Instance的时候, 答案已经出来了, Member
不应该有父类的东西, 因为站在面向对象的层面去看, 子类是没有权利去管理父类的, 而且从C的层面去看,也就是Runtime, 2种结构虽然是统一的,但是因为pImpl,可以把功能抽离出去, 互不影响的,这样就是最后定下来的类的关系
  4.5 那么怎么在C中实现子类父类的部分父类管理呢? 换言之也就是托管,在C里要想实现托管, 就要用到著名的设计 pimpl(Pointer to Implementation), 用一根指针指向要托管的类父类, 所有的相关功能托管给父类,最后BaseClass结构就是上面最后定义, 再次贴在这里

struct BaseClass{
                            struct Member* members;
                            strcut FunPoint* funs;
                            string className;   ///当前类的类名
                            struct BaseClass* superClass;   ///指向父类
                        };

PS:PS:对于子类来说, 他的 subClass记录的只有自己的members, 而且这个链表里的idx应该是计算完父类所有的成员变量之后开始计数的 比如父类有 name, 子类有新的变量 age, 那么 子类的member中有一个成员age成员, age的idx要从1开始,而不是从0开始, subInstance里的values还必须是3, 因为实例化类的时候, 他实际的内存大小必须要将父类的成员一起算进去, 讲到这里,我们可以完全去探讨OC的Runtime了

Runtime

简介

 从字面意思可以翻译成运行的时候, 他是OC底层运行的机制, 即在面向对象层面(编译器时期)是Objective-C, 在编译器转化后, 所有的Objective-C代码都被转换成了Runtime代码. 他的目的就是实现运行时的功能, 多态和反射

本质

 Runtime可以理解成为一套框架, 是苹果自家开发的,而且是开源的,不过这套框架是C层面的.
 任何OC相关的东西都要经过Runtime的处理, 只是在上层开发的我们没有直接接触到而已, 所有的转换工作都是编译器(通常是xcode)来完成的, 我们也可以手动通过提供的接口去调用

和我理解的异同

 和我最后定义BaseClass的结构不同的是, OC里多了许多东西, 这里说重要的
  1. objc_protocol_list* 协议列表
  2. objc_cache* 方法查找的缓存, 用来优化方法的查找
  3. instance_size 当前对象类型(结构体)的内存大小, 这个值在已经把父类的内存大小也算进去了, 成员变量列表里的个数只是当前类里的成员个数, 并没有父类的成员变量
  4.多了一个和本身同样类型的指针, 这个指针指向了自己, 直接说了吧, 这个是OC里处理类方法的一种机制, 他指向了传说中的MetaClass

核心数据结构

 1. 成员变量链表里item

stuct objc_ivar{
                char* ivar_name;    ///成员变量的名字
                char* ivar_type;     ///成员变量的类型编码
                int ivar_offset;        ////结构体偏移
                
#ifdef __LP64__
                    int space;
#endif
            }

和我上面定义的不同, 这里给出了 offset, 内部会通过这个offset获取到对应实例变量的对应成员变量的值, 我的方案是给出idx, 然后values存储在 struct Instance里, 而oc是在别的什么地方,总之这个东西必不可少, 取值的时候用

 2. id (objc_object*) 对应我struct Instance, 不过里面只有一个指向类信息实例(Map[@"类名"])的指针
 3. Method

struct objc_method{
                SEL method_name;
                char* method_types;
                IMP method_imp;
            };
////和我定义结构相比, 这里是通过 SEL作为Identity寻找函数的, 而且多了函数签名的字符串

 4. Cache 就不提了,有机会的话后面会介绍下功能


工作流程(仅代表个人观点)

 0. 程序启动后,系统会创建所有的类实例(类对象), 注册到某个地方, 假设是Map
 1. 所有的OC代码在经过编译器阶段后, 都被编译成了Runtime的代码, 也就是说OC完全被编译成了动态语言, 所有OC代码调用格式全部编译成了objc_msgsend(instance(id或者Class), methodName(SEL), arg0, arg1, arg2, ….), super调用的地方先不提

 2. objc_msgsend
  2.1 内部先做nil处理, 即发现instance是nil的时候, 直接返回了, 所以oc里对nil发送消息是安全的
  2.2 如果instance不空, 则根据他指向的 struct objc_object*(也就是id)的实体里 被赋值的isa指针, 到对应的类实体里(类对象)去找方法的实现
   2.2.1 先在当前类实体被赋值的cache链表中查找, 匹配的基础就是 SEL方法的名字, 找打后就获取到了对应的Method结构, 然后就找到了IMP, 接着调用IMP
   2.2.2 如果在cache中没有找到, 就到MethodList去找, 匹配, 如果找到就执行IMP
   2.2.3 如果还没找到, 就通过SuperClass到父类的类对象里做同样的操作, 一直找到rootClass
   2.2.4 上诉流程里如果找到了就会顺利执行, 如果没有找到, 并不会立即崩溃, Runtime会进行到转发流程
   2.2.5 如果找到了,会将方法缓存起来, 下次直接从缓存找

 3 消息转发 如果从2.2.4跳过来
  3.1 如果当前是Instance, 进入+bool[NSObject resolveInstanceMetho:SEL]

哪个Instance调用的, 就是在哪个类中调用
这里可以在当前类里做一些简单的拦截, 发现sel是什么方法可以过滤掉, 或者为这个sel添加一个新的实现,返回yes,处理掉消息

  3.2 如果上面的不想处理, 直接返回false, 就会进入到消息转发的第二阶段 - id[NSObject forwardingTargetForSelector:SEL]

这里需要返回一个id, 如果返回id表示将sel转发给这个实例, 他去执行

  3.3 如果上面也不想处理, 直接返回nil, 或调用super, 就会进入消息转发的第三阶段

-NSMethodSignature* [NSObject methodSignatureForSelector:SEL]这里要创建一个签名对象返回, 必须要创建系统索要到签名对象后, 会进入消息转发的最后一步

  3.4 - void[NSObject forwardInvocation:NSInvocation]

这里的invocation对象是上面一步系统根据签名对象创建的一个对象,在这里 可以通过 -[NSInvocation invokeWithTarget:otherObjc]

  3.5 如果上述还是没有处理, 就异常崩溃了


NSObject 提供的接口

 1. +/-[NSObject class] 获取当前的类
 2. +/-[NSObject isKindOfClass: Class] 判断当前的类或对象是否为Class(isa关系)
 3. +/-[NSObject superClass] 获取当前类或者对象的父类
PS: 看NSObject.h头文件

Runtime提供的功能更强大的接口
 1 反射获取类对象(类的数据结构的信息)

NSClassFromeString(classNameStr);
objct_getClass(chassNameCStr)
objc_getMetaClass(classNameCStr)

PS: objct_getClass(id), 不管id是对象还是类对象, 获取的都是他们的isa指针, 对象的时候获取到的是类对象, 类对象的时候获取到的是metaClass +/-[NSObject class], 当是对象调用的时候,获取的是类对象, 当是类对象调用的时候返回的是他本身,即还是类对象

 2 获取当前类成员变量IVar
  2.1 根据成员变量的名字获取对应的 IVar结构

IVar var = class_getInstanceVariable(class, memNameCStr);
IVar var = object_getInstanceVaiable(id, memNameCStr, void** outVaue)
第二种没有第一种效率高, 而且第二种只能在MRC环境下使用

  2.2 根据获取到的IVar, 然后get到value和set对应的值
   2.2.1 id obj = objct_getIVar(obj, var);

   2.2.2 不能获取到基本数据类型的值, 如果想获取到基本类型的值, 可以通过IVar里的偏移量

   1.  基地址是当前 obj的指针 转换成 char* basicAddress;
   2.  obj指向的是类对象, 第0个隐含成员是isa指针, 8个字节;
   3.  第1个成员变量的地址 == basicAddress + 8;  mem1Address;
   4.  第2个成员变量的地址 == mem1Address + sizeof(mem1); mem2Address
   5.  依次类推, 第N个成员变量的地址 == memN-1Address + sizeof(memN-1)
   6.  需要注意的是, 当前成员变量的类型是记录在IVar里的, 是字符串的编码格式, 具体对应到的类型,可以直接通过表映射取出来, 如果有的成员变量是自己定义的结构体, 就会更负责, 结构体/union 这种解构他的编码格式要自己写一个函数映射出来,计算出总大小, 所以如果类成员中需要定义结构体, 最好定义成结构体指针
   7.  找到地址后,  强制转换成当前成员变量类型的指针, 就可以赋值了

  2.3 根据获取到的var设置值, 也就是设置成员变量的值(要求类型必须是id)

object_setIvar(id, var, id value);
无法设置基本数据类型的成员变量的值, 而且调用后, 之前的值会莫名变动

  2.4 获取这个类的所有成员变量IVarLIst 不会获取父类里的成员变量, 只会获取当前类的成员变量的信息,注意IVar中没有value,只有一个偏移量, Runtime内部获取值的时候, 根据这偏移量去获取的, 就像我上面推到的数据结构一样, 我的定义里是idx

1 class_copyIvarList (class, int* countBuffer)
2 返回的就是指定class的所有Ivar
3 函数内部会设置 countBuffer , 对应多少个成员变量
4 不会获取到与父类相关的成员变量
5 返回值最后处理完要free(头文件里已经说得很清楚了)

  2.5 动态添加成员变量 有机会讲

 3 获取Class中的方法Method(是一个结构, 里面有SEL type 和 IMP)
  3.1 获取实例方法 class_getInstanceMethod(class,SEL); 会到父类去寻找

  3.2 获取类方法 class_getClassMethod(class, SEL) 会到父类去寻找

  3.3 获取方法签名 (char*)method_getTypeEncoding(Method)

  3.4 获取当前类所有的对象方法

> class_copyMethodList(class, int* countBuffer)
> 不会获取到父类相关的方法
> 如果没有实现, 不会获取到,比如分类里只声明一个属性,表示只有方法的声明, 没有实现
> 返回值记得最后free掉

  3.5 获取当前类所有的类方法

> 要先获取到类的元类
> 再调用class_copyMethodList

  3.6 获取方法的实现地址(IMP)

1 根据Class的数据结构可以知道, IMP是和SEL对应的, 可以通过SEL去在当前的类的方法列表里找到对应的IMP,OC里提供了相应的接口
            
2 NSObject提供的接口
- IMP[NSObject methodForSelector:SEL]
+IMP[NSObject instanceMethodForSelector:SEL]
注意最好是按他的格式, 类方法就用+, 实例方法用-, 虽然不会报错,但他妈的有时候不准确
        
3 通过Runtime的接口
IMP class_getMethodImplementation(Class, SEL) 
IMP method_getImplementation(Method)

  3.7 方法交换 既然Runtime是通过SEL去对应的类里寻找IMP,那么可不可以替换掉SEL对应的实现, 这样就会执行我们的实现, 交换的流程,一般交互实例方法

Method myM = class_getInstanceMethod(class,MySEL);
Method sysM = class_getInstanceMethod(class,SysSEL);
if(class_addMethod(class,  sysM, method_getImplementation(myM), method_getTypeEncoding(myM))){
  class_replaceMethod(class,  MySEL,   
  method_getImplementation(SysSEL),  
  method_getTypeEncoding(SysSEL)
}else{
    method_exchangeImplementations(myM,sysM);
}

PS:解释一下add的目的, 因为class_getInstanceMethod会到父类去寻找, 如果当前sysM是在本类里找到了, 可以直接交换,但是sysM如果是父类找到了, 表示当前类并没有实现, 这种情况下如果交换, 那么当前类的父类的其他子类调用SysSEL的时候也被交换了, 这不是我们要的目的, 所以要判断SysSEL有没有在本类里实现, 通过add的myM方式, 把SysSEL指向myM的实现, 如果成功则表示当前类添加了一个新的Method, SysSEL指向了自己定义的实现myM, 父类的SysSEL不会被改变, 接下来就是要处理将MySEL指向sysM, sysM当前还是父类里的实现的, 所以用了replace函数, 只是单向的将MySEL指向了父类的sysM,这样就实现了效果, 当调用MySEL的时候会去父类的sysM, 调用SysSEL的时候会到子类里的myM, 当前类父类其他子类是么有影响的

 4 属性
  4.1 属性的数据结构没有具体的数据结构, 但是可以从关于属性的一些函数了解一些

  4.2 返回 除分类里的所有属性,编译阶段分类里添加的属性是不能获取到的

  4.3 获取属性的名 property_getName(property);

  4.4 获取属性的信息property_getAttrubutes(property)

  4.5 通过属性的信息, 可以推到出属性共性, 以便后面可以用代码添加属性

1 @property(nonatomic, strong) NSString* name;    T@“NSString”,&,N,V_name

2 @property(nonatomic, copy)   NSString* name;    T@“NSString”,C,N,V_name
                
3 @property(nonatomic, assign) NSString* name;    T@“NSString”,N,V_name
                
4 @property(nonatomic, weak)   NSString* name;    T@“NSString”,W,N,V_name
                
5 @property(atomic, strong)      NSString* name;    T@“NSString”,&,V_name
                
6 @property(nonatomic, assign)   float* money;       T^f,N,V_name
                
7 @property(nonatomic, assign)   float money;         Tf,N,V_name
                
                
8 @property(atomic, strong,readonly) NSString* name;  
T@“NSString”,&,R,V_name
                
9 @property(atomic, strong,readonly,getter=isName)      NSString* name;    T@“NSString”,&,R.GisName,V_name
                
10 @property(nonatomic, strong)   id money;             T@,&,N,V_name

PS: 从上面明显看出 一个属性的信息里, 必须有的东西
类型 ===> T + 对应的编码
线程安全 ===> N(nonAtomic) 没有N代表默认的aomic
内存管理 ===> 针对id &:strong C:copy W:weak
关联变量 ===> V + 成员变量的名字
只读 ===> R

  4.6 添加属性
   4.6.1 静态添加, 一般是在分类里

1 @property(nonatomic, strong) NSString* name;

2 实现setter 具体不多讲, objc_setAssociatedObject

3 实现getter方法 objc_getAssociatedObject

4 这样可以在编译的时候, 使用 点语法
                
5 这样的弊端是不能kvc

   4.6.2 动态添加, 比较复杂但是可以kvc

1 调用接口函数 class_addProperty(class, 属性名, 属性信息, 属性个数)
>属性信息, 就是上面 总结的共性
eg:     objc_property_attribute att[] = {
            {“”,”T@”,&,N,V_addNew”}
        } 

class_addProperty(class, “addNew”, att,1)

2 添加方法
2.1 添加对应 getter,  方案为定义一个全局的方法, 在里面实现自定义的取值

2.2 添加对应的setter, 方案和getter一样, 在里面实现自定义存储

2.3 需要说明的是, 在添加完属性后, 可以通过kvc赋值和读取, 只要你实现了对应的getter和setter

2.4 具体看demo runtime NSObject + AddProperty

demo

@interface NSObject (AddProperty)
/** 分类里扩展一个添加属性的方法 取值赋值就根据kvc就行了*/
+ (bool)addPropertyWithPName:(NSString*)name;

/** 移除 不存在也是true, 本质是删除不了的*/
+ (bool)rmvPropertyWithPName:(NSString*)name;
@end


#import "NSObject+AddProperty.h"
#import <objc/runtime.h>


#define _KEY_PREFIX @""

static inline NSString* _getPropertyName(id obj, NSString* argName){
    return [NSString stringWithFormat:@"%@%@%@",@"",_KEY_PREFIX,argName];
}

static inline NSString* _getterName(id obj, NSString* argName){
    return _getPropertyName(obj, argName);
}

static inline NSString* _setterName(id obj, NSString* argName){
    ///name.capitalizedString girlFriend 会变成 Girlfriend; 艹啊

    NSMutableString* handleStr = argName.mutableCopy;

    NSString* upperStr = [handleStr substringToIndex:1].uppercaseString;
    NSString* fir_otherStr = [handleStr substringFromIndex:1];
    return [NSString stringWithFormat:@"set%@%@:",upperStr,fir_otherStr];
}

static inline NSString* _dic_key(id obj, NSString* argName){
    return [NSString stringWithFormat:@"%@%@",NSStringFromClass([obj class]),argName];
}


@implementation NSObject (AddProperty)
+ (NSMutableDictionary*)newPropertys{
    static NSMutableDictionary* _newPropetys;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        _newPropetys = [NSMutableDictionary dictionaryWithCapacity:20];
    });
    return _newPropetys;
}
+ (bool)addPropertyWithPName:(NSString *)name{
    if (name.length == 0)return false;

    if([self checkProperty:name])return false;

    @synchronized (@"__add") {
        return [self _addPropertyWith:name];
    }
}

+ (bool)checkProperty:(NSString*)name{

    ///检查的是成员变量, 如果已经是成员变量了 eg:_address, 就不添加, 因为大部分情况下 是定义好的属性 address
    if(class_getInstanceVariable(self, [@"_" stringByAppendingString:name].UTF8String)) return true;

    return false;
}

+ (bool)_addPropertyWith:(NSString*)name{

    NSString* tmpPropertyName = _getPropertyName(self, name);

    ///动态添加属性 @property(nonatomic,strong) id obj;
    NSString* attStr = [NSString stringWithFormat:@"T@,&,N,V_%@",tmpPropertyName];

    objc_property_attribute_t att[] = { "",  attStr.UTF8String };

    const char* cStr = [[NSString stringWithFormat:@"_%@",tmpPropertyName] UTF8String];

    bool result = class_addProperty(self.class, cStr, att, 1);

    if (!result) class_replaceProperty(self.class, cStr, att, 1);



    NSString* getName = _getterName(self, name);
    class_addMethod(self, NSSelectorFromString(getName), imp_implementationWithBlock(^NSString*(NSObject* obj){
        NSString* key = _dic_key(obj, name);
        if([[NSObject newPropertys] objectForKey:key]){
NSDictionary* result = [[NSObject newPropertys] valueForKey:key];
            id returnValue = result[@(obj.hashCode).stringValue];
        if([returnValue isKindOfClass:NSNull.class])
            returnValue = nil;
        return returnValue;
}else
          return nil;
    }), "@@:");


    NSString* setterKey = _setterName(self, name);
    class_addMethod(self, NSSelectorFromString(setterKey), imp_implementationWithBlock(^void(NSObject* obj,id value){
        NSString* key = _dic_key(obj, name);
        if([[NSObject newPropertys] objectForKey:key]){
            if(value == nil) value = [NSNull new];
            NSMutableDictionary* result = [NSObject newPropertys][key];
            [result setObject:value forKey: @(obj.hashCode).stringValue];
}else{
            ///调用 方法 抛异常
            NSString* reason = [NSString stringWithFormat:@"-[%@ %@]:unrecognized selector sent to instance %p'",NSStringFromClass([obj class]), setterKey, obj];
            @throw [NSException exceptionWithName:NSInvalidArgumentException reason:reason userInfo:nil];
        }
    }), "v@:@");

    //// 首次主动注册到集合里
    NSString* tmpDicKey = _dic_key(self, name);
    if(![[NSObject newPropertys] objectForKey:tmpDicKey])[[NSObject newPropertys] setObject:@{}.multableCopy forKey:tmpDicKey];

    return true;
}



#pragma mark - 移除(模拟的)
+ (bool)rmvPropertyWithPName:(NSString*)name{
    if (name.length == 0)return false;

    NSString* key =  [NSString stringWithFormat:@"_%@",_getPropertyName(self, name)];
    objc_property_t t = class_getProperty(self,key.UTF8String);
    if(!t)return true;

    @synchronized (@"__rmv") {
        return [self _rmvPropertyWithPName:name];
    }
}


+ (bool)_rmvPropertyWithPName:(NSString*)name{
    NSString* key = _dic_key(self, name);
    [[self newPropertys] removeObjectForKey:key];
    return true;
}
@end

调用

- (void)viewDidLoad {
    [super viewDidLoad];

    [Person addPropertyWithPName:@"girlFriend"];

    Person* p = [Person new];
    p.name = @"zl";
    _p = p;
    [p setValue:@"凤姐" forKey:@"girlFriend"];
    Log([p valueForKey:@"girlFriend"]);
///内部会回调替换的方法
}

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    [Person rmvPropertyWithPName:@"girlFriend"];
    [_p setValue:@"123" forKey:@"girlFriend"];
}

 5 KVO的实现原理
  5.1 KVO的实现原理, 大家都知道理论,kvo能实现 1对1监听 1对多监听 多对1监听, 原理大家都懂, 就是教会isa指针, 这里给出demo, 只是实现了原理, 没有处理当传进来的类本身就是kvo的时候的情况

KVO demo


#import <Foundation/Foundation.h>

typedef void(^LBKVO_Callback) (id old, id modified, NSString* property);

#define LBKVOBlock   ^void(id _old_, id _new_, NSString* _property_)

#define _OBJC_PROPERTY(_obj_,_keyPath_) ((void)_obj_._keyPath_,@(#_keyPath_))

#define LBKVO_ADD(_register_, _property_, LBKVOBlock)\
[(_register_) kvoRegiesterName:_OBJC_PROPERTY(_register_,_property_)\
observer:self \
callback:LBKVOBlock]


#define LBKVO_REMOVE(_register_,_property_)\
[_register_ kvoRemoveWith:self name:_OBJC_PROPERTY(_register_,_property_)]




@interface NSObject (LBKVO)
/** self表示被监听者, obj是观察者 */
- (void)kvoRegiesterName:(NSString* _Nonnull)property
                observer:(id _Nonnull)observer
              callback:(LBKVO_Callback _Nullable)callback;

/** self表示被监听者, obj是观察者 */
- (void)kvoRemoveWith:(id)observer
                 name:(NSString*)property;
@end



#import "NSObject+LBKVO.h"
#import <objc/runtime.h>

#define _ClassPrefix @"LBKVO_"

#define _CLASS_SET_KEY_SUBCLASS @"subClass"
#define _CLASS_SET_KEY_RELEVANT @"relevant"
#define _CLASS_SET_KEY_REGISTER @"register"
#define _CLASS_SET_KEY_OBSERVER @"observer"
#define _CLASS_SET_KEY_CALLBACK @"callback"
#define _CLASS_SET_KEY_PROPERTY property   ///对应各个函数的变量名 统一的 property

#define _ARRAY_CAPACITY 8


static inline NSMutableDictionary* ALL_REGISTER_CLASS_SET(void){
    static NSMutableDictionary* _dic;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        _dic = [NSMutableDictionary dictionaryWithCapacity:_ARRAY_CAPACITY];
    });
    return _dic;
}


#define weakself() __weak typeof(self) weakSelf = self

#define ALL_REGISTER_CLASS_SET ALL_REGISTER_CLASS_SET()

#define _PROPERTY_SET ALL_REGISTER_CLASS_SET[[weakSelf propertySetKey:_CLASS_SET_KEY_PROPERTY]]

#define _SUBCLASS_VALUE _PROPERTY_SET[_CLASS_SET_KEY_SUBCLASS]

#define _RELEVANT_ARRAY _PROPERTY_SET[_CLASS_SET_KEY_RELEVANT]


static inline NSInteger _SUBCLASS_VALUE_ADD(NSString* key){
    NSInteger tmpCount = [ALL_REGISTER_CLASS_SET[key] integerValue];
    tmpCount++;
    ALL_REGISTER_CLASS_SET[key] = @(tmpCount);
    return tmpCount;
}

static inline NSInteger _SUBCLASS_VALUE_SUB(NSString* key){
    NSInteger tmpCount = [ALL_REGISTER_CLASS_SET[key] integerValue];
    tmpCount--;
    ALL_REGISTER_CLASS_SET[key] = @(tmpCount);
    return tmpCount;
}

static inline NSInteger _REGISTER_NUMBER_ADD(NSString* key){
    NSInteger tmpCount = [ALL_REGISTER_CLASS_SET[key] integerValue];
    tmpCount++;
    ALL_REGISTER_CLASS_SET[key] = @(tmpCount);
    return tmpCount;
}

static inline NSInteger _REGISTER_NUMBER_SUB(NSString* key){
    NSInteger tmpCount = [ALL_REGISTER_CLASS_SET[key] integerValue];
    tmpCount--;
    ALL_REGISTER_CLASS_SET[key] = @(tmpCount);
    return tmpCount;
}

static inline NSString* _LBKVO_SET_NAME(NSString* name){
    NSMutableString* str0 = name.mutableCopy;
    NSString* firStr = [str0 substringToIndex:1];
    NSString* otherStr = [str0 substringFromIndex:1];
    return [NSString stringWithFormat:@"set%@%@:",firStr.uppercaseString,otherStr];
}



@implementation NSObject (LBKVO)

#pragma mark - 注册KVO
- (void)kvoRegiesterName:(NSString*)_CLASS_SET_KEY_PROPERTY
                observer:(id)observer
                callback:(LBKVO_Callback)callback{

    NSParameterAssert(_CLASS_SET_KEY_PROPERTY.length != 0);
    NSParameterAssert(observer != nil);


    @synchronized(@"create"){
        [self _kvoRegiesterName:_CLASS_SET_KEY_PROPERTY
                       observer:observer
                       callback:callback];
    }
}


#pragma mark - 移除kvo
- (void)kvoRemoveWith:(id)observer
                 name:(NSString*)_CLASS_SET_KEY_PROPERTY{

    @synchronized (@"delete") {
        [self _kvoRemoveWith:observer name:_CLASS_SET_KEY_PROPERTY];
    }
}



#pragma mark - private method

#pragma mark - 检查有没有这个属性
- (bool)checkPropertyAvaiable:(NSString*)_CLASS_SET_KEY_PROPERTY{
    return class_getProperty(self.class, _CLASS_SET_KEY_PROPERTY.UTF8String);
}


#pragma mark - 当前对象是否已经为 observer添加了property的监听
- (bool)alreadlyAddObserver:(id)observer key:(NSString*)_CLASS_SET_KEY_PROPERTY{
    weakself();
    if (!_PROPERTY_SET) return false;

    NSArray* tmpArray = _RELEVANT_ARRAY;
    NSInteger tmpCount = tmpArray.count;

    for (int i = 0; i < tmpCount; ++i)
        if (tmpArray[i][_CLASS_SET_KEY_REGISTER] == self &&
            tmpArray[i][_CLASS_SET_KEY_OBSERVER] == observer)
            return 1;

    return false;
}


#pragma mark - 是否需要创建类
- (int)judgeClass{
    if ([self selfIsSubClass]) return 2;

    return [self shouldDynamicCreateSubClass]?1:0;
}

#pragma mark - 要不要动态创建子类
- (Class)shouldDynamicCreateSubClass{
    Class selfClass = object_getClass(self);
    NSString* classStr = [_ClassPrefix stringByAppendingString:NSStringFromClass(selfClass)];
    return objc_getClass(classStr.UTF8String);
}

#pragma mark - 当前的self对象是不是已经指向了创建的子类
- (bool)selfIsSubClass{
    Class reallyClass = object_getClass(self);
    NSString* classStr = NSStringFromClass(reallyClass);
    return [classStr hasPrefix:_ClassPrefix];
}

#pragma mark - 构建全局字典里的key (创建的classStr + property)
- (NSString*)propertySetKey:(NSString*)_CLASS_SET_KEY_PROPERTY{
    if ([self selfIsSubClass])
        return [NSString stringWithFormat:@"%@_%@",object_getClass(self),_CLASS_SET_KEY_PROPERTY];

    Class selfClass = object_getClass(self);
    return [NSString stringWithFormat:@"%@%@_%@",_ClassPrefix,NSStringFromClass(selfClass),_CLASS_SET_KEY_PROPERTY];
}


#pragma mark - 返回记录subClass数量的key
- (NSString*)subClassNumberKey{
    ///self一定指向动态创建的子类
    return NSStringFromClass(object_getClass(self));
}

#pragma mark - 返回记录 当前对象引用数量的key
- (NSString*)registerNumberKey{
    return @([self hash]).stringValue;
}


#pragma mark - 创建class
- (void)createClassWith:(NSString*)_CLASS_SET_KEY_PROPERTY
               observer:(id)observer
               callback:(LBKVO_Callback)callback{

    Class reallyClass = object_getClass(self);

    NSString* tmpClassName = [_ClassPrefix stringByAppendingString:NSStringFromClass(reallyClass)];

    ///创建新的class
    Class newClass = objc_allocateClassPair(reallyClass, tmpClassName.UTF8String, 0);

    ///注册
    objc_registerClassPair(newClass);


    ///修改当前对象isa指针
    [self resetSelfIsa:newClass
              observer:observer
                   key:property
              callBack:callback];
}


#pragma mark - 设置isa 修改setter 及记录他相关的信息
- (void)resetSelfIsa:(Class)subClass
            observer:(id)observer
                 key:(NSString*)_CLASS_SET_KEY_PROPERTY
            callBack:(LBKVO_Callback)callBack{
    ///将被监听者的isa指向新建的类
    object_setClass(self, subClass);


    ///修改property的setter
    NSString* _setMethodName =_LBKVO_SET_NAME(_CLASS_SET_KEY_PROPERTY);


    weakself();

    ///为新建子类添加 setter实现
    class_addMethod(subClass, NSSelectorFromString(_setMethodName), imp_implementationWithBlock(^void(__weak id obj, id value){
        NSMutableArray* relevantArray = _RELEVANT_ARRAY;

        NSInteger tmpCount = relevantArray.count;

        for (int i = 0; i < tmpCount; ++i) {
            NSDictionary* dic = relevantArray[i];
            if (dic[_CLASS_SET_KEY_REGISTER] == obj) {
                id old = [obj valueForKey:_CLASS_SET_KEY_PROPERTY];
                ///通知外界
                LBKVO_Callback block = dic[_CLASS_SET_KEY_CALLBACK];
                if (block) block(old,value,_CLASS_SET_KEY_PROPERTY);
            }
        }

        ////覆盖值
        NSString* varName = [@"_" stringByAppendingString:_CLASS_SET_KEY_PROPERTY];
        Ivar var = class_getInstanceVariable([obj class], varName.UTF8String);
        if (var) object_setIvar(obj, var, value);


    }), "v@:@");

    ///引用 subClass的数量
    _SUBCLASS_VALUE_ADD([self subClassNumberKey]);


    ///引用 当前对象的数量
    _REGISTER_NUMBER_ADD([self registerNumberKey]);


    ///添加关联
    NSMutableArray* relevantArray = _RELEVANT_ARRAY;
    if (!relevantArray) relevantArray = [NSMutableArray arrayWithCapacity:_ARRAY_CAPACITY];

    [relevantArray addObject:@{_CLASS_SET_KEY_OBSERVER:observer,
                               _CLASS_SET_KEY_REGISTER:self,
                               _CLASS_SET_KEY_CALLBACK:callBack
                               }];

    [ALL_REGISTER_CLASS_SET setObject:@{
                                        _CLASS_SET_KEY_SUBCLASS:subClass,
                                        _CLASS_SET_KEY_RELEVANT:relevantArray}
                               forKey:[self propertySetKey:_CLASS_SET_KEY_PROPERTY]];
}

-(void)_kvoRegiesterName:(NSString*)_CLASS_SET_KEY_PROPERTY
                observer:(id)observer
                callback:(LBKVO_Callback)callback{
    ///0 如果当前类没有这个 property属性, 则不能监听
    if(![self checkPropertyAvaiable:_CLASS_SET_KEY_PROPERTY])return;

    ////如果已经为当前self 的property添加了 observer, 就直接返回
    if([self alreadlyAddObserver:observer key:_CLASS_SET_KEY_PROPERTY])return;


    switch ([self judgeClass]) {

            ////没有为 当前类 创建过子类
        case 0:{
            [self createClassWith:_CLASS_SET_KEY_PROPERTY
                         observer:observer
                         callback:callback];
        }break;




            ///当前 对象的isa 并没有指向 创建过的子类 但是子类已经创建了
        case 1:{
            [self resetSelfIsa:[self shouldDynamicCreateSubClass]
                      observer:observer
                           key:_CLASS_SET_KEY_PROPERTY
                      callBack:callback];
        }break;




            ////当前 对象的isa 已经指向了 创建过的子类, 监听的是另外的 属性
        case 2:{
            [self resetSelfIsa:object_getClass(self)
                      observer:observer
                           key:_CLASS_SET_KEY_PROPERTY
                      callBack:callback];
        }break;
    }
}

- (void)_kvoRemoveWith:(id)observer
                  name:(NSString*)property{
    ///如果当前的self的isa根本没有指向 子类, 就表示self没有进入过这个监听系统
    if (![self selfIsSubClass]) return;

    weakself();
    NSMutableArray* tmpArray = _RELEVANT_ARRAY;

    if (!tmpArray)return;

    NSInteger tmpCount = tmpArray.count;
    NSDictionary* tmpDic = nil;
    for (int i = 0; i < tmpCount; ++i){
        tmpDic = tmpArray[i];

        if (tmpDic[_CLASS_SET_KEY_REGISTER] == self &&
            tmpDic[_CLASS_SET_KEY_OBSERVER] == observer)break;

        tmpDic = nil;
    }

    if (!tmpDic) return;


    [tmpArray removeObject:tmpDic];



    ///引用 subClass的数量
    NSString* subClassCountKey = [self subClassNumberKey];
    NSInteger subClassCount = _SUBCLASS_VALUE_SUB(subClassCountKey);


    ///引用 当前对象的数量
    NSString* registerNumberKey = [self registerNumberKey];
    NSInteger registerObjCount = _REGISTER_NUMBER_SUB(registerNumberKey);

    Class tmpC = _SUBCLASS_VALUE;

    if (tmpArray.count == 0)
        [ALL_REGISTER_CLASS_SET removeObjectForKey:[self propertySetKey:_CLASS_SET_KEY_PROPERTY]];


    if (registerObjCount == 0)
        object_setClass(self, [tmpC superclass]);



    if (subClassCount == 0) {
        [ALL_REGISTER_CLASS_SET removeObjectForKey:subClassCountKey];

        ///重置之前被监听者的isa 也就是self
        object_setClass(self, [tmpC superclass]);


        objc_disposeClassPair(tmpC);
    }

    /** 这里要注意一点 objc_disposeClassPair

     objc_disposeClassPair会直接free掉引用的class, 所以在调用之前, 应该保证没有指针指向已经释放的类对象,
     特别是局部的 字典数组什么的, 函数结束后集合会对 里面引用的class 进行release
     */
}
@end

测试代码Person类

@interface Person : NSObject
@property (nonatomic, strong)NSString* nane;
@property (nonatomic, strong)NSString* address;
@end

@implementation Person
@end

测试代码VC

@interface VC()
@property (nonatomic, strong)Person* p1;
@property (nonatomic, strong)Person* p2;
@end

调用实例1 一个注册者 的一个属性对一个 监听者

- (void)viewDidLoad {
    [super viewDidLoad];

   Person* p = [Person new];
    p.name = @"贾静雯";
    _p1 = p;


    ////一个注册者 的一个属性对一个 监听者
    LBKVO_ADD(p, name, LBKVOBlock{
        NSLog(@"self name xxxxxx p1");
    });


- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
   NSLog(@"before kvo %@",_p1.name);
    _p1.name = @"凤姐";
    NSLog(@"after kvo %@",_p1.name);

    NSLog(@"--------------------\n");

    NSLog(@"kvo rm before");
    LBKVO_REMOVE(_p1, name);

    NSLog(@"--------------------\n");
    NSLog(@"再次调用 setter before");
    NSLog(@"setter before: %@",_p1.name);
    _p1.name = @"钟楚红";
    NSLog(@"setter after: %@",_p1.name);
}

///输出结果
before kvo 贾静雯
self name xxxxxx p1
after kvo 凤姐
--------------------
 kvo rm before
--------------------
再次调用 setter before
setter before: 凤姐
setter after: 钟楚红


调用实例2 一个注册者多个属性 对一个监听者

- (void)viewDidLoad{
LBKVO_ADD(p, name, LBKVOBlock{
    NSLog(@"self name xxxxxx p1");
});

LBKVO_ADD(p, address, LBKVOBlock{
    NSLog(@"self address xxxxxx p1");
});
}


- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    NSLog(@"before kvo p1 name: %@",_p1.name);
    _p1.name = @"凤姐";
    NSLog(@"after kvo p1 name %@",_p1.name);

    NSLog(@"\n");

    NSLog(@"before kvo p1 address: %@",_p1.address);
    _p1.address = @"东京";
    NSLog(@"after kvo p1 name %@",_p1.address);


    NSLog(@"\n");
    NSLog(@"before kvo rm p1 address: %@",_p1.address);
    LBKVO_REMOVE(_p1, address);
    NSLog(@"call p1 address setter before %@",_p1.address);
    _p1.address = @"上海";
    NSLog(@"call p1 address setter after %@",_p1.address);

    NSLog(@"\n");
    NSLog(@"before kvo again p1 name: %@",_p1.name);
    _p1.name = @"张柏芝";
    NSLog(@"after kvo again p1 name %@",_p1.name);


    LBKVO_ADD(_p1, address, LBKVOBlock{
        NSLog(@"重新添加address的监听");
    });
    NSLog(@"\n");

    NSLog(@"重新添加 address kvo before call setter value: %@",_p1.address);
    _p1.address = @"东京";
    NSLog(@"重新添加 address kvo after call setter value %@",_p1.address);
}


////测试结果
2019-05-19 17:04:38.616917+0800 Runtime[69523:8773175] before kvo p1 name: 贾静雯
2019-05-19 17:04:38.617388+0800 Runtime[69523:8773175] self name xxxxxx p1
2019-05-19 17:04:38.617518+0800 Runtime[69523:8773175] after kvo p1 name 凤姐
2019-05-19 17:04:38.617604+0800 Runtime[69523:8773175] 
2019-05-19 17:04:38.617679+0800 Runtime[69523:8773175] before kvo p1 address: (null)
2019-05-19 17:04:38.617802+0800 Runtime[69523:8773175] self address xxxxxx p1
2019-05-19 17:04:38.617962+0800 Runtime[69523:8773175] after kvo p1 name 东京
2019-05-19 17:04:38.618241+0800 Runtime[69523:8773175] 
2019-05-19 17:04:38.618623+0800 Runtime[69523:8773175] before kvo rm p1 address: 东京
2019-05-19 17:04:38.618960+0800 Runtime[69523:8773175] call p1 address setter before 东京
2019-05-19 17:04:38.619257+0800 Runtime[69523:8773175] call p1 address setter after 上海
2019-05-19 17:04:38.619491+0800 Runtime[69523:8773175] 
2019-05-19 17:04:38.619715+0800 Runtime[69523:8773175] before kvo again p1 name: 凤姐
2019-05-19 17:04:38.620041+0800 Runtime[69523:8773175] self name xxxxxx p1
2019-05-19 17:04:38.620255+0800 Runtime[69523:8773175] after kvo again p1 name 张柏芝
2019-05-19 17:04:38.620644+0800 Runtime[69523:8773175] 
2019-05-19 17:04:38.620951+0800 Runtime[69523:8773175] 重新添加 address kvo before call setter value: 上海
2019-05-19 17:04:38.621284+0800 Runtime[69523:8773175] 重新添加address的监听
2019-05-19 17:04:38.621689+0800 Runtime[69523:8773175] 重新添加 address kvo after call setter value 东京


调用实例3 1个注册者的一个属性 对应多个监听者

- (void)viewDidLoad{
  [super ....];

    LBKVO_ADD(p, name, LBKVOBlock{
        NSLog(@"self name xxxxxx p1");
    });

    [p kvoRegiesterName:@"address" observer:UIApplication.sharedApplication.delegate callback:^(id  _Nonnull old, id  _Nonnull modified, NSString * _Nonnull property) {
        NSLog(@"delegate address xxxxxx p1");
    }];
}


- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    NSLog(@"before kvo p1 name: %@",_p1.name);
    _p1.name = @"凤姐";
    NSLog(@"after kvo p1 name %@",_p1.name);


    NSLog(@"\n");
    NSLog(@"will rm self p1 name");
    LBKVO_REMOVE(_p1, name);
    NSLog(@"after rm self p1 name");

    NSLog(@"\n");
    NSLog(@"again call setter before %@",_p1.name);
    NSLog(@"注意这次是否和没有删除之前 输出了2次, 这次只会输出1次");
    _p1.name = @"赵雅芝";
    NSLog(@"again call setter after %@",_p1.name);


    NSLog(@"\n");
    NSLog(@"will rm self p1 name again");
    LBKVO_REMOVE(_p1, name);
    NSLog(@"after rm self p1 name again");

    NSLog(@"\n");
    NSLog(@"again call setter before %@",_p1.name);
    NSLog(@"这次只会输出1次");
    _p1.name = @"周海媚";
    NSLog(@"again call setter after %@",_p1.name);


    NSLog(@"\n");
    NSLog(@"尝试再为delegate添加 kvo delegate name before %@",_p1.name);
    [_p1 kvoRegiesterName:@"name" observer:UIApplication.sharedApplication.delegate callback:^(id  _Nonnull old, id  _Nonnull modified, NSString * _Nonnull property) {
        NSLog(@"重新添加了 delegat name kvo settering .... 这里不会再次添加");
    }];
    _p1.name = @"赵敏";
    NSLog(@"again call setter after %@",_p1.name);


    NSLog(@"\n");
    NSLog(@"will add self p1 name again");
    [_p1 kvoRegiesterName:@"name" observer:self callback:^(id  _Nonnull old, id  _Nonnull modified, NSString * _Nonnull property) {
        NSLog(@"重新为 self add kvo p1 name");
    }];
    _p1.name = @"ayumi";
    NSLog(@"after add self p1 name again");
}


///测试结果
2019-05-19 17:37:29.222853+0800 Runtime[69932:8788585] before kvo p1 name: 贾静雯
2019-05-19 17:37:29.223526+0800 Runtime[69932:8788585] self name xxxxxx p1
2019-05-19 17:37:29.223780+0800 Runtime[69932:8788585] delegate name xxxxxx p1
2019-05-19 17:37:29.224822+0800 Runtime[69932:8788585] after kvo p1 name 凤姐
2019-05-19 17:37:29.225256+0800 Runtime[69932:8788585] 
2019-05-19 17:37:29.225581+0800 Runtime[69932:8788585] will rm self p1 name
2019-05-19 17:37:29.225796+0800 Runtime[69932:8788585] after rm self p1 name
2019-05-19 17:37:29.225954+0800 Runtime[69932:8788585] 
2019-05-19 17:37:29.226697+0800 Runtime[69932:8788585] again call setter before 凤姐
2019-05-19 17:37:29.227157+0800 Runtime[69932:8788585] 注意这次是否和没有删除之前 输出了2次, 这次只会输出1次
2019-05-19 17:37:29.227679+0800 Runtime[69932:8788585] delegate name xxxxxx p1
2019-05-19 17:37:29.227990+0800 Runtime[69932:8788585] again call setter after 赵雅芝
2019-05-19 17:37:29.228509+0800 Runtime[69932:8788585] 
2019-05-19 17:37:29.228769+0800 Runtime[69932:8788585] will rm self p1 name again
2019-05-19 17:37:29.229074+0800 Runtime[69932:8788585] after rm self p1 name again
2019-05-19 17:37:29.249167+0800 Runtime[69932:8788585] 
2019-05-19 17:37:29.249294+0800 Runtime[69932:8788585] again call setter before 赵雅芝
2019-05-19 17:37:29.249388+0800 Runtime[69932:8788585] 这次只会输出1次
2019-05-19 17:37:29.249515+0800 Runtime[69932:8788585] delegate name xxxxxx p1
2019-05-19 17:37:29.249602+0800 Runtime[69932:8788585] again call setter after 周海媚
2019-05-19 17:37:29.249682+0800 Runtime[69932:8788585] 
2019-05-19 17:37:29.249762+0800 Runtime[69932:8788585] 尝试再为delegate添加 kvo delegate name before 周海媚
2019-05-19 17:37:29.249959+0800 Runtime[69932:8788585] delegate name xxxxxx p1
2019-05-19 17:37:29.250254+0800 Runtime[69932:8788585] again call setter after 赵敏
2019-05-19 17:37:29.250738+0800 Runtime[69932:8788585] 
2019-05-19 17:37:29.250994+0800 Runtime[69932:8788585] before add self p1 name again 赵敏
2019-05-19 17:37:29.251320+0800 Runtime[69932:8788585] delegate name xxxxxx p1
2019-05-19 17:37:29.251475+0800 Runtime[69932:8788585] 重新为 self add kvo p1 name
2019-05-19 17:37:29.251671+0800 Runtime[69932:8788585] after add self p1 name again 滨崎步


调用实例4 多个注册者一个属性对应一个监听者也测试了多个注册者不同属性对应一个监听者

- (void)viewDidLoad{
  Person* p2 = [Person new];
    p2.name = @"布兰妮";
    _p2 = p2;


    LBKVO_ADD(p, name, LBKVOBlock{
        NSLog(@"self name xxxxxx p1");
    });

    LBKVO_ADD(p2, name, LBKVOBlock{
        NSLog(@"self name xxxxxx p2");
    });
}


- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    NSLog(@"before kvo p1 name: %@",_p1.name);
    _p1.name = @"席琳迪翁";
    NSLog(@"after kvo p1 name %@",_p1.name);



    NSLog(@"\n");
    NSLog(@"before kvo p2 name: %@",_p2.name);
    _p2.name = @"斯嘉丽约翰逊";
    NSLog(@"after kvo p2 name %@",_p2.name);
    

    NSLog(@"\n");
    NSLog(@"before rm kvo p2 name");
    [_p2 kvoRemoveWith:self name:@"name"];
    NSLog(@"after rm kvo p2 name");

    NSLog(@"\n");
    NSLog(@"call p2 name seter again before:%@",_p2.name);
    _p2.name = @"Rihanna";
    NSLog(@"call p2 name seter again after:%@",_p2.name);



    NSLog(@"\n");
    NSLog(@"will add p2 address to self");
    [_p2 kvoRegiesterName:@"address" observer:self callback:^(id  _Nonnull old, id  _Nonnull modified, NSString * _Nonnull property) {
        NSLog(@"add p2 address to self setter ....");
    }];
    NSLog(@"after add p2 address to self");

    NSLog(@"\n");
    NSLog(@"call p2 address seter before:%@",_p2.address);
    _p2.address = @"香格里拉";
    NSLog(@"call p2 address seter after:%@",_p2.address);


    NSLog(@"\n");
    NSLog(@"will add p1 address to self");
    [_p1 kvoRegiesterName:@"address" observer:self callback:^(id  _Nonnull old, id  _Nonnull modified, NSString * _Nonnull property) {
        NSLog(@"add p1 address to self setter ....");
    }];
    NSLog(@"after add p1 address to self");
    NSLog(@"\n");
    NSLog(@"call p1 address seter before:%@",_p1.address);
    _p1.address = @"塔里木盆地";
    NSLog(@"call p1 address seter after:%@",_p1.address);
}

///测试结果
2019-05-19 17:46:57.321188+0800 Runtime[70058:8793303] before kvo p1 name: 贾静雯
2019-05-19 17:46:57.321416+0800 Runtime[70058:8793303] self name xxxxxx p1
2019-05-19 17:46:57.321559+0800 Runtime[70058:8793303] after kvo p1 name 席琳迪翁
2019-05-19 17:46:57.321642+0800 Runtime[70058:8793303] 
2019-05-19 17:46:57.321725+0800 Runtime[70058:8793303] before kvo p2 name: 布兰妮
2019-05-19 17:46:57.321812+0800 Runtime[70058:8793303] self name xxxxxx p2
2019-05-19 17:46:57.321895+0800 Runtime[70058:8793303] after kvo p2 name 斯嘉丽约翰逊
2019-05-19 17:46:57.321994+0800 Runtime[70058:8793303] 
2019-05-19 17:46:57.322295+0800 Runtime[70058:8793303] before rm kvo p2 name
2019-05-19 17:46:57.322576+0800 Runtime[70058:8793303] after rm kvo p2 name
2019-05-19 17:46:57.322977+0800 Runtime[70058:8793303] 
2019-05-19 17:46:57.323214+0800 Runtime[70058:8793303] call p2 name seter again before:斯嘉丽约翰逊
2019-05-19 17:46:57.323457+0800 Runtime[70058:8793303] call p2 name seter again after:Rihanna
2019-05-19 17:46:57.323769+0800 Runtime[70058:8793303] 
2019-05-19 17:46:57.323945+0800 Runtime[70058:8793303] will add p2 address to self
2019-05-19 17:46:57.324480+0800 Runtime[70058:8793303] after add p2 address to self
2019-05-19 17:46:57.325522+0800 Runtime[70058:8793303] 
2019-05-19 17:46:57.325967+0800 Runtime[70058:8793303] call p2 address seter before:(null)
2019-05-19 17:46:57.326371+0800 Runtime[70058:8793303] add p2 address to self setter ....
2019-05-19 17:46:57.326553+0800 Runtime[70058:8793303] call p2 address seter after:香格里拉
2019-05-19 17:46:57.326704+0800 Runtime[70058:8793303] 
2019-05-19 17:46:57.326973+0800 Runtime[70058:8793303] will add p1 address to self
2019-05-19 17:46:57.327367+0800 Runtime[70058:8793303] after add p1 address to self
2019-05-19 17:46:57.341871+0800 Runtime[70058:8793303] 
2019-05-19 17:46:57.342702+0800 Runtime[70058:8793303] call p1 address seter before:(null)
2019-05-19 17:46:57.342843+0800 Runtime[70058:8793303] add p1 address to self setter ....
2019-05-19 17:46:57.342938+0800 Runtime[70058:8793303] call p1 address seter after:塔里木盆地

 6 自定义实现消息转发(Aspeacts, RAC的消息几种处理的原理, 有时间会详细介绍的)

结语

OC这门语言,说句实话, 指针满天飞, 但还不至于像c++那样, 如果你OC玩的很溜, 基本是吊打安卓java的,还有所谓的架构师, 不是只有后台才有架构的概念, 更不是说C层面的东西都是过程式调用, 没架构可言, 前端需要掌握的优化的东西不比后台少, 其实你想想那么多语言的底层都是靠C或Cpp来支撑的, cpp的STL的体系结构, 值得学的太多, 你可以将思想运用到任何语言上面, swift虽然很人性化, 但是看个人了, RAC里那么强大的宏运用, 在swfit里完全没有体现出来, 更别谈指针了. 程序猿这种名称在我看来就真的只会写代码而已, 编程的路层要一直往下学,往上学,永远学不完, 当然这是看个人兴趣了.

再次声明

本人没有去看Runtime的源码, 只是凭着经验表示自己对runtime的理解, 我也不想去看什么源码,很多文章源代码贴出来了, 然后介绍一下方法的参数和返回和大致的事情, 我觉得没有必要, 我的学习方式是从自己开发遇到的问题和经验, 然后反向思考而有所获,注重的是自己的理解然后加以运用.
如果有和大家认为出入的地方也仅代表个人理解有误, 欢迎大家指正

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念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

推荐阅读更多精彩内容

  • Swift1> Swift和OC的区别1.1> Swift没有地址/指针的概念1.2> 泛型1.3> 类型严谨 对...
    cosWriter阅读 11,093评论 1 32
  • 前言:面试笔试都是必考语法知识点。请认真复习和深入研究OC。 目录:iOS-面试题-OC基础篇 (1) - (84...
    麦穗0615阅读 4,251评论 0 33
  • 重点掌握 3 类对象和方法 对象就是一个物体 类的独特存在就是一个实例,对实例进行操作叫做方法。方法可以应用于类或...
    Coder大雄阅读 1,256评论 0 2
  • 1.import static是Java 5增加的功能,就是将Import类中的静态方法,可以作为本类的静态方法来...
    XLsn0w阅读 1,217评论 0 2
  • ✨连续两天烧脑的《预炼七年》课程,在欣频老师的引导下,我发现了自己藏在深层的木马程式,也慢慢觉察到现在所处的境遇都...
    lemonfatty阅读 119评论 0 0