プログラミング言語 Swift の解説書として名著である詳解 Swift 改訂版の一人読書会を開始する。 ここには私が「なるほど」と思った Swift の特筆すべき文法を備忘録代わりに記録するものとする。
私は Android アプリの開発経験は 5 年以上だが iOS アプリの開発経験は無く Swift 経験も過去に公式ドキュメントを流し読みして Hello World した程度となっている。 なので Java などのプログラミング経験は豊富だが Swift 経験は無いという方がこの文章に一番マッチする対象読者となる。
CHAPTER 01 Swift でプログラミング
文字列
Swift の文字列埋め込みの記法は \(式)
のようにする:
let n = 8
let str = "\(n) の 2 乗は \(n * n) です。" // "8 の 2 乗は 64 です。"
print 関数
print
関数は Python のようにセパレータを指定することができる:
print(6, 2014, "林檎", separator:"; ") // "6; 2014; 林檎"
Swift 1.2 までは println
があったが Swift 2.0 からは print
がデフォルトで改行するようになった。
もし改行したくない場合はキーワード terminator
に空文字列を渡す:
print("ほげ", terminator:"")
配列
若干記法が独特だ:
// Int 型の配列は [Int] とかく
let a: [Int] = [2, 5, 8, 11, 7]
// 空の配列生成 (イニシャライザ呼び出し)
let s = [String]()
let s: [String] = []
Java と違って配列も値型のデータらしい。なので普通に他の変数に代入するとディープコピーされるようだ。
型に別名を付ける
ちょっとイマイチ使いどころが分からないが、型に別名を付けることができる:
typealias SInteger = Int32
repeat-while 文
Java や C の do-while
にあたるものは repeat-while
となっている。Swift だと do
が他の文法で使用されている為だろう。
for-in 文
for-in
(foreach) が使えるのは大体想像がつくが where
でループ対象を絞り込むことができる:
for i in 1..<64 where i % 3 != 0 && i % 8 != 0 {
print(i, terminator:" ")
}
print("")
// 1 2 4 5 7 10 11 13 14 17 19 20 22 23 25 26 28 29 31 34 35 37 38 41 43 44 46 47 49 50 52 53 55 58 59 61 62
switch 文
switch
は break
で抜ける必要が無くなった。
break
必須というのはバグの元なので当然だろう。
もし Java や C でいうところのあえて break を書かずに続けて実行させるという場合は fallthrough
キーワードを使用する。
do 文
変数のスコープを絞るのに Java や C だと {}
で囲えばできるが Swift はできない。代わりに do
文を使う:
if a >= 0 {
b += a
do {
// t はこのブロック内でのみ有効
let t = a
a = c
c = t
}
}
CHAPTER 02 関数
外部引数名
Swift では関数の第 1 引数のみキーワードを省略できるが第 2 引数以降はキーワードが必須という文法になっており、これは Objective-C を踏襲したものになっている:
func add(x: Int, y: Int) -> Int {
return x + y
}
add(1, y:2) // 本当は add(1, 2) と書きたいが許されない
第 1 引数もキーワード必須にしたり第 2 引数以降もキーワード不要にする文法もあり以下のようにする:
// 引数に別名を付けると第 1 引数でも必須となる
func add2(a x: Int, b y: Int) -> Int {
return x + y
}
// 引数に _ を別名として付けると第 2 引数以降でもキーワード不要
func add3(x: Int, _ y: Int) -> Int {
return x + y
}
add2(a:1, b:2) // 両方キーワード引数
add3(1, 2) // 両方キーワードなし
下線の特殊な用法
基本的に _
はワイルドカードとして無視するのに使える:
_ = myGreatProcess(10, 20) // 結果を捨てることを明示
func compare(a: Int, _ b: Int, _: Bool) -> Bool { return a > b } // 第 3 引数は使わない
func compare(a: Int, _ b: Int, option _: Bool) -> Bool { return a > b } // 第 3 引数は外部引数としては要るが内部で使わない
inout 引数
Swift の関数の引数は値渡しだが inout
を付けるといわゆる参照渡しになる。
ちょっと今の時点で使いどころがよくわからない。
PHP などでもよくある話だが参照渡しは分かりにくくなる元だ。
引数の値を処理中に変更できるようにする
Swift の関数の引数は定数だが var
を付けると処理中に変更可能になる。
これも分かりにくくなるのであまり使わない気がする。
返り値を必ず使うための指定
@warn_unused_result
という属性 (Java のアノテーションもしくは Python のデコレータみたいなもの) を使うと関数を呼び出した箇所で戻り値を使っていないと警告を表示するようになる。
CHAPTER 03 構造体
Swift は構造体があるしクラスもあるが、どう違うのかがよくわからなかったので調べてみたらこの Qiita の記事がうまくまとまっていた。 要するに構造体は値渡しになりクラスは参照渡しになるらしいので、比較的小さいデータ型を定義するのに構造体を使用するようだ。
構造体を定数に代入した場合
この辺りの話がちょっと Java の感覚と違ったので注意が必要だった。
構造体が値型なのでプロパティも let
扱いとなるようだ。
構造体で var
で定義してあるプロパティであっても構造体を定数に代入すると var
のプロパティが変更不可となる:
struct Date {
var year = 2010
var month = 7
var day = 28
}
var d = Date() // 2010-07-28
d.day = 29 // これは OK
var camp = Date(year:1998, month:8, day:8) // 1998-08-08 変更可
let event = Date(year:2000, month:9, day:13) // 2000-09-13 変更不可
event.day = 30 // これは許されない!
イニシャライザの定義
構造体のイニシャライザ (コンストラクタのようなもの) は init
で書く:
struct Date {
var year, month, day: Int
init() {
year = 2095
month = 10
day = 31
}
}
もちろん init
を複数定義したり (コンストラクタのオーバーロード) もできる。
構造体の内容を変更するメソッド
構造体の内容を変更するメソッドを定義する場合は mutating
を func
の前に付ける。
そもそも内容を変更するのは推奨されないのであまり使うことはないだろう。
計算型プロパティ
計算型プロパティ (変更可能なプロパティ) にはゲッタとセッタを定義することができるが、以下の様な記法となる:
struct Ounce {
var mL: Double = 0.0
static let ounceUS = 29.5735
init(ounce: Double) {
self.ounce = ounce
}
var ounce: Double {
get {
// get 節のみの場合 get と {} を省略可能
return mL / Ounce.ounceUS
}
set {
// 仮引数を省略した場合 newValue という名前の引数として使用できる
mL = newValue * Ounce.ounceUS
}
}
}
- セッタが不要の場合
get
と{}
が省略可能 - セッタの仮引数は省略可能
- セッタの仮引数を省略した場合
newValue
という名前で引数を使用できる
計算型プロパティに対する特殊な設定
get
でプロパティを変更するような場合は mutating
を付加し set
でプロパティを変更しない場合は nonmutating
を付ける。
あまり使わない気がする。
プロパティオブザーバ
格納型プロパティの値が更新されるときにメソッドを起動するのをプロパティオブザーバと呼ぶ。
willSet
と zdidSet
(要するに pre と post) で定義できる。
ログ出力とかで使えるかもしれない。
ちなみに willSet
では newValue
で参照でき didSet
では oldValue
で参照できる。
添字付け
要するに構造体を使って配列のような仕組みを自前で定義できるというもの。
subscript
を使う。
struct A {
let items = ["ほげ", "ふが", "はげ"]
subscript(i: Int) -> String {
return items[i]
}
}
print(A()[1]) // ふが