DragonFly的博客

我曾七次鄙视自己的灵魂


  • 首页

  • 分类

  • 标签

  • 归档

iOS混淆--OLLVM在iOS中的实践

发表于 2017-09-07 | 分类于 LLVM |

OLLVM简介

OLLVM(Obfuscator-LLVM)是瑞士西北应用科技大学安全实验室于2010年6月份发起的一个项目,该项目旨在提供一套开源的针对LLVM的代码混淆工具,以增加对逆向工程的难度。后期转向商业项目strong.protect。目前,OLLVM已经支持LLVM-4.0版本。

LLVM是一个优秀的编译器框架,它也采用经典的三段式设计。前端可以使用不同的编译工具对代码文件做词法分析以形成抽象语法树AST,然后将分析好的代码转换成LLVM的中间表示IR(intermediate representation);中间部分的优化器只对中间表示IR操作,通过一系列的Pass对IR做优化;后端负责将优化好的IR解释成对应平台的机器码。LLVM的优点在于,中间表示IR代码编写良好,而且不同的前端语言最终都转换成同一种的IR。

LLVM IR 是LLVM的中间表示,优化器就是对IR进行操作的,具体的优化操作由一些列的Pass来完成,当前端生成初级IR后,Pass会依次对IR进行处理,最终生成后端可用的IR。下图可以说明这个过程:

OLLVM的混淆操作就是在中间表示IR层,通过编写Pass来混淆IR,然后后端依据IR来生成的目标代码也就被混淆了。得益于LLVM的设计,OLLVM适用LLVM支持的所有语言(C,C++,Objective-C,Ada,Fortran)和目标平台(x86,x86-64,PowerPC,PowerPC-64, ARM, Thumb, SPARC, Alpha, CellSPU, MIPS, MSP430, SystemZ, 和 XCore)

阅读全文 »

iOS Core NFC指南

发表于 2017-08-22 | 分类于 iOS |

大家可能听过NFC这项功能,或者有可能你每天都在使用这个功能,比如当你在进出地铁时闸机扫描地铁卡就用到了NFC技术。
简单来说NFC就是可以让智能手机的NFC模块,可以像读卡器一般,读取电子标签的相关信息,实现NFC手机之间的数据交互或是读取其他IC卡内的数据。NFC(机场通讯),其实由非接触式射频识别(RFID)演变而来,是一种短距高频的无线电技术,在13.56MHz频率运行于20厘米距离内。它的传输速度有106 Kbit/秒、212 Kbit/秒或者424 Kbit/秒三种。目前NFC已通过成为ISO/IEC IS 18092国际标准、ECMA-340标准与ETSI TS 102 190标准。NFC可以采用主动和被动两种读取模式。

NFC使用场景

Core NFC介绍

或者你可能还吐槽过Apple怎么还不支持NFC呢,其实iPhone6已经有NFC硬件了,已支持Apple Pay支付系统,只是接口没开放,终于在今年的WWDC,苹果在iOS11系统上对开发者开放了NFC接口框架Core NFC,虽然目前权限只有只读模式。
Apple的Core NFC可以用于检测NFC(近场通讯)标签和读取包含NDEF(NFC Data Exchange Format)数据1到5类型的标签信息,只是该功能只支持iPhone 7和iPhone 7P及以上的机型。目前Core NFC其实同时有NFC和RFID的API存在,但是RFID可能没有很高的安全性,所以苹果没有推广使用(或者还在开发中)。

NFC Data Exchange Format : NFC数据交换格式,NFC组织约定的NFC tag中的数据格式。NDEF是轻量级的紧凑的二进制格式,可带有URL、vCard和NFC定义的各种数据类型。NDEF的由各种数据记录组成,而各个记录由报头(Header)和有效载荷(Payload)组成,其中NDEF记录的数据类型和大小由记录载荷的报头注明,这里的报头包含3部分,分别为Length、Type和Identifier。

NFC标签图例

阅读全文 »

iOS Bonjour的使用-本地通信/智能交互

发表于 2017-07-21 | 分类于 iOS |

之前一直考虑在local现场怎么与别的用户通信,后来陆续了解了苹果的Bonjour。现在简单写一篇Bonjour的入门介绍。

Bonjour介绍

bonjour其实来自法语,是你好的意思。而Bonjour服务是苹果公司发布的一个基于ZEROCONF工作组(IETF下属小组)的工作,用于实现零配置网络联网的解决方案。Bonjour是基于IP层协议的,简单来说,就是一套解决方案,能够不需要复杂的配置,即可互相发现彼此的解决方案。可以用它来轻松探测并连接到相同网络中的其他设备,并与别的智能硬件进行交互或者其他操作。典型的Bonjour应用有Remote、AirPrint等。

Bonjour-overview

为了实现简单配置网络,Bonjour做了以下三点:

  1. 寻址(分配IP地址给主机)
    一个在网络中的设备需要有一个自己的IP。有了IP地址,我们才能基于IP协议进行通信。对于IPV6标准,IP协议已经包括了自动寻找IP地址的功能。但是目前仍然普遍使用的IPV4不包含本地链路寻址功能。而Bonjour会在本地网络选择一个随机的IP地址进行测试,如果已经被占用,则继续挑选另外一个地址,直到选到可用的IP地址。

  2. 命名(使用名字而不是IP地址来代表主机)
    Bonjour还实现了命名和解析功能,保证了我们服务的名字在本地网络是唯一的,并且把别人对我们名字的查询指向正确的IP地址和端口,而不是以IP地址这样不易读的方式来作为服务的标志。
    而且Bonjour在系统级别上添加了一个mDNSResponder服务来处理请求和发送回复,从系统级层面上处理,我们就无需在应用内自己监听和返回IP地址,只需到系统内注册服务即可。减少了我们应用的工作量和提高了稳定性。

  3. 服务搜索(自动在网络搜索服务)
    Bonjour可以只需指定所需服务的类型,即可收到本地网络上可用的设备列表。设备在本地网络发出请求,说我需要”XXX”类型的服务,例如:我要打印机服务。所有打印机服务的设备回应自己的名字。

阅读全文 »

ARKit入坑指南--技术实现分析

发表于 2017-06-28 | 分类于 iOS |

ARKit简介

iOS11发布了ARKit之后,iPhone瞬间变成了最大的AR平台。一直以来对这块都比较感兴趣,最近简单做了些研究,下面介绍下ARKit基础的一些概念,算是当作入坑路径吧。
ARKit是一种为iOS构建增强现实(AR,augmented reality)App的框架,意在实现将虚拟内容精确且真实地浸入真实世界场景上。ARKit的核心是为一些基本的关键功能提供支持,包括运动跟踪、水平面检测,以及尺度和环境光预测。
有些人认为ARKit是一种SLAM实现,也有人认为是VIO。下面分别简单介绍下对ARKit的技术实现方式的不同理解。

1.Slam的概念

SLAM,全称叫做Simultaneous Localization and Mapping,中文叫做同时定位与建图。就是通过传感器获取环境的有限信息,比如视觉信息、深度信息、自身的加速度和角速度等来确定自己的相对或者绝对位置,并完成对于地图的构建。简单点可以说是一个传感器在不停的运动,还在实时的扫描着周围的地形。
与其说SLAM是一种技术,不如说是一种概念,SLAM可通过多种方法实现。

要实现SLAM,最基础就是传感器,目前传感器分为激光和VSLAM两大类。激光雷达由于精度高、速度快,能方便地实现SLAM功能而被研究得最多,主要用于机器人及无人车(机)领域,但缺点就是价格昂贵。
而VSLAM则主要用摄像头来实现,摄像头品种繁多,主要分为单目、双目(或多目)、RGBD。实现难度正序:单目视觉>双目视觉>RGBD。 单目视觉因为基于一个摄像头,在深度感知上存在问题,无法得到机器人的运动轨迹及地图的真实大小,但这也使得单目SLAM不受环境大小的影响,既可用于室内,又可用于室外。

目前,SLAM技术的实现途径主要包括VSLAM、Wifi-SLAM与Lidar SLAM。

阅读全文 »

iOS代码加固

发表于 2017-04-01 | 分类于 iOS |

众所周知的是大部分iOS代码一般不会做加密加固,因为iOS APP一般是通过AppStore发布的,而且苹果的系统难以攻破,所以在iOS里做代码加固一般是一件出力不讨好的事情。万事皆有例外,不管iOS、adr还是js,加密的目的是为了代码的安全性,虽然现在开源畅行,但是不管个人开发者还是大厂皆有保护代码安全的需求,所以iOS代码加固有了生存的土壤。下面简单介绍下iOS代码加密的几种方式。

iOS代码加密的几种方式

1.字符串加密

字符串会暴露APP的很多关键信息,攻击者可以根据从界面获取的字符串,快速找到相关逻辑的处理函数,从而进行分析破解。加密字符串可以增加攻击者阅读代码的难度以及根据字符串静态搜索的难度。
一般的处理方式是对需要加密的字符串加密,并保存加密后的数据,再在使用字符串的地方插入解密算法。简单的加密算法可以把NSString转为byte或者NSData的方式,还可以把字符串放到后端来返回,尽量少的暴露页面信息。下面举个简单例子,把NSString转为16进制的字符串:

1
2
3
+ (NSString *)globalString {    
return @"5f48494748203d20323b202020202020202020202020202020202020202020202020202020202020676c6f62616c2e44495350415443485f51554555455f5052494f524954595f44454641554c54203d20303b2020202020202020202020202020202020202020202020202";
}
阅读全文 »

React Native源码分析

发表于 2017-03-02 |

由于Apple严格的审核流程,很多人一直以来都寻找iOS端的动态热发方案,从Hybrid到Lua、JSPatch再到现在非常火的ReactNative,但是动态热发的核心流程都是从服务端获取配置-->客户端解析-->运行。目前很多业务的实现例如可配置方案实现,其基本流程都符合以上流程,但是各种方案都有其优劣势,比如可配制方案导致业务逻辑复杂,Hybrid体验不太好,JSPatch被禁用等。而ReactNative则没有以上这些问题,除了学习成本,你可以很方便快捷的使用JS来实现客户端动态热发的功能。

我们团队内部使用ReactNative开发已经一段时间了,开始的时候大概了解过其基础运行原理,但没有深入研究,在工作过程中抽空阅读了源码,从代码层级了解了ReactNative的运行流程,下面分享下其运行的完整流程(依赖于RN:0.41版本)。

ReactNative原理概述

React Native项目依赖React,React是一个支持JSX语法的用于构建用户界面的JS库,可以认为其相当于MVC框架中的View(视图)。React独创了虚拟Dom机制,使他有可能和原生语言互相结合。
而ReactNative让React拥有了于原生APP交互的功能,有了它就能让JS和移动端技术(iOS或Android)互相调用,这样就可以通过JS开发原生APP的功能,同时还可以支持热发上线。React Native也秉承了React的理念,Learn Once,Write Anywhere。

当然所有的动态热发底层都离不开原生语言(iOS/Android)本身,在iOS中,ReactNative之所以能够运行起来,最主要依赖的是Objective-C语言与JavaScript的交互。Apple从iOS7开始,提供了JavaScriptCore的框架,提供了JS和OC沟通的可能,不了解JavaScriptCore的同学可以去了解下。例如iOS可以通过以下代码直接运行JS代码:

1
2
3
4
//JSContext为JS代码的运行环境  
JSContext *context = [[JSContext alloc] init];
JSValue *result = [context evaluateScript:@"2+8"];
int sum = [result toInt32];

在OC端,可以很容易获取JS上下文,但是JS不知道OC有哪些方法可以调用。ReactNative解决该问题的方法是在OC和JS端保存了一份配置表,里面标记了所有的OC暴露的JS的模块和方法,这样无论那一方调用另一方,实际传递的数据只有ModuleId(模块标志ID)、MethodId(方法标志ID)和Arguments(参数)三个元素,当OC接收到三个参数之后,就可以通过runtime唯一确定要调用的函数并调用。

另外在RN中JS调用OC代码时,会注册要回调的block并且把BlockID作为参数发送给OC,OC收到参数是会创建block,用用完OC后就会执行这个刚刚创建的block。并会向block中传入参数和BlockId,然后在block内部调用JS方法,随后JS查找当时注册的block并执行。

源码分析

ReactNative的核心代码包括:

OC代码:在React项目的Base文件夹下,包括RCTRootView,RCTBridge,RCTBatchedBridge,RCTJSCExecutor(0.44之后RCTBatchedBridge,改为RCTCxxBridge)
JS代码:NodeModule中react-native/Libraries下的BatchedBridge和ReactNative文件夹,包括BatchedBridge,MessageQueue,NativeModules,UIManager等文件

以上代码流程可以分为初始化阶段和方法调用阶段。

初始化ReactNative环境

要使用ReactNative创建View,首先需要在OC的入口文件需要用如下代码来进行初始化操作:

1
2
3
4
RCTRootView *rootView = [[RCTRootView alloc] initWithBundleURL:jsCodeLocation
moduleName:@"AwesomeProject"
initialProperties:nil
launchOptions:launchOptions];

用户看到的内容都源于该RootView,所有的初始化工作都在其内完成。进入该方法内部,会发现ReactNative实际上先创建了一个Bridge对象,它是OC与JS交互的桥梁,而整个初始化最终的目的就是创建该对象,该对象其实是一个外壳。初始化方法的核心是Birdge对象的setUp方法,该方法主要的任务则是创建BatchedBridge(0.44版本之后用的是CxxBridge)。BatchedBridge的作用是批量读取JS对OC的方法调用,其内部持有JSCExecutor对象,用来执行JS代码。
创建BatchedBridge的关键是start方法,其流程可以分为五个步骤:

  • (1)读取JS源码
  • (2)初始化模块信息
  • (3)初始化JS代码执行器,即RCTJSCExecutor对象
  • (4)生成模块列表并写入JS端
  • (5)执行JS源码
读取JavaScript源码

第一步首页把JS代码加载进内存中,这一步中,JSX代码已经被转化成原生的JS代码。这一部分很简单,不用多说。

1
2
3
4
5
6
7
8
[self loadSource:^(NSError *error, RCTSource *source) {
sourceCode = source.data;
}];
// loadSource内部实际调用方法为:
NSData *data = [self attemptSynchronousLoadOfBundleAtURL:scriptURL
runtimeBCVersion:JSNoBytecodeFileFormatVersion
sourceLength:&sourceLength
error:&error];

初始化模块信息

初始化模块在initModulesWithDispatchGroup中实现,该方法会找到所有需要暴露给JS的类。每个需要暴露的类都会标记一个宏RCT_EXPORT_MODULE,宏的具体实现为:

1
2
3
4
5
6
7
8
9
10
11
12
13
#define RCT_EXPORT_MODULE(js_name) \
RCT_EXTERN void RCTRegisterModule(Class); \
+ (NSString *)moduleName { return @#js_name; } \
+ (void)load { RCTRegisterModule(self); }
// RCTRegisterModule具体实现:
void RCTRegisterModule(Class moduleClass){
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
RCTModuleClasses = [NSMutableArray new];
});
// Register module
[RCTModuleClasses addObject:moduleClass];
}

标记了宏的类会在load时候调用RCTRegisterModule注册自己,这样RN就可以通过RCTModuleClasses拿到所有暴露给JS的类。然后循环遍历该数组,生成RCTModuleData对象。

1
2
3
4
5
6
7
8
for (Class moduleClass in RCTGetModuleClasses()) {
NSString *moduleName = RCTBridgeModuleNameForClass(moduleClass);
RCTModuleData *moduleData = [[RCTModuleData alloc] initWithModuleClass:moduleClass
bridge:self];
moduleDataByName[moduleName] = moduleData;
[moduleClassesByID addObject:moduleClass];
[moduleDataByID addObject:moduleData];
}

RCTModuleData就是模块配置表的主要内容,该对象保存了每个Module的名字、常量已经所有暴露给JS的方法数组。所有暴露的JS方法需要用RCT_EXPORT_METHOD宏来标记,该宏会为函数名加上__rct_export__的前缀,后续再通过runtime获取类的函数列表,找出其中带有指定前缀的方法放入函数数组中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
- (NSArray<id<RCTBridgeMethod>> *)methods{
unsigned int methodCount;
Method *methods = class_copyMethodList(object_getClass(_moduleClass), &methodCount);
for (unsigned int i = 0; i < methodCount; i++) {
Method method = methods[i];
SEL selector = method_getName(method);
if ([NSStringFromSelector(selector) hasPrefix:@"__rct_export__"]) {
IMP imp = method_getImplementation(method);
NSArray<NSString *> *entries = ((NSArray<NSString *> *(*)(id, SEL))imp)(_moduleClass, selector);
id<RCTBridgeMethod> moduleMethod = [[RCTModuleMethod alloc] initWithMethodSignature:entries[1]
JSMethodName:entries[0]
moduleClass:_moduleClass];
[_methods addObject:moduleMethod];
}
}
return _methods;
}

以上流程使得Bridge持有一个数组,数组中保存了所有模块的RCTModuleData对象,只要给定ModuleID和MethodId就可以唯一确定要调用的方法。

初始化JS代码执行器

这一步先创建了一个group,用于执行该步及下一步的操作,(setUpExecutor)方法会初始化RCTJSCExecutor对象,该对象被bridge持有,所有的JS与OC的通信都是通过该类来实现的。RCTJSCExecutor初始化时会实例化了一个JS线程,用来执行JS代码的线程。该线程执行的runRunLoopThread方法其实是启动了JS线程的runloop,保证了_javaScriptThread能够一直运行。

1
2
3
4
5
6
7
8
9
static NSThread *newJavaScriptThread(void){
NSThread *javaScriptThread = [[NSThread alloc] initWithTarget:[RCTJSCExecutor class]
selector:@selector(runRunLoopThread)
object:nil];
javaScriptThread.name = RCTJSCThreadName;
......
[javaScriptThread start];
return javaScriptThread;
}

这一步还会进行很多JS上下文的准备,创建全局JSContext并添加很多全局回调block,由JS发起调用。需要重点注意的是nativeRequireModuleConfig和nativeFlushQueueImmediate这两个block,其中nativeRequireModuleConfig在JS注册新模块时调用。

1
2
3
4
context[@"nativeRequireModuleConfig"] = ^NSArray *(NSString *moduleName) {
NSArray *result = [strongSelf->_bridge configForModuleName:moduleName];
return RCTNullIfNil(result);
};

nativeFlushQueueImmediate该block用于在JS中直接调用OC,注意在RN中JS调用OC一般是由OC发起的,JS会把调用信息放到MessageQueue中等待OC来取。当OC调用了JS后,在对应的JS回调中才会实现调用OC的逻辑。

1
2
3
context[@"nativeFlushQueueImmediate"] = ^(NSArray<NSArray *> *calls){
[strongSelf->_bridge handleBuffer:calls batchEnded:NO];
};

但是当消息队列由等待OC处理的逻辑,并且OC超过5ms没有取走的话,JS就会主动调用OC的以上方法,该部分JS的代码在MessageQueue中如下:

1
2
3
4
5
6
7
const now = new Date().getTime();
if (global.nativeFlushQueueImmediate && (now - this._lastFlush >= MIN_TIME_BETWEEN_FLUSHES_MS || this._inCall === 0)){
var queue = this._queue;
this._queue = [[], [], [], this._callID];
this._lastFlush = now;
global.nativeFlushQueueImmediate(queue);
}

该handleBuffer方法是JS调用OC方法的关键,后续调用模块会提及。注意在这个里面使用到的JSCJSXXX的宏的作用,实际上是会转换为调用苹果的JavaScriptCore对应的方法(去掉JSC)。

生成模块列表并写入JS端

这一步就是让JS获取所有模块的名字。首先调用moduleConfig方法获取当前module的config数据:

1
2
3
4
5
6
7
8
9
10
11
- (NSString *)moduleConfig{
NSMutableArray<NSArray *> *config = [NSMutableArray new];
for (RCTModuleData *moduleData in _moduleDataByID) {
if (self.executorClass == [RCTJSCExecutor class]) {
[config addObject:@[moduleData.name]];
} else {
[config addObject:RCTNullIfNil(moduleData.config)];
}
}
return RCTJSONStringify(@{@"remoteModuleConfig": config,}, NULL);
}

然后调用injectJSONConfiguration方法把config注入JS执行环境中,保存为__fbBatchedBridgeConfig,之后在NativeModule.js中会用到该属性。

1
2
3
[_javaScriptExecutor injectJSONText:configJSON
asGlobalObjectNamed:@"__fbBatchedBridgeConfig"
callback:onComplete];

执行JS源码

modules和JS代码准备好后,会在dispatch_group_notify的JS子线程内执行jsBundle代码。执行完毕后会将_displayLink添加到runloop(注意是在jsThread所在的runloop)中,开始运行。运行代码时,第三步中的block会被执行,从而向JS端写入配置信息。这样JS和OC就具备了交互的能力,准备工作到此完成。具体代码如下:

1
2
3
4
5
6
7
[_javaScriptExecutor executeApplicationScript:script sourceURL:url onComplete:^(NSError *scriptLoadError) {

[self->_javaScriptExecutor flushedQueue:^(id json, NSError *error){
[self handleBuffer:json batchEnded:YES];
onComplete(error);
}];
}];

方法执行阶段

上面的工作完成之后,初始化bridge的工作就完成了,jsBundle已经加载完成,OC端的配置表也已经处理好,并且成功已经传递给JS端,JS的上下文配置已经准备好,下面就开始执行JS代码了。React就会开始计算好所有的布局信息,以及Component层级关系等,等待native端完成对应的真正的页面渲染和布局。回到RCTRootView的初始化方法中,注意在[RCTRootView initWithBridge:…]的初始化方法中,注册了几个JS执行情况的通知,我们重点关注JS执行完毕后的通知RCTJavaScriptDidLoadNotification。

1
2
3
4
5
6
- (void)bundleFinishedLoading:(RCTBridge *)bridge{
_contentView = [[RCTRootContentView alloc] initWithFrame:self.bounds bridge:bridge
reactTag:self.reactTag sizeFlexiblity:_sizeFlexibility];
[self runApplication:bridge];
[self insertSubview:_contentView atIndex:0];
}

在创建RCTRootContentView的时候,注意有个参数是reactTag,这个属性很重要,每一个reactTag都应该是唯一的,从1开始每次递增10。RCTRootContentView初始化时,还需要在RCTUIManager中通过reactTag去注册,从而由RCTUIManager来统一管理所有的JS端使用Component对应的每个原生view(_viewRegistry[tag]表),有了这个我们就可以很方便的在其他地方通过reactTag获取到我们的Component所在的rootView.

1
2
3
4
5
- (NSNumber *)allocateRootTag{
NSNumber *rootTag = objc_getAssociatedObject(self, _cmd) ?: @1;
objc_setAssociatedObject(self, _cmd, @(rootTag.integerValue + 10), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
return rootTag;
}

然后才是[RCTRootView runApplication],这里就会调用JS里面AppRegistry对应的方法runApplication了。

1
2
3
4
5
6
7
8
9
10
11
12
- (void)runApplication:(RCTBridge *)bridge{
NSString *moduleName = _moduleName ?: @"";
NSDictionary *appParameters = @{
@"rootTag": _contentView.reactTag,
@"initialProps": _appProperties ?: @{},
};
RCTLogInfo(@"Running application %@ (%@)", moduleName, appParameters);
[bridge enqueueJSCall:@"AppRegistry"
method:@"runApplication"
args:@[moduleName, appParameters]
completion:NULL];
}

执行JS代码的时候,JS会计算好每个View的布局属性等信息,然后通过调用native的系统方法来完成页面渲染布局。这个过程就设置到JS和Native的相互通信了。如前文所述,在RN中,OC和JS的交互都是通过传递ModuleId,MethodId和Arguments进行的。

调用JS代码

在OC中,JS代码一直在一个单独的子线程上面运行,如下

1
2
3
4
5
6
7
8
- (void)executeBlockOnJavaScriptQueue:(dispatch_block_t)block{
if ([NSThread currentThread] != _javaScriptThread) {
[self performSelector:@selector(executeBlockOnJavaScriptQueue:)
onThread:_javaScriptThread withObject:block waitUntilDone:NO];
} else {
block();
}
}

调用JS代码的核心函数如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
- (void)_executeJSCall:(NSString *)method arguments:(NSArray *)arguments unwrapResult:(BOOL)unwrapResult callback:(RCTJavaScriptCallback)onComplete{
__weak RCTJSCExecutor *weakSelf = self;
[self executeBlockOnJavaScriptQueue:^{
RCTJSCExecutor *strongSelf = weakSelf;
JSContext *context = strongSelf->_context.context;
JSGlobalContextRef ctx = context.JSGlobalContextRef;

JSValueRef errorJSRef = NULL;
JSValueRef batchedBridgeRef = strongSelf->_batchedBridgeRef;

JSValueRef methodJSRef = JSC_JSObjectGetProperty(ctx, (JSObjectRef)batchedBridgeRef, methodNameJSStringRef, &errorJSRef);
JSValueRef jsArgs[arguments.count];
for (NSUInteger i = 0; i < arguments.count; i++) {
jsArgs[i] = [JSC_JSValue(ctx) valueWithObject:arguments[i] inContext:context].JSValueRef;
}
JSValueRef resultJSRef = JSC_JSObjectCallAsFunction(ctx, (JSObjectRef)methodJSRef, (JSObjectRef)batchedBridgeRef, arguments.count, jsArgs, &errorJSRef);

JSValue *result = [JSC_JSValue(ctx) valueWithJSValueRef:resultJSRef inContext:context];
id objcValue = unwrapResult ? [result toObject] : result;

onComplete(objcValue, error);
}];
}

需要注意的是,这个函数名是我们要调用JS的中转函数名,比如callFunctionReturnFlushedQueue。也就是说它的作用其实是处理参数,而非真正要调用的 JS函数。
这个中转函数接收到的参数包含了ModuleId、MethodId和Arguments,然后由中转函数查找自己的模块配置表,找到真正要调用的JS函数。

JS调用OC代码

JS代码中MessageQueue.js和NativeModule.js是核心文件。BatchedBridge.js文件仅仅是MessageQueue.js的导入文件,不用关注。
MessageQueue文件是js端与oc通信的一个桥梁文件,里边有个计时器将要调用oc方法的事件放入一个queue中。
NativeModule文件实现了JS调用OC代码。需要注意的是这个调用其实都是OC先调用JS文件,执行JS,在这时候传入适当的参数,然后实现了JS调用OC方法。
在调用OC代码时,JS会解析出方法的ModuleId、MethodId和Arguments并放入到MessageQueue中,等待OC主动拿走,或者超时后主动发送给OC。

OC负责处理调用的方法是handleBuffer,它的参数是一个含有四个元素的数组,每个元素也都是一个数组,分别存放了ModuleId、MethodId、Params,第四个元素目测用处不大。
函数内部在每一次方调用中调用_handleRequestNumber:moduleID:methodID:params方法,通过查找模块配置表找出要调用的方法,并通过runtime动态的调用:

1
[method invokeWithBridge:self module:moduleData.instance arguments:params];

在这个方法中,有一个很关键的方法:processMethodSignature,它会根据JS的CallbackId创建一个Block,并且在调用完函数后执行这个Block。

RCTUIManager.h, UIManage.js这两个类一个是在oc端,一个是在js端,通过这两个类RN将我们的component转换成了我们原生的视图类。该类实现了视图的创建、查找、删除等功能。创建View的时候,每个View都有一个

最新的0.48版本的总体流程和以上流程差别不大,主要是替换了初始化部分的执行顺序,大家可以自行参阅最新的代码。

参考文档
  1. React Native通信机制详解
  2. React Native源码分析

Swift语法特性2

发表于 2017-02-25 | 分类于 iOS |

1.递归/间接(indirect)类型

间接类型是Swift2.0新增的类型,它允许将枚举中一个case的关联值再次定义为枚举,

2.inline

阅读全文 »

iOS文件共享

发表于 2017-01-11 | 分类于 iOS |

iOS7以来苹果支持了AirDrop,可以通过蓝牙来在设备之间传输文件,而且一直推荐使用iClound Drive、Handoff或者AirDrop来共享文件,但是iOS其实还支持通过iTunes在电脑和APP间通过拖动来共享文件,只是这项功能不常用到。下面介绍下iOS文件共享的集成流程。
注意该功能只支持iTunes9.1及之后的版本。

1.开启iTunes File Sharing

要让你的App支持文件共享,你需要在App的Info.plist中配置UIFileSharingEnabled值为true。配置完成后在Info.plist中展示为Application supports iTunes file sharing,如下图所示。

FileShare Info.plist

在之前的版本中可能需要在Info.plist中配置CFBundleDisplayName值为Bundle Display Name即${PRODUCT_NAME}。

之后,你就可以把你需要分享的文件放在Documents文件目录下。当你通过usb连接电脑与iPhone后,iTunes会展示File Sharing区域。这时候,你可以在电脑和app拖动来共享数据了。

FileShare iTunes

注意这时候你在iPhone设备上并无法访问其他App的这些共享数据。还有iTunes12.7开始无法在iTunes上查看当前iPhone安装的App了,小伙伴们千万别升级了。。。

在iOS11中,还可以把App的可共享Documents文件展示在默认Files应用中,要开启这项功能,需要在Info.plist中添加LSSupportsOpeningDocumentsInPlace值为YES。配置完成后在Info.plist中展示为Supports opening documents in place。当然,当你的Documents下有文件才会在Files应用中展示。

FileShare Files

2.开启AirDrop

未完待续

参考文档
  1. https://support.apple.com/en-us/HT201301
  2. https://developer.apple.com/library/content/documentation/…
    3.

Swift语法特性1

发表于 2016-11-01 | 分类于 iOS |

最近在写swift的过程中,发现有很多特性使用的不太熟练,而且有些新特性看过之后很容易忘记,所以该文章记录一些Swift语法的新特性。(本文持续更新)

1. 初始化

Designated initializers
即最常用的初始化方法init(),默认不加修饰符的init方法都需要在方法中保证所有的非Optional实例变量被赋值初始化,其子类也会隐式或显式调用父类的designated初始化方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
class House {
let door: Int
init(door: Int) {
self.door = door
}
}
class Room: House {
let table: Int
override init(door: Int) {
self.table = 0
super.init(door: door)
}
}

这儿注意如果子类有新增的属性的话,必须调用override重写父类init方法。如果没有的话,则可以不重写,直接使用父类的Designated初始化方法创建子类对象。推荐在init使用self给变量赋值。

阅读全文 »

iOS中的attribute和宏

发表于 2016-06-12 | 分类于 iOS |

在学习开源代码的时候,经常会遇到__attribute__的相关使用,但是在使用的时候又会忘记,所以用这篇文章总结下iOS开发中经常使用的attribute。
__attribute__其实是GNU C的一种机制,本质是一个编译器的指令,在声明的时候可以提供一些属性,在编译阶段起作用,来做多样化的错误检查和高级优化。可以分别用于设置函数属性、变量属性和类型属性,语法一般为: __attribute__(xx)。下面分别介绍下这些属性。

函数属性

函数属性可以帮助开发者把一些特性添加到函数声明中,从而可以使编译器在错误检查方面的功能更强大。

format
语法为__attribute__((format(__NSString__, F, A))),可以给被声明的函数加上类似printf或者scanf的特征,它可以使编译器检查函数声明和函数实际调用参数之间的格式化字符串是否匹配。format (archetype, m, n),第一个参数传递archetype指定为哪种类型,string-index指定格式化字符串的位置,n指定可变参数检查开始的位置。例如:

1
2
3
#define NS_FORMAT_FUNCTION(F,A) __attribute__((format(__NSString__, F, A)))  
FOUNDATION_EXPORT void NSLog(NSString *format, ...) NS_FORMAT_FUNCTION(1,2);
extern void myPrint(const char *format,...)__attribute__((format(printf,1,2)));

objc_designated_initializer
指定内部实现的初始化方法,系统宏NS_DESIGNATED_INITIALIZER展开即为该指令,语法为__attribute__((objc_designated_initializer))。例如:

1
2
- (instancetype)initNoDesignated ;
- (instancetype)initNoDesignated1 NS_DESIGNATED_INITIALIZER;

当一个类存在方法带有NS_DESIGNATED_INITIALIZER属性时,它的NS_DESIGNATED_INITIALIZER方法必须调用super的NS_DESIGNATED_INITIALIZER方法。它的其他方法(非NS_DESIGNATED_INITIALIZER)只能调用self的方法初始化。

objc_requires_super
表示子类重写当前类的方法时,必须要调用super函数,否则会有警告。语法为__attribute__((objc_requires_super)),例如:
- (void)fatherMethod __attribute__((objc_requires_super));

阅读全文 »
123
DragonFly

DragonFly

iOS开发、ReactNative开发、AR

21 日志
5 分类
17 标签
GitHub E-Mail 微博 Twitter FB Page JianShu
© 2018 DragonFly
由 Hexo 强力驱动
|
主题 — NexT.Muse v5.1.2
总访问量 次