- 投稿日:2019-03-24T23:41:25+09:00
try! Swift Pre Talks 2019 に参加しました
ディー・エヌ・エーで開催された try! Swift の前夜祭的なイベントについての報告です。
ほとんどの発表について資料を展開していただいているので、リンク集としてでもお役に立てば幸いです。イベント概要
ディー・エヌ・エーで開催された、コミュニティ有志による try! Swift 関連イベント。
try! Swift Tokyo 2019 に応募されたCfP(Call for Paper)では見送りになってしまったものの、聞きたかったトークをコミュニティ有志がお願いして登壇してもらうもの。イベント名
Twitter ハッシュタグ
発表テーマ/登壇者
詳細
詳細は資料を見ていただくとして、簡単な概要と所感だけまとめさせていただきます。
はじめに
コミュニティを代表して、d_dateさんによるイベントの紹介。
スポンサートーク(DeNA)
勉強会に開催、スポンサーも積極的にやっていて、iOSDCに11名、try!swiftに4名の登壇者を出しているとのこと。
今年のtry!swiftではLLDBのチートシートを配ってくれるようです。スポンサートーク(twilio)
英語での会社の紹介に続けて英語でのデモンストレーションをしていました。
サーバー上のSwiftのコードにより、電話のコールをアプリに中継していたっぽいです。
コーディングから動作確認まで実演してくれました。『継続渡しと契約による設計』
iOSアプリ設計パターン入門の著者の一人のtakasekさん。
ざっとまとめると、継続渡しというのは関数の引数に処理を渡すことで、処理を渡された関数の実装の不備をコンパイルで気づけないので、非同期処理の続きの処理をクロージャで渡さずに(=継続渡しせずに)Rxなどを使うことでコンパイル時にミスに気付けるようにしよう、という感じです。継続渡しという単語を知らず、資料にある失敗例も見に覚えがあったので、参考にしようと思いました。
『次世代の非同期処理はこうやって使い分ける!』
最近は皆Rxを使って非同期処理を簡単にかけるようになったが、果たして本当に簡単なのか、という流れから、Swift5で導入されるasync/waitの紹介へ。async/waitはPromise、RxはStreamと考えられ、単純な非同期処理ならStreamではなくPromiseで十分ではということでした。
確かに、RxのSingleで書いていたところはasync/waitにするとわかりやすくなりそうですね。
『iOS SDKの歴史からSwiftでのAPI命名について考える』
API命名についての参考として、SwiftのAPI Design Guidelinesは非常に重要、かつ実際の参考としてiOS SDKやCocoaが参考になり、最近のiOS SDKのアップデートでもガイドラインに沿ってAPI名が変更されているという紹介。
命名規則などもあったりしますが、Swiftらしい命名という観点は忘れがちなので、たまにガイドラインやSDKのコードを読まないと。
『Swift4.2で追加されたDynamic Member Lookupを使ってみよう』
タイトルの通りDynamic Member Lookupの紹介ですがこれも知らなかったです。
コンパイル時に存在しないプロパティに対してドットでアクセスできるというもので、プロトコル拡張やPythonとの連携考慮など使い方がいろいろありそうです。『あなたはどう書き、なぜそう書くのか?』
コードを改善していく中でenum,struct,protocolなど役割を考えて使っていきましょう、ということでしょうか。
サンプルも共有していただいているので参考にさせていただければと。『Further tests - testing basics』
うろ覚えなのであまり参考にならないかもしれませんが、タイトルに有る通りテストコードのノウハウについてです。
Qiitaにも書いているというのはこちらと思われます。『Xcodeの Debug Memory Graph を使った華麗なるデバッグ』
Memory Graph でデバッグするためのLLDBコマンドvinfoを作ったという紹介。
Memory Graph に表示される任意のオブジェクトのプロパティやメソッドにアクセスできるようになるというもので、コードを編集せずにいつでも呼び出し可能で、View Hierarchy でも使えるスグレモノです。
発表ではデモもあり、時間的な制約からスピード感もありかなり盛り上がっていました。試しに使ってみましたが、実際にbackgroundColorを変更できたりして楽しいです。
デバッグ時の解析の手法の一つとして使わせていただきます。『Swift における経過時間の計測』
研究者の方ということで時間計測がテーマ。
反応時間計測とタイマー処理についての実装と遅延の関係とヒトの認識について。
タッチの検出レートというのはあまり意識したことがなかったですが(単純に処理の遅延くらいとしか)、精度が求められる計測ではハードウェアレベルでの遅延の考慮が必要ですね。『アプリの起動速度と Dynamic/Static Framework の関係を紐解く』
XcodeのでFrameworkの扱いの歴史に始まり、DynamicなものとStaticなものについてのg特徴や使い方をわかりやすくまとめてくれています。
環境変数「DYLD_PRINT_STATISTICS」をセットすることで起動時の各ロード時間が表示されることや複数のDynamic FrameworksをStatic LibraryにまとめてDyldのオーバーヘッドを少なくできるというのは興味深かったです。
『try! swift-sh』
Swiftでスクリプト書いてますか?という問いかけに始まり、swift-shの紹介をしてくれました。
まだまだiOS/macOSアプリ開発向けの言語としての認識がほとんどなSwiftですが、スクリプトもSwiftでかけるのならその方が便利ですし楽しいですね。
『Implement P2P connection and stream your content』
ストリーミング市場の話とMultipeer Connectivityを使ったP2Pの実装、Google Castに関する発表でした。
最近はこの辺りの情報には疎いですが、かなり昔は組み込みでレコーダーを作っていたのでちょっと興味がわきました。
『Swiftを利用したサーバレスアプリケーション開発の可能性』
AWSのDynamoDB・LambdaとSwift5の組み合わせでサーバーレスなシステムを構築するという話ですね。
UbuntuにSwift5を入れたDockerコンテナを呼び出すようです。理解しきれてはいませんが、新たな可能性を見たような気がしました。
『Developing Apple Pay In-App Provisioning With Sandbox App』
Apple Pay In-App プログラミングとSandboxアプリでの開発について。
発表したのはメルカリの方で、Sandboxアプリというのはメルカリの個々の機能を単独で動くフレームワークに分割し、それぞれのフレームワークだけを組み込んだミニマムなアプリということのようです。
すべてをビルドしてテストするよりも効率がよく、TestFlightにアップロードするにもバイナリが小さい方が処理が早くて待ちが少ないのだとか。Sandboxアプリが作れるようなアーキテクチャになっている事によるメリットは他にもありそうでもっといろいろ聞いてみたいところ。
おわりに
まさにライトニングトークというべきか、勢いのある発表が多く、try!Swift 向けの資料というのもうなずけるものだったと感じました。
正直なところ理解しきれていないものも多いので興味を持ったものから調べて習得していきたいと考えています。
- 投稿日:2019-03-24T23:30:18+09:00
Swift4のKeyPathを学ぶ
はじめに
KeyPathはSwift4から導入されました。
同じくSwift4から導入されたKVOのコードとともに見ることもあり、これは何だ?思った記憶があります。その時はKVOのクロージャ採用【Qiita記事参照】に気を取られていてあまり深く考えなかったのですが、今回 try! Swift 2019でKeyPathの発表1があったため、改めて資料を参考にしながらXcodeで動かしてみました。環境
Xcode10.1
Swift4.2KeyPathの定義
KeyPathの公式ドキュメントはこちらです。
https://developer.apple.com/documentation/swift/swift_standard_library/key-path_expressions
https://developer.apple.com/documentation/swift/keypathこれによるとKeyPathには、いくつかの種類があります。
Class Declaration KeyPath class KeyPath<Root, Value> : PartialKeyPath<Root>(リードオンリー) PartialKeyPath class PartialKeyPath<Root> : AnyKeyPath(リードオンリー)KeyPath のsuper class AnyKeyPath class AnyKeyPath(リードオンリー)PartialKeyPath のsuper class WritableKeyPath class WritableKeyPath<Root, Value> : KeyPath<Root, Value>値の読み書きをサポートするキーパス。 ReferenceWritableKeyPath class ReferenceWritableKeyPath<Root, Value> : WritableKeyPath<Root, Value>参照セマンティクスを使用して値の読み書きをサポートするキーパス。 下記の(1)ようにKeyPathを記述するとUserのnameにアクセスできます。
Swiftfunc testKeyPathBasic2() { struct User { var name: String } var player = User(name: "Lupin") let namePath = \User.name // <--- (1) print(String(describing: type(of: namePath))) // WritableKeyPath<User, String> player[keyPath: namePath] = "Goemon" // <--- (2) XCTAssertTrue(player.name == "Goemon") }ここで(1)のnamePathの型を見ると
KeyPath<User, String>ではなく、WritableKeyPath<User, String>であることが分かります。
KeyPathはリードオンリーのため、WritableKeyPathにしないと(2)でエラーになるからです。KeyPath Composition
KeyPathは下記のようにパスを構成することができます。これによって複雑な構造体の変数にアクセスすることもできます。
Swiftfunc testKeyPathBasic3() { struct User { var name: Name } struct Name { var first: String var last: String } let player = User(name: Name(first: "Lupin", last: "the 3rd" )) let namePath = \User.name let firstPath = \Name.first let firstNamePath = namePath.appending(path: firstPath) // ここ let firstName = player[keyPath: firstNamePath] XCTAssertTrue(firstName == "Lupin") }上記のパスの連結はそもそも下記のように書けます。
Swiftlet firstNamePath2 = \User.name.first // パスを記述する let firstName2 = player[keyPath: firstNamePath2]KeyPathの応用
try! Swift 2019で取り上げられたKeyPathの手法に関しては正直メリットがそれほど伝わってきませんでした。(英語理解力不足もありますが)
しかしながら下記のようなKeyPathを使った拡張は有効だと思います。Swiftenum SortOrder { case ascending case descending } extension Sequence { func sorted<T: Comparable>(by keyPath: KeyPath<Element, T>, order: SortOrder = .ascending) -> [Element] { return sorted { a, b in switch order { case .ascending: return a[keyPath: keyPath] < b[keyPath: keyPath] case .descending: return a[keyPath: keyPath] > b[keyPath: keyPath] } } } }このsortedメソッドは、Comparableな型の変数のKeyPathでソートします。
Swiftstruct User { var name: Name } struct Name { var first: String var last: String } let person1 = User(name: Name(first: "Lupin", last: "the 3rd" )) let person2 = User(name: Name(first: "Goemon", last: "the 13th" ))User、Nameの変数でソートする場合は、通常は下記のようにsort関数を用います。
Swiftvar people = [person1, person2] let newPeople = people.sort{ $0.name.first > $1.name.first }KeyPathを使用した場合は下記のようになります。
Swiftlet people = [person1, person2] let newPeople = people.sorted(by: \User.name.first)KeyPathを用いると
1)対象になる配列はletで良い
2)sorted()という関数になっている
3)上記関数の引数がKeyPathでより内容が明快
という違いがあります。軽微な違いではありますが、より可読性が高いコードとなっていると思います。
参考
このような拡張を行うライブラリーとしてKeyPathKitがあります。
まだ使用してなくて感想は書けませんが、ソースコードを読む限りでは同じような方向を向いた使いやすいライブラリーのようです。
- 投稿日:2019-03-24T22:27:22+09:00
はじめまして。このような場を借りて恐縮ですが助けていただけたら幸いです。ひま部という学生限定アプリがあるのですが、だれでも通話という機能があります。
それは通話(音声のみ)とビデオタイプ(カメラオン)の2種類のタイプがあるのですが、ツイッターみたいな感じで時間順、人気順で投稿されます。しかし通話タイプとビデオタイプを区別する機能がなく、ビデオのみを見たい人にとっては探すのが面倒くさいです。そこで、アプリでもwebでもいいので区別して、そこから通話に参加するものをつくることはできるでしょうか?よろしくお願いいたします。
- 投稿日:2019-03-24T21:33:57+09:00
Swift の型制約付きプロトコルの実行時エラー
先日、自分が発見した型制約付きプロトコルの実行時エラーの説明とその解消法について紹介します。
型制約付きプロトコル
Swift の Protocol は、宣言時に
whereで準拠する型に制約を指定することができます。
準拠する型が制約を満たしていないと、コンパイラが、コンパイル時にエラーとして検知してくれます。型制約付きプロトコルの例class SuperClass { } // 型制約付きプロトコル protocol SomeProtocol where Self: SuperClass { // 準拠する型は SuperClass を継承するように制約を付ける func doSomething() } class SomeSubClass: SuperClass, SomeProtocol { func doSomething() { print("doSomething") } } class InvalidSomeSubClass: SomeProtocol { // コンパイルエラー: 'SomeProtocol' requires that 'InvalidSomeClass' inherit from 'SuperClass' func doSomething() { print("doSomething") } }型制約付きプロトコルによる実行時エラー
型制約付きプロトコルは、準拠する型に制約を付け、コンパイル時にその制約を検知できる便利な機能です。
しかし、型制約付きプロトコルゆえの実行時エラーも存在します。実行時エラーの例class SuperClass { } protocol SomeProtocol where Self: SuperClass { func doSomething() } class SomeSubClass: SuperClass, SomeProtocol { func doSomething() { print("doSomething") } } let sp: SomeProtocol = SomeSubClass() // SomeProtocol 型として保持する sp.doSomething() // EXC_BAD_ACCESS で落ちるエラーの原因は、
SomeSubClassのインスタンスをSomeProtocol(型制約付きプロトコル) 型として保持して、そのメソッドを呼び出したことです。
SomeSubClassはSomeProtocolに準拠しているため、そのインスタンスをSomeProtocol型として扱ってもコンパイルは通ります。
しかし、インスタンスsp自体は、SuperClassを継承した型ではありません。そのため、spがdoSomething()に実行時にアクセスすると、型制約を満たしていないため、エラーとして落ちてしまいます。実行時エラーの解決
上記の実行時エラーを解決するための方法は、2通りあります。
1. インスタンスを型制約の型として実行する
let sp: SuperClass & SomeProtocol = SomeSubClass() // SuperClass & SomeProtocol 型として保持する sp.doSomething() // "doSomething"インスタンス
spが実行時に型制約を満たしていないことがエラーとなっていたため、spを型制約を満たすような型として実行すれば解決します。2. 型制約付きプロトコルに
@objc属性を適用する@objc protocol SomeProtocol where Self: SuperClass { // @objc を適用 func doSomething() } ... let sp: SomeProtocol = SomeSubClass() // SomeProtocol 型として保持する sp.doSomething() // "doSomething"なぜ
@objcの適用によって実行時エラーを回避するか理解はできていませんが、解決法として紹介されていました。
https://bugs.swift.org/browse/SR-7068まとめ
エラー文が
EXC_BAD_ACCESSとしか表示されず、原因を突き止めるのに時間がかかりました。Swift の Protocol は型制約を指定することができますが、実行時のエラーも考慮して使いましょう。参考
- 投稿日:2019-03-24T13:13:09+09:00
【iOS】UIButtonにON/OFFのスイッチ処理を1行で書く【Swift】
UIButtonに「on/off」のスイッチを付与したい...
けどいちいちフラグ立てるのはスマートじゃない...
それに複数のUIButtonにon/offのスイッチを持たせるとなったらアーッ
ってことで、UIButtonにon/offのスイッチを持たせたい時に、簡単一行でon/offのスイッチを実装するコードを書いたので(誰でもやってるかもしれないけど)晒したいと思います。
開発環境
- Swift4.2
- Xcode10.1
目的
UIButtonにON/OFFのスイッチ機能を持たせたい時に、下のように書くのはスマートじゃない。
ViewController.swift@IBOutlet var label: UILabel! var flag: Bool = false @IBAction func tappedButton(_ sender: UIButton) { if flag == false { //ONにする時に走らせたい処理 label.text = "ONになったよ!" flag = true } else if flag == true { //OFFにする時に走らせたい処理 label.text = "OFFになったよ!" flag = false } }ボタン1個だけなら良いかもしれないが、何個ものUIButtonにスイッチ機能を持たせたい時は全部に同じコードを書く羽目になる。
あと誰でも考えつきそう
UIButton.switch(on: , off: )みたいな感じで一行で書きたい(切実)解決法:新しいクラスを作る
そんなわけで解決法を「Extensionでなんとか実装できないかなー...」と思って頑張ってみたんですがダメでした()
どうしてもUIButtonのインスタンスに状態を保持するプロパティを持たせる必要があったので、インスタンスプロパティを追加することができないExtensionでは実装は難しそうです。Extensionで出来る方法あればぜひ教えてください(懇願)
というわけでUIButtonにON/OFFのスイッチ機能を持たせるには、「UIButtonを継承した新しいクラスを作る」のが良さそうです。
今回は「switchButton」というクラスを作ってます。
ViewController.swiftimport UIKit class ViewController: UIViewController { @IBOutlet var label: UILabel! override func viewDidLoad() { super.viewDidLoad() } @IBAction func tappedButton(_ sender: switchButton) { //一行でon/offスイッチが書ける!!すごい!! sender.switchAction(initState: .on, onAction: { label.text = "ONになったよ!" }) { label.text = "OFFになったよ!" } } } class switchButton: UIButton { //on/offの状態を保持する private var currentState: Bool? func switchAction(initState: Switch, onAction: ()->Void, offAction: ()->Void) { //initstateの値をcurrentStateに設定 if currentState == nil { currentState = initState.state } let currentSwitch:Bool = currentState! switch currentSwitch { case true: //ボタンをonからoffにする時にcurrentStateをoffにする→offActionを実行 currentState = Switch.off.state offAction() case false: //ボタンをoffからonにする時にcurrentStateをonにする→onActionを実行 currentState = Switch.on.state onAction() default: break } } //ボタンのon/offを直感的にするためenumで値を設定(無くてもよし) enum Switch { //stateの種類 case on,off //stateにboolを振り分け var state: Bool { switch self { case .on: return true case .off: return false default: break } } } }これをそのままコピペして、Storyboardで@IBAction接続しているUIButtonのクラスにswitchButtonを指定してあげれば動きます。(あとテスト用のlabelも@IBOutlet接続してるのでそれもお忘れなく)
そんなこんなでどのUIButtonでも、switchButtonクラスを指定してあげればいつでも一行でon/offスイッチが書けるようになりました。
他に良い方法あれば教えて頂けると嬉しいです!
- 投稿日:2019-03-24T12:04:16+09:00
[iOS]KeyPathを利用してRxSwiftベースのViewModelを刷新する
はじめに
RxSwiftを用いたMVVMでアプリケーションを開発する際、ViewModelが以下のような実装になりやすくはないでしょうか。
- ①公開用の入力メソッドと、内部でsubscribeするためのRelayの2つを定義している
- ②内部状態を公開するためのObservableと、値自体を公開するためのpropertyの2つを定義している
- ③内部状態なのか、入力のRelayなのかが一見わかりにくい
上記の問題があるViewModelfinal class SearchViewModel { let repositories: Observable<[Repository]> let error: Observable<Error> var repositoriesValue: [Repository] { return _repositories.value } private let _repositories = BehaviorRelay<[Repository]>(value: []) private let _search = PublishRelay<String>() private let disposeBag = DisposeBag() init() { let apiAciton = SearchAPIAction() self.repositories = _repositories.asObservable() self.error = apiAction.error apiAction.response .bind(to: _repositories) .disposed(by: disposeBag) _search .subscribe(onNext: { apiAction.execute($0) }) .disposed(by: disposeBag) } func search(query: String) { _search.accept(query) } }①、②については、Swift4から利用できるようになったKeyPathを用いて解決します。
try! Swift Tokyo 2019にてKeypath入門のトークもあったので、KeyPathを用いて解決方法はちょうど良い利用事例になるのではないかなと思います。I just published my slides for my @tryswiftconf talk about #keypaths ?? in #swift.
— Benedikt Terhechte (@terhechte) March 21, 2019
You can find them here.https://t.co/UJXxsETAMv
For any questions, feel free to contact me here. #tryswiftconf③に関しては、定義する場所を明示することで解決しようと思います。
KeyPathを用いて実装するViewModel
UnioというOSSを利用することで、上記の問題を解決することができます。UnioとはUnidirectional Input Outputを示しており、InputからOutputまでの流れを単一方向にすることを目的としたframeworkです。まずは上記の
SearchViewModelを、Unioを利用してInputとOutputの流れを単一方向にしたSearchViewStreamとして実装を置き換えます。※Unioの内部実装については後ほど解説します。
Unioを利用した実装例
SearchViewStream.swiftprotocol SearchViewStreamType: AnyObject { var input: Relay<SearchViewStream.Input> { get } var output: Relay<SearchViewStream.Output> { get } } final class SearchViewStream: UnioStream<SearchViewStream.Logic>: SearchViewStreamType { struct Input: InputType { let search = PublishRelay<String>() } struct Output: OutputType { let repositories: BehaviorRelay<[Repository]> let error: Observable<Error> } struct State: StateType { let repositories = BehaviorRelay<[Repository]>(value: []) } struct Extra: ExtraType { let apiAction = SearchAPIAction() let disposeBag = DisposeBag() } struct Logic: LogicType { func bind(from dependency: Dependency<Input, State, Extra>) -> Output { let apiAction = dependency.extra.apiAction let disposeBag = dependency.extra.disposeBag let state = dependency.state apiAction.response .bind(to: state.repositories) .disposed(by: disposeBag) dependency.inputObservable(for: \.search) .subscribe(onNext: { apiAction.execute($0) }) .disposed(by: disposeBag) return Output(repositories: state.repositories, error: apiAction.error) } } init() { super.init(input: Input(), state: State(), extra: Extra(), logic: Logic()) } }上記の
SearchViewStreamの実装が
Inputに外部からの入力を定義Outputに外部への出力を定義Stateに内部状態を定義Extraに上記以外の依存を定義LogicがInput・State・ExtraをもとにOutputを生成という区切り方で、明示的になっていることがわかるかと思います。
SearchViewStreamを実際にViewControllerで利用すると以下のようになります。SearchViewController.swiftfinal class SearchViewController: UIViewController { let viewStream: SearchViewStreamType = SearchViewStream() let disposeBag = DisposeBag() override func viewDidLoad() { super.viewDidLoad() viewStream.input.accept("search-text", for: \.search) viewStream.output.observable(for: \.repositories) .subscribe() .disposed(by: disposeBag) print(viewStream.output.value(for: \.repositories)) } }まず、viewStreamで公開されているpropertyは、以下のように
inputとoutputのみになります。そして、それらはRelay型でラップされています。
inputは、KeyPathで指定しているオブジェクトがPublishRelayだった場合に、acceptを実行しています。(PublishSubjectだった場合は、onEventを実行します)そして
outputでは、KeyPathで指定しているオブジェクトがObservableConvertibleTypeだった場合に、Observable<[Repository]>を返しています。
加えて、KeyPathで指定しているオブジェクトがBehaviorRelayだった場合は、valueとして[Repository]を返しています。(BehaviorSubjectだった場合は、throwableなメソッドでvalueを返します)
InputとOutputのPropertyがinternalになっているが、単一方向は保証されているのか?
SearchViewStreamで定義されている、InputとOutputを再度見てみましょう。extension SearchViewStream { struct Input: InputType { let search = PublishRelay<String>() } struct Output: OutputType { let repositories: BehaviorRelay<[Repository]> let error: Observable<Error> } ... }それぞれで保持しているPropertyはinternalになっています。一見、PublishRelayやBehaviorRelayに直接アクセスできそうで、単一方向が保証されていないように見えます。
続いて、SearchViewStreamのsuper classであるUnioStreamの定義を見てみましょう。Unio.UnioStream.swiftopen class UnioStream<Logic> where Logic : LogicType { public let input: Unio.Relay<Logic.Input> public let output: Unio.Relay<Logic.Output> public init(input: Logic.Input, state: Logic.State, extra: Logic.Extra, logic: Logic) }UnioStreamでは
input: Relay<Input>とoutput: Relay<Output>が公開されています。InputやOutputが直接公開されているのではなく、Relay<T>にラップされています。
そのため、Relay<T>とKeyPathを介してInputやOutputに定義されているPropertyの任意のメソッドにアクセスすることはできますが、InputやOutputだったりそれらが保持しているPropertyに直接アクセスすることはできません。
つまり、ラップしたオブジェクトからKeyPathを利用して、InputやOutputで定義されているPropertyの任意のメソッドを1階層飛び越えて呼び出すために、それらのPropertyはinternalで定義されています。内部状態なのか、入力のRelayなのかがわかりやすくなったのか?
SearchViewStreamで、Input(入力)、State(内部状態)とExtra(その他依存)が定義されています。
定義自体が分かれているため、何を示しているものなのかはわかりやすくなりました。
それではOutputを生成する際に、それらがわかりやすい状態になっているのでしょうか?
Outputを生成しているLogicのfunc bind(from dependency: Dependency<Input, State, Extra>) -> Outputを見てみます。
(※func bind(from:) -> OutputはUnioStreamが初期化される際に一度だけ呼び出されます。)func bind(from dependency: Dependency<Input, State, Extra>) -> Output { let apiAction = dependency.extra.apiAction let disposeBag = dependency.extra.disposeBag let state = dependency.state apiAction.response .bind(to: state.repositories) .disposed(by: disposeBag) dependency.inputObservable(for: \.search) .subscribe(onNext: { apiAction.execute($0) }) .disposed(by: disposeBag) return Output(repositories: state.repositories, error: apiAction.error) }Outputを生成する際に必要なものは、
Dependency<Input, State, Extra>として引数で渡されます。
dependency.stateでアクセスができ、dependency.inputObservable(for:)からKeyPathを介してInputのPropertyからObservableを取得します。
このようにOutput生成時でも、内部状態や入力が明示的になっています。公開用のPropertyなどを2つ定義していた部分は不要になったのか?
こちらでも触れていますが、InputとOutputは
Relay<T>にラップされKeyPathを介すことでアクセスできるものが変わるようになっています。
そのため、公開用と内部用であったり、Observable用とValue用で定義を2つする必要はなくなっていて、PublishRelayやBehaviorRelayを1つずつ定義するだけで済むようになっています。
Unio内部でどのようにKeyPathを用いているのか
Unio内部の実装で、必要な部分だけを抜き出した類似コードを下記に記載しています。
そちらをもとに解説します。まず、InputとOutputを表現するprotocolを定義します。
この2つは、様々なオブジェクトのGeneric Where Clauseで利用します。InputとOutputpublic protocol InputType {} public protocol OutputType {}PublishRelayとBehaviorRelayを表現するためのprotocolを定義します。
この2つは、KeyPathでpropertyにアクセスする際のGeneric Where Clauseで利用します。PublishRelayとBehaviorRelaypublic protocol PublishRelayType { associatedtype E func accept(_ element: E) func asObservable() -> Observable<E> } public protocol BehaviorRelayType: PublishRelayType { var value: E { get } } extension PublishRelay: PublishRelayType {} extension BehaviorRelay: BehaviorRelayType {}そして、InputとOutputをラップしているRelay型です。
Generic ArgumentはTとなっていますが、initializerがprivateとなっているため、初期化が制限されます。public final class Relay<T> { private let dependency: T private init(dependency: T) { self.dependency = dependency } }RelayのGeneric Where ClauseがInputの場合、Inputが引数となるinitializerを公開します。
そして、KeyPath<Root, Value>のRootがInputでValueがPublishRelayである場合、dependency: TからKeyPathを介してacceptを実行できる実装をします。
dependencyの定義がprivateになっていても、RelayのメソッドのKeyPathのRootをGeneric ArgumentのTにすることで、階層が違っても内部では任意のpropertyの任意のメソッドなどにアクセスすることができるようになります。
ただ、それらにアクセスするためにはInputで定義しているproperty自体はinternalになっている必要があります。TがInputTypeの場合extension Relay where T: InputType { public convenience init(_ dependency: T) { self.init(dependency: dependency) } public func accept<U: PublishRelayType>(_ element: U.E, for keyPath: KeyPath<T, U>) { return dependency[keyPath: keyPath].accept(element) } }一方で、RelayのGeneric Where ClauseがOutputの場合、Outputが引数となるinitializerを公開します。
そして、KeyPath<Root, Value>のRootがOutputでValueがPublishRelayである場合、dependency: TからKeyPathを介してObservableを取得できる実装をします。
加えて、ValueがBehaviorRelayである場合、dependency: TからKeyPathを介してvalueを取得できる実装をします。TがOutputTypeの場合extension Relay where T: OutputType { public convenience init(_ dependency: T) { self.init(dependency: dependency) } public func observable<U: PublishRelayType>(for keyPath: KeyPath<T, U>) -> Observable<U.E> { return dependency[keyPath: keyPath].asObservable() } public func value<U: BehaviorRelayType>(for keyPath: KeyPath<T, U>) -> U.E { return dependency[keyPath: keyPath].value } }また、Outputを生成する際に必要となるDependencyはInputをラップし、KeyPathを利用してObservableを取得できる実装にします。
なぜRelayを使わ回さないかというと、RelayでObservableが利用できる場合はGeneric ArgumentがOutputであるはずです。
つまり、性質が逆になってしまうので単一方向を保てなくなってしまうので、別な型で表現しています。public final class Dependency<T: InputType> { private let input: T internal init(_ input: T) { self.input = input } public func inputObservable<U: PublishRelayType>(for keyPath: KeyPath<T, U>) -> Observable<U.E> { return input[keyPath: keyPath].asObservable() } }そして、UnioStream内では
InputとOutputをもとに、Relay<Input>とRelay<Output>を生成しています。open class UnioStream<Input: InputType, Output: OutputType> { public let input: Relay<Input> public let output: Relay<Output> public init(input: Input, output: (Dependency<Input>) -> Output) { self.input = Relay(input) let dependency = Dependency(input) self.output = Relay(output(dependency)) } }UnioStreamが公開している
Relay<Input>とRelay<Output>をもとに、ViewControllerではviewStream.inputに入力だけを行い、viewStream.outputから出力の受取だけを行えるようになっています。
これらが、KeyPathを利用してInput / Outputの流れをKeyPathを利用して保証している実装となります。※Playground上で動作確認ができるサンプルコードをGistで公開しています。
その他
ここまでに、
Unioが従来のViewModelとは何が違い、どういった動作をしているのかを説明してきました。
ここでは、開発でUnioを導入するための補足情報を紹介していきます。Xcode Templateを用いた自動生成
./Tools/install-xcode-template.shを実行することで、UnioのXcode Templateがインストールされます。
インストールが完了すると、ファイルの追加時に以下のテンプレートが選べるようになります。
Unio Componentsを選択すると、ViewControllerとViewStreamが生成されます。
生成されたViewControllerはViewStreamがPropertyとして定義された状態になっています。
そしてViewStreamは、Input、Output、State、ExtraとLogicが定義された状態になっています。加えて、ViewStreamTypeのprotocolも定義されるため、テストターゲットでMockも簡単に作成することが可能になっています。Stream内で別のStreamを利用する
ExtraにpropertyとしてStreamを定義することで、Stream内で別なStreamを利用することができます。
例として、SearchViewStreamで利用していた、SearchAPIActionをStream化します。SearchAPIStreamprotocol SearchAPIStream: AnyObject { var input: Relay<SearchStream.Input> { get } var output: Relay<SearchStream.Output> { get } } final class SearchAPIStream: UnioStream<SearchStream.Logic>: SearchAPIStream { typealias State = NoState typealias Extra = NoExtra struct Input: InputType { let search = PublishRelay<String>() } struct Output: OutputType { let repositories: Observable<[Repository]> let error: Observable<Error> } struct Logic: LogicType { func bind(from dependency: Dependency<Input, State, Extra>) -> Output { let response = dependency.inputObservable(for: \.search) .flatMapLatest { search -> Observable<Event<[Repository]>> // APIアクセスの実装 } .share() return Output(repositories: response.flatMap { $0.element.map(Observable.just) ?? .empty() } error: response.flatMap { $0.error.map(Observable.just) ?? .empty() }) } } init() { super.init(input: Input(), state: State(), extra: Extra(), logic: Logic()) } }Inputにsearch、Outputにrepositoriesとerrorが定義されています。
SearchViewStreamでは、それらにアクセスして処理を実行するようなります。
SearchViewStreamでSearchAPIStreamを利用した場合、// before:の部分がもともとの実装との差分となります。SearchViewStreamfinal class SearchViewStream: UnioStream<SearchViewStream.Logic>: SearchViewStreamType { struct Input: InputType { let search = PublishRelay<String>() } struct Output: OutputType { let repositories: BehaviorRelay<[Repository]> let error: Observable<Error> } struct State: StateType { let repositories = BehaviorRelay<[Repository]>(value: []) } struct Extra: ExtraType { let apiStream: SearchAPIStreamType // before: let apiAction = SearchAPIAction() let disposeBag = DisposeBag() } struct Logic: LogicType { func bind(from dependency: Dependency<Input, State, Extra>) -> Output { let apiStream = dependency.extra.apiStream // before: let apiAction = dependency.extra.apiAction let disposeBag = dependency.extra.disposeBag let state = dependency.state apiStream.output.observable(for: \.repositories) // before: apiAction.response .bind(to: state.repositories) .disposed(by: disposeBag) dependency.inputObservable(for: \.search) .bind(to: apiStream.input.accept(for: \.search)) // before: .subscribe(onNext: { apiAction.execute($0) }) .disposed(by: disposeBag) return Output(repositories: state.repositories, error: apiStream.output.observable(for: \.error)) // before: return Output(repositories: state.repositories, error: apiAction.error) } } init(searchAPIStream: SearchAPIStreamType = SearchAPIStream()) { let extra = Extra(apiStream: searchAPIStream) super.init(input: Input(), state: State(), extra: extra, logic: Logic()) // before: init() { // before: super.init(input: Input(), state: State(), extra: Extra(), logic: Logic()) } }Stream内で別のStreamを利用する場合でも、input経由で入力、output経由でObservableの取得を行っていることがわかると思います。
また、SearchViewStreamのinitializerでprotocol SearchAPIStreamTypeを受け取ることで、Streamをモック化してテストを容易に行うことができるようになります。UnioStreamのテスト方法
Streamをテストする場合、InputとOutputに注目してテストをすることになります。
SearchViewStreamを例に、テストを実装していきます。
まずSearchViewStreamは、SearchAPIStreamTypeに依存しているので、Mock化したSearchAPIStreamを定義します。MockSearchAPIStreamfinal class MockSearchAPIStream: SearchAPIStreamType { let input: Relay<SearchAPIStream.Input> let output: Relay<SearchAPIStream.Output> let _input = SearchAPIStream.Input() let _outputRepositories = BehaviorRelay<[Repository]?>(value: nil) let _outputError = BehaviorRelay<Error?>(value: nil) init() { self.input = Relay(_input) let _repositories = _outputRepositories.flatMap { $0.map(Observable.just) ?? .empty() } let _error = _outputError.flatMap { $0.map(Observable.just) ?? .empty() } let _output = SearchAPIStream.Output(repositories: _repositories, error: _error) self.output = Relay(_output) } }
input: Relay<SearchAPIStream.Input>とoutput: Relay<SearchAPIStream.Output>はそれぞれ公開されているメソッドが限定されているため、依存しているものをpropertyで定義し外部からそれらに変更を加えられるようにします。
それでは、実際のテストケースの実装を見ていきます。
func setUp()では、テストターゲットとなるSearchViewStreamを、依存してるMockSearchAPIStream (SearchAPIStreamType)とともに初期化しています。final class SearchViewtreamTests: XCTestCase { private var stream: SearchViewStream! private var mock: MockSearchAPIStream! override func setUp() { self.mock = MockSearchAPIStream() self.stream = SearchViewStream(searchAPIStream: mock) } }まず、入力のテストを見てみます。
SearchViewStreamの入力のsearchは、Logicのfunc bind(from:) -> Output内でSearchAPIStreamのsearchに接続されています。
つまり、SearchAPIStreamのsearchから結果を確認することができます。
下記のように実装することで、Inputのテストが可能となります。func testInput_search_is_called() { let expected = "test-search-text" let searchTextStack = BehaviorRelay<String?>(value: nil) let disposable = dependency.mock._input.search .bind(to: searchTextStack) stream.input.accept(expected, for: \.search) XCTAssertEqual(expected, searchTextStack.value) disposable.dispose() }次に、出力のテストを見てみます。
SearchViewStreamのoutputの出力のrepositoriesは、Logicのfunc bind(from:) -> Output内でSearchAPIStreamのrepositoriesから接続されています。
つまり、SearchAPIStreamのrepositoriesから変更を通知することで確認することができます。
下記のように実装することで、Outputのテストが可能となります。func testOutput_recieving_repositories() { let expected = [GitHub.Repository(...)] let repositoriesStack = BehaviorRelay<[Repository]?>(value: nil) let disposable = stream.output .observable(for: \.repositories) .bind(to: repositoriesStack) mock._outputRepositories.accept(expected) XCTAssertEqual(expected, repositoriesStack.value.first) disposable.dispose() }最後に
はじめにであげた①、②の冗長になる実装をKeyPathを利用することで改善し、①、②をまとめることができたことで
Input、Output、Extraを明示することができので③も改善できました。
それらはUnioを利用することで簡単に実装することができるので、是非試してみてください!
- 投稿日:2019-03-24T02:23:25+09:00
Windows 用 Swift コンパイラをビルドする
最近 Windows でも Swift コンパイラ, libdispatch, foundation がビルドできるようなり、簡単なプログラムを動かせるようになってきたのでその方法を紹介します。
環境構築
- OS: Windows 10 1809
- PC: 速いやつ
- ビルドには結構時間がかかります。下のスペックのマシンで 1 時間以上かかります。
- Core i7 8700K (6コア, 12 スレッド, 全コア 4.3GHz にオーバークロック), メモリ 48GB, SATA SSD
- ストレージの空き: 40GB
開発者モードを有効にする
「Windows の設定」から「更新とセキュリティ」の「開発者向け」を開き、「開発者モード」を有効にします。
Visual Studio をインストールする
https://visualstudio.microsoft.com/vs/community/ から Visual Studio 2017 Community Edition をダウンロードしてインストールします。インストール時のオプションは「C++ によるデスクトップ開発」と「ユニバーサル Windows プラットフォーム開発」を有効にしてください。
Git for Windows をインストールする
https://gitforwindows.org/ から Windows 用の Git をダウンロードしてインストールします。オプションは全てデフォルトのままにしてください。
Python をインストールする
https://www.python.org/downloads/windows/ からバージョン 2.7 系の「Windows x86-64 MSI installer」をダウンロードしてインストールします。インストール時のオプションは「Add python.exe to Path」を有効にしてください。
インストールしたら一度再起動します。ソースコードのチェックアウト
Swift や llvm、そのほかのライブラリのソースコードをチェックアウトします。ここでは全て C:\swift に置くことにします。コマンドプロンプトを開いて次のように入力してください
C: cd \ mkdir swift cd swift git clone https://github.com/apple/swift-cmark cmark git clone https://github.com/apple/swift-clang clang git clone https://github.com/apple/swift-llvm llvm git clone https://github.com/apple/swift-compiler-rt compiler-rt git clone https://github.com/apple/swift git clone https://github.com/apple/swift-corelibs-libdispatch git clone https://github.com/apple/swift-corelibs-foundation git clone https://github.com/apple/swift-corelibs-xctest git clone https://github.com/apple/swift-lldb lldb git clone https://github.com/curl/curl.git curl git clone https://gitlab.gnome.org/GNOME/libxml2.git libxml2 git clone https://compnerd@dev.azure.com/compnerd/windows-swift/_git/windows-swift powershell -Command "Invoke-WebRequest -Uri http://download.icu-project.org/files/icu4c/63.1/icu4c-63_1-Win64-MSVC2017.zip -Outfile %TEMP%\icu.zip; Expand-Archive -Path %TEMP%\icu.zip"チェックアウトしたタイミングによってはビルドできないことも多いので、初めは次のようにして私が試したコミットと合わせておくのが無難でしょう。
git -C clang checkout b1f4baeb56 git -C cmark checkout 32fa496 git -C compiler-rt checkout 166077175 git -C curl checkout 4331a3b8f git -C libxml2 checkout 8161b463 git -C lldb checkout e4b0ffcff6 git -C llvm checkout ab94816b593 git -C swift checkout 8fcd4e6ee3 git -C swift-corelibs-foundation checkout a733b6b5 git -C swift-corelibs-libdispatch checkout 9d485ca git -C swift-corelibs-xctest checkout 10140f4 git -C windows-swift checkout c45ba86ビルド
シンボリックリンクの作成
ビルド中に必要になるシンボリックリンクを作成します。この操作には管理者権限が必要になります。コマンドプロンプトを右クリックして管理者権限で開いてください。
ここで次のコマンドを実行します。
"C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\VC\Auxiliary\Build\vcvars64.bat" mklink "%UniversalCRTSdkDir%\Include\%UCRTVersion%\ucrt\module.modulemap" C:\swift\swift\stdlib\public\Platform\ucrt.modulemap mklink "%UniversalCRTSdkDir%\Include\%UCRTVersion%\um\module.modulemap" C:\swift\swift\stdlib\public\Platform\winsdk.modulemap mklink "%VCToolsInstallDir%\include\module.modulemap" C:\swift\swift\stdlib\public\Platform\visualc.modulemap mklink "%VCToolsInstallDir%\include\visualc.apinotes" C:\swift\swift\stdlib\public\Platform\visualc.apinotesこれ以降管理者権限のコマンドプロンプトは使用しないので閉じて構いません。
ビルド環境の設定
通常のコマンドプロンプトに戻りビルド中に必要になる環境変数を設定します。ビルドしたものインストール先は DIST により C:\swift\dist にしていますが、必要に応じて変更してください。
set SWIFT=C:\swift set BUILD=%SWIFT%\build set ICU=%SWIFT%\icu set DIST=%SWIFT%\dist set WINCMAKE=%SWIFT%\windows-swift\cmake\caches\Windows-x86_64.cmake set CNERDCMAKE=%SWIFT%\windows-swift\cmake\caches\org.compnerd.dt.cmake call "C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\VC\Auxiliary\Build\vcvars64.bat" call "C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\Common7\Tools\VsDevCmd.bat" -arch=amd64 set PATH=%ICU%\bin64;%BUILD%\llvm\bin;%BUILD%\foundation;%BUILD%\xctest;%PATH%LLVM
mkdir "%BUILD%\llvm" pushd "%BUILD%\llvm" cmake -G Ninja^ -DCMAKE_BUILD_TYPE=RelWithDebInfo^ -DCMAKE_C_COMPILER=cl^ -DCMAKE_CXX_COMPILER=cl^ -DCMAKE_INSTALL_PREFIX="%DIST%"^ -DLLVM_DEFAULT_TARGET_TRIPLE=x86_64-unknown-windows-msvc^ -DLLVM_ENABLE_ASSERTIONS=YES^ -DLLVM_ENABLE_PDB=YES^ -C "%WINCMAKE%"^ -C "%CNERDCMAKE%"^ %SWIFT%\llvm popd ninja -C "%BUILD%\llvm"cmark
mkdir "%BUILD%\cmark" pushd "%BUILD%\cmark" cmake -G Ninja^ -DCMAKE_BUILD_TYPE=RelWithDebInfo^ -DCMAKE_C_COMPILER=cl^ -DCMAKE_CXX_COMPILER=cl^ -DCMARK_TESTS=NO^ -C "%WINCMAKE%"^ "%SWIFT%\cmark" popd ninja -C "%BUILD%\cmark"swift
このプロセスは終盤、非常にメモリを使用します。メモリ不足でビルドが失敗する場合は ninja に -j 2 等と指定して同時実行数を減らしましょう。時間がかかるようになりますが、失敗はしなくなります。
mkdir "%BUILD%\swift" pushd "%BUILD%\swift" cmake -G Ninja^ -DCMAKE_BUILD_TYPE=RelWithDebInfo^ -DCMAKE_C_COMPILER=clang-cl^ -DCMAKE_CXX_COMPILER=clang-cl^ -DCMAKE_INSTALL_PREFIX="%DIST%"^ -DClang_DIR="%BUILD%\llvm\lib\cmake\clang"^ -DSWIFT_PATH_TO_CMARK_BUILD="%BUILD%\cmark"^ -DSWIFT_PATH_TO_CMARK_SOURCE="%SWIFT%\cmark"^ -DSWIFT_PATH_TO_LIBDISPATCH_SOURCE="%SWIFT%\swift-corelibs-libdispatch"^ -DLLVM_DIR="%BUILD%\llvm\lib\cmake\llvm"^ -DSWIFT_INCLUDE_DOCS=NO^ -DSWIFT_WINDOWS_x86_64_ICU_UC_INCLUDE="%ICU%/include"^ -DSWIFT_WINDOWS_x86_64_ICU_UC="%ICU%/lib64/icuuc.lib"^ -DSWIFT_WINDOWS_x86_64_ICU_I18N_INCLUDE="%ICU%/include"^ -DSWIFT_WINDOWS_x86_64_ICU_I18N="%ICU%/lib64/icuin.lib"^ -DSWIFT_BUILD_STATIC_STDLIB=NO^ -DSWIFT_BUILD_STATIC_SDK_OVERLAY=NO^ -DLLVM_INSTALL_TOOLCHAIN_ONLY=YES^ -DSWIFT_BUILD_SOURCEKIT=YES^ -DSWIFT_ENABLE_SOURCEKIT_TESTS=NO^ -DSWIFT_INSTALL_COMPONENTS="autolink-driver;compiler;clang-resource-dir-symlink;stdlib;sdk-overlay;editor-integration;tools;sourcekit-inproc;swift-remote-mirror;swift-remote-mirror-headers"^ -C "%WINCMAKE%"^ "%SWIFT%\swift" popd ninja -C "%BUILD%\swift"lldb
mkdir "%BUILD%\lldb" pushd "%BUILD%\lldb" cmake -G Ninja^ -DCMAKE_BUILD_TYPE=Release^ -DCMAKE_CXX_COMPILER=clang-cl^ -DCMAKE_C_COMPILER=clang-cl^ -DCMAKE_INSTALL_PREFIX="%DIST%"^ -DLLDB_PATH_TO_CLANG_BUILD="%BUILD%\llvm"^ -DLLDB_PATH_TO_LLVM_BUILD="%BUILD%\llvm"^ -DLLDB_PATH_TO_SWIFT_BUILD="%BUILD%\swift"^ -DLLDB_PATH_TO_SWIFT_SOURCE="%SWIFT%\swift"^ -DLLVM_ENABLE_ASSERTIONS=YES^ -C "%WINCMAKE%"^ "%SWIFT%\lldb" popd ninja -C "%BUILD%\lldb"libdispatch
mkdir "%BUILD%\libdispatch" pushd "%BUILD%\libdispatch" cmake -G Ninja^ -DCMAKE_BUILD_TYPE=Debug^ -DCMAKE_C_COMPILER=clang-cl^ -DCMAKE_CXX_COMPILER=clang-cl^ -DCMAKE_SWIFT_COMPILER="%BUILD%\swift\bin\swiftc.exe"^ -DCMAKE_INSTALL_PREFIX="%DIST%"^ -DBUILD_SHARED_LIBS=YES^ -DENABLE_TESTING=NO^ -DCMAKE_C_COMPILER_TARGET=x86_64-unknown-windows-msvc^ -DENABLE_SWIFT=YES^ -C "%WINCMAKE%"^ "%SWIFT%\swift-corelibs-libdispatch" popd ninja -C "%BUILD%\libdispatch"curl
pushd "%SWIFT%\curl" call buildconf.bat pushd winbuild nmake /f Makefile.vc mode=static VC=15 MACHINE=x64 popd popdlibxml2
pushd "%SWIFT%\libxml2\win32" cscript configure.js iconv=no nmake /f Makefile.msvc popdcore-foundation
途中、止まっているように見えますが辛抱強く待ちましょう。
mkdir "%BUILD%\foundation" pushd "%BUILD%\foundation" cmake -G Ninja^ -DCMAKE_BUILD_TYPE=Debug^ -DCMAKE_C_COMPILER=clang-cl^ -DCMAKE_SWIFT_COMPILER="%BUILD%\swift\bin\swiftc.exe"^ -DCURL_LIBRARY="%SWIFT%/curl/builds/libcurl-vc15-x64-release-static-ipv6-sspi-winssl/lib/libcurl_a.lib"^ -DCURL_INCLUDE_DIR="%SWIFT%/curl/builds/libcurl-vc15-x64-release-static-ipv6-sspi-winssl/include"^ -DENABLE_TESTING=NO^ -DICU_ROOT="%ICU%"^ -DLIBXML2_LIBRARY="%SWIFT%/libxml2/win32/bin.msvc/libxml2_a.lib"^ -DLIBXML2_INCLUDE_DIR="%SWIFT%/libxml2/include"^ -DFOUNDATION_PATH_TO_LIBDISPATCH_SOURCE="%SWIFT%\swift-corelibs-libdispatch"^ -DFOUNDATION_PATH_TO_LIBDISPATCH_BUILD="%BUILD%\libdispatch"^ -DCMAKE_INSTALL_PREFIX="%DIST%"^ -C "%WINCMAKE%"^ "%SWIFT%\swift-corelibs-foundation" popd ninja -C "%BUILD%\foundation"xctest
mkdir "%BUILD%\xctest" pushd "%BUILD%\xctest" cmake -G Ninja^ -DBUILD_SHARED_LIBS=YES^ -DCMAKE_BUILD_TYPE=Debug^ -DCMAKE_SWIFT_COMPILER="%BUILD%\swift\bin\swiftc.exe"^ -DXCTEST_PATH_TO_COREFOUNDATION_BUILD="%BUILD%\foundation\CoreFoundation-prefix"^ -DXCTEST_PATH_TO_FOUNDATION_BUILD="%BUILD%\foundation"^ -DXCTEST_PATH_TO_LIBDISPATCH_SOURCE="%SWIFT%\swift-corelibs-libdispatch"^ -DXCTEST_PATH_TO_LIBDISPATCH_BUILD="%BUILD%\libdispatch"^ -DLIT_COMMAND="%SWIFT%\llvm\utils\lit\lit.py"^ -DPYTHON_EXECUTABLE="C:\Python27\python.exe"^ -DCMAKE_INSTALL_PREFIX="%DIST%"^ -C "%WINCMAKE%"^ "%SWIFT%\swift-corelibs-xctest" popd ninja -C "%BUILD%\xctest"インストール
途中、Foundation がエラーを吐きますが、無視して構いません。
ninja -C "%BUILD%\llvm" install ninja -C "%BUILD%\swift" install ninja -C "%BUILD%\lldb" install ninja -C "%BUILD%\libdispatch" install ninja -C "%BUILD%\foundation" install ninja -C "%BUILD%\xctest" installSwift プログラムのコンパイル
では Swift プログラムをコンパイルしてみましょう。まずコンパイラとコンパイルしたプログラムの実行に必要なディレクトリにパスを通します。
set PATH=%DIST%\bin;%DIST%\lib\swift\windows;%PATH%こんなコードを用意しました。
// hello.swift import Foundation let f = DateFormatter() f.dateStyle = .full let now = Date() print("Hello Swift!") print("It's \(f.string(from: now))!")このコードをコンパイルするには次のオプションをつけてコンパイラを起動します。
swiftc -Xcc -DDEPLOYMENT_TARGET_WINDOWS^ -Xlinker /NODEFAULTLIB:libcmt^ -Xlinker /LIBPATH:C:\swift\build\foundation^ -Xlinker /LIBPATH:C:\swift\build\libdispatch^ -Xlinker /LIBPATH:C:\swift\build\libdispatch\src^ -Xlinker user32.lib^ -o hello.exe^ hello.swiftそうすればコンパイルできて次のように実行できるはずです!
参考

Unio








