RunTime和RunLoop的区别

RunLoop

  • runloop是事件的接受的分发机制的实现
  • runloop提供一种异步执行代码的机制,不能并行执行任务
  • 在主队列中,main Runloop直接配合任务的执行,负责处理UI事件, 定时器以及其他内核相关的事件

Runloop 的主要目的

  • 保证程序执行时不会被系统终止
  • 什么时候使用Runloop
  • 当需要和该线程进行交互的时候才会使用Runloop
  • 灭一个线程都有对应的的Runloop,但是默认的非主线程的Runloop是不执行,需要为Runloop添加至少一个事件源,然后去run。
  • 一般情况下我们不去做这些,除非你要长久去检测单独线程中的某个事件
  • Runloop,是线程进入好被线程用来响应事件以及调用事件处理函数的地方,需要在代码中使用控制语句实现Runloop的循环,需要代码提供while或者for来驱动Runloop在这个循环中,使用一个Runloop对象[NSRunLoop CurrentRunLoop]执行接收消息,调用对应的处理函数

Runloop接受两种数据源事件:Input 和timer Source

Input Source传递事件,通常是来自其他线程和不同线程和不同程序中的消息

Timer Source (定时器)传递同步事件(重复执行或者在特定时间上触发)

处理 input Source Runloop 也会产生一些关于本身的notification,注册Runloop的observe,可以接收这些notification,做一些额外的处理。(使用CoreFundation来成为Runloop的observe)

Runloop工作特点

当有时间发生时,Runloop会根据具体的事件类型通知相应程序作出相应

当没有事件发生时,Runloop会进入休眠状态,从而达到省电的目的

当事件再次发生时,Runloop会被唤醒,处理事件

Runloop组成

  1. 与线程和自动释放迟有关

  2. CFRunLoopRef构造:数据结构;创建与退出;mode切换和item依赖;Runloop启动

    • CFRunLoopModeRef:数据结构(与CFRunLoopRef放一起了);创建;类型;
    • modeItems:
      • CFRunLoopSourceRef:数据结构(source0/source1);
        • source0 :
        • source1 :
    • CFRunLoopTimerRef:数据结构;创建与生效;相关类型(GCD的timer与CADisplayLink)
    • CFRunLoopObserverRef:数据结构;创建与添加;监听的状态;
  3. Runloop内部逻辑:关键在两个判断点(是否睡觉,是否退出)

    • 代码实现:
    • 函数作用栈显示:
  4. Runloop本质:mach port和mach_msg()。

  5. 如何处理事件:

    • 界面刷新:
    • 手势识别:
    • GCD任务:
    • timer:(与CADisplayLink)
    • 网络请求:
  6. 应用:

    • 滑动与图片刷新;
    • 常驻子线程,保持子线程一直处理事件

线程(创建)-->runloop将进入-->最高优先级OB创建释放池-->runloop将睡-->最低优先级OB销毁旧池创建新池-->runloop将退出-->最低优先级OB销毁新池-->线程(销毁)

RunLoop 的构造

 CFRunLoopRef构造:
// runloop数据结构
struct __CFRunLoopMode {
    CFStringRef _name;            // Mode名字, 
    CFMutableSetRef _sources0;    // Set<CFRunLoopSourceRef>
    CFMutableSetRef _sources1;    // Set<CFRunLoopSourceRef>
    CFMutableArrayRef _observers; // Array<CFRunLoopObserverRef>
    CFMutableArrayRef _timers;    // Array<CFRunLoopTimerRef>
...
};
// mode数据结构
struct __CFRunLoop {
    CFMutableSetRef _commonModes;     // Set<CFStringRef>
    CFMutableSetRef _commonModeItems; // Set<Source/Observer/Timer>
    CFRunLoopModeRef _currentMode;    // Current Runloop Mode
    CFMutableSetRef _modes;           // Set<CFRunLoopModeRef>
...
};
复制代码

创建与退出: model切换和item的依赖

  1. 主线程的runloop自动创建,子线程的runloop默认不创建(在子线程中调用NSRunLoop *runloop = [NSRunLoop currentRunLoop]; 获取RunLoop对象的时候,就会创建RunLoop);

  2. runloop退出的条件:app退出;线程关闭;设置最大时间到期;modeItem为空;

  3. 同一时间一个runloop只能在一个mode,切换mode只能退出runloop,再重进指定mode(隔离modeItems使之互不干扰);

  4. 一个item可以加到不同mode;一个mode被标记到commonModes里(这样runloop不用切换mode)。

RunLoop 启动

  • 用DefaultMode启动

     void CFRunLoopRun(void) {
         CFRunLoopRunSpecific(CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 1.0e10, false);
     }
    复制代码
  • 用指定的Mode启动,允许设置RunLoop最大时间(假无限循环),执行完毕是否退出

    int CFRunLoopRunInMode(CFStringRef modeName, CFTimeInterval seconds, Boolean stopAfterHandle) {
        return CFRunLoopRunSpecific(CFRunLoopGetCurrent(), modeName, seconds, returnAfterSourceHandled);
    }
    复制代码
  • RunLoop 自动创建对应的model,model 只能添加不能删除

    添加model CFRunLoopAddCommonMode(CFRunLoopRef runloop, CFStringRef modeName);
    复制代码

    类型

    1. kCFRunLoopDefaultMode: 默认 mode,通常主线程在这个 Mode 下运行。
    2. UITrackingRunLoopMode: 追踪mode,保证Scrollview滑动顺畅不受其他 mode 影响。
    3. UIInitializationRunLoopMode: 启动程序后的过渡mode,启动完成后就不再使用。
    4. GSEventReceiveRunLoopMode: Graphic相关事件的mode,通常用不到。
    5. kCFRunLoopCommonModes: 占位mode,作为标记DefaultMode和CommonMode用。

    modelItems

     // 添加移除item的函数(参数:添加/移除哪个item到哪个runloop的哪个mode下)
     CFRunLoopAddSource(CFRunLoopRef rl, CFRunLoopSourceRef source, CFStringRef modeName);
    
     CFRunLoopAddObserver(CFRunLoopRef rl, CFRunLoopObserverRef observer, CFStringRef modeName);
     
     CFRunLoopAddTimer(CFRunLoopRef rl, CFRunLoopTimerRef timer, CFStringRef mode);
    
     CFRunLoopRemoveSource(CFRunLoopRef rl, CFRunLoopSourceRef source, CFStringRef modeName);
    
     CFRunLoopRemoveObserver(CFRunLoopRef rl, CFRunLoopObserverRef observer, CFStringRef modeName);
    
     CFRunLoopRemoveTimer(CFRunLoopRef rl, CFRunLoopTimerRef timer, CFStringRef mode);
    复制代码

    CFRunLoopSourceRef:事件来源

    按照官方文档CFRunLoopSourceRef为3类,但数据结构只有两类(???)
    Port-Based Sources:与内核端口相关
    Custom Input Sources:与自定义source相关
    Cocoa Perform Selector Sources:与PerformSEL方法相关)
    复制代码

CFRunLoopObserverRef:监听runloop状态,接收回调信息(常见于自动释放池创建销毁)

  // 第一个参数用于分配该observer对象的内存空间
  // 第二个参数用以设置该observer监听什么状态
  // 第三个参数用于标识该observer是在第一次进入run loop时执行还是每次进入run loop处理时均执行
  // 第四个参数用于设置该observer的优先级,一般为0
  // 第五个参数用于设置该observer的回调函数
  // 第六个参数observer的运行状态   
  CFRunLoopObserverCreateWithHandler(CFAllocatorGetDefault(), kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
     // 执行代码
  }

  typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
      kCFRunLoopEntry         = (1UL << 0), // 即将进入Loop
      kCFRunLoopBeforeTimers  = (1UL << 1), // 即将处理 Timer
      kCFRunLoopBeforeSources = (1UL << 2), // 即将处理 Source
      kCFRunLoopBeforeWaiting = (1UL << 5), // 即将进入休眠
      kCFRunLoopAfterWaiting  = (1UL << 6), // 刚从休眠中唤醒
      kCFRunLoopExit          = (1UL << 7), // 即将退出Loop
  };
复制代码

Runloop内部逻辑:关键在两个判断点(是否睡觉,是否退出)

int CFRunLoopRunSpecific(runloop, modeName, seconds, stopAfterHandle) {

  // 0.1 根据modeName找到对应mode
  CFRunLoopModeRef currentMode = __CFRunLoopFindMode(runloop, modeName, false);
  // 0.2 如果mode里没有source/timer/observer, 直接返回。
  if (__CFRunLoopModeIsEmpty(currentMode)) return;

  // 1.1 通知 Observers: RunLoop 即将进入 loop。---(OB会创建释放池)
  __CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopEntry);

  // 1.2 内部函数,进入loop
  __CFRunLoopRun(runloop, currentMode, seconds, returnAfterSourceHandled) {

      Boolean sourceHandledThisLoop = NO;
      int retVal = 0;
      do {

          // 2.1 通知 Observers: RunLoop 即将触发 Timer 回调。
          __CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopBeforeTimers);
          // 2.2 通知 Observers: RunLoop 即将触发 Source0 (非port) 回调。
          __CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopBeforeSources);
          // 执行被加入的block
          __CFRunLoopDoBlocks(runloop, currentMode);

          // 2.3 RunLoop 触发 Source0 (非port) 回调。
          sourceHandledThisLoop = __CFRunLoopDoSources0(runloop, currentMode, stopAfterHandle);
          // 执行被加入的block
          __CFRunLoopDoBlocks(runloop, currentMode);

          // 2.4 如果有 Source1 (基于port) 处于 ready 状态,直接处理这个 Source1 然后跳转去处理消息。
          if (__Source0DidDispatchPortLastTime) {
              Boolean hasMsg = __CFRunLoopServiceMachPort(dispatchPort, &msg)
              if (hasMsg) goto handle_msg;
          }

          // 3.1 如果没有待处理消息,通知 Observers: RunLoop 的线程即将进入休眠(sleep)。--- (OB会销毁释放池并建立新释放池)
          if (!sourceHandledThisLoop) {
              __CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopBeforeWaiting);
          }

          // 3.2. 调用 mach_msg 等待接受 mach_port 的消息。线程将进入休眠, 直到被下面某一个事件唤醒。
          // -  一个基于 port 的Source1 的事件。
          // -  一个 Timer 到时间了
          // -  RunLoop 启动时设置的最大超时时间到了
          // -  被手动唤醒
          __CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort) {
              mach_msg(msg, MACH_RCV_MSG, port); // thread wait for receive msg
          }

          // 3.3. 被唤醒,通知 Observers: RunLoop 的线程刚刚被唤醒了。
          __CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopAfterWaiting);

          // 4.0 处理消息。
          handle_msg:

          // 4.1 如果消息是Timer类型,触发这个Timer的回调。
          if (msg_is_timer) {
              __CFRunLoopDoTimers(runloop, currentMode, mach_absolute_time())
          } 

          // 4.2 如果消息是dispatch到main_queue的block,执行block。
          else if (msg_is_dispatch) {
              __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg);
          } 

          // 4.3 如果消息是Source1类型,处理这个事件
          else {
              CFRunLoopSourceRef source1 = __CFRunLoopModeFindSourceForMachPort(runloop, currentMode, livePort);
              sourceHandledThisLoop = __CFRunLoopDoSource1(runloop, currentMode, source1, msg);
              if (sourceHandledThisLoop) {
                  mach_msg(reply, MACH_SEND_MSG, reply);
              }
          }

          // 执行加入到Loop的block
          __CFRunLoopDoBlocks(runloop, currentMode);


          // 5.1 如果处理事件完毕,启动Runloop时设置参数为一次性执行,设置while参数退出Runloop
          if (sourceHandledThisLoop && stopAfterHandle) {
              retVal = kCFRunLoopRunHandledSource;
          // 5.2 如果启动Runloop时设置的最大运转时间到期,设置while参数退出Runloop
          } else if (timeout) {
              retVal = kCFRunLoopRunTimedOut;
          // 5.3 如果启动Runloop被外部调用强制停止,设置while参数退出Runloop
          } else if (__CFRunLoopIsStopped(runloop)) {
              retVal = kCFRunLoopRunStopped;
          // 5.4 如果启动Runloop的modeItems为空,设置while参数退出Runloop
          } else if (__CFRunLoopModeIsEmpty(runloop, currentMode)) {
              retVal = kCFRunLoopRunFinished;
          }

          // 5.5 如果没超时,mode里没空,loop也没被停止,那继续loop,回到第2步循环。
      } while (retVal == 0);
  }

  // 6. 如果第6步判断后loop退出,通知 Observers: RunLoop 退出。--- (OB会销毁新释放池)
  __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit);
}

复制代码

一步一步写具体的实现逻辑过于繁琐不便理解,按Runloop状态大致分为:

  1. Entry:通知OB(创建pool);
  2. 执行阶段:按顺序通知OB并执行timer,source0;若有source1执行source1;
  3. 休眠阶段:利用mach_msg判断进入休眠,通知OB(pool的销毁重建);被消息唤醒通知OB;
  4. 执行阶段:按消息类型处理事件;
  5. 判断退出条件:如果符合退出条件(一次性执行,超时,强制停止,modeItem为空)则退出,否则回到第2阶段;
  6. Exit:通知OB(销毁pool)

Runloop本质:mach port和mach_msg()。

Mach是XNU的内核,进程、线程和虚拟内存等对象通过端口发消息进行通信,Runloop通过mach_msg()函数发送消息,如果没有port 消息,内核会将线程置于等待状态 mach_msg_trap() 。如果有消息,判断消息类型处理事件,并通过modeItem的callback回调。
Runloop有两个关键判断点,一个是通过msg决定Runloop是否等待,一个是通过判断退出条件来决定Runloop是否循环。
复制代码

如何处理事件

1.界面处理

  • 当UI改变( Frame变化、 UIView/CALayer 的继承结构变化等)时,或手动调用了 UIView/CALayer 的 setNeedsLayout/setNeedsDisplay方法后,这个 UIView/CALayer 就被标记为待处理。
  • 苹果注册了一个用来监听BeforeWaiting和Exit的Observer,在它的回调函数里会遍历所有待处理的 UIView/CAlayer 以执行实际的绘制和调整,并更新 UI 界面。
  1. 事件响应
  • 当一个硬件事件(触摸/锁屏/摇晃/加速等)发生后,首先由 IOKit.framework 生成一个 IOHIDEvent 事件并由 SpringBoard 接收, 随后由mach port 转发给需要的App进程。
  • 苹果注册了一个 Source1 (基于 mach port 的) 来接收系统事件,通过回调函数触发Sourece0(所以UIEvent实际上是基于Source0的),调用 _UIApplicationHandleEventQueue() 进行应用内部的分发。
  • _UIApplicationHandleEventQueue() 会把 IOHIDEvent 处理并包装成 UIEvent 进行处理或分发,其中包括识别 UIGesture/处理屏幕旋转/发送给 UIWindow 等。
  1. 手势识别
  • 如果上一步的 _UIApplicationHandleEventQueue() 识别到是一个guesture手势,会调用Cancel方法将当前的touchesBegin/Move/End 系列回调打断。随后系统将对应的 UIGestureRecognizer 标记为待处理。
  • 苹果注册了一个 Observer 监测 BeforeWaiting (Loop即将进入休眠) 事件,其回调函数为_UIGestureRecognizerUpdateObserver(),其内部会获取所有刚被标记为待处理的 GestureRecognizer,并执行GestureRecognizer的回调。
  • 当有 UIGestureRecognizer 的变化(创建/销毁/状态改变)时,这个回调都会进行相应处理。

4.GCD任务

  • 当调用 dispatch_async(dispatch_get_main_queue(), block) 时,libDispatch 会向主线程的 RunLoop 发送消息,RunLoop会被唤醒,并从消息中取得这个 block,并在回调里执行这个 block。Runloop只处理主线程的block,dispatch 到其他线程仍然是由 libDispatch 处理的。

RunTime

Runtime就是系统在运行的时候的一种机制,其中最重要的就是消息机制,对于C语言,函数的调用在编译的时候就会决定调用那个函数C语言的函数调用请看这里 
)。编译完成之后直接顺序执行,无任何二义性。OC的函数调用成为消息发送。属于动态调用过程。在编译的时候并不能决定真正调用哪个函数(事实证明,在编
译阶段,OC可以调用任何函数,即使这个函数并未实现,只要申明过就不会报错。而C语言在编译阶段就会报错)。只有在真正运行的时候才会根据函数的名称找
到对应的函数来调用。
复制代码
  1. 封装:在这个库中,对象可以用C语言中的结构体表示,而方法可以用C函数来实现,另外再加上了一些额外的特性。这些结构体和函数被runtime函数封装后,我们就可以在程序运行时创建,检查,修改类、对象和它们的方法了。

Runtime主要实现思路

实例对象instance->类class->方法method(->SEL->IMP)->实现函数
实例对象只存放ISA指针和实例变量,由ISA指针找到所属类,类维护一个运行时可接收的方法列表;方法列表中的每个入口是一个方法(Method),其中key是一个特定名称,即选择器(SEL),其对应一个指向底层C实现函数的指针,即实现(IMP),。运行时机制最关键核心是objc_msgSend函数,通过给target(类)发送selecter(SEL)来传递消息,找到匹配的IMP,指向实现的C函数。
由于OC的运行时动态特性,在编译之后可以在运行时通过C操作函数,动态地创建修改类信息,动态绑定方法和重写实现,灵活地实现一些自定义功能。
在oc中消息机制如何调用
复制代码

RunTime 介绍

  1. 类的本质
- 类相关:
    + 数据类型:class,object;
                 - isa 元类
                 - superClass 根类
    + 操作函数:
       - class_:
           + get: 类名,父类; 实例变量,成员变量;属性;实例方法,类方法,方法实现;
           + copy: 成员变量列表;属性列表;方法列表;协议列表;
           + add: 成员变量;属性;方法;协议;
           + replace:属性;方法;
           + respond:响应方法判断(内省)
           + isMetaclass:元类判断(内省)
           + conform:遵循协议判断(内省)
       - objc_:
           + get: 实例变量;成员变量;类名;类;元类;关联对象;
           + copy: 对象;类;类列表;协议列表;
           + set: 实例变量;成员变量;类;类列表;协议;关联对象;
           + dispose: 对象;
           - 动态创建/销毁类、对象
- 成员变量、属性相关:
+ 数据类型:Ivar;objc_property_t;objc_property_attribute_t;
          + 操作函数:
           - ivar_:
           - property_:
- 方法消息相关:
   + 数据类型:SEL;IMP; Method;方法缓存
   + 操作函数: 
        - method_:
            + invoke: 方法实现的返回值;
            + get: 方法名;方法实现;参数与返回值相关;
            + set:方法实现;
            + exchange:交换方法实现
    + 方法调用:msgSend函数(找到方法实现)
    + 消息转发:
            - Method Resolution
- Fast Forwarding
- Normal Forwarding
- 协议相关:
    + 数据类型:Protocol;
    + 操作函数:
        - protocol_:
             + get: 协议;属性;
             + copy:协议列表;属性列表;
             + add:属性;方法;协议;
             + isEqual:判断两协议等同;
             + comform:判断是否遵循协议;
 - 其他:类名;版本号;类信息;(忽略)
复制代码

动态实现方法交换

	Method Swizzling
   具体方法:方法交还,也可做方法拦截
//静态就交换静态,实例方法就交换实例方法
void Swizzle(Class c, SEL origSEL, SEL newSEL) {
   Method origMethod = class_getInstanceMethod(c, origSEL);
   Method newMethod = nil;
   if (!origMethod) {
   	origMethod = class_getClassMethod(c, origSEL);
       if (!origMethod) {
           return;
       }
       newMethod = class_getClassMethod(c, newSEL);
       if (!newMethod) {
           return;
       }
   } else {
       newMethod = class_getInstanceMethod(c, newSEL);
       if (!newMethod) {
           return;
       }
   }
   
   //自身已经有了就添加不成功,直接交换即可
   if(class_addMethod(c, origSEL, method_getImplementation(newMethod), method_getTypeEncoding(newMethod))) {
       class_replaceMethod(c, newSEL, method_getImplementation(origMethod), method_getTypeEncoding(origMethod));
   } else {
       method_exchangeImplementations(origMethod, newMethod);
   }
}
复制代码

runtime对类分析

@interface NSObject<nsobject>{

Class isa OBJC_ISA_AVAILABILITY;
}</nsobject>

在NSObject中存在一个Class 指针, 然后我们看Class

typedef struct objc_Class *Class;

struct objc_class{
Class isa;//指向metaclass

Class super_class;//指向父类
Const char *name;// 类名

Long version;//版本信息,初始化默认是0,可以根据runtime函数class_setVersion和class_getVersion进行修改、读取
Long info一些标识信息,如CLS_CLASS (0x1L)
表示该类为普通 class ,其中包含对象方法和成员变量;CLS_META (0x2L)
表示该类为 metaclass,其中包含类方法; 
  long instance_size ; // 该类的实例变量大小(包括从父类继承下来的实例变量);  
struct objc_ivar_list *ivars; // 用于存储每个成员变量的地址  
struct objc_method_list **methodLists ; // 与 info 的一些标志位有关,如CLS_CLASS (0x1L),则存储对象方法,如CLS_META (0x2L),则存储类方法;  
 struct objc_cache *cache; // 指向最近使用的方法的指针,用于提升效率;
 struct objc_protocol_list *protocols; // 存储该类遵守的协议  
}


Class 
isa:指向metaclass,也就是静态的Class。一般一个Obj对象中的isa会指向普通的Class,这个Class中存储普通成员变量和对
象方法(“-”开头的方法),普通Class中的isa指针指向静态Class,静态Class中存储static类型成员变量和类方法(“+”开头的方
法)。

复制代码
  1. 数据类型:
|isa和super_class :不同的类中可以有相同的方法(同一个类的方法不能同名,哪怕参数类型不同,后面解释...),所以要先确定是那个类。isa和super_class是找到实现函数的关键映射,决定找到存放在哪个类的方法实现。(isa用于自省确定所属类,super_class确定继承关系)。
实例对象的isa指针指向类,类的isa指针指向其元类(metaClass)。对象就是一个含isa指针的结构体。类存储实例对象的方法列表,元类存储类的方法列表,元类也是类对象。
复制代码
  1. 操作函数:类对象以class_为前缀,实例对象以object_为前缀
  • class
get: 类名,父类,元类;实例变量,成员变量;属性;实例方法,类方法,方法实现;
// 获取类的类名
const char * class_getName ( Class cls );
// 获取类的父类
Class class_getSuperclass ( Class cls );

// 获取实例大小
size_t class_getInstanceSize ( Class cls );
// 获取类中指定名称实例成员变量的信息
Ivar class_getInstanceVariable ( Class cls, const char *name );
// 获取类成员变量的信息
Ivar class_getClassVariable ( Class cls, const char *name );
// 获取指定的属性
objc_property_t class_getProperty ( Class cls, const char *name );

// 获取实例方法
Method class_getInstanceMethod ( Class cls, SEL name );
// 获取类方法
Method class_getClassMethod ( Class cls, SEL name );
// 获取方法的具体实现
IMP class_getMethodImplementation ( Class cls, SEL name );
IMP class_getMethodImplementation_stret ( Class cls, SEL name );

复制代码
  • copy 成员变量列表;属性列表;方法列表;协议列表;
Ivar * class_copyIvarList ( Class cls, unsigned int *outCount );
// 获取属性列表
objc_property_t * class_copyPropertyList ( Class cls, unsigned int *outCount );
// 获取所有方法的列表
Method * class_copyMethodList ( Class cls, unsigned int *outCount );
// 获取类实现的协议列表
Protocol * class_copyProtocolList ( Class cls, unsigned int *outCount )

复制代码
  • add: 成员变量;属性;方法;协议;(添加成员变量只能在运行时创建的类,且不能为元类)
// 添加成员变量
BOOL class_addIvar ( Class cls, const char *name, size_t size, uint8_t alignment, const char *types );
// 添加属性
BOOL class_addProperty ( Class cls, const char *name, const objc_property_attribute_t *attributes, unsigned int attributeCount );
// 添加方法
BOOL class_addMethod ( Class cls, SEL name, IMP imp, const char *types );
// 添加协议
BOOL class_addProtocol ( Class cls, Protocol *protocol );
复制代码
  • replace: 属性和方法
// 替换类的属性
void class_replaceProperty ( Class cls, const char *name, const objc_property_attribute_t *attributes, unsigned int attributeCount );
// 替代方法的实现
IMP class_replaceMethod ( Class cls, SEL name, IMP imp, const char *types );
复制代码
  • reponse:响应方法判断
// 类实例是否响应指定的selector
BOOL class_respondsToSelector ( Class cls, SEL sel );
复制代码
  • isMetaCLass: 元类判断
// 判断给定的Class是否是一个元类
BOOL class_isMetaClass ( Class cls );
复制代码
  • conform: 遵循协议判断
// 返回类是否实现指定的协议
BOOL class_conformsToProtocol ( Class cls, Protocol *protocol );
复制代码
  • objc_ //对象
get: 实例变量;成员变量;类名;类;元类;关联对象
// 获取对象实例变量
Ivar object_getInstanceVariable ( id obj, const char *name, void **outValue );
// 获取对象中实例变量的值
id object_getIvar ( id obj, Ivar ivar );
// 获取对象的类名
const char * object_getClassName ( id obj );
// 获取对象的类
Class object_getClass ( id obj );
Class objc_getClass ( const char *name );
// 返回指定类的元类
Class objc_getMetaClass ( const char *name );
//获取关联对象
id objc_getAssociatedObject(self, &myKey);
复制代码
  • copy : 对象、类、类列表、协议列表
// 获取指定对象的一份拷贝
id object_copy ( id obj, size_t size );
// 创建并返回一个指向所有已注册类的指针列表
Class * objc_copyClassList ( unsigned int *outCount );

复制代码
  • set: 实例变量、类、类列表、协议、关联对象
// 设置类实例的实例变量的值
Ivar object_setInstanceVariable ( id obj, const char *name, void *value );
// 设置对象中实例变量的值
void object_setIvar ( id obj, Ivar ivar, id value );
//设置关联对象.其中myKey自己在用的时候需要在设置一个,在运行时的时候它是通过这个未标记的,里面有一套完善的机制,就是你在实现kvo的时候也是可以的
void objc_setAssociatedObject(self, &myKey, anObject, OBJC_ASSOCIATION_RETAIN);
复制代码
  • dispose: 对象
// 释放指定对象占用的内存
id object_dispose ( id obj );
复制代码

动态创建/销毁类、对象

  • 动态创建/销毁类:
// 创建一个新类和元类
Class objc_allocateClassPair ( Class superclass, const char *name, size_t extraBytes );

// 销毁一个类及其相关联的类
void objc_disposeClassPair ( Class cls );

// 在应用中注册由objc_allocateClassPair创建的类
void objc_registerClassPair ( Class cls );
复制代码
  • 动态创建/销毁对象:
// 创建类实例
id class_createInstance ( Class cls, size_t extraBytes );

// 在指定位置创建类实例
id objc_constructInstance ( Class cls, void *bytes );

// 销毁类实例
void * objc_destructInstance ( id obj );
Class super_class:指向父类,如果这个类是根类,则为NULL。
下面一张图片很好的描述了类和对象的继承关系:
复制代码

注意:所有metaclass中isa指针都指向跟metaclass。而跟metaclass则指向自身。
Root metaclass是通过继承Root class产生的。与root class结构体成员一致,也就是前面提到的结构。不同的是Root 
metaclass的isa指针指向自身。

上图的解释

1、isa:实例对象->类->元类->(不经过父元类)直接到根元类(NSObject的元类),根元类的isa指向自己;

2、 superclass:类->父类->...->根类NSObject,元类->父元类->...->根元类->根类,NSObject的superclass指向nil。
@selector (makeText):

这是一个SEL方法选择器。SEL其主要作用是快速的通过方法名字(makeText)查找到对应方法的函数指针,然后调用其函数。SEL其本身是一个
Int类型的一个地址,地址中存放着方法的名字。对于一个类中。每一个方法对应着一个SEL。所以iOS类中不能存在2个名称相同的方法,即使参数类型不
同,因为SEL是根据方法名字生成的,相同的方法名称只能对应一个SEL。
这就是为什么  oc中不支持方法重载,因为函数指针只是根据函数名来生成的,在调用的时候,在去通过映射去找,
objc_msgSend(self, @selector(makeText));
void makeText (id self,SEL sel)
{
    NSLog(@"%@ %@",self,NSStringFromSelector(sel));
}

// 第一个参数:给哪个类添加方法
        // 第二个参数:添加方法的方法编号
        // 第三个参数:添加方法的函数实现(函数地址)
        // 第四个参数:函数的类型,(返回值+参数类型) v:void @:对象->self :表示SEL->_cmd
class_addMethod(self, @selector(eat), eat, "v@:");
下面我们就来看看具体消息发送之后是怎么来动态查找对应的方法的。

        首先,编译器将代码[obj makeText];转化为objc_msgSend(obj, @selector 
(makeText));,在objc_msgSend函数中。首先通过obj的isa指针找到obj对应的class。在Class中先去cache中
通过SEL查找对应函数method(猜测cache中method列表是以SEL为key通过hash表来存储的,这样能提高函数查找速度),若
cache中未找到。再去methodList中查找,若methodlist中未找到,则取superClass中查找。若能找到,则将method加
入到cache中,以方便下次查找,并通过method中的函数指针跳转到对应的函数中去执行。
复制代码

实例变量、属性相关

  1. 实例变量和属性也是类对象的关键配置。
  • 数据类型:
Ivar;
typedef struct objc_ivar *Ivar;

struct objc_ivar {
  char *ivar_name                 OBJC2_UNAVAILABLE;  // 变量名
  char *ivar_type                 OBJC2_UNAVAILABLE;  // 变量类型
  int ivar_offset                 OBJC2_UNAVAILABLE;  // 基地址偏移字节
#ifdef __LP64__
  int space                       OBJC2_UNAVAILABLE;
#endif
}
objc_property_attribute_t(属性的特性有:返回值、是否为atomic、getter/setter名字、是否为dynamic、背后使用的ivar名字、是否为弱引用等)
typedef struct {
  const char *name;           // 特性名
  const char *value;          // 特性值
} objc_property_attribute_t;
复制代码
  • 操作函数
ivar_:
get:
// 获取成员变量名
const char * ivar_getName ( Ivar v );

// 获取成员变量类型编码
const char * ivar_getTypeEncoding ( Ivar v );

// 获取成员变量的偏移量
ptrdiff_t ivar_getOffset ( Ivar v );

property
// 获取属性名
const char * property_getName ( objc_property_t property );

// 获取属性特性描述字符串
const char * property_getAttributes ( objc_property_t property );

// 获取属性中指定的特性
char * property_copyAttributeValue ( objc_property_t property, const char *attributeName );

// 获取属性的特性列表
objc_property_attribute_t * property_copyAttributeList ( objc_property_t property, unsigned int *outCount );
复制代码

方法消息机制相关

  • SEL

    SEL又叫选择器,是表示一个方法的selector的指针,映射方法的名字。Objective-C在编译时,会依据每一个方法的名字、参数序列,生成一个唯一的整型标识(Int类型的地址),这个标识就是SEL。 SEL的作用是作为IMP的KEY,存储在NSSet中,便于hash快速查询方法。SEL不能相同,对应方法可以不同。所以在Objective-C同一个类(及类的继承体系)中,不能存在2个同名的方法,就算参数类型不同。多个方法可以有同一个SEL。 不同的类可以有相同的方法名。不同类的实例对象执行相同的selector时,会在各自的方法列表中去根据selector去寻找自己对应的IMP 相关概念:类型编码(Type Encoding) 编译器将每个方法的返回值和参数类型编码为一个字符串,并将其与方法的selector关联在一起。可以使用@encode编译器指令来获取它。

    typedef struct objc_selector *SEL; <objc/runtime.h>中没有公开具体的objc_selector结构体成员。

    但通过log可知SEL本质是一个字符串。

  • IMP

    IMP是指向实现函数的指针,通过SEL取得IMP后,我们就获得了最终要找的实现函数的入口

    typedefine id (*IMP)(id, SEL, ...)

  • Method

    这个结构体相当于在SEL和IMP之间作了一个绑定。这样有了SEL,我们便可以找到对应的IMP,从而调用方法的实现代码。(在运行时才将SEL和IMP绑定, 动态配置方法)
    复制代码

typedef struct objc_method *Method;

struct objc_method { SEL method_name OBJC2_UNAVAILABLE; // 方法名 char *method_types OBJC2_UNAVAILABLE; // 参数类型 IMP method_imp OBJC2_UNAVAILABLE; // 方法实现 } objc_method_list 就是用来存储当前类的方法链表,objc_method存储了类的某个方法的信息。 struct objc_method_list { struct objc_method_list obsolete OBJC2_UNAVAILABLE; int method_count OBJC2_UNAVAILABLE; #ifdef LP64 int space OBJC2_UNAVAILABLE; #endif / variable length structure */ struct objc_method method_list[1] OBJC2_UNAVAILABLE; }

```
复制代码
  • 方法缓存

    方法调用最先是在方法缓存里找的,方法调用是懒调用,第一次调用时加载后加到缓存池里。一个objc程序启动后,需要进行类的初始化、调用方法时的cache初始化,再发送消息的时候就直接走缓存(引申:+load方法和+initialize方法。load方法是首次加载类时调用,绝对只调用一次;initialize方法是首次给类发消息时调用,通常只调用一次,但如果它的子类初始化时未定义initialize方法,则会再调用一次它的initialize方法)。
      struct objc_cache {
      // 缓存bucket的总数
      unsigned int mask /* total = mask + 1 */                 OBJC2_UNAVAILABLE;
    
      // 实际缓存bucket的总数
      unsigned int occupied                                    OBJC2_UNAVAILABLE;
      // 指向Method数据结构指针的数组
      Method buckets[1]                                        OBJC2_UNAVAILABLE;
      };
    
    复制代码
  • 操作函数

method_:
invoke: 方法实现的返回值;
// 调用指定方法的实现
id method_invoke ( id receiver, Method m, ... );

// 调用返回一个数据结构的方法的实现
void method_invoke_stret ( id receiver, Method m, ... );
get: 方法名;方法实现;参数与返回值相关;
// 获取方法名
SEL method_getName ( Method m );

// 返回方法的实现
IMP method_getImplementation ( Method m );
// 获取描述方法参数和返回值类型的字符串
const char * method_getTypeEncoding ( Method m );
// 返回方法的参数的个数
unsigned int method_getNumberOfArguments ( Method m );
// 通过引用返回方法指定位置参数的类型字符串
void method_getArgumentType ( Method m, unsigned int index, char *dst, size_t dst_len );
copy: 返回值类型,参数类型
// 获取方法的返回值类型的字符串
char * method_copyReturnType ( Method m );

// 获取方法的指定位置参数的类型字符串
char * method_copyArgumentType ( Method m, unsigned int index );

// 通过引用返回方法的返回值类型字符串
void method_getReturnType ( Method m, char *dst, size_t dst_len );
set:方法实现;
// 设置方法的实现
IMP method_setImplementation ( Method m, IMP imp );
exchange:交换方法实现
// 交换两个方法的实现
void method_exchangeImplementations ( Method m1, Method m2 );
description : 方法描述
// 返回指定方法的方法描述结构体
struct objc_method_description * method_getDescription ( Method m );
sel_
// 返回给定选择器指定的方法的名称
const char * sel_getName ( SEL sel );

// 在Objective-C Runtime系统中注册一个方法,将方法名映射到一个选择器,并返回这个选择器
SEL sel_registerName ( const char *str );

// 在Objective-C Runtime系统中注册一个方法
SEL sel_getUid ( const char *str );

// 比较两个选择器
BOOL sel_isEqual ( SEL lhs, SEL rhs );
c、方法调用流程:向对象发送消息,实际上是调用objc_msgSend函数,obj_msgSend的实际动作就是:找到这个函数指针,然后调用它。
id objc_msgSend(receiver self, selector _cmd, arg1, arg2, ...)
self和_cmd是隐藏参数,在编译期被插入实现代码。
self:指向消息的接受者target的对象类型,作为一个占位参数,消息传递成功后self将指向消息的receiver。
_cmd: 指向方法实现的SEL类型。
当向一般对象发送消息时,调用objc_msgSend;当向super发送消息时,调用的是objc_msgSendSuper; 如果返回值是一个结构体,则会调用objc_msgSend_stret或objc_msgSendSuper_stret。
0.1-检查target是否为nil。如果为nil,直接cleanup,然后return。(这就是我们可以向nil发送消息的原因。) 如果方法返回值是一个对象,那么发送给nil的消息将返回nil;如果方法返回值为指针类型,其指针大小为小于或者等于sizeof(void*),float,double,long double 或者long long的整型标量,发送给nil的消息将返回0;如果方法返回值为结构体,发送给nil的消息将返回0。结构体中各个字段的值将都是0;如果方法的返回值不是上述提到的几种情况,那么发送给nil的消息的返回值将是未定义的。 0.2-如果target非nil,在target的Class中根据Selector去找IMP。(因为同一个方法可能在不同的类中有不同的实现,所以我们需要依赖于接收者的类来找到的确切的实现)。
1-首先它找到selector对应的方法实现: 
*1.1-在target类的方法缓存列表里检查有没有对应的方法实现,有的话,直接调用。 *1.2-比较请求的selector和类方法列表中的selector,对应的话,直接调用。 
*1.3-比较请求的selector和父类方法列表,父类的父类,直至根类,如果有对应,则直接调用。(方法重写拦截父类方法的原理) 
2-调用方法实现,并将接收者对象及方法的所有参数传给它。 
3-最后,将实现函数的返回值作为自己的返回值。
复制代码
  • 动态方法解析与消息转发:如果以上的类中没有找到对应的selector(一般保险起见先用respondsToSelector:内省判断):,还可以利用消息转发机制依次执行以下流程:
Method Resolution(动态方法解析):
用所属类的类方法+(BOOL)resolveInstanceMethod:(实例方法)或者+(BOOL)resolveClassMethod:(类方法),在此方法里添加class_addMethod函数。一般用于@dynamic动态属性。(当一个属性声明为@dynamic,就是向编译器保证编译时不用管/get实现,一定会在运行时实现)。
Fast Forwarding (快速消息转发):
如果上一步无法响应消息,调用- (id)forwardingTargetForSelector:(SEL)aSelector方法,将消息接受者转发到另一个对象target(不能为self,否则死循环)。
Normal Forwarding(普通消息转发):
如果上一步无法响应消息:
调用方法签名- (NSMethodSignature )methodSignatureForSelector:(SEL)aSelector,方法签名目的将函数的参数类型和返回值封装;
如果返回非nil,则创建一个NSInvocation对象利用方法签名和selector封装未被处理的消息,作为参数传递给- (void)forwardInvocation:(NSInvocation )anInvocation。
这一步比较耗时。
如果以上步骤(消息传递和消息转发)还是不能响应消息,则调动doesNotRecognizeSelector:方法,抛出异常。
复制代码
  • 协议相关:@protocol声明了可以被其他任何类实现的方法,协议仅仅是定义一个接口,而由其他的类去负责实现。 protocol是一个对象结构体。
objc_:
// 返回指定的协议
Protocol * objc_getProtocol ( const char *name );

// 获取运行时所知道的所有协议的数组
Protocol ** objc_copyProtocolList ( unsigned int *outCount );

// 创建新的协议实例
Protocol * objc_allocateProtocol ( const char *name );

// 在运行时中注册新创建的协议
void objc_registerProtocol ( Protocol *proto );
protocol_:
get: 协议;属性;
// 返回协议名
const char * protocol_getName ( Protocol *p );
// 获取协议的指定属性
objc_property_t protocol_getProperty ( Protocol *proto, const char *name, BOOL isRequiredProperty, BOOL isInstanceProperty );
copy:协议列表;属性列表;
// 获取协议中的属性列表
objc_property_t * protocol_copyPropertyList ( Protocol *proto, unsigned int *outCount );
// 获取协议采用的协议
Protocol ** protocol_copyProtocolList ( Protocol *proto, unsigned int *outCount );
add:属性;方法;协议;
// 为协议添加方法
void protocol_addMethodDescription ( Protocol *proto, SEL name, const char *types, BOOL isRequiredMethod, BOOL isInstanceMethod );

// 添加一个已注册的协议到协议中
void protocol_addProtocol ( Protocol *proto, Protocol *addition );

// 为协议添加属性
void protocol_addProperty ( Protocol *proto, const char *name, const objc_property_attribute_t *attributes, unsigned int attributeCount, BOOL isRequiredProperty, BOOL isInstanceProperty );
isEqual:判断两协议等同;
// 测试两个协议是否相等
BOOL protocol_isEqual ( Protocol *proto, Protocol *other );
comform:判断是否遵循协议;
// 查看协议是否采用了另一个协议
BOOL protocol_conformsToProtocol ( Protocol *proto, Protocol *other );
复制代码
  • 动态实现
Method Swizzling;
Method Swizzling可以在运行时通过修改类的方法列表中selector对应的函数或者设置交换方法实现,来动态修改方法。可以重写某个方法而不用继承,同时还可以调用原先的实现。通常应用于在category中添加一个方法。
为保证改变方法引起冲突,确保方法混用只能一次性:
比如,在+load方法或者dispatch_once中执行。
ISA Swizzling;
ISA Swizzling可以动态修改对象的isa指针,改变对象的类,类似于创建子类实现相同的功能。KVO即是同过ISA Swizzling实现的。

复制代码
  • 其他概念:category、super
category:
typedef struct objc_category *Category;

struct objc_category {
    char *category_name                          OBJC2_UNAVAILABLE; // 分类名
    char *class_name                             OBJC2_UNAVAILABLE; // 分类所属的类名
    struct objc_method_list *instance_methods    OBJC2_UNAVAILABLE; // 实例方法列表
    struct objc_method_list *class_methods       OBJC2_UNAVAILABLE; // 类方法列表
    struct objc_protocol_list *protocols         OBJC2_UNAVAILABLE; // 分类所实现的协议列表
}  

// objc-runtime-new.h中定义:
struct category_t {
    const char *name;                        // name 是指 class_name 而不是 category_name
    classref_t cls;                          // cls是要扩展的类对象,编译期间是不会定义的,而是在Runtime阶段通过name对应到对应的类对象
    struct method_list_t *instanceMethods;       
    struct method_list_t *classMethods;
    struct protocol_list_t *protocols;
    struct property_list_t *instanceProperties;    // instanceProperties表示Category里所有的properties,(这就是我们可以通过objc_setAssociatedObject和objc_getAssociatedObject增加实例变量的原因,)不过这个和一般的实例变量是不一样的

};
category是定义方法的结构体,instance_methods列表是objc_class中方法列表的一个子集,class_methods列表是元类方法列表的一个子集。由其结构成员可知,category为什么不能添加成员变量(可添加属性,只有set/get方法)。
给category添加方法后,category_list会生成method list。这个方法列表是倒序添加的,也就是说,新生成的category的方法会先于旧的category的方法插入。(category的方法会优先于类方法执行)。
super:
super并不是隐藏参数,它实际上只是一个”编译器标示符”,它负责告诉编译器,当调用方法时,跳过当前类去调用父类的方法,而不是本类中的方法。self是类的一个隐藏参数,每个方法的实现的第一个参数即为self。实际上给super发消息时,super还是与self指向的是相同的消息接收者。
struct objc_super {
   __unsafe_unretained id receiver;
   __unsafe_unretained Class super_class;
};
原理:使用super来接收消息时,编译器会生成一个objc_super结构体。发送消息时,不是调用objc_msgSend函数,而是调用objc_msgSendSuper函数:(这就是为什么咱们在super init 返回的是本类)
id objc_msgSendSuper ( struct objc_super *super, SEL op, ... );
该函数实际的操作是:从objc_super结构体指向的superClass的方法列表开始查找selector,找到后以objc->receiver去调用这个selector。
复制代码
  • RunTime对一些方法的实现
- (Class)class ;

- (Class)class {
    return object_getClass(self);
}

+ (Class)class;

+ (Class)class {
    return self;
}

- (BOOL)isKindOf:aClass;// (for循环遍历父类,每次判断返回的结果可能不同)

- (BOOL)isKindOf:aClass
{
    Class cls;
    for (cls = isa; cls; cls = cls->superclass) 
        if (cls == (Class)aClass)
            return YES;
    return NO;
}
- (BOOL)isMemberOf:aClass;

- (BOOL)isMemberOf:aClass
{
    return isa == (Class)aClass;
}
复制代码

猜你喜欢

转载自juejin.im/post/5c983128e51d4540293448d6