ラベル Swift の投稿を表示しています。 すべての投稿を表示
ラベル Swift の投稿を表示しています。 すべての投稿を表示

2015年1月15日木曜日

ReactiveCocoa を Swift から使ってみた(2) KVO編

前回の記事から引き続き、ReactiveCocoaを触ったりしています。FRPの概念に慣れてくると通常のプログラミングスタイルでは得られない知見に遭遇出来てなかなか面白いです。

今回はまずSwiftでReactiveCocoaを学ぶときに参考にするドキュメントについてご紹介したいと思います。といってもSwiftに特化したドキュメントはほとんど存在しないため、Objective-CでReactiveCocoaを学ぶときのドキュメントを見て学ぶしか無いのが現状です。

私は個人的にはサンプルコードを直接触るほうが性に合っているようなので、以下のオープンソースのReactiveCocoaで作られたアプリのコードを見ています。
https://github.com/AshFurrow/C-41
https://github.com/jspahrsummers/GroceryList
GroceryListのほうがより本格的にFRPっぽい書き方をしているのでオススメです。実際にコードを動かしたい場合はC-41のほうが比較的簡単に動かしやすいと思います(それでも使われているライブラリが古いためビルドを通すのが大変だったりしますが・・・)

SwiftでReactiveCocoaを使っている例としてはCarthageが良いと思います。ただしMacのコマンドラインアプリなのでUIまわりがらみのサンプルとしては余り参考になりませんでした。
https://github.com/Carthage/Carthage

例えばReactiveCocoaを使う上でどうしても欠かせないのがRACObserveを使った特定プロパティに対するKVOのシグナル化です。これにより特定のプロパティが変化した時にシグナルを受け取ることができるようになります。通常Objective-CでReactiveCocoaを使う場合は標準のRACObserveというマクロを使用すれば良いのですが、Swiftではマクロが使用できないため他の方法を採用する必要があります。

調べてみたところ、幸いにしてRACObserveマクロは単なるNSObjectのrac_valuesForKeyPathメソッドのラッパですので、以下のようにしてrac_valuesForKeyPathを使用すれば解決できます。


ここで注意することとして、KVO対象となるプロパティにはdynamic修飾子を付ける必要があります。Objective-Cにもdynamic修飾子はありましたが、Swiftのdynamic修飾子はObjective-Cのものとは意味が異なりdynamic修飾子を付けたプロパティについてObjective-Cと同様に動的プロパティアクセスを行うようにする(具体的にはobjc_msgSendする?)という意味があります。詳細については以下の記事を参照してみてください。
http://stackoverflow.com/questions/24092285/is-key-value-observation-kvo-available-in-swift#comment39273366_24092370
https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Declarations.html

2015年1月1日木曜日

Swiftプログラマ格付けチェック (2015新年スペシャル)

Swiftプログラマ格付けチェック

今回一流のSwiftプログラマの皆さんに格付けチェックしていただくのはこちら!

JSONライブラリ

です!

ひとつは日本が誇る一流プログラマ、dankogai氏が作成されたJSONライブラリ、githubスター数312
ひとつは当ブログ管理人三流プログラマ、akisuteが適当にググって見つけたJSONライブラリ、スター数16
となっております。
皆様にはこの2つのうちからdankogai氏のライブラリを当てていただきます!

Aのライブラリ
Bのライブラリ
(コメントはすべて削除しています)

それでは正解だと思ったほうの部屋に入っていただきましょう!

Aが正解だと思った人の部屋
Bが正解だと思った人の部屋

2014年12月23日火曜日

ReactiveCocoa を Swift から使ってみた

FRP(Functional Reactive Programming)なるものが流行っているらしいので、私もたまには流行に乗っかってみることにしました。手始めにReactiveCocoaをSwiftで一日ほど使ってみました。

導入

こちらのブログにまとまっていますので、そちらを参照していただければ良いかと。基本的にはCocoaPodsで一発です。
http://tnakamura.hatenablog.com/entry/2014/11/15/how_to_use_reactivecocoa_in_swift

ドキュメント

基本的にはプロジェクトのGitHubにしっかりドキュメントがあるのでそれを見ればだいたい大丈夫かなと思います。
https://github.com/ReactiveCocoa/ReactiveCocoa
https://github.com/ReactiveCocoa/ReactiveCocoa/tree/master/Documentation
APIの使い方がわからなければヘッダファイルを見れば相当詳細にコメントがついているのでそれでほぼ問題無いです。それでもわからなければ結構ググればかなりヒットします。熱狂的なファンがいるようです。

概念

こちらのページに非常に詳細に書いてあるのでお勧めです。こちらを見るだけでなんとなく概念がつかめてReactiveCocoaは何をするフレームワークなのかがわかって良いと思います。以下主要なクラスに対して私が理解した内容です(間違ってたらゴメンナサイ)。

RACStream

連続した値を表すすべての既定概念。連続した値というのは今現在すぐに返せる値も通信や計算などによって将来的に返される値も含む。関数型言語で言うところのモナドらしいけどモナドはさっぱり。

RACSequence

RACStreamの実装の一つで、Pull-Baseなもの。すなわちユーザが値を要求して、それに対して値を返すオブジェクト。関数型言語でいうところのリストとかシーケンス。

RACSignal

RACStreamの実装の一つで、Push-Baseなもの。すなわちシステムが何らかのイベントやタイミングに応じて値を返すオブジェクト。もちろん値が返ってくるのは直後かもしれないし遠い未来かもしれない。

RACSubscription

RACSignalに対するコールバックみたいなもの。Promiseパターンのthenとかcatchとかfinallyみたいなもの。

RACCommand

UIBarButtonItemとかUIButtonなどのユーザーインタラクションを表すクラスみたいです。まぁ要するにIBActionみたいなもののようです。加えてRACSignalを使ってenabledの状態をコントロールしたりsenderをfilter/map/reduce/その他いろいろ加工可能。

RACTuple

引数とか返り値とかでよく使われるタプル。Swiftのtupleとは違うので注意。

だいたいこれぐらいわかっていたらコードが書けました。

実験

単純なメモアプリを作って実験しようと思いとりあえずこんなテーブルビューを作ってみました。

そしたら問題が出るわ出るわ。

マクロが使えない

Swiftからだと便利なマクロが使えないので非常に困ります。さらに次に挙げる一部の問題はマクロがないと解決できません。

配列の内容変化に対してRACSignalを取れない

オブジェクトの変化を監視するRACSignalがKVOを元に実装されていて、KVOがArrayに対して使用できないので当然なんですが、これのせいでいきなりReactiveCocoaの世界からいつものCocoaの世界に引き戻されます。
良い対処法はないようです。一応Objective-Cならマクロとか使って対応可能なように見えますがSwiftではどうしようもありませんでした。
https://github.com/ReactiveCocoa/ReactiveCocoa/issues/500
https://github.com/ReactiveCocoa/ReactiveCocoa/issues/1197

delegateパターンをRACSignalに変換するのが厄介

RACSubjectという自分で自由自在に状態を操作できるRACSignalのサブクラスを使ってシグナルをコントロールする方法がまずお手軽です。

またはこちらのブログで紹介されているrac_signalsForSelectorなどを使う方法もあります。
http://spin.atomicobject.com/2014/02/03/objective-c-delegate-pattern/

内部的に黒魔法が多い

先ほどのrac_signalsForSelectorもそうですが、内部で平然とmethod swizzlingを使い放題使いまくっていたりするのでちょっと怖いです。


まとめ

Objective-CでFRPの勉強をするにはちょうどいいんじゃないでしょうか。

2014年12月19日金曜日

SwiftからCやObjective-Cのライブラリを扱うときのテクニック数点

Objective-C Bridging Headerを利用することで、Swiftは既存のいかなるC/Objective-Cコードのシンボルでも呼び出すことが可能になっています。しかしながら場合によってはSwift単体では素直に書きづらいハマりどころがあります。C/Objective-Cのラッパーを作り、Objective-C Bridging Header経由でSwiftから呼び出せば全ての問題は解決できるのですが、面倒くさいですしやはりSwift単体で何とかしたいですよね。そこでここでは素直に書きづらいハマりどころと、それを何とかしてSwift単体で解決する方法をご紹介します。

※以下の情報は2014/12/19現在のものです。Swiftは言語仕様の変化が激しいので予期せず変更されている場合があります、ご了承ください。

1. ARCに管理されていないObjective-Cオブジェクトを扱う

例えばKeychainを扱うAPIなどで、ARCに管理されていないObjective-Cオブジェクトを扱うことがあります。このような場合にはUnmanaged型を使用します。

Unmanaged型はメソッド経由でretain, release, autoreleaseを行うことができるほか、takeUnretainedValue()またはtakeRetainedValue()メソッド経由でT型のSwiftオブジェクトを取り出すことができます。

2. C言語のポインタを扱う

生のC言語のポインタを扱う場合、たいていのケースではUnsafePointer型を使うようにCのAPIがSwiftのAPIに変換されます。このとき、Swift上ではUnsafePointer型を要求するのに、C言語のAPI的にはNULLを渡したい場合には、nilを渡すことができないので、代わりにUnsafePointer.null()を渡すことができます。

UnsafePointer以外にもCOpaquePointer型やCFunctionPointer型に変換されるAPIもありますが、この場合も同様にCOpaquePointer.null()やCFunctionPointer.null()をAPIに渡してやればうまくいきます。

3. cStringUsingEncoding()メソッドの罠に注意する

SwiftにはSwiftネイティブのString型とCocoaのNSString型が存在します。基本的にはこの2つは自動的にうまい具合にブリッジされるためプログラマは違いを意識する必要がありませんが、実はcStringUsingEncoding()メソッドを使う場合にはこれが重大な問題になってきます。String.cStringUsingEncoding()は[CChar]?を、NSString.cStringUsingEncoding()はUnsafePointerを返すのです。さらにコンパイラは文字列をStringとして解釈するのを優先するため、先ほど述べたC言語のAPIに渡す際に型が合わないという理由でエラーになりがちです。

対策として上記の通り明示的にNSStringにキャストすることをおすすめします。

4. enum値のOR結合を何とかする

すみません、なんともなりませんでした(´・_・`)
例えば以下のようなコードを書くこと自体は可能なのですが、適切なenum値を得ることができません。optionsはnilになってしまいます。

どうしてもOR結合が必要なenum値が存在する場合は、現状C/Objective-Cでラッパーを作りObjective-C Bridging Header経由でSwiftから呼び出すしかないようです。


2014年9月8日月曜日

【ヤヴァい】リリース直前の Swift の仕様が早くも悲惨なことになってる

正式版リリースまで後一ヶ月と噂されるXcode 6と新言語Swiftですが、リリース一ヶ月前にも関わらずその仕様が早くも悲惨との声がごく一部から上がっているようです!!



public private(set)って結局どっちなの!?

beta 5から追加されたアクセス制限指定子ですが、その仕様に疑問の声が!


なるほど、public private(set) var numberはキモいですね〜。ちなみにこれはgetterがpublicでsetterがprivateな変数の宣言のようです。なんかもっといい文法はなかったんでしょうか・・・



一行closureを使うときに注意!思わぬ落とし穴が!




これはSwiftの言語仕様上、実行コードが一行しかないclosureは暗黙的にreturnするものとみなされてしまうからのようです。これはちょっとひどいですね〜〜!



中には良い一面も・・・?



Nil Coalescing Operatorというのは ?? 演算子のことです。これは A ?? B のように使い、AがNot NilならAを、そうでなければBを返すという演算子です。なかなか便利に使えますよ!ちょっとはSwiftも見なおしたかも!?



おわりに

某まとめっぽくしたらなんかイマイチな感じになりました(´・_・`)
あとAppleふざけんな

2014年6月9日月曜日

既存の Objective-C のメソッド引数の Swift 上での扱われ方を調べてみた

前置き

こちらの記事には2014/06/09現在、公式にはリリースされていないiOS8プレリリースドキュメントへのリンクが含まれます。iOS8にて新しく追加された内容には一切触れておらずAppleとのNDA規約にも違反するものではないという認識ですが、場合により予告なく削除する可能性があります。予めご了承ください。

本題

iOS8プレリリースドキュメントを眺めていて気になったのですが、ほとんどのCocoaのメソッドの引数に!がついています。例えばNSKeyValueObservingプロトコルのaddObserver:forKeyPath:options:context:メソッドのシグネチャは以下のようになっています。

func addObserver(_ anObserverNSObject!,
      forKeyPath keyPathString!,
         options optionsNSKeyValueObservingOptions,
         context contextCMutableVoidPointer)

第1引数と第2引数に!がついているのがわかると思います。
更にもうひとつ気になったのが、このメソッドの第3・第4引数です。これらは元々0やNULLポインタを渡すことができる引数だったのですが、見ての通り引数が?で宣言されておらず、そのままnilを渡してしまうとエラーになってしまうように見えます。

直感的に考えると、

  • !がついている引数は呼び出し時に強制的にunwrapされるので、nilを渡してはいけない、必須引数なのではないか?
  • !がついていない第3第4引数はnilが渡せないのではないか?

という風に思うのですが、実際には

  • !がついている引数にnilを渡すことができる。さらにnilを渡してもランタイムでクラッシュしない。
    • 今回の例のaddObserver:forKeyPath:の場合はクラッシュしますが、これは元々のAPIがそうだったからで、例えば他のCocoaのAPIで!がついているものでnilを渡しても問題ないものが多数存在します。
  • !がついていないNSKeyValueObservingOptions, CMutableVoidPointerにnilを渡してもコンパイルエラーにすらならず、何の問題もなくそのまま動作する。

このような私の理解とは正反対の挙動をします。大変気になったので調べてみることにしました。

!がついている引数の謎

これについてはそもそも私のSwiftに対する理解が完全に間違っていました。正しい理解は以下のとおりです。

  • 無印型 - nilを渡すことができない。nilになる瞬間が一瞬もない。これこそがnilを渡せない必須引数である。
  • ?型 - Optional型であり、nilを扱うことができる。これについては問題ない。
  • !型 - ImplicitlyUnwrappedOptionalという特殊なOptional型であり、Optional型なのでnilを扱うことができる。すなわち!が付いている引数については任意引数でありnilを渡すことができる。
    • !も?も演算子でも命令でもなく型であり、!や?をつけることで対象を型に包んでいる、と考えれば良いと思います。
    • !と?の違いはメンバにアクセスした際に暗黙的かつ強制的に元の型にアンラップされるか、そのままOptionalとして扱われるかの違いだけです。

ではなぜCocoaのAPIは引数で?ではなく!を使ってnilを受け取れるようにしているのでしょうか?これは引数を受け取った後に、その引数にアクセスした場合の挙動がおそらく影響しているのではないかと思われます。以下の例を見てください。

こちらのコードですが、safeSwim()メソッドが旧Objective-Cと完全に同じ挙動を示します。これは以下の様な理由によるものです。

  • self.name(!型)が?型によってラップされる。
  • self.name?の.descriptionにアクセスする。このとき?型によってラップされているので、self.nameがnilであれば何も起こらず、self.nameがnilでなければまず?型からアンラップされ、次に強制的に!型からアンラップされ、その結果通常通りdescriptionが呼び出される。
  • 通常だと?型の返り値は?型にラップされてしまうが、self.nameが!型なので強制的にアンラップされ?型ではなく素のString型を返すことができる
    • ここだけよく理解できてないです・・・

間違っていたらすみません(´・_・`)

とにかくこれこそが旧Objective-CのAPIについて!で型が表現されている理由ではないかと考えています。

!がついていないのにnilが渡せる引数の謎

こちらの謎はカラクリがわかってしまえば簡単です。前回の記事をご覧になった方はお気づきになったかもしれませんが、これらの型はAppleが__convertion()メソッドをNilTypeに対して追加しているため、nilを問題なく扱うことができます。

まとめ


  • !が付いている引数は?と同様にnilを渡しても良い引数である。nilを渡せない必須型は何もついていない引数のみである。
  • !はImplicitlyUnwrappedOptionalという特殊なOptional型であり、アクセス時にOptionalではなく強制的に元の型を返す点がOptionalと異なる、と考えれば理解しやすい。
  • 変数をすべて!で定義し、メンバアクセス時に常に?をつけるようにすると、どの変数にもnilを渡すことができ、実行時にnilがあればそこで実行がスキップされ、さらに返り値もOptionalではない通常の型にできるため、旧Objective-Cとほぼ同じ挙動になる。
    • ただしSwiftで新しく用意されているAPIを見る限りnilを扱う必要がある箇所については可能な限り!ではなく?を引数や返り値に使っているように見えるため、新しくSwiftで書く箇所については!を乱用するべきではないと思われる。
  • Cocoaの用意している型のうち、元々nilやNULLや0を渡すことができた型については、NilTypeに__conversion()メソッドが追加されているので、そのままnilを渡すことができる。

Swift で __conversion メソッドを使ってカスタムの型変換を定義する方法

2014/10/21追記:
Xcode 6.0 beta 6以降、__conversion()を使った暗黙的なas演算子を用いた型変換はサポートされていません。Xcode 6.1(Swift 1.1)現在、暗黙的な型変換を行う手段はないため、型変換を行いたい場合はイニシャライザを定義する方法を取るのが通例として良いと思います。

class 変換対象の型 {
  init(_ obj: 変換元の型:) -> 変換対象の型 {
    return 適当に変換対象の型を返す
  }
}



Swiftではas演算子を使ったり、型の定義されている変数・定数へ代入したり、メソッド呼び出しの引数にオブジェクトを渡す際に型変換が行われますが、デフォルトでは対応していない型変換があったりします。例えばStringはasを使ってもIntに変換することはできません。

また、SwiftではnilはNilTypeという型のシングルトンとして実装されており、nilを渡すとNilTypeから当該型への型変換が行われるようです。?(Optional)型や!(ImplicitlyUnwrappedOptional)型はNilTypeからの型変換に対応しているからnilが代入でき、それ以外の型は対応していないためコンパイルエラーになるというのがカラクリのようです。

この型変換ですが、__conversion()というメソッドを変換元の型に実装することで、任意の型変換を自分で実装する事ができます。以下のようになります。
class 変換元の型 {
  //@conversion属性は付けても付けなくても大丈夫みたいですが、一応つけます
  @conversion func __conversion() -> 変換対象の型 {
    return 適当に変換対象の型を返す
  }
}
以下にNilTypeを使ったnilからカスタムクラスへの型変換サンプルを示します。こうすればnilを引数にとってもエラーになりません。

ご覧のとおり、extensionとの組み合わせで既存のクラスも含むどのような型からどのような型への型変換にも対応可能になりますので色々はかどります。ぜひご活用ください。

2014年6月6日金曜日

Swift の enum型を for-in でイテレーションする方法

例えばJavaのEnum型などはそのまま以下のようにイテレーションすることが可能なのですが、

なぜかSwiftのenum型はそのままではイテレーションすることができません。対策としてGeneratorという仕組みが標準ライブラリに用意されてますので、それを使ってenumをイテレーションできるようにします。

具体的には、Generatorを継承したクラスを作成して next() -> Element? を実装してください。ElementはAnyObjectのtypealiasなので実際には好きな型を返していただければOKです。あとはSequenceOf<T>型でGeneratorをラップしてあげればOKです。next()メソッドがnilを返すまでSequenceOf<T>はイテレーションを続けてくれます。

以下にサンプルコードを示します。

Generator内部でyieldが使えれば便利なんですが、おそらくyield構文は無さそうです。Enum型を一覧したい場合以外にもGeneratorは便利に使えますのでぜひお試しください。

Swift を使ってみてがっかりした点まとめ



数日間iOS8/Xcode6/Swiftな環境で色々試してみて、Swiftを使っていて思ったよりがっかりした点が多かったのでちょっとまとめてみようと思います。

動的な処理がSwiftだけでは一切できない

[NSObject performSelector:]の類と、NSInvocationがSwiftからは一切呼び出せません。使おうとすると怒られます。objc/runtime.hは試していませんが、同様に直接Swift経由では呼び出せず間にObjective-Cをかます必要があるのではないかと思われます。

@optionalなprotocolが限定的にしか使用できない

具体的には@objc属性を付けないと使えません。しかしながらこのような後方互換性のためだけに存在する属性をいつまでもAppleがサポートするかは疑問が残るというのと、もう一つ以下の様な問題があります。

@objc属性のついたSwiftの型はただのObjective-Cクラスになる


こういう問題があるのであまり使いたいとは思えません。ちなみになのですがCocoaのクラスはほぼすべて@objc属性が付いているため、それを継承して使うことになるアプリでは事実上Swiftの本来の能力を出せないのではないかと思っていますが、実際のところはわかっていません・・・

メモリ管理が相変わらず必要

Swiftのメモリ管理はGCではなくARCでありただの参照カウント方式にすぎないため、Swiftでも循環参照が発生しないようにプログラマが明示的に参照の種類を指定しなければなりません。その上Objective-Cでも存在したstrong, weakに加えunownedという新しい種類のメモリ管理が追加されています。これはweakは参照が消滅するとnilにするという挙動であるためOptional型を使わなければならないのに対し、unownedは参照が消滅してもnilにならない代わりに通常の型がそのまま使えるというもののようです。

closureでselfをキャプチャするときの循環参照対策が相変わらず必要

いちばんがっかりしたのがこれです。Swiftはdelegateよりもclosureを使ったcallbackのほうが言語構造上向いているためclosureを大量に使うことになると思うのですが、このときselfがclosureを強参照し、closureがselfをキャプチャするようなコードを書いてしまうと、循環参照になるためメモリが開放されなくなるという問題がObjective-Cから引き続き発生します。対策としてclosure capture listと呼ばれる新たな構文が追加されています。closureの先頭、引数宣言の前に[unowned self]のような構文を追加することで、selfをunownedとしてキャプチャすることができます。

以下に使用前・使用後の例を示します。

Objective-CのweakSelfよりはマシに思えますが、とはいえこの辺りはコンパイラが自動的に対応してほしいところです(´・_・`)