Hatena::ブログ(Diary)

Over&Out その後 このページをアンテナに追加 RSSフィード Twitter

2012-03-27

NSUserDefaults に保存する際に自動的に iCloud にも保存してくれるライブラリ "MKiCloudSync"

MKiCloudSync を使用すると、たった1行コードを追加しておくだけで、あとは自動的に NSUserDefaults の内容を iCloud に同期してくれます。


もともとシンプルな iCloud 同期ですが、もっとシンプルになるのでとりあえず iCloud 対応したい、ちょっと試してみたい、等の場合にオススメです。


使い方

1. ソースをダウンロード

https://github.com/MugunthKumar/MKiCloudSync


2. ヘッダをインポート

#import "MKiCloudSync.h"

3. start メソッドをコール

AppDelegate の application:didFinishLaunchingWithOptions: 内で、以下のように start メソッドをコール

[MKiCloudSync start];

たったこれだけ。


「おいおい、iCloudに書き込みたいときはどうすんだ?」と思った方もいらっしゃったかもしれませんが、そこを自動化してくれてるのが MKiCloudSync なので、上記だけでいいのです。



MKiCloudSync のしくみ

簡単にいうと(実際に中身も簡単なのですが)、

NSUserDefaults の変更を監視 → 変更があれば iCloud に反映

というしくみになっています。


ソースをのぞいてみると、start メソッドをコールすると2つの通知の監視を開始していることがわかります。

if([NSUbiquitousKeyValueStore defaultStore]) {  // is iCloud enabled
    
    [[NSNotificationCenter defaultCenter] addObserver:self 
                                             selector:@selector(updateFromiCloud:) 
                                                 name:NSUbiquitousKeyValueStoreDidChangeExternallyNotification 
                                               object:nil];
    
    [[NSNotificationCenter defaultCenter] addObserver:self 
                                             selector:@selector(updateToiCloud:) 
                                                 name:NSUserDefaultsDidChangeNotification
                                               object:nil];

NSUserDefaultsDidChangeNotification は、NSUserDefaults に変更があったときに発行される通知で、この通知が発行されたときに実行される updateToiCloud: メソッドで NSUserDefaults の内容を iCloud に同期するようになっています。


また NSUbiquitousKeyValueStoreDidChangeExternallyNotification は、iCloud のキーに外部からの変更があった場合に発行される通知で、この通知を受け取ると updateFromiCloud: メソッドで iCloud の内容を NSUserDefaults に同期するようになっています。(もちろんこの間のNSUserDefaultsDidChangeNotification の監視は止めてあります)



注意点

MKiCloudSync では NSUserDefaults のキーの削除には対応していません。NSUserDefaultsDidChangeNotification の通知を受け取ったときに実行されるハンドラメソッドである updateToiCloud では、

NSDictionary *dict = [[NSUserDefaults standardUserDefaults] dictionaryRepresentation];

[dict enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) {
    
    [[NSUbiquitousKeyValueStore defaultStore] setObject:obj forKey:key];
}];

このように NSUserDefaults に存在するキーを列挙して iCloud に書き込んでいるので、NSUserDefaults に存在しなくなったキーを iCloud に書き込んではくれないのです。


これで何が困るかというと、開発中のアプリを削除して最初から動作確認しようとしても、再インストール時に NSUbiquitousKeyValueStoreDidChangeExternallyNotification 通知が発行されて iCloud からの自動同期が行われてしまい、諸々の設定値が復活してしまうという点です。


この辺りは、キーの削除への対応であれば fork して使うなり、アプリ削除への対応であればデバッグモード用に iCloud の内容をリセットする機能を追加するなりする必要があります。


たとえば僕の場合、デバッグビルド版にはこんな感じで iCloud の内容をリセットする機能をつけています。

NSUbiquitousKeyValueStore *iCloudStore = [NSUbiquitousKeyValueStore defaultStore];
NSDictionary *dict = [iCloudStore dictionaryRepresentation];                    
[dict enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) {
    
    [iCloudStore removeObjectForKey:key];
}];


2012-03-17

ログ出力用マクロ

下記のような有名なログ出力マクロがありますが、

#ifdef DEBUG
#  define LOG_CURRENT_METHOD NSLog(@"%@/%@", NSStringFromClass([self class]), NSStringFromSelector(_cmd))
#else
#  define LOG_CURRENT_METHOD ;
#endif

これプラスアルファでちょっと便利なマクロをご紹介します。


下記マクロを定義して、

#ifdef DEBUG
#   define LOG_CURRENT_LINE(fmt, ...) NSLog((@"%s [Line %d] " fmt), __PRETTY_FUNCTION__, __LINE__, ##__VA_ARGS__);
#else
#   define LOG_CURRENT_LINE(...)
#endif

たとえばこのようにマクロを実行すると、

LOG_CURRENT_LINE(@"test");

こんな感じでクラス名、メソッド名、コード内の何行目か、が出力されます。

-[ViewController hoge:] [Line 542] test

__PRETTY_FUNCTION__ という定義済み変数はクラス名とメソッド名を、

__LINE__ という定義済み変数は行数を保持しているようです。



2012-03-16

クラッシュイベントを受け取る

TestFlight SDK とか、クラッシュイベントを受け取って Stack Trace をレポートしてくれたりしますが、どうやるんだろう?と思い調べてみました。


クラッシュイベントを受け取って何かしたい場合は、 NSSetUncaughtExceptionHandler() にハンドラ関数のポインタを渡すだけ。

NSSetUncaughtExceptionHandler(&exceptionHandler);

例外発生時に実行するハンドラ関数は、こんな感じで実装します。

(下記サンプルでは、スタックトレース結果をログ出力しています)

void exceptionHandler(NSException *exception)
{
	NSString *trace = [[exception callStackReturnAddresses] componentsJoinedByString:@"\n"];
	NSLog(@"Stack: (\n%@\n)", trace);
}

TestFlight が実際にこういう実装になっているかは、定かではありません。



2012-03-15

GKLeaderboard でどのようなスコアデータの絞り込みができるか

Game Center の Leaderboard のスコアデータは、GKLeaderboard クラスを使用して直接とってくることができます。


アプリケーションでスコアデータを調べたり、独自の Leaderboard ビューを作成したい場合は、GameCenterから直接スコアデータをロードできます。それには、GKLeaderboard クラスを使用します。GKLeaderboard オブジェクトは、Game Center に保存されているアプリケーションのデータへのクエリを表します。スコアデータをロードするには、アプリケーションは GKLeaderboard オブジェクトを作成して、特定のスコアセットをフィルタリングするようにそのプロパティを設定します。(Game Kitプログラミングガイドより)


で、どんなプロパティをセットすることでどのような絞り込みが可能なのか、リファレンスを見たりサンプル作ってみたりして調べてみました。


絞り込み条件用プロパティは次の4つ。

@property(nonatomic, assign)            GKLeaderboardTimeScope      timeScope;
@property(nonatomic, assign)            GKLeaderboardPlayerScope    playerScope;        // Filter on friends. Does not apply to leaderboard initialized with players.
@property(nonatomic, retain)            NSString                    *category;          // leaderboard category.  If nil, then it will fetch the aggregate leaderboard
@property(nonatomic, assign)            NSRange                     range;              // Leaderboards start at index 1 and the length should be less than 100. Does not apply to leaderboards initialized with players.  Exception will be thrown if developer tries to set an invalid range

また、loadScores:が成功すると値がセットされるプロパティ(つまり取得できる情報)は次の4つ。

@property(nonatomic, readonly, retain)  NSString                    *title;             // Localized category title. Defalts to nil until loaded.
@property(nonatomic, readonly, retain)  NSArray                     *scores;            // Scores are not valid until loadScores: has completed.
@property(nonatomic, readonly, assign)  NSUInteger                  maxRange;           // The maxRange which represents the size of the leaderboard is not valid until loadScores: has completed.
@property(nonatomic, readonly, retain)  GKScore                     *localPlayerScore;  // The local player's score

以下、4つの絞り込み条件用プロパティについてどのような指定ができるのか調べた結果です。


timeScope

timeScope プロパティは GKLeaderboardTimeScope 型となっています。

GKLeaderboardTimeScope の定義を見てみると、

enum {
    GKLeaderboardTimeScopeToday = 0,
    GKLeaderboardTimeScopeWeek,
    GKLeaderboardTimeScopeAllTime
};
typedef NSInteger GKLeaderboardTimeScope;

当日、週、全期間の指定ができるようです。


playerScope

playerScope プロパティは GKLeaderboardPlayerScope 型となっています。

GKLeaderboardPlayerScope の定義を見てみると

enum {
    GKLeaderboardPlayerScopeGlobal = 0,
    GKLeaderboardPlayerScopeFriendsOnly
};
typedef NSInteger GKLeaderboardPlayerScope;

となっており、このプロパティを指定することで全体/友達のスコープ指定ができるようです。



category

Leaderboardの「カテゴリー」って何だっけ?と思い iTunes Connect の設定画面を見返したりしてみましたが、見当たらず、ググってみたところ自分で書いた記事が出てきました。

http://d.hatena.ne.jp/shu223/20110131/1296408051

カテゴリってのを指定する必要があるのですが、iTunes Connectで指定した「リーダーボードのID」がそれにあたるようです。

GKScore からスコア送信する際に initWithCategory: メソッドのcategory引数と同じもの、すなわち単に Leaderboard ID でした。

複数のLeaderboardをカテゴライズするタグみたいな概念があればいいなぁと思っていたので、残念・・・



range

型は NSRange.

リファレンスによると

  • 取得する順位の範囲を指定できる。たとえば [1,10] と指定すると、トップ10のスコアを取得することになる
  • デフォルトは [1,25]
  • 指定できるindexの最小値は1、lengthの最大値は100

とのこと。



まとめ

Leaderboard で使用できる絞り込み条件

  • 当日、週、全期間
  • 全体、友達のみ
  • Leaderboard ID
  • 順位の範囲

そもそもこれを調べようと思った経緯として、複数ユーザーのスコアを合算してひとつのスコアにして、「チーム対抗ランキング」みたいなことをやりたかったからなのですが、残念ながらそれはできなそうです。。



2012-03-12

ARC 有効/無効に両対応させたい場合に便利なマクロ

汎用ライブラリ等、ARCプロジェクトでも非ARCプロジェクトでも使用する可能性のあるクラスを実装する場合、

#if !__has_feature(objc_arc)
- (void)dealloc {
    [super dealloc];
}
#endif

といった感じで "__has_feature(objc_arc)" を用いて判定を行いそれぞれの処理を書くわけですが、正直これは面倒です。


これを打開する便利なマクロを、こちらで発見しました。


下記マクロを定義しておけば、

#ifndef AH_RETAIN
#if __has_feature(objc_arc)
#define AH_RETAIN(x) x
#define AH_RELEASE(x)
#define AH_AUTORELEASE(x) x
#define AH_SUPER_DEALLOC
#else
#define __AH_WEAK
#define AH_WEAK assign
#define AH_RETAIN(x) [x retain]
#define AH_RELEASE(x) [x release]
#define AH_AUTORELEASE(x) [x autorelease]
#define AH_SUPER_DEALLOC [super dealloc]
#endif
#endif

こんな感じで一行かくだけで済むようになります。

AH_SUPER_DEALLOC;


2012-03-08

iOS 5.0 から iOS 5.1 への API 変更点まとめ

単なる API Diffs ドキュメントの抜粋ですが、iOS 5.1 での iOS SDK API 変更点です。


AssetsLibrary

ALAssetRepresentation.h

  • Added -[ALAssetRepresentation dimensions]

AudioToolbox

AudioFormat.h

  • Added kAudioFormatProperty_FormatIsEncrypted

AudioUnit

AUComponent.h

  • Added kAudioUnitSubType_RoundTripAAC (no architecture available)

AudioUnitParameters.h

  • Removed k3DMixerParam_PostAveragePower (no architecture available)
  • Removed k3DMixerParam_PostPeakHoldLevel (no architecture available)
  • Removed k3DMixerParam_PreAveragePower (no architecture available)
  • Removed k3DMixerParam_PrePeakHoldLevel (no architecture available)

AVFoundation

AVError.h

  • Added AVErrorInvalidOutputURLPathExtension

CoreAudio

CoreAudioTypes.h

  • Added kAudioFormatMPEG4AAC_ELD_V2

CoreFoundation

CFURL.h

  • Added kCFURLIsExcludedFromBackupKey

CoreVideo

CVPixelBuffer.h

  • Added kCVPixelFormatType_OneComponent8
  • Added kCVPixelFormatType_TwoComponent8

Foundation

NSURL.h

  • Added NSURLIsExcludedFromBackupKey

UIKit

UISplitViewController.h

  • Added UISplitViewController.presentsWithGesture

UITextInput.h

  • Added UIDictationPhrase
  • Added UIDictationPhrase.alternativeInterpretations
  • Added UIDictationPhrase.text
  • Added -[UITextInput dictationRecognitionFailed]
  • Added -[UITextInput dictationRecordingDidEnd]
  • Added -[UITextInput insertDictationResult:]


個人的に特に気になったのは新たに追加された UIDictationPhrase クラス。リファレンスをチラッと見てみた限りでは音声を文字列に変換できる、つまり音声認識っぽいことができるようです。

A dictation phrase object represents the textual interpretation of a spoken phrase as dictated by a user.



2012-03-07

Xcode の Guard Malloc オプション

Xcode 4 で、


Edit Scheme → Debug を選択 → "Diagnostics" タブを選択


と進むと、下記のような設定項目が出てきます。


f:id:shu223:20120309002112p:image:w500


このうち、"Enable Zombie Objects" にはいつもチェックを入れるのですが、他のチェック項目はどういう効果があるんだろう?と思いつつもずっと調べていませんでした。


で、この中の "Guard Malloc" について日本語で書かれた記事を発見。


MacOSプログラミング/Guard Malloc(libgmalloc) について


これによると、Guard Malloc とは、

Guard Mallocはmalloc, callocなどで確保したメモリに対して不正な操作を行ってしまう類のバグの検出を助けるデバッグ用のライブラリです。

とのことです。


で、どんなことをやってくれるかというと、

Guard Mallocを使ってアプリケーションを実行すると、そうしたメモリに対してのバグがある場合、アプリケーションがバグの位置でハングアップします。

Guard Mallocは、malloc, free, NSZoneMallocとその派生系(callocなど)をリプレースし、バグの検出に特化したやりかたでメモリを確保します。

とのこと。


なんだか良さそうですが、その先に書いてある使い方を見ると、メモリまわりのしっかりした知識がないと実際のデバッグで使いこなすのは難しそうです。


というわけで、その辺りの知識が深まったらまた手を出してみようと思います。



2012-03-05

Twitter や Facebook への投稿ライブラリ ShareKit の Tips

iOSアプリにソーシャルサイトでのシェア機能を3行で簡単実装できる」という謳い文句で有名な ShareKit を初めて使ってみたのですが、ひっかかった部分や、ドキュメントに書いてなくてソースから汲み取った部分などあったので、諸々メモしておきます。


ソース入手先に注意!

ググるとここがトップに出てきて、このページ内にリンクが張られているソースのダウンロード先はここということになっていますが、最終更新が1年以上前とえらく古いものなので要注意です。


現在の本家はこちらのようです。

https://github.com/ShareKit/ShareKit


ドキュメントはこちら。

https://github.com/ShareKit/ShareKit/wiki



iOS 5 から追加された Twitter フレームワークにも対応済み

作っているアプリのビルドターゲットが iOS 5.0 以上なので、Twitter は TWTweetComposeViewController を使い、Facebook は ShareKit を使うつもりでいたのですが、ShareKit ライブラリ内に SHKiOS5Twitter なるクラスを発見!ソースを見てみると内部で TWTweetComposeViewController を使用しているようです。


iOS5用の方を使う方法ですが、SHKTwitter クラスに次のような判定処理が入っており、

if ([self twitterFrameworkAvailable]) {
	
	[SHKiOS5Twitter shareItem:self.item];
	[SHKTwitter logout];//to clean credentials - we will not need them anymore
	return;
}

特に iOS5 用にコードを書かなくても Twitter フレームワークをプロジェクトに追加しておけば、自動的に Twitter フレームワークの方(SHKiOS5Twitter)を使ってくれます。



Twitter 画像投稿

ShareKit/Sharers/Services/ 配下に Facebook と Twitter だけを残し、下記のようなコードでアクションシートを呼び出したときに、

SHKItem *item = [SHKItem image:[UIImage imageNamed:@"hoge.png"] title:@"Title"];

SHKActionSheet *actionSheet = [SHKActionSheet actionSheetForItem:item];
[actionSheet showInView:self.view];

デフォルトだと「Facebook」「コピー」「その他」がアクションシートの選択肢として出てきます。


ここに、「Twitter」という選択肢を登場させたい場合は、DefaultSHKConfigurator.m の defaultFavoriteImageSharers を下記のようにカスタマイズします。

- (NSArray*)defaultFavoriteImageSharers {
//    return [NSArray arrayWithObjects:@"SHKMail",@"SHKFacebook", @"SHKCopy",@"SHKVkontakte", nil];
    return [NSArray arrayWithObjects:@"SHKFacebook", @"SHKTwitter", nil];
}


アクションシートの「その他」をなくす

英語では "more"、日本語では「その他」という選択肢が出てきますが、上と同様に DefaultSHKConfigurator.m の showActionSheetMoreButton をカスタマイズすることで消すことができます。

- (NSNumber*)showActionSheetMoreButton {
//	return [NSNumber numberWithBool:true];
    return [NSNumber numberWithBool:false];
}


Facebookでの画像投稿時に編集画面を出す

結論から言って、画像つき投稿用の編集画面は用意されてなさそうです。UIフォルダ配下のクラスの定義を見ても、UIImageView をメンバに持っているクラスは見当たらなかったので・・・


間違っていたらすいません。



投稿前に確認を挟む

上記の通り画像付き投稿時には編集画面を経由できないっぽいので、一旦確認用アラートを出すことにしました。


アラートを出したいクラスで SHKShareItemDelegate への準拠を宣言し、shareDelegate を設定します。

SHKActionSheet *actionSheet = [SHKActionSheet actionSheetForItem:item];
actionSheet.shareDelegate = self;

aboutToShareItem:withSharer: デリゲートメソッドを実装します。

これは SHKActionSheet でいずれかの投稿先が選択されると呼ばれるメソッドで、戻り値に NO を返すと、ShareKit による投稿を止めることができます。

下記サンプルコードでは Facebook が選択されたときにすぐに投稿せず一旦確認アラートを出すようにしています。

- (BOOL)aboutToShareItem:(SHKItem*)item withSharer:(SHKSharer*)sharer {

    if ([sharer isKindOfClass:[SHKFacebook class]]) {

        self.pendingSharer = sharer;

        // 確認用アラートを出す

        return NO;
    }

    return YES;
}


2009 | 08 |
2011 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2012 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2013 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2014 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2015 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2016 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 11 | 12 |
2017 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2018 | 02 |