JSPatch源码分析

前言

JSPatch是一个可以动态更新iOS APP的开源库。通过JSPatch,可以使用JS书写原生代码,动态更新APP,替换项目原生代码修复Bug。

JSPatch充分应用了Objective-C的runtime,来实现自己的功能。

这篇文章将结合Demo,来分析JSPatch的主要的实现过程。包括:

  1. 使用JS新建类
  2. 使用JS替换原有方法
  3. 使用JS添加全新方法
  4. OC调用JS定义的方法
  5. JS调用OC定义的方法

Demo

使用官方提供的Demo。先来看看Demo的关键代码:

JPViewController

@implementation JPViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    UIButton *btn = [[UIButton alloc] initWithFrame:CGRectMake(0, 100, [UIScreen mainScreen].bounds.size.width, 50)];
    [btn setTitle:@"Push JPTableViewController" forState:UIControlStateNormal];
    [btn addTarget:self action:@selector(handleBtn:) forControlEvents:UIControlEventTouchUpInside];
    [btn setBackgroundColor:[UIColor grayColor]];
    [self.view addSubview:btn];
}

- (void)handleBtn:(id)sender
{
}

@end

这个JPViewController创建了一个button,并给该button赋予了一个点击事件。但是该点击事件- (void)handleBtn:(id)sender;没做任何事情

demo.js

/* 给JPViewController重新定义 handleBtn: 方法 */
defineClass('JPViewController', {
  handleBtn: function(sender) {
    var tableViewCtrl = JPTableViewController.alloc().init()
    self.navigationController().pushViewController_animated(tableViewCtrl, YES)
  }
})

// 定义了一个新的类型 JPTableViewController
defineClass('JPTableViewController : UITableViewController <UIAlertViewDelegate>', ['data'], {
  dataSource: function() {
    var data = self.data();
    if (data) return data;
    var data = [];
    for (var i = 0; i < 20; i ++) {
      data.push("cell from js " + i);
    }
    self.setData(data)
    return data;
  },
  numberOfSectionsInTableView: function(tableView) {
    return 1;
  },
  tableView_numberOfRowsInSection: function(tableView, section) {
    return self.dataSource().length;
  },
  tableView_cellForRowAtIndexPath: function(tableView, indexPath) {
    var cell = tableView.dequeueReusableCellWithIdentifier("cell") 
    if (!cell) {
      cell = require('UITableViewCell').alloc().initWithStyle_reuseIdentifier(0, "cell")
    }
    cell.textLabel().setText(self.dataSource()[indexPath.row()])
    return cell
  },
  tableView_heightForRowAtIndexPath: function(tableView, indexPath) {
    return 60
  },
  tableView_didSelectRowAtIndexPath: function(tableView, indexPath) {
     var alertView = require('UIAlertView').alloc().initWithTitle_message_delegate_cancelButtonTitle_otherButtonTitles("Alert",self.dataSource()[indexPath.row()], self, "OK",  null);
     alertView.show()
  },
  alertView_willDismissWithButtonIndex: function(alertView, idx) {
    console.log('click btn ' + alertView.buttonTitleAtIndex(idx).toJS())
  }
})

该JS是Demo的唯一一个JS。它做了两件事:

  1. 重新定义JPViewControllerhandleBtn:方法;现在点击该button的话,会新建一个tableView
  2. 定义一个新的类型 JPTableViewController;这个新的类型负责创建相关的tableView

JPEngine.m 和 JSPatch.js

这是JSPatch库的最核心的两个文件。

该Demo创建了一个button,点击button之后,会调用JS里面的方法:该JS方法会创建一个tableView,并给每个cell设置一个click事件

源码分析

使用JS创建Objective-C的类

demo.js中使用了 defineClass 方法来创建新的Objective-C类。这个方法定义在JSPatch.js中。现在来看看它的实现:(只显示了核心代码)

global.defineClass = function(declaration, properties, instMethods, clsMethods) {
    var newInstMethods = {}, newClsMethods = {}

    // 获得类名,忽略冒号之后的superClass以及protocol //
    var realClsName = declaration.split(':')[0].trim()

    // JS <=> OC 的一些格式化 暂时不管,这一步之后将会产生
    _formatDefineMethods(instMethods, newInstMethods, realClsName)
    _formatDefineMethods(clsMethods, newClsMethods, realClsName)

    // 核心代码: 在_OC_defineClass中调用OC的方法,创建新的Objective-C类,并添加或者替代方法
    var ret = _OC_defineClass(declaration, newInstMethods, newClsMethods)

    /* 此处省略800字 */
    return require(className)
}

使用iOS7引入的JavaScriptCore框架 ,可以实现在JS中调用OC的方法(具体参考JavaScriptCore)。defineClass方法中,调用_OC_defineClass方法来创建新的类,以及替换原有方法或者生成新的方法。

来看看定义在JPEngine.m文件中的这个方法:

+ (void)startEngine {
  context[@"_OC_defineClass"] = ^(NSString *classDeclaration, JSValue *instanceMethods, JSValue *classMethods) {
    return defineClass(classDeclaration, instanceMethods, classMethods);
  };
}

这里的context 变量是JSContext变量,也就是JS的执行环境;在该执行环境里面定义了一个_OC_defineClass Block。那么JS就可以调用该_OC_defineClass方法,并执行Block,也就是执行OC代码。

来看看Block里面的defineClass这一个C方法

static NSDictionary *defineClass(NSString *classDeclaration, JSValue *instanceMethods, JSValue *classMethods)
{
      // 这里的classDeclaration类似这种格式 : ClassName : NSObject <protocolName>
    NSScanner *scanner = [NSScanner scannerWithString:classDeclaration];

    NSString *className;
    NSString *superClassName;
    NSString *protocolNames;

      /* 此处省略800字 解析生成className  superClassName 和 protocolName*/

    Class cls = NSClassFromString(className);

    // 如果OC中不存在这个类 则新建class
    if (!cls) {
        Class superCls = NSClassFromString(superClassName);
        cls = objc_allocateClassPair(superCls, className.UTF8String, 0);
        objc_registerClassPair(cls);
    }

    // 给该类添加协议
    if (protocols.count > 0) {
        for (NSString* protocolName in protocols) {
            Protocol *protocol = objc_getProtocol([trim(protocolName) cStringUsingEncoding:NSUTF8StringEncoding]);
            class_addProtocol (cls, protocol);
        }
    }

      /* 此处省略800字 */
}

总结: 可以看到,在JS中创建的类名会被传入OC。OC会判断该类名对应的类是否存在;如果不存在,就会使用runtime方法来动态创建这个类

使用JS替换原有方法

再来看一下demo.js

defineClass('JPViewController', {
  handleBtn: function(sender) {
    var tableViewCtrl = JPTableViewController.alloc().init()
    self.navigationController().pushViewController_animated(tableViewCtrl, YES)
  }
})

这里使用JS定义了JPViewController类的handleBtn:方法;执行完defineClass这个JS方法之后,这里的实现会替换原来的handleBtn:的实现。

先来说一下方法替换的具体流程:

  1. 创建一个ORIGhandleBtn: selector,指向handleBtn:的IMP
  2. 创建一个ORIGforwardInvocation: selector,指向JPViewControllerforwardInvocation: 的IMP
  3. handleBtn: selector 指向 _objc_msgForward ;也就是说执行handleBtn:这个方法的时候,不会去查找它的实现,会直接进行消息转发(关于消息转发,可以看我的这篇文章 Objective-C runtime - 消息)
  4. 在OC的全局变量 _JSOverideMethods 中保存这个方法的实现,也就是保存JS中定义的这个方法
  5. forwardInvocation: 这个selector指向 JPForwardInvocation的实现;JPForwardInvocation又是JSPatch里面的一个核心方法。在这个方法里面会寻找JS定义的handleBtn,并调用该方法

来看看代码

static NSDictionary *defineClass(NSString *classDeclaration, JSValue *instanceMethods, JSValue *classMethods)
{
    /* 此处省略800字 */

      // i == 0 添加实例方法; i == 1 添加类方法
    for (int i = 0; i < 2; i ++) {
        BOOL isInstance = i == 0;
        JSValue *jsMethods = isInstance ? instanceMethods: classMethods;

        Class currCls = isInstance ? cls: objc_getMetaClass(className.UTF8String);

          // 获取方法列表,for循环逐个给类添加方法
        NSDictionary *methodDict = [jsMethods toDictionary];
        for (NSString *jsMethodName in methodDict.allKeys) {
            JSValue *jsMethodArr = [jsMethods valueForProperty:jsMethodName];
            int numberOfArg = [jsMethodArr[0] toInt32];
            NSString *selectorName = convertJPSelectorString(jsMethodName);
            if ([selectorName componentsSeparatedByString:@":"].count - 1 < numberOfArg) {
                selectorName = [selectorName stringByAppendingString:@":"];
            }

            JSValue *jsMethod = jsMethodArr[1];
            if (class_respondsToSelector(currCls, NSSelectorFromString(selectorName))) {
                // 已经有对应的方法了,替换方法
                overrideMethod(currCls, selectorName, jsMethod, !isInstance, NULL);
            } else {
                // 新增方法
               /* 先省略800字 后文再说 */
            }
        }
    }

      // 添加统一的setter/getter函数
    class_addMethod(cls, @selector(getProp:), (IMP)getPropIMP, "@@:@");
    class_addMethod(cls, @selector(setProp:forKey:), (IMP)setPropIMP, "v@:@@");

    return @{@"cls": className, @"superCls": superClassName};
}

看来替换方法的核心还在overrideMethod 里面,来看看这个方法

static void overrideMethod(Class cls, NSString *selectorName, JSValue *function, BOOL isClassMethod, const char *typeDescription)
{
      // 获取方法名对应的selector
    SEL selector = NSSelectorFromString(selectorName);

    // 获取method的typeDescription:也就是获取方法的typeEncoding
    if (!typeDescription) {
        Method method = class_getInstanceMethod(cls, selector);
        typeDescription = (char *)method_getTypeEncoding(method);
    }

      // 获取方法的最初的实现
    IMP originalImp = class_respondsToSelector(cls, selector) ? class_getMethodImplementation(cls, selector) : NULL;

      // 获取_objc_msgForward的实现
    IMP msgForwardIMP = _objc_msgForward;

    // 保留forwardInvocation:的原有实现,并将它指向JPForwardInvocation这个新的实现上面
    if (class_getMethodImplementation(cls, @selector(forwardInvocation:)) != (IMP)JPForwardInvocation) {
        IMP originalForwardImp = class_replaceMethod(cls, @selector(forwardInvocation:), (IMP)JPForwardInvocation, "v@:@");
        if (originalForwardImp) {
            class_addMethod(cls, @selector(ORIGforwardInvocation:), originalForwardImp, "v@:@");
        }
    }

    // 保留将要替换的方法:使用ORIGselectorName这一个selector指向原有的实现
    if (class_respondsToSelector(cls, selector)) {
        NSString *originalSelectorName = [NSString stringWithFormat:@"ORIG%@", selectorName];
        SEL originalSelector = NSSelectorFromString(originalSelectorName);
        if(!class_respondsToSelector(cls, originalSelector)) {
            class_addMethod(cls, originalSelector, originalImp, typeDescription);
        }
    }

      // 将JS定义的实现保存到OC的全局变量中
    NSString *JPSelectorName = [NSString stringWithFormat:@"_JP%@", selectorName];
    _initJPOverideMethods(cls);
    _JSOverideMethods[cls][JPSelectorName] = function;

       // 将要替换的方法,指向_objc_msgForward这一函数,直接进行消息转发
    class_replaceMethod(cls, selector, msgForwardIMP, typeDescription);
}

使用JS创建新的方法

还是先来看看demo.js

defineClass('JPTableViewController : UITableViewController <UIAlertViewDelegate>', ['data'], {
  dataSource: function() {
    var data = self.data();
    if (data) return data;
    var data = [];
    for (var i = 0; i < 20; i ++) {
      data.push("cell from js " + i);
    }
    self.setData(data)
    return data;
  },
  numberOfSectionsInTableView: function(tableView) {
    return 1;
  },
  tableView_numberOfRowsInSection: function(tableView, section) {
    return self.dataSource().length;
  },
  tableView_cellForRowAtIndexPath: function(tableView, indexPath) {
    var cell = tableView.dequeueReusableCellWithIdentifier("cell") 
    if (!cell) {
      cell = require('UITableViewCell').alloc().initWithStyle_reuseIdentifier(0, "cell")
    }
    cell.textLabel().setText(self.dataSource()[indexPath.row()])
    return cell
  },
  tableView_heightForRowAtIndexPath: function(tableView, indexPath) {
    return 60
  },
  tableView_didSelectRowAtIndexPath: function(tableView, indexPath) {
     var alertView = require('UIAlertView').alloc().initWithTitle_message_delegate_cancelButtonTitle_otherButtonTitles("Alert",self.dataSource()[indexPath.row()], self, "OK",  null);
     alertView.show()
  },
  alertView_willDismissWithButtonIndex: function(alertView, idx) {
    console.log('click btn ' + alertView.buttonTitleAtIndex(idx).toJS())
  }
})

在这个JS方法里面,不仅定义了一个新的OC类JPTableViewController,并给他添加了多个方法。来看看具体实现:

static NSDictionary *defineClass(NSString *classDeclaration, JSValue *instanceMethods, JSValue *classMethods)
{
    /* 上文提过,省略 */

      // i == 0 添加实例方法; i == 1 添加类方法
    for (int i = 0; i < 2; i ++) {
        BOOL isInstance = i == 0;
        JSValue *jsMethods = isInstance ? instanceMethods: classMethods;

        Class currCls = isInstance ? cls: objc_getMetaClass(className.UTF8String);
        NSDictionary *methodDict = [jsMethods toDictionary];
        for (NSString *jsMethodName in methodDict.allKeys) {
            JSValue *jsMethodArr = [jsMethods valueForProperty:jsMethodName];
            int numberOfArg = [jsMethodArr[0] toInt32];
            NSString *selectorName = convertJPSelectorString(jsMethodName);
            if ([selectorName componentsSeparatedByString:@":"].count - 1 < numberOfArg) {
                selectorName = [selectorName stringByAppendingString:@":"];
            }

            JSValue *jsMethod = jsMethodArr[1];
            if (class_respondsToSelector(currCls, NSSelectorFromString(selectorName))) {
                overrideMethod(currCls, selectorName, jsMethod, !isInstance, NULL);
            } else {
                /* 开始新增方法 */
                BOOL overrided = NO;

                // 先添加protocol的方法
                for (NSString *protocolName in protocols) {
                      // 通过protocol获取方法的typeEncoding
                    char *types = methodTypesInProtocol(protocolName, selectorName, isInstance, YES);
                    if (!types) types = methodTypesInProtocol(protocolName, selectorName, isInstance, NO);
                    if (types) {
                          // 还是使用 overrideMethod 方法来创建新的方法。走的是消息转发的那一套
                        overrideMethod(currCls, selectorName, jsMethod, !isInstance, types);
                        free(types);
                        overrided = YES;
                        break;
                    }
                }

                // 不是protocol方法,新增类的方法
                if (!overrided) {
                    if (![[jsMethodName substringToIndex:1] isEqualToString:@"_"]) {
                        NSMutableString *typeDescStr = [@"@@:" mutableCopy];
                        for (int i = 0; i < numberOfArg; i ++) {
                            [typeDescStr appendString:@"@"];
                        }
                        overrideMethod(currCls, selectorName, jsMethod, !isInstance, [typeDescStr cStringUsingEncoding:NSUTF8StringEncoding]);
                    }
                }
            }
        }
    }

      /* 省略无关代码 */

    return @{@"cls": className, @"superCls": superClassName};
}

可以看到:新增方法其实和替换方法是一个原理,走的都是消息转发那一套;但是新增方法没有必要保留原有方法的实现,因为本来就不存在原有的方法

OC调用JS定义的方法

现在JS已经定义好了需要的类,以及需要的方法了。那么何时来调用这些方法,以及如何调用呢?

回顾一下JPViewController.m

#import "JPViewController.h"

@implementation JPViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    UIButton *btn = [[UIButton alloc] initWithFrame:CGRectMake(0, 100, [UIScreen mainScreen].bounds.size.width, 50)];
    [btn setTitle:@"Push JPTableViewController" forState:UIControlStateNormal];
    [btn addTarget:self action:@selector(handleBtn:) forControlEvents:UIControlEventTouchUpInside];
    [btn setBackgroundColor:[UIColor grayColor]];
    [self.view addSubview:btn];
}

- (void)handleBtn:(id)sender
{
}

@end

这个文件很简单:创建一个button,给button添加一个执行方法handleBtn: 。那么当运行APP,并点击这一button的时候;会去调用handleBtn:方法。但是由上面的分析可知:handleBtn:方法已经指向_objc_msgForward,也就是说会对这一方法直接进行转发,调用forwardInvocation:方法。下面来看看这个JPForwardInvocation方法:

static void JPForwardInvocation(__unsafe_unretained id assignSlf, SEL selector, NSInvocation *invocation)
{
      // 根据Invocation,来获取方法的参数信息等等
    BOOL deallocFlag = NO;
    id slf = assignSlf;
    NSMethodSignature *methodSignature = [invocation methodSignature];
    NSInteger numberOfArguments = [methodSignature numberOfArguments];
    NSString *selectorName = NSStringFromSelector(invocation.selector);
    NSString *JPSelectorName = [NSString stringWithFormat:@"_JP%@", selectorName];

      // 从全局JS方法列表中获取当前的JS方法
    JSValue *jsFunc = getJSFunctionInObjectHierachy(slf, JPSelectorName);

    // JS 没有定义该方法 那么就走原始的转发方法
    if (!jsFunc) {
        JPExecuteORIGForwardInvocation(slf, selector, invocation);
        return;
    }

    NSMutableArray *argList = [[NSMutableArray alloc] init];
    /* 省略800字 */

    // 从Invocation中根据method的 type Encoding 来获取参数列表,并将其添置argList数组中
    for (NSUInteger i = 2; i < numberOfArguments; i++) {
        const char *argumentType = [methodSignature getArgumentTypeAtIndex:i];

        // 根据type Encoding来添加相应的参数。r代表const
        switch(argumentType[0] == 'r' ? argumentType[1] : argumentType[0]) {
            /* 此处省略不只800字。。 具体实现看源码 */

          // for example
          case '@':
              // 这个参数是一个OC对象,所以需要往argList中添加对象
              id arg;
              [invocation getArgument:&arg atIndex:i];
              [argList addObject:arg];
              break;
          case ':':
              // 添加selector对应的NSString
        }
    }

    /* 此处省略800字 */

      // 参数列表,将OC对象转化成JS可以使用的对象
    NSArray *params = _formatOCToJSList(argList);

      // 保存返回值的类型
    char returnType[255];
    strcpy(returnType, [methodSignature methodReturnType]);

      // 根据返回类型来调用JS函数
    switch (returnType[0] == 'r' ? returnType[1] : returnType[0]) {
        /* 此处省略不只800字  这一块是该函数的核心代码  下面只说主要过程 */

          // jsval用来存储JS调用的返回值
          JSValue *jsval;

           // JS调用时单线程,需要加锁
        [_JSMethodForwardCallLock lock];

          // !!!  核心代码出现啦  OC调用JS定义的方法  !!!  */
        jsval = [jsFunc callWithArguments:params];
        [_JSMethodForwardCallLock unlock];

          // 检查JS方法的返回值,看是否需要执行回调函数
        while (![jsval isNull] && ![jsval isUndefined] && [jsval hasProperty:@"__isPerformInOC"]) {
            NSArray *args = nil;
            JSValue *cb = jsval[@"cb"];
            if ([jsval hasProperty:@"sel"]) {
                id callRet = callSelector(![jsval[@"clsName"] isUndefined] ? [jsval[@"clsName"] toString] : nil, [jsval[@"sel"] toString], jsval[@"args"], ![jsval[@"obj"] isUndefined] ? jsval[@"obj"] : nil, NO);
                args = @[[_context[@"_formatOCToJS"] callWithArguments:callRet ? @[callRet] : _formatOCToJSList(@[_nilObj])]];
            }
            [_JSMethodForwardCallLock lock];
              //  执行回调函数,该回调函数也是在JS中定义的
            jsval = [cb callWithArguments:args];
            [_JSMethodForwardCallLock unlock];
        }

          // 设置消息转发的调用结果,也就是设置 invocation 的 returnValue
          [invocation setReturnValue:&jsval];
    }

    /* 此处省略800字 和对象释放相关的 */
}

了解了这个消息转发的过程之后,就会知道OC调用JS定义的方法是在JSForwardInvocation中。它回去查找定义在OC里面的存储JS方法的全局变量,找到该JS方法,并执行。

JS调用OC的方法

回顾一下demo.js的代码:

defineClass('JPViewController', {
  handleBtn: function(sender) {
    var tableViewCtrl = JPTableViewController.alloc().init()
    self.navigationController().pushViewController_animated(tableViewCtrl, YES)
  }
})

当OC调用handleBtn:方法时,实际上执行的是这个JS定义的方法。在这个JS方法里面会创建一个JPTableViewController对象,并将该对象显示出来。来看看JS如何调用OC的代码:

__c: function(methodName) {
  /* 省略一系列迷人的JS操作 */

  return function(){
    var args = Array.prototype.slice.call(arguments)
    // 获取调用方法的类名,调用方法的对象,调用的方法的参数,并且执行这个 _methodFunc 方法
    return _methodFunc(slf.__obj, slf.__clsName, methodName, args, slf.__isSuper)
  }
},

在JS里面调用OC方法:如 UIView.alloc().init() 都会被转化成这种格式 UIView.__c('alloc')().__c('init')()。也就是调用上述的__c方法。

__c()方法也就是获取调用该方法的类名,对象;以及该方法的参数。并将这些全部传给_methodFunc()方法。所以说__c()方法有点像OC的objc_megSend(id self, selector, ...),它只是一个转发器。

真正调用OC的JS方法是这个_methodFunc(定义在JSPatch.js中):

var _methodFunc = function(instance, clsName, methodName, args, isSuper, isPerformSelector) {
  // 做一些准备活动:将JS方法名转成相应的OC方法名(驼峰命名)
  var selectorName = methodName
  if (!isPerformSelector) {
    methodName = methodName.replace(/__/g, "-")
    selectorName = methodName.replace(/_/g, ":").replace(/-/g, "_")
    var marchArr = selectorName.match(/:/g)
    var numOfArgs = marchArr ? marchArr.length : 0
    if (args.length > numOfArgs) {
      selectorName += ":"
    }
  }

  // 使用_OC_callI / _OC_callC 来调用OC的方法。传递给这两方法的参数是对象,参数,selector等等
  var ret = instance ? _OC_callI(instance, selectorName, args, isSuper):
  _OC_callC(clsName, selectorName, args)
  return _formatOCToJS(ret)
}

我们以_OC_callI为例 (定义在JPEngine.m中):

context[@"_OC_callI"] = ^id(JSValue *obj, NSString *selectorName, JSValue *arguments, BOOL isSuper) {
    return callSelector(nil, selectorName, arguments, obj, isSuper);
};

在OC中,这个方法接收obj,selectorName,arguments等参数;实际调用的callSelector方法:

static id callSelector(NSString *className, NSString *selectorName, JSValue *arguments, JSValue *instance, BOOL isSuper)
{
      /* 省略800字 做一些前期准备:获取类,selector,格式化参数列表等等 */

       /* 再省略800字 设置Invocation的target 这个调用还是以消息转发的形式来的 */
      [invocation setTarget:instance];

      // 和OC调用JS一样,set Invocation的调用参数
      // 注意:在OC调用JS中,这里是 get Invocation的参数
    for (NSUInteger i = 2; i < numberOfArguments; i++) {
        const char *argumentType = [methodSignature getArgumentTypeAtIndex:i];
        id valObj = argumentsObj[i-2];
        switch (argumentType[0] == 'r' ? argumentType[1] : argumentType[0]) {
              // 代码省略了
             [invocation setArgument:&value atIndex:i];       
        }
    }

      // 执行OC代码,也就是设置好这一个invocation之后,invoke它
      [invocation invoke];

    char returnType[255];
    strcpy(returnType, [methodSignature methodReturnType]);

      // 使用returnValue来存储Invocation返回值
    id returnValue;
    [invocation getReturnValue:&result];

    // 根据返回值类型来创建returnValue,并返回。 for example
      returnValue = (__bridge id)result;
      return returnValue;
}

这样就实现了OC代码的调用。

JSPatch核心方法

JPEngine.m

  1. defineClass
  2. overrideMethod
  3. JPForwardInvocation
  4. callSelector

JSPatch.js

  1. __c()
  2. _methodFunc()

总结

JSPatch充分应用了 runtimeJavaScriptCore 两大技术,实现了OC和JS的通信。

对于OC调用JS,和JS调用OC 都是使用的消息转发的机制;这两种调用是一个相反的过程。

参考

当前网速较慢或者你使用的浏览器不支持博客特定功能,请尝试刷新或换用Chrome、Firefox等现代浏览器