cockscomblog?

cockscomb on hatena blog

Swift 1.2

第61回 Cocoa勉強会関西で“Swift 1.2 The long-awaited language updates”と題して発表した、Swift 1.2の主だった(おもしろい)変更点の紹介です。

if let

Swift 1.2で最も改善されたのはif文です。if letでOptionalをunwrapできる機能が大きく向上し、複数のOptionalを同時にunwrapできるほか、unwrapされた値について条件を加えることができるようになりました。

例えばcondition: Boolが真でふたつのOptional<Int>nilではなく、大小関係にも条件がある、という条件を表してみます。

Swift 1.1

let condition = true
let aNumber: Int? = 3
let anotherNumber: Int? = 7
if condition {
    if let a = aNumber {
        if let b = anotherNumber {
            if a < b {
                println(a + b)
            }
        }
    }
}

Swift 1.2

let condition = true
let aNumber: Int? = 3
let anotherNumber: Int? = 7
if condition, let a = aNumber, let b = anotherNumber where a < b {
        println(a + b)
}

極端な例ではありますが、Swift 1.1ではifを4回重ねていたものを、Swift 1.2では1つのifで表現できます。

let constant must be initialized before use

let定数は再代入できません。Swift 1.1ではそのために、宣言と同時に値を決める必要がありました。Swift 1.2ではその制限が少し緩和され、値が使われる前に確実に初期化されるなら、宣言の後で値が決まってもよいことになりました。

Swift 1.1

let hour = NSCalendar.currentCalendar().component(.CalendarUnitHour, fromDate: NSDate())

var meridiem: String
if hour < 12 {
    meridiem = "AM"
} else {
    meridiem = "PM"
}

println(meridiem)

Swift 1.2

let hour = NSCalendar.currentCalendar().component(.CalendarUnitHour, fromDate: NSDate())

let meridiem: String
if hour < 12 {
    meridiem = "AM"
} else {
    meridiem = "PM"
}

println(meridiem)

Swift 1.1ではvarにしなければならなかったmeridiemが、Swift 1.2ではletにできます。ifに限らずswitchなども利用できますが、全ての条件で初期化されることが保証されている必要があります。

Set

CollectionTypeArrayDictionaryなどに加えてSetが追加されました。Setは集合を表し、Foundation.frameworkのNSSetに対応します。Setの導入に合わせて、Objective-CでNSSetを利用していたAPISwiftから利用する場合にはSwiftのSetを使うことになります。

Swift 1.1

let abc: NSSet = NSSet(objects: "A", "B", "C")
if NSSet(objects: "A").isSubsetOfSet(abc) {
    println(abc)
}

Swift 1.2

let abc: Set<String> = ["A", "B", "C"]
if abc.isSupersetOf(["A"]) {
    println(abc)
}

NSSetと違って内包する値の型を指定でき、またArrayリテラルで表現できます。

Static methods and properties

Swift 1.2からclassにもstaticな関数やプロパティを持てるようになりました。これまでもclass funcがありましたが、static funcclassかつfinalという意味になります。またstatic letで宣言した定数は最初にアクセスされた時点で評価されるという特徴を持ちます。

class StaticPropertiesAndMethods {
    static func printDate() {
        println(date)
    }
    static let date = NSDate()
}

StaticPropertiesAndMethods.printDate() // => "2015-04-10 07:01:00 +0000"

クラス変数はObjective-Cには存在せず代替的な手法で実現していましたが、Swiftでついに用意された文法と言えます。

Non-Void return types in Void contexts

Swift 1.1でクロージャを使うときに、返り値としてVoidを要求するところに1行だけ、Voidではない値を返す式があると、コンパイルエラーになっていました。これはクロージャが1行だけの式を持つとき、その式の結果がクロージャ全体の返り値として取り扱われ、型がミスマッチだと判定される結果です。このために無意味なreturnを付け加えるなどして回避していました。

Swift 1.2ではこのようなときにVoidではない値が返っても許容されるようになりました。これで意味上は不要なreturnを書く必要がなくなり、すっきりします。

Swift 1.1

let wantsVoid: (() -> Void) = {
    "non-Void here"
    return
}

Swift 1.2

let wantsVoid: (() -> Void) = {
    "non-Void here"
}

zip

Swift 1.2ではzip関数が導入されました。ふたつのSequenceTypeの組み合わせを簡単に作ることができます。要素数が異なる場合には短い方に合わせられます。

let ordinals = ["first", "second", "third"]
let values = [1, 2, 3]

var ordinalsDict: [String: Int] = [:]

for (key, value) in zip(ordinals, values) {
    ordinalsDict[key] = value
}

println(ordinalsDict) // => ["first": 1, "second": 2, "third": 3]

Type casting

キャスト関連でも非互換な変更などがあります。

let any: AnyObject = 3
let number: Int = any as! Int

危険な操作であるときの一貫性のため、ダウンキャストするときas!を使うことになりました。

protocol SomeProtocol {
    func something() -> String
}

class SomeClass: SomeProtocol {
    func something() -> String {
        return "Something awesome"
    }
}

let some: AnyObject = SomeClass()
if some is SomeProtocol {
    println((some as! SomeProtocol).something())
}

@objcではない通常のprotocolにもキャスト関連の操作ができるようになっています。

@noescape

関数が引数としてクロージャを受け取るとき、クロージャの内部で利用される変数はキャプチャされ、それぞれ強参照された状態となります。またこのためにクロージャの外のオブジェクトのプロパティなどを利用する場合にはself.で修飾する必要があります。これによってクロージャを実際に評価するタイミングはいつでもよいことになっています。

しかしクロージャを受け取ってすぐに評価することが決まっている関数の場合はどうでしょう。そういうときはキャプチャする必要が無いし、self.も不要です。キャプチャしない方がパフォーマンス上でも有利でしょうし、最適化しやすいかもしれません。このようにすぐに評価されることがわかっているクロージャのために追加された新しいアノテーション@noescapeです。

func map<U>(f: @noescape (T) -> U) -> U?

Optionalmapでは上記のように@noescapeされています。このためクロージャで使う変数がプロパティであってもself.は不要です。

このようにSwiftではこれらの挙動をescapeという言葉で表しています。

func map<U>(transform: (T) -> U) -> [U]

ところでArraymapを見てみると、上記のように@noescapeアノテーションがないことが分かります。一貫性がないように思われるかもしれませんが、これには理由があるように思えます。lazy()関数などを用いて遅延評価する場合にはescapeせざるを得ません。これは簡単な実験で確かめられます。

let start = NSDate()
let array = Array(0..<3)

let result = lazy(array).map { (number: Int) -> Int in sleep(1); return 100 * number }

println(NSDate().timeIntervalSinceDate(start))

このようにlazy()関数を使えばmap中でsleep(1)していても一瞬で実行が終わり、result[2]などresultの要素にアクセスしたとき初めてmapクロージャが評価されます。このlazy()を消すと、このスニペットは少なくとも3秒の実行時間を必要とします。

これがArray.mapがescapeを必要とする理由です。

この@noescapeが追加されたのに合わせてデフォルトでは@autoclosureもescapeされなくなりました。escapeしてほしい場合には@autoclosure(escaping)と書く必要があります。

flatMap

新しいflatMap関数を使うとmapではできないちょっとしたことが簡単にできるようになります。

let mapped = ["A B C", "D E", "F"].map {
    $0.componentsSeparatedByString(" ")
}
println(mapped) // => [[A, B, C], [D, E], [F]]

let flatMapped = ["A B C", "D E", "F"].flatMap {
    $0.componentsSeparatedByString(" ")
}
println(flatMapped) // => [A, B, C, D, E, F]

上記の例のように、Arrayの要素に何らかの変換を行うとき、flatMapなら要素毎に新たなArrayを返しても2次のArrayになりません。

func toInt(value: String) -> Int? {
    return value.toInt()
}

let one: String? = "1"

let mapOne = one.map { toInt($0) }
println(mapOne) // => Optional(Optional(1))

let flatMapOne = one.flatMap { toInt($0) }
println(flatMapOne) // => Optional(1)

また上記の例では、OptionalmapflatMapの違いを示しています。mapの中でさらにOptionalな値を返すとOptional<Optional<Int>>を得ることになります。flatMapではOptional<Int>です。これは非常に有用であることがわかるでしょう。

このようにflatMapmapとよく似ていますが、最初のArrayと要素数が変わったArrayが返ったり、多重のOptionalを避けられたりといった特徴があります。

let intLike = ["12.3", "45", "6-7-8"]
let results = intLike.flatMap {
    $0.toInt().map { [ $0 ] } ?? []
}

println(results) // => [45]

上記はこれの応用例で、ふつうのmapであればArray<Int?>が返るところを、nilを除去してArray<Int>にしています。

NSEnumerator.generate() -> NSFastGenerator

Swift 1.2ではFoundation.frameworkにも少し拡張が増えています。

let fonts = "/System/Library/Fonts"
let files = NSFileManager().enumeratorAtPath(fonts)!
for path in files {
    println(path)
}

enumeratorAtPathNSDirectoryEnumeratorというNSEnumeratorのサブクラスを返します。Swift 1.2より前では、NSEnumeratorSequenceTypeではなく、for inが使えませんでした。Swift 1.2からはgenerator()メソッドが加わり、protocol SequenceTypeを満たすようになったためfor inできるようになりました。

Swiftはこのように、Foundation.frameworkについてSwiftから利用しやすいように多少の拡張を加えており、今後もこれらが拡充されていく可能性があります。

その他の非互換な変更

Remove implicit conversions from NSString/NSArray/NSDictionary to String/Array/Dictionary

NSStringなどのFoundation.frameworkのオブジェクトから対応するSwiftStringなどへの暗黙的な変換が行われなくなりました。明示的にas Stringなどと書いてキャストしておく必要があります。逆のStringからNSStringといった変換はこれまで通り暗黙的に行われます。

SwiftからObjective-Cのヘッダを見たときにはNSStringStringになっており、Swiftからはほとんど常にStringを利用していることになるので、実際的にこれが問題になるケースは少ないでしょう。ただしCoreFoundation.frameworkに関するオブジェクトを利用するときは別です。

countElements → count

素数を数える関数の名前が変わりました。分かりやすいですね。

パフォーマンスに関する変更

Swift 1.2と共に様々なパフォーマンスが改善されました。Swiftで書かれたプログラムのパフォーマンスが改善されたほかにも興味深い変更があります。

Incremental build

Swiftコンパイルがインクリメンタルになりました。これまでは、ひとつのファイルを変更したときにも全てのファイルのコンパイルをやり直す必要がありましたが、これからは必要最低限のファイルをコンパイルし直すだけでよくなりました。ただし今後も、変更したファイルだけをコンパイルし直すのではなく、変更が影響するであろうファイル全てをコンパイルし直す必要があります。

Swift 1.2でも思ったよりコンパイルが遅いということがあるかもしれませんが、もしかするとXcode 6.3.1の問題かもしれません。

Whole Module Optimization

オブジェクト指向パラダイムにおいて、クラスを継承してメソッドをオーバーライドするというのがあります。すなわち、メソッドを呼び出す際にはオーバーライドされた後の実装を呼び出す必要があるということです。プログラムの実行中、メソッドの呼び出しやプロパティを操作する時に、実際に呼び出すべき対象を動的に解決します。これをdynamic dicpatchと呼んでいます。

ここで、例えばfinal修飾されているメソッドは、ぜったいにオーバーライドできません。そういった場合にはdynamic dispatchする必要がないので、Swiftコンパイラは静的に解決してくれます。dynamic dispatchが行われない分、実行時のパフォーマンスは改善します。

さらにアクセス制御のprivateで修飾されていれば、そのファイルの外からはオーバーライドできません。つまりそのファイル中でオーバーライドされているか検査することで不要なdynamic dispatchをしないようにコンパイルできます。private修飾はパフォーマンスにも役立っていることがわかります。

さらにinternalでもこれを可能にするのがWhole Module Optimizationです。internalで修飾されていればそのモジュールの外からオーバーライドされません。つまり、モジュール全体を検査することで、不要なdynamic dispatchを避けることができます。この最適化は比較的時間がかかるため、現在のところオプションになっています。

これらの話題はSwiftのブログにも書かれています。

Objective-CのNullability

Swift 1.2に合わせてObjective-Cにも変更があります。変数や引数、返り値などがnilを取り得るかをアノテートできるようになりました。nullablenonnullで修飾することでこれが可能です。また全てにこれらのアノテーションを付けて回るのは現実的ではないので、NS_ASSUME_NONNULL_BEGINNS_ASSUME_NONNULL_ENDで囲われている部分はnonnullということになっています。基本的にはこれで囲んでおいて、nullableだけ明示的に書くのがよいでしょう。

これらのアノテーションを書いておくと、Swiftから見たときにImplicitly Unwrapped OptionalではなくOptionalかそうでないかはっきりします。さらにObjective-Cにおいても、nonnullとアノテートされたポインタにnilを入れようとしていれば静的解析で検出されます。ただし実行時には影響しません。


Swiftはまだ変わり続ける生きた言語です。Swift 1.2はSwiftの正式なリリース以来もっとも大きな変化で、開発者にとっても非常に歓迎すべき改善が数多くあります。今後のSwiftの進化に期待しながら、Swift 1.2でバリバリ開発していきたいですね。

詳解 Swift

詳解 Swift


いっしょにSwiftでバリバリ開発しませんか。

iOS/OS X 用ライブラリ UTIKit を OSS でリリースしました

UTI

UTI とは Uniform Type Identifier のことで、iOSOS X の世界で、ファイルなどの種類を表すために用いられる文字列です。

UTI の実例として、画像一般を表す public.image や、そのサブタイプで JPEG 画像を表す public.jpeg などがあります。iOSOS X で利用されるほとんどのファイル形式には対応する UTI があり、例えば Keynote ファイルは com.apple.iwork.keynote.key などとなります。もちろんサードパーティも例外ではなく、Adobe Photoshop のファイルは com.adobe.photoshop-image となり、Microsoft Word の docxorg.openxmlformats.wordprocessingml.document です。

iOSOS Xフレームワークは、ファイルの種類を表すのにこの UTI を利用することが多くあります。例えば Photos.framework から画像データを得たいというとき、PHImageManagerrequestImageDataForAsset(_:options:resultHandler:) メソッドを利用します。この resultHandler((NSData!, String!, UIImageOrientation, [NSObject : AnyObject]!) -> Void)! という型のクロージャで、クロージャの第2引数はドキュメントを読むと dataUTI となっています。すなわち、画像データの種類が UTI として得られます。

ところで、この UTI から拡張子MIME type や拡張子を取得するにはどうすればよいのでしょうか。画像をサーバーにアップロードするときには MIME type が必要かもしれません。例えば public.jpeg だったら image/jpeg という風な対応を丁寧に処理しても良いかもしれませんが、そもそも iOSOS X にはそういった関数が備わっています。func UTTypeCopyPreferredTagWithClass(inUTI: CFString!, inTagClass: CFString!) -> Unmanaged<CFString>! を利用することで UTI から MIME type や拡張子が得られます。

let mime = UTTypeCopyPreferredTagWithClass("public.jpeg", kUTTagClassMIMEType).takeRetainedValue() // => "image/jpeg"

let ext = UTTypeCopyPreferredTagWithClass("public.jpeg", kUTTagClassFilenameExtension).takeRetainedValue() // => "jpeg"

またここでは紹介しませんが、逆に MIME type や拡張子から UTI を得る関数もあります。非常に便利ですが、ごくたまにしか使わないので忘れがちです。筆者は毎回のように忘れて、過去のプロジェクトや IRC のログを検索して思い出します。

UTIKit

ということで、このような UTI に関連した処理を利用しやすくするラッパーライブラリが、UTIKit です。これを利用すると上記の処理が以下のようになります。

UTI("public.jpeg").MIMEType // => "image/jpeg"

UTI("public.jpeg").filenameExtension // => "jpeg"

ごく短くなる上に、インターフェースもわかりやすいですね。

もちろん MIME type や拡張子から UTI を得るのも簡単です。

let jpeg = UTI(MIMEType: "image/jpeg")

let jpeg = UTI(filenameExtension: "jpeg")

Equatable を実装しているためこれらふたつは同値です。

UTI(MIMEType: "image/jpeg") == UTI(filenameExtension: "jpeg") // => true

また、public.jpegpublic.image に従っているので、switch を利用して以下のように書けます。

switch UTI("public.jpeg") {
case UTI("public.image"):
    println("JPEG is a kind of images")
default:
    fatalError("JPEG must be a image")
}

これらのように、UTI が Swift ライクに扱えることがわかります。

ご利用方法

CocoaPods 0.36 以降でインストールできます。Podfile に以下の行を書き足しましょう。

pod "UTIKit"

また Carthage でもインストールできるかもしれません(試してない)。

MIT ライセンスです。どうぞご利用ください。

To Do

NSWindowのタイトルバーをカスタマイズする

人類の多年の夢であったNSWindowのタイトルバーのカスタマイズであるが、YosemiteになってついにオフィシャルなAPIが実装された。


YosemiteではApple製のアプリケーションほとんどにおいてデザインが一新され、かつてOS Xを特徴付けていたツルッとしたテクスチャは取り払われた。そしてウインドウのタイトルバーまでもが変わった。

f:id:cockscomb:20150208004605p:plain
タイトルバー

ウインドウのタイトルバーは、OS XにおいてGUIアプリケーションの要である。タイトルバー自体がウインドウを移動するためのノブであり、左にはウインドウを操作するための赤・黄・青のコントロールが、中央にはウインドウのタイトルが表示される。ときにはツールバーと見かけ上くっついたりするが、いずれにしてもウインドウの中心はタイトルバーである。

f:id:cockscomb:20150208005228p:plain
Safariのタイトルバーにはツールバーが表示されている

そのタイトルバーをカスタマイズしたい、これは全人類が生まれながらに持っている当然の欲求である。そして、しかし、Appleは長年にわたってそういったAPIを提供してこなかった。ウインドウをウインドウたらしめるタイトルバーを、心ないデベロッパーが欲望のままに毀損することを許さないというように、禁断の果実を決して手放そうとはしなかった。それがYosemiteになって、Apple製のアプリケーションでさえ、一般的なタイトルバーのルールを破壊した。いまやタイトルバーには信号機のようなコントロールを残して、タイトルの代わりにツールバーが表示されたり、あるいはウインドウのコンテンツがそのまま表示されるようになった。これはいまやAppleだけの特権ではなく、ついにAPIが提供されたのである。

f:id:cockscomb:20150208005317p:plain
リマインダーのタイトルバーはコンテンツと重なる

タイトルを非表示にする

OS X 10.10 YosemiteからNSWindowに新しく追加されたプロパティvar titleVisibility: NSWindowTitleVisibilityを使うと、ウインドウのタイトルを非表示にできる。enum NSWindowTitleVisibilityは以下のように二つの値からなる。

enum NSWindowTitleVisibility : Int {
    case Visible
    case Hidden
}

デフォルトは.Visibleであるが、ここでヘッダから.Hiddenのコメントを引用する。

The always hidden mode hides the title and moves the toolbar up into the area previously occupied by the title.

NSToolbarがある場合にはタイトルがあった場所に移動するようだ。これによって、例えばSafariやカレンダーのように、タイトルバーの高さが少し高く、信号機のようなコントロールのすぐ右にツールバーがあるようなウインドウが作れる。NSWindowNSToolbarを設定して、あとは以下のようなコードをNSWindowControllerfunc windowDidLoad()などに実装する。

window?.titleVisibility = .Hidden

f:id:cockscomb:20150208005920p:plain
単にツールバーを置いたウインドウ

f:id:cockscomb:20150208005928p:plain
ツールバーを置いてtitleVisibility = .Hiddenとしたウインドウ

タイトルバーを装飾する

OS X 10.10 YosemiteではNSViewControllerが大幅に強化され、iOSUIViewControllerから多くの思想を受け継いだ。そして同時にNSViewControllerのサブクラスがいくつか追加された。その一つがNSTitlebarAccessoryViewControllerである。

NSTitlebarAccessoryViewControllerは、その名前が示す通り、タイトルバーを装飾するViewのControllerである。このViewControllerのviewプロパティが返すViewが、NSWindowのタイトルバーに表示される。合わせてNSWindowにも以下のようなプロパティやメソッドが追加されている。

var titlebarAccessoryViewControllers: [AnyObject]
func addTitlebarAccessoryViewController(childViewController: NSTitlebarAccessoryViewController)
func insertTitlebarAccessoryViewController(childViewController: NSTitlebarAccessoryViewController, atIndex index: Int)
func removeTitlebarAccessoryViewControllerAtIndex(index: Int)

NSWindow複数NSTitlebarAccessoryViewControllerを順序付きで管理する。タイトルバーの中でどの部分に表示されるのかは、NSTitlebarAccessoryViewControllerの持つvar layoutAttribute: NSLayoutAttributeというプロパティで決定される。ただし現在の所NSLayoutAttributeのうち有効な値は.Bottom.Rightのみであることがヘッダに書かれている。もちろんそれぞれに対応する位置は、タイトルバーの下と右である。

実際に表示するには以下のようなコードを、例えばNSWindowControllerfunc windowDidLoad()に実装する。NSTitlebarAccessoryViewControllerインスタンスはStoryboardから生成している。

let accessory = storyboard?.instantiateControllerWithIdentifier("TitlebarAccessory") as NSTitlebarAccessoryViewController
accessory.layoutAttribute = .Right
window?.addTitlebarAccessoryViewController(accessory)

f:id:cockscomb:20150208010129p:plain
layoutAttribute = .BottomNSTitlebarAccessoryViewControllerを設定したウインドウ

f:id:cockscomb:20150208010116p:plain
layoutAttribute = .RightNSTitlebarAccessoryViewControllerを設定したウインドウ

コンテンツをタイトルバーの領域まで拡げる

タイトルバーの位置までウインドウのコンテンツを表示するために、新たにvar titlebarAppearsTransparent: Boolプロパティと、NSFullSizeContentViewWindowMask定数が用意された。

titlebarAppearsTransparentはタイトルバーの背景を透明にするかどうかを表す。NSFullSizeContentViewWindowMaskは、NSWindowvar styleMask: Intプロパティに利用する定数で、タイトルバーの領域までをコンテンツの領域とすることを表現するものである。

NSFullSizeContentViewWindowMaskNSScrollViewの組み合わせによって、タイトルバーの下にコンテンツが透けるような表現が可能になる。またNSFullSizeContentViewWindowMaskを設定した上でtitlebarAppearsTransparentを真にすると、信号のようなコントロールだけのウインドウが作れるのだ。

window?.titleVisibility = .Hidden
window?.styleMask |= NSFullSizeContentViewWindowMask
window?.titlebarAppearsTransparent = true

f:id:cockscomb:20150208010259p:plain
タイトルバーの部分までNSSplitViewのコンテンツを表示させるようにしたウインドウ

伝統的手法

有史以来、NSWindowのタイトルバーをカスタマイズすると言えば大きく二つの方法があり、ひとつはウインドウのコントロールまで完全に自作して自由に表示する方法、そしてもうひとつはwindow.contentView.superviewという風にNSWindowの実装に依存してビューの階層を辿る方法である。現実的には主に後者が利用されることが多かったのではないだろうか。(NSThemeFrameなどで検索するとそういった方法を指し示すページが今も見つかるだろう。)

しかしOS X 10.10 Yosemiteでは、このビューの階層を辿る方法を使っていると"NSWindow warning: adding an unknown subview:"というログが出力されるようになり、代わりにオフィシャルな方法が提供されることになった。このことはAppKitのリリースノートに記載されている。

まとめ

ここまでで、OS X 10.10 Yosemiteから追加されたAPIによって、柔軟にタイトルバーをカスタマイズできることがわかった。これらはもちろん組み合わせることもできる。例えばSafariでは、titlebarAppearsTransparent = trueとした上でlayoutAttribute = .BottomNSTitlebarAccessoryViewControllerを設定しているかもしれない。

f:id:cockscomb:20150208010722p:plain
SafariのようにtitlebarAppearsTransparent = trueかつlayoutAttribute = .BottomNSTitlebarAccessoryViewControllerを設定したウインドウ

また昨今噂されるUXKitでは、UXNavigationControllerなどUIKit由来の機能が存在するらしい。これらのNSWindowの新しいAPIを使って、例えばUINavigationBar相当の機能をタイトルバーに押し込めることもできるのではないだろうか。

まだ見ぬOS X 10.11への期待を膨らませつつ、読者諸氏もNSWindowのタイトルバーをカスタマイズしてみてほしい。こちらからは以上だ。


詳解 Swift

詳解 Swift

砦を破壊する技術

みんな楽しそうに遊んでいてけまらしいので、Besiege買った。僕だって物理エンジンの世界で城砦を破壊してみたい。

古来より城攻めには投石器と相場は決まっており、かの芭蕉も「夏草や 兵どもが 夢のあと」という句で投石器の威力を詠っている。

f:id:cockscomb:20150205222218p:plain
見よ、これが投石機の雄姿である

どうせ一度きりの人生、ただの石ではなく爆弾を発射したいものである。

f:id:cockscomb:20150205222223p:plain
投石に失敗すると自爆する……

物理エンジンだけあって、現実世界と同じように失敗したりもする。人生に筋書きは存在しない。

f:id:cockscomb:20150205222227p:plain
美しい投石フォーム

画像だけでは伝わりづらいかもしれませんので動画を撮りました。

おもしろいので皆さんもどうぞ。

Androidアプリ開発を始めるときに読む本

今年はついにAndroidアプリを作りました。Androidアプリを開発するに当たって、Java言語にもAndroidプラットフォームにも明るくなかったので、勉強しようと本を読みました。いろいろ読みましたが、そのうち特によかったものを紹介します。

Java言語

Effective Java

Javaをちゃんと書けるようにしようと思って読みました。JavaのSerializableやClonableの挙動について知識を得られたし、Javaの言語上の特性を意識した設計について一定の視座を得られたように思います。読まずにJavaを書けていたとは思えないので、大変有意義でした。『並行性』の章などはまた再読したいと思います。

EFFECTIVE JAVA 第2版 (The Java Series)

EFFECTIVE JAVA 第2版 (The Java Series)

Android

プロの力が身につくAndroidプログラミングの教科書

ActivityやServiceにContentProvider、レイアウト、テストなど、一通り書いてあります。おおよそ網羅的で、どのような要素があるのかを最初に把握できます。これはアプリの設計においてなるべく正しい手段を選択するために必要なことだと思います。とにかく一通り書いてあるというのがとても良くて、なかなか他にはありませんでした。Androidアプリの開発を学ぶときに最初に手に取るべきは本書だと思います。

プロの力が身につく Androidプログラミングの教科書

プロの力が身につく Androidプログラミングの教科書

Master of Fragment

Fragmentのライフサイクルや復元、Support LibraryのFragmentについて、あるいはアニメーションについてなど、Fragmentについて学ぶべきほとんどのことが書いてあります。AndroidではFragmentでけっこうハマると思うので、Fragmentを活用したいならこれを読むとよいでしょう。

達人出版会でベータ版が買えるという状態ですが、いろいろなフォーマットに対応しているので不便はないと思います。

Master of Fragment (Android Professional Developerシリーズ)
あんざいゆき, わかめまさひろ
達人出版会
発行日: 2014-02-14
対応フォーマット: PDF, EPUB

Android Security 安全なアプリケーションを作成するために

AndroidiOSより自由度が高く、アプリ間の連携の仕組みもリッチですが、その副作用としてセキュアじゃないアプリを簡単に作れてしまいます。本書はAndroidの基本的なセキュリティの仕組みから解説されていて、とても役立ちます。Androidアプリを作るときには必ず理解しておきたい内容が書かれています。

Android Security  安全なアプリケーションを作成するために

Android Security  安全なアプリケーションを作成するために

Androidアプリ開発のセキュリティについては『Androidアプリのセキュア設計・セキュアコーディングガイド』も役に立ちます。

まとめ

Androidアプリ開発に入門するには、大体上記の4冊くらいを読めばよいと思います。あとはGoogleのドキュメントを眺めるとか、APIのdiffを全部見るとか、最新のイケてるライブラリを調べるとかするのがよいでしょう。技術の進歩が早い分野ですから、絶えず知識をアップデートしていく必要があります。

これから年末年始で、退屈凌ぎにAndroidアプリ開発を学ぼうという皆さんのお役に立てたらと思います。

WatchKitアーキテクチャ概論

来年初めにリリースされるというApple WatchSDKがプレリリースされた。Appleはこれを WatchKit と呼ぶ。Apple Watchで動作するWatchKitアプリは、いったいどのようなアーキテクチャにより駆動されるのか。この興味深い仕組みについて考察する。

WatchKit - Apple Developer

ふたつの予想

Appleウェアラブルデバイスを開発しているという噂が流れていた頃、そのアプリケーションのアーキテクチャについて異なる二つの予想をしていた。

一つは、ウェアラブル端末本体で動作する通常のアプリを作るというパターン。これはAndroid Wearが採用しており、Androidのアプリを作るのとほとんど同じようにウェアラブル端末で動作するアプリを作ることができる。これが採用されると、UIKitの多くのコンポーネントが利用できることになり、柔軟なアプリを作成できる。その反面でウェアラブル端末の少ないバッテリーは大きく消費される恐れがあるし、比較的非力なSoCに最適化する必要がある。加えて、ウェアラブル端末には通常インターネットに接続する機能はなく、コンパニオンとなるスマートフォンから借用することになるが、これを実装するのは面倒な作業になる。

もう一つは、コンパニオンとなるスマートフォンの側にアプリの機能を実装し、その画面をウェアラブル端末に表示するというパターンである。すなわちウェアラブル端末をセカンドスクリーンとして用いることになる。結果としてウェアラブル端末のハードウェア上の機能に束縛されることなくアプリを作ることができる。これはApple TVを利用するAirPlayや、車載端末であるCarPlayアーキテクチャに近いものであろう。ただし、ウェアラブル端末とスマートフォンは現行の技術ではBluetooth Low Energyで接続されることが予想され、画面を逐一転送できるだけの通信速度は出ないだろうと思われる。

これらの二つの予想は、それぞれに利点や問題を持っている。ただAppleの最近の技術的なトレンドを考慮すると、後者の方に分があるように思われた。

WatchKit

プレリリースされたWatchKit SDKのページを見ると以下のように記載されている。

WatchKit apps have two parts: A WatchKit extension that runs on iPhone and a set of user interface resources that are installed on Apple Watch. When your app is launched on Apple Watch, the WatchKit extension on iPhone runs in the background to update the user interface and respond to user interactions.

すなわち、ウェアラブル端末であるApple Watchには、Storyboardやリソースファイルだけをインストールし、コンパニオンとなるスマートフォンであるiPhoneの側でApp Extensionとしてプログラムが動作する。Apple Watchのユーザーインターフェイスが操作されればそれはiPhoneのExtensionに伝わり、iPhoneのExtensionからApple Watchのユーザーインターフェイスを更新する。

これは最初の二つの予想のちょうど中間的なアーキテクチャと言える。アプリはiPhoneの側にその多くを置いて、ユーザーインターフェイスApple Watchの側で動作させる。結果として多くの問題が解決され、アプリはiPhoneの潤沢なリソースや機能を利用できるし、BLEで画面を送信する必要もない。

注意すべきは、Apple WatchとiPhoneの間のやりとりに遅延が生じることである。例えばユーザーインターフェイスが操作されたときのインタラクションはApple Watchの側で素早く表示しつつ、少し遅れてiPhoneでアクションが起きる、ということになる。このことで、iPhoneのExtensionからApple WatchのViewの様子を取得することができないようになっている。iPhoneの側で、Apple Watchのユーザーインターフェイスに行った操作を記憶しておく必要がある。

また画像リソースはなるべくApple Watchのリソースに含めておくべきである。それができない動的に生成または取得される画像は、キャッシュとしてApple Watchの側へ登録しておくことで、再表示するときのコストを大きく下げられる。

この他にも制限は多く、例えばカスタマイズされた独自のユーザーインターフェイス要素は、Apple Watchで利用できない。WatchKitで事前に用意されたユーザーインターフェイス要素だけを用いてデザインする必要がある。

総論

Apple WatchとWatchKitのSDKは、ウェアラブル端末に注意深く最適化されたものであることが読み取れる。WatchKitのアーキテクチャによって、優れたユーザーエクスペリエンスが得られるだろう。

またこれはApple Watchに限らず、その他のiOS関連機器にも有効なアーキテクチャであると考えられる。CarPlayApple TVのほか、将来登場するかもしれない新たな端末にも応用可能である。

いずれにせよWatchKitのこのアーキテクチャは、AppleiOSOS Xで長年開発してきた技術の結実である。

ところでWatchKitアプリに独自のユーザーインターフェイスを作ることは未来永劫できないのだろうか。恐らく答えはXcodeにある。Xcode 6から、Interface Builderに独自のViewを表示できるようになった。内部的には独自のViewをXcodeとは別のプロセスで動作させ、レンダリングさせているように見える。WatchKitアプリでも、Apple Watch側でカスタマイズしたViewだけを動作させることは技術的に可能になると予想できる。将来のWatchKitでそのような機能が搭載されるかもしれない。


※ 以上の内容はAppleによって公開された情報を元に書かれています。