Windows Bridge for iOS 〜Objective-C(Xcode)で作成したプロジェクトを変換してみた〜
今日は、Objective-Cで作成たプロジェクトをWindows用のプロジェクトに変換してみました。
↓WinObjCなど、基本的な事については、昨日の記事をご参照ください。furuya02.hatenablog.com
1 vsimporter.exe
WinObjCに含まれるvsimporter.exeを使用することで、Objective-CのプロジェクトをWindows用のプロジェクトに変換できます。vsimporter.exeの使用方法は、-help スイッチで確認できます。
c:\winobjc\bin>vsimporter.exe -help Usage: vsimporter.exe [-project projectname] [-target targetname ...] [-configuration configurationname] [-interactive] [setting=value ...] vsimporter.exe [-project projectname] -scheme schemename [-configuration configurationname] [-interactive] [setting=value ...] vsimporter.exe -workspace workspacename -scheme schemename [-configuration configurationname] [-interactive] [setting=value ...] vsimporter.exe -list [-project projectname | -workspace workspacename] Program Options -usage print brief usage message -help print full usage message -interactive enable interactive mode -loglevel LEVEL debug | info | warning | error -list list the targets and configurations in the project -sdk SDKROOT specify path to WinObjC SDK root (by default calculated from binary's location) -project PATH specify project to process -workspace PATH specify workspace to process -target NAME specify target to process -alltargets process all targets -scheme NAME specify scheme to process -allschemes process all schemes -configuration NAME specify configuration to use -xcconfig FILE apply build settings defined in FILE as overrides -format FORMAT winstore8.1 | winphone8.1 | winstore10 (default) -version print the tool version
- formatでターゲットを指定することで、3種類のプロジェクトが生成可能です。
2 Xcodeでのプロジェクト作成
Xcodeの「Single View Application」から、テスト用のアプリを作成します。
なお、vsimporter.exeは、現時点では、Storyboardに対応できていないので、Xcode6でデフォルトで使用されるMain.Storyboardは削除して、ラベルをコードで貼りました。
AppDelegate.h
#import <UIKit/UIKit.h> @class ViewController; @interface AppDelegate : UIResponder <UIApplicationDelegate> @property (strong, nonatomic) UIWindow *window; @property (strong, nonatomic) ViewController *viewController; @end
AppDelegate.m
・・・省略・・・ - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { // Override point for customization after application launch. self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]]; self.window.backgroundColor = [UIColor whiteColor]; self.viewController = [[ViewController alloc] init]; self.window.rootViewController = self.viewController; [self.window makeKeyAndVisible]; return YES; } ・・・省略・・・
ViewController.m
・・・省略・・・ - (void)viewDidLoad { [super viewDidLoad]; [self initLabel]; } - (void)initLabel{ CGRect rect = CGRectMake(60,100,200,30); UILabel *label = [[UILabel alloc] initWithFrame:rect]; label.text = @"Hello World!"; label .textAlignment = NSTextAlignmentCenter; label.textColor = [UIColor blackColor]; [self.view addSubview:label]; } ・・・省略・・・
3 Windows用プロジェクトの作成と実行
Xcodeで作成したプロジェクトを丸ごとWindows環境へ移して、vsimporter.exeを使用して、WindowsPhone用のプロジェクトを作成してみます。コピーした時点でのフォルダの状態は次のとおりです。
vsimporter.exeに-formatスイッチでWindowsPhoneを指定して実行した様子です。
変換後のフォルダの状態です。
生成されたslnファイルをダブルクリックしてVisualStudio2015を起動して、実行した様子です。
4 Windows Bridge for iOS について補足
Bridgeは、次の4つのコンポーネントでできています。1.Objective-C コンパイラ
Objective-Cのコードを、ネイティブなユニバーサルWindowsアプリにコンパイルします
2.Objective-C ランタイム
Objecttive-Cランタイムには、メッセージのディスパッチやデリゲーション、自動参照カウントなどが含まれています
3.iOS API ヘッダ/ライブラリ
基本APIについては、すでに提供されていますが、さらに改善するために意見を待っているとの事です
4.VisualStudio IDEの統合
Xcodeプロジェクトをインポートする
参考:Windows Bridge for iOS: Let’s open this up | Building Apps for Windows
ちなみに、このプロジェクトは、単に既存のObjecttiv-CのコードをWindowsでコンパイルする事だけが目標ではないそうです。
例えば、次のように、Objective-CにコードからWindowsのAPIもコールできるようです。
上記では、Windows.Foundation.UriやWindows.System.Launcherが利用されています。
参考:Windows Bridge for iOS: Let’s open this up | Building Apps for Windows
5 Windows Bridge for iOS の現状
現時点では、あくまで限定的なリリースで、完成度は、まだまだだと感じます。
今回の作業も、細いハッピーパスをくぐり抜けた感じが否定できません。
最終的なリリースは、今秋のVisualStudio2015のUpdateが予定との事です。
参考:Open Sourcing the Windows Bridge for iOS | Building Apps for Windows
なお、Androidの、テクニカルプレビューも一部の招待者で始まっており、8月末のパブリックベータが予定されているようです。
試してみたい方はサインアップしておくといいでしょう。
参考:Windows Bridge for Android - Windows アプリの開発
参考資料
Windows Bridge for iOS: Let’s open this up | Building Apps for WindowsOpen Sourcing the Windows Bridge for iOS | Building Apps for Windows
Windows Bridge for Android - Windows アプリの開発
Objective-CのコードがWindowsで動作する!「Windows Bridge for iOS」(WinObjcのサンプルをコンパイルしてみた)
Windows Bridge for iOS
5月のBuild2015のKeynoteでプレゼンされていた、Objective-CのコードがWindwos10で動くよ!という信じられないプロジェクトが遂に公開されました。
※当時「Project Islandwood」と呼ばれていたものは、「Windows Bridge for iOS」となったようです。
Windows Bridge for iOS - Windows app development
同プロジェクトは、オープンソースとしてGithubで公開されています。
WinObjcとは
Githubで公開されている、WinObjCとは、Windows(VisualStudio)上にObjective-Cの開発環境を提供するものです。
iOSのAPIとの互換性をサポートしています。
動作させるための要件は次のようになっています。
1 Windows10
2 VisualStudio2015(無償で利用可能な Visual Studio 2015 Communityでも大丈夫とアナウンスされています)
なお、
・言語としてVisual C++
・ユニバーサルアプリの開発ツール(すべて)
・Windows 8.1と Windows Phone 8.0/8.1 の開発ツール(すべて)
が必要です。
サンプルアプリ
プロジェクトにはサンプルが2つ入っていましたので、とりあえずこれをコンパイルしてみました。
https://github.com/Microsoft/WinObjC/releases/download/0.1-preview/winobjc.zip
HelloUI
次の図は、winobjc.zipの中のsampleフォルダの中にある「HelloUI」のWinStore10で実行したものです。※プロジェクトは、ストアアプリ(8.1、10)及びWindows Phone 8.1用となっています。
WOCCatalog
続いて同じくサンプルに含まれる「WOCCatalog」です。WindowsPhone用のプロジェクトをコンパイル・実行してみました。
こちらは、iOSの各種コントロールが、ブリッジされて置き換えられている様子がよく分かります。
iOSからの移植が前提なので、Xamarinと違って独自にコントロールを作成した感が滲みでています・・・特にPickerの力ずく感がっパねすw
これがネイティブなiOS利用者にどう映るのか・・・
参考リンク
Open Sourcing the Windows Bridge for iOS | Building Apps for WindowsMicrosoft/WinObjC · GitHub
ニュース - Microsoft、iOSアプリをWindows 10に移植するWindows Bridgeを早期公開:ITpro
SwiftyJSONをクラスオブジェクトで初期化する(Swiftにおけるリフレクション)
1 SwiftyJSON
SwiftyJSONは、Swiftで簡単にJSONを扱うライブラリとして、各所で紹介されており、機能も非常に充実しています。
しかし、何故か、JSON文字列をパースする処理ばかりで、その逆のJSON文字列へのシリアライズは見あたりませんでした。
つまり
// サンプルのクラス class Parson{ var name:String // 名前 var age:Int // 年齢 init(name:String,age:Int){ self.name = name self.age = age } } // クラスオブジェクトからJSONオブジェクトを生成する var json:JSON = JSON.Parse(Parson(name: "TARO", age: 25)) // JSONテキストを吐く println(json.rawString())
みたいに書いて、
{ "age" : 25, "name" : "TARO" }
と出力してほしいのです。
ちょっと、悩んでいたのですが・・・もしかすると、Swiftでは、リフレクションが正式に仕様化されていないようですので、それが理由なのかも知れません。
しかし、一応、リフレクションは可能なようで、いくつか紹介されている記事を見つけましたので、
今回は、踏ん張って、SwiftyJSONの拡張メソッドとして、JSON文字列へのシリアライズ(あくまで触りだけ)を書いてみます。
Swift初めて、約1ヶ月の超初心者で突っ込みどころ満載ですw
2 クラスオブジェクトのシリアライズ
とりあえず、最初のParsonクラスをシリアライズできるように書いてみました。
reflect() で、オブジェクトの名前や値、型などが取得できるので、個別にJSONオブジェクトを作成してディクショナリに追加しました。
extension JSON { static func Parse(data:Any) -> JSON{ var json:JSON=[:] let ref = reflect(data) for var i = 0; i < ref.count; ++i { let (key, mirror) = ref[i] //とりあえず、String,Int,Boolのみ対応 if let n = mirror.value as? String { json[key] = JSON(n) } else if let n = mirror.value as? Int{ json[key] = JSON(n) } else if let n = mirror.value as? Bool{ json[key] = JSON(n) } } return json } } // クラスのオブジェクトからJSONオブジェクトを生成する var json:JSON = JSON.Parse(Parson(name: "TARO", age: 25)) // JSONテキストを吐く textView.text = json.rawString() // 表示
なんとか動いているようです。
3 オプショナル型と階層構造への対応
続いて、先ほどのParsonクラスが、さらにParson型のプロパティを持つ場合への対応を書いてみます。
なお、これは、オプショナル型で定義し、nilの場合も処理できるようにしました。
クラスは、次のように変化します。
class Parson{ var name:String var age:Int var mother:Parson? // オプショナル型のParson(母親の情報) init(name:String,age:Int){ self.name = name self.age = age } }
そして、拡張メソッドは、こんな感じでしょうか
extension JSON { static func Parse(data:Any) -> JSON{ var json:JSON=[:] let ref = reflect(data) for var i = 0; i < ref.count; ++i { let (key, mirror) = ref[i] //nil検出(やや強引ですがAny型がnilと比較できなかったので・・・) if mirror.disposition == .Optional && "\(mirror.value)" == "nil" { json[key] = nil } else { //とりあえず、String,Int,Boolのみ対応 if let n = mirror.value as? String { json[key] = JSON(n) } else if let n = mirror.value as? Int{ json[key] = JSON(n) } else if let n = mirror.value as? Bool{ json[key] = JSON(n) } else { let subref = reflect(mirror.value) let (k, m) = subref[0] json[key] = Parse(m.value) // 再帰で同じメソッドにぶっ込む } } } return json } }
var taro = Parson(name: "TARO", age: 25) // motherプロパティは初期化していないのでnilになっている var json:JSON = JSON.Parse(taro) textView.text = json.rawString() // 表示
var taro = Parson(name: "TARO", age: 25) taro.mother = Parson(name:"HANAKO",age:50) // motherプロパティにParsonをセットする var json:JSON = JSON.Parse(taro) textView.text = json.rawString() // 表示
4 配列への対応
調子に乗って、配列への対応も挑戦してみます。
配列の検出は、クラス名から行いました。
extension JSON { static func Parse(data:Any) -> JSON{ var json:JSON=[:] let ref = reflect(data) // 配列検出 var str = _stdlib_getDemangledTypeName(data) if str.hasPrefix("Swift.Array") { if let range = str.rangeOfString(".") { // クラス名もこんな感じで取れるけど、とりあえず必要ないか・・・ //let separators = NSCharacterSet(charactersInString: "<.>") //var tmp = str.componentsSeparatedByCharactersInSet(separators) //var className = tmp[tmp.count-2] var ar:[JSON] = [] for var i = 0; i < ref.count; ++i { let (key, mirror) = ref[i] var j = Parse(mirror.value) ar.append(j) } json = JSON(ar) // 配列としてJSONに格納する } }else{ // 以下は、先ほどと変化なし for var i = 0; i < ref.count; ++i { let (key, mirror) = ref[i] //nil検出(やや強引ですがAny型がnilと比較できなかったので・・・) if mirror.disposition == .Optional && "\(mirror.value)" == "nil" { json[key] = nil } else { //とりあえず、String,Int,Boolのみ対応 if let n = mirror.value as? String { json[key] = JSON(n) } else if let n = mirror.value as? Int{ json[key] = JSON(n) } else if let n = mirror.value as? Bool{ json[key] = JSON(n) } else { let subref = reflect(mirror.value) let (k, m) = subref[0] json[key] = Parse(m.value) // 再帰 } } } } return json } }
Parsonクラスは、前回と同じです。
var ar:[Parson] = [] var taro = Parson(name: "TARO", age: 25) taro.mother = Parson(name:"HANAKO",age:50) ar.append(taro)// 配列の1件目 ar.append(Parson(name: "KEISUKE", age: 21))// 配列の2件目 var json:JSON = JSON.Parse(ar) textView.text = json.rawString() // 表示
5 まとめ
現時点で、Int、Bool、String以外、まったく対応できていません。
Enumは、挑戦してみたのですが、型が取れずに挫折しました。
かなり、力ずくで、かつ適当なので、実用には程遠いですが、僅かでもリフレクションを書く際の参考になれば幸いです。
6 参考にさせて頂いたリンク
SwiftyJSON/SwiftyJSON · GitHub
MirrorType - NSHipster
Simple Reflection in Swift
Swiftでリフレクションを使ってインスタンスからプロパティの一覧を取得する - 酢ろぐ!
Swift Reflection (Swift 的反射) - 猫·仁波切
Safx: Swiftでリフレクションを使ってみる
Swift1.2でクラス名が取得できなくなった - Qiita
Xamarin.FormsでAzureモバイルサービスによるToDoアプリを作成するには?
BuildInsiderで連載されている「Xamarin逆引きTips」に寄稿させて頂きました。
Xamarin.FormsでAzureモバイルサービスによるToDoアプリを作成するには? - Build Insider
使用したコードは、下記にあります。github.com
Xamarin.FormsでTwitterクライアントを作成するには?
BuildInsiderで連載されている「Xamarin逆引きTips」に寄稿させて頂きました。
Xamarin.FormsでTwitterクライアントを作成するには? - Build Insider
使用したコードは、下記にあります。github.com
Xamarin.Formsでローカルデータベースを使用するには?
BuildInsiderで連載されている「Xamarin逆引きTips」に寄稿させて頂きました。
使用したコードは、下記にあります。
Xamarin パケットモニターで見た、Build Hostとの通信
1.Build Host
Xamarin.VisualStudioでは、iOSのバイナリを作成するために、Mac(Xcode)が必須となりますが、この時、WindowsとMacの間の連携に使用されるのが「Build Host」です。しかし、色々な事情で、この「BuildHost」との通信が、途絶えてしまうことが(よく)あります。
よく出てくるこの画面です・・・
再起動したり、バージョンを変更したりしているうちに、いつの間に繋がる・・・という印象で、私としては、上手く繋がらないときの対処方法が、今ひとつハッキリしていませんでした。
そこで、今回は、トラブル対処時の情報の一つとなるように、WindowsとMac間の通信をパケットモニターで確認してみることにしました。
なお、本記事は、私が個人的に勝手に解釈したものであり、間違いを含んでいる可能性は十分にありますことを、予めご了承ください。
なお、パケットモニターには、Wiresharkを利用させて頂きました。
https://www.wireshark.org/
2.通信の種類
Build Hostとの通信には、大きく2種類があります。
一つは、「制御通信」で、もう一つは、「データ通信」です。
(この呼び方は、私が勝手に命名してしまってます)
「制御通信」は、リンクが開始してから終了するまで、ずっと繋がりっぱなしで、「データ通信」は、必要な情報を、Windows側からリクエストする毎に使用されています。
3.制御通信
Build Hostは、最初の接続口として、TCPの5000番ポートで待ち受けており、Pair開始からUnpairで終了するまで、ずっと繋がっています。
Wiresharkでは。ポート番号を頼りにして、次のようにフィルタすると簡単に確認できます。
tcp.port==5000
最悪、他にも5000番の通信がある場合、sourceとdestinationと加えて、こんな感じにすると見えてくると思います。
//Windowsが192.168.0.103、Macが192.168.0.102で動作している場合 tcp.port==5000 && (ip.addr==192.168.0.103 || ip.addr==192.168.0.102)
制御通信は、Windows側から、Macの5000番に対するTCPの3ウエイハンドシェークで始まっているのを確認できます。
表面的には、図の状態で「Connect」ボタンを押したときです。
そして、Buid Hostを止める(Unpairをクリックする)と、MacからのFINパケットで、この通信は終了しています。
これも、表面的には、図の状態で「Unpair」ボタンを押したときです。
下記は、同通信のTCPストリームを表示したもので、赤色が、Windowsからのもので青色がMacからのものです。
赤枠で囲んだ部分が、VisualStudioで最初にPINを入れるまで(リンク確率前)のものです。
PIN入力の完了後(リンク中)は、定期的にWindows側からKEEPALIVEが送られています。
4.データ通信
先の制御通信のMac側からの最初のレスポンスにある、「MTBSERVERPORTS:数字;数字」が、データ通信の際のMac側の待ち受けポート番号となります。
このポート番号は、制御通信ごとに変化(インクリメント)しています。
(いったん制御が切れると、次の制御開始時にポート番号は、変わっています)
事後、このポート番号を頼りに、データ通信のフィルタをすることになります。
(数値は、まずは最初のものが使用されているようで、2つ目のものが使用されている状況は、今のところ確認できていません。)
//ポート番号が49353の場合のフィルタの例 tcp.port==49353
データ通信は、HTTPプロトコルでWindowsからリクエストされ、Macからは、XML形式でレスポンスされます。
以下は、いくつかのデータ通信をTCPストリームで表示したものです。
・WindowsでPINを入力した時
Code="OK"でログインに成功している
・PINの整合がとれリンクが確立した後の最初の通信(情報取得?)
・シュミレータの一覧を取得している?
5.トラブル時の状態
(1) BuidlHostが息をしていない場合
なんらかの理由で、BuildHostが正常に動作していない場合、Windowsからのハンドシェークが失敗して、制御通信が始まらないことになります。図では、Windowsから5000番に対するSYNパケットに対して、MacがRSTを返しているのが確認できます。
(2) PINを間違えている場合
Code="Error"になっています。(3) Bonjour Serviceが息をしていない
Xamarin.VisualStudioとBuild Hostとの間には、Bonjour Serviceというサービスが、介在しています。擬似的に、このサービスを止めてみると・・・
5000番への通信は、一切行われなくなります。