はじめに
この文献はSwiftの基本構文の説明ではなく、僕が使っている構文で便利なものをまとめたものです。
なので、基本的構文の一部は割愛されてます。
※便利な構文を随時追加していきます。
これ以外にも他にも便利な構文があればコメントで教えていただけると僕が喜びます。
改定履歴
2015/7/16
enum追記、Array,Dictionaryの初期化を追記。
for文でeach_with_index的なものの追記。
Enumでコンパイルエラーが発生するケースを追記。2015/7/19
Arrayの存在確認を追加メモ
setメソッドのハック、Lazy、filterなどもいずれ書く。
OptionalValue
Swiftと言えばOptionalValue
と言うほど便利な構文です。
あまりに基本的な構文なので構文の説明は割愛して、僕が使う局面について説明します。
非Optional 型
var str = "初期化コード"
nilが入ることが絶対なく、nilを考慮するプログラムを書くことが面倒な場合に使用します。
この構文は必ず初期化をしなければならない。
OptionalValueは便利だが、僕は極力nilが入らないコードを作るようにプログラムを心がけているので非Optionalの方が使用頻度が高いです。
Optional 型
var str:String?
var human:Human?
使用方法
str = "Optionalデータ"
if let s = str {
print("\(s)")
}
human = Human()
human?.run()
human!.run() // 値が入っていることがわかっている場合は`!`で呼び出すこともある。
human?.foot?.kick() // チェーンしても問題ない。途中でnilがある場合はそれ以降の行われずnilと評価される。
Optinal型はアンラップしないと実際の定義値が使えないため、簡単なことをする場合でも手間がかかるときがある。
しかし、強固なアプリを作る上では絶対不可欠なのでSwiftを行う上では覚えておく必要がある。
かなり奥が深い構文なので、深く知りたい方はkoherさんのSwiftのOptional型を極めるを見ると勉強になる。
また、Swift1.2から複数のOptional型を同時にアンラップできるようになった。
if let s = str, let h = human {
print("\(s)")
}
僕は極力nilを含めないようなプログラムを心がけているため、主に以下のケースで使用する。
- 他のクラスが強参照しているオブジェクトを一時的に借りる場合。(主にweakと合わせて使う。)
- コールバック系の処理など、格納されるかどうかわからない処理を扱う場合。
- cocoaなどのフレームワークからの戻り値がOptional型の場合。
ちなみに関数をOptionalで扱う場合は以下のような構文になる。
var callback:(()->())?
外側のカッコが重要。
()->()
で関数の型を定義していて、その関数をくくるために外側のカッコをつけている。
使用方法
callback = { () in
...
// コールバックの処理
}
// 呼び出し処理
callback?()
などになる。
関数の書き方はいろいろあるので、慣れてきたら略式で書くと工数が削減できる。(後述)
※weakについては僕の記事のweak(弱参照)の使うタイミング(特にクロージャの中)について説明してみるなどを見てもらえると嬉しい。
Implicitly unwrapped optional 型
var str:String!
非Optional型
と同じように使えるが、nilをチェックしないOptional型
です。
絶対に値は入っているが、初期化時には値が入れない場合に使用する。
主に使用するのはxibやstoryboardなどによってオブジェクトが挿入される時だろう。
例
@IBOutlet weak var text:UITextField!
使用方法は非Optional型
と同じように扱ってほとんど問題がない。
nilが挿入される可能性が少しでもあるならOptional型
を使うべきだろう。
private / final
説明は不要だと思うが、オブジェクト指向のカプセル化で使用する。
動的ディスパッチを減らす意味でも積極的につけていくとパフォーマンスが向上する。
・・・とは言っても、劇的な効果があるわけではないので気づいたらfinalをつけていこうって感じ。
同期処理
gfxさんSwiftでマルチスレッド時の同期処理はどうやるのかがとても便利。
記事の内容の転記だが、以下のコードを使うことで同期処理が簡単にできる。(使い方はgfxさんの記事をみてください。)
class AutoSync {
let object : AnyObject
init(_ obj : AnyObject) {
object = obj
objc_sync_enter(object)
}
deinit {
objc_sync_exit(object)
}
}
このコードのポイントはblock構文を使わないため、インテントが深くならずに書きやすいところだ。
関数の書き方色々
僕はものぐさなので、できるだけ簡単に関数を書きたい。
その例をいくつか書いてみる。
通常関数引数の呼び出し方
関数を引数にする場合は基本的に最後に記載すること。
Swiftでは最後に関数がある場合に、スマートに呼び出すことができる。
func function(str:String, callback:()->()) {
// なんらかの処理
callback()
}
普通に呼び出すと以下のようになる。
function("引数", { () in
// コールバックとしての処理
})
省略して呼び出すと以下のようになる。
function("引数", {
// コールバックとしての処理
})
スマートに呼び出すと以下のようになる。
function("引数") {
// コールバックとしての処理
}
どれも同じ挙動をするが、一番最後の呼び出し方法がSwift的に綺麗な記述方法だろう。
※カッコを閉じる位置に注目してください。
引数がある場合
引数がある場合でも省略して書ける。
func function(str:String, callback:(human:Human)->()) {
// なんらかの処理
callback(Human())
}
普通に呼び出すと以下のようになる。
function("引数", { (human) in
// コールバックとしての処理
human.run()
})
スマートに呼び出すと以下のようになる。
function("引数") { (human:Human) in
// コールバックとしての処理
human.run()
}
変数の型を省略して書くと以下のようになる。
function("引数") { (human) in
// コールバックとしての処理
human.run()
}
変数の定義すら省略すると以下のようになる。
function("引数") {
// コールバックとしての処理
$0.run()
}
変数を省略する場合は、$0,$1,$2...
の順番に引数が設定される。
引数が渡ってきているのに、$0
などを記載しないとコンパイルエラーになる。
以下のコードは$0
を使っていないのでコンパイルエラー。
function("引数") {
// コールバックとしての処理
print("コンパイルエラー")
}
変数として定義している関数の設定の仕方
funcを使わずに、関数を変数として定義した場合に設定すると以下のようになる。
// 定義
var function:()->()
// 処理の設定
function = { () in
// 呼び出したい処理
}
function()
上記同様で以下のようなことも可能
// 定義
var function:(human:Human)->()
// 処理の設定
function = {
// 呼び出したい処理
$0.run
}
function(Human())
キャプチャーリスト
objective-cやswiftにはガベージコレクション(GC)がないので、変数の管理は自分で行う必要がある。
例えば、ある画面でボタンを押したらWebサービスからデータを取得するような処理を書くと以下のようになる。
@IBAction func onClick(sender:AnyObject) {
// APIを呼び出す処理
API.call() {
// APIの結果を画面に描画する。
self.write($0)
}
}
この処理は画面が切り替わる前にAPIの結果が返って来れば問題ないが、APIの結果が返ってくる前に画面遷移等をして画面が無くなってしまうと予期せぬ挙動が発生する。
その理由はself.write($0)
の部分のselfのオブジェクトがAPI.call()
によってキャプチャー(強参照)されてしまっているからである。
詳細は割愛するが、self(UIViewController)のオブジェクトが壊れている状態に対してwrite()
などの関数を呼び出してしまっているため予期しない挙動が発生する。
この手のエラーは見つけるのが困難なので、非同期処理をする場合は極力このようなコードは書かないようにすべきである。
で、どうするかというとキャプチャーリストとしてweak(弱参照)を定義しておくのだ。
@IBAction func onClick(sender:AnyObject) {
// APIを呼び出す処理
API.call() { [weak self] in
// APIの結果を画面に描画する。
self?.write($0)
}
}
構文としてのポイントは[weak self] in
この部分だ。
この構文によって、selfは使いたいけど変数が解放されていたら無視したいということができる。
これによって、ブロック構文内ではself
がOptional型に変わり、self?
でアクセスできるようになる。
※キャプチャーリストを使う場合は最後にあるin
を省略できないので注意。
self?は弱参照なので、画面遷移などが発生した場合はnilになっている。
また、コールバック後に長い処理を行うと処理の途中でself?
がnilになる場合が発生してしまう。
その場合は以下のような構文で対応するとよい。
@IBAction func onClick(sender:AnyObject) {
// APIを呼び出す処理
API.call() { [weak self] in
// APIの結果を画面に描画する。
if let _self = self {
_self.write($0)
// 長い処理・・・
}
}
}
if let _self = self
によってself?
を_self
で強参照しているためif文の中でself?
が解放されることがなくなる。
また、キャプチャーリストは以下のように複数定義することもできる。
@IBAction func onClick(sender:AnyObject) {
// APIを呼び出す処理
API.call() { [weak self, myButton, myObjyect] in
// APIの結果を画面に描画する。
self?.write($0)
}
}
変数が空だったら初期化する
Rubyの場合は以下のような構文によって、@objectが空だったら初期化するというコードが使える。
@object ||= Object.new
これは部分的にキャッシュしたりするときにとても有効なのだが、残念ながらswiftにはない。
しかし、工夫をすると同じようなことができる。
self.object = self.object ?? Object()
??
の構文は左の値がnilの場合は右の値を使用するという構文。
以下と等価です。
self.object = self.object != nil ? self.object : Object()
キャスト
Swiftでキャストする場合は二種類の方法があります。
プリミティブ型っぽいもの(Integer,Int32、Int64,Flort,etc...)の場合は以下のように行います。
let floatObj = 0.5
let intObj = Int32(floatObj)
これは厳密にはキャストではなく型を変えてます。
なので、floatObj
とintObj
は別物です。
他のオブジェクト型の場合
class ObjectParent {}
class objectChild : ObjectParent {}
let objectParent = ObjectParent()
let objectChild = objectChild as? ObjectChild
この結果はobjectChild
にnil
が入ります。
objectChild
はOptinal型です。
as?
はダウンキャストができるかをチェックしてできない場合にnilを返します。
let objectParent = ObjectParent()
let objectChild = objectChild as! ObjectChild // 実行時エラー!
ObjectParent
はObjectChild
ではないので、as!
は強制的にダウンキャストを行うので上記の場合は実行時エラーが発生します。
let objectParent = ObjectChild()
let objectChild = objectChild as! ObjectChild
この場合はダウンキャストが成功します。
また、objectParent
とobjectChild
は同一オブジェクトになります。
let objectParent = ObjectChild()
let objectChild = objectChild as? ObjectChild
この場合もダウンキャストは成功しますがobjectChild
はOptional型なのでobjectParent
とobjectChild!
が同じオブジェクトになります。
主な使用例は以下のようになります。
let objectParent = ObjectChild()
if let objectChild = objectChild as? ObjectChild {
// objectChildの場合の処理を記載
} else {
// objectParentの場合の処理を記載
}
as?は非常に便利な構文です。
覚えておいて損はないでしょう。
また、OptionalValueの?
や!
とは別物なので注意してください。
enum
enumはかなり便利です。
enum Type {
case A
case B
}
このように普通に使えますし、関数を使って拡張もできます。
しかし、僕が一番便利だと思っているのは
enum TypeString:String {
case A = "a"
case B = "b"
}
// 文字列からEnumを生成。Optional型。
let typeString = TypeString(rawValue: "a")
print("type = |(typeString!.rawValue())"
この構文です。
enumに型をつけれるようになったので、型からenumが生成できるようになります。
どこで役に立つかというと、メタプロで外部からの情報を元にプログラムを動かす時にenumに定義していない値が送られてきたらエラーを発生させれるところです。
他にも、最終的には文字列として処理をしたいけどリテラルな値を持ち回りたくない(コンパイルエラーが出ないから)という時などにとても便利です。(もちろんString以外でも同様のことができます。NSUserDefaultsとも相性が良いです。)
ちなみに上記の構文ではtypeString
はOptional型になります。
定義されていない文字を渡すとnilが返却されます。
略式
enumは略して書くこともできる。
例えば以下のようなenumとfuntionがあったとする。
enum TestEnum {
case TestA
case TestB
}
func test(testEnum:TestEnum) {}
この関数を呼び出す場合は普通は以下のように記載します。
test(TestEnum.TestA)
しかし、swiftでは型がわかっている場合はオブジェクトを省略できます。
test(.TestA)
という記述が可能になります。
この省略記号はとても便利なのですが、XCode6.4現在ではコード補完がほとんど働きません。
Swich構文以外は働かないんじゃないかな・・・?
というくらい補完が出てきませんので、使うときには頑張ってタイピングしてください。
クラスの中身を書き換える。
Objective-cにあったカテゴリーという機能はSwiftではありません。
代わりにextension
という構文でクラスの中身を変えることができます。
Objective-cではファイル単位でカテゴリーを適用させることができましたが、Swiftでは一括適用をするしかありません。
(コメントにて指摘され、修正。)
extension String {
func text() -> String {
retrun "test"
}
}
書いておきながら、僕はあまりextension
を使いません。
extension
はいざという時に使うものであって、乱用すべきではないと思います。
しかし、設計上巨大なクラスになることが分かっているクラスを分割するなどの場合には結構有効だったりします。
また、NSArray
にはあるけどArray
にはない関数を追加する場合に使えます。
2015/07/16 追記
Arrayの初期化
Arrayの初期化は以下の構文で行えます。
どれも結果は同じになります。
// 空で初期化
let array:[String] = []
let array:[String] = Array()
let array:[String] = [String]()
let array = [String]()
// データを入れて初期化
var array:[String] = ["データ1", "データ2"]
var array = ["データ1", "データ2"]
Dictionaryの初期化
Dictionaryの初期化は以下の構文で行えます。
// 空で初期化
let array:[String: String] = Dictionary()
let array:[String: String] = [:]
let array:[String: String] = [String: String]()
let array = [String: String]()
// データを入れて初期化
let array:[String: String] = ["Key1": "データ1", "Key2": "データ2"]
let array = ["Key1": "データ1", "Key2": "データ2"]
ArrayもDictionaryも初期化する方法がいくつか用意されているので、お好みで使用してください。
たまにコンパイルエラーが発生する。
なぜかわからないが以下の様なクラス内のEnumを以下の様に生成するとコンパイルエラーが発生する。
let array = [ObjectClass.AnyEnum]()
この場合は諦めて以下の書き方で対応する。
let array:[ObjectClass.AnyEnum] = []
これ、Swiftのバグなんじゃないかな・・・?
Arrayの存在確認
SwiftのArrayにはcontains
関数が無い。
なので、Arrayに存在するかチェックするためには以下のように行う。
contains(["Test1", "Test2"], "Test1") // true
知らなかったがcontains
はSwift.contains
と書いてもコンパイルが通る。
ジャンプで飛べばわかるが、Swiftファイルの中にはInt64なども定義されてた。
このファイルがなんなのかは謎だが、Swiftの構文全てが定義されていそうな雰囲気のファイルだ。
for文
僕はswiftでfor文を使う場合は大抵以下の使い方が多いです。
let array = ["データ1", "データ2"]
for str in array {
print("\(str)")
}
swiftは配列に対してmap関数は用意されているが、each関数がないのでこの形を使うことがほとんどです。
(each関数があったとしてもswiftの場合はブロック構文はキャプチャーされるため、やはりfor文を使うケースの方が多いでしょう。)
しかし、たまにrubyでいうeach_with_indexを使いたくなります。
array = ["データ1", "データ2"]
array.each_with_index { |str, index|
p "#{index}:#{str}"
}
これをSwiftで行う場合は以下のように書くとできます。
let array = ["データ1", "データ2"]
for (index, str) in enumerate(array) {
print("\(index):\(str)")
}
気がついたら追記していきます。
P.S.
やはりSwiftは面白い。
今まではRubyが最強に気持ちいい言語だったけど、Swiftを知ってからSwiftの気持ちよさに酔いしれる・・・。
もう少しコード補完が早く、強くなってくれると嬉しいのでこの辺りはxCodeに頑張って欲しいw