Apple が新しいプログラミング言語 Swift を2014年の WWDC で発表しました。言語仕様自体は色々なモダンプログラミング言語のいいとこ取りのような感じです。独特な仕様もいくつかあるのでおぼえがきしておきます。

なお、Swift で作成したアプリは iOS8 と OS X Yosemite がリリースされた時点で審査に入れるようです。

Xcode6 Beta4 で Swift コンパイラでの minimum target が iOS7 と OS X10.9 に制限されました

Introducing Swift

※ Xcode6 Beta4 を元に記載しています。

The Basics

NSLog の代わりに println が使える。Xcode のコンソールにも表示される。文字列の変数展開は \() を使う。

1
2
let name = "hamasyou"
println("Hello \(name)")  // Hello hamasyou

基本型

IntDoubleFloatBoolStringArrayDictionary などが用意されている。Int16UInt8 などもある。型の変換は 型名() を使う。

Swift では nil を特別な値として定義している。Bool 値は truefalse。条件式では Bool 値のみが使える。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
UInt8.max   // 255
UInt8.min   // 0

let three = 3
let floatValue = 0.14159
let pi = Double(three) + floatValue // 3.14159
let integerPi = Int(pi) // 3

if pi {
    // この文法はコンパイルエラーになる。正しくは↓
}

if pi == 3.14159 {
}

タプルも使える。不要な値を受け取るときは _ を使う。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
let httpError = (404, "Not Found")
httpError.0   // 404
httpError.1   // "Not Found"

let otherError = (errorCode: 500, errorMessage: "Internal Server Error")
otherError.errorCode    // 500
otherError.errorMessage // "Internal Server Error"

let (code, msg) = httpError
code  // 404
msg   // "Not Found"

let (_, msg2) = otherError
msg2  // "Internal Server Error"

配列は [Int] のように記述する。

1
let ary: [Int] = [10, 20, 30]

リテラルと変数

Swift ではリテラルの定義は let を使い、変数の定義は var を使う。let で定義したリテラルは変更できない。

1
2
3
4
let myName = "hamasyou"
let π = 3.14
var x = 0.0, y = 0.0, z = 0.0
x = 3.0

文字列を数値に直すのに toInt メソッドが使える。この時返される値は Int 型でなく、Int? 型なので注意。

1
2
let str = "123"
let num = str.toInt()   // num は Int? か Optional Int として型推論される

if 文の条件式には Bool しか記述できないが、この、型 + ? の形式で表される Optional 型 も if 文の式に記述できる。この場合値が nil ではない場合に true と評価される。

1
2
3
4
5
6
var str = "123"
var num = str.toInt()

if num {
    println("\(str) is number of \(num!)")
}

このように、Swift では nil を返す可能性があるメソッドの戻り値の型は Optional 型 になる。Optional 型 から値を取り出すには ! をつける。但し、nil が入っている変数から値を取り出そうとするとランタイムエラーになる。

1
2
3
4
5
6
7
8
str = "abc"
num = str.toInt()

println(num!)   // これはランタイムエラーになるので、必ず下の様に呼び出す

if !num {
    println("\(str) is not number")
}

Optional 型から値を取り出す方法に、! をつける以外に次のようにも書ける。(Optional Binding)

1
2
3
4
5
let str = "123"
let expectNum = str.toInt()
if let actual = expectNum {
    println(actual)
}

Implicity Unwrapped Optionals

Swift は Objective-C の nil ポインタとは違う nil を採用している。nil が格納される可能性がある変数は Optional 型として定義する必要がある。

Optional 型の変数から値を取り出すには ! を使う。必ず値が入っている確証が持てる場合は 型名 + ! の形で記述することもできる。値が入っていない時に ! を使うとランタイムエラーがでるので注意。

1
2
3
4
5
var optioanValue: Int?   // Optional 型の変数は nil で初期化される

let str = "123"
let num: Int! = str.toInt()
num   // num は 123 が入る Int! で定義しない場合は型推論によって Int? 型の Wrapped Value が入っているので 123 ではない

Range

(a..<b)(a...b) の形で範囲を求めることができる。(a..<b) は b を含まない。Ruby とは逆の動きをする。

1
2
3
for i in (0 ..< 3) {
    println(i)    // 0, 1, 2
}

Xcode6 Beta3 から終端を含まない範囲を ..< で表すようになった。

String And Character

StringNSString が使われているところでも使える。String は Character のコレクション型である。

1
2
3
4
5
6
7
8
let str = "Hello World"
let c: Character = "!"

let s = str + c

str.isEmpty             // false
str.hasPrefix("Hello")  // true
str.hasSuffix("World")  // true

Collection Types

Array

Swift の配列は Array 型。Array<SomeType> で表す。略語として 型名[] の形式も使える。NSArrayNSMutableArray が使われているところでも使える。

1
2
3
4
5
6
7
8
var list = ["Hello", "World", "Good"]
list.count
list += "Nice"      // ["Hello", "World", "Good", "Nice"]
list.removeLast()   // "Nice"

var stringList: String[]?             // nil
var anyList: Array<AnyObject>?        // nil
var stringList2: Array<String> = []   // empty String array

Dictionary

Swift の Hash は Dictionary 型。Dictionary<KeyType, ValueType> で定義する。NSDictionaryNSMutableDictionary が使われているところでも使える。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var airports = ["TYO": "Tokyo", "DUB": "Dublin"]
// var airports: Dictionary<String, String> = ["TYO": "Tokyo", "DUB": "Dublin"] とも書ける
airports.count
airports["OSK"] = "OSAKA"
airports["HOGE"]    // nil
airports.keys       // ["TYO", "DUB", "OSK"]
airports.values

for (code, name) in airports {
}

var dict: Dictionary<String, String>?             // nil
var emptyDict = Dictionary<String, String>()      // empty Dictionary 
var emptyDict2: Dictionary<String, String> = [:]  // empty Dictionary

Functions

関数は func メソッド名(引数: 型) -> 戻り値の型 の形で書く。戻り値が必要ない場合は省略するか、Void を記述する。

Swift の関数(メソッド)は 第一級関数 なので、変数や引数に渡すことができる。

1
2
3
4
5
6
7
8
9
func hello(name: String, greet: String) -> String {
    return "Hello \(name), \(greet)"
}
println(hello("hamasyou", "Good Morning"))

func sayHello(name: String) -> Void {
    println("Hello \(name)")
}
sayHello("hamasyou")

戻り値にはタプルを指定することもできる。

1
2
3
4
5
6
7
8
9
func count() -> (vowels: Int, consonants: Int, others: Int) {
    var n1 = 3, n2 = 5, n3 = 10
    return (n1, n2, n3)
}

let ret = count()
ret.vowels
ret.consonants
ret.others

External Parameter Names

関数呼び出し時にキーワード引数として呼び出す名前を指定できる。

1
2
3
4
5
6
7
8
9
func someFunction(externalParameterName localParameterName: Int) {
    var total = 10 + localParameterName
}
someFunction(externalParameterName: 5)

func join(s1: String, s2: String, withJoiner joiner: String) -> String {
    return s1 + joiner + s2
}
join("Hello", "World", withJoiner: "-")

externalParameterNamelocalParameterName を同じにしたいときは、# を使う。

1
2
3
4
5
6
7
8
func(containsCharacter(#string: String, #characterToFind: Character) -> Bool {
    for character in string {
        if character == characterToFind {
            return true
        }
    }
    return false
}

引数にデフォルト値を指定することもできる。デフォルト値を指定して、externalParameterName を指定しないときは、localParameterName が externalParameterName になる。

1
2
3
4
func join(s1: String, s2: String, joiner: String = "-") -> String {
    return s1 + joiner + s2
}
join("Hello", "World", joiner: " ")

_ を externalParameterName に指定すれば、デフォルト値を設定しても呼び出し時に externalParameterName を指定する必要はなくなる。

1
2
3
4
func join(s1: String, s2: String, _ joiner: String = "-") -> String {
    return s1 + joiner + s2
}
join("Hello", "World", " ")

In-Out Parameters

関数の引数は定数(let)として渡される。引数を変数(var)として渡したい場合は、引数名の前に var をつける。

var をつけて引数を定義しても、変更の影響を受けるのは関数の中だけ。関数の外の変数にも影響を及ぼしたい場合には inout をつけて引数を定義して、引数を渡すときに & を使う。

1
2
3
4
5
6
7
8
9
10
11
func swap(inout s1: String, inout s2: String) {
    let tmp = s1
    s1 = s2
    s2 = tmp
}

var s1 = "Hello"
var s2 = "World"
swap(&s1, &s2)
s1    // "World"
s2    // "Hello"

Closures

Swift の Closure は次の点が最適化されている。

  • 引数と戻り値の型が推論される
  • Closre Body が単一式の場合はそれが暗黙の戻り値になる
  • 引数を略式で書ける
  • Closure を最後の引数として取るメソッドの場合、Closure を () の外側に書くことができる
Closure Syntax
1
2
3
{ (parameters) -> returnType in
    statements
}
Closure syntaxes
1
2
3
4
5
6
7
8
9
10
let names = ["Chris", "Alex", "Ewa", "Barry", "Daniella"]
sort(names, { (s1: String, s2: String) -> Bool in
    return s1 < s2
})

sort(names, { (s1: String, s2: String) -> Bool in return s1 < s2 })
sort(names, { s1, s2 in return s1 < s2 })
sort(names, { s1, s2 in s2 < s2 })
sort(names, { $0 < $1 })
sort(names) { $0 < $1 }
map sample
1
2
3
4
5
6
7
8
9
10
11
12
13
14
let digitNames = [
    0: "Zero", 1: "One", 2: "Two",   3: "Three", 4: "Four",
    5: "Five", 6: "Six", 7: "Seven", 8: "Eight", 9: "Nine"
]
let numbers = [16, 58, 510]
let strings = numbers.map { (var num) -> String in
    var output = ""
    while num > 0 {
        output = digitNames[num % 10]! + output
        num /= 10
    }
    return output
}
strings
Closure sample
1
2
3
4
5
6
7
8
9
10
11
12
13
func makeIncrementor(forIncrement amount: Int) -> (() -> Int) {
    var runningTotal = 0
    func incrementor() -> Int {
        runningTotal += amount
        return runningTotal
    }
    return incrementor
}

let incrementByTen = makeIncrementor(forIncrement: 10)
incrementByTen()    // 10
incrementByTen()    // 20
incrementByTen()    // 30

Enumerations

Swift では Enumeration も first-class type である。Swift ではデフォルト値を設定しない限り、自動で 0, 1, 2 の様な値がふられることはない。

1
2
3
4
5
6
7
8
9
10
11
12
13
enum CompassPoint {
    case North
    case South
    case East
    case West
}
CompassPoint.North

enum Numbers: Int {
    case Zero = 0, One, Two, Three, Four, Five, Six, Seven, Eight, Nine
}
Numbers.Four.toRaw()                // 4
Numbers.fromRaw(7) == Numbers.Seven // true 

Associated Values

Swift の enumerations は関連する値を保持することができる。

1
2
3
4
5
6
7
8
9
10
11
12
enum Barcode {
    case UPCA(Int, Int, Int)
    case QRCode(String)
}

let productBarcode = Barcode.UPCA(8, 85900_51226, 3)
switch productBarcode {
case let .UPCA(numberSystem, identifier, check):
    println("UPC-A with value of \(numberSystem), \(identifier), \(check).")
case let .QRCode(productCode):
    println("QR code with value of \(productCode).")
}

Classes and Structures

Swift では、クラスと構造体はほぼ同じ物と考えることができる。構造体は参照ではなく copy value で管理される。

Unlike value types, reference types are not copied when they are assigned to a variable or constant, or when they are passed to a function. Rather than a copy, a reference to the same existing instance is used instead.

Classes Are Reference Types

クラスと構造体の共通点

  • プロパティ を定義することができる
  • メソッド を定義することができる
  • subscripts (配列の[0] のようにアクセスできる仕組み)を提供することができる
  • イニシャライザ (コンストラクタ)を定義することができる
  • デフォルトの振る舞いを拡張することができる
  • protocol を使うことができる

クラスだけがもつ機能

  • 継承
  • タイプキャストが可能
  • デイニシャライザ (デストラクタ)を定義することができる
  • ひとつ以上の参照カウントが許される(構造体は copy-value、クラスは reference)
syntax
1
2
3
4
5
6
7
8
9
10
11
12
13
14
struct Resolution {
    var width = 0
    var height = 0
}

class VideoMode {
    var resolution = Resolution()
    var interlaced = false
    var frameRate = 0.0
    var name: String?
}
let someResolution = Resolution()
let someVideoMode = VideoMode()
someVideoMode.resolution.width = 1280

Objective-C とは違って、参照をたどって直接値を設定することも可能。

構造体は、暗黙のイニシャライザを使って、プロパティを初期化することが可能。クラスはイニシャライザを明示的に定義しなければいけない。

1
let rvga = Resolution(width: 640, height: 480)

クラスと構造体のどちらを選ぶか

  • 構造体を使う一番の理由は関係する値をまとめるため
  • プロパティのデータを何かしら処理する場合には、クラスの方がいいかも

Array と Dictionary

Swift の Array と Dictionary は構造体の様に振舞う。

Dictionary は変数に割り当てられる際にコピーされる。Key/Value の Value がさらに Dictionary の場合はそれもコピーされる。

1
2
3
4
var ages = ["Peter": 23, "Wei": 35, "Anish": 65, "Katya": 19]
var copiedAges = ages
ages["Peter"] = 24    // 24
copiedAges["Peter"]   // 23

Xcode6 Beta3 からは、Array の動作も Dictionary と同じ動きをする。つまり、値がコピーされる。

1
2
3
4
5
6
7
8
9
var nums = [10, 20, 30, 40]
var otherNums = nums

nums[0] = 50
otherNums[0]    // 50

nums += 100     // occure copy, nums is [50, 20, 30, 40, 100]
nums[0] = 10    // nums is [10, 20, 30, 40, 100]
otherNums[0]    // 50, otherNums is [50, 20, 30, 40]

Array で参照を切りたいときは unshare() メソッドを使う。変数への割り当て時に強制的にコピーしたいときは copy() メソッドを使う。

Properties

Lazy Stored Properties

最初にアクセスされるまで初期化されないようにするには、@lazy を使用する。変数(var)で定義するプロパティには lazy をつけるようにするといい。

You must always declare a lazy property as a variable (with the var keyword), because its initial value may not be retrieved until after instance initialization completes. Constant properties must always have a value before initialization completes, and therefore cannot be declared as lazy.

Lazy Stored Properties
1
2
3
class DataManager {
    lazy var importer = DataImporter()   // DataImporter is assumed to take a non-trivial amount of time to initialize.
}

Xcode6 Beta4 から @final@lazy@optional@required@ が不要になった。

Computed Properties

center プロパティが Computed Property になる。set で受け取る引数は明示してもいいし、明示しない場合は newValue で受け取れる。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
struct Rect {
    var origin = Point()
    var size = Size()
    var center: Point {
    get {
        let centerX = origin.x + (size.width / 2)
        let centerY = origin.y + (size.height / 2)
        return Point(x: centerX, y: centerY)
    }
    set {
        origin.x = newValue.x - (size.width / 2)
        origin.y = newValue.y - (size.height / 2)
    }
    }
}

Property Observers

willSet, didSet を使うと、プロパティが設定されるまえ、設定された後にコールバックを受け取ることができる。これらのコールバックは初期化時にはよばれない。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class StepCounter {
    var totalSteps: Int = 0 {
    willSet(newTotalSteps) {
        println("About to set totalSteps to \(newTotalSteps)")
    }
    didSet {
        if totalSteps > oldValue  {
            println("Added \(totalSteps - oldValue) steps")
        }
    }
    }
}
let stepCounter = StepCounter()
stepCounter.totalSteps = 200
// About to set totalSteps to 200
// Added 200 steps

Methods

メソッドにも関数と同じく externalParameterNamelocalParameterName を指定できる。メソッドの場合は関数と違って、externalParameterName と localParameterName の定義の振る舞いが違う。

メソッドは最初の引数の externalParameterName は、localParameterName と同じになり、呼び出し時は最初の引数の externalParameterName は省略できる。

1
2
3
4
5
6
7
8
class MyClass {
    func hello(name: String, greet: String) -> String {
        return "Hello \(name) \(greet)"
    }
}

let obj = MyClass()
obj.hello("hamasyou", greet: "Good Morning")

第二引数以降の呼び出しで externalParameterName を指定したくない場合は _ を使う。

1
2
3
4
5
6
7
8
class MyClass {
    func hello(name: String, _ greet: String) -> String {
        return "Hello \(name) \(greet)"
    }
}

let obj = MyClass()
obj.hello("hamasyou", "Good Night")

Modifying Value Types from Within Instance Methods

構造体と Enumerations はデフォルトでメソッドの中からプロパティの変更を行うことができない。変更できるようにするには mutating でメソッドを修飾する。

1
2
3
4
5
6
7
struct Point {
    var x = 0.0, y = 0.0
    mutating func moveByX(deltaX: Double, y deltaY: Double) {
        x += deltaX
        y += deltaY
    }
}

Access Control Level

Xcode6 Beta4 より 3つのアクセスレベルが用意された。

private
定義された ソースファイル 内からのみアクセスできる
internal
定義されたエンティティ内からであればどこからでもアクセスできる
public
どこからでもアクセスできる

デフォルトは internal

Subscripts

Subscript はコレクションにアクセスするための略式を定義するもの。クラスや構造体、Enumeration に定義できる。定義すると [0] などでアクセスできるようになる。

subscript の引数は複数指定することもできる。

1
2
3
4
5
6
7
8
subscript(index: Int) -> Int {
    get {
        // return an appropriate subscript value here
    }
    set(newValue) {
        // perform a suitable setting action here
    }
}

Inheritance

メソッドやプロパティのオーバーライドには override キーワードを使用する。override されたくないメソッドやプロパティには @final を指定する。

Initialization

イニシャライザは init で定義する。イニシャライザに引数を指定する場合、externalPropertyName を指定しなければ暗黙で localPropertyName が externalPropertyName に指定される。

メソッドと違い、イニシャライザの呼び出し時には全ての externalPropertyName の指定が必要

1
2
3
4
5
6
7
8
9
struct Color {
    let red = 0.0, green = 0.0, blue = 0.0
    init(red: Double, green: Double, blue: Double) {
        self.red   = red
        self.green = green
        self.blue  = blue
    }
}
let magenta = Color(red: 1.0, green: 0.0, blue: 1.0)

Designated Initializers and Convenience Initializers

Designated Initializer はインスタンスの初期化の目的で定義される。Convenience Initializer は Designated Intitializer を呼び出す際のデフォルト値などを設定する目的で使う。

1
2
3
4
5
6
7
8
9
10
11
class MyClass {
    var name: String
    init(name: String) {
        self.name = name
    }

    convenience init() {
        self.init(name: "hamasyou")
    }
}
let me = MyClass()

Type Casting

型チェックには is を使う。

1
2
3
4
5
for item in library {
    if item is Movie {
        // execute movie logic
    }
}

ダウンキャストに as が使える。Optional Type の場合は as? を使う。Dictionary とかから値を取り出すときに使える。

1
2
3
4
5
6
7
for item in library {
    if let movie = item as? Movie {
        println("Movie: '\(movie.name)', dir. \(movie.director)")
    } else if let song = item as? Song {
        println("Song: '\(song.name)', by \(song.artist)")
    }
}

配列の型に AnyObject を使うと、利用時にキャストが必要だが何の型か指定しなくても入れれるようになる。

1
2
3
4
5
6
7
8
9
let someObjects: AnyObject[] = [
    Movie(name: "2001: A Space Odyssey", director: "Stanley Kubrick"),
    Movie(name: "Moon", director: "Duncan Jones"),
    Movie(name: "Alien", director: "Ridley Scott")
]
for object in someObjects {
    let movie = object as Movie
    println("Movie: '\(movie.name)', dir. \(movie.director)")
}

配列の型に Any を使うと、なんでもいれれるようになる。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
var things = Any[]()

things.append(0)
things.append(0.0)
things.append(42)
things.append(3.14159)
things.append("hello")
things.append((3.0, 5.0))
things.append(Movie(name: "Ghostbusters", director: "Ivan Reitman"))

for thing in things {
    switch thing {
    case 0 as Int:
        println("zero as an Int")
    case 0 as Double:
        println("zero as a Double")
    case let someInt as Int:
        println("an integer value of \(someInt)")
    case let someDouble as Double where someDouble > 0:
        println("a positive double value of \(someDouble)")
    case is Double:
        println("some other double value that I don't want to print")
    case let someString as String:
        println("a string value of \"\(someString)\"")
    case let (x, y) as (Double, Double):
        println("an (x, y) point at \(x), \(y)")
    case let movie as Movie:
        println("a movie called '\(movie.name)', dir. \(movie.director)")
    default:
        println("something else")
    }
}

Extensions

既存のクラス、構造体、Enumeration に機能を追加できる。

1
2
3
4
5
6
7
8
9
10
11
extension SomeType: SomeProtocol, AnotherProtocol {
    // implementation of protocol requirements goes here
}

extension Double {
    var km: Double { return self * 1_000.0 }
    var m: Double { return self }
    var cm: Double { return self / 100.0 }
    var mm: Double { return self / 1_000.0 }
    var ft: Double { return self / 3.28084 }
}