Powered By Blogger
ラベル iOS の投稿を表示しています。 すべての投稿を表示
ラベル iOS の投稿を表示しています。 すべての投稿を表示

2012年3月24日土曜日

Objective-C++でboostを使う時のリンクエラー

XCodeでObjective-C++でboostを使う時に以下のリンクエラーが出た場合。

Undefined symbols for architecture x86_64:
"boost::system::generic_category()"

boostの.dylibが足りないらしい。
libboost_system-mt.dylib をTARGETSのBuild PhaseのLink Binary With Librariesに追加する。

2012年3月23日金曜日

Objective-cでコルーチン?!

「C10K問題を回避するためにコルーチンを使う方法」というブログ記事があって、なるほどと思いObj-cに移植しようとしたのですが、どうもコルーチンクラスをサンプルに特化させていじっている様で汎用性が無さそうであまり使い勝手よくありません。

仕方ないので元記事のソースはあまり参考にせずに移植と言うより作ってみました。
→ここgithub

C10K問題を回避するためには、コネクション数とスレッド数が比例しないような設計をしなければならない事からコルーチンが有効らしいです。
実際に通信するコードを書ければ素晴らしいですが、面倒くさいので省いてコルーチンの動作を確認するだけです。

まぁObj-cのコルーチンはCSCoroutineとかあるみたいですが、setjmp, longjmp使ってるのが気持ち悪かったりします。(食わず嫌い)

yieldではsetjmp, longjmp使わず普通にreturnで抜けているのでローカル変数等は保存されません。
なのでローカル変数の代わりにメンバ関数を使っています。
そういう作りなのでコルーチンからサブコルーチンを呼ぶ時には新たなコルーチンオブジェクトを作成します。
色々と制約あって万能ではありませんが、ちょこっとコルーチンを使いたいぐらいなら丁度よいかも。

使用例
二組のコルーチンを使って擬似マルチスレッドをシングルスレッドで実現しています。
performメソッドがコルーチンの中身ですが、COR_YIELDでmainへ値を戻し、mainで次にnextメソッド呼ぶとCOR_YIELDの直後から続き実行します。

//
#import "TestCoroutine.h"
#import "CoroutineX.h"


@interface TestCoroutine (/*PRIVATE*/)
- (NSString*)makeChildName;
@end

@implementation TestCoroutine {
  int _concurrency_level;
  NSString *_name;

  int _cl, _i, _j;
  NSString *_retStr;
}

- (id)initWithConcurrencyLevel:(int)lv name:(NSString*)name {
  self = [super init];
  if (self) {
    _concurrency_level = lv;
    _name = name;
  }
  return self;
}


- (NSString*)makeChildName {
  static int c =0;
  return [NSString stringWithFormat:@"%@c%d", _name, c++];
}

- (id)perform {
  /*
   You can NOT use any local variables in this method.
   */
  static int forkDepth = 2;
  COR_BEGIN;

  for (_cl = 0; _cl < _concurrency_level; ++_cl) {
    //recursive call
    COR_FORK([[TestCoroutine alloc] initWithConcurrencyLevel:forkDepth-- name:[self makeChildName]]);        
  }
  
  for (_i=0; _i < 3; ++_i) {
    _retStr = [NSString stringWithFormat:@"%@'s loop1 i=%d",self->_name, _i];
    COR_YIELD(_retStr);
  }
  
  for (_i=0; _i < 2; ++_i) {
    for (_j=0; _j < 3; ++_j) {
      _retStr = [NSString stringWithFormat:@"%@'s loop2 i=%d, j=%d",self->_name, _i, _j];
      COR_YIELD(_retStr);
    }
  }

  //sub coroutine call
  COR_FORK([[SubCoroutine alloc] init]);

  COR_END;
}

@end


@interface SubCoroutine (/*PRIVATE*/)
@end

@implementation SubCoroutine

- (id)perform {
  /*
   You can NOT use any local variables in this method.
   */
  COR_BEGIN;

  COR_YIELD([NSNumber numberWithFloat:1.2]);
  COR_YIELD([NSNumber numberWithFloat:2.3]);

  COR_END;
}

@end


//
//  main.m
//

#import 
#import "TestCoroutine.h"


int main(int argc, const char * argv[])
{

  @autoreleasepool {

    CoroutineState *corState1 = [[CoroutineState alloc] init];
    TestCoroutine *coro1 = [[TestCoroutine alloc] initWithConcurrencyLevel:3 name:@"p0"];
    [corState1 push:coro1];

    CoroutineState *corState2 = [[CoroutineState alloc] init];
    TestCoroutine *coro2 = [[TestCoroutine alloc] initWithConcurrencyLevel:2 name:@"p1"];
    [corState2 push:coro2];

    id retVal1, retVal2;
    do {
      retVal1 = [corState1 next];
      retVal2 = [corState2 next];
      NSLog(@"1:%@, 2:%@", retVal1, retVal2);
      NSLog(@"1's stack-size=%ld, 2's stack-size=%ld", [corState1 stackSize], [corState2 stackSize]);
    } while (retVal1 || retVal2);

  }
    return 0;
}

2012年3月5日月曜日

iOS,ARC:weakポインタを使う場合の注意

うっかり循環強参照(循環参照では無い)にしてしまうとオブジェクトが解放されずリークとなりますので、
弱い参照を使って循環強参照を防ぎます。

ただし、weak修飾子は iOS5以降でしか利用できないので、iOS4ではunsafe_unretainedをweak修飾子の代わりとします。
ところがunsafe_unretainedは挙動が素直と言うかweakほど気が利かないので、iOS5/ARCに慣れてしまうとiOS4/ARCで痛い目を見ると言う話です。

詳細はこの元ネタ参照。
元ネタはObj-cお馴染みのデリゲートパターンでは循環参照になるので、これが循環強参照とならない様にdelegateの属性を弱参照とするのだが、うっかりiOS4/ARCで痛い目を見たという話です。

weak属性が気が利いてるというのは、例えばこの本によると、
コンパイラは、__weak修飾子付きの変数をアクセスする場合、必ずautoreleasepoolに登録してからアクセスする様なコードを生成するらしい。
//
id __weak obj1 = obj0;
NSLog(@"class=%@", [obj1 class]);

//
id __weak obj1 = obj0;
id __autoreleasing tmp = obj1;
NSLog(@"class=%@", [tmp class]);
と等価なコードに生成するそうです。

わざわざこんなコードを生成する理由は元ネタを読むと分かるので説明は省略します。

●対策
元ネタにも対策が書いてあるのですが、問題が発覚してからの対処の様な感じでちょっと分かりにくいと感じるかもしれません。
ここは敢えて別の方法を考えてみましょう。
要はレシーバが弱参照の場合は__weakの時と同じ挙動をする様にすれば良いのです。

こんなマクロ関数(WeakSendMessage)を作ります。
//
//1:弱参照にweakを使う。0:弱参照にunsafe_unretainedを使う
#define kUsingWeak 0

#if kUsingWeak
  #define unretained   weak
  #define __unretained __weak
  #define WeakSendMessage(_r, _s, _a1) \
         [(_r) _s(_a1)];
#else
  #define unretained   unsafe_unretained
  #define __unretained __unsafe_unretained
  #define WeakSendMessage(_r, _s, _a1) \
         __autoreleasing NSObject *tmp = (_r); \
         [tmp performSelector:@selector(_s) withObject:(_a1)];
#endif
いったん__autoreleasing属性の一時ポインタに置き換えてます。

この様にレシーバが弱参照のメッセージングの箇所を
//
  //self.delegateは弱参照
  [self.delegate callFromClassB:self];
こう変えます。
//
  //self.delegateは弱参照
  WeakSendMessage(self.delegate, callFromClassB:, self);

これでiOS4でもiOS5でも同じ挙動になるはず。

と言うか、循環参照するならiOS4は切り捨てろ。
iOS4を切り捨てられないなら循環参照はするな、というのが正しいのかもしれません。

因みにWeakSendMessageの一時ポインタの属性__autoreleasingを__strongに変えても問題無く動作します。
//
  #define WeakSendMessage(_r, _s, _a1) \
         __strong NSObject *tmp = (_r); \
         [tmp performSelector:@selector(_s) withObject:(_a1)];
この方法はC++0x(あるいはboost)のスマートポインタweak_ptrと考え方がだいたい似ていると思われます。
弱参照weak_ptrからは直接参照先にアクセス出来なくて、メンバ関数lock()で強参照を取り出す必要があります。
こんな感じ
//弱参照から強参照を取り出している
weakPtr.lock()->doIt();
//weakPtr->doIt(); //これはコンパイルエラー
weak_ptrは使った事が無いので、確信はありませんが…

2012年3月4日日曜日

iOS:TableViewのセクションヘッダにUITextFieldを追加する

UITableViewControllerに以下を追加する。
*iOS5 ARCです。MRCの場合は少しコードを変える必要があります。
//
static const CGFloat kHeight = 40.0f;

- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section {
  UIView *v = [[UIView alloc] init];
  UITextField *txtF = [[UITextField alloc] initWithFrame:CGRectMake(0.0f, 0.0f, 160.0f, kHeight)];
  v.backgroundColor = [UIColor brownColor];
  txtF.textColor = [UIColor yellowColor];
  [v addSubview:txtF];
  return v;
}

- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section {
  return kHeight;
}

2011年6月10日金曜日

iOS:キーボードをカスタマイズ

元ネタ

カスタムキーボード画面1
顔ボタンを押すとキーボードが変化します。

カスタムキーボード画面2
変化後のカスタムキーボード。ABCボタンで元に戻ります。


このUITextFieldから派生したクラスを画面(*.xib)のカスタムキーボードで入力したいテキストフィールドに設定します。
//
//--------------------------------------------------
//  CustomTextField.h
//--------------------------------------------------

#import 
#import "CustomKeybord.h"


@interface CustomTextField : UITextField {
  @private
  UIView *inputAccessoryView_;
  BOOL useCustomKeybord_;
  NSString *customBtnTxt_;
}

@property (nonatomic, retain) IBOutlet CustomKeybord *keybord;
@property (nonatomic, retain) UIButton *compButton;

- (IBAction)faceBtn1:(id)sender;
- (IBAction)faceBtn2:(id)sender;
- (IBAction)faceBtn3:(id)sender;

@end


//---------------------------------------------------------
//  CustomTextField.m
//---------------------------------------------------------

#import "CustomTextField.h"


@implementation CustomTextField

@synthesize keybord=keybord_;
@synthesize compButton;

- (void)changeKeybord:(id)sender {
  self->useCustomKeybord_ = !self->useCustomKeybord_;
  self->customBtnTxt_ = (self->useCustomKeybord_) ? @"ABC" : @"(^-^)";
  [self reloadInputViews];
}

- (UIView *)inputAccessoryView {
  if (self->inputAccessoryView_ == nil) {
    CGFloat width = [[UIScreen mainScreen] applicationFrame].size.width;
    CGRect accessFrame = CGRectMake(0.0, 0.0, width, 50.0);
    self->inputAccessoryView_ = [[UIView alloc] initWithFrame:accessFrame];
    self->inputAccessoryView_.backgroundColor = [UIColor blueColor];
    self.compButton = [UIButton buttonWithType:UIButtonTypeRoundedRect];
    self.compButton.frame = CGRectMake(20.0, 5.0, 80.0, 40.0);
    [self.compButton setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];
    [self.compButton addTarget:self
                   action:@selector(changeKeybord:)
         forControlEvents:UIControlEventTouchUpInside];
    [self->inputAccessoryView_ addSubview:compButton];
    self->customBtnTxt_ = @"(^-^)";
  }
  [self.compButton setTitle: self->customBtnTxt_ forState:UIControlStateNormal];
  return self->inputAccessoryView_;
}

- (UIView *)inputView {
  if (self->useCustomKeybord_) {
    if (self->keybord_ == nil) {
      UINib *customKeybord = [UINib nibWithNibName:@"CustomKeybord" bundle:nil];
      [customKeybord instantiateWithOwner:self options:nil];
    }
    return self->keybord_;
  } else {
    return [super inputView];
  }
}

- (IBAction)faceBtn1:(id)sender {
  self.text = [NSString stringWithFormat:@"%@(^-^)", self.text];
}

- (IBAction)faceBtn2:(id)sender {
  self.text = [NSString stringWithFormat:@"%@(^^;)", self.text];
}

- (IBAction)faceBtn3:(id)sender {
  self.text = [NSString stringWithFormat:@"%@(-.-;)", self.text];
}

@end


カスタムキーボードとしてこのようなNibを作ります。


上のNibに対応するUIViewの派生クラスを作ります。

//
//------------------------
//  CustomKeybord.h
//------------------------

#import 


@interface CustomKeybord : UIView {
}

@end


//-------------------------------
//  CustomKeybord.m
//-------------------------------

#import "CustomKeybord.h"


@implementation CustomKeybord

- (id)initWithFrame:(CGRect)frame
{
    self = [super initWithFrame:frame];
    if (self) {
        // Initialization code
    }
    return self;
}

- (void)dealloc
{
    [super dealloc];
}

@end

2011年4月26日火曜日

iOS:年月日時分を一つのUIDatePickerで入力する

UIDatePickerはプロパティdatePickerModeを変更することで、年月日モードと月日時分(または時分)モードに変更出来る。


それでは年月日時分を一つのUIDatePickerで入力したい場合はどうするのだろうか。
Pickerを二つ用意してそれぞれ年月日用と時分用に分けるのは常套手段かもしれないが、画面を二つに分ける事になり少し面倒臭い。


ユーザーのボタン選択によりdatePickerModeを動的に変えれば良いのではないか。
ボタン押下で下のswitchDateTimeを呼ぶようにする。
//

- (IBAction) switchDateTime {
  switch (datePicker_.datePickerMode) {
    case UIDatePickerModeDate: {
      datePicker_.datePickerMode = UIDatePickerModeDateAndTime;
      [swBtn_ setTitle:NSLocalizedString(@"swichBtn_date", @"") forState:UIControlStateNormal];
    } break;
    case UIDatePickerModeDateAndTime: {
      datePicker_.datePickerMode = UIDatePickerModeDate;
      [swBtn_ setTitle:NSLocalizedString(@"swichBtn_time", @"") forState:UIControlStateNormal];
    } break;
    default:
      assert(0);
      break;
  }
}
うまくいった!!ボタンを押す度にPickerの形が変わる。
と、思いきや駄目だ、モードを切り替えると時分が0時0分に戻ってしまう。
年月日は入力した値が保たれているが、時分だけは幾ら入力してもモードを切り替えると0:00になってしまう。
この様な使い方は想定されていないのか、UIDatePickerは UIDatePickerModeDateでは時分の値を保持しないらしい。


ならばUIDatePickerModeDateに変える直前で日付データを一時退避し後で書き戻す事にする。

//

- (IBAction) switchDateTime {
  switch (datePicker_.datePickerMode) {
    case UIDatePickerModeDate: {
      datePicker_.datePickerMode = UIDatePickerModeDateAndTime;
      [swBtn_ setTitle:NSLocalizedString(@"swichBtn_date", @"") forState:UIControlStateNormal];
    } break;
    case UIDatePickerModeDateAndTime: {
      NSDate *tmp = [datePicker_.date copy]; //<-ここで一時退避
      datePicker_.datePickerMode = UIDatePickerModeDate;
      datePicker_.date = tmp;                          //<-ここで書き戻す
      [tmp release];
      [swBtn_ setTitle:NSLocalizedString(@"swichBtn_time", @"") forState:UIControlStateNormal];
    } break;
    default:
      assert(0);
      break;
  }
}
モードを切り替えても時分は保持されている。
取りあえず問題は解決した様だ。

2011年4月5日火曜日

iOSでBlocks(クロージャ)2

●Objective-CのクラスのインスタンスをBlockから使用する場合に注意する事
思いがけずオブジェクトが自己参照してしまい、そのオブジェクトが解放されなくなるケース。

以下の様なインスタンス変数を持つクラスがあるとする。

//

//--- TestClass.h ---
typedef void (^block_t)();

@interface TestClass : NSObject {
@private
NSNumber* num_;
block_t block_;
}

- (id)initWithNumber:(NSNumber*)num;
- (void)doBlock;

@end

//--- TestClass.m ---
@implementation TestClass

- (id)initWithNumber:(NSNumber*)num {
  self = [super init];
  if (self) {
    num_ = num;
  }
  return self;
}

- (void)dealloc {
  num_ = nil;
  Block_release(block_);
  [super dealloc];
}

- (void)doBlock {
  if (block_) return;

  block_ = Block_copy(^{NSLog(@"memberObj : %@", num_);});
 //ここでselfのretain countが1つ増加、自己参照!!
 //num_のretain countは変化なし

  block_();
}

@end
一見問題の無い様に見えるが、実際は自分自身(self)をretainしている(循環参照している)ので、このクラスのオブジェクトを最後に破棄しようとreleaseしてもdeallocが呼ばれない(リーク!!)

//

TestClass* testObj = [[TestClass alloc] initWithNumber:[NSNumber numberWithInt:10]];
  [testObj doBlock];
  [testObj release]; //ここで破棄、しかしdeallocが呼ばれない
●何故循環参照してしまったのか
ブロック内でnum_を参照しているのでnum_の参照カウンタが増加すると思うだろうがそうではない。
オブジェクトのメソッド内でそのオブジェクトのインスタンス変数を参照するときは、明示的に書かなくてもself->変数、と解釈され、ブロックはselfの参照カウンタを増加させてしまう。

●解決方法
オブジェクトのメソッド内のブロック内でselfを参照しない。
上のケースの様にselfを明示してなくても実はselfを参照するケースに注意する。
上のケースの場合

//

# if 0
  block_ = Block_copy(^{NSLog(@"memberObj : %@", num_);}); //ここを
# else
  NSNumber* tmp = num_;
  block_ = Block_copy(^{NSLog(@"memberObj : %@", tmp);});   //こう書き換える
# endif
一旦ローカル変数へコピーする事でブロック内でself->と参照される事を防ぐ。
これでブロックはselfを参照せずにnum_だけを参照する様になる。

●雑感
クラスのメソッド内でBlocksを使うときは毎回こういう冗長なことをしないといけないのだろうか。
一見意図の見えづらいコードだし:(

2011年4月4日月曜日

iOSでBlocks(クロージャ)1

iOS 4プログラミングブックはBlocksについて分かりやすく書いてある。

●Blocksとは
いわゆるクロージャ。
関数内で宣言できるいわゆる無名関数だが、ただの無名関数ではない。
引数以外に外側のスコープのローカル変数を参照出来る。

前に^が付いているのがBlock構文である。
戻り値がvoidなので省略が可能で
^(int arg) { /* Block処理 */ };
としたが、省略しない場合は
void ^(int arg) { /* Block処理 */ };
となる。
ちなみに引数もvoidなら更に省略できて
^{ /* Block処理 */ };
と記述出来る。

下のコードでは変数fnにBlockを代入し、aとbをインクリメントした後でfnを関数ポインタの様に呼び出している。

//
int a = 1, b = 1;
void (^fn)(int) = ^(int arg) {printf("a=%d, b=%d\n", arg, b);};//(1)
++a; ++b;
fn(a);  //a=2, b=1 と出力される
//
ローカル変数aは引数として渡される直前でインクリメントされているのでa=2と表示される。
一方bはb=1と表示される。
aと同じ行でインクリメントされているのでfn(a)呼び出し時点でbも値は2である。

では何故b=1表示されるのだろうか。
これがクロージャたる所以で(1)でfnにBlockが代入された時点で変数bをキャプチャ(コピー)されるからである。

●キャプチャされたローカル変数はどこに記憶されるのか?
ローカル変数を使っていない場合:.dataセクション
ローカル変数を使っている場合:スタック
上の例はローカル変数をキャプチャするので、この場合ブロックはスタック上に配置される。
このスタック上に配置されるブロックがうっかりすると厄介な間違いの元になる。
★間違った使い方の例

//
typedef void (^block_t)();
  block_t blocks[5];
  int i;
  for(i=0; i < sizeof(blocks)/sizeof(blocks[0]); ++i) //Loop1
    blocks[i] = ^{printf("%d\n", i);};
  for(i=0; i < sizeof(blocks)/sizeof(blocks[0]); ++i) //Loop2
    blocks[i]();

実行結果
4
4
4
4
4

意図した実行結果は以下だったのだが...
0
1
2
3
4

何が間違っているのか?

Loop1のブロック^{printf("%d\n", i);};はループ毎に一時的にスタック上に配置され、そのスタック位置のアドレスは各ループiのblocks[i]に保存される。 ループが進むたびに破棄されスタック位置は元に戻る。 つまりループ毎にブロックはスコープから外れるので、このケースではループの全ての回で同じスタック位置に一時ブロックが上書きされる事になる。 blocks[0]からblocks[4]には結果的に同じアドレスを指しているので実行結果は全て4になるのだが、Loop1を抜けた時にはこのアドレスはダングリングなのでLoop2を実行する事自体が間違っている。

●この問題の回避方法
Block_copyを使ってスタックに一時的に生成されたブロックをヒープにコピーする。

//
typedef void (^block_t)();
  block_t blocks[5];
  int i;
  for(i=0; i < sizeof(blocks)/sizeof(blocks[0]); ++i)
    blocks[i] = Block_copy(^{printf("%d\n", i);});//Blockをヒープにコピー
  for(i=0; i < sizeof(blocks)/sizeof(blocks[0]); ++i){
    blocks[i]();
    Block_release(blocks[i]);//ヒープ上のものは使い終わったらreleaseする
  }

実行結果
0
1
2
3
4