Xcode 6.0とSwift 1.0で気になったところ その2
Xcode 6でSwiftをまじめに使ってみて、その間に気になった事のまとめシリーズ、第2弾。相変わらず、細かすぎて伝わらなくても気にしない。Xcode 6.1で、いろいろ、直ってるといいな。
問題を確認した環境
環境 | 情報 |
---|---|
Xcode | 6.0.1 (6A317) |
Swift | 1.0 |
Date | 2014/10/08 |
protocolの中で、適用された後の型を示すのは『Self』
具体的にはこんな感じ。
protocol Equatable { func ==(lhs: Self, rhs: Self) -> Bool }
『Self』がprotocolが適用された後の型を示すことになる。Appleの資料を読んだ限りではこの説明はなかったと思うんだけど・・・ 見落としてたか? ちなみに、演算子の定義はグローバルレベルにしか書けないはずだけど、protocolの制約として書いておくのはokらしい。この辺りの細かい縛りもよくわからない。
構文の意味がわからない
Arrayの定義から一部分を抜粋。
extension Array { // 中略 /// Erase all the elements. If `keepCapacity` is `true`, `capacity` /// will not change mutating func removeAll(keepCapacity: Bool = default)
この『default』はどういう意味? 同じ形を自分で再現してもコンパイルエラー。資料をあたってみてもよくわからない。さっぱり謎。
Computed Propertiesの派生型をテストで使おうとすると、コンパイルエラーになる
日本語で説明しても、さっぱり意味がわからない・・・ ソースコードはこんな感じ。
/// Intに変換して保存 class TestClass3Base { var intValue = 0 var string: String { get { return String(intValue) } set { if let no = newValue.toInt() { intValue = no } else { intValue = 0 } } } } /// やっぱりStringのままで保存 class TestClass3Derive: TestClass3Base { var stringValue = "0" override var string: String { get { return stringValue } set { stringValue = newValue } } }
これを、通常のプロジェクト(?)で実験すると問題なく使える。
/// 基底クラス/派生クラスの実験 func testClass3All() { let base = TestClass3Base() println("base str1: \(base.string)") base.string = "31" println("base str2: \(base.string)") let der = TestClass3Derive() println("der str1: \(der.string)") der.string = "31" println("der str2: \(der.string)") }
けど、テストコードでTestClass3Deriveのstringを使おうとするとコンパイルエラーに。
基底クラスの方はコンパイルが通るのに、派生クラスの方だけコンパイルが通らないという不思議な状態。もっとも、派生クラスのComputed Propertiesさえ使わなければ問題ないので、回避はそれほど難しくない。
メインのコードのほうで、テストに使うextensionを追加。
/// テストコードでコンパイルが通らないのを回避するためだけの拡張 extension TestClass3Base { func getString() -> String { return self.string } func setString(str: String) { self.string = str } }
あとは、これを使う形でテストを書けばいい。
func testClass3DeriveExt() { let tc = TestClass3Derive() XCTAssert(tc.getString() == "0", "Pass") tc.setString("31") XCTAssert(tc.getString() == "31", "Pass") }
コードをテストしたいのに、テストのためにコードが必要になるってのは大いなる矛盾だけど。
派生クラスを元のクラスの中で定義できる
これも、日本語にすると逆に難しいな。ソースコードだとこんな感じ。
class TestClass4 { var intValue = 0 var strValue = "" func testPrint() { println("int: \(intValue), string: \(strValue)") } class ForInt: TestClass4 { init(value: Int) { super.init() self.intValue = value } } class ForString: TestClass4 { init(value: String) { super.init() self.strValue = value } } } func testClass4() { let tc0 = TestClass4() tc0.testPrint() let tc1 = TestClass4.ForInt(value: 100) tc1.testPrint() let tc2 = TestClass4.ForString(value: "200") tc2.testPrint() }
実際の例を見れば、何をやってるかは一目瞭然。うまく使えば、名前空間の切り分けとか、いろいろと便利そう。
Genericsの派生クラスはGenericsにしかできない
C++では何と言ってたっけ・・・ テンプレートの一般化? 特殊化? これが、Swiftでは使えない。
具体的にはこんな感じ。まず、元になるGenericsクラス。
class TestClass5<Type> { var value: Type init(value: Type) { self.value = value } func testPrint() { println("value: ???") } }
ここで、TypeがStringの時だけtestPrint()でvalueを表示するようにしたい。
この場合、C++っぽく書いたらこうなる。たぶん。
class TestClass5StringNG: TestClass5<String> { override init(value: String) { super.init(value: value) } override func testPrint() { println("value: \(value)") } }
厳密に言うと、これは、ただの派生であって特殊化ではないな。
今のSwiftでは、このコードはコンパイルできない。
では、どうするか? とりあえず、手っ取り早いのはダミーのGenericsクラスを作って、そこからtypealiasする方法。
class TestClass5Dummy<Dummy>: TestClass5<String> { override init(value: String) { super.init(value: value) } override func testPrint() { println("value: \(value)") } } typealias TestClass5String = TestClass5Dummy<String>
これでやりたいことは出来るけど、あまり美しくない。
Swiftの流儀としては、必要な機能はあらかじめprotocolで定義しておいて、Genericsではその機能を使うってのが正当なやり方(?)っぽい。この辺りにC++のTemplateとSwiftのGenericsの考え方の違いが現れてて面白いんだけど、書いてると長くなるので省略。
ちなみに、Swiftの正当なやり方っぽく実装するとこんな感じになる。たぶん。
/// 専用のprotocolを定義 protocol TestClass5Protocol { func testPrint() } /// protocolを使う形でGenericsを実装 class TestClass5P<Type: TestClass5Protocol> { var value: Type init(value: Type) { self.value = value } func testPrint() { value.testPrint() } } /// 使いたい型にprotocolを適用 /// これが、事実上の特殊化になる extension String: TestClass5Protocol { func testPrint() { println("value: \(self)") } } /// typealiasで専用の型を定義 typealias TestClass5PString = TestClass5P<String>
NSObjectの派生クラスでGenericsを使うとSegmentation fault
以下のコードをコンパイル。
/// NSObjectの派生クラス class TestClass6<Type>: NSObject { var value: Type init(value: Type) { self.value = value super.init() } }
こうなる。
言語仕様としてエラーになるのはわかるんだけど、もう少しマシなエラーは出せないんだろうか?
returnを付けるとコンパイルが通らなくなるケースがせつない
ソースコードはこんな感じ。
let num = [0, 10, 20, 30, 100, 200] /// compile ok let str1 = num.map() { no in String(no) } /// compile ng let str2 = num.map() { no in return String(no) } /// compile ok let str3: [String] = num.map() { no in return String(no) }
str2のところがエラーになる。
returnを付けてない時は型推測がうまく働いて、戻り値が[String]になる。それが、親切心からreturnを付けると、戻り値の型が判定できなくなってエラーに。これは切ない・・・ しかも、エラーが出るケースが限定的でエラーメッセージもわかりにくい。ハマる人はどっぷりハマりそう。
けど、str3のように戻り値の型まではっきり指定してやれば、一周回って(?)コンパイルが通るようになったり。
小ネタ集
大文字の『TRUE』と『FALSE』が鬱陶しい
どこかで、こんな感じに定義されてる。
var FALSE: DYLD_BOOL { get } var TRUE: DYLD_BOOL { get }
普通に、『true』や『false』を書きたいのに、自動補完で混ざってくるのが邪魔。#undef的な機能は無いんだろうか?
UIControlのremoveTargetでNULLが指定できない
他にも、APIでNULLを使ってるところはありそうな気がするけど、とりあえずこれが困った。NULLの代わりに『""』を渡してみても動作は期待通りにならず。結局、真面目に(?)指定してremoveするしか無い、と。
はてなブログの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/
class - Limitation with classes derived from generic classes in swift - Stack Overflow
http://stackoverflow.com/questions/24138359/limitation-with-classes-derived-from-generic-classes-in-swift
(2014/10/08 作成)