Entries

スポンサーサイト

上記の広告は1ヶ月以上更新のないブログに表示されています。
新しい記事を書く事で広告が消せます。

07月22日のココロ日記(BlogPet)

まさか、むいさんができるなんて!今日は眠れません。

*このエントリは、ブログペットのココロが書いてます♪

対多プロパティのキー値監視

タグ: Objective-C

対多リレーションシップのプロパティをキー値監視したい場合、インスタンス変数名をプロパティキーにしてmutableArrayValueForKeyを使うのが一番手っ取り早い。

#import <Foundation/Foundation.h>

@interface Model : NSObject
{
	NSMutableArray* array;
};

@end

@implementation Model

- (id)init
{
	if(self = [super init])
	{
		array = [[NSMutableArray alloc] init];
	}
	return self;
}

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

@end

@interface View : NSObject
{
	Model* model;
};

- (id)initWithModel:(Model*)targetModel;

@end

@implementation View

- (id)initWithModel:(Model*)targetModel
{
	if(self = [super init])
	{
		model = [targetModel retain];
		[model addObserver:self
		        forKeyPath:@"array"
		           options:NSKeyValueObservingOptionNew
		           context:NULL];
	}
	return self;
}

- (void)dealloc
{
	[model removeObserver:self forKeyPath:@"array"];
	[model release];
	[super dealloc];
}

- (void)observeValueForKeyPath:(NSString*)keyPath
                      ofObject:(id)object 
                        change:(NSDictionary*)change
                       context:(void*)context
{
	NSLog(@"%@", change);
}

@end

int main (int, const char*)
{
	NSAutoreleasePool*
		pool = [[NSAutoreleasePool alloc] init];

	Model* model = [[[Model alloc] init] autorelease];
	[[[View alloc] initWithModel:model] autorelease];

	NSMutableArray* array =
		[model mutableArrayValueForKey:@"array"];
	[array addObject:[NSNumber numberWithInt:10]];
	[array addObject:[NSNumber numberWithInt:201]];
	[array removeObjectAtIndex:1];
	
    [pool drain];
	
    return 0;
}

ただ、この方法はコード的に「arraymutableArrayValueForKeyでいじって良いこと」を伝えることができないので、何かしらコメントを残しておく必要がある。

/// mutableArrayValueForKey でいじってね(´З`)
NSMutableArray* array;

NSMutableArrayのインスタンスをインスタンス変数にしたくない」とか「インスタンス変数名とプロパティキーを別にしたい」場合、Modelクラスに以下のメソッドを実装する(「Array」の部分がプロパティキー)。

- (unsigned int)countOfArray;
- (id)objectInArrayAtIndex:(unsigned int)index;
- (void)insertObject:(id)anObject inArrayAtIndex:(unsigned int)index;
- (void)removeObjectFromArrayAtIndex:(unsigned int)index;

そしたら、あとはまたmutableArrayValueForKeyを使うなり、実装したメソッドを直接呼び出すなり、好きにする。

KVOとC++オブジェクト

タグ: Objective-C C++

Cocoaのキー値監視(KVO)は、データ型が「ポインタ」であるプロパティに対しては動作しない。
そのようなプロパティは、そもそもキー値監視の前提である「キー値コーディング(KVC)」でサポートされていない

// NSObjectを継承しているオブジェクトはKVCでそのまま使える
@property (retain) NSString* value;

// int型の値はKVCでは「NSNumberオブジェクト」になる
@property int value;

// ポインタはKVCではサポートされていない
@property int* value;

C++のオブジェクトをObjective-Cクラスのプロパティにしてキー値監視しようと目論んでいた身としては、残念な事実である。
NSValueにはvalueWithPointerがあるのだから、それでラップしてくれれば良いのに。
まあ、そんな「謎のポインタ」を渡されても、キー値監視の主な用途である「Cocoaバインディング」で役に立たないのだから、仕方ないのかも知れないが。

プロパティとC++オブジェクト

タグ: Objective-C C++

Objective-C 2.0のメソッドはC++のオブジェクトを参照型で受け渡しできる(値渡しもできる)。

#include <boost/shared_ptr.hpp>

class Foo;

@interface Bar : NSObject
{
	boost::shared_ptr<const Foo>* foo;
}

- (const boost::shared_ptr<const Foo>&)foo;
- (void)setFoo:(const boost::shared_ptr<const Foo>&)newFoo;

@end

Objective-C 2.0はC++のオブジェクトもポインタならプロパティにできる。

- (boost::shared_ptr<const Foo>*)foo;
- (void)setFoo:(boost::shared_ptr<const Foo>*)newFoo;

@property boost::shared_ptr<const Foo>* foo;

ならば以下の様に記述すれば参照型のプロパティも宣言可能か。

- (const boost::shared_ptr<const Foo>&)foo;
- (void)setFoo:(const boost::shared_ptr<const Foo>&)newFoo;

@property const boost::shared_ptr<const Foo>& foo;

答え:internal compiler error: Bus error

const参照を非const参照に変えてもダメ。
readonlyにしてもダメ。
参照にsetterは有り得ないので理解できるが、getterのみでもダメか。

ではC++のオブジェクトそのものをプロパティにしたらどうなるだろう。

- (boost::shared_ptr<const Foo>)foo;
- (void)setFoo:(boost::shared_ptr<const Foo>)newFoo;

@property boost::shared_ptr<const Foo> foo;

答え:setterが呼ばれない

値を代入しようとしても、getterの戻り値である一時オブジェクトに代入してしまう。

結局、C++のオブジェクトをObjective-Cクラスのプロパティにするには、ポインタを使う他ないようだ。

C++から見たObjective-C - キー値監視

タグ: Objective-C C++ Boost

MVCを綺麗に実現するための仕組み。
KVC(Key-Value Coding――キー値コーディング)に準拠するModelに変更が生じたとき、それをViewやControllerに知らせる。
原語はKVO(Key-Value Observing)。

この仕組みがフレームワークで用意されているのは嬉しい。
ModelクラスはC++のクラスにしようかと考えていたが、これがあるならObjective-Cのクラスにした方が良さそうだ。

#import <Foundation/Foundation.h>

// Model

@interface Model : NSObject
{
	int value;
};

- (id)init;

@property int value;

@end

@implementation Model

@synthesize value;

- (id)init
{
	if(self = [super init])
	{
		value = 0;
	}
	return self;
}

@end

// View

@interface View : NSObject
{
	Model* model;
}

- (id)initWithModel:(Model*)targetModel;

@end

@implementation View

- (id)initWithModel:(Model*)targetModel
{
	if(self = [super init])
	{
		model = [targetModel retain];
		
		[model addObserver:self
		        forKeyPath:@"value"
		           options:NSKeyValueObservingOptionNew
		           context:NULL];
	}
	
	return self;
}

- (void)dealloc
{
	[model removeObserver:self forKeyPath:@"value"];
	[model release];
	[super dealloc];
}

- (void)observeValueForKeyPath:(NSString*)keyPath
                      ofObject:(id)object
                        change:(NSDictionary*)change
                       context:(void*)context
{
	NSLog(@"%@", [change objectForKey:NSKeyValueChangeNewKey]);
}

@end

// main

int main (int, const char*)
{
    NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
	
	Model* model = [[[Model alloc] init] autorelease];
	View* view = [[[View alloc] initWithModel:model] autorelease];
	
	model.value = 10;
	model.value = 201;

    [pool drain];
	
    return 0;
}

似たようなことをC++(+Boost)でやってみる。

#include <iostream>
#include <boost/signals.hpp>
#include <boost/noncopyable.hpp>
#include <boost/shared_ptr.hpp>
#include <boost/bind.hpp>

// Model

class Model : boost::noncopyable
{
public:
	Model() :
	val(0)
	{
	}
	
	void value(int n)
	{
		if(n != val)
		{
			val = n;
			value_change_event(this, n);
		}
	}
	
	int value() const
	{
		return val;
	}
	
	template <typename Slot>
	boost::signals::connection connect_value_slot(Slot slot)
	{
		return value_change_event.connect(slot);
	}
	
private:
	int val;
	boost::signal<void (Model*, int)> value_change_event;
};

// View

class View : boost::noncopyable
{
public:
	View(const boost::shared_ptr<Model>& target_model) :
	model(target_model)
	{
		if(model)
		{
			value_slot_connection =
				model->connect_value_slot(
					boost::bind(&View::display_value, _2)
				);
		}
	}
	
private:
	static void display_value(int val)
	{
		std::cout << val << std::endl;
	}
	
	boost::shared_ptr<Model> model;
	boost::signals::scoped_connection value_slot_connection;
};

// main

int main (int, const char*)
{
	const boost::shared_ptr<Model> model(new Model);
	View view(model);
		
	model->value(10);
	model->value(201);
	
    return 0;
}

流石にModelクラスの楽さは段違い。

XLink Kai: Evolution 7.4 Beta

2週間ほど前に『モンスターハンターポータブル 2nd G』を買った。
古い知り合いに、「ネット越しに一緒に遊ばね?」と誘われたのだ。

「あれ、PSPってそんなことできたっけ」と聞くと、XLink Kaiという環境があって、非公式ではあるが、それを使ってインターネット越しに遊ぶことができるのだという。
その時教えてもらったのはWindows用の情報だった(私がMacユーザーだということを失念していたそうだ)のだが、調べてみるとこれにはちゃんとMac用のクライアントもあり、しかもMacの場合、内蔵のAirMacとPSPを繋いで、Ethernet辺りでインターネットに繋げば、特に新たに無線LANアダプタを買うこともなく遊べるのだとか。

なるほど、それは(システム的に)おもしろそうだし、やってみるかー、ということでソフトを購入。
Webであれこれ調べて設定し、無事集会所で友人2人と合流。
PSP上では「名前は一覧に出てくるが姿は見えない」状態だが、それは「よくあること」らしい。
そうか、なら気にしない。
とっととクエスト行きまっしょい。

と、ここで問題発生。

何度やっても、誰がやっても、「私」と「私以外」の間で、「クエストボード」の情報が伝わらない(私が受注すると私以外のクエストボードに、私以外が受注すると私のクエストボードに、クエストが貼り出されない)のだ。
いろいろルータのポートフォワーディングなどいじってみたが一向に解決する気配がない。
それどころか、「Macの現行のクライアントは3人以上で遊ぶと『このゲーム2人用なんだ』と言わんばかりに物凄く不安定である」という嫌な噂まで入ってくる始末。
しかし同時に、「もうすぐ、本当にすぐに、Mac向けの新バージョン出しますよ:]」との宣言が公式フォーラムであったことも知る。

結局、クエストできないし、出来たとしてもすぐ切断されるようでは仕方ないので、その新バージョンが出てから試してみようということで、その日は解散となった。

で、今週になって7.4 Betaが登場。
今回のエンジンはUIをWebサーバの形で提供するので、Webブラウザさえあれば、専用のUIアプリケーションは必要ない。
設定項目やなんかは前のバージョンとさして変わらないので、特に迷うことなく操作可能。(強いて言えば、「左上の円環」が設定ページへの入り口だということがわかりにくい)
今回開けておくポートは30000っぽいが、これは前色々試したときから開けっ放し(;´Д`)なので問題なし。
あとは、クエスト受注の情報を正しく授受できるか否かだ。

まずは3人揃うまでの間、2人で実験。
クエスト受注からクリアまで、問題なく動作。
ついでに、今まで見えていなかった相方の姿がちゃんと見えていることに感動。

そして3人。

まずは友人Aがクエストを受注。
私のクエストボードにはクエストが掲載されるが、友人Bのボードには掲載されず。

続いて友人Bがクエストを受注。
だがこれも同じように、私の方には掲載されるものの、友人Aの方には掲載されず。

それならばと、今度は私がクエストを受注。
すると、友人ABともにボードにクエストが現れ、無事クエストを開始することができた。

その後も何度かクエストをこなしたが、切断等でクエストが中断されることは一度もなく(その前、2人でやっていたときは、kaiがPSPを見失って、友人がクエストからはじき出されるということがあったが)、それどころか、「クエスト受注情報が飛んでくるのが速い」等、友人達2人で遊んでいたときよりむしろ快適な模様。
これは、私だけ新しいバージョンを使っている(はず。友人Aは新バージョンを試したが上手く動かなかったと言っていた)のが理由かもしれない。

というわけで7.4 Betaはかなり良い。
「エンジンの正しい終了のさせ方が分からない(ので、いつも強制終了させている)」とか、「相変わらずチャットが化ける」とかの問題はあるが、まあ別にその辺はどうでもいい。
クエストを安定して遂行できれば、それで十分だ。

大文字のファイル名とインクルード

タグ: C++ gcc

Linux用に作られたソース群をMacでビルドする作業中。
「特に何のエラーも出ないな」とか言った矢先に、ずらずらと姿を現す「未定義」エラーたち。

/usr/include/c++/4.0.0/bits/localefwd.h:124: error: ‘mbstate_t’ was not declared in this scope
/usr/include/c++/4.0.0/bits/localefwd.h:124: error: template argument 3 is invalid
/usr/include/c++/4.0.0/bits/localefwd.h:126: error: ‘mbstate_t’ was not declared in this scope
/usr/include/c++/4.0.0/bits/localefwd.h:126: error: template argument 3 is invalid
/usr/include/c++/4.0.0/bits/localefwd.h:132: error: expected type-specifier before ‘istreambuf_iterator’
/usr/include/c++/4.0.0/bits/localefwd.h:132: error: expected `>' before ‘istreambuf_iterator’
……

こんな(標準ヘッダファイルがインクルードしている)ファイルでエラーが出るとは、嫌な感じだ。
mbstate_t はよく知らないが、 istreambuf_iterator くらいは必要なら自分でインクルードするのではないか? 標準ヘッダなら。

これは何かおかしい。
もしかしたら誰か読み込むヘッダを間違っているのかもしれない。
とりあえず、コンパイルエラーを起こしているファイルの中身を

#include <iostream>

だけにしてコンパイルしてみる。

../util/time.h:29: error: expected constructor, destructor, or type conversion before ‘&’ token
/usr/include/c++/4.0.0/ctime:68: error: ‘::tm’ has not been declared
/usr/include/c++/4.0.0/ctime:70: error: ‘::clock’ has not been declared
/usr/include/c++/4.0.0/ctime:71: error: ‘::difftime’ has not been declared
……

( ゚д゚)ポカーン
標準ヘッダしかインクルードしていないのに、最初の行の相対パスは一体……って、あ、いや、そうか。

../util は -I../util によってインクルードパスに指定されている。
そしてその中には「time.h」は存在しないが、頭文字が大文字の「Time.h」が存在する。
「大文字小文字を区別する」ファイルシステム(元の環境のような)であれば、この2つは別物として扱われる。
だがMacの現在一般的なファイルシステムは「大文字小文字を区別しない」。
そのため、標準ヘッダが標準ヘッダの「time.h」をインクルードしようとして、パス優先度が上位にきている ../util の「Time.h」をインクルードすることになってしまったのだ。
このことは g++ -Mg++ -M -I../util の出力を見比べてみるとよく分かる。

  • g++ -M コンパイルエラーを起こしているファイル | grep time.h

      /usr/include/time.h /usr/include/_structs.h /usr/include/sys/_structs.h \
  • g++ -M -I../util コンパイルエラーを起こしているファイル | grep time.h

      ../util/time.h /usr/include/ctype.h /usr/include/runetype.h \

この状態を解決するには、問題のパスをメインインクルードパスに追加(-I../util)するのではなく、第2インクルードパスに追加(-idirafter ../util)すれば良い。

もっとも、そもそもこんなことにならないよう、予め「大文字小文字を区別しないファイルシステムでも重複しない名前を付ける」とか、「インクルード対象が曖昧にならないよう構成(例えばBoostなどのように)しておく」ことの方が大切なのだが。

CRITICAL_SECTION構造体

タグ: VS2005 C++

CRITICAL_SECTIONのデッドロック箇所を特定する方法を探していたところ、CRITICAL_SECTION自体がそこそこ情報を持っていることに気付いたのでメモ。

Windows CE 5.0 SDK

typedef struct CRITICAL_SECTION {
    unsigned int LockCount;         /* Nesting count on critical section */
    HANDLE OwnerThread;         	/* Handle of owner thread */
    HANDLE hCrit;					/* Handle to this critical section */
    DWORD needtrap;					/* Trap in when freeing critical section */
    DWORD dwContentions;			/* Count of contentions */
} CRITICAL_SECTION, *LPCRITICAL_SECTION;
LockCount
EnterCriticalSection のネスト深度。
初期値は1で、OwnerThreadがNULLでも0にはならない。
OwnerThread
クリティカルセクションオブジェクトを所有している(ロックしている)スレッド。
どのスレッドも所有していないときはNULL。
hCrit
クリティカルセクションオブジェクトハンドル。
needtrap
待機スレッドの有無。
dwContentions
クリティカルセクションオブジェクトを所有するためにスレッドが待機状態になった回数。

Visual Studio 2005 Platform SDK

typedef struct _RTL_CRITICAL_SECTION_DEBUG {
    WORD   Type;
    WORD   CreatorBackTraceIndex;
    struct _RTL_CRITICAL_SECTION *CriticalSection;
    LIST_ENTRY ProcessLocksList;
    DWORD EntryCount;
    DWORD ContentionCount;
    DWORD Spare[ 2 ];
} RTL_CRITICAL_SECTION_DEBUG, *PRTL_CRITICAL_SECTION_DEBUG, RTL_RESOURCE_DEBUG, *PRTL_RESOURCE_DEBUG;

#define RTL_CRITSECT_TYPE 0
#define RTL_RESOURCE_TYPE 1

typedef struct _RTL_CRITICAL_SECTION {
    PRTL_CRITICAL_SECTION_DEBUG DebugInfo;

    //
    //  The following three fields control entering and exiting the critical
    //  section for the resource
    //

    LONG LockCount;
    LONG RecursionCount;
    HANDLE OwningThread;        // from the thread's ClientId->UniqueThread
    HANDLE LockSemaphore;
    ULONG_PTR SpinCount;        // force size on 64-bit systems when packed
} RTL_CRITICAL_SECTION, *PRTL_CRITICAL_SECTION;

こちらはMSDN Magazineに詳しい説明あり
DebugInfoとか、名前を見て「デバッグ用の詳細情報か?」と期待したら、なんか普通にリリース版でも必要なものらしい。
ちょっとがっかり。

Type
0
CreatorBackTraceIndex
よく分からない。
ProcessLocksList
プロセスにある全てのクリティカルセクションを繋ぐリスト?
EntryCount
ContentionCount
クリティカルセクションオブジェクトを所有するためにスレッドが待機状態になった回数。
Spare
自由に使っていい?
LockCount
ロックされていない = -1
ロックされている = RecursionCount - 1 + 待機スレッド数
RecursionCount
EnterCriticalSection のネスト深度。
ロックされていないか、 LeaveCriticalSection で所有権を完全に手放した直後は0。
OwningThread
クリティカルセクションオブジェクトを所有している(ロックしている)スレッド。
どのスレッドも所有していないときはNULL。
LockSemaphore
CreateEvent(0, TRUE, FALSE, 0) みたいなもの。

Appendix

タグ

Blog内検索

上記広告は1ヶ月以上更新のないブログに表示されています。新しい記事を書くことで広告を消せます。