Xcode 6.0とSwift 1.0で気になったところ
Xcode 6でSwiftを一週間ほどまじめに使ってみて、その間に気になった事のまとめ。細かすぎて伝わらなくても気にしない。
問題を確認した環境
環境 | 情報 |
---|---|
Xcode | 6.0.1 (6A317) |
Swift | 1.0 |
Date | 2014/09/25 |
条件式の『()』はオプションのはずだけど、付けるとエラーになるパターンが有る
Swiftの資料の説明はこんな感じ。
Use if and switch to make conditionals, and use for-in, for, while, and do-while to make loops. Parentheses around the condition or loop variable are optional. Braces around the body are required.
けど、『()』を付けるとコンパイルが通らなくなるパターンが有る。具体的に言うと、『if let』の形を使うパターンでコンパイルが通らなくなる。
コンパイル出来るパターン
var str: String? // コンパイル出来るパターン if let tmp = str { println("str: \(tmp)") }
コンパイル出来ないパターン
var str: String? // コンパイル出来ないパターン if( let tmp = str ) { println("str: \(tmp)") }
ちなみに、『()』の位置を調整しても駄目。
var str: String? // コンパイル出来ないパターン if let( tmp = str ) { println("str: \(tmp)") }
結論
C言語系の流れで、すべての条件式に『()』を付けようとしてるとハマる。そもそも言語が違うんだから、素直に、Swiftに馴染んだほうが良いかと。たぶん。
定数(?)がconvenience initから使えないのが不便
クラスのプロパティに、定数として使うつもりで初期値付きのletを定義しても、これを、 convenience initで使おうとするとエラーになるのが不便。
具体的にはこんな感じ。
class ConstTest { let DEFAULT_LEN = 2.0 // 定数として使いたい値 var length: Double init(length: Double) { self.length = length } convenience init() { self.init(length: DEFAULT_LEN) // ここで、コンパイルエラーになる } }
このケースだと、convenienceを外してその場で初期化するようにすれば対応できる。ただ、initの中で複雑な処理をやってる場合、いろいろと面倒なことに。
対応策
定数として使いたい値は、はっきり、定数にしてしまうのがわかりやすいかと。
class ConstTest { struct Const { static let len = 2.0 // 定数として使いたい値 } var length: Double init(length: Double) { self.length = length } convenience init() { self.init(length: Const.len) // 問題なくコンパイル可能 } }
Swiftでは普通のクラス定数(?)が使えないので、定数を管理するための構造体が必要になるのが面倒かな。複数のクラスで同じ値を使うのであれば、構造体を外に出せば良いかと。
『Plain Text』のファイルにペーストした時、先頭のtabが切り捨てられるようになった
『Plain Text』ってのはファイルの属性で指定するあれ。
定数の説明に使ったクラスを、Xcode 5.1でペーストした場合。
class ConstTest { struct Const { static let len = 2.0 // 定数として使いたい値 } var length: Double init(length: Double) { self.length = length } convenience init() { self.init(length: Const.len) // 問題なくコンパイル可能 } }
Xcode 6でペーストした場合。
class ConstTest { struct Const { static let len = 2.0 // 定数として使いたい値 } var length: Double init(length: Double) { self.length = length } convenience init() { self.init(length: Const.len) // 問題なくコンパイル可能 } }
対応策はこんな感じ。
- 諦める
先頭のtabが消えても気にしない。 - 『Plain Text』を使わないようにする
『Swift Source』に変更するとか。
そもそも、こんな事を気にする人間がどれぐらい居るのか・・・
『YES』と『NO』が無くなった
Objective-Cで多用されてた『YES』と『NO』が無くなった。
Objective-Cの『TRUE』と『FALSE』は、SwiftではBool型の『true』と『false』になる。『YES』と『NO』も同様に、『true』と『false』になるんだけど・・・ わかりにくい?
例えば、ViewControllerを表示する場合。Swiftで書くとこんな感じになる。
presentViewController(vc, animated: true, completion: nil)
Objective-Cみたいに『yes』が使えれば、こんな風に書けたはず。
presentViewController(vc, animated: yes, completion: nil)
では、どうするべきか?
- 諦める
すべて『true』と『false』で書く。違和感は諦める。慣れろ。 - グローバル変数で『yes』と『no』を定義する
これで『yes』と『no』が普通に使えるようになるけど、『no』なんて変数はローカル変数でもよく出てくるので、変数名の重複が問題になる。 - その他
重複しそうにない名前でグローバルに定義する? それはそれで違和感がありそう。
これは、諦めて慣れるのが正解かな・・・
遷移先のNavigation Itemが無くなった?
Navigation Controllerを使って複数のViewControllerをつなげる場合、Xcode 5.1までは2個目以降のViewControllerにもNavigation Itemが自動で追加されていたのが、Xcode 6では自動で追加されなくなった。
例えば、こんな風にViewControllerを接続した場合。
1個目のViewControllerにはNavigation Itemが自動で追加されてるが、2個めのViewControllerには無い。Xcode 5.1までは、2個めのViewControllerにもNavigation Itemが自動で追加されていた。
では、どうすればいいのか? とても単純な話で、自動で追加されないなら手動で追加すれば良い。具体的に言うと、Navigation Itemを自力でViewControllerに追加。
これで、先頭の画面と同じように、Navigation Itemの設定ができるようになる。
意味のないブロックが作れない
Objective-Cでは、特に意味が無いブロックでも問題なく作成できたので、変数のスコープを管理するのに便利だったんだけど、Swiftでは同じようなことができなくなってる。
具体的にはこんな感じ。
Objective-Cでは問題なし。
- (void)scopeTest { { NSString* str = @"Scope 1"; NSLog( @"Scope: %@", str ); } { NSString* str = @"Scope 2"; NSLog( @"Scope: %@", str ); } }
Swiftではコンパイルエラー。
func scopeTest() { { let str = "Scope 1" println("Scope: \(str)") } { let str = "Scope 2" println("Scope: \(str)") } }
これは、Swiftの言語仕様として、すべてのブロックがクロージャーとして扱われてるのが原因。たぶん。
では、どうするべきか? ・・・諦めるしかないかな。
クラス内extensionは使えない
具体的にはこんな感じ。
class ClassExt { struct Info { } extension Info { // ここでエラーになる } let info = Info() }
これが出来れば、クラスの上の方にプロパティや構造体の定義を集めて書いて、関数は下にまとめるって書き方が出来たんだけど・・・ 現時点ではコンパイルエラーに。
単純にコンパイルエラーになるのはもちろん、ソースを保存しただけでSourceKitServiceがクラッシュしたりするのでおすすめできない。
ちなみに、extensionをクラスの外に出せば普通に使用可能。
class ClassExt { struct Info { } let info = Info() } extension ClassExt.Info { }
class定義にプロパティや構造体の定義を集めて、あとはすべてextentionにするのは可能だけど・・・ これはこれで、わかりにくいかな。
enumの自動補完が甘い
自動補完して欲しいのに、してくれないパターンが有る。具体的にはこんな感じ。
enum ColorPattern { case Red case Blue case Green } func test(pat: ColorPattern) { if pat == . // ここで『.』を入力した瞬間に補完して欲しい }
enumの名称(この場合はColorPattern)を先に入力しておけば自動補完が効くけど・・・ それは、あまり美しくないような。
もっと言うと、switchの条件式でenumの値を使う時は、自動ですべての条件に対応するcase文を補完して欲しいところ。Xcode 7.1ぐらいで出来るようになるかな?
小ネタ集
以下、本当に細かいネタ。たぶん。
ソースコードの区切りはどうなった?
Objective-Cの場合はこんな感じ。
#pragma mark - now
Swiftではこんな風に書けば、同じ効果になる。
// MARK: - now
ソースコードはディレクトリの中に入れても大丈夫?
Swiftのソースコードを、サブディレクトリを作って管理してみたけど特に問題なさそう。
デバッグ実行が全体的におかしい?
ステップオーバーの挙動がソースコードと一致しない。Xcodeのバージョンが上がれば解決する問題・・・ かな?
コンパイルが重い
Objective-Cよりかなり重い。同じ内容をObjective-CからSwiftに書き換えて、Objective-Cの時代は特に何も感じなかったのが、Swiftだとコンパイルで待たされるように。CPUパワー(?)をがんがん使ってファンまで回るように。
一昔前のMacBook Airで開発するのは厳しい時代になったか・・・
if letの連結はできない
以下の様な書き方は出来ない。今のところ。
var v0: UIView? var v1: UIView? // ng if let t0 = v0 && let t1 = v1 { println("ng") }
素直に、二重に書くしか無い。
var v0: UIView? var v1: UIView? // ok if let t0 = v0 { if let t1 = v1 { println("ok") } }
基本的な構造体でも演算子が定義されてない
例えば、『CGPoint + CGPoint -> CGPoint』とか。あっても良さそうな物がない。すべて自力で定義すれば済む話だけど・・・ 最初から、公式で用意してくれたほうが、いろいろ便利なんだけど。
リファクタリングが使えない
さらっと書いてるけど、たぶん、現時点での最大の問題点。
すべて、手動でどうにかするしか無い。クラス名の変更とか、面倒すぎるな。
はてなブログのmarkdownはいつになったらswiftに対応するのか?
やる気はあるの?
資料
iTunes - ブック - Apple Inc.「The Swift Programming Language」
https://itunes.apple.com/jp/book/swift-programming-language/id881256329?mt=11
iTunes - ブック - Apple Inc.「Using Swift with Cocoa and Objective-C」
https://itunes.apple.com/jp/book/using-swift-cocoa-objective/id888894773?mt=11
iOS Developer Library
https://developer.apple.com/library/prerelease/ios/navigation/
(2014/09/25 作成)