Swiftを触ったことがないプログラマーのためのSwiftチュートリアル
はじめに
前回も似たような記事を書きました.
が,今回はSwift特化版です.
前回の記事では,SwiftUIチュートリアルを進めながら,各所に突っ込みを入れていたのですが,やはりSwift自身の基本知識がある程度必要だと感じました.
そこで今回の記事です.
この記事は言うなれば,SwiftUIで快適にアプリ開発を行うためのSwiftチュートリアルと言ったところです.
また,JavaやPython,C++などの他プログラミング言語をすでに学んでいる人向けの解説なので,やや中級者向けかもしれません.
「へえ,Swiftの構文ってこうなのか...」みたいな感じで流し読みしてくれると嬉しいです.
それでは始めます.
あ,ちなみに本記事はSwift 5です.
Swift 2やSwift 3などとは構文がやや違う場合もあります.
Swiftを触ったことがないプログラマーのためのSwiftチュートリアル
Xcode立ち上げ
XcodeはSwift特化のIDEですが,アプリ開発以外にも,もちろん簡単なSwiftプログラミングにも利用できます.
Get started with a playground > Blank でプロジェクトを作成しましょう.
そうすると,ほとんど真っ新なコードが出てきます.
まずはこれを全て消して,本当に真っ新にしてスタートです.
変数定義
基本からいきますよ.
Swiftでは変数定義は,後置型付け.
省略可能らしいが,Pythonなどと比べ型の決まりは厳しいらしいので,付けといたほうが無難.
1 2 3 4 5 6 7 8 9 10 | var hoge_i: Int = 10 // 整数 var hoge_i32: Int16 = 100 // 8bit 16bit 32bit 64bitまで var hoge_f: Float // 浮動小数点 hoge_f = 1.2 // 最初にメモリ確保して,後で初期化でも良い var hoge_s: String = "Swift" // 文字列 var hoge_c: Character = "a" // 最小単位文字列 (C/C++でいうchar) let const_hoge: Int = 100 // 定数 |
標準出力
これはPythonと似ている使い方.
ただ,与える引数名と,関数への明示的な引数の与え方に注意.(Pythonなどとは異なる)
1 2 3 4 5 6 7 | print("Hello, Swift") // 通常出力 (行末改行あり) print("Hello", ",", "Swift") // 引数は複数与えることができる print("Hello, Swift", terminator: "!\n") // 行末を改行"\n"から"!\n"に変更 print(1, 2, 3, 4, 5, separator: ",") // カンマ区切りでは,セパレータを指定可能 var a: Int = 10 print("a = \(a)") // 文字列中に変数展開 |
1 2 3 4 5 6 | # 出力 Hello, Swift Hello , Swift Hello, Swift! 1,2,3,4,5 a=10 |
if文
条件分岐の基本.
使い方は,C++とPythonの間って感じ.
条件式に括弧はいらないけど,スコープの { } は必要だし, else if はPythonのような elif という形ではない.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | var capacity: Int = 500 if capacity > 700{ print("L") } else if capacity > 400 { print("M") } else if capacity > 0 { print("S") } else { print("Error") } |
1 2 | # 出力 M |
if文 (三項演算子)
Swiftでももちろん三項演算子が使えます.
先ほどのコードを短くすると以下のようになります.
1 2 | var capacity: Int = 450 print(capacity > 700 ? "L": capacity > 400 ? "M": "S") |
いやー,簡潔でいいですね!
ただ注意として,条件式のあとにスペースを入れて ? を書く必要があります.
switch文
条件分岐の基本その2.
Swiftのswitch文は多機能で使い勝手が良いです.
まずは基本的な構文.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | var num: Int = 1 switch num { case 0: print("S0") // breakはなくても良い case 1: print("S1") // breakはなくても良い fallthrough // これがあると,以降のcaseも強制的に実行される case 2: print("S2") default: print("Switch END") } |
1 2 3 | # 出力 S1 S2 |
break はなくても,基本的にbreakしてくれる.
そして,なんといっても便利なのは以下のような使い方.
1 2 3 4 5 6 7 8 9 10 11 12 | var num: Int = 100 switch num { case 0...9: // 9を含む print("0~9") case 10..<100: // 100は含まない print("10~99") case 100...: // 100以上 print("a big number") default: print("Negative") } |
条件式として,範囲演算子 ( ... ) が使用できます.
1 2 | # 出力 a big number |
範囲演算子って,Rubyの影響受けてそうだよね.
for文
SwiftではPythonのような範囲for文です.
1 2 3 4 | for i in 1...5{ print(i, terminator: ", ") } // => 1, 2, 3, 4, 5, |
もちろん,後に紹介する配列も同じように回せます.
また,等差数列的な回し方も可能です.(for文の説明では無いが...)
1 2 3 | for i in stride(from: 0, to: 10, by: 3){ print(i, terminator: ", ") } // => 0, 3, 6, 9, |
while文
これも,特筆すべきところはないので,コードを見て理解して欲しい.
1 2 3 4 5 6 7 | var num: Int = 0 // 普通のwhile while num < 10 { print(num, terminator: " ") num += 2 } // => 0 2 4 6 8 |
1 2 3 4 5 6 7 8 | var num: Int = 0 // repear-while // 他言語のdo-whileと一緒で,必ず最初はスコープ内に入る repeat { print(num, terminator: " ") num += 2 } while num < 10 |
と,少し名前が特殊なだけ.
関数
Swiftにおける関数定義は,以下のようになります.
1 2 3 4 5 | func get_size(capacity: Int) -> String{ return capacity > 700 ? "L": capacity > 400 ? "M": "S" } print(get_size(capacity: 350)) // => S |
このとき, capacity: はラベルと呼ぶらしく,引数を渡すときにはこのラベルが必要です.
戻り値がある場合, -> String のように指定します
また,
1 2 3 4 5 6 | func print_args(args: Int...){ print(args[0]) print(args[1]) } print_args(args: 10, 100, 1000) |
1 2 3 | # 出力 10 100 |
のように,動的に引数をとることも可能です.
そして戻り値はタプルも可です.
1 2 3 4 5 | func get_add_sub(a: Int, b: Int) -> (Int, Int){ return (a+b, a-b) } print(get_add_sub(a: 10, b: 5)) |
1 2 | # 出力 (15, 5) |
ここで,以下のように戻り値にラベルをつけることもできます.
1 2 3 4 5 6 7 8 | func get_add_sub(a: Int, b: Int) -> (add: Int, sub: Int){ return (a+b, a-b) } let result = get_add_sub(a: 10, b: 5) print(result.add) print(result.sub) |
クロージャ
Swiftでよく出てくるのがクロージャ.
無名関数とも言う.
これでもかってくらい,クロージャを多用してくる,超重要構文.
特に,関数の引数としてクロージャを渡す,という形が多い.
まあ,クロージャ自体は他の言語でもあるんだけどね.
基本的な使い方は以下のような感じ.
1 2 3 4 5 | var add = { (a: Int, b:Int) -> Int in a + b } print(add(10, 5)) // => 15 |
そしてこのクロージャ,省略しようと思えばかなり省略できてしまう.
まず,処理部が一文の場合, return 省略のほかに,戻り値の型も省略ができる.
1 2 3 | var add = { (a: Int, b:Int) in a + b } |
また,予め型の宣言をしていれば,初期化の際には型の明記は不要になる.
1 2 3 4 5 | var add: (Int, Int) -> Int add = { (a, b) in a + b } |
さらに,引数名にこだわりがなければ, $0, $1, ... で代替可能である.
1 2 3 | var add: (Int, Int) -> Int add = { $0 + $1 } |
これが一番省略されたクロージャ.(Swiftっぽくなってきた)
よく関数の引数でこういうクロージャが渡されているのをSwiftではよく見る.
多分,この後も出てきます.
配列: 前編
Swiftの配列は定義は普通だけど,扱いに少しだけ癖がある.(ように感じる)
1 2 | var arr: [Int] = [1, 5, 3, 7, 4, 12, 3, 8, 11, 2, 3, 5] print(arr) |
1 2 | # 出力 [1, 5, 3, 7, 4, 12, 3, 8, 11, 2, 3, 5] |
そう,定義は至って普通.
ここで,配列に対して最大値と最小値を見つける場合はどうすれば良いかと言うと,こうします.
1 2 | print(arr.min()) print(arr.max()) |
なんら不思議なことはありませんが,出力を見ると,
1 2 3 | # 出力 Optional(1) Optional(12) |
Optional() というものに包まれて返ってきたようです.
しかも,XcodeはなにやらWarningも出しています.
話は逸れますが,この Optional 型について先に簡単に説明します.
Optional型
Optional 型は一言で言い表すならば「nilを許容する変数」です.
nilは他言語のnullと同義で,何もデータがない状態を指しています.
本当はもっと奥が深いのですが,ここでは簡単にコードとコメントだけで解説します.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | var a: Int = 10 // 通常のInt型 var b: Int? = 10 // Optional<Int>型 var c: Int! = 10 // Optional<Int>型 (暗黙的アンラップOptional) // a = nil // できない b = nil // できる (もちろん c = nil もできる) print(a) // => 10 print(b) // => nil (Warningが出る) print(c) // => Optional(10) (Warningが出る) // print(b!) // => Error : 強制的なアンラップをするがnilなのでアンラップ失敗 print(c!) // => 10 : 強制的なアンラップ (中身がnilじゃなかったので成功) print(b ?? 0) // => 0 : nilだった場合にデフォルト値を表示させるようにする (Optional Binding ?) // print(a + b) // => Error : bは"?"によるOptional型なので暗黙的にアンラップは不可能 // print(a + b!) // => Error : bがnilだったのでアンラップ失敗 b = 10 print(a + b!) // => 20 : bがたまたまnilではなかったので強制的なアンラップに成功した例 (nilだった場合はエラー) print(a + c) // => 20 : 非Optional型との計算では,"!"によるOptional型は暗黙的にアンラップされる |
結構ややこしいのですが,これだけでだいぶOptional型の扱いは分かるかと思います.
さっきほどの最小値・最大値も,何かしらの手法でアンラップしてあげれば良いということになりますね.
詳しくは以下の記事で解説されているので,余力のある人は見てみると良いかも.
配列: 後編
さて,配列に戻ってきました.
最小値・最大値は良いとして,次に紹介するのはモデルとかでよく使いそうな,データ抽出です.
例えば以下のような操作.
1 2 3 4 5 6 7 8 9 10 11 | var countries: [String] = ["Japan", "America", "Canada", "Australia", "China"] // "A"で始まる最初の国とインデックス var extracted = countries.first(where: {$0.hasPrefix("A")}) var index = countries.firstIndex(where: {$0.hasPrefix("A")}) print(index ?? "None", extracted ?? "None") // "C"で始まる最後の国とインデックス extracted = countries.last(where: {$0.hasPrefix("C")}) index = countries.lastIndex(where: {$0.hasPrefix("C")}) print(index ?? "None", extracted ?? "None") |
1 2 3 | # 出力 1 America 4 China |
このようにSQLのような操作をサポートしています.
他にも,
1 2 3 | // 一番最初の"America"のインデックス var index = countries.firstIndex(of: "America") print(index ?? "None") // => 1 |
のように,完全一致のケースも可能です.
どちらかといと, (where: ) の方が,引数にクロージャを与える形なので汎用性は高そうです.
もっと実用的な使い方は,この後の後で紹介する構造体で説明します.
プロトコル (protocol)
プロトコルは,PHPなどでいうインターフェース的な働きをします.
言い換えれば,仕様書のようなもので,Swiftではクラスベースというより,プロトコルベースの開発が基本なようです.
さて,早速何か定義してみます.
1 2 3 4 5 6 7 8 | protocol base_protocol{ // プロパティ var num: Int { get set } // 読み書き可能 var name: String { get } // 読みだけ // メソッド func calculate(num: Int) -> Int } |
仕様書なので,何か決まった値が必ずしも入るわけではありません.
あくまで,これを継承する構造体やクラスの実装を容易にするためのものです.
今,自分で適当にプロトコルを実装してみましたがSwiftUIでは便利なプロトコルがたくさんあるので,それらを使って構造体やクラスを設計する形がほとんどになるかと思います.
Swiftだと,プロトコルを上手く使いこなせる人こそ真のSwifterなのかな,知らんけど.
構造体 (struct)
これ以上継承しないクラス.
参照型ではないクラス.
といったところでしょうか.
さっき作成したプロトコルを継承して実装してみましょう.
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 | protocol Base_protocol{ // プロパティ var num: Int { get set } // 読み書き可能 var name: String { get } // 読みだけ // メソッド (+ mutating) mutating func calculate(num: Int) -> Int } // 継承して定義 struct Addition: Base_protocol{ // 初期化 init() を使っても良い var num: Int = 0 // Stored Propaty var name: String { "Add Operation" } // Computed Propaty (この場合,本当はStored Propatyを使うべき) mutating func calculate(num: Int) -> Int { self.num += num // もしメソッド内でストアドプロパティを弄るならば,mutating が必要 (面倒くさくない??) return self.num } } var operation = Addition() print(operation.name, operation.num) operation.calculate(num: 5) print(operation.name, operation.num) |
1 2 3 | # 出力 Add Operation 0 Add Operation 5 |
プロトコルと構造体でのメソッド定義で,自身のプロパティを使って計算をするならば, mutating func として定義しなければならない.
面倒臭いですね.
そしてここで,2つのプロパティが登場します.
ストアドプロパティ (Stored Propaty)とコンピューテッドプロパティ (Computed Propaty) です.(カタカナ英語だとダサいね,英語で表記します.)
Stored Propatyとはいわゆる値やデータを保持する普通のプロパティです.
一方でComputed Propatyはクロージャのように,動的なものを扱って都度出力を計算するプロパティです.
ただ,クロージャはStored Propatyで一度しか呼ばれないので,根本的に違うものです.
「じゃあいつ使うんだ」と思うかもしれませんが,例えば他のStored Propatyを引数とする関数の戻り値を使う場合はComputed Propatyが使えます.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | struct Addition: Base_protocol{ // 初期化 init() を使っても良い var num: Int = 0 // Stored Propaty var name: String { "Add Operation" } // Computed Propaty // var status: String = sign(num: num) // Stored Propaty だとnumが不定なのでエラー var status: String { sign(num: num) } // Computed Propaty で都度計算するようにする mutating func calculate(num: Int) -> Int { self.num += num // もしメソッド内でストアドプロパティを弄るならば,mutating が必要 (面倒くさくない??) return self.num } } func sign(num: Int) -> String { return num > 0 ? "Positive": num < 0 ? "Negative": "Zero" } |
さてここで,この構造体を複数持った配列の扱いについて見ていきます.
先ほど,配列の操作の仕方を解説したので,おそらく以下のコードが理解できるはずです.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | var op1 = Addition() var op2 = Addition() var op3 = Addition() op1.calculate(num: 4) op2.calculate(num: -5) op3.calculate(num: 10) op2.calculate(num: 5) var operations: [Addition] = [op1, op2, op3] // numが0のものを見つける var equal_zero_index = operations.firstIndex(where: {$0.num == 0}) print(equal_zero_index ?? "None") // => 1 |
まあ,意味のないコードですが,許してください.
ここで, $0 が operations: [Addition] = [op1, op2, op3] の各要素 (インスタンス) を表していることが分かります.
なので, {$0.num == 0} のような使い方ができるのです.
クラス (class)
次にクラス.
基本的には構造体とあまり変わらないが,違うといえば違う.
まずは,先の構造体と同じものをクラスに書き換えてみます.
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 | protocol Base_protocol{ // プロパティ var num: Int { get set } // 読み書き可能 var name: String { get } // 読みだけ // メソッド func calculate(num: Int) -> Int } // 継承して定義 class Addition: Base_protocol{ // 初期化 init() を使っても良い var num: Int = 0 // Stored Propaty var name: String { "Add Operation" } // Computed Propaty // var status: String = sign(num: num) // Stores Propaty だとエラー var status: String { sign(num: num) } // Computed Propaty func calculate(num: Int) -> Int { self.num += num // class ならmutatingはいらない return self.num } } func sign(num: Int) -> String { return num > 0 ? "Positive": num < 0 ? "Negative": "Zero" } |
このように,ほとんど同じにかけるけれど,構造体と違って mutating は必要なし.
また,クラスは参照型なので以下のような扱い方ができる.
1 2 3 4 5 | var operations: [Addition] = [op1, op2, op3] for op in operations{ op.num = 100 } |
これは,構造体だと参照型ではないのでエラー.
あと,もちろん private などのアクセス修飾子も付けることができます.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | class Hoge{ // クラス内でのみ使用可 private var a: Int = 10 // 同ファイル内同でのみ使用可 fileprivate var b: Int = 10 // どこでも呼び出せる.継承はされないしオーバーライド不可 public var c: Int = 10 // 一つのプロジェクト内でターゲットが同じであれば呼び出せる.継承とオーバーライド可 (デフォルトではこれ) internal var d: Int = 10 // setはprivate,getはinternal private(set) var e: Int = 10 // どこでも呼び出せるし,継承もされる,オーバーライドもできる open var f: Int = 10 } |
アクセス修飾子が多い.
使い分けが難しいが, private と fileprivate あたりを使いこなせれば良いのでは,とも思う.
おわりに
長かった...
最後まで読んでくれてありがとうございます.
これで,Swiftに入門は出来たのでは無いでしょうか.
みなさんもこれを機にSwiftを私と一緒に始めませんか?
今度, Swiftの構造体についてもっと詳しく調べたいなあ (願望)
???? Swiftでのアプリ開発を学ぶなら
- タグ:
- Swift