読者です 読者をやめる 読者になる 読者になる

開発メモ

開発関係のメモをいろいろと。たぶん。

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を使おうとするとコンパイルエラーに。

f:id:see_ku:20141008153001p:plain

基底クラスの方はコンパイルが通るのに、派生クラスの方だけコンパイルが通らないという不思議な状態。もっとも、派生クラスの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では、このコードはコンパイルできない。

f:id:see_ku:20141008153155p:plain

では、どうするか? とりあえず、手っ取り早いのはダミーの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とSwiftGenericsの考え方の違いが現れてて面白いんだけど、書いてると長くなるので省略。

ちなみに、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()
    }
}

こうなる。

f:id:see_ku:20141008153241p:plain

言語仕様としてエラーになるのはわかるんだけど、もう少しマシなエラーは出せないんだろうか?

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のところがエラーになる。

f:id:see_ku:20141008153317p:plain

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 作成)

広告を非表示にする