
スイスイSwift!第5回
Swiftへの不安を解消しよう-実践編-
■ 定数はどう書く?
Objective-CではC言語の記述でdefineマクロやextern また、staticでグロバールな場所で変数を宣言し、定数的に扱うのが慣例であった。次のような宣言をよく目にしたことがあるだろう。
#define kConstantValue 100;
extern NSString *kConstantString;
NSString const *kConstantString = @"定数文字列";
これらはいずれもlet宣言子で記述すればよい。普通の変数宣言と同じであるが、クラス外に書けばグローバルな宣言となり、let宣言なのでプロセス生存中に値が変わることも無く、定数そのものの挙動を保証する。
let kConstantValue = 100
let kConstantString = "定数文字列"
■ 構造体はどう書く?
Objective-CではC言語の要素である構造体が使用可能であったが、Swiftでは言語仕様そのものに取り込まれた。iOS 6以降で構造体にオブジェクトを持てなくなったが、Swiftでは改善されている。それどころか構造体自体にメソッドを実装できるなど一歩進化した拡張が見られる。
例えばiOSではおなじみの矩形座標を表す構造体CGRectの定義を見てみよう。まずはObjective-Cはこうだ。
・Objective-Cの構造体
struct CGRect {
CGPoint origin;
CGSize size;
};
構造体の中にCGPointとCGSizeという構造体が入っているが基本的には値を保持した集合体にすぎない。では次にSwiftの定義を見てみよう。
・Swiftの構造体
struct CGRect {
var origin: CGPoint
var size: CGSize
}
定義自体はほぼ変わらない。が、次のような行が定義されている。extension宣言は後述するが、Objective-Cのカテゴリに相当する。これを見るとCGRectにコンストラクタやメソッドを後付で宣言しているのがわかるだろう。
・Swiftの構造体拡張
extension CGRect {
static var zeroRect: CGRect { get }
static var nullRect: CGRect { get }
static var infiniteRect: CGRect { get }
init()
init(x: CGFloat, y: CGFloat, width: CGFloat, height: CGFloat)
init(x: Double, y: Double, width: Double, height: Double)
init(x: Int, y: Int, width: Int, height: Int)
var width: CGFloat { get }
var height: CGFloat { get }
var minX: CGFloat { get }
var midX: CGFloat { get }
~略~
func contains(rect: CGRect) -> Bool
func contains(point: CGPoint) -> Bool
func intersects(rect: CGRect) -> Bool
}
ここまで来るとクラスとの違いは?という疑問が湧くが、一番大きな違いとしては値代入時に参照渡しではなく値渡しとなる点が異なる。
■ Blocks構文はどう書く?
Objective-Cの機能の中では比較的歴史の浅いBlocks構文はその強力な機能の反面、記述の難解さにおいて多くのプログラマを苦しめてきた。Swiftではどのように改善されているのか見てみよう。
・Objective-CのBlocks
// 引数、返り値なしのBlocks定義。
void (^handler1)(void) = ^void(void) {
~処理~
};
// 引数、返り値ありのBlocks定義。
NSString *(^handler2)(NSString *param) = ^NSString*(NSString *param) {
~処理~
};
・Swiftでのクロージャー
// 引数、返り値なしのBlocks定義。
let handler1 = { () -> Void in
~処理~
}
// 引数、返り値ありのBlocks定義。
let handler2 = { (string1:String) -> String in
~処理~
}
Swiftではクロージャーとして生まれ変わった。{ (引数) -> 返り値 in ~処理~ } という形式でモダンな言語に近い構文となっている。
それでも一部の開発者にはウケが悪いのかfuckingswiftblocksyntax.comというドメインのサイトがある。Blocks登場時に現れたfuckingblocksyntax.comと同じくチートシートのみのサイトなので、記法を忘れた際はここを訪れると良いだろう。
□Closure in Swift
http://fuckingswiftblocksyntax.com
□How Do I Declare A Block in Objective-C
http://fuckingblocksyntax.com
■ プロトコルはどう書く?
プロトコルはJavaのInterface宣言のように実装の無いクラス宣言だ。Objective-Cとの違いは、宣言したメソッドやプロパティは全て必須となる点だ。従来のように@requiredや@optional宣言子で実装の可否を選択させることはできない。
・Objective-Cでのプロトコル宣言
// プロトコルの宣言
@protocol GeekroidProtocol
@property (nonatomic) BOOL isExactMatch;
- (void)searchArticleWithKeyword:(NSString *)keyword exactMatch:(BOOL)isExactMatch;
@end
// プロトコルの実装
@interface GeekroidPage: NSObject <GeekroidProtocol>
@end
@implementation GeekroidPage
@synthesize isExactMatch;
- (void)searchArticleWithKeyword:(NSString *)keyword exactMatch:(BOOL) isExactMatch {
~メソッドの実装~
}
@end
・Swiftでのプロトコル宣言
// プロトコルの宣言
protocol GeekroidProtocol {
var isExactMatch:Bool {get}
func searchArticle(keyword:NSString, exactMatch:Bool)
}
// プロトコルの実装
class GeekroidPage: GeekroidProtocol {
var isExactMatch:Bool = false
func searchArticle(keyword:NSString, exactMatch:Bool) {
// ~メソッドの実装~
}
}
プロトコルで宣言したメソッド、プロパティの全てが必須となるため、Objective-Cからの移行の場合は設計を見直す必要も出てくるだろう。その場合は、プロトコル宣言時に@objc属性をつけoptional修飾子を付けると良い。
// プロトコルの宣言
@objc protocol GeekroidProtocol {
var isExactMatch:Bool {get}
optional func searchArticle(keyword:NSString, exactMatch:Bool)
}
// プロトコルの実装 (optional宣言したメソッドが無くてもエラーにならない)
class GeekroidPage: GeekroidProtocol {
var isExactMatch:Bool = false
}
■ カテゴリはどう書く?
Objective-Cには定義済みのクラスを拡張する仕組みとしてカテゴリという機能があった。SwiftではExtensionという仕組みがこれにあたる。Objective-Cとの違いはメソッドだけでなくプロパティもクラスに後付けできるという点が違う。また定義済みの列挙型や構造体にもExtensionでメソッドやプロパティが拡張ができる。
下記はFoundationにて定義されているNSURLクラスに、ローカルホストかどうかを返すメソッドを追加した例だ。
・Objective-Cでのカテゴリ実装
// カテゴリの宣言
@interface NSURL (CheckLocalhostExtension)
- (BOOL)isLocalhost;
@end
// カテゴリの実装
@implementation NSURL (CheckLocalhostExtension)
- (BOOL)isLocalhost {
return ([self.host isEqualToString:@"localhost"]);
}
@end
// カテゴリメソッドの使用
NSURL *url = [NSURL URLWithString:@"http://localhost/index.html"];
if (url.isLocalhost) {
NSLog(@"ローカルホストです");
}
・Swiftでのカテゴリ宣言
// カテゴリの宣言
extension NSURL {
func isLocalhost() -> Bool {
return (self.host == "localhost")
}
}
// カテゴリメソッドの使用
if let url = NSURL(string: "http://localhost/index.html") {
if (url.isLocalhost()) {
println("ローカルホストです")
}
}
■ オートリリースプール
Swiftではメモリ管理は不要、というのは間違いで、Objective-C同様に参照カウンタ方式のメモリ管理がおこなわれている。コンパイラが文脈を判断し、自動でメモリの解放コードを挿入し中間コードを生成。また不要になったオブジェクトはオートリリースプールに溜め込まれ、イベントループが処理されるたびに開放されてる。この辺りの仕組みはARCが導入された辺りから開発者が意識しなくて良くなったが、それでも同じイベントループ内で大量のメモリを使用する場合は、開発者が適宜オートリリースプールを用意する必要がある。Objective-Cでは@autoreleasepoolという修飾子により文脈を記述できたが、Swiftでも同様の記述で実装できる。
・Objective-Cでのオートリリースプール
for (int i = 0; i < 1000; ++i) {
@autoreleasepool {
//~メモリを多く使用する処理~
}
}
・Swiftでのオートリリースプール
for i in 1...1000 {
autoreleasepool {
//~メモリを多く使用する処理~
}
}
注意すべき点として、Objective-Cでの@autoreleasepool修飾子以降の括弧内はBlocks構文ではなく、反対にSwiftのautoreleasepool構文内の文脈はクロージャーとして実行される事だ。Objective-Cのように文脈上で処理されるコードではないため、returnやbreakはクロージャーの外には影響せず内側で処理される。上記のSwiftのコードの場合において、autorelasepoolクロージャー内でreturnやbreakを記述してもfor文のループが終了するわけではない点に注意しよう。
■ 例外処理は?
Objective-Cでのアプリ開発では習慣としてtry~catchでの例外処理はあまり記述されることは無かった。もちろん構文としては用意されているが、特にARC環境においてはランタイム時の例外で文脈がスキップされることにより参照カウンタに齟齬(いわゆるメモリリーク)が起きやすい、という理由から推奨はしていなかった。
Swiftでは何とtry~catch構文がサポートされていない。Optional型などの採用により、いわゆるnull pointer的な例外は起こりにくくなっているとは言え何とも思い切りの良い決断だ。
従来の例外クラスNSException自体はSwiftからも使用可能なため、どうしても例外をハンドルする必要があるのであれば、間にObjective-Cクラスを用意し、擬似的に例外をキャッチする仕組みを用意すると良いだろう。
■ まとめ
Swiftには多くの文法や作法があるが、Objective-Cからの移植に関して言えば実はそれほど多くのことを学ぶ必要は無い。まずは手元にObjective-Cのプロジェクトがあれば、それを徐々にSwift化していく事は最初の実践課題としては申し分ないだろう。おそらく完成した頃にはもうObjective-Cには戻れないはずだ。
qnote最高技術責任者。猫とビールをこよなく愛するゆるキャラ系プログラマ。
Webアプリ開発からMacアプリ開発を経て現在はiOSアプリ開発に没頭。弱点はAndroid。