React NativeからObjective-Cのコードを呼ぶ方法(Native Modules)


#Native Modulesについて

React Nativeを使えばJavaScriptでネイティブアプリを作ることができます。

ただ、各OSの全APIを呼べるわけではないですし、Objective-CやSwift書いた既存のコードを使いたい場面もあります。

そのために用意されているNative Modulesという仕組みを試してみました。
React Native official document: [Native Modules](https://facebook.github.io/react-native/docs/native-modules-ios.html "Native Modules")

minimumなsampleはここが分かりやすかったです。
Objective-C側で作成したNSStringをReact Nativeで出力するだけです。
[React Native - Super Simple Native Module Example](https://colinramsay.co.uk/2015/03/27/react-native-simple-native-module.html "React Native - Super Simple Native Module Example")

#Native Moduleの実装

##NSURLProtocolを使って広告ブロックを実装

参考記事: [iOSアプリ中のUIWebviewで広告ブロック(Objective-C)](http://qiita.com/koyopro/items/7ae28b5f58f99e87c1f9"iOSアプリ中のUIWebviewで広告ブロック(Objective-C)")

上記の記事ではNSURLProtocolを使ってUIWebviewに広告ブロック機能を実装しています。

React NativeにはNSURLProtocolを呼ぶAPIが存在しないため、Native Moduleを使ってWebViewに広告ブロック機能を実装してみます。


Objective-Cのコードでは、広告ブロックのロジックを実装したFilteredURLProtocolを以下のコードでNSURLProtocolに設定しています。
*詳しくは元記事をご覧ください。

```[NSURLProtocol registerClass:FilteredURLProtocol.class]; ```

これをReact Nativeから呼んであげればOKということです。


##ブリッジ用Classの作成

ブリッジ用にAdblockManager Classを作成します。

ブリッジで使用するClassはRCTBridgeModuleプロトコルを継承する必要があります。


```objc:AdblockManager.h
#import "RCTBridgeModule.h"

@interface AdblockManager : NSObject<RCTBridgeModule>

@end
```



React Nativeから呼び出せるメソッドの作成にはRCT_EXPORT_METHOD()マクロを使います。

ブリッジ呼び出ししたメソッドは非同期なので返り値が必要な場合はcallbackで渡します。

```objc:AdblockManager.m
#import "AdblockManager.h"
#import "FilteredURLProtocol.h"
@implementation AdblockManager


RCT_EXPORT_MODULE();

RCT_EXPORT_METHOD(setAdblock:(RCTResponseSenderBlock)callback)
{
  [NSURLProtocol registerClass:FilteredURLProtocol.class];
  NSString* result = @"setAdblock";
  callback(@[result]);
  
}

RCT_EXPORT_METHOD(unsetAdblock:(RCTResponseSenderBlock)callback)
{
  [NSURLProtocol unregisterClass:FilteredURLProtocol.class];
  NSString* result = @"unsetAdblock";
  callback(@[result]);
}
@end
```

##React Native側からの呼び出し

NativeModulesをimportします。

WebViewのmount時に登録、unmount時に登録解除します。

```js:SimpleWebView.js
import React, {Component} from 'react-native';
import {AdblockManager} from 'NativeModules';
const {
  StyleSheet,
  View,
  WebView
} = React;

export default class SimpleWebView extends Component{

  componentWillMount(){
    if (this.props.adblock){
      AdblockManager.setAdblock((result) => {
        console.log(result);
      });
    }
  }

  componentWillUnmount() {
    if (this.props.adblock){
      AdblockManager.unsetAdblock((result) => {
        console.log(result);
      });
    }
  }

  render(){
    return (
      <View style={styles.container}>
        <WebView url={this.props.url} startInLoadingState={true}/>
      </View>
    );
  }
}

let styles = StyleSheet.create({
  container: {
    flex: 1
  }
});

```

メソッドを呼び出すだけならこれだけです。
思っていたよりもかなり簡単でした。

UIが絡むとどうなるんでしょうね。

ソースはこちら。
https://github.com/enu-kuro/ReactNative-Adblock-WebView

#React Nativeを触ってみた感想
起動させるまでがしんどかったです。
githubからサンプルコードを落としてきても簡単には起動してくれません。
ググッてググッてゴニョゴニョしてやっと起動できましたが、心に余裕がないと起動する前に諦めてしまいそうです・・・。
一通り罠を通ったので最近は起動まではなんとかできることが多くなりました。

作っていると謎の挙動によくぶち当たります。
情報がまだ少ないので解決できないで別の方法で回避しなくてはならないことがあったりします。
例えばNavigatorIOSのtitleやpropsが更新されてくれないとか。
そのせいでサンプルに作ったコードもやや無理やりな実装になっている部分があります。
(現在FacebookはAndroidでも使用できるNavigatorを使用するようにしておりNavigatorIOSのメンテはしていないとのことです)

JavaScriptだけで簡単なアプリは作れてしまいますが、iOSでの実装の仕組みを知っていないと細かい挙動を実装するのは難しいかなと思いました。
ListViewのDataSourceとか。

StoryBoardが使えないので複雑なアプリになるとUIや画面遷移管理はしんどそうです。
CSSかけないのでつらいです。

iPhone6で操作する分にはパフォーマンスはまったく気になりませんでした。
(追記: と思いましたがいろいろ触っていると挙動がぎこちなくなってきました。使用メモリがどんどん増えているのでメモリリークしているようです。メモリはまったく意識せずに作っていたのですがどうすればいいのでしょうね・・・。)

お疲れ様でした。