iOSアプリ with Swift 第1回 – Swiftの文法編 –
なんかこう、iOSアプリを作ろうとするにあたって、Swift独特の文法というか、書き方だけをまとめたようなのがパッと見つからない気がしたので書いてみた。
他のプログラミング言語をやってきて、Swiftをちょっとやってみよう、とか考えている人はだいたいこの辺を知っておけば良いのではないだろうか。
いや、まぁ、以下に書いたのが全部Swift独特か、って言ったら全然そんなことはないんだけど、Swift特有の安全さ、というのを武器にして書くには以下のことを理解しておくと、Swiftらしいコードが書けるのではないかと。
ちなみに、基本文法は押さえている前提で書いているので、まず基本文法はがあやふやな人は、まずは公式のSwiftのチュートリアルをやったほうが良い。
The Swift Programming Language (Swift 3.1): A Swift Tour
本日のお品書き
- Optional
- func
- enum & switch – case
- set, get, willSet, didSet
- lazy
- guard
- defer
- Generics
- Extension
- Protocol
- Protocol + Extension
- Delegate(委譲)パターン
Optional
すべての基本。
optionalとはそれがnilになり得る変数、または状態のこと。
nilになり得ない変数、状態のことは、 un-optionalと言う。
すべてのオブジェクトにはoptionalか、un-optionalか、が決まっているし、自分で変数を定義するときは決めなければならない。
un-optionalの変数はoptionalの変数に入れることはできるが、optionalの変数はun-optionalに入れることができない。
Swiftはこの仕組を最大限に活用して、ぬるぽで死ぬ危険性を回避して安全に書くことが出来る。
var hoge: String? = "hoge"
var fuga: String = "fuga"
hoge = nil
fuga = nil // Error
hoge = fuga
fuga = hoge // Error
強制的にoptionalの変数をun-optionalの変数に入れたければ、optionalの変数のあとにつづけて!をつければ良い。
これをoptional変数のunwrapという。
安全にwrapされていたものを強制的に外す(unwrap)イメージ。
fuga = hoge! // unwrap
もし上記のhogeが動作上nilになった場合は、アプリはクラッシュする。
したがって、基本的にunwrapする場合は、明らかにnilになり得ないような場合にのみ限定して使うべき。
それ以外の場合は、後述するようにguard構文を使って回避するべし。
func
なんてことはない。
Swiftにはenum, struct, classなどのオブジェクトを作ることができるが、class以外のオブジェクトにもメソッドを追加することができる。地味に便利。
enum UserRole {
case user
case admin
case owner
func whichRole(roleNumber: Int) -> UserRole? {
switch roleNumber {
case 0:
return .user
case 1:
return .admin
case 2:
return .owner
default:
return nil
}
}
}
struct User {
let id: Int
let name: String
let role: UserRole
let email: String
let isPurchased: Bool
let expired: NSDate
func isAvailablePurchasedProduct() -> Bool {
return isPurchased && expired.timeIntervalSinceNow > 0
}
}
あれ、じゃあstructとclassって同じじゃね、って思うけど、地味にそれぞれできることが違う。
classはオーバーライドできるけど、structはできなかったり。
詳しくは下記。
Swiftのクラスと構造体の使い分けについてのメモ
それぞれの役割を考えて使い分けるべし。
enum & switch – case
enumとswitch – case文のあわせ技。
swiftのswitch – case文はInt以外にもString、enum等にも使うことができる。
特にenumを使ったswitch – case文はSwiftではよく使われる。
enum UserRole {
case user
case admin
case owner
func getUserRoleNumber() -> Int {
switch self {
case UserRole.user:
return UserRole.user.hashValue
case UserRole.admin:
return UserRole.admin.hashValue
case UserRole.owner:
return UserRole.owner.hashValue
}
}
}
set, get, willSet, didSet
swiftにはどんな変数にもsetter、getterメソッドを簡単に追加することが出来る。
var _fugafuga: String?
var fugafuga: String {
set(value) {
_fugafuga = value
}
get {
if _fugafuga == nil {
return "unknown"
} else {
return _fugafuga!
}
}
}
さらに便利なことに、willSet、didSetという、変数への代入直前、代入直後のコールバック関数を簡単に追加することが出来る。
var hogehoge: String = "Hogehoge" {
willSet(newValue){ // 変更後のhoge
print(hoge) // 変更前のhoge
}
didSet(oldValue) { // 変更前のhoge
print(hoge) // 変更後のhoge
}
}
大変に便利。
特に特定の値の変化に同期してViewにも変更を加えたい時に、didSetをよく使う。
あんまり多用すると処理の順番が追えなくなって泣きを見るので、気をつけるべし。
lazy
lazyを付けたプロパティは、そのプロパティが必要とされるタイミングで値が参照される。
var initialRole = UserRole.admin
class UserManager {
lazy var currentUserRole = initialRole
}
let manager = UserManager()
initialRole = UserRole.user
print(manager.currentUserRole) // lazyをつけないとadmin、つけるとuserになる
便利そうなんだけど使い所がわからなくて使っていない件。
guard
if文の逆みたいなものだけど、optional変数を扱うSwiftならではの構文。
Swift以外でif文でoptionalを判断する場合、普通は次のように書く。
var optionalValue: Int?
if optionalValue != nil {
// nilチェック済みの処理
} else {
throw NSError(domain: "optionalValue is optional", code: 0, userInfo: nil)
}
これをguardを使うと次のようになる。
guard optionalValue != nil else {
throw NSError(domain: "optionalValue is optional", code: 0, userInfo: nil)
}
// nilチェック済みの処理
if文だとnilチェック済みの処理が一段インデントした箇所に書くことになるが、guard文だとそれがない。
なので複数の値をnilチェックしたい場合、if文だとインデント地獄に陥るが、guardだとそれがない。便利。
ちなみに上記guard文は下記のようにも書ける。というか、下記のように書くことが多い。
guard let _optionalValue = optionalValue else {
throw NSError(domain: "optionalValue is optional", code: 0, userInfo: nil)
}
// nilチェック済みの処理 : _optionalValueは Int? ではなく、 Int になるので、以降の処理はこれを用いる
これはnilチェックをしつつ_optionalValue変数に入れているので、guard文以降は_optionalValueをIntとして扱って処理をすることが出来る。
optionalValueをわざわざunwrapして処理とかしなくて良い。
また、この書き方は以下のように型チェックにも応用できる。
var anyValue: Any = "hogehoge"
guard let stringValue = anyValue as? String else { // anyValueがStringとして扱えるなら。
throw NSError(domain: "anyValue is string", code: 0, userInfo: nil)
}
// 以降はstringValueを使って処理をする
とっても便利。
defer
スコープを抜けた時に必ず行う処理を記述することができる。
先のguard文と合わせてよく使われる…ようだ。
便利そうだけど使い所がまだつかめてなくて、個人的にはあまり使っていない。
いや、実際使いそうなんだけど、非同期処理がはさんであるとdefer使えないし…。
とにかく以下の用に使うようだ。
func hogeFunc(hoge: Any) throws -> Bool {
defer {
print(hoge) // NSErrorを投げたり、returnする値にかかわらず必ず実行される処理
}
guard let _hoge = hoge as? String else {
throw NSError(domain: "hoge is string", code: 0, userInfo: nil)
}
if _hoge == "Hogehoge" {
return true
} else {
return false
}
}
Generics
まぁ、どこにでもあるジェネリクス型。
これも多用すると元の型がわかんなくなるので注意。
class ObjectCountedList<T> {
let count: Int
let list: [T]
init(count: Int, list: [T]) {
self.count = count
self.list = list
}
}
let user = User(id: 0, name: "hoge", role: UserRole.user, email: "hoge@example.com", isPurchased: true, expired: Date())
let userObjectList = ObjectCountedList(count: 1, list: [user])
let url = URL(string: "https://www.google.co.jp")
let urlObjectList = ObjectCountedList(count: 1, list: [url])
Extension
swiftには既存のオブジェクトを拡張してメソッド等を追加することができるextensionというのがある。
たとえば既存のArrayのオブジェクトに対して、シャッフルするメソッドを追加するには以下のように書く。
extension Array {
mutating func shuffle() {
for i in 0..<self.count {
let j = (self.count - 1) - i
let k = Int(arc4random_uniform(UInt32(j + 1))) // 0 <= k <= j
if j != k {
swap(&self[k], &self[j])
}
}
}
}
余談だが、extensionを使って既存のクラスに便利メソッドを頑張って実装したものの、実際にはすでに似たようなやつが公式で実装されていたことがよくある。かなしい。
Protocol
javaでいうinterfaceみたいなもの。
オブジェクトの振る舞いを予め規定することができる。
後述のDelegateモデルでよく使われる。
protocol HogeProtocol {
func hoge()
}
class Hoge: HogeProtocol{
func hoge(){ // HogeProtocolで規定されているhogeメソッドを実装しないとコンパイルエラー
//何らかの処理
}
}
Protocol + Extension
protocolにデフォルトの処理を実装することができる。
これを書いておくと、毎回protocolのメソッドを実装しなくてもよくなる。
protocol FugaProtocol {
func fuga()
}
extension FugaProtocol {
func fuga(){
//何らかのデフォルトの処理
}
}
class Fuga: FugaProtocol {
// fugaメソッドを実装しなくてもエラーは出ない
}
Delegate(委譲)パターン
Protocol + Extensionの一番良く使う例。
UIKit等の基本的なコンポーネントでよく使われている。
あるオブジェクトAで複数のコールバック関数を設定したい時、予めprotcolで実装したいコールバック関数を規定しておき、そのprotcolを適用したオブジェクトBを実装、オブジェクトBをオブジェクトAのプロパティに追加して、オブジェクトA内の処理内でプロパティに追加されたオブジェクトBのメソッドを実行することで、コールバック処理を実行する方法。
ややこしいのでコードを書くと以下のようになる。
// 予めprotocolで実装したいコールバック関数を規定
protocol ObjectADelegateProtocol {
func callbackA()
func callbackB()
}
// ObjectBを追加するプロパティ、delegateを追加。delegateプロパティはObjectADelegateProtocolが適用されているはず。
class ObjectA {
var delegate: ObjectADelegateProtocol?
func methodA(){
// 処理
delegate?.callbackA() // delegateプロパティはObjectADelegateProtocolが適用されているので、callbackAメソッドがあるはず。
}
func methodB(){
// 処理
delegate?.callbackB() // delegateプロパティはObjectADelegateProtocolが適用されているので、callbackBメソッドがあるはず。
}
}
// ObjectBにprotocolを適用
class ObjectB: ObjectADelegateProtocol {
func callbackA() {
// コールバック処理A
}
func callbackB() {
// コールバック処理B
}
}
let objectB = ObjectB()
let objectA = ObjectA()
objectA.delegate = objectB
objectA.methodA() // callbackA()が呼ばれる
objectA.methodB() // callbackB()が呼ばれる
ObjectAの処理の一部をObjectBに委譲しているので、Delegate(委譲)パターン。
こんなややこしいことして何が嬉しいんだよ、って感じだけど、これをやることによってObjectADelegateProtocolが適用されてさえいれば、別にObjectBに限らず他のオブジェクトでも委譲出来る。
これによって特定のオブジェクトへの依存度を減らすことができる。
UIKitは結構このパターンが使われていて、たとえばUITableView、UICollectionView、UITextField、UIPickerView…等、各種基本的なコンポーネントにはだいたい使われているので、覚えておいたほうが良い。