React NativeのiOSネイティブをSwiftで書く

##この記事の対象

この記事は、

- **ReactNative**アプリを作っていて
- **iOS側でネイティブ**を実装する必要があり
- かつそれを**Swiftで書きたい**んだけど
- **Swiftもネイティブのブリッジの仕方も分からない！**

という方が対象です。

かく言う私がそうでして、ここで書かれているコードは動くものの、間違いはあるかもしれませんのでお気をつけください！！

(私の場合、とあるサービスの実装ガイドがSwift版しかなく、jsでの実装が難しそうだったので泣く泣くSwiftでネイティブを実装しました。。。)


##この記事で分かるようになる（と願う）こと
数を増やしたり減らしたりするカウンターアプリを通して、以下3つを共有できればと思います。

1. ネイティブ側でSwift使うのに必要な最低限のファイル3つ(`.swift`, `.m`, `.h`)の最低限の書き方
2. ネイティブ側からjs側への値の渡し方(**コールバック**と**プロミス**の2パターン)
3. js側で値を受け取り方(同様にコールバックとプロミスの2パターン)

##この記事で説明しないこと

- ネイティブ実装の諸々の詳細（使える型とか、その他のパターンとか）
- React Nativeのプロジェクトの作り方
- React Nativeのjs部分の書き方(stateの概念とか更新の仕方とか)
- Androidのネイティブ実装

##手っ取り早くソース見たい方は

[GitHub](https://github.com/nichiyoshi/react-native-swift-bridge-sample)に上げてるのでこちらをご覧ください。

なお、この記事は以下の記事の簡易版なので、詳細はそちらを参考にしてください。

- [Swift in React Native - The Ultimate Guide Part 1: Modules](https://teabreak.e-spres-oh.com/swift-in-react-native-the-ultimate-guide-part-1-modules-9bb8d054db03)

## 環境

```
    System:
      OS: macOS 10.14
    Binaries:
      Node: 10.5.0 - ~/.nodebrew/current/bin/node
      Yarn: 1.12.3 - /usr/local/bin/yarn
      npm: 6.1.0 - ~/.nodebrew/current/bin/npm
      Watchman: 4.9.0 - /usr/local/bin/watchman
    SDKs:
      iOS SDK:
        Platforms: iOS 12.1, macOS 10.14, tvOS 12.1, watchOS 5.1
    IDEs:
      Xcode: 10.1/10B61 - /usr/bin/xcodebuild
    npmPackages:
      react: 16.6.1 => 16.6.1 
      react-native: 0.57.7 => 0.57.7 
    npmGlobalPackages:
      create-react-native-app: 1.0.0
      react-native-cli: 2.0.1
      react-native-git-upgrade: 0.2.7
```
あとSwiftは`3.0`に指定しています。

##この記事でやること
簡単なカウンターアプリの作り方の紹介を通じてネイティブ実装の方法を共有します。

![swiftsample.gif](https://qiita-image-store.s3.amazonaws.com/0/214755/9a6aac68-ec2d-2b8b-280f-c0638da528d8.gif)

機能は以下3つです。

```
1. 数を1増やす (Increment)
2. 数を1減らす (Decrement)
3. 数が0未満になる時にはエラーメッセージを表示する
```

すごく単純ですね。

js側ではstateで現在の値(初期値は0)を持っていて、その現在値をネイティブ側に渡し、swiftコードで足したり引いたりした値をjs側で受け取って、stateを更新し画面を再描画します。

```
js(現在の値) -> swift(計算) -> js(計算後の値)
```

また、ネイティブ側からjs側に値を渡す時ですが、

- Incrementの時は**コールバック**
- Decrementの時は**プロミス**

を使っています。

それでは、以下の順に見ていきます。

1. ネイティブ側で必要な最低限のファイル3つ(`.swift`, `.m`, `.h`)の最低限の書き方
2. ネイティブ側からjs側への値の渡し方(**コールバック**と**プロミス**の2パターン)
3. js側で値を受け取り方(同様にコールバックとプロミスの2パターン)

### ネイティブ側で必要な最低限のファイル3つ

Swiftでネイティブコードを書くには、最低限以下三つのファイルが必要です。

1. Swiftファイル(`.swift`)
2. Objective-C Bridging Headerファイル(`.h`)
3. Objective-C Macrosファイル(`.m`)

Macrosがなんなのかとか、私は全然分かっておりません。。


### 1. Swiftファイルを作る

1. ReactNativeプロジェクトのiOSディレクトリを、Xcodeで開く
2. `AppDelegete.h`などがある階層に、`New File`で新規ファイルを作る(**Swift File を選択**)

※ この時に「Objective-C bridging headerを作りますか？」的なことを聞かれたら作成してください。

```
SampleProject
 ∟ SampleProject <- ここのディレクトリにSwiftファイルを新規追加
    ∟ Counter.swift <- 追加したSwiftファイル
 ∟ Libraries
 ∟
```

以上!

### 2. Objective-C Bridging Headerファイルを作る

Swiftファイルをjs側にブリッジするために、 `Objective-C Bridging Header`ファイルというのが必要なようで。SwiftだけじゃなくObjective-Cも書かないといけないんすね。

ここのステップは、先ほどSwiftファイルを作った段階で「「Objective-C bridging headerを作りますか？」で作成した方はもう完了です。

ただ、私の場合は聞かれないこともありました。その場合は、**自分で作る**必要があります。

まずファイルを作る前に、ファイル名を確認します。

基本的には、

```
プロジェクト名-Bridging-Header.h
```

です。

```
SampleProject-Bridging-Header.h
```

など。

Targetの`Build Settings`からも`Objective-C Bridging Header`という項目でファイル名を確認できます。

![スクリーンショット 2018-11-30 23.05.39.jpeg](https://qiita-image-store.s3.amazonaws.com/0/214755/3886fe27-cfac-2817-4d18-9c3ccad29fa0.jpeg)

上記で確認したファイル名で、新規ファイルを作成します。
ファイル作成時には**Header Fileを選択**します。

```
SampleProject
 ∟ SampleProject
    ∟ Counter.swift 
    ∟ SampleProject-Bridging-Header.h <- 追加したヘッダーファイル
 ∟ Libraries
 ∟
```

完了！

###3. Objective-C Macrosファイルを作る

これまでと同様にして新規ファイルを作ります。
今度は、**Objective-C Fileを選択**してください。

```
SampleProject
 ∟ SampleProject
    ∟ Counter.swift 
    ∟ SampleProject-Bridging-Header.h
    ∟ Counter.m <- 追加したObj-Cファイル
 ∟ Libraries
 ∟
```

完了！

## ネイティブ側からjs側への値の渡し方

ここからが本題。

ネイティブ側を書いていきます。

以下の順番で見ていきます。

**Swift**
1. Counterクラスの用意
2. Incrementメソッド実装(コールバック使う)
3. Decrementメソッド実装(プロミス使う)

**Objective-C(.m)**
4. Counterクラスのエクスポート
5. Incrementメソッドのエクスポート
6. Decrementメソッドのエクスポート

**ブリッジヘッダー(.h)**
7. ブリッジを完成させる


###1. Counterクラスの用意

Swiftファイル

```swift
import Foundation

@objc(Counter)
class Counter: NSObject {

}
```

Swiftの書き方分からないので、おまじないだと思ってください。。
Objective-Cに読み取ってもらうためには` @objc ` が大事だそうです。

###2. Incrementメソッド実装(コールバック使う)
SwiftファイルのCounterクラス内に以下を追加します。

```swift
 @objc
  func increment(_ origin: Int, withCallback callback: RCTResponseSenderBlock) -> Void {
    callback([origin + 1])
  }
```

入力値に１を加えた数を、コールバックで返すようにしています。

簡単に説明すると、

`origin: Int`
=>引数１で計算元の数値。

`withCallback callback: RCTResponseSenderBlock`
=>引数2でコールバック。 `withCallback`は呼び出し元に表示される引数名で、 `callback`がメソッド内で使う引数名。

`callback([origin + 1])`
=>計算元に１加えた数を引数に、コールバックを呼び出し。
**コールバック呼び出し時の引数は、引数が一個でも配列にする**。

また、この**メソッド自体は返り値がない**です。
以下みたいに普通にメソッドの返り値使えばいいじゃんって私は思ったのですが、ReactNativeのブリッジではそれは**NG**のようです。Androidも同じです。なので、js側に値を渡すために、コールバックかプロミスを使っています（他にも方法あるのかもしれませんが）。

```swift
 @objc
  func increment(_ origin: Int) -> Int {
    return origin + 1
  }
```

###3. Decrementメソッド実装(プロミス使う)

同様に以下を追加します。

```swift
  @objc
  func decrement(_ origin: Int, withResolve resolve: RCTPromiseResolveBlock, withReject reject: RCTPromiseRejectBlock) -> Void{
    
    if(origin <= 0){
      reject("E_COUNT", "You can't decrement any more!!", nil)
      return
    }
    
    resolve(origin - 1)
    
  }
```

計算元の数値が0以下の場合にはエラーを返すようにしています。
それ以外の場合には、計算元から1引いた数をプロミスの渡し手に返します(今回は配列じゃないです)。

エラーハンドリングはよくわかってないので誤魔化しています(nilの部分とか)。

プロミスって何よっていうのは、私にはうまく説明することができないので、ググってください。。

これでSwiftファイルは完成です。

全体は[こちら](https://github.com/nichiyoshi/react-native-swift-bridge-sample/blob/master/ios/Counter.swift)をご確認ください。

**補足**

上記ファイルには

```swift
 static func requiresMainQueueSetup() -> Bool {
    return false
  }
```

というのがあります。
これは、「requiresMainQueueSetupを実行しろ！」という警告が出るのを防ぐためです。非同期でもいい場合は `false`を返すそうです。詳細は分かりません。すみません。

###4. Counterクラスのエクスポート

ここから、Objective-Cファイル(`Counter.m`)をいじっていきます。

先ほどの`Counter.swift`のCounterクラスをjs側にエクスポートするには、

```objectivec
#import <Foundation/Foundation.h>
#import "React/RCTBridgeModule.h"

@interface RCT_EXTERN_MODULE(Counter, NSObject)

@end
```

と書きます。

Counter(Swiftのやつ)をエクスポートします、これはオブジェクトです、という感じです。
`Counter.swift`で

```swift
@objc(Counter)
class Counter: NSObject {
}
```

としましたが、 この`@objc(Counter)` の部分と紐づいているみたいです（ただの予想です。。）

###5. Incrementメソッドのエクスポート

Swiftクラスの`increment`メソッドをエクスポートします。

```objectivec
@interface RCT_EXTERN_MODULE(Counter, NSObject)

//↓を追加
RCT_EXTERN_METHOD(increment: (NSInteger)origin withCallback:(RCTResponseSenderBlock)callback)

@end
```

###6. Decrementメソッドのエクスポート

Swiftクラスの`decrement`メソッドをエクスポートします。

```objectivec
@interface RCT_EXTERN_MODULE(Counter, NSObject)
RCT_EXTERN_METHOD(increment: (NSInteger)origin withCallback:(RCTResponseSenderBlock)callback)

// ↓を追加
RCT_EXTERN_METHOD(decrement: (NSInteger)origin withResolve:(RCTPromiseResolveBlock)resolve withReject:(RCTPromiseRejectBlock)reject)

@end
```

特に説明なくてすみません。
引数とかは、Swiftファイルと見比べてください。

###7. ブリッジを完成させる

Native側の最後は、Bridging-Headerファイルです。

```objectivec
#import "React/RCTBridgeModule.h"
```

はい、**この一行で大丈夫**です。

##js側での値を受け取り方

さぁ、ようやくjs側まで来ました。

`App.js`クラスをいじります。

レイアウトとかそこらへんは触れません。

**前提**
初期stateは以下にします。

```javascript
  constructor(props) {
    super(props)
    
    this.state = {
      count: 0,
      errorText: ""
    }
    
  }
```

###1. NativeModulesをimportする

```javascript
import {
  Platform,
  StyleSheet,
  Text,
  View,
  //↓↓↓
  NativeModules,
  Button
} from "react-native"
```


###2. incrementメソッドを呼び出して値を受け取る

```javascript

  onPressIncrement = () => {
    const {count} = this.state

    const callBack = incrementedCount => {
      this.setState({count: incrementedCount, errorText: ""})
    }

    NativeModules.Counter.increment(count, callBack)
  }

```

コールバックを定義し、今の値とコールバックを引数にして`NativeModules.Counter.increment`を呼び出します。
`incrementedCount`が、swiftのincrementメソッド内で１足された数値です。


###3. dncrementメソッドを呼び出して値を受け取る

```javascript
  onPressDecrement = () => {
    const {count} = this.state
    
    NativeModules.Counter.decrement(count)
      .then(decrementedCount => this.setState({count: decrementedCount}))
      .catch(error => {
        this.setState({errorText: error.message})
      })
      
  }
```

または async/awaitを使うのであれば以下のようにも書けます。

```javascript
  onPressDecrement = async () => {
    const {count} = this.state
    
    const decrementedCount = await NativeModules.Counter.decrement(count).catch(error =>
      this.setState({errorText: error.message})
    )
    
    if (decrementedCount) {
      this.setState({count: decrementedCount})
    }
  }
```

ネイティブの方では以下のように引数３つですが、js側では `decrement(count)`と１つだけ。不思議ですが、プロミスの場合はよろしくやってくれるようです。

```swift
  @objc
  // 引数3つ
  func decrement(_ origin: Int, withResolve resolve: RCTPromiseResolveBlock, withReject reject: RCTPromiseRejectBlock) -> Void{
   ///
  }
```
```javascript
// 引数１つ
NativeModules.Counter.decrement(count)
```



さて、後半スピードアップした、または説明を面倒がりましたが、Swiftを使ってのネイティブ実装は以上です。

改めて、ソース全体は[こちら](https://github.com/nichiyoshi/react-native-swift-bridge-sample)でご確認ください。

##参考サイト

- [Swift in React Native - The Ultimate Guide Part 1: Modules](https://teabreak.e-spres-oh.com/swift-in-react-native-the-ultimate-guide-part-1-modules-9bb8d054db03)
- [Native Modules](https://facebook.github.io/react-native/docs/native-modules-ios)