TwitterにポストするiPhoneアプリをBasic認証からxAuthに変更してみました
TwitterはAPIアクセスの 6月でBasic認証を廃止し OAuthのサポートのみになります。ただし、OAuthは認証手順の中でブラウザーを使ってTwitter側で認証する必要があり iPhoneアプリ等ではたいへんな手間になります。その為にOAuthの簡易版のような xAuthがサポートされました 。
そこで 私の作ったiPhoneアプリ「Tweetなう」をBasic認証からxAuthに変更してみました。
1. アプリを Twitterに登録する
a) xAuthを使うには、まず http://twitter.com/oauth_clients/new でアプリをTwitterに登録する必要があります(もちろんTwitterのアカウントも必要です)。
b) 登録後に表示される登録アプリ情報ページにOAuthで使う Consumer key や Consumer secret を始め重要な情報が書かれいるので保存しておきましょう。
c) ここで注意する点として、ここで登録しても OAuthしか使えせん。 登録アプリ情報ページの下の方に書いているようにメールでxAuthを有効にしてくれるようにリクエストする必要がります。
Note: xAuth is not enabled for applications by default. Email api@twitter.com to request xAuth access.
私の場合は、登録アプリ情報ページのURL と twitterのアカウントを書いた簡単なメールを送りましたが、次の日に有効になったよメールが来ました。
2. XAuthTwitterEngine を使ってみる
xAuth の手順は複雑なので、既存のライブラリーを使うのがお手軽だと思います。ここでは XAuthTwitterEngine を使ってみました。このライブラリーには Twitter API を簡単に使える MGTwitterEngine が使われています(含まれています)。
a) まずは XAuthTwitterEngine を clone し中にあるデモアプリを動かしてみて下さい。ただし、自分のアプリの Consumer key と Consumer secret を設定してあげる必要があります。
b) TwitterのID,Passwordを設定し、 1. Get xAuth Access Token をクリックし、2. Send test tweet をクリックするとTwitterにアプリ名で Testing xAuth from the XAuthTwitterEngineDemo! とつぶやかれます。
3. XAuthTwitterEngine を組み込む
デモアプリを参考に下のようなヘルパークラスを作りました。デモアプリから変更した点は:
- 1コールでポストできるようにした
- アクセストークンのキャッシュはしない。「Tweetな」は 単純にTwitterにつぶやくだけのアプリなのでセキュリティー的に注意が必要な情報のキャッシュは止めてしまいました。
- タイムアウト検出の追加
- エラーメッセージの変更。エラーの内容を表示しても通常のユーザーにはあまり役に立たないかなと思いシンプルしました。
注意点としては MGTwitterEngine はXMLとJSONの両方に対応出来るように書かれているのですが、そのままコンパイルすると YAJLのライブラリが無いエラーになります。XMLでやり取りするなら、ターゲット(ソース)からMGTwitter*YAJLParser.mを削除して libxml2.dylib をリンクして下さい。 MGTwitterEngineを使ってラクラクTwitter連携 - Tomute’s Notes に詳しく書かれています。
まだ、十分なテストは行ってませんのでバグがあるかも ^^);
5月12日追記:タイムアウトの処理にバグがあったのでコードを変更しました。
5月15日追記: delegate オブジェクトは retain すべきではない - 24/7 twenty-four seven を見てコード変更、ありがとうざいます!
XAuthTwitterStatusPost.h
#import <Foundation/Foundation.h> #import "XAuthTwitterEngineDelegate.h" @class XAuthTwitterEngine; @interface XAuthTwitterStatusPost : NSObject <XAuthTwitterEngineDelegate> { id delegate; SEL didSuccessSelector; SEL didFailedSelector; @private XAuthTwitterEngine *twitterEngine; NSString *username; NSString *password; NSString *message; } @property (nonatomic, assign) id delegate; @property (nonatomic) SEL didSuccessSelector; @property (nonatomic) SEL didFailedSelector; - (XAuthTwitterStatusPost *) init; - (void) setUserName:(NSString *)aUsername password:(NSString *)aPassword; - (void) postMessage:(NSString *)aMessage timeoutInterval:(NSTimeInterval)timeoutInterval; @end
XAuthTwitterStatusPost.m
#import "XAuthTwitterStatusPost.h" #import "XAuthTwitterEngine.h" #import "XAuthInformations.h" #import "SimpleAlertView.h" // シンプルなアラート表示 #pragma mark Private properties and methods definition @interface XAuthTwitterStatusPost () @property (nonatomic, retain) XAuthTwitterEngine *twitterEngine; @property (nonatomic, retain) NSString *username; @property (nonatomic, retain) NSString *password; @property (nonatomic, retain) NSString *message; - (void) timeOutAction; - (void) errorAlert:(NSError *)error; @end #pragma mark - @implementation XAuthTwitterStatusPost @synthesize twitterEngine; @synthesize username; @synthesize password; @synthesize message; @synthesize delegate; @synthesize didSuccessSelector; @synthesize didFailedSelector; #pragma mark Memory management methods. - (void)dealloc { [twitterEngine release]; [username release]; [password release]; [message release]; [super dealloc]; } #pragma mark public methods - (XAuthTwitterStatusPost *) init { self = [super init]; if (self) { self.twitterEngine = [[XAuthTwitterEngine alloc] initXAuthWithDelegate:self]; self.twitterEngine.consumerKey = kOAuthConsumerKey; self.twitterEngine.consumerSecret = kOAuthConsumerSecret; self.delegate = nil; self.didSuccessSelector = nil; self.didFailedSelector = nil; } return self; } - (void) setUserName:(NSString *)aUsername password:(NSString *)aPassword { self.username = aUsername; self.password = aPassword; } - (void) postMessage:(NSString *)aMessage timeoutInterval:(NSTimeInterval)timeoutInterval { self.message = aMessage; // NSLog(@"exchangeAccessTokenForUsername %@", self.username); [self.twitterEngine exchangeAccessTokenForUsername:self.username password:self.password]; if (timeoutInterval != 0.0) { [self performSelector:@selector(timeOutAction) withObject:nil afterDelay:timeoutInterval]; } } #pragma mark XAuthTwitterEngineDelegate methods - (void) sendUpdateToTwitter:(id)anArgument { // NSLog(@"sendUpdate: %@", self.message); [self.twitterEngine sendUpdate:self.message]; } - (void) storeCachedTwitterXAuthAccessTokenString: (NSString *)tokenString forUsername:(NSString *)username { // NSLog(@"Access token string returned: %@", tokenString); [self performSelector:@selector(sendUpdateToTwitter:) withObject:nil afterDelay:0.1]; } - (NSString *) cachedTwitterXAuthAccessTokenStringForUsername: (NSString *)username { // NOT cache the xAuthAccess token return nil; } - (void) twitterXAuthConnectionDidFailWithError: (NSError *)error { [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(timeOutAction) object:nil]; NSlog(@"twitterXAuthConnectionDidFailWithError: %@", error); [self errorAlert:error]; // callback if (self.delegate && self.didFailedSelector) { [self.delegate performSelector:self.didFailedSelector withObject:nil afterDelay:0.1]; } } #pragma mark MGTwitterEngineDelegate methods - (void)requestSucceeded:(NSString *)connectionIdentifier { [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(timeOutAction) object:nil]; NSlog(@"Twitter request succeeded: %@", connectionIdentifier); // callback if (self.delegate && self.didSuccessSelector) { [self.delegate performSelector:self.didSuccessSelector withObject:nil afterDelay:0.1]; } } - (void)requestFailed:(NSString *)connectionIdentifier withError:(NSError *)error { [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(timeOutAction) object:nil]; NSlog(@"Twitter request failed: %@ with error:%@", connectionIdentifier, error); [self errorAlert:error]; // callback if (self.delegate && self.didFailedSelector) { [self.delegate performSelector:self.didFailedSelector withObject:nil afterDelay:0.1]; } } #pragma mark Private methods - (void) timeOutAction { NSlog(@"Twitter request Timout"); [SimpleAlertView alertWithTitle:@"タイムアウト" message:@"時間をおいてから再度つぶやいて下さい"]; // callback if (self.delegate && self.didFailedSelector) { [self.delegate performSelector:self.didFailedSelector withObject:nil afterDelay:0.1]; } } - (void) errorAlert:(NSError *)error { [SimpleAlertView alertWithTitle:[NSString stringWithFormat:@"Twitter送信エラー (%d)", [error code]] message:@"ログイン/パスワードが正しくない、Twitterサービス、通信などが考えられます"]; } @end