ローカルP2Pアプリをつくるためにググりまくった結果
GKSession、GKVoiceChatServiceがiOS7からdeprecated orz...
こんな感じで紹介されていたGKSessionとGKVoiceChatServiceの組み合わせによる構成も今は推奨されていない。
iOS7からはMultipeer Connectivity Framework
今まで、GKSession等を使って構成していたP2Pを専用のフレームワークを使ってつくることができる。
まずは、WWDC 2013 Session VideosのNearby Networking with Multipeer Connectivity
で概要を確認。
そのあとAppleの公式サンプルアプリMultipeerGroupChatをビルドして実機確認。
【簡単】MCAdvertizerAssistantとMCBrowserViewController
クラスメソッドさんのこの記事「[iOS 7] P2P 通信を手軽に実現する Multipeer Connectivity Framework を使ってみる」に実装例を含めて詳しく書いてある。 MCAdvertizerAssistantとMCBrowserViewControllerが端末を探して、相手と接続するまでの煩わしい部分をよしなにしてくれるので、非常に簡単!
MCBrowserViewControllerはフレームワークでUIまで用意してくれちゃっているので、公式のサンプルアプリも併せるとすごく簡単にわりとリッチなP2Pのチャットアプリまで作れてしまう。
「デフォルトのピア選択画面はねーよ」という意見もあり、
iOS7のフラットなデザインと相まって、MCBrowserViewControllerで提供してくれるUIは非常にシンプルで洗練されているのですが、いかんせん味気がないため、用意しているデザインなどとフィットしないということが十分にありえます。
※今回はその目的のために色々と調べてきたわけです。
【カスタマイズ】MCNearbyServiceAdvertiserとMCNearbyServiceBrowserを使ってP2P接続
こんな感じで実際にも需要はありそうなところだと思います。
一番、欲しい形でまとまっていたのがこの方の記事です。 [iOS 7 SDK] Multipeer Connectivity framework で Peer-to-Peer Connectivity
ここで紹介されているテスト接続用のアプリを同じように書いてみて、接続できたので、一応は調べたいところまでは行きました。
以下、気になる点
- Wi-fiではうまく接続できたが、Bluetoothでは??になるときがあった。
このあたりドキュメント上、Wi-fiもBluetoothも問題なくいけるはずだけどちょっと心配。
接続を許可するところなどもこっちで書かないといけないので、汎用的に使えるように書いておきたい。
VoiceChat部分の実装。
いいのなんかないかな。
ただ「いいね!」を押してもらうだけのシンプルなサンプルアプリ"Go Like On"を書いた
ずいぶん前に書いたサンプルアプリ。アプリ側からワンタップで「いいね!」までいけるかと聞かれて試しに実装してみた。 クライアント側の実装はすごくシンプルだけど、facebook developer側の設定がちょっとややこしかったのでメモとして残しとく。
Check Account
まず、端末側でfacebookにサインアップしているかをチェックする。このときアプリのアクセス許可を求めるダイアログが表示される。
- (void)checkAccounts { __block __weak ViewController *weakSelf = self; ACAccountType *accountType; ACAccountStore *accountStore = [[ACAccountStore alloc] init]; if ([SLComposeViewController isAvailableForServiceType:SLServiceTypeFacebook]) { accountType = [accountStore accountTypeWithAccountTypeIdentifier:ACAccountTypeIdentifierFacebook]; NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys: @"YOUR_FACABOOK_APP_ID", ACFacebookAppIdKey, [NSArray arrayWithObjects:@"public_actions", @"publish_stream", nil], ACFacebookPermissionsKey, ACFacebookAudienceFriends, ACFacebookAudienceKey, nil]; [accountStore requestAccessToAccountsWithType:accountType options:options completion:^(BOOL granted, NSError *error) { NSLog(@"error:%@",[error description]); NSArray *accountArray = [accountStore accountsWithAccountType:accountType]; NSLog(@"accounts:%@",accountArray); for (ACAccount *account in accountArray) { NSString *text = [NSString stringWithFormat:@"%@",[account username]]; [weakSelf performSelectorOnMainThread:@selector(showFacebookAccount:) withObject:text waitUntilDone:YES]; } }]; } }
アカウントの取得に成功するとアカウント(メールアドレス)が表示され、デカイいいねボタンがアクティブになる。
Facebook developer設定
基本設定
基本設定の中でbundle IDを指定する。
詳細設定
App Type
をNative/Desktop
に、
App Secret in Client
はNo
。
型
ここでちゃんとアクション型として"Like"を指定しておく。
いいねする
Facebookアプリ側の設定が正しく行われていれば、下記のメソッドでちゃんと「いいね」できるようになっているはず!
- (IBAction)onLikeButton:(id)sender { NSDictionary *information = @{@"url":@"http://somtd.hatenablog.com"}; [self postStreakWithInfomation:information]; } - (void)postStreakWithInfomation:(NSDictionary *)information { if ([SLComposeViewController isAvailableForServiceType:SLServiceTypeFacebook]) { ACAccountStore *accountStore = [[ACAccountStore alloc] init]; ACAccountType *accountType = [accountStore accountTypeWithAccountTypeIdentifier:ACAccountTypeIdentifierFacebook]; NSDictionary *optionsToRead = @{ ACFacebookAppIdKey: @"YOUR_FACABOOK_APP_ID", ACFacebookPermissionsKey: @[@"basic_info"], }; NSDictionary *optionsToWrite = @{ ACFacebookAppIdKey: @"YOUR_FACABOOK_APP_ID", ACFacebookPermissionsKey: @[@"publish_stream"], ACFacebookAudienceKey : ACFacebookAudienceFriends, }; [accountStore requestAccessToAccountsWithType:accountType options:optionsToRead completion:^(BOOL granted, NSError *error) { [accountStore requestAccessToAccountsWithType:accountType options:optionsToWrite completion:^(BOOL granted, NSError *error) { NSArray *accountArray = [accountStore accountsWithAccountType:accountType]; for (ACAccount *account in accountArray) { NSString *urlString = [NSString stringWithFormat:@"https://graph.facebook.com/%@/og.likes", [[account valueForKey:@"properties"] valueForKey:@"uid"]] ; NSURL *url = [NSURL URLWithString:urlString]; NSDictionary *params = [NSDictionary dictionaryWithObject:information[@"url"] forKey:@"object"]; SLRequest *request = [SLRequest requestForServiceType:SLServiceTypeFacebook requestMethod:SLRequestMethodPOST URL:url parameters:params]; [request setAccount:account]; [request performRequestWithHandler:^(NSData *responseData, NSHTTPURLResponse *urlResponse, NSError *error) { NSLog(@"responseData=%@", [[NSString alloc] initWithData:responseData encoding:NSUTF8StringEncoding]); }]; } }]; }]; } }
iTunes Storeの情報を簡単に取ってきて表示させるだけのサンプル"AppRankingParser"を書いた
Appleが太っ腹にも提供している"RSS Feed Generator" 目当てのランキングを取り出すためのインターフェイスをつくりました。 (RSS側の値が変更されると身も蓋もないことになるので今後なんとかしたい)
使ってもらうのはclass
以下の4つのファイルです。
RankingApi.h
RankingApi.m
RankingApiClient.h
RankingApiClient.m
RankingApiClient
はAFHTTPClient
のサブクラスとなっているので、こちらをそのまま利用する場合はAFNetworking
が必要になります。
RankingApiの概要
ARAudiobooks : iOSアプリのランキングを取得
+ (void)getiOSAppsRankingWithCountry:(ARCountry)country feedType:(ARiOSApps)feedType genre:(ARiOSAppsGenre)genre limit:(int)limit handler:(void (^)(id response, NSError *error))handler;
ARCountry : RSS取得対象の国
現在は、UnitedStates
,Japan
のみ利用可能。
ARiOSApps : フィードタイプ
ARiOSAppsTypeTopFree // iPhoneトップ無料
ARiOSAppsTypeTopPaid // iPhoneトップ有料
ARiOSAppsTypeTopGross // iPhoneトップセールス
ARiOSAppsTypeTopFreeiPad // iPadトップ無料
ARiOSAppsTypeTopPaidiPad // iPadトップ有料
ARiOSAppsTypeTopGrossiPad // iPadトップセールス
ARiOSAppsTypeNew //新着アプリ
ARiOSAppsTypeNewFree //新着無料アプリ
ARiOSAppsTypeNewPaid //新着有料アプリ
ARiOSAppsGenre : アプリのジャンル
iOSAppsBooks //ブック
iOSAppsBusiness //ビジネス
iOSAppsCatalogs //カタログ
iOSAppsEducation //教育
iOSAppsEntertaiment //エンターテインメント
iOSAppsFinance //ファイナンス ・・・
使い方(Demo)
% git clone git@github.com:somtd/AppRankingParser.git % cd AppRankingParser/AppRankingParserDemo % pod install
AppRankingParser/AppRankingParserDemo
ディレクトリの
AppRankingParserDemo.xcworkspace
を開く。
Demoでは適当なUITableViewControllerのサブクラスで上記のクラスメソッドを呼んでやってます。
[RankingApi getiOSAppsRankingWithCountry:Japan feedType:_iOSAppsType genre:0 limit:20 handler:^(id response, NSError *error) { _array = [NSMutableArray arrayWithCapacity:0]; NSLog(@"response:%@",response); for (NSDictionary *appDictionary in (NSArray *)response) { App *app = [[App alloc] initWithDictionary:appDictionary]; [_array addObject:app]; } [self.tableView reloadData]; }];
※Appというクラス名は適切ではありませんでした。ランキングを取得した結果をオブジェクトとして取り出す用に使っています。
はじめてのC4!C4のデモまとめ
前回、C4について本当に簡単にまとめて概要を説明してしまったため、結局何ができるのかさっぱりわからない記事になってしまった\(^o^)/
今回はあまり深く掘り下げずに、公式ページのTutorialsをもとにして、どういうことができるか動画を中心にまとめる。
C4Shape関連
Line End Points
Animated Dash Pattern 1
Animated Dash Pattern 2
Animated Dash Pattern 3
Shape Morphing
Interactive Curves
Image関連
Image Property
Movie関連
Volueme
Height/Width
Rate
OpenGL関連
Animation
C4Control関連
Perspective Distance
Subview Shapes
Animated Images Masking
User Interface関連
Interaction関連
LongpressAdvanced
Advanced Panning
Advanced
phasing
probability
シンプルなだけにできることの範囲は広そう。
はじめてのC4!
最近気になっているiOS向けのframeworkについて手を出しついでに簡単にまとめてみます。
公式ページはこちらC4:http://www.c4ios.com/
あんまり日本語の記事を見ないので手探りではありますが、今回は概要の拙訳とHello Worldから。
なんて読むの?
おそらく"CocoaFor"と読むのではないでしょうか。
C4とは
Code, Creatively.
C4 is a brand new creative-coding framework lets you build expressive user experiences and create works of art. C4 gives you the power of the native iOS programming environment with a simplified API that lets you get down to working with media right away. Build artworks, design interfaces, explore new possibilities of working with media and interaction.
要はメディアの扱いに特化したiOS向けのフレームワークです。 openFrameworksのiOS版?といったところなのでしょうか。
To The New Ones
Creating an expressive, intuitive mobile application is hard work. C4 makes this simple by letting you build projects that already deal with a lot of boring stuff. You get to start creating gorgeous elements faster and with more ease than tackling app development on your own.
C4 will help you learn how to make things move and interact in a very short amount of time. It is designed to look and feel like a simpler version of Objective-C, so that when you're ready to venture out on your own, the step from build C4 apps to native mobile applications for iOS is tiny.
C4はObjective-C自体の複雑さを感じさせることなく、小さなステップでネイティブのアプリをつくることができます。 とにかく、つくりたいものをより早く、簡単に、ゴージャスにつくることができます。
To The Crazy Ones
If you're already a developer and you know the ins and outs of developing for mobile platforms C4 still has a lot to offer you. First, C4 approaches animation and interaction in a synthetic way so that all its media objects are consistent and similar. If you want to learn from the way we build things the API is open (you can grab it HERE). If you don't want to learn how we do things, but just get down to using the core API you can include a compiled C4 library in your own projects and take advantage of all the work we've been doing (you can pull the headers and the libC4.a file from a project built off of an Xcode template).
すでにモバイルでのアプリ開発に親しんでいる人であっても、C4から学べることは多いはず。 中身をもっと詳しく知りたい人はC4はすべてオープンになっているのでそこから色々調べていってもいいし、 とにかく手っ取り早く試したい人はライブラリごとプロジェクトにつっこんでビルドしてもいい。
Open Source
C4 is an open source project, hosted on GitHub. It has an MIT license, so it’s as open as possible.
C4のプロジェクトはGitHubにホストされていて、ライセンスはMITとなっているので、公開できるもの全てがOpen Sourceになっている。
ではさっそくHello Worldを
C4をはじめるために以下の3つのものが必要になる。
今回はXcodeでの開発経験がある前提ではじめてのC4をすすめていく。
まずはC4のインストーラーを起動する。
インストール完了後、Xcodeを立ち上げる。
Xcodeの[File] > [New] > [Project...]を選択。
プロジェクトのテンプレートでC4が選択できるようになっている。
[C4 Single View Application]を選択し、適当なプロジェクト名をつけてプロジェクトを作成。
プロジェクトをビルドしてエラーが出ずに無事にシミュレーター上に以下のスプラッシュが表示されればひとまずは成功です。
次回以降で、C4のサンプルを試しながら何か作っていこうと思います。
C4をはじめるにあたってfollowすべきアカウント一覧
GitHub
Bluetoothサイコロ「DICE+」を試す
DICE+ DICE+が届いたので早速"Hello world"してみる。
DICE+をHello Worldする。
iOS版、android版、Unity版が用意されていてサンプルコードはGithub上にアップされている。
すぐcloneして試すことができるが、DicePlus.framework
自体はデベロッパ登録した後に手に入れることができるので、まずdeveloperサイトでサインアップしておく。
このリポジトリ"GameTechnologies / Hello-DICEPlus-for-iOS"からcloneしてきたプロジェクトに、developerサイトからダウンロードしたDicePlus.framework
をドラッグして追加する。
実機でビルドが完了したら、DICE+
本体を180度ひっくり返すことで電源が入る。
Bluetoothが接続しにいっている様子は伺えるが、iPhoneとDICE+の接続が確立しない。
色々と調べてみたが、ソフトウェア側には問題がないようなので、ハード側(DICE+側)を確認する。
Firmware uploaderで最新のDICE+にアップデート
結論からいうと出荷時のDICE+のファームウェアのバグ?で接続が確立していないようだった。 (デモとして配布されているDICE+対応ゲームはちゃんとうごいていたので、その時点でのファームウェアに対応して開発されたのだろうと思う)
ファームウェアのアップデートに際して、まずはuploaderをダウンロードしてくる。
ダウンロードしたアプリケーションを開くと下記のような画面が出る。 どうやらファームウェアをアップするためにまずはUSBケーブルの 抜き差しを2回 する必要がある。 抜き差しを2回 というのが重要。
普通にUSBケーブルを差しているだけだと薄いオレンジ色で点滅しているが、アップロードモードになっているときは白色で点滅。
認識されると下記のような画面に変わるので、uploaderをダウンロードしたところから最新のFirmwareをダウンロードしてきて、アップロードする。
リベンジ Hello DICE+のプロジェクトを再びビルド
今度は接続までの時間がスゴく短くなった!ちゃんと接続を確立したことを確認して(サイコロの字が緑色に光る)サイコロを振る!
ログの結果はこちら。完全にハロワー用のコードなのでログはシンプル。 もっと色々な情報が取れるのかも。
2013-10-23 23:11:13.550 Hello DICE+[18850:907] Good and bad rolls: 3 2013-10-23 23:11:13.552 Hello DICE+[18850:907] Good rolls: 3 2013-10-23 23:11:18.260 Hello DICE+[18850:907] Good and bad rolls: 4 2013-10-23 23:11:18.262 Hello DICE+[18850:907] Good rolls: 4
とにかくちゃんとサイコロの出目までとれた!
リアクティブプログラミングをかじる ReactiveCocoaについて
遅ればせながら、リアクティブプログラミングをかじる。
すごくまとまっている記事ですが、まだイメージできない...
なのでObjective-Cで書かれたコードを読んで理解してみようと試みている。
ReactiveCocoaはCocoa/Cocoa touch向けのリアクティブプログラミングframework。 OctoKitの中で採用されたりもしているので読み始めてみた。
Github:ReactiveCocoa/ReactiveCocoa
どういうものかをざっと把握するために、GithubのREADMEの一部を訳してみた。 ちょっとまだどこまで理解できているのかわからないので、次はサンプルアプリものぞいてみる。
Introduction
ReactiveCocoaはリアクティブ・プログラミングの機能が実装されています。
RAC(ReactiveCocoa)は現在と未来の値をキャプチャしたRACSignal
を提供します。
RAC
はソフトウェア側が継続的に値の監視を行う必要はありません。
RAC
はRACSignal
に反応したり、連鎖させたり、結合させたりすることによって、宣言的に記述することができます。
RACSignal
はFuture,Promiseデザインパターンのように、非同期処理を記述することができます。
これによってネットワークのコードを含むソフトウェアを大幅に簡素化することができます。
またRAC
を使う主な利点の一つとしては、callbackやBlocks、notification、KVO、さらにdelegateメソッドといったあらゆる非同期処理を単一のインターフェイスで扱うことができる点といえるでしょう。
Signals
次の例では、self.username
に変更が合った場合、コンソールに新しい名前を表示させます。
RACObserve(self, username)
によって現在のself.username
の値を送信するRACSignal
を新たに作成します。
-subscribeNext:
:新しい値が送信されるたびにBlockが実行されます。
[RACObserve(self, username) subscribeNext:^(NSString *newName) { NSLog(@"%@", newName); }];
Chaining
RACSignal
ははKVOのnotificationとは異なり連鎖することができます。
次の例では、コンソールに表示させるのは"j"
から始まる名前の場合とします。
-filter:
:そのBlock内の実行結果がYESを返したときにのみ値を送信する新しいRACSignalを返します。
[[RACObserve(self, username) filter:^(NSString *newName) { return [newName hasPrefix:@"j"]; }] subscribeNext:^(NSString *newName) { NSLog(@"%@", newName); }];
State
Signal
は状態の導出にも用いることができます。
これまでのようにプロパティを監視し、その変化に応じて状態を示すための新たなプロパティを設定しなくとも、RAC
によってSignalや操作そのものが状態の特性を示すことができるのです。
次の例では、パスワード入力欄とパスワード確認用の入力欄の値が同じであればcreateEnabled
をTrue
とする一方向のバインディングを作成します。
RAC()
はバインディングをいい感じに見せるためのマクロです。
+combineLatest: reduce:
はSignalの配列を受け取り、各Signalの最新の値をもとにBlockを実行し、その実行結果に応じて新たに戻り値を送信するRACSignal
を作成します。
RAC(self, createEnabled) = [RACSignal combineLatest:@[ RACObserve(self, password),RACObserve(self, passwordConfirmation)] reduce:^(NSString *password, NSString *passwordConfirm) { return @([passwordConfirm isEqualToString:password]); }];
Not just KVO
Signal
はただ単にKVOするだけではなく、どんなストリーム上にでもつくりだすことができます。
次の例では、ボタンを押したかどうかについても表すことができます。
RACCommand
はUIのアクションを示すRACSignal
のサブクラスになります。
-rac_command
がNSButtonに追加されています。
ボタンが押されることで自分自身にコマンドを通知します。
self.button.rac_command = [RACCommand command]; [self.button.rac_command subscribeNext:^(id _) { NSLog(@"button was pressed!"); }];
Asynchronous network operations
次の例では、ネットワーク経由でログインするためのボタンをフックします。
ボタンが押される度にloginCommand
が通知されます。
self.loginCommand = [RACCommand command];
loginCommand
が値を送信するたびに、このBlockは、ログインプロセスを開始します。
-addActionBlock:
コマンドが実行されるたびに、このブロック内で実行した結果を返します。
self.loginSignals = [self.loginCommand addActionBlock:^(id sender) { //この`-logIn`メソッドはrequestが完了したときにsignalを返します。 return [client logIn]; }];
ログインが成功したときのみ、ログメッセージを表示します。
[self.loginSignals subscribeNext:^(RACSignal *loginSignal) { [loginSignal subscribeCompleted:^(id _) { NSLog(@"Logged in successfully!"); }]; }];
ボタンをタップしたときにログインを実行するようにします。
self.loginButton.rac_command = self.loginCommand;
またSignalsはタイマーや他のUIイベント、時間を通じて変化するものについてであれば何でも表現することができます。
非同期の処理にSignalを使う場合、Signalを変換したり連鎖したりすることで、さらに複雑な処理を構築することができます。
次の例では2つのネットワーク処理の両方が完了した時点にコンソールログを表示する処理になります。
+marge:
:Signalの配列を受け取り、全てのSignalが完了した時点で新しいRACSignal
を返します。
-subscribeCompleted:
:Signalが完了した時点でBlockを実行します。
[[RACSignal merge:@[ [client fetchUserRepos], [client fetchOrgRepos] ]] subscribeCompleted:^{ NSLog(@"They're both done!"); }];
SignalはBlockのコールバックをネスティングする代わりに非同期処理を順次実行するために連鎖させることができます。 これはFuture,Promiseデザインパターンでよく用いられる方法に似ています。
次の例では、まずユーザーログインを行い、キャッシュされたメッセージをロードします。次にサーバーから残りのメッセージを受信し、全てが終わった時点でコンソールにログを表示します。
-flattenMap:
:新しいSignalを受ける度にBlockを実行し、受け取ったすべてのSignalを単一のSignalにマージして新しいRACSignalを返します。
[[[[client logInUser] //`-loginUser`は完了後にSignalを送る。 flattenMap:^(User *user) { // Return a signal that loads cached messages for the user. return [client loadCachedMessagesForUser:user]; }] flattenMap:^(NSArray *messages) { // Return a signal that fetches any remaining messages. return [client fetchMessagesAfterMessage:messages.lastObject]; }] subscribeNext:(NSArray *newMessages) { NSLog(@"New messages: %@", newMessages); } completed:^{ NSLog(@"Fetched all messages."); }];
RAC
は非同期処理の結果をバインドすることも容易になります。
次の例では、ユーザーの画像がダウンロードされたらすぐにself.imageViewのimageにセットするための一方向のバインディングをつくります。
-deliverOn:
:他のキューに自分の仕事をさせる新しいSignalを作成します。この例ではメインスレッドに戻って、バックグラウンドキューに作業を移すために用いられています。
-map:
:この例ではフェッチされたユーザーのBlockを呼び出し、Blockから返された値を送信する新しいRACSignalを返します。
RAC(self.imageView, image) = [[[[client fetchUserWithUsername:@"joshaber"] deliverOn:[RACScheduler scheduler]] map:^(User *user) { // Download the avatar (this is done on a background queue). return [[NSImage alloc] initWithContentsOfURL:user.avatarURL]; }] // Now the assignment will be done on the main thread. deliverOn:RACScheduler.mainThreadScheduler];