2010年11月28日日曜日

[iOS] UIViewControllerのdeallocが呼ばれなくてハマった

ふとしたきっかけでdeallocが呼ばれないUIViewControllerのサブクラスができてハマってしまいました。
小ネタですがメモしておきます。

[self.navigationController popViewControllerAnimated:YES];

popViewControllerAnimated:の裏側では popされたViewControllerがreleaseされてdeallocが呼ばれるはずです。

ところが

下のような構成のViewContorllerではpopされてもdeallocメソッドが呼ばれませんでした。
UITableViewControllerを使わずに、素のUIViewControllerのUIViewにUITableViewを埋め込もうとしています。
UIViewControllerとTableViwe間でやりとりをするために、お互いに相手のpropertyを持たせています。

@interface MyTableView : UITableView 
{
    MyViewController *myViewController;
}

@property (nonatomic, retain) MyViewController *myViewController;

@end

@interface MyViewController : UIViewController
{
    MyTableView *myTableView;
}

@property (nonatomic, retain) MyTableView *myTableView;

@end


@implement MyViewController

@synthesize myTableView;

- (void)loadView {
    [super loadView];

    self.myTableView = [[MyTableView alloc] initWithFrame:CGRectMake(0, 0, 100, 100)];
    myTableView.myViewController = self;
    [self.view addSubview:myTableView];
}

- (void)dealloc { // なぜかこれが呼ばれない
    [myTableView release];
    [super dealloc];
}

@end
どうも
myTableView.myViewController = self;
このようにpropertyのpropertyにselfを指定してしまうとdeallocが呼ばれなくなるようです。

対処法としてはselfを指定しているpropertyにnilをセットすること。
- (void)viewDidDisappear:(BOOL)animated {
    [super viewDidAppear:animated];

    if (self.parentViewController == nil) { // popされた時のみ実行される
        myTableView.myViewController = nil;
    }
}


delegateパターンでも同じようにnilをセットしてやる必要があるので注意です。




[2011/02/15 追記]

上のと似たような事例ですがまたdeallocが呼ばれない現象が発生したのでメモしておきます。
// NSArrayのプロパティにselfを格納する
self.dataSourceArray = [NSArray arrayWithObjects:self, obj1, obj2, nil];
NSArrayに(NSDictionaryも)値を入れるとその時点でretainCountがひとつ増えます。
なので上の事例と同じように、例えばviewDidDisappearなどで
dataSourceArray = nil;
としてやる必要があります。

2010年11月25日木曜日

Facebookのアカウントを使ってOAuth認証するメモ

Facebookのユーザーが日本でも増えてるらしいのでFacebookのAPIを使って何かできたらなと思い、とりあえず認証の仕組みを調べてみたのでざっくりとしたメモを残しておきます。

基本的にWebアプリの話です。
iOSアプリの場合はiOS用のSDKをごにょごにょすればあんまり難しいことを考えずにすみます。



なにはともあれ、ドキュメントはこちら

Authentication - Facebook Developers
http://developers.facebook.com/docs/authentication/



Facebookの認証はWebアプリでもデスクトップアプリでもOAuth2.0を利用しています。
Twitterの認証もOAuthなのですが、TwitterのほうはOAuth1.0なので仕様が異なります。

また、Twitterの認証は 「認証即全権限付与」という感じなのですが
Facebookでは パーミッションの付与 ->  認証 という流れになっています。

デフォルトではユーザーのプロフィール情報などのみ取得できます。
メールを送信したり写真/ビデオを扱うためには別途パーミッションをリクエストする必要があります。



以上を踏まえた上で、実際に認証するコードが下のようになります。
facebookが用意しているPython用のコードから一部抜き出しています。

# アプリの設定ページでApplication ID, Application Secretを確認しておく
# http://www.facebook.com/developers/apps.php
FACEBOOK_APP_ID = 'app_id'
FACEBOOK_APP_SECRET = 'app_secret'

class LoginHandler(BaseHandler):
  def get(self):
    verification_code = self.request.get("code")
    args = dict(client_id=FACEBOOK_APP_ID, redirect_uri=self.request.path_url)

    if self.request.get("code"):
      # authorizedされたらcodeが飛んでくる
      # そのcodeを利用してaccess_tokenを取得するリクエストを投げる

      args["client_secret"] = FACEBOOK_APP_SECRET
      args["code"] = self.request.get("code")
      content = urllib.urlopen(
                  "https://graph.facebook.com/oauth/access_token?" +
                  urllib.urlencode(args)).read()

      # access_token=***&expires=*** というクエリストリング形式の文字列が返ってくるのでパースする
      response = cgi.parse_qs(content)
      access_token = response["access_token"][-1]

      # access_tokenがゲットできたらプロフィール情報も取得できる
      profile = json.load(urllib.urlopen(
                  "https://graph.facebook.com/me?" +
                  urllib.urlencode(dict(access_token=access_token))))

      self.redirect("/")
    else:
      # 認証用のコードがなければauthorizeへリダイレクト (パーミッションの確認)
      self.redirect(
        "https://graph.facebook.com/oauth/authorize?" +
        urllib.urlencode(args))
その他、Cookieを仕込んだりaccess_tokenやプロフィール情報をストレージに格納したりログアウト処理の実装は割愛
PHP用のコードもfacebookが用意してくれているので必要な方はそちらをどうぞ。

access_tokenがあってパーミッションが与えられていればGraph APIの各メソッドを実行することが可能です。


FacebookはなんとJavaScript用のSDKまで用意していて、これを使えばJavaScriptオンリーで認証やGraph APIの利用が可能になっています。



[2011/02/03 追記]

OAuth 2.0の概要やOAuth 1.0とOAuth 2.0の違いなどは下の記事が参考になります。
OAuth 2.0でWebサービスの利用方法はどう変わるか