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#sorted
はElement, 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