月別アーカイブ: 2011年12月

【UINavigationController】ボタンを押すことで画面遷移する構造の基本構成

画面遷移する際の概念がやっと理解できたので、構成を残しておく。
サンプルは以下の通り。

処理順のイメージ

  1. AppDelegateがWindowを生成
  2. AppDelegateがTopViewControllerを生成してWindowに表示
  3. TopViewControllerがViewに載ったらイベント付きのボタンを2つ生成
  4. ボタンが押されたらViewController1を生成してWindowのViewに掲示。
  5. ViewController1がViewに載ったらイベント付きのボタンを1つ生成
  6. ボタンが押されたらTopViewControllerを生成してViewに乗せる。

気をつけなければ成らないのは以下の点

  • このサンプルではUINavigationControllerを利用しているので、遷移経路が全て保持されており、経路上の画面自体も保持され続ける。
  • よって、メモリオーバー必至。
  • メモリオーバー時用の対処はUIKit詳細リファレンスに載ってるのでそれを参照。

サンプル

AppDelegate.h

#import 

@interface AppDelegate:NSObject {
  UIWindow* window_;
  UIViewController* rootController_;
}

@property (strong, nonatomic) UIWindow *window;

@end

AppDelegate.m

#import "AppDelegate.h"
#import "TopViewController.h"

@implementation AppDelegate
@synthesize window = _window;

- (void)applicationDidFinishLaunching:(UIApplication *)application{
  // window を自分で作成
  CGRect bounds = [[UIScreenmainScreen]bounds];
  window_ = [[UIWindowalloc] initWithFrame:bounds];
  TopViewController* top = [[[TopViewController alloc]init]autorelease];
  rootController_ = [[UINavigationController alloc] initWithRootViewController:top];
  [window_addSubview:rootController_.view];
  [window_makeKeyAndVisible];
}

- (void)dealloc{
  [rootController_release];
  [window_release];
  [superdealloc];
}

@end

TopViewController.h

#import 
@interface TopViewController : UIViewController
@end

TopViewController.m

#import "TopViewController.h"

@implementation TopViewController

- (UIButton*)makeButton:(CGRect)rect text:(NSString*)text tag:(NSInteger)tag{
  UIButton* button = [UIButtonbuttonWithType:UIButtonTypeRoundedRect];
  [button setFrame:rect];
  [button setTag:tag];
  [button setTitle:text forState:UIControlStateNormal];
  [button addTarget:selfaction:@selector(clickButton:) forControlEvents:UIControlEventTouchUpInside];
  return button;
}

- (void)viewDidLoad{
  NSLog(@"top view did load");
  [superinit];
  UIButton* milkButton = [self makeButton:CGRectMake(10, 20, 300, 100) text:@"milk"tag:0];
  [self.viewaddSubview:milkButton];
  UIButton* soltButton = [self makeButton:CGRectMake(10, 120, 300, 100) text:@"solt"tag:1];
  [self.viewaddSubview:soltButton];
  NSLog(@"top view did load end");
}

- (IBAction)clickButton:(UIButton*)sender{
  NSLog(@"click Milk: tag is : %d",sender.tag);
  /*
   画面遷移の処理を行う。
   1:viewControllerを生成する。
   2:その画面に遷移。
   */
  //UIViewControllerを生成
  Class class = NSClassFromString(@"ViewController1");
  id viewController = [[[class alloc] init ]autorelease];
  [self.navigationController pushViewController:viewController animated:YES];
}

- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
    return (interfaceOrientation == UIInterfaceOrientationPortrait);
}
@end

ViewController1.h

#import 
@interface ViewController1 : UIViewController
@end

ViewController1.m

#import "ViewController1.h"

@implementation ViewController1

- (void)viewDidLoad{
  [super viewDidLoad];

  //Hello,world!ラベルを追加
  //背景は白、文字は黒で。
  UILabel* label = [[[UILabel alloc] initWithFrame:self.view.bounds] autorelease];
  label.text = @"Hello, world";
  label.textAlignment = UITextAlignmentCenter;
  label.backgroundColor = [UIColor whiteColor];
  label.textColor = [UIColor blackColor];
  label.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
  [self.view addSubview:label];

  //これをタップしたら画面遷移する
  UIButton* button = [UIButton buttonWithType:UIButtonTypeRoundedRect];
  [button setTitle:@"画面遷移" forState:UIControlStateNormal];
  [button sizeToFit];
  CGPoint newPoint = self.view.center;
  newPoint.y += 50;
  button.center = newPoint;
  button.autoresizingMask =
    UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleBottomMargin;
  [button addTarget:self
             action:@selector(buttonDidPush:)
   forControlEvents:UIControlEventTouchUpInside];
  [self.viewaddSubview:button];
}

-(id)init{
  if((self = [superinit])){
    self.title=@"Hello";
  }

  returnself;
}


- (void)buttonDidPush:(UIButton*)sender{
  NSLog(@"Pushed!!");
  id TopViewController = [[[TopViewController alloc] init ]autorelease];
  [self.navigationController pushViewController:TopViewController animated:YES];
}

@end

おまけ

ちなみにViewController1のbuttonDidPush:senderメソッドを以下の様に書き換えると、トップに戻る様になるので遷移経路を保持せずにメモリを節約できる。

- (void)buttonDidPush:(UIButton*)sender{
  NSLog(@"Pushed!! tag:%d",sender.tag);
  [self.navigationController popToRootViewControllerAnimated:YES];
//  id viewController = [[[TopViewController alloc] init ]autorelease];
//  [self.navigationController pushViewController:viewController animated:YES];
}

iOSプログラミング逆引きリファレンス108 ~知りたいことがすぐわかるiPhoneプログラミングテクニック~

iPhoneプログラミングUIKit詳解リファレンス

iOSデバッグ&最適化技法 for iPad/iPhone
詳解 Objective-C 2.0 第3版

Objective-cにおけるプロパティとはなんだ?

プロパティとインスタンス変数の違いが良くわからなかったので調べてみた。

インスタンス変数

インスタンス変数はそのクラスから生成されたインスタンスオブジェクトが生きている限りは保持されるインスタンス固有の変数。

プロパティはそのインスタンス変数にアクセス指定子を付けて宣言することで、インスタンス自身ではないオブジェクトからもアクセスできるようになる。

インスタンス変数にはアクセス指定子を付けてアクセスを制御できる。

  • @private
  • @protected
  • @public //デフォルト
  • 例:@private uint age;

->でインスタンス変数にアクセスできる。
ただしアクセス指定子をつけても警告のみでアクセスできてしまうし、ビルドも通る…。ェ…。

Objective-Cにおけるプロパティはどんな機能を持っているのか?

* アクセサメソッドが自動的に作られる

Objective-Cのプロパティを実装するにはどうしたらいいか?

.hファイル側で

  • @property(retain) NSString* propertyName;
#import

@interface PropertyCheck : NSObject

@property (retain) NSString* name;

@end

.mファイル側で

  • 通常はこう記述する。
    • @synthesize propertyName;
  • インスタンス変数名とプロパティ名が違う場合は以下の様にする。(※インスタンス変数が”_propertyName”の場合)
    • @synthesize propertyName = _propertyName;
#import "PropertyCheck.h"

@implementation PropertyCheck{
//  NSString* name;
}

- (id)init{
  self = [super init];
  self.name = @"default";
  return self;
}

@synthesize name;

@end

ただし!

  • @dynamic propertyName で宣言するとアクセサメソッドは生成されない。
  • @dynamic propertyName で宣言するとコンパイル時にチェックされない。エラー、ワーニング等がでない。

生成されたアクセサメソッドの構文はドット表記で行うが良い。

  PropertyCheck* propChck = [[[PropertyCheck alloc]init]autorelease];
  NSLog(@"prop.name = %@",propChck.name);
  propChck.name = @"iPhone3GS";
  NSString* name = propChck.name;
  NSLog(@"name      = %@",name);
  NSLog(@"prop.name = %@",propChck.name);
  propChck.name = @"iPhone4S";
  name = propChck.name;
  NSLog(@"name      = %@",name);

これでログ側には

 prop.name = default
 name      = iPhone3GS
 prop.name = iPhone3GS
 name      = iPhone4S

と表示されるはず。

ドット演算子の利点

通常の表記だとコンパイル時にチェックされないので実行するまでメソッド名が正しいかとか、存在するか等がわからない。
しかしドット演算子を使用すると使用可否がコンパイル時にチェックされる。

アクセサメソッドに任意の名前を付ける

アクセサメソッド名を自作するには以下の様に表記を変更する。
@property (getter=nowHealthPoint, setter=damagedHealthPoint:) NSInt enabled;
getterは引数無しだがsetterは引数ありなので必ずセミコロンを記述しておく。
同じ変数にアクセスするのでgetterの返り値の型とsetterの引数の型は一致する。この例ではNSIntにしてある。

プロパティの属性

  • readwrite
    • 読み書き可だよ。
  • readonly
    • 読み込みのみだよ。setterが生成されなくなる。
  • retain
    • 新規参照。参照数をインクリメント。
    • ガービッジコレクションを有効にしてるとretainは無効になる。
    • ガービッジコレクションを無効にしている場合、手動でreleaseする必要がある。
  • assign
    • まま渡される。
    • ガービッジコレクションが無効だと警告の上、これが適用される。
  • copy
    • set時にコピーが渡される。
    • ガービッジコレクションを無効にしている場合、手動でreleaseする必要がある。

プロパティとインスタンス変数

インスタンス変数として宣言するならプロパティとして定義した方が良いという結論に。
歴史ある言語って色々便利な分だけ分かりにくいね…。等など。

参考

iOSプログラミング逆引きリファレンス108 ~知りたいことがすぐわかるiPhoneプログラミングテクニック~

iPhoneプログラミングUIKit詳解リファレンス

iOSデバッグ&最適化技法 for iPad/iPhone
詳解 Objective-C 2.0 第3版

パブリッククラスとコンクリートクラスとクラスクラスタと

コンクリートクラスとかクラスクラスタとか聞き慣れない単語がでてきたので、漠然と理解するよりもちゃんと解説されてるものを読んで理解しておこうと思い調べたのでメモ。

コンクリートクラスって?

コンクリートクラスとは、”有名どころのクラス”のサブクラスで、”有名どころのクラス”を使用しておけば、格納するデータに応じて最適なクラスを自動的に割り当てるObjective-cの機能で呼び出されたクラス。

クラスクラスタって?

クラスクラスタとは、”有名どころのクラス”とそのサブクラス全体を含めたクラス群のこと。

具体的にどういうものかをソースを書いて理解

  NSString* str = [[NSString alloc]initWithString:@"/path/path/"];
  NSLog(@"str = %@",[str class]);

コンソールには…

2011-12-12 13:41:24.295 playground[13697:f803] str class = __NSCFConstantString

だとさ。

つまり、ユーザが宣言したクラスではなく、そのクラスを継承したサブクラスが自動で割り当てられてて、

ってあれ?パス用のクラスクラスタが割り当てられると思っていたのだけれども?

…。

そうそう、これをさっきのコードに追加してみると…

  NSString* path = [NSHomeDirectory() stringByAppendingPathComponent:@"Documents"];
  path = [path stringByAppendingPathComponent:[NSStringstringWithFormat:@"%d.txt",str]];
  NSLog(@"path class = %@",[path class]);

こんなレスポンス

2011-12-12 13:41:24.295 playground[13697:f803] str class = __NSCFConstantString
2011-12-12 13:41:24.296 playground[13697:f803] path class = NSPathStore2

となる。

両方ともNSStringで宣言してるのにClassが違う。
ここで実際に表示されたクラス群がコンクリートクラス。
親になってるスーパークラスはNSStringはパブリッククラスと呼ばれる。
なのでNSStringクラスで使用可能なクラスメソッド/インスタンスメソッドは継承してるコンクリートクラスでも使える。

stringByAppendingFormat:メソッドを実行するとNSPathStore2クラスに変換されたりするのかな?という思いつき。
※stringByAppendingFormat:メソッドがNSPathStore2クラスに付属するメソッドなのかどうかは調べてません。

最後にこれを追加してみる。

  str = [str stringByAppendingFormat:@"file.txt"];
  NSLog(@"str = %@",str);
  NSLog(@"str class = %@",[str class]);

コンソールには

2011-12-12 13:41:24.295 playground[13697:f803] str class = __NSCFConstantString
2011-12-12 13:41:24.296 playground[13697:f803] path class = NSPathStore2
2011-12-12 13:52:22.455 playground[13873:f803] str = /path/path/file.txt
2011-12-12 13:52:22.456 playground[13873:f803] str class = __NSCFString

最初にチェックしたstrのクラスは”__NSCFConstantString”だったのが”__NSCFString”になってる。
データをstringByAppendingFormat:メソッドによって生成したオブジェクトなので格納されてるデータが変わったのかな?と仮説を立ててみた。

色々試した結果、どうやら格納されるデータをどのように生成したかによって割り当てられるクラスが変動するみたい。

  NSString* str2 = [@"test" stringByAppendingPathComponent:@"/path/path/"];
  NSLog(@"str class = %@",[str2 class]);

このコードだと、最初に宣言されたstr2は NSStringのstringByAppendingPathComponent:メソッドによって生成されているので、クラスはPathStore2な!と言われて割り当てられた感じ。

2011-12-12 14:12:31.654 playground[14265:f803] str2 class = NSPathStore2

宣言はNSStringで行っているのでメモリサイズもそれに準じたフィールドが宛てられていると推測。なのにクラスが変わっててもいいの?とかとか疑問は湧く、が追い過ぎ注意。
間違っていたら改めるとして、生成時に副次的に生じる情報を元に判別している、とだけ解釈するまでにここでは留めておこう。

図にしてみる

話を戻してコンクリートクラスとパブリッククラスを図にするとこんな感じ。

クラスクラスタ

まとめ

プログラマが宣言するクラスがパブリッククラス。
実際に割り当てられるクラスがコンクリートクラス。
それらを全部合わせてクラスクラスタ。

また一つ、賢くなってしまった…。

参考

Format string is not a string literal

警告!な写真
NSLogの引数にNSStringのメッセージ文を直接書いていたらなぜかワーニング“Format string is not a string literal”が出て気持ち悪いので調べてみたらあっさり。

NSLog([NSString stringWithFormat:@"Milk"]);

これだとWarningがでます。

調べたら…

グーグル先生に訪ねたらこんな回答が。
http://stackoverflow.com/questions/5428325/issue-with-code-format-string-is-not-a-string-literal


NSLog(@"%@",[NSString stringWithFormat:@"%@", entered]);

教えてくれた人曰く、セキュリティを破る事に成りかねないYOとの事。

内容があまりにも簡単過ぎるからだとは思うのだけど、日本後でこういったエラーメッセージに対応してる記事をあまりみない。英語力が解決力に通じてると実感。