こんにちは、Swift言語にどっぷり浸かってる渡部です。
Swift言語でプログラミングを始めて数ヶ月経ちます。
今回は、気になったことや、覚えておきたい事、Java などの他のオブジェクト指向言語でプログラミングされていた方が Swift言語の実装で詰まりそうな事をまとめてみました。
環境は、Xcode 6.1.1を使用しています。
Contents
モダンプログラミング言語、Swift にしびれた!
1.for 句
3種類の記述が可能です。
for – in 文を使って、要素分ループします。
|
1 2 3 4 |
let names = ["aaa", "bbb", "ccc"] for name in names { println("\(name)") } |
指定数分ループします。
|
1 2 3 |
for var i = 0; i < 5; i++ { println(" \(i)") } |
レンジ(範囲)指定した範囲でループします。
|
1 2 3 |
for i in 0..<5 { println("\(i)") } |
僕はレンジ指定での記述が気に入りました。
もちろん for each文に該当する for – in文があるのも如何にもモダンな言語です。
2.列挙型(enum)
宣言の仕方
|
1 2 3 4 5 6 7 8 |
アクセス修飾子 enum 定義名称: 型名 { case 定義1 = 値 case 定義2 = 値 func メソッド名(引数) -> 戻り値型 { return 戻り値 } } |
enum で定義した値を取得したい場合は、rawValue を使用します。
|
1 2 3 4 5 6 7 |
enum hogeho: Int { case hoge1 = 1 case hoge2 = 3 } var aaa: hogeho = hogeho.hoge1 println(aaa.rawValue) // 1 |
enum のメソッド内で、自分が指定されている値を取得する場合は self を使用します。self に設定されている値が入っていますので、その値と enum で定義した値を比較して処理を実施します。
|
1 2 3 4 5 6 7 8 9 10 11 12 13 |
enum hoge: Int { case hoge1 = 1 case hoge2 = 3 func hogeA() { if (self == hoge.hoge1) { println("hoge1") } if (self == hoge.hoge2) { println("hoge2") } } } |
enum にメソッドを記述出来るのはかなり凄い要素だと思いますが、どれほど活かせるかは別ですね。
僕は今のところ、この機能を活かせてないです。
3.アクセス修飾子
アクセス修飾子は、下記の3つがあります。
Swift言語は Java言語と違い、クラス = ファイルではないので、Java言語と同じように考えてしまうと、思ったように動作しない場合があるので注意が必要です。
| public | モジュール内外からもアクセス可能。 |
| internal | モジュール内からアクセス可能。またアクセス修飾子を付けずに、クラス・メソッド・変数・定数を定義した場合は、このアクセス修飾子になります。 |
| private | ファイル内のみアクセス可能 |
モジュールは、配布する際の Framework やアプリケーションの事です。
C++、C#もファイルとクラス名が別だったりしますので、この記述に違和感はないのですが、クラスの外に変数が定義出来たりと、ちょっとオブジェクト指向から外れた記述が出来るのがなんとも。。。
private の使い方としては、外部からアクセスさせたくない変数やメソッド等の隠蔽と、インナークラスや無名クラスを privateクラスとしてクラスから追い出して、同ファイルで定義するやり方があっているんじゃないかと思っています。
4.クラス定義
クラスを作成する場合は、通常はファイルを作ってから、そのファイルにクラスを定義します。
ファイルの作成は、Xcode の「File」→「New」→「File」で、「Choose a template for your new file:」画面が表示されるので、そこから「iOS」か、「OS X」を選択し、「Source」→「Swift File」を選択し、名前を付けて作成します。
「import Foundation」があるだけの File が作成されますので、そこに記述していきます。
クラスの定義の仕方は、下記の通りです。
|
1 2 3 |
[アクセス修飾子] class [クラス名] { } |
クラスのアクセス修飾子で定義した以上に公開範囲の広いアクセス修飾子のメソッドや、定数、変数を持つことは出来ません。
publicクラス、public変数は同じ公開範囲なのでOK。
|
1 2 3 |
public class aaa { public var sss: String = "" // OK } |
internalクラス、public変数は変数の方が公開範囲が広いのでNG。
|
1 2 3 |
internal class ddd { public var sss: String = "" // NG } |
privateクラス、public変数は変数の方が公開範囲が広いのでNG。
|
1 2 3 |
private class ccc { public var sss: String = "" // NG } |
クラスのデフォルトのアクセス修飾子が internal になっていますので、アクセス修飾子を付けずに定義した場合は、internal、private 以下の変数・定数・メソッドしか定義できませんので注意して下さい。
5.構造体
Swift言語では、クラスの他に構造体(struct)を使用出来ます。クラスと構造体とでは、次の4つが異なりますので注意が必要です。
1.継承が使えない
2.終了処理(deinit)が出来ない(初期化処理( init()) は使える)
3.構造体で定義した値は値渡しになり、クラスで定義した値は参照渡しになる(ここ大事!)
4.型のキャストが出来ない
参照渡しで配列を渡したい場合は、Array、Dictionary の代わりに NSArray、NSDictionary を使う必要があります。
この辺の言語とライブラリ間のつながりが、Objective-C からの負の遺産と思われる感じがして凄く嫌です。
NSってなに?ノースキンなん?って感じです。また、構造体とクラスが共有してるのも気持ち悪いです。
6.クラスメソッド定義
クラスメソッドは、「class func」で定義します。また、クラスメソッドを、インスタンスのメソッド内から呼びたい場合は、「self.dynamicType」を使用します。
|
1 2 3 4 5 6 7 8 9 |
class aaa { class func fff() -> String { return "hogehoge" } func hoge() { self.dynamicType.fff() } } |
7.クラス変数
クラス変数とは、クラスのインスタンス全てで共有される情報ですが、現在、Swift言語ではその機能が存在しません。
Swiftで変数を定義する場合は「var」を使用しますので、「static var」、「class var 」でも良さそうですが、出来ません。
8.クラス定数
クラス定数も無いです。通常の定数定義は「let」を使用します。
ファイル単位で管理されているので、ファイルの private変数として定義する方法もありますが、オブジェクト指向には合いません。
クラスメソッドからは、クラス変数もクラス定数も呼べませんので、実質使い物になりません。単純な計算させるロジックや、変換関係等などのユーティリティ用メソッド・クラスとしてなら十分なんですが、この辺が Swift言語の今の仕様の弱いところですね。
9.シングルトン
7.8.でも触れていますが、Swift言語ではクラス変数・定数が使えないので、クラスメソッドの実装には苦労します。
それを解決するための、シングルトンですが、クラス定数が使えないのでちょっと小細工が必要です。
こんな感じで実装します。
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
class Singleton { class var getInstance: Singleton { struct Static { static let instance: Singleton = Singleton() } return Static.instance } // 生成出来ないようにする。 private init() {} public func hogehoge() { println("hogehoge") } } |
クラス変数が定義出来ないので、struct をかませて Static化します。本当に気持ち悪いです。
10.関数(メソッド)とクロージャ
関数(メソッド)とクロージャの関係ですが、無名関数が(厳密には違いますが)クロージャですので、関数の定義方法と、クロージャの定義方法を合わせて記載します。
関数は次のように定義します。
|
1 2 3 |
アクセス修飾子 func 関数名(引数名: 引数型) -> 戻り値型 { return 戻り値 } |
クロージャは次のように定義し、 {}内がクロージャになります。こうやって比較して見ると、クロージャは関数名がない関数だというのがわかるかと思います。
|
1 2 3 |
{ (引数名: 引数型) -> 戻り値型 in return 戻り値 } |
また、型推論でもっとシンプルな記述も可能です。
クロージャを実際に記述してみましょう。
|
1 2 3 4 5 6 7 8 9 10 11 |
var numbers = [20, 19, 7, 12] // 通常パターン let a = numbers.map({(number: Int) -> Int in return 3 * number }) println(a) // [60, 57, 21, 36] // 型推論 let b = numbers.map({ number in 3 * number }) println(b) // [60, 57, 21, 36] |
こんな感じに記述出来ます。
アプリケーションを作ってる際に、アニメーションの記述はクロージャを使いますので、知っていた方が良いと思います。
11.引数
可変引数は、…(ピリオド3つ)で定義します。中身は配列で渡されます。
|
1 2 3 4 5 6 7 8 |
func hogehoge(val: Int...) { for v in val { println(v) } } hogehoge() hogehoge(1,2,3,4,5) // 1 2 3 4 5 |
デフォルト引数は、= の後に値を設定することでデフォルトの値が設定出来ます。
|
1 2 3 4 5 6 |
func hogehoge(val: Int = 0) { println(val) } hogehoge() // 0 hogehoge(val: 9) // 9 |
Swift言語では、メソッドの引数はデフォルトでは let で定義されおり、代入するとエラーになります。
|
1 2 3 |
func hogehoge(val: String) { val = "aa" // エラーになる。 (letで定義されていることになっている) } |
引数を関数の中で変更し、その結果を受け取りたい場合は、inout で宣言する。また、値を渡す側は & を付けて渡します。
|
1 2 3 4 5 6 7 |
func hogehoge(inout val: String) { val = "aa" } var str = "" hogehoge(&str) println(str) // aa |
12.戻り値
複数の値を戻り値にすることが可能です。
|
1 2 3 4 5 6 7 |
func hogehoge() -> (str: String, num: Int) { return ("", 0) } var val = hogehoge() println(val.num) println(val.str) |
複数の戻り値を定義出来ます。これをしたい事が時々あるので本当にありがたい。
13.初期化・終了処理
インスタンスの生成時や開放時に自動で呼ばれるメソッドです。どちらも記述は必須ではありません。
初期化処理(イニシャライザ)は、コンストラクタですね。インスタンス化する際に呼ばれます。
|
1 2 3 |
init() { } |
終了処理(デイニシャライザ)は、インスタンスが破棄される際に呼ばれる。
|
1 2 3 |
deinit { } |
14.Optional(?、!)
Swift言語では、安全性を高めるためにエラーの原因になりやすい。nil(ヌル)の設定が基本的に出来ません。
どうしても nil を使用したい場合は、明確な宣言が必要になります。
それが Optional型と呼ばれ、型宣言の後ろに「?」をつけることで定義が出来ます。
|
1 2 3 4 5 6 |
// nilの設定は通常はできない。 var hogeA: String = nil // エラー // optional型で宣言する。 var hogeB: String? = nil // ?を使って定義すれば、nilを代入可能 var hogeC: Optional<String> = nil // ? は Optional<形名> でも同じ用に定義可能 |
一旦 Optional型で宣言すると、通常の型ではなく、Optional型として扱われるため、例えば Optional なしの String と Optional ありの String では、Optional ありの String の変数に Optional なしの String を代入出来ますが、その逆は出来ません。
どうしても代入したい場合は、変数の後ろに「!」をつけることで、nil 以外が入ってる事が明示され、代入可能になります。
因みに、「!」を付けた状態で nil が入っていた場合は、実行時に ”fatal error: unexpectedly found nil while unwrapping an Optional value” という例外がスローされますので、事前に nil のチェックを行って下さい。
|
1 2 3 4 5 6 |
var hogeA: String? = nil var hogeB: String = "" hogeA = hogeB // OK hogeB = hogeA // エラー hogeB = hogeA! // コード上ではOK。でも実行時はエラーになるので、事前チェックが必要になる。 |
これはかなり気に入った機能です。もちろんこれによって記述が冗長になったりしますが、安全性は段違いに高くなりますね。
15.型チェック・キャスト
対象のインスタンスの型をチェックする場合は、is を使用して判断を行います。
|
1 2 3 4 5 |
var obj: NSObject = "aaa" if (obj is String) { println("String") // String } |
対象の方へキャストする場合は、as を使用して代入を行います。
|
1 2 3 |
var obj: NSObject = "aaa" var str: String = obj as String |
16.CocoaPods の使い方
標準で用意されている以外の UIコントローラなどの外部ライブラリを使用したい場合に、iOSライブラリ管理ツールの CocoaPods を使用する事で、非常に楽に外部のライブラリを使用する事が出来ます。
CocoaPodsがインストールされていないようでしたら、下記のコマンドを実行して下さい。
ruby が入ってること事が前提ですが、下記のコマンドを実行して、インストール、セットアップして下さい。
インストール
|
1 |
$ sudo gem install cocoapods |
セットアップ
|
1 |
$ pod setup |
CocoaPods を使用する大まかな手順としては、Podfileファイルの作成、ライブラリのインストール、Bridging-Headerファイルの作成、 Bridging-Headerファイルの編集になります。
Podfileの作成
対象となるプロジェクトのプロジェクトファイル([プロジェクト名].xcodeproj)があるディレクトリで、Podfileという名前の空のファイルを作成します。
記述する内容は、platform や pod などを定義します。pod は必須です。使用したいライブラリのドキュメントや、Github のライブラリのドキュメントなどに記載があるので、参考にしてください。
MMDrawerControllerの場合は、こんな感じの Podfileファイルを記述します。
|
1 |
pod 'MMDrawerController', '~> 0.5.7' |
ライブラリのインストール
Podfileファイルを作った初回の場合は、先ほどの Podfileファイルを作ったディレクトリで、以下のコマンドを実行して、ライブラリのインストールを行います。
成功すると [プロジェクト名].xcworkspace というファイルが出来ます。
実行する際は該当のプロジェクトは閉じてから、実行して下さい。
|
1 |
$ pod install |
また、再度 Podfile を編集してライブラリの追加、削除した場合は、次のコマンドを実行して、[プロジェクト名].xcworkspace の内容を更新します。
|
1 |
$ pod update |
bridging headerの作成・編集
プロジェクトファイル([プロジェクト名].xcodeproj)をクリックして、プロジェクトを起動します。(ここで開くプロジェクトファイルは、xcodeproj の方です。先ほど作ったファイルではないので注意して下さい。)
メニューから新規ファイルを作成します。(メニュー[File]→[New]->[File])
「Choose a template for your new file」ダイアログ画面が表示されますので、[iOS]→[Source]→[Cocoa Touch Class]を選択し、「Next」ボタンを押下します。
ここで作成したファイルはすぐに消すので、クラス名は何でも構いません。
今回、ファイル名は [dummy] にしました。Language は [Objective-C] にし、[Next]ボタンを押下します。
[bridging header]ファイルを作るか聞かれますので、[Yes]ボタンを押下します。
[プロジェクト名]-Bridging-Header.h ファイルが出来ますので、このファイルに、追加するファイルのヘッダーを記述します。
また、dummy のファイル、[dummy.h]、[dummy.m] は削除します。
MMDrawerController の場合は、作成した Bridging-Header.h ファイルに以下のように記述します。
|
1 |
#import <MMDrawerController.h> |
プロジェクトを閉じます。
使い方
これ以降のプロジェクトは、全てライブラリのインストールで作った [プロジェクト名].xcworkspace を開いて、プロジェクトを更新していきます。
一応ビルドを実施して、エラーが無いことを確認して下さい。
17.Swift言語のサンプルソースが無い場合の対処方法
新しい言語のためか、Swift言語のサンプルとなるソースがない場合が多々あります。
通常であれば、「Swift」+ 「やりたい事」で検索すると思いますが、ヒットしない場合は以下の手順で検索します。
1.検索キーワードを、「Swift」の代わりに「iPhone 開発」 「iOS 開発」、「Objective-C」などにし、+「やりたい事」で検索します。
2.検索結果として Objective-C のソースが出てくると思いますので、そのソースの肝となる部分を探します。
3.その肝の部分(メソッド名)と、「Swift」で検索する。
それでもダメな時は、Objective-CのソースからSwiftへ変換しますが、気をつける点としては、初期化の部分と、定数の書き方がかなり違うので、そこだけ注意です。
まとめ
最後まで読んで頂き、ありがとうございました。
オブジェクト指向プログラミング言語好きの僕からすると、interface定義が protocolなのが好ましく思えます。
しかし、enumの謎実装だったり、構造体定義があったり、プリミティブな型とか、NSArrayなど命名が気持ち悪いものを使わないといけないとか、なにそれ的なのが多いのも事実です。
クラス定義周りが未実装だったりと、まだまだこれからな部分もありますね。
それでも Objective-C の記述で躓いた僕としては、Swift は記述が非常に楽で、さくさく記述できるのが本当に楽しいです。
今後も付き合っていくことになると思いますが、さらなる言語の発展に期待しています。
エクステンション(extension)や、ジェネリックなど、今回記載しなかった機能に関しても、いずれまとめたいと思います。