iOS/iPhone/iPad/watchOS/tvOS/MacOSX/Android プログラミング, Objective-C, Cocoa, Swiftなど
_ 節電対策アプリケーションの制作時に参考にした情報です。
■日別の停電や公共交通機関のハッシュタグ(3月15日の例) #315teiden: 停電情報 #315train: 電車の運行状況 #315bus: バスの運行状況 #315car: 道路交通状況 #315plane: 飛行機の運行状況 ■節電対策 #brownout 節電対策の情報 #setsuden 節電対策の情報 #jishin 地震 #coolbiz クールビズ ■地域別情報のハッシュタグ #save_miyagi 宮城県の情報 #save_fukushima 福島県の情報 #save_ibaraki 茨城県の情報 #save_iwate 岩手県の情報 #save_aomori 青森県の情報 #save_yamagata 山形県の情報 #save_niigata 新潟の情報 #save_nagano 長野の情報 ■地震関連ハッシュタグ #jishin: 地震一般に関する情報 #j_j_helpme :救助要請 #hinan :避難 #anpi :安否確認 #311care: 医療系被災者支援情報 #311sppt: 本当に支援が必要な現地の方々の生の声のみ 【東日本大震災】ネットで確認できる災害情報まとめ http://freesoft-100.com/topic/disaster.html 東日本大震災 Twitter情報整理サイト http://b.volunteer-platform.org/earthquake/
■twitter#setsuden#brownout#teiden #315teiden 月日teiden ※7月4日は? #704teiden でいいのか? ※日本の年月日形式でいいのか? http://search.twitter.com/search.json?q=%23setsuden http://search.twitter.com/search?q=%23315teiden&format=iphone ■Go節電ガジェット http://goo.gl/mESGj http://www.gosetsuden.jp/static/gadget/index.html?view=mobile
_ 私は、XcodeのPrefix header(Supporting Files/プロジェクト名-Prefix.pch)に、必ず、以下のデバッグ出力のマクロを記述しています。
#ifdef DEBUG
#define DBGMSG(...) NSLog(__VA_ARGS__)
#else /* DEBUG */
#define DBGMSG(...)
#endif /* DEBUG */
_ また、ファイルの末尾には、ファイルが壊れたら分かるように、以下の注釈を必ず入れています。
/* End Of File */
例えば、Twitterアプリケーションの投稿画面の様に、iPhoneではモーダル・ビューがよく利用される。写真撮影で使われるUIImagePickerControllerもモーデル・ビューとして実装されていて、独自のモーダル・ビューを実装する場合の参考になる。
モーダル・ビューの為のUIViewControllerのサブクラスを用意する。 その際、モーダル・ビューの呼び出し元の為のデリゲートを作成する。
@class ModalPaneViewController;
@protocol ModalPaneViewControllerDelegate
- (void)modalPaneViewControllerDidDone:(ModalPaneViewController *)modalPaneViewController;
- (void)modalPaneViewControllerDidCancel:(ModalPaneViewController *)modalPaneViewController;
@end
そして、モーダル・ビューのビュー・コントローラに呼び出し元のデリゲートをプロパティとして追加する。
@interface ModalPaneViewController : UIViewController
@property (nonatomic, weak) id delegate;
- (IBAction)done:(id)sender;
- (IBAction)cancel:(id)sender;
@end
モーダル・ビューで、デリゲートのメソッドを呼んであげると、呼び出し元に制御が戻る。
@implementation ModalPaneViewController
@synthesize delegate = _delegate;
- (IBAction)done:(id)sender
{
if ([self.delegate respondsToSelector:@selector(modalPaneViewControllerDidDone:)]) {
[self.delegate modalPaneViewControllerDidDone:self];
}
}
- (IBAction)cancel:(id)sender
{
if ([self.delegate respondsToSelector:@selector(modalPaneViewControllerDidCancel:)]) {
[self.delegate modalPaneViewControllerDidCancel:self];
}
}
@end
呼び出し元のモーダル・ビューを開くコードは以下のとおり。
- (IBAction)modalPane:(id)sender
{
ModalPaneViewController *viewController = [[ModalPaneViewController alloc]
initWithNibName:@"ModalPaneViewController"
bundle:nil];
viewController.delegate = self;
[self presentModalViewController:viewController animated:YES];
}
呼び出し元でデリゲートのメソッドを実装。その際に、モーダル・ビューを閉じるコードを呼ぶ。
- (void)modalPaneViewControllerDidDone:(ModalPaneViewController *)modalPaneViewController
{
[self dismissModalViewControllerAnimated:YES];
}
- (void)modalPaneViewControllerDidCancel:(ModalPaneViewController *)modalPaneViewController
{
[self dismissModalViewControllerAnimated:YES];
}
呼び出し元のボタンを押下すると、モーダル・ビューが表示され。
モーダル・ビューのボタンを押下すると、モーダル・ビューは閉じる。
昨日、説明したモーダル・ビュー。iOS4から利用できる様になったBlocksを使えば、もっと、簡単に記述できる、はず。多分。
モーダル・ビューで、Blocksと扱うイベントを定義する。
typedef enum ModalPaneViewControllerResult {
ModalPaneViewControllerResultCancelled,
ModalPaneViewControllerResultDone
} ModalPaneViewControllerResult;
typedef void (^ModalPaneViewControllerCompletionHandler)(ModalPaneViewControllerResult result);
モーダル・ビューで、delegateプロパティの代わって、Blocksをプロパティとして追加する。
@interface ModalPaneViewController : UIViewController
@property (nonatomic, copy) ModalPaneViewControllerCompletionHandler completionHandler;
- (IBAction)done:(id)sender;
- (IBAction)cancel:(id)sender;
@end
モーダル・ビューで、デリゲートのメソッドを呼んであげる代わりに、Blocksを呼ぶ。
@implementation ModalPaneViewController
@synthesize completionHandler = _completionHandler;
- (IBAction)done:(id)sender
{
if (self.completionHandler) {
self.completionHandler(ModalPaneViewControllerResultDone);
}
}
- (IBAction)cancel:(id)sender
{
if (self.completionHandler) {
self.completionHandler(ModalPaneViewControllerResultCancelled);
}
}
@end
呼び出し元のモーダル・ビューを開くコードは以下のとなる。
- (IBAction)modalPane:(id)sender
{
ModalPaneViewController *viewController = [[ModalPaneViewController alloc]
initWithNibName:@"ModalPaneViewController"
bundle:nil];
[viewController setCompletionHandler:^(ModalPaneViewControllerResult result) {
switch (result) {
case ModalPaneViewControllerResultCancelled:
[self performSelectorOnMainThread:@selector(didCencel:) withObject:nil waitUntilDone:NO];
break;
case ModalPaneViewControllerResultDone:
[self performSelectorOnMainThread:@selector(didDone:) withObject:nil waitUntilDone:NO];
break;
default:
break;
}
[self dismissModalViewControllerAnimated:YES];
}];
[self presentModalViewController:viewController animated:YES];
}
- (void)didDone:(id)arg { }
- (void)didCencel:(id)arg { }
確かにスッキリした気がするが、楽になったかどうかは。。。
時間を計測する関数には、色々な種類がある。どのような精度の値が取得できるかも重要だが、その関数を呼び出すコストも重要になる。
以前、Cocoa勉強会の発表の為に調べた、各種の時間取得関数は以下のとおり。現在では、使用できない物もあるが、そのまま、掲載している。
種類 | モジュール | 関数 |
---|---|---|
ANSI C | 標準ライブラリ | clock |
time | ||
Mach | Clock Interface | clock_get_time |
Mach Absolute Time Units | mach_absolute_time | |
カーネル開発 | カーネル・ライブラリ | clock_get_uptime |
clock_get_system_value | ||
clock_get_calendar_value | ||
clock_get_calendar_offset | ||
BSD | POSIX | gettimeofday |
Core Foundation | CarbonLib | CFAbsoluteTimeGetCurrent |
Carbon | デバイス・ドライバー | UpTime |
OS Utilities | ReadDateTime | |
Date, Time, and Measurement Utilities | GetDateTime | |
Cocoa | NSDate | +timeIntervalSince1970: |
+timeIntervalSinceReferenceDate: |
Lionで呼び出す事が可能な関数について、複数回のループ内で呼び出して、ループ全体にかかる時間を測定したのが、以下のコード。
#import <Foundation/Foundation.h>
#import <stdio.h>
#import <sys/time.h>
#import <mach/mach.h>
/* #import <CoreServices/CoreServices.h> */
#define TIMERSUB(a, b, result) \
do { \
(result)->tv_sec = (a)->tv_sec - (b)->tv_sec; \
(result)->tv_usec = (a)->tv_usec - (b)->tv_usec; \
if ((result)->tv_usec < 0) { \
--(result)->tv_sec; \
(result)->tv_usec += 1000000; \
} \
} while (0)
int main (int argc, const char * argv[])
{
int i, j;
clock_serv_t clock_serv;
mach_timespec_t cur_time;
struct timeval tv_start, tv_end, tv_diff;
/* AbsoluteTime at_start; */
/* float deltaTime; */
/* uint64_t result; */
/* unsigned long secs; */
@autoreleasepool {
/* ANSI C: clock_t clock(void) */
gettimeofday(&tv_start, NULL);
for (i = 0; i < 10000; i++) {
for (j = 0; j < 100; j++) {
clock();
}
}
gettimeofday(&tv_end, NULL);
TIMERSUB(&tv_end, &tv_start, &tv_diff);
printf("ANSI C: clock: %ld.%06d[sec]\n", tv_diff.tv_sec, tv_diff.tv_usec);
/* ANSI C: time_t time(time_t *pSec) */
gettimeofday(&tv_start, NULL);
for (i = 0; i < 10000; i++) {
for (j = 0; j < 100; j++) {
time(NULL);
}
}
gettimeofday(&tv_end, NULL);
TIMERSUB(&tv_end, &tv_start, &tv_diff);
printf("ANSI C: time: %ld.%06d[sec]\n", tv_diff.tv_sec, tv_diff.tv_usec);
/* Mach: kern_return_t clock_get_time(clock_serv_t clock_serv, mach_timespec_t *cur_time) */
gettimeofday(&tv_start, NULL);
for (i = 0; i < 10000; i++) {
for (j = 0; j < 100; j++) {
host_get_clock_service(mach_host_self(), REALTIME_CLOCK, &clock_serv);
clock_get_time(clock_serv, &cur_time);
}
}
gettimeofday(&tv_end, NULL);
TIMERSUB(&tv_end, &tv_start, &tv_diff);
printf("Mach: clock_get_time: %ld.%06d[sec]\n", tv_diff.tv_sec, tv_diff.tv_usec);
/* Mach: uint64_t mach_absolute_time(void) */
gettimeofday(&tv_start, NULL);
for (i = 0; i < 10000; i++) {
for (j = 0; j < 100; j++) {
mach_absolute_time();
}
}
gettimeofday(&tv_end, NULL);
TIMERSUB(&tv_end, &tv_start, &tv_diff);
printf("Mach: mach_absolute_time: %ld.%06d[sec]\n", tv_diff.tv_sec, tv_diff.tv_usec);
#if 0
/* Kernel: void clock_get_uptime(uint64_t *result) */
gettimeofday(&tv_start, NULL);
for (i = 0; i < 10000; i++) {
for (j = 0; j < 100; j++) {
clock_get_uptime(&result);
}
}
gettimeofday(&tv_end, NULL);
TIMERSUB(&tv_end, &tv_start, &tv_diff);
printf("Mach: clock_get_uptime: %ld.%06d[sec]\n", tv_diff.tv_sec, tv_diff.tv_usec);
/* Kernel: mach_timespec_t clock_get_system_value(void) */
gettimeofday(&tv_start, NULL);
for (i = 0; i < 10000; i++) {
for (j = 0; j < 100; j++) {
clock_get_system_value();
}
}
gettimeofday(&tv_end, NULL);
TIMERSUB(&tv_end, &tv_start, &tv_diff);
printf("Mach: clock_get_system_value: %ld.%06d[sec]\n", tv_diff.tv_sec, tv_diff.tv_usec);
/* Kernel: mach_timespec_t clock_get_calendar_value(void) */
gettimeofday(&tv_start, NULL);
for (i = 0; i < 10000; i++) {
for (j = 0; j < 100; j++) {
clock_get_calendar_value();
}
}
gettimeofday(&tv_end, NULL);
TIMERSUB(&tv_end, &tv_start, &tv_diff);
printf("Mach: clock_get_calendar_value: %ld.%06d[sec]\n", tv_diff.tv_sec, tv_diff.tv_usec);
/* Kernel: mach_timespec_t clock_get_calendar_offset(void) */
gettimeofday(&tv_start, NULL);
for (i = 0; i < 10000; i++) {
for (j = 0; j < 100; j++) {
clock_get_calendar_offset();
}
}
gettimeofday(&tv_end, NULL);
TIMERSUB(&tv_end, &tv_start, &tv_diff);
printf("Mach: clock_get_calendar_offset: %ld.%06d[sec]\n", tv_diff.tv_sec, tv_diff.tv_usec);
#endif /* 0 */
/* POSIX: int gettimeofday(struct timeval *tv, struct timezone *tz) */
gettimeofday(&tv_start, NULL);
for (i = 0; i < 10000; i++) {
for (j = 0; j < 100; j++) {
gettimeofday(&tv_end, NULL);
}
}
gettimeofday(&tv_end, NULL);
TIMERSUB(&tv_end, &tv_start, &tv_diff);
printf("POSIX: gettimeofday: %ld.%06d[sec]\n", tv_diff.tv_sec, tv_diff.tv_usec);
/* Core Foundation: CFAbsoluteTimeGetCurrent */
gettimeofday(&tv_start, NULL);
for (i = 0; i < 10000; i++) {
for (j = 0; j < 100; j++) {
CFAbsoluteTimeGetCurrent();
}
}
gettimeofday(&tv_end, NULL);
TIMERSUB(&tv_end, &tv_start, &tv_diff);
printf("Core Foundation: CFAbsoluteTimeGetCurrent: %ld.%06d[sec]\n", tv_diff.tv_sec, tv_diff.tv_usec);
#if 0
/* Carbon: UpTime */
gettimeofday(&tv_start, NULL);
for (i = 0; i < 10000; i++) {
for (j = 0; j < 100; j++) {
UpTime();
}
}
gettimeofday(&tv_end, NULL);
TIMERSUB(&tv_end, &tv_start, &tv_diff);
printf("Carbon: UpTime: %ld.%06d[sec]\n", tv_diff.tv_sec, tv_diff.tv_usec);
#endif /* 0 */
#if 0
/* Carbon: ReadDateTime */
gettimeofday(&tv_start, NULL);
for (i = 0; i < 10000; i++) {
for (j = 0; j < 100; j++) {
ReadDateTime(&secs);
}
}
gettimeofday(&tv_end, NULL);
TIMERSUB(&tv_end, &tv_start, &tv_diff);
printf("Carbon: ReadDateTime: %ld.%06d[sec]\n", tv_diff.tv_sec, tv_diff.tv_usec);
#endif /* 0 */
#if 0
/* Carbon: GetDateTime */
gettimeofday(&tv_start, NULL);
for (i = 0; i < 10000; i++) {
for (j = 0; j < 100; j++) {
GetDateTime(&secs);
}
}
gettimeofday(&tv_end, NULL);
TIMERSUB(&tv_end, &tv_start, &tv_diff);
printf("Carbon: GetDateTime: %ld.%06d[sec]\n", tv_diff.tv_sec, tv_diff.tv_usec);
#endif /* 0 */
/* Cocoa: NSDate::timeIntervalSince1970 */
gettimeofday(&tv_start, NULL);
for (i = 0; i < 10000; i++) {
for (j = 0; j < 100; j++) {
[[NSDate date] timeIntervalSince1970];
}
}
gettimeofday(&tv_end, NULL);
TIMERSUB(&tv_end, &tv_start, &tv_diff);
printf("Cocoa: NSDate::timeIntervalSince1970: %ld.%06d[sec]\n", tv_diff.tv_sec, tv_diff.tv_usec);
/* Cocoa: NSDate::timeIntervalSinceReferenceDate */
gettimeofday(&tv_start, NULL);
for (i = 0; i < 10000; i++) {
for (j = 0; j < 100; j++) {
[[NSDate date] timeIntervalSinceReferenceDate];
}
}
gettimeofday(&tv_end, NULL);
TIMERSUB(&tv_end, &tv_start, &tv_diff);
printf("Cocoa: NSDate::timeIntervalSinceReferenceDate: %ld.%06d[sec]\n", tv_diff.tv_sec, tv_diff.tv_usec);
#if 0
/* Unsupported: hrtime_t gethrtime(void) */
gettimeofday(&tv_start, NULL);
for (i = 0; i < 10000; i++) {
for (j = 0; j < 100; j++) {
gethrtime();
}
}
gettimeofday(&tv_end, NULL);
TIMERSUB(&tv_end, &tv_start, &tv_diff);
printf("Unsupported: gethrtime: %ld.%06ld[sec]\n", tv_diff.tv_sec, tv_diff.tv_usec);
#endif /* 0 */
}
return 0;
}
mach_absolute_timeの呼び出しコストが最も軽いみたい。
iOS5から、Twitter Frameworkが追加され、自分のアプリケーションにTwitter機能を追加するのが楽になったが、それ以前は、独自にTwitter機能を実装するか、オープンソースのフレームワークを利用していた。
この情報は、iOS5以降は不要なのかもしれないが、理由があって、古いOSに対応しなければならばい際に役立つと考えている。
少し、ややこしい話だが、iOSで著名なTwitterフレームワークにMGTwitterEngineというのがあったのだが、Twitterの認証方法がBASIC認証からOAuth/xAuthに移行した際、このフレームワークをベースに作成されたxAuthに対応したXAuthTwitterEngineというのが使われだした。ただ、その後、MGTwitterEngineがxAuthに対応したので、利用上問題がなければこちらを利用すればいいと考えている。
MGTwitterEngineは、以下のサイトから入手できる。
Matt Gemmell
ソースコードは、GitHubで公開されている。
https://github.com/mattgemmell/MGTwitterEngine
ただし、MGTwitterEngineは、上記のソースコードだけで完結していなくて、使い方によって、他のオープンソースのライブラリが必要になる。例えば、TouchJSONやOAuthConsumer、yajl等。
自分のアプリケーションでTwitter機能を利用する為には、使用する機能によっては、Twitter社への申請が必要になる。
申請のサイトは、以下のURLだ。
http://dev.twitter.com/apps/new
ただし、最後の『xAuth認証の使用許可申請』については、新規アプリ申請後に、電子メールで依頼する事になる。
以下が、私が出したメールのサンプルだ。参考にして欲しい。
To: api@twitter.com Subject: Please apply this app to use xAuth Hello. My name is 名前. I am a developer of "アプリケーション名" "アプリケーション名" is a iPhone application. <アプリケーションの説明> ※英語なので辛い! Please apply this app to use xAuth. Application Name: 申請したアプリケーション名 My account: @アカウント名 ※Twitterの Consumer Key: xxxxxxxx Thank you.
xAuthの申請について、Googleで検索すると、追加の手続きが必要という情報もあるようだが、私の場合は、既にリリース済みのアプリケーションだった為か、すんなりといきました。
Xcodeで、ターゲットのSummaryのiPhone/iPod Deployment InfoのSupported Device Orienttationsで、対応する向きを選択する。(下記の図の赤い丸で囲まれたボタン)古い資料では、Info.plistのUIInterfaceOrientationキーのカスタマイズが説明されているが、この操作によってInfo.plistが更新される。
四つの項目(Portrait, Upside Down, Landscape Left, Landscape Right)の選択順には意味があって、最初に選択された物が、起動時の画面の向きという事になるようだ。
ユーザーがiPhoneの向きを変更するのに追従して、自動で画面の向きを変更したい場合は、ビュー・コントローラのshouldAutorotateToInterfaceOrientation:メソッドをカスタマイズする。
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
BOOL result = YES;
if (interfaceOrientation == UIInterfaceOrientationPortrait)
result = YES;
else if (interfaceOrientation == UIInterfaceOrientationLandscapeLeft)
result = YES;
else if (interfaceOrientation == UIInterfaceOrientationLandscapeRight)
result = YES;
else if (interfaceOrientation == UIInterfaceOrientationPortraitUpsideDown)
result = NO;
return result;
}
例えば、上記の例は、上下が逆さま(UIInterfaceOrientationPortraitUpsideDown)以外が自動で向きが変わるというコードだ。
_ eien [Appleは、 UIInterfaceOrientationIsLandscape UIInterfaceOri..]
MVCのモデルは、実際の値を持っているデータ構造と、それを抽象的に見せるインタフェースから成り立っていると考えている。ビジネス・ロジックは、このインタフェース部分で実装されると考えていて、このインタフェース部分を私はDocumentというクラスで実装している。
HMDT木下誠さんの著書『iOS開発におけるパターンによるオートマティズム』で、モデルレイヤ設計パターンとして、モデルオブジェクトクラスとモデルマネージャクラスが解説されているが、ほぼ、同様なものと考えられる。
モデルの実装方法が決まったら、それをどうアクセスするかが問題となる。オッティモの小池邦人さんが、MOSADeNで説明されていたサンプルでは、アプリケーションのデリゲートクラスのインスタンス変数として持つ方法。
@interface AppDelegate : NSObject <UIApplicationDelegate> {
UIWindow *window;
MyViewController *viewController;
MyDocument *document;
}
@property (nonatomic, retain) IBOutlet UIWindow *window;
@property (nonatomic, retain) IBOutlet MyViewController *viewController;
@property (nonatomic, retain) MyDocument *document;
@end
こうすれば、アプリケーションのインスタンスを取得すれば、documentインスタンス変数にアクセスできる。
AppDelegate *appl = nil;
appl = (AppDelegate *)[[UIApplication sharedApplication] delegate];
先ほどの、木下さんの著書では、シングルトンのパターンで実装されていた。
static MyDataManager *_sharedInstance = nil;
+ (MyDataManager *)sharedManager
{
if (!_sharedInstance) {
_sharedInstance = [[MyDataManager alloc] init];
}
return _sharedInstance;
}
好みの問題だと思われるが、私はデリゲートのインスタンス変数とする実装が好みだ。
Objective-Cで、非公開メソッドが必要になった、無名カテゴリで実現できる。
@interface MyClass : NSObject {
}
- (void)publicMethod;
@end
@interface MyClass ()
- (void)privateMethod;
@end
カテゴリには、メソッドしか宣言できない為、無名カテゴリで非公開に出来るのはメソッドのみと考えていたが、Clang/LLVM 2.0コンパイラを使うとプロパティでもOKのようだ。
@interface MyClass : NSObject
- (void)publicMethod;
@end
@interface MyClass ()
@property (nonatomic, strong) NSMutableString *string;
- (void)privateMethod;
@end
@implementation MyClass
@synthesize string = _string;
@end
通常のクラス宣言部にも同じプロパティの宣言をして、公開用と非公開用で宣言の内容を変える事も可能なようだ。
@interface MyClass : NSObject
@property (nonatomic, strong, readonly) NSMutableString *string;
- (void)publicMethod;
@end
@interface MyClass ()
@property (nonatomic, strong, readwrite) NSMutableString *string;
- (void)privateMethod;
@end
@implementation MyClass
@synthesize string = _string;
@end
無名カテゴリは、AppleのDeveloper Libraryの説明によると、匿名のカテゴリに似ているが、正確にはクラス拡張と呼ぶ、ちょっと、異なる物のようだ。
iPhone用グラフ描画ライブラリで、軽量なものとして、S7GraphViewというものがある。自分のアプリケーションでも利用しているのがこれだ。
個人的には気に入っているのだが、最近は更新が滞り、先日、改良コードを送付したのだが、返信がない。そして、久しぶりにサイトをアクセスしたところ、無くなったようだ。
その為、困ってしまったので、何か継承できたらと考えている。
ただし、パクリにならないよう、継承するのなら、一から設計し直すつもりだ。
アプリケーションの初期設定の情報は、NSUserDefaultsクラスを使えば実現できる。ただ、単純にNSUserDefaultsを使って値を読み書きするだけだと、扱う情報が増えてくると複雑になってくるので、先日紹介したDocumentクラスを使った実装を紹介する。
自分のアプリケーション用のDocumentクラスを用意する。
@interface Document : NSObject
@property (strong, nonatomic) NSString *message;
- (void)clearDefaults;
- (void)updateDefaults;
- (void)loadDefaults;
@end
初期設定の項目をプロパティとして持ち、読み書き処理をメソッドして用意する。以下が実装例だ。
@implementation Document
@synthesize message = _message;
- (void)clearDefaults
{
if ([[NSUserDefaults standardUserDefaults] objectForKey:@"message"]) {
DBGMSG(@"remove message:%@", self.message);
[[NSUserDefaults standardUserDefaults] removeObjectForKey:@"message"];
}
}
- (void)updateDefaults
{
NSString *aMessage = nil;
if ([[NSUserDefaults standardUserDefaults] objectForKey:@"message"]) {
aMessage = [[NSUserDefaults standardUserDefaults] objectForKey:@"message"];
}
if (self.message) {
if ((aMessage) && ([aMessage compare:self.message] == NSOrderedSame)) {
}
else {
[[NSUserDefaults standardUserDefaults] setObject:self.message forKey:@"message"];
[[NSUserDefaults standardUserDefaults] synchronize];
}
}
}
- (void)loadDefaults
{
if ([[NSUserDefaults standardUserDefaults] objectForKey:@"message"]) {
self.message = [[NSUserDefaults standardUserDefaults] objectForKey:@"message"];
}
}
@end
iOSでは、ユーザに保存という処理を意識させないのが優れたUIの条件のようだ。アプリケーションのデリゲート・クラスでアプリケーションが非アクティブになるタイミングで初期設定の値を保存する。
@implementation AppDelegate
@synthesize window = _window;
@synthesize viewController = _viewController;
@synthesize document = _document;
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
self.document = [[Document alloc] init];
[self.document loadDefaults];
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
// Override point for customization after application launch.
self.viewController = [[ViewController alloc] initWithNibName:@"ViewController" bundle:nil];
self.window.rootViewController = self.viewController;
[self.window makeKeyAndVisible];
return YES;
}
- (void)applicationDidEnterBackground:(UIApplication *)application
{
[self.document updateDefaults];
}
- (void)applicationWillTerminate:(UIApplication *)application
{
[self.document updateDefaults];
}
@end
アプリケーションに初期設定情報を持たせる場合、バージョン管理が重要になる。できれば、最初の版からバージョン情報を持たせる事を薦める。
バージョン情報は、新旧の比較を考えると数値型の方が便利だと思うが、バージョンが異なると以前の初期設定を初期化していいのなら、アプリケーションのバージョン番号と同じ内容を文字列として持たせても問題ないと考えている。
@interface Document : NSObject
@property (strong, nonatomic) NSString *version;
@property (strong, nonatomic) NSString *message;
- (void)clearDefaults;
- (void)updateDefaults;
- (void)loadDefaults;
@end
Documentクラスに、バージョン情報が一致しない場合は初期化するコードを追加する。
@implementation Document
@synthesize version = _version;
@synthesize message = _message;
- (id)init
{
if ((self = [super init]) != nil) {
NSDictionary *infoDictionary = [[NSBundle mainBundle] infoDictionary];
self.version = [infoDictionary objectForKey:@"CFBundleShortVersionString"];
NSString *aVersion = @"1.0";
if ([[NSUserDefaults standardUserDefaults] objectForKey:@"version"]) {
aVersion = [[NSUserDefaults standardUserDefaults] objectForKey:@"version"];
}
if ([aVersion compare:self.version] != NSOrderedSame) {
[self clearDefaults];
}
}
return self;
}
- (void)dealloc
{
self.version = nil;
self.message = nil;
}
- (void)clearDefaults
{
if ([[NSUserDefaults standardUserDefaults] objectForKey:@"message"]) {
[[NSUserDefaults standardUserDefaults] removeObjectForKey:@"message"];
}
}
- (void)updateDefaults
{
BOOL fUpdate = NO;
NSString *aVersion = @"";
if ([[NSUserDefaults standardUserDefaults] objectForKey:@"version"]) {
aVersion = [[NSUserDefaults standardUserDefaults] objectForKey:@"version"];
}
if (self.version) {
if ([aVersion compare:self.version] != NSOrderedSame) {
[[NSUserDefaults standardUserDefaults] setObject:self.version forKey:@"version"];
fUpdate = YES;
}
}
NSString *aMessage = @"";
if ([[NSUserDefaults standardUserDefaults] objectForKey:@"message"]) {
aMessage = [[NSUserDefaults standardUserDefaults] objectForKey:@"message"];
}
if (self.message) {
if ([aMessage compare:self.message] != NSOrderedSame) {
[[NSUserDefaults standardUserDefaults] setObject:self.message forKey:@"message"];
fUpdate = YES;
}
}
if (fUpdate) {
[[NSUserDefaults standardUserDefaults] synchronize];
}
}
- (void)loadDefaults
{
NSString *aVersion = @"";
if ([[NSUserDefaults standardUserDefaults] objectForKey:@"version"]) {
aVersion = [[NSUserDefaults standardUserDefaults] objectForKey:@"version"];
}
if ([aVersion compare:self.version] != NSOrderedSame) {
[self clearDefaults];
}
else {
if ([[NSUserDefaults standardUserDefaults] objectForKey:@"message"]) {
self.message = [[NSUserDefaults standardUserDefaults] objectForKey:@"message"];
}
}
}
@end
_ eien [delegateメソッドを呼ぶ前に、delegateがそれをサポートしているかどうか確認したほうが安全なコードになり..]
_ m_yukio [確かに、そうですね! 参考にさせていただきます。早速、修正しました。]