[React Native]ネイティブモジュール(Swift)を使う[iOS編]

potproject.net Advent Calendar 20169日目の記事です。


react-nativeをとりあえずすぐ実機で動かしてみる

で、とりあえず実機で動くかの確認は取れました。
で、今回は、React Nativeとネイティブコードの連携をやってみたいと思います。

React NativeはiOSやAndroidを共通のJavascriptで書いてマルチプラットフォームでしかもネイティブに動くよ!というのがウリです。

しかし、当然ながら、SwiftやObjective-Cを使うようなものも必要になります。カメラとかプッシュ機能。

まあメジャーなものは用意されてる場合もあるんですが、マイナーなものは無かったりするので、

自分で作ってね!ってことでネイティブモジュールという機能で補完できるようになってます。

Cordovaを思い出す。しかしこっちは完全ネイティブなのだ。

このあたりを参考にしました。しかしまあやっぱり例によってSwift3.0なので結構コード変えてたりもします。
React NativeでNative機能をSwiftで書いて使うには

Native Modules

Objective-Cは全く分からないのでSwiftです。でもブリッジしなくてはならないので多少はObjective-Cは避けられないんだなあこれが。
原則、React NativeはネイティブモジュールにSwiftを使うように設計されていません。生成されたプロジェクトもObjective-C用で生成されます。

なので、使うにはObjective-CとSwiftを連携させる必要があります。

swiftでモジュールを作成

まずは、モジュールを作成します。ちなみにこれだけだとまだ動きません。
端末の時間を取得してAny(Dictionary)型でコールバックを返します。

どうやらReact Nativeのjavascriptにデータを受け渡す際は必ずcallbackなのかな。
ちなみにPromiseも使えます。

ReactNativeModule.swift

import Foundation

@objc(ReactNativeModule)
class ReactNativeModule: NSObject {

  @objc func callbackMethod(_ callback: RCTResponseSenderBlock) -> Void {
    // システムのカレンダーを取得
    let cal = Calendar.current
    // 現在時刻のDateComponentsを取り出す
    var dataComps = cal.dateComponents([.year, .month, .day, .hour, .minute], from: Date())
    let object = [
      "year":dataComps.year!,
      "month":dataComps.month!,
      "day":dataComps.day!
    ]
    callback([object])
  }
}

配列で返しても大丈夫なの?と思いでしょうが、ちゃんとReact Native側はjavascript Object型で受け取ってくれます。優秀です。  
callbackMethodの第一引数が無名関数がなっているのは、このあたりの問題から。認識してくれないのです。Swift3.0での変更点となります。
Got “is not a recognized Objective-C method” when bridging Swift to React-Native – stackoverflow

Objective-Cでエクスポートする

ReactNativeModuleBridge.m

#import "RCTBridgeModule.h"

@interface RCT_EXTERN_MODULE(ReactNativeModule, NSObject)

RCT_EXTERN_METHOD(callbackMethod:(RCTResponseSenderBlock)callback)
@end

実際はこのObjective-Cが読み込まれます。これが上で書いたSwiftコードの橋渡しになります。

Objective-Cの文法は相変わらず割と理解できていない。

testreactnative-Bridging-Header.h

#import "RCTBridgeModule.h"

RCTBridgeModule.hというObjective-CのコードをSwiftで読み込むために設置。
このあたりに関してはググるといっぱい出てくるため割愛。

React Native側のソースはこんな感じ。画面の更新もしたかったのだが、結構時間掛かりそうで・・・ただログ出すだけ。

index.ios.js

import React, { Component } from 'react';
import {
  NativeModules,
  AppRegistry,
  StyleSheet,
  Text,
  View
} from 'react-native';
var nm=NativeModules.ReactNativeModule;
nm.callbackMethod(function(res){
    console.log(res);
});

これでデバッグ画面からログが確認できました。

ちゃんと{"year":2016,"month":12,"day":9}と出てきます。Any型も受け渡せるのは楽でいいですねえ。

今回、タイトルが(iOS編)です。

これで気づいた人もいるかもですが、明日はネイティブモジュール(Java)を使う[Android編]をやります。多分。

Swift3.0でjsonパラメータをHTTP POST

potproject.net Advent Calendar 2016
8日目の記事です。

Swift3.0です。本当Swift2.x時代が強すぎて検索に全く引っかかってこないのが辛い。


もう結構前になってしまいますが、iOS10が公開され、iPhone7も発売されました。

しかし、相変わらず自分的にはiOS10もiPhone7も興味ありません。

でも、残念ながらiOS開発をしているとiOS10にも対応しなきゃならなくなり、

小規模なアプリを作っていたので、swift2.3からswift3.0に書き直すことに。

しかし、swift3.0はもう2.xの互換性をかなぐり捨てていて、公式でコンバータが用意されるくらいにめっちゃ変更されてます。

まあ大体はコンバートで対処できるのですが、http通信のコードの部分でエラーが出ていました。

調べるとやっぱりここはうまく自動変換ができない模様。

Swift 3 URLSession.shared() Ambiguous reference to member ‘dataTask(with:completionHandler:) error (bug)

で、大体の人が詰まっているようだったswift3.0でHTTPリクエストを送るコードを自分のメモがてら載せときます。

つかswiftは仕様変わりすぎなんだよね・・・

コピペエンジニアには辛すぎる言語だ。ググっても2と3が混合してて辛い。

まず、swift2.3のコード。バックアップ取ってなかったんで再現になりますが。
Swift2.xだとこんな感じです。
参考:http://qiita.com/sushichop/items/ac4ae99b905ce523c2fe

// create the url-request
        let urlString = "http://httpbin.org/post"
        var request = NSMutableURLRequest(URL: NSURL(string: urlString)!)

        // set the method(HTTP-POST)
        request.HTTPMethod = "POST"
        // set the header(s)
        request.addValue("application/json", forHTTPHeaderField: "Content-Type")

        // set the request-body(JSON)
        var params: [String: AnyObject] = [
            "foo": "bar",
            "baz": [
                "a": 1,
                "b": 20,
                "c": 300
            ]
        ]
        request.HTTPBody = NSJSONSerialization.dataWithJSONObject(params, options: nil, error: nil)

        // use NSURLSessionDataTask
        var task = NSURLSession.sharedSession().dataTaskWithRequest(request, completionHandler: {data, response, error in
            if (error == nil) {
                var result = NSString(data: data, encoding: NSUTF8StringEncoding)!
                println(result)
            } else {
                println(error)
            }
        })
        task.resume()

いろいろありますが、これはNSURLSessionDataTaskというものを使ってでHTTP POSTを送るようです。

多分今までのswiftだとこれが主流なのかな?

このNSURLSessionがswift3.0ではURLSessionになり、仕様も変わっています。

URLSession.DataTaskは、第一引数に NSMutableURLRequest型ではなくURLRequest型でないと動きません。

NSMutableURLRequestを受け渡すと、クラッシュします。

requestは明示的に型宣言してないため、コンバートが見逃したということですね。

しかもコンパイル時エラーも起きない。困りますね。

(他のブログだとこれでビルドが通らないらしい。もしかして修正されたのかも?

というか通り抜けてたならAppleさんが悪い・・・俺は悪くない

参考:https://blog.areare.net/archives/8321

URLRequestは、NSMutableURLRequestとほぼ同じように書けます。

というより、内部的にはほぼ同じものらしいので、なんでこれで通らなくなってしまったのかわからん。

let urlRequest = URLRequest(url: requestURL)

これを踏まえて、書きます。

        let urlString = "https://httpbin.org/post"
        var request = URLRequest(url: URL(string:urlString)!)

        // set the method(HTTP-POST)
        request.httpMethod = "POST"
        // set the header(s)
        request.addValue("application/json", forHTTPHeaderField: "Content-Type")

        // set the request-body(JSON)
        let params: [String: Any] = [
            "foo": "bar",
            "baz": [
                "a": 1,
                "b": 20,
                "c": 300
            ]
        ]
        do{
            request.httpBody = try JSONSerialization.data(withJSONObject: params, options: [])
        }catch{
            print(error.localizedDescription)
        }
        // use NSURLSessionDataTask
        let task = URLSession.shared.dataTask(with: request, completionHandler: {data, response, error in
            if (error == nil) {
                let result = String(data: data!, encoding: .utf8)!
                print(result)
            } else {
                print(error)
            }
        })
        task.resume()

こんな感じかな。

初期に公開したコードはやっぱり間違っていました。ごめんなさい。

ちゃんとこのソースのままでjsonをpostできていることを確認しました。

後は、他にもいろいろ変わっているところがありました。

NSJSONSerialization.dataWithJSONObject([AnyObject], options: [options], error: [error])は、

JSONSerialization.data(withJSONObject: [Any], options: [options])に置き換わりました。

errorのコールバックがなくなった分、try-catchを行わないとコンパイルエラーとなります。

他にも、微妙に大文字から小文字へと変数名が変わったり。

iOS開発初心者のSwift3の道は険しい。

2週間くらいSwiftを触ってみた話

swift

何故か現在SwiftでiOS開発のためよく使っているので、それに関しての雑記。

ちなみに、Swiftを2週間くらいと書いてますが、2週間前はMacすら使ったことなかったです!
なのでつまり、iOSアプリ開発自体初めてです。Objective-Cももちろんわからないです。今もわかりません。

Swiftは、Appleさんが作った新しい言語です。iOS開発のための言語と言っていいでしょう。

公式には、

Swiftは、iOS、Mac、Apple TV、Apple Watch向けのアプリケーションを開発するためにAppleが作ったプログラミング言語です。 この直感的でパワフルな言語は、今までコードを書いたことがない人でも簡単に使うことができます。 Swift – Apple(日本)

とあります。本当でしょうか。

書き方としては、kotlinとすごく似てます。元々kotlinは少しだけやったことがあったので、なんとかすんなり抵抗なく使えてます。
変数に!や?を付けるのは最初はナニコレと思うかもしれませんが、kotlinと似た感じなのですんなりいけました。
とりあえず、?はnilの可能性がある、!はnilの可能性はほぼ無しってことらしいですよ。大体Xcodeがすごく指摘してくれるのでなんとかなります。
なので、Android開発はkotlin、iOS開発はswiftとすることでかなり捗るんじゃないですかね。どっちがどっちかわからなくなりそうですが。
Objective-Cと比べれば、かなり今風な新しい言語だとは思う。でもやはりアップルの悪い癖が少しあるような言語だとも思う。

その他、使ってみてなんか思ったこと。
・変更しない変数(定数)は全部letにしないとXcodeさんから怒られる
変更しないであろう変数は全部定数宣言。
let hoge="huga"みたいな。まあ、確かにそっちがわかりやすいですな。

・ログ出力がswift1.xはprintln、swift2.0からprintに
この辺、1.xと2.xで仕様とか変わっているようです。新しく始める自分にはなんのことやら。
ググっても古いソースだと混ざっていて、最初はちょっとだけ焦ります。

・swift3.0からCっぽいfor文が廃止
for var i = 0; i < 10; i++ {}
・++や–といったCライクなインクリメントデクリメントも廃止
var i = 0
i++

これが廃止されます。なんというか、swiftは完全にCからの脱却を狙っているのかなあと思います。
やはり新しい言語は新しくわかりやすい書き方でっていうことなんでしょうか・・・
でも、多分完全に素人じゃないなら元々の書き方がわかりやすいなあ。

これらはこういう風に書き直されます。
for i in 0..<10 {}

var i = 0
i= i+1

あと、新しい言語らしく関数型っぽい書き方もできるとか。

【Swift】SwiftでC-styleのforループがなくなる話

【Swift】Swiftの++, –がなくなる話

ここで書いてるところはかなり初歩ですが、Xcodeの保管機能が優秀なおかげで割とガリガリ書けてます。
まあ、やっぱり問題点としては、当然ですけどWindowsでは使えない。
この記事もWindowsで書いているため、あまり詳しいことが書けないというジレンマ。

しかし、これで晴れてAndroid/iOSどっちもいけるエンジニアにレベルアップしました。
まだまだSwiftは新しく情報も少ないですが、iOS開発では変数名なんかはほぼ共通なので、Objective-Cのコードを見ながらSwiftで書いていけば何とか書けます。
いい言語だとは思うので、iOS開発なら積極的に使っていきたいです。