Skip to content

Instantly share code, notes, and snippets.

@ykst
Created January 28, 2015 14:06
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ykst/e2123636203d5b26fd11 to your computer and use it in GitHub Desktop.
Save ykst/e2123636203d5b26fd11 to your computer and use it in GitHub Desktop.
Swiftで気になった所

Swiftで気になった所

ここ数日Swiftを触ってみた印象など、良くなることを願いつつ雑にメモしておきます。 Xcode6.1の頃の話です。

微妙だったところ

enumが再帰出来ない

代数的データ構造を彷彿とさせる魔改造を施されたenumですが、再帰的データ構造が出来そうで出来ないのでガッカリ感が有ります。

enum List { // (!) Recursive value type 'List' is not allowed
    case Car(AnyObject)
    case Cdr(List?)
}

正座して実装される日を待ちます。

参考: Algebraic Data Types in Swift

Genericを実体化したクラスを継承したクラスもGenericにしないといけない

サブクラスで特殊化しようとしても、何らかのGenericにしないとコンパイルが通りません。

class BaseClass<T> { }
// (!) Classes derived from generic classes must also be generic <- えっ
class SubClass : BaseClass<Int> { } 

typealiasで誤魔化したり、無意味な型パラメータを渡してスルーするなどのワークアラウンドが有りますが、いかんせんメタプログラマー涙目なので早めの改善が期待されます。

参考: http://stackoverflow.com/questions/24138359/limitation-with-classes-derived-from-generic-classes-in-swift

init?から抜けるまでに非Optionalなメンバを全て初期化しないといけない

Failable Initializerで早目にnilを返そうとしても非Optionalなメンバを初期化し終わるまでinit?を抜けられません。

class Foo {
    private var natural: Int

    init?(natural: Int) {
        // (!) All stored properties of a class instance must be initialized 
        // before returning nil from an initializer <- 何言ってんだこいつ
        if (natural < 1) { return nil } 

        self.natural = natural
    }
}

継承が絡むとsuper.initも呼ばないといけないので更にややこしくなります。 「失敗は素早く検出して抜けろ」というプラクティスに従ってすんなりnilを返させて欲しいところです。 implicitly unwrappedでワークアラウンド出来るとはいえ、どうやらこれはバグらしいので、改善を待ちましょう。

参考: http://stackoverflow.com/questions/26495586/best-practice-to-implement-a-failable-initializer-in-swift

initの分割が出来ない

Swiftのコンストラクターは、

1.Explicitなメンバを全て初期化 2.(継承していれば)super.initを呼ぶ 3.その他の処理(Failable initでnilを返すケースも含む)

という順番で記述するのですが、2が終わるまでselfもprivate methodも呼べないので初期化処理をサブルーチンに分割したり、リセット処理と共通化したりする事が出来ません。

class Foo {
    private var bar: Int

    private func reset() {
        bar = 0
    }

    init() {
        // (!) Variable 'self.bar' used before being initialized <- そんなぁ..
        reset() 
    }
}

一応これもimplicitly unwrappedで解決出来ますが、!が蔓延るとOptionalの恩恵がどんどん薄れていくので悲しくなります。 元々selfの存在が不確定な時点で自由にアクセス出来たObj-Cが変だったと言えばそうなのですが、そこに慣れていると結構面喰います。

IDEが安定していない

これは散々言われてる上に時間が解決してくれる話ですが、今はガリガリ書いてると大体1時間に1回位はクラッシュする気がします。 大抵は20秒位で復活しますが、たまにコード補完が死んだ時の復旧作業が発生するのはなかなか困り者です。 ステップ実行のソース位置がずれるぐらいはご愛嬌ですが、言語パラダイムとか移行コストとか以前にここで躓いてしまうチームも多そうなので、是非中の人には頑張って頂きたいです。

不便だが、気持ちは分かるところ

例外ハンドラが無い

Exceptionを無くす事自体は個人的には賛成なのですが、いかんせんObj-Cの低レベルAPIがハンドリングを要する例外(NSFileHandleとかNSFileHandleとか)を投げてくるパターンがあるので、完全に処置無しはきつい気がします。 そういう低レベルなコードは引き続きObj-Cで書いて、Swift自体は綺麗なままにしておくという方向性も一定有りではあります。

requiredがinitにしか使えない

virtualの代わりになるかと思われたrequiredですが、initにしか使えません。 引き続きprotocolで代替出来ると言われればそれまでですが、他のメソッドでも使えたら嬉しい気がしました。 ただ、そもそも目的が異なるのでこれは別にいい気がしてきました。

Closure式の最後の評価結果がreturnされるの中が単一式だと暗黙的にreturnされる

Closureを担当した人はおそらくRubyistだったのではないかと思いますが、Rubyと違ってreturnする値は型推論に影響を与えるので、()で返したいClosure式は最後にreturnを付けるなどする必要が有ります。

class A {
    func f(g: () -> ()) {
        g()
    } 

    func callF() {
        // これはダメ
        // (!) Type '()' does not conform to protocol 'IntegerLiteralConvertible'
        f { 1 } 
        // これはOK
        f { 1; return } 
    }

    // でもこれはダメ
    // (!) Missing return in a function expected to return 'Int'
    func h() -> Int { 1 }

    // これはOK    
    func h() -> () { 1 }
}

既にこの挙動に依存したコードも多くなってそうで取り返しは付かない気がしますが、そこだけreturnの意味論が特殊になってしまっている()を返す際の書き方が異なるので、一貫性を欠いた仕様に見えます。 とはいえ記述量を減らすには有効ですし、型推論のおかげで間違いが起こらないようにもなっているので、return一文でとやかく言うのも無粋かもしれません。

※ 一部訂正、ご指摘ありがとうございました。

protocolにoptionalが無い

@objcを付ければ使えるとは言え、出来ればクリアなSwiftでもoptionalが使いたいところです。 アプリケーションレイヤでは必要ないかもしれませんが、ライブラリではさすがに必要でしょう。 せっかく型にOptionalがあるので、ちょっと糖衣構文があっても良さそうな気もしますが。。

遅い

現実歪曲空間では高速に動作する等と揶揄されていますが、まだ少なくともゲームループの中に突っ込みたいとは思わないです。 Obj-Cは即座にC/asmにフォールバック出来たので速度に関して疑問を挟む余地がほぼ無かったのですが、Swiftに関してはまた探求していかないとですね。

参考: http://blog.sudeium.com/2014/12/10/swift-performance-too-slow-for-production/

でも全然いい

Rustに並び今後数年のハイブリッドパラダイム言語の流れを作ろうとしたその心意気や良し、です。 コード量はほぼ確実に減らせるので、言語を選べる状況ならば個人的には上からドメインレイヤまではもうデフォルトでSwiftで書いてしまって良いと思います。 まだまだ安定しているとは言い難いですが、Obj-CにしてもARCとBlocksが導入されるまでは大分辛かったですし、言語仕様が変わった時にXCodeが不安定になるのは前からなので、そうした頃を思えば現状は随分うまくやっていると思います。

ただ、ライブラリに関しては互換性や安定性の面で引き続きObj-Cで書いた方がベターだと思います。 何にせよBridging Headerに一行書けばObj-Cライブラリを取り込めるので、Cocoapods含めObj-C資産との共存はかなり楽です。

いくつかSwiftでリリースしてみたらまた印象が変わりそうですが、今のところはこんな感じでした。

@ykst
Copy link
Author

ykst commented Sep 27, 2015

この後で5万行程度のSwift+UIKitのアプリを出荷してみたところで得た知見としては、ここで挙げたようなメリットデメリットなどよりも、コンパイル時間の遅さ、コンパイラ最適化スイッチのバグ(exit 1地獄)、IDEの補完の遅さ等による"非機能的な"問題によるインパクトが大きかった。開発のスムーズさを犠牲にしてSwiftが型レベルで保証してくれる安全性はコンパイラのバグによって相殺されてしまった。
少なくともコード生成の対象としてSwiftを選ぶことは、万が一Obj-Cがdeprecateされない限り、今後とも無いだろう。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment