20190324のSwiftに関する記事は8件です。

try! Swift Pre Talks 2019 に参加しました

ディー・エヌ・エーで開催された try! Swift の前夜祭的なイベントについての報告です。
ほとんどの発表について資料を展開していただいているので、リンク集としてでもお役に立てば幸いです。

イベント概要

ディー・エヌ・エーで開催された、コミュニティ有志による try! Swift 関連イベント。
try! Swift Tokyo 2019 に応募されたCfP(Call for Paper)では見送りになってしまったものの、聞きたかったトークをコミュニティ有志がお願いして登壇してもらうもの。

イベント名

try! Swift Pre Talks 2019

Twitter ハッシュタグ

tryswift_pre

発表テーマ/登壇者

発表テーマ 登壇者
継続渡しと契約による設計 takasek
次世代の非同期処理はこうやって使い分ける! Yuta Abe
iOS SDKの歴史からSwiftでのAPI命名について考える Kento Nagata
Swift4.2で追加されたDynamic Member Lookupを使ってみよう Tamappe
あなたはどう書き、なぜそう書くのか? shiz
Further tests - testing basics ha1f
Xcodeの Debug Memory Graph を使った華麗なるデバッグ po_miyasaka
Swift における経過時間の計測 Yuto Mizutani
アプリの起動速度と Dynamic/Static Framework の関係を紐解く Shota Kashihara
try! swift-sh Satoshi Nagasaka
Implement P2P connection and stream your content Yuki Yamamoto
Swiftを利用したサーバレスアプリケーション開発の可能性 Yu Tawata
Developing Apple Pay In-App Provisioning With Sandbox App kenmaz

詳細

詳細は資料を見ていただくとして、簡単な概要と所感だけまとめさせていただきます。

はじめに

コミュニティを代表して、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との連携考慮など使い方がいろいろありそうです。

『あなたはどう書き、なぜそう書くのか?』

発表資料
stzn/Analytics

コードを改善していく中で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 向けの資料というのもうなずけるものだったと感じました。
正直なところ理解しきれていないものも多いので興味を持ったものから調べて習得していきたいと考えています。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Swift4のKeyPathを学ぶ

はじめに

KeyPathはSwift4から導入されました。
同じくSwift4から導入されたKVOのコードとともに見ることもあり、これは何だ?思った記憶があります。その時はKVOのクロージャ採用【Qiita記事参照】に気を取られていてあまり深く考えなかったのですが、今回 try! Swift 2019でKeyPathの発表1があったため、改めて資料を参考にしながらXcodeで動かしてみました。

環境

Xcode10.1
Swift4.2

KeyPathの定義

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にアクセスできます。

Swift
    func 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は下記のようにパスを構成することができます。これによって複雑な構造体の変数にアクセスすることもできます。

Swift
    func 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")
    }

上記のパスの連結はそもそも下記のように書けます。

Swift
        let firstNamePath2 = \User.name.first  // パスを記述する
        let firstName2 = player[keyPath: firstNamePath2]

KeyPathの応用

try! Swift 2019で取り上げられたKeyPathの手法に関しては正直メリットがそれほど伝わってきませんでした。(英語理解力不足もありますが)
しかしながら下記のようなKeyPathを使った拡張は有効だと思います。

Swift
enum 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でソートします。

Swift
        struct 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関数を用います。

Swift
        var people = [person1, person2]
        let newPeople = people.sort{ $0.name.first > $1.name.first }

KeyPathを使用した場合は下記のようになります。

Swift
        let people = [person1, person2]
        let newPeople = people.sorted(by: \User.name.first)

KeyPathを用いると
1)対象になる配列はletで良い
2)sorted()という関数になっている
3)上記関数の引数がKeyPathでより内容が明快
という違いがあります。

軽微な違いではありますが、より可読性が高いコードとなっていると思います。

参考

このような拡張を行うライブラリーとしてKeyPathKitがあります。
まだ使用してなくて感想は書けませんが、ソースコードを読む限りでは同じような方向を向いた使いやすいライブラリーのようです。


  1. try! Swift 2019で取り上げられた「Keypath入門」 

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

はじめまして。このような場を借りて恐縮ですが助けていただけたら幸いです。ひま部という学生限定アプリがあるのですが、だれでも通話という機能があります。

それは通話(音声のみ)とビデオタイプ(カメラオン)の2種類のタイプがあるのですが、ツイッターみたいな感じで時間順、人気順で投稿されます。しかし通話タイプとビデオタイプを区別する機能がなく、ビデオのみを見たい人にとっては探すのが面倒くさいです。そこで、アプリでもwebでもいいので区別して、そこから通話に参加するものをつくることはできるでしょうか?よろしくお願いいたします。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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 (型制約付きプロトコル) 型として保持して、そのメソッドを呼び出したことです。
SomeSubClassSomeProtocol に準拠しているため、そのインスタンスを SomeProtocol 型として扱ってもコンパイルは通ります。
しかし、インスタンス sp 自体は、 SuperClass を継承した型ではありません。そのため、 spdoSomething() に実行時にアクセスすると、型制約を満たしていないため、エラーとして落ちてしまいます。

実行時エラーの解決

上記の実行時エラーを解決するための方法は、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 は型制約を指定することができますが、実行時のエラーも考慮して使いましょう。

参考

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【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.swift
import 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接続してるのでそれもお忘れなく)

Simulator Screen Shot - iPhone 8 - 2019-03-24 at 13.11.45.png

そんなこんなでどのUIButtonでも、switchButtonクラスを指定してあげればいつでも一行でon/offスイッチが書けるようになりました。

他に良い方法あれば教えて頂けると嬉しいです!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[iOS]KeyPathを利用してRxSwiftベースのViewModelを刷新する

はじめに

RxSwiftを用いたMVVMでアプリケーションを開発する際、ViewModelが以下のような実装になりやすくはないでしょうか。

  • ①公開用の入力メソッドと、内部でsubscribeするためのRelayの2つを定義している
  • ②内部状態を公開するためのObservableと、値自体を公開するためのpropertyの2つを定義している
  • ③内部状態なのか、入力のRelayなのかが一見わかりにくい
上記の問題があるViewModel
final 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を用いて解決方法はちょうど良い利用事例になるのではないかなと思います。

③に関しては、定義する場所を明示することで解決しようと思います。

KeyPathを用いて実装するViewModel

UnioというOSSを利用することで、上記の問題を解決することができます。UnioとはUnidirectional Input Outputを示しており、InputからOutputまでの流れを単一方向にすることを目的としたframeworkです。まずは上記のSearchViewModelを、Unioを利用してInputとOutputの流れを単一方向にしたSearchViewStreamとして実装を置き換えます。

※Unioの内部実装については後ほど解説します。

Unioを利用した実装例

SearchViewStream.swift
protocol 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.swift
final 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は、以下のようにinputoutputのみになります。そして、それらはRelay型でラップされています。

スクリーンショット 2019-03-23 20.56.56.png

inputは、KeyPathで指定しているオブジェクトがPublishRelayだった場合に、acceptを実行しています。(PublishSubjectだった場合は、onEventを実行します)

input.png

そしてoutputでは、KeyPathで指定しているオブジェクトがObservableConvertibleTypeだった場合に、Observable<[Repository]>を返しています。
加えて、KeyPathで指定しているオブジェクトがBehaviorRelayだった場合は、valueとして[Repository]を返しています。(BehaviorSubjectだった場合は、throwableなメソッドでvalueを返します)

output.png

InputOutputのPropertyがinternalになっているが、単一方向は保証されているのか?

SearchViewStreamで定義されている、InputOutputを再度見てみましょう。

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.swift
open 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を利用して、InputOutputで定義されているPropertyの任意のメソッドを1階層飛び越えて呼び出すために、それらのPropertyはinternalで定義されています。

内部状態なのか、入力のRelayなのかがわかりやすくなったのか?

SearchViewStreamで、Input(入力)、State(内部状態)とExtra(その他依存)が定義されています。
定義自体が分かれているため、何を示しているものなのかはわかりやすくなりました。
それではOutputを生成する際に、それらがわかりやすい状態になっているのでしょうか?
Outputを生成しているLogicfunc 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とOutput
public protocol InputType {}
public protocol OutputType {}

PublishRelayとBehaviorRelayを表現するためのprotocolを定義します。
この2つは、KeyPathでpropertyにアクセスする際のGeneric Where Clauseで利用します。

PublishRelayとBehaviorRelay
public 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内ではInputOutputをもとに、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は、InputOutputStateExtraLogicが定義された状態になっています。加えて、ViewStreamTypeのprotocolも定義されるため、テストターゲットでMockも簡単に作成することが可能になっています。

ezgif.com-optimize.gif

Stream内で別のStreamを利用する

ExtraにpropertyとしてStreamを定義することで、Stream内で別なStreamを利用することができます。
例として、SearchViewStreamで利用していた、SearchAPIActionをStream化します。

SearchAPIStream
protocol 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:の部分がもともとの実装との差分となります。

SearchViewStream
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 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を定義します。

MockSearchAPIStream
final 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は、Logicfunc 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は、Logicfunc 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を利用することで改善し、①、②をまとめることができたことでInputOutputExtraを明示することができので③も改善できました。
それらはUnioを利用することで簡単に実装することができるので、是非試してみてください!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Windows 用 Swift コンパイラをビルドする

最近 Windows でも Swift コンパイラ, libdispatch, foundation がビルドできるようなり、簡単なプログラムを動かせるようになってきたのでその方法を紹介します。

環境構築

  • OS: Windows 10 1809
  • PC: 速いやつ
    • ビルドには結構時間がかかります。下のスペックのマシンで 1 時間以上かかります。
      • Core i7 8700K (6コア, 12 スレッド, 全コア 4.3GHz にオーバークロック), メモリ 48GB, SATA SSD
  • ストレージの空き: 40GB

開発者モードを有効にする

「Windows の設定」から「更新とセキュリティ」の「開発者向け」を開き、「開発者モード」を有効にします。
devmode.PNG

Visual Studio をインストールする

https://visualstudio.microsoft.com/vs/community/ から Visual Studio 2017 Community Edition をダウンロードしてインストールします。インストール時のオプションは「C++ によるデスクトップ開発」と「ユニバーサル Windows プラットフォーム開発」を有効にしてください。
vsinstall.PNG

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」を有効にしてください。
python.PNG
インストールしたら一度再起動します。

ソースコードのチェックアウト

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

ビルド

シンボリックリンクの作成

ビルド中に必要になるシンボリックリンクを作成します。この操作には管理者権限が必要になります。コマンドプロンプトを右クリックして管理者権限で開いてください。
super.PNG

ここで次のコマンドを実行します。

"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
popd

libxml2

pushd "%SWIFT%\libxml2\win32"
cscript configure.js iconv=no
nmake /f Makefile.msvc
popd

core-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" install

Swift プログラムのコンパイル

では 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

そうすればコンパイルできて次のように実行できるはずです!

run.PNG

参考

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

storyboardをうっかり削除したとき復活させる方法

こんなときに使える

・xcode内でstoryboardの部品を消そうとしたら、うっかりファイルごと消してしまったとき
・うっかりGitにもあげてなかったとき

順序

・xcode左側のアプリ名の上で右クリック
・上から8番目のAdd Files to ”ファイル名”・・・をクリック
・Base.Iprojフォルダを開き、Main.storyboardを選び、Add

参考

https://teratail.com/questions/62934

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む