Swift移行時に引っかかりそうなことを解決する!

第4回iOSアプリ開発におけるSwiftでの実装の実例とTips

先日、Apple 公式のSwift Blogで、Swift 1.2のリリースがアナウンスされていました。コンパイラの性能改善と、新しい仕様の追加があるようです(まだ試してません……⁠⁠。プログラミング言語がこのようなスピードで進化していくのも、技術の進化のスピードが速いこの時代ならではなのかなと思います。

さて今回は、実際のiOSアプリ開発を例に、Swiftでどのように実装していくか、また引っかかりそうなポイントを解説していきます。また、まだ説明できていないSwift言語仕様も併せて解説したいと思います。

なお、Using Swift with Cocoa and Objective-Cと言うiBookコンテンツが無料で提供されていますWeb版はこちら⁠。タイトルのとおり、SwiftでのiOS/OS X開発に関する具体的な事例が書かれています。英語ではありますが、こちらも併せてご覧ください。

クラスの基礎

さっそく実践編へ、と行きたいところですが、第2回で説明できていなかったクラスについて簡単に説明したいと思います。AppDelegateを参考に、基本的なクラスの構成要素を見ていきます。

// AppDelegate.swift
class AppDelegate: UIResponder, UIApplicationDelegate {

  var window: UIWindow?

  func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
    // Override point for customization after application launch.
    return true
  }
  ...
}

クラス定義

クラスの定義は、classに続けて「クラス名: 親クラス名」のように記述します。デリゲートやデータソースなどのプロトコルは、Objective-Cの場合は、⁠<>」でくくって記述していましたが、Swiftの場合はそのままカンマ区切りで続けて行きます(上記例でのUIApplicationDelegate⁠⁠。

このクラスブロックの中に、プロパティやメソッドを記述していきます。

プロパティ(メンバ変数)

プロパティの定義は、第2回で説明した変数定義のルールと同じです。上記の例では、UIWindow型のwindowというプロパティが定義されていますが、⁠?」が付いているので、オプショナル型となりnilを許容する変数となります。オプショナル型については第2回をご参照ください。

メソッド

メソッドは、funcに続けてメソッド名と引数を定義する括弧が続きます。返り値がある場合にはその後ろに「-> Type」という書式で戻り値の方を指定します。上記例では、Bool型の値を返す事になります。

引数は括弧内にカンマ区切りで定義します。上記の例ですと、第1引数は変数名がapplicationで、その型がコロンの後に続くUIApplicationとなります。

第2引数を見ると、ちょっと長めのラベルdidFinishLaunchingWithOptionsに続いて、launchOptionsと言うラベルがあります。前者は外向きの名前で、Objective-Cと同じく引数に名前をつけることができます。後者は内部で使用する変数名となります。外向きの名前を省略した場合には、変数名がそのまま名前となります。詳しくは、メソッド呼び出しの解説の中で説明します。

そのあとのコロンに続く第2引数の型は、上記の例の場合はDictionaryもしくはnilを渡されることを示します(⁠⁠?」が付いているのでオプショナル型となります⁠⁠。

イニシャライザとデイニシャライザ

initという名前のメソッドはイニシャライザと呼ばれ、インスタンスする時に呼び出されます(C++やJavaでいうところのコンストラクタ⁠⁠。funcキーワードは不要です。引数の名前を変えることで、複数定義(オーバーロード)することができます。これについては「オブジェクトの初期化」の解説の中でも説明します。

また、deinitという名前のメソッドはデイニシャライザ(デストラクタ)で、オブジェクトが破棄されるタイミングで呼び出されます。引数の括弧は不要です。

下記は、initとdeinitの例です。

class Person {
  
  var name = ""
  var age : UInt = 0
  
  init() {
  }
  
  init(name: String) {
    self.name = name
  }
  
  init(name: String, age: UInt) {
    self.name = name
    self.age = age
  }
  
  deinit {
    println("deinit")
  }
}

オブジェクトの初期化(インスタンス)

アプリ開発においては提供されている様々なクラスを利用しますが、Swiftではそのインスタンス方法がシンプルになりました。NSURLを例に見てみましょう。

// Objective-C
NSURL *url = [[NSURL alloc] initWithString:@"http://example.com/"];

// Swift
let url = NSURL(string: "http://example.com/")

このように、Swiftでは ClassName()の形式でインスタンスします。先ほど引数には名前が付けられると述べましたが、上記のstring:がそれにあたります。

この名前には変換ルールがあり、Objective-CのinitWithで始まるメソッドの場合には、そのあとに続く文字列(上記の例でのString)の1文字目を小文字にしたものが第1引数の名前となります。説明だけだとわかりづらいので、別の例も見てみましょう。

// Objective-C
UIColor *color = [[UIColor alloc] initWithRed:0.5 green:0.5 blue:0.5 alpha:1.0];
// Swift 
// initWithがなくなり、残った「Red」が第1引数の名前になっている。
let color = UIColor(red: 0.5, green: 0.5, blue: 0.5, alpha: 1.0)

Objective-Cに比べると、allocとかinitのような記述が不要でシンプルになっています。また、Xcodeの補完機能を使うことで簡単に入力できます。クラス名を入力してすぐ括弧「(」を入力すると下記のように候補が出てきて、選択したらあとは引数を与えるだけです。

図1 候補が出てきて選択したらあとは引数を与えるだけ
図1 候補が出てきて選択したらあとは引数を与えるだけ

インスタンスすると、先に説明したイニシャライザinit()が呼び出されます。候補が複数提示されるのは、引数の異なる複数のイニシャライザが定義されているためです。

メソッド呼び出し

メソッドの呼び出しは、RubyやJavaなどと同じくピリオド「.」でつなげます。アプリケーションでよく使われるNSUserDefaultsを例に見てみます。

let defaults = NSUserDefaults.standardUserDefaults()
defaults.setInteger(10, forKey: "age")
defaults.synchronize()
let age = defaults.integerForKey("age")

引数はカンマ区切りで指定します。一般的に第1引数には名前を付けず値だけを渡します。第2引数以降は「名前: 値」の組み合わせで記述します。

ちなみにこれらは、Objecitve-Cのルールを踏襲しているためですが、Swift言語としては第1引数に名前を持たせたり、第2引数以降の名前を省略することも可能です。

プロパティのアクセス

オブジェクトのプロパティも同じくピリオド「.」でアクセスします。UILabelの例で見てみましょう。

let label = UILabel(frame: CGRect(x: 10, y: 10, width: 100, height: 50))
label.textColor = UIColor.redColor()
label.text = "Label"

クロージャ

Objective-Cでは「Blocks」と呼ばれていましたが、Swiftでは「Closures」です。UIViewControllerのdismissViewControllerAnimatedメソッドで見てみます。

// selfはUIViewControllerのサブクラス
self.dismissViewControllerAnimated(true, completion: { () -> Void in
  println("Complete!")
})

第2引数でクロージャを渡しています。クロージャは波括弧{}に囲まれていて、中の丸括弧()が渡れてくる引数(このケースでは引数なし)で、⁠-> Void」は、返り値がVoid(すなわち無し)で、in以降が実際の処理を記述する部分となります。

これも、Xcodeの補完機能を使うことで、ある程度自動的に記述してくれます。

図2 Xcodeの補完機能を使う
図2 Xcodeの補完機能を使う

この状態で、returnキーを押すと、

図3 このように展開してくれる
図3 このように展開してくれる

このように展開してくれます。あとは、codeのプレースホルダに実際のコードを記述します。ちなみにクロージャの型が(() -> Void)?となっていますが、⁠?」が付いていることから第2引数はオプショナル型であることがわかると思います。すなわち、nilを渡すことができるということです。

別の例で、Arrayのsortメソッドを見てみます。

var numbers = [1, 3, 2]
numbers.sort { (a, b) -> Bool in
  return a < b
}

これは、aとbの値を受け取りBool値を返すクロージャで、その返り値に応じて配列をソートします。このケースだと、numbersが昇順に並べ替えられます。

よく見ると、sortメソッドの引数が省略されていて、直ぐにクロージャが記述されています。このようにクロージャには様々な省略記法があり、正直筆者も全てを覚えていないのですが、Xcodeが補完してくれるのでそれに従ってコーディングすれば良いでしょう。

Storyboardとの連携(IBActionとIBOutlet)

UIとコードをつなげる時は、StoryboardからObjective-Cのヘッダファイルに、controlキーを押しながらドラッグして接続していましたが、Swiftの場合は.swiftファイルにドラッグして接続します。

図4 Swiftの場合は.swiftファイルにドラッグして接続する
図4 Swiftの場合は.swiftファイルにドラッグして接続する

Xcodeによって自動生成されるコードはそれぞれ下記のようになります。

// Action
@IBAction func pressButton(sender: AnyObject) {
}

Actionの引数は、渡される送信元オブジェクトが様々なクラスであるため、AnyObjectとなっています(Objective-Cでのidに相当⁠⁠。あとで説明するキャストを行うことで、元々のオブジェクトとして扱うことができます。

// Outlet
@IBOutlet var view: UIView!

Outletの場合、接続するUIのクラスUIViewが指定されていますが、その後ろに「!」が付けられています。これはちょっと特殊なオプショナル型で、後ほど説明します。

オプショナル型(の続き)

第2回でオプショナル型を取り上げましたが、実際のアプリ開発で出てくるケースも解説します。

Optional Chaining

オプショナル型のオブジェクトを使う場合はアンラップする必要がありますが、アンラップするときにはnilでないこと保証する必要がありました。たとえばNSUserDefaultsのケースで見てみます。

let defaults = NSUserDefaults.standardUserDefaults()
let name = defaults.stringForKey("name")  // 定義されていないキーなのでnilが返る
name!.lowercaseString                     // nilなのでアンラップ時にエラー

nameはオプショナル型ですが、アンラップする「!」の代わりに「?」を付けることで、nilの場合にはそのままnilを返すことができます。

name?.lowercaseString                     // エラーにならずにnilを返す

なお、この「?」でアンラップする場合はnilが返る可能性があるため、得られる値はオプショナル型となります。

Implicitly Unwrapped Optional

前述の「Storyboard との連携」の中で出てきた、

// Outlet
@IBOutlet var view: UIView!

この「!」ですが、オプショナル型の一種で「ImplicitlyUnwrappedOptional<T>」の省略形です。この型で宣言すると、nilが代入できるのに非オプショナル型かのように扱えます。もちろん、値がnilのときにメソッドを呼んだりすると、実行時にエラーとなります。

var view : UIView!      // ImplicitlyUnwrappedOptional<UIView>
let frame = view.frame  // view がnilなので実行時にエラー

Swiftのポリシーからすると極力使用しないほうが良いですが、前述の例のように、viewDidLoad()が呼び出される時点で必ず初期化されている(nilではない)ようなケースでは、以降の記述が煩雑にならずに便利です。

キャスト(as)

Objective-Cのライブラリを使っていると、取得した値がAnyObjectであること結構あります。その時には、後ろに「as ClassName」と続けることでキャストすることができます。

var person : [String: AnyObject] = ["name": "taro", "age": 32, "married": true]
let name = person["name"] as String
name.uppercaseString

上記の例ですと、person["name"]がAnyObjectとしてみなされるので、⁠as String」を付けて、Stringとして扱えるようにしています。

また、nilをキャストする可能性がある場合やダウンキャストができるかわからない場合には、下記のように「as?」でキャストします。そうすると、返り値がオプショナル型となり、nilだった場合にエラーにならず、nilが返ります。

var address = person["address"] as? String  // nilをキャスト

その他

ログ出力

デバッグで欠かせないNSLog()ですが、Swiftでは代わりにprintln()を使用します。

// Objective-C
NSInteger n = 10;
NSLog(@"%ld times", n);

// Swift
let n = 10
println("\(n) times")

ちなみに、NSLog()を使うことも可能です。

マクロ

Swiftでも #if/#elseif/#else/#endifが使えます(#ifdefは使えません⁠⁠。条件は、ターゲットのBuild Settingsの中のOther Swift Flagsにて設定します。たとえば、⁠DEBUG」を条件に使いたい時は下記の図のように値を設定します。

図5 ⁠DEBUG」を条件に使いたい時
図5 「DEBUG」を条件に使いたい時

すると、下記のコードで、DEBUGが真になります。

#if DEBUG
  println("Debug mode")
#else
  println("Release mode")
#endif

ちなみに余談ですが、Swiftでも下記はサポートされています。

// Objective-C
#pragma mark - Title

// Swift
// MARK: - Title

NSLocalizedString()

NSLocalizedString()も使えますが、Xcodeで補完すると予想外の引数が出てきます。

図6 Xcodeで補完すると予想外の引数が出る
図6 Xcodeで補完すると予想外の引数が出る

実は、Swiftでは引数にデフォルト値を指定できるようになっていて、このNSLocalizedString()もkeyとcomment以外にはデフォルト値が設定されています。それらの引数を削除して、Objective-Cと同様の記述ができます。

NSLocalizedString("key", comment: "comment")

最後に

4回に渡って、SwiftでのiOSアプリ開発に必要なノウハウをお届けしました。いかがだったでしょうか?

Swiftには今回説明した以外にも様々な仕様があり、今でも進化を続けています。まず最初にこの特集の内容を押さえておけば、Objective-CからSwiftにスイッチする際に戸惑うことも少ないかと思います。

今後、Swiftを最大限活かしたフレームワークやライブラリなども出てくると思いますので、その前に、早めにSwiftにチャレンジしてみてはいかがでしょうか? この特集が、その手助けとなる事を願っています。

おすすめ記事

記事・ニュース一覧