SwiftプログラミングMotivationかつてSIOSocketなるSocket.IOのiosクライアントがありました。"かつて"というのも、このクライアントはObjective-Cで書かれているのですが、2015年の3月にSwift製公式クライアントが発表されたことにより、お役御免で開発STOPとなってしまいました。僕はこのライブラリ面白いなーと思ったところがあって、それはminifyしたjsのコードと少しのソースで、Objective-C製ライブラリの如く使えるようにしていたところです。*args){}];こんなふうに書けていました。いうのを、少しの労力で作れるなら、javascript界(以後jsと書きます)の膨大な資産をios界でも気軽に使いまわせるのでは?と思い興味を持っていました。いうことで、今回はこのSIOSocketをSwiftで換装してみて、時が来たら同様のことができるように前知識を蓄えておこう、というコンテンツになります。JavaScriptCoreについて今回のようにjsのコードをベースにしたswiftクライアントを作る上で欠かせないのがJavaScriptCore.framework。2013年からあって、Mac/iOS両方で使えます。Objective-CのAPIなので、Swiftでは使いにくい部分がままあります。追ってその点についても触れていければと思います。してあげれば使えるようになります。Swift上でjsのコードを実行する基本的な例を挙げてみます。返ってきた値をよしなに変換して使うprint(result.toInt32())また、3の手順は、以下のように呼ぶこともできます。factorial.callWithArguments([3])以下、各用語や概念の補足です。JSValue!評価の結果返り値があれば、ここで取得することもできます。ここでは返り値は特に指定していないので、undefinedになります。JSValue!コンテキスト上にある変数にアクセスすることができます。上記の場合はfactorial変数を取り出しています。いうふうにsubscriptをつけることで取り出せたのですが、swiftだと上記のようにメソッドを呼ばなければいけない上に、ドット記法(例えばwindow.onload)で取り出すのもできないようです。JSValue!先に取り出したfactorial関数に引数を渡すことができます。面白いのは、引数が配列になっていて、且つAnyObject!になっていること。配列のindex順にjsのfunctionに渡してくれるswift側の型とjs側の型を自動的に変換してくれるということになります。js上でswiftのコードを実行する大きく分けて2種類の方法があります。代わりにJSContext.currentContext()を使うと、WWDC2013セッションのビデオでは言っていましたが、普通にキャプチャすると強参照になってリークしやすくなるので、weakを使いましょうということと認識しました(後述しますがunownedはJavaScriptCoreがObjective-C製APIということで使えない模様)。Objective-CのBlocksとSwiftのクロージャでは変数のキャプチャが違うをご参照ください。クロージャを使った例を示します。context.objectForKeyedSubscript("say")print(sayFunc.callWithArguments(["hello2"]))上記は、jsのコードにswifitで書いたfunctionを渡し、jsのコードとして実行するサンプルです。見かけない単語が幾つか出てきています。@convention(block)AppleのドキュメントのClosuresの部分をみると、Objective-CのブロックとSwiftのクロージャは互換性があるので、@convention(block)アトリビュートをつければ使えるよとのこと。APIなので、このような処理をする必要が出ています。unsafeBitCast上記@convention(block)アトリビュートによって、sayオブジェクトはObjective-Cのブロックとしてコンパイル時には扱われますが、そのままだと、context.setObject~のところで、エラーが出てしまいます。objectをAnyObject!として渡さなければならないからです。ここはエラーをさけるため、unsafeBitCastを使っています。<<余談>>元々Objective-CのAPIなので、Objective-Cで書くととても楽です。blockのところはObjective-Cだと、このように書けます。evaluateScript:@"say('hello')"]);Swiftより断然直感的ですね...。JSExportを使うここは今回使っていないので、説明は以下の参考リンクをご参照ください。#cocoa_kansaiSIOSocketをSwift製にする上で気づいた点などSIOSocket.swiftのソースhttps://github.com/mitolog/SIOSocket-swifttodo:テスト書いて、実際にすべてのargumentがおくれるか確認(文字列パラメータしかチェックしてないッス...)JSExportを使ってみるUI適当すぎsocket.ioを使うには、UIWebView()を使う必要があるsocket.ioのクライアントAPIのページに書いてあるように、socket.ioをスタンドアローンで使う場合、ioオブジェクトはwindowのプロパティとして配置されます。前提として、UIWebView()のJSContextを使うUIWebView()にhtmlをロードするロードが完了(window.onload)してから、ioオブジェクトを取り出す必要があります。SIOSocketでは、以下のように実現しています。inとしていたのですが、unownedがエラーになってしまい、weakに変えたらコンパイル通りました。swiftにはあって、Objective-Cにはないからだと思います。])これだけのパラメータを軽々と自動的に変換してくれるので、感動でした。SIOSocketはこの部分元々以下のように書いていたのですが、上のようにするほうが楽だしスマートな気がします。];}JSContextにアクセスするスレッドはどこでもいい元々のSIOSocketでは、onloadコールバックでioオブジェクトを取得する際に利用したスレッドを保持しておき、そのスレッド上でのみJSContextにアクセスしていましたが、WWDC2013のビデオを見てみたら、JSContextはスレッドセーフとのこと。実際に、元々performSelectorでスレッドを指定して実行していた箇所を外してみたら、うまく動いたので問題ないと思います。jsを複数同時に実行したいなら複数のvmを立ち上げる必要がある)らしいので、そこは要注意です。jsを文字列リテラルとして埋め込む場合は、エスケープに気をつけろ当初は、staticなglobal変数としてsocket.io.jsを読み込んでいました。その際は、以下のエスケープを施しました。Literalsの部分を参考に。換装しての感想JavaScriptCoreに関しては、WWDC2013のセッションビデオを見るのが一番理解が進みましたので、超オススメです。socket.ioの場合、windowプロパティのonloadをキャッチする必要があったので、UIWebViewからJSContextを引き出せる部分が結構キモだなと思いました。socket.ioみたく、UIを伴わないjsで且つサイズが小さいものだと、swift製クライアントを作る必要性はあるのかな〜と感じました(Underscore.jsとか?)。ぶっちゃけ、objcで書いてbridging-headerで読み込んで使ったほうが楽かもしれない。あと、他にJavaScriptCoreを使ったライブラリがあったら教えていただきたいです。サンプルを作るうえで一部ソースを使わせていただきました。ありがとうございました。はてなブログProベトナム・ハノイ拠点の個人デベロッパ。東南アジア向けに素敵なサービスと文化を作るべく模索中。好きなSF本は「モレルの発明」。検索リンクミトミネ農場(農業系)DotsNest(旅行記)リモート通訳アプリベトナム農業メディア最新記事2019年上半期の振り返りと下半期の目標ベトナムの片田舎でマイクロサービスなアプリを個人開発した話JellyTalkというアプリをリリースしました!「逆説のスタートアップ思考」が良かったベトナムの片田舎で約1年間、未経験から農業をしてみての振り返り月別アーカイブ参加グループはてなブログをはじめよう!mitolabさんは、はてなブログを使っています。ブログを報告するスターをつけました引用をストックしましたストック一覧を見る閉じる引用するにはまずログインしてくださいログイン閉じる引用をストックできませんでした。再度お試しください閉じる限定公開記事のため引用できません。