The Swift Programming Languageの導入部分を読んだ

swift.orgをぼちぼち読み進めているんだけど、A Swift TourとThe Basicsを読んだだけでもいろんな発見があった。 特に個人的に気になった部分をメモしておく。

A Swift Tour

A Swift Tourは関数の定義などの基礎が詰まっているページ。他言語を経験している人がSwiftがどんな言語なのかを軽く学ぶのに適している。

始める前に

$ xcrun swiftあるいは$ swiftでREPLを起動することができる。

関数とクロージャ

function greet(name: String, email: String) -> String {
  return "Hello \(name), your email is \(email)."
}

greet(name: "hoge", email: "test@example.com")

# => Hello hoge, your email is test@example.com.

関数を呼ぶときは、基本的に引数のラベル名を一緒につけてやらないといけない。 つけなかった場合、error: missing argument label 'name:' in callなどのエラーが出る。

ラベルには別名をつけることができ、さらに、ラベルなしで引数に渡したい場合は_をつけることで実現できる。 ラベルにfunc request(to url: String)などがついているのはこれのこと。

function greet(_ name: String, address email: String) -> String {
  return "Hello \(name), your email is \(email)."
}

//nameはラベルなしで、emailはエイリアスで与えている
greet("hoge", address: "test@example.com")

# => Hello hoge, your email is test@example.com.

関数はファーストクラスオブジェクトなので、関数を関数の中で定義してそれをreturnできる。 関数はクロージャの一種と考えることができる。

クロージャ{}で囲まれたコードで表現できる。

var numbers = [ 1, 2, 3 ]
numbers.map({ (number: Int) -> Int in 
  let result = 3 * number
  return result
})

inより手前がクロージャの取る引数、その後が実際の処理。

クロージャの取る型が既知であるなら、(例えばコールバックやデリゲートなどの場合)引数や返り値の型情報が省略ができる。

let mappedNumbers = numbers.map({ number in 3 * number })
print(mappedNumbers)

さらに、パラメータを名前ではなく$1,$2などの番号で関連づけることもできる。 短いクロージャを書きたいときに便利。

Int#sortedElement, Elementを引数に取り、Boolを返すクロージャを引数にとる。 このメソッドを使う場合は、

let sortedNumbers = numbers.sorted { $0 > $1 }
print(sortedNumbers)

と書くことができる。

全く省略しなかった場合、以下のように書くことになる。

let sortedNumbers = numbers.sorted({ (elem1: Int, elem2: Int) -> Bool in
  return elem1 > elem2
})
print(sortedNumbers)

enum

みんな大好き列挙型。メソッドを持てるのが特徴。

enum Rank: Int {
  case ace = 1
  case two, three, four, ...
  case jack, queen, king
  
  func simpleDescription() -> String {
    switch self {
    case .ace:
      return "ace"
    case .jack:
      return "jack"
    case .queen:
      return "queen"
    case .king:
      return "king"
    default:
      return String(self.rawValue)
    }
  }
}

let ace = Rank.ace
let aceRawValue = ace.rawValue

enumインスタンスを作ることができ、caseに紐づいたvalueをそれぞれ設定することができる。

enum ServerResponse {
  case result(String, String)
  case failure(String)
}

let success = ServerResponse.result("8:00 am", "8:09 pm")
let failure = ServerResponse.failure("Out of cheese.")

switch success {
case let .result(sunrise, sunset):
  print("Sunrise is at \(sunrise) and sunset i \(sunset).")
case let .failure(message):
  print("Failure... \(message)")
}

# => Prints "Sunrise is at 6:00 am and sunset is at 8:09 pm".

struct

構造体はクラスと同じ機能をだいたいサポートしている。 メソッドを定義するとか、初期化とか。 クラスと構造体の一番の違いは、構造体は常にコード間で受け渡しされる時に、必ずコピーが作られること。(値渡し) クラスの場合は参照で渡される。

struct Hoge {
  let fuga: Int
  
  func foo(num: Int) -> Bool {
    if num == 1 {
      return true
    }
  }
}

エラーハンドリング

Errorプロトコルを継承したenumでエラーを定義することが多いと思う。 エラーを発行する可能性のある関数には、throwsキーワードを付与してやる必要がある。

enum PrinterError: Error {
  case outOfPaper
  case noToner
  case onFire
}

func send(job: Int, toPrinter pritnerName: String) throws -> String {
  if printerName == "Never Has Toner" {
    throw PrinterError.noToner
  }
  
  return "Job sent"
}

エラーをキャッチする方法はいくつかある。一番シンプルなのはdo~try~catch構文を使う。 catch句の中では、errorと言う変数名でエラーの情報が取得できるようになっている。 tryを、例外を発行する可能性のあるメソッドの前に設置することで、マークを設定できる。(これが実際に何を行なっているのかは要調査)

do {
  let printerResponse = try send(job: 1040, toPrinter: "Bi Sheng")
  rint(printerResponse)
} catch {
  print(error)    
}

さらに、optionalを使ってキャッチすることもできる。 try?キーワードを使うことで、結果をoptionalに変換することができる。 メソッドが例外を出した時、特定の例外は破棄されてnilになる。 そうでなければ、結果は関数が返した値を含むoptionalになる。

let printerSuccess = try? send(job:1884, toPrinter: "Mergenthaler")
let printerFailure = try? send(job:1885, toPrinter: "Never Has Toner")

defer

deferキーワードを使うことで、そこ以外の部分を全て実行したあと、かつreturnされる前で評価されるコードを書くことができる。 この部分は、関数がエラーを発行するかに関わらず、必ず実行される。

deferキーワードを使うときは、セットアップやクリーンアップするコードを書くことが多い。

var fridgeIsOpen = false
let fridgeContent = ["milk", "eggs", "leftovers"]

func fridgeContains(_ food: String) -> Bool {
  fridgeIsOpen = true
  defer {
    fridgeIsOpen = false
  }
  
  let result = fridgeContent.contains(food)
  return result
}

fridgeContains("Banana")
print(fridgeIsOpen)
# => false

Generics

関数、クラス、enum、構造体に使用できる。

enum OptionalValue<Wrapped> {
  case none
  case some(Wrapped)
}

var possibleInteger: OptionalValue<Int> = .none
possibleInteger = .some(100)

whereキーワードを使うことで、Genericsの型が特定の条件を満たすかどうかで条件分岐することができる。

func anyCommonElements<T: Sequence, U: Sequence>(_ lhs: T, _ rhs: U) -> Bool
  where T.Element: Equatable, T.Element == U.Element
{
  for lhsItem in lhs {
    for rhsItem in rhs {
      if lhsItem == rhsItem {
        return true
      }
    }
  }
  return false
}

anyCommonElements([1, 2, 3], [3])

The Basics

The Basicsには変数宣言やifステートメント、エラーハンドリングなどについて記述されている。

A Swift Tourと説明が重複している部分はなさそうなので、こちらも一通り読んでおくと良い。

Type Aliases

型に別名をつけることができる。

typealias AudioSample = UInt16
var maxAmplitudeFound = AudioSample.min
// maxAmplitudeFound is now 0

Bool

Rubyとは違って、Bool以外をifの条件にそのまま渡すとコンパイルエラーになる。

let i = 1
if i {
  // this example will not compile, and will report an error
}

==などでちゃんと比較してやる必要がある。

let i = 1
if i == 1 {
  ...
}

Tuples

タプルは型の異なるものを1つの変数に格納する時に便利。

let a = ( 1, "hoge")

// 取り出すときはこんな感じで
let (val1, val2) = a

print(val1)
# => 1

print(val2)
# => "hoge"

// 要らなかったら_で捨てる

let (_, val3) = a

print(val3)
# => "hoge"

// 添字でもアクセスできる
print(a.0)
# => 1

// ラベルもつけられる
let http200status = (statusCode: 200, description: "OK")
print(http200status.statusCode)
# => 200

Optional

Optionalは値が無い可能性があるときに使う。 Optionalは2つの可能性があることを示す。

  • 値があり、値にアクセスするために、optionalをunwrap(後述)できる
  • 値がない

SwiftのInt型は初期化時にString型をInt型に変換しようとする機能がある。 しかし、全てのStringがIntegerに変換できるとは限らない。例えば、"123"123に変換できるが、"hello, world"は変換できない。

let possibleNumber = "123"
let convertedNumber = Int(possibleNumber)
// convertedNumber is inferred to be of type "Int?", or "optional int"

コンストラクタは失敗するかも知れないので、これはoptionalなIntを返す。 optionalなIntはInt?と記述される。この?は変数がoptionalであり、なんらかのIntの値を持っているか、あるいは値を何も持っていないかを表す。

nil

nilと言う特別な値をoptionalに代入することで、値がないことを表現できる。

var serverResponseCode: Int? = 404
// serverResponseCode contains an actual Int value of 404
serverResponseCode = nil
// serverResponseCode now contains no value

optionalに何も値を与えずに初期化した場合、nilがセットされる。

var surveyAnswer: String?
// surveyAnswer is automatically set to nil

If statements and Forced Unwrapping

ifステートメントを使うことで、optionalが値を格納しているかを判別できる。 具体的には、== nil!= nilを使う。

if convertedNumber != nil {
  print("convertedNumber contains some integer value")
}

一度optionalが値を持っていることが判明すれば、!をoptionalの名前の後につけることで値にアクセスできるようになる。 このマークは「このoptionalはちゃんと値を持っている」と言うことを表す。 これを、optionalの強制アンラップと呼ぶ。

if convertedNumber != nil {
  print("convertedNumber contains some integer value of \(convertedNumber!).")
}

Optional Binding

Optional Bindingはoptionalが値を持っているか調べつつ、値があるなら、その値を一時的に定数や変数に格納する機能。

ifもしくはwhileステートメントで値をチェックするときに使う。

if let constantName = someOptional {
  statements
}

Intの例だと以下のようになる。

if let actualNumber = Int(possibleNumber) {
  //...
} else {
  //...
}

Implicitly unwrapped optional

暗黙的アンラップ型。optionalだけどアクセス時に明示的なアンラップがいらない型を表現するときに使う。 型の末尾に!を付与することで宣言できる。

var foo: String! = "Hello"
print(foo.lowercaseString) // hello

暗黙的アンラップ型は

  • Optionalである
  • アクセス時に強制アンラップされる(variable!if ~ letが不要)

使い道としては、初期化時にはnilにはなり得るけど、Viewのように特定のタイミングで代入されることが決まっているオブジェクト、例えばiOSのStoryBoardが持つViewに対する参照IBOutletに対してよく使われる。

@IBOutlet weak var hogeLabel: UILabel!

おわりに

結構新しめの言語な上に、変化がそこそこ激しい言語なのでイマドキな機能が充実しているように感じた。*1

iOSアプリ開発をするならThe Basicsまでは読んでおくことをオススメしたい。

*1:Genericsのwheteとか特にそう思う