Cocoaサンプル - MVC
Xcode4を使ってMVCパターンに則った簡単なCocoaアプリーケーションのサンプルを作成します。
MVC(Model-View-Controller)パターン
MVCとは以下の3つの要素からなるアーキテクチャのことです。
Model
アプリケーションが利用するデータとその処理を行う部分です。データは隠蔽されていて操作自体は内部で完結しており、一般に再利用が可能。
View
データを可視化する部分。CocoaではNSViewクラスの派生クラスです。
Controller
ユーザーの操作を受け付けてModelとViewの制御を行う部分です。ModelとViewの仲介役ですので通常は再利用できないことが多いです。CocoaではこのクラスにOutletとActionを追加します。
MVCの相互関係
一般的な処理の流れは以下のようになります。
View | ユーザー入力→ | Controller | 更新→ | Model |
---|---|---|---|---|
View | ←更新 | Controller | ←通知 | Model |
・ViewはControllerに指示を出す
・ControllerはModelとViewを制御する。
・ModelからみてControllerは間接的な相関関係があってもよい。状態が変化したことをControllerに知らせることができる。
※間接的な相関関係については後述
作成するサンプルは出来るだけ簡単なものにします。
仕様1:ウィンドウにボタンとラベルがあり、ラベルに0が表示されている。 仕様2:ボタンを押すと数字がカウントアップされてラベルに表示される
Viewを作成する
interface builderを利用して、ボタンとラベル(Static Text)を追加します。
Attributes InspectorでLabelのTitleを0にしておきます。
Controllerを作成する
NSObjectの派生クラスとして作成します。
Controller.h
#import <Foundation/Foundation.h> @interface Controller : NSObject { IBOutlet id label; } -(IBAction)onButtonClicked:(id)sender; @end
Controller.m
#import "Controller.h" @implementation Controller - (id)init { self = [super init]; if (self) { // initialize } return self; } - (void) dealloc { // finalize } -(IBAction)onButtonClicked:(id)sender { [label setIntValue:1]; } @end
ViewとControllerを関連付ける
Controllerオブジェクトをxibに追加します。
ユーザー入力
Viewからのイベントを受け取るために、ActionをControllerに追加して接続します。
具体的には、ボタンを押されたときのイベントハンドラをControllerクラスに追加して、
-(IBAction)onButtonClicked:(id)sender; -(IBAction)onButtonClicked:(id)sender { [label setIntValue:1]; }
Connections InspectorのReceived ActionsでボタンViewと接続します。この時点では仮実装としてラベルに1を表示することにします。
onButtonClicked <-- 接続--> Push Button
Viewの更新
ControllerからViewを更新するには、OutletをControllerに作成してViewと接続します。
ラベルのOutletをControllerクラスのメンバに追加して、
IBOutlet id label;
Connections InspectorでStatic Text Viewと接続します。
label <-- 接続--> Static Text
以上で、ViewとControllerを接続することができました。
この時点でビルド・実行してボタンを押すと1が表示されます。
Modelを作成する
データの操作、ここではカウントアップするクラスを追加します。
Model.h
#import <Foundation/Foundation.h> @interface Model : NSObject @property(readonly)int counter; -(int)countUp; @end
Model.m
#import "Model.h" @implementation Model @synthesize counter; -(id)init{ self = [super init]; if (self) { counter = 0; } return self; } - (int)countUp { ++counter; return counter; } @end
ModelとControllerを関連付ける
現時点ではControllerはModelと接続されていません。ControllerのメンバにModelクラスを追加します。
Controller.h
#import <Foundation/Foundation.h> @class Model; // Modelとの接続 @interface Controller : NSObject { IBOutlet id label; Model* model; // Modelとの接続 } -(IBAction)onButtonClicked:(id)sender; @end
さらに、イベントハンドラ内でModelとViewを更新します。
Controller.m
#import "Controller.h" #import "Model.h" // Modelとの接続 @implementation Controller - (id)init { self = [super init]; if (self) { model = [[Model alloc]init]; // Modelとの接続 } return self; } - (void) dealloc { [model release]; // Modelとの接続 } -(IBAction)onButtonClicked:(id)sender { int data = [model countUp]; // Modelの更新処理 [label setIntValue:data]; // Viewの更新処理 } @end
以上で実行すると、カウントアップされるので完成です。
ControllerとModelの接続にObserverパターンを利用する
この例ではControllerが直接ModelとViewを同じメソッドで更新していますが、Modelの処理が終わったことをControllerに通知したときにViewを更新するようなケースを考えてみます。
-(IBAction)onButtonClicked:(id)sender { int data = [model countUp]; // ボタンを押したときはModelの更新処理だけ }
-(void)Notified // Modelから通知されたときに呼び出されるメソッド { [label setIntValue:data]; // Viewの更新処理 }
ModelからControllerに通知する仕組みを入れたい場合、ModelクラスがControllerクラスを持ってしまうと再利用性に欠けてしまいます。
@interface Model: NSObject { Controller* controller; // ×これはあまり良くない }
そこで初めの方で述べた間接的な相互関係を実現するのにObserverパターンを利用します。
実はNSObjectにはメンバの値を監視できるような仕組みがデフォルトで実装されているので、Observerパターンをすぐに実装することができます。メンバの値の変化を監視する方法に自動と手動の2つの実装方法がありますが、手動の方で実装してみることにします。
監視対象クラスにObserverを追加する
NSObjectに用意されているaddObserverメソッドを使います。
Controller.m
- (id)init { self = [super init]; if (self) { model = [[Model alloc]init]; [model addObserver:self forKeyPath:@"counter" options:(NSKeyValueObservingOptionNew) context:nil]; } return self; }
Modelから通知(Notify)された時に呼び出されるメソッドを追加する
監視対象の値が変化したことを知らせるためのメソッドとして、NSObjectのobserveValueForKeyPathメソッドが用意されているので追加します。
Controller.h
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context;
Controller.m
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { if ([keyPath isEqual:@"counter"]) { int data = [model counter]; [label setIntValue:data]; // Viewを更新 } }
次に観察対象Modelの実装をします。
値が変化したことを通知する
値が変化したことをObserverに対してNotifyするためのメソッドが、NSObjectのwillChangeValueForKey(値が変化する直前に実行する),didChangeValueForKey(値が変化した直後に実行)です。
Model.m
- (int)countUp { [self willChangeValueForKey:@"counter"]; ++counter; // 必ず変化する [self didChangeValueForKey:@"counter"]; return counter; }
手動で観察するための設定を追加する
これは単にNSObjectの仕様の問題なので直接Observerパターンとは関係ありません。観察対象の値が変化したことを手動で実装するために以下のメソッドを追加して、戻り値が必ずNOになるようにします。
Model.h
+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)theKey;
Model.m
+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)theKey { BOOL automatic = NO; if ([theKey isEqualToString:@"counter"]) { automatic=NO; } else { automatic=[super automaticallyNotifiesObserversForKey:theKey]; } return automatic; }
以上で、ModelがカウントアップをControllerに通知しなおしてViewを更新するようになりました。
インターフェースを分離する
本来のObserverパターンはObserverとSubjectというinterfaceを用意して、その具象クラスで実装をします。IModelクラス、IControllerクラスを用意すると通知に必要な部分と本来の処理とを分離することができます。
Modelクラスの分離
automaticallyNotifiesObserversForKeyをIModel側に移します。
IModel.h
#import <Foundation/Foundation.h> @class IController; @interface IModel : NSObject + (BOOL)automaticallyNotifiesObserversForKey:(NSString *)theKey; @end
IModel.m
#import "IModel.h" @implementation IModel + (BOOL)automaticallyNotifiesObserversForKey:(NSString *)theKey { BOOL automatic = NO; if ([theKey isEqualToString:@"counter"]) { automatic=NO; } else { automatic=[super automaticallyNotifiesObserversForKey:theKey]; } return automatic; } @end
Model.h
#import <Foundation/Foundation.h> #import "IModel.h" @interface Model : IModel @property(readonly)int counter; -(int)countUp; //+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)theKey; //消す @end <|| Model.m >|objc| #import "Model.h" @implementation Model @synthesize counter; -(id)init{ self = [super init]; if (self) { counter = 0; } return self; } - (int)countUp { NSLog(@"countup"); [self willChangeValueForKey:@"counter"]; ++counter; [self didChangeValueForKey:@"counter"]; return counter; } // automaticallyNotifiesObserversForKey// 削除
Controllerを分離する
こちらは再利用性を考慮しないので、特に分離しなくてもよいですが、TemplateMethodパターンなどと組み合わせて、IController側にobserveValueForKeyPathを移動して、Controllerでは分かりやすい名称のメソッドを改めて定義しています。
IController.h
#import <Foundation/Foundation.h> @interface IController:NSObject -(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context; -(void)onModelChanged; @end
IController.m
#import "IController.h" @implementation IController - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { NSLog(@"obs"); if ([keyPath isEqual:@"counter"]) { [self onModelChanged]; } } -(void)onModelChanged { // do nothing if base class } @end
Controller.h
#import <Foundation/Foundation.h> #import "IController.h" @class Model; @interface Controller :IController { IBOutlet id label; IBOutlet id window; Model* model; } -(IBAction)onButtonClicked:(id)sender; -(void)onModelChanged; @end
Controller.m
#import "Controller.h" #import "Model.h" @implementation Controller - (id)init { self = [super init]; if (self) { model = [[Model alloc]init]; [model addObserver:self forKeyPath:@"counter" options:(NSKeyValueObservingOptionNew) context:nil]; } return self; } - (void) dealloc { [model release]; } -(IBAction)onButtonClicked:(id)sender { [model countUp]; } -(void)onModelChanged { int data = [model counter]; [label setIntValue:data]; } @end
Cocoaサンプル - スレッドと排他処理
スレッド
NSThreadクラスを使います。
detachNewThreadSelectorメソッドで起動、exitメソッドで終了します。
別スレッドで実行する関数をセレクタで指定します。mainスレッドとは別なので、スレッドプロシージャー内では改めてNSAutoreleasePoolを準備しておきます。
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; // スレッド処理 [pool release];
スレッドをスリープするにはsleepForTimeIntervalメソッドを使います。
排他制御
NSLockクラスでlock, unlockによって排他制御を実現することができます。
例
2つのスレッドでカウントアップした数値をStatic Textに表示するサンプルです。予めStatic Textを作成してCounterオブジェクトをxibに登録し、OutletsでStatic Textと接続しておきます。
Counter.h
#import <Foundation/Foundation.h> @interface Counter : NSObject { IBOutlet id label; NSLock *mylock; } @property(readonly)int count; -(void)countUpAutomatically; -(void)dealloc; @end
Counter.m
#import "Counter.h" @implementation Counter @synthesize count; - (id)init { self = [super init]; if (self) { mylock=[NSLock new]; // スレッド1 [NSThread detachNewThreadSelector:@selector(countUpAutomatically) toTarget:self withObject:self]; [NSThread sleepForTimeInterval:0.5]; // スレッド2 [NSThread detachNewThreadSelector:@selector(countUpAutomatically) toTarget:self withObject:self]; } return self; } -(void)dealloc { [mylock release]; } -(void)countUpAutomatically { NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; for(int i =0 ;i<10;i++){ [NSThread sleepForTimeInterval:2]; // sleep 2 seconds // 排他処理 [mylock lock]; count++; [mylock unlock]; [label setIntValue:count]; } [pool release]; [NSThread exit]; } @end
Objective-C2.0文法メモ プロトコル
Objective-Cではあるクラスのインターフェースを定義するのにプロトコルという仕組みを利用します。Javaでいうインターフェース、C++では純粋仮想関数によるクラス定義に相当するものです。書式は以下のようになります。
プロトコルの定義
@protocol MyProtocol @required // 省略可 -(void) requiredMessage; @optional -(void) optionalMessage; @end
requiredは必ず実装しなければならないメソッド、optionalはその名前通り必要があれば実装できるメソッドです。これらの識別子を省略するとデフォルトではrequiredになるので、通常はrequiredを省略して定義します。
プロトコルのクラスへの適用
@interface MyClass:SubClass <MyProtocol> @end @implementation MyClass - (void) requierdMessage { } @end
実装例
メソッドdrawを規定するプロトコルDrawableを作成して、Circleクラスに適用した例です。
#import <Foundation/Foundation.h> @protocol Drawable -(void) draw; @optional -(void) fill; @end @interface Circle:NSObject <Drawable> { int x; int y; int raius; } @end @implementation Circle - (void) draw { NSLog(@"draw"); } @end int main (int argc, const char * argv[]) { Circle *circle = [[Circle alloc]init]; [circle draw]; [circle release]; return 0; }
プロトコルの継承
JavaのInterface同様にプロトコルも継承させることができます。クラスに適用した後は基底・派生両方のメソッドを実装する必要があります。
@protocol BaseProtocol -(void) baseMessage; @end @protocol DerivedProtocol<BaseProtocol> -(void) derivedMessage; @end @interface MyClass:NSObject <DerivedProtocol> @end @implementation MyClass - (void) baseMessage { } - (void) derivedMessage { } @end
プロトコルの多重定義
Javaのインターフェースと同様に複数のプロトコルを一つのクラスでまとめて実装することもできます。
@protocol FirstProtocol -(void) firstMessage; @end @protocol SecondProtocol -(void) secondMessage; @end @interface MyClass2:NSObject <FirstProtocol, SecondProtocol> @end @implementation MyClass2 - (void) firstMessage { } - (void) secondMessage { } @end
Objective-C2.0文法メモ カテゴリ
Objective-Cでは既存のクラスに動的にメソッドを追加することができるカテゴリという仕組みがあります。この方法を用いると派生クラスを新たに作成せずに簡単にクラスを拡張することができます。
カテゴリの定義
既存のクラスMyClassが定義されているとします。
MyClass.h
@interface MyClass:NSObject - (void) method; @end
MyClass.m
@implementation MyClass - (void) method; { } @end
MyCategoryというカテゴリを利用して、MyClassに新しくnewMethodを追加してみます。
MyClass.h
@interface MyClass(MyCategory) - (void) newMethod; @end
MyClass.m
@implementation MyClass (MyCategory) - (void) newMethod { } @end
実行する側ではカテゴリを意識する必要なく、単にメソッドを呼び出すだけで済みます。
MyClass *obj =[[MyClass alloc]init]; [obj method]; [obj newMethod];
複数のカテゴリ
別のカテゴリでさらに同じクラスにメソッドを追加することも可能です。
@interface MyClass(MyCategoryExtension) - (void) newMethodExt; @end @implementation MyClass (MyCategoryExtension) - (void) newMethodExt { } @end
カテゴリによるprivateメソッドの実装
カテゴリの仕組みを利用してPrivateなメソッドを作成することができます。Privateというカテゴリに属するメソッドの定義と実装を.mファイルに追加すれば外側からアクセスすることができません。
MyClass.h
@interface MyClass : NSObject - (void) method; @end
MyClass.m
#import "MyClass.h" // privateメソッド定義 @interface MyClass(Private) + (void) classMethod; - (void) objectMethod; @end // privateメソッド実装 @implementation MyClass(Private) + (void) classMethod { } - (void) objectMethod { NSLog(@"call private method"); } @end // 本メソッド実装 @implementation MyClass - (void) method; { [self objectMethod]; // privateメソッド呼び出し } @end
main.m
#Import "MyClass.h" int main (int argc, const char * argv[]) { MyClass *obj =[[MyClass alloc]init]; // publicメソッド [obj method]; // privateメソッド呼び出しできない //[obj classMethod]; [obj release]; return 0; }
実行するとクラス内部から実行できて、外部から呼び出せないことが確認できます。
call private method
Opaqueポインタについて
Opaqueポインタ
インターフェース上で未定義のデータ型をopaque data type(不透明型)と呼び、そのような型を指すポインタをOpaqueポインタと呼びます。例えばヘッダで定義されている以下のようなポインタのことを指します。
MyInterface.h
#ifndef IF_H #define IF_H typedef struct tag_mydata* mydata_t; #endif
Opaqueポインタを使った例をいくつか取り上げたいと思います。
Opaqueポインタを使ってデータ型を隠蔽する
独自フォーマットの構造体がインターフェース上で定義されているとします。もしフォーマットを修正するとヘッダをインクルードしている部分は再度コンパイルしなおさなければなりません。特にフォーマットが非常に基本的な型であればあるほど影響範囲が広くなると思います。
MyFormat.h
static const unsigned long MY_FORMAT_TAG_SIZE = 4; typedef struct tag_myformat{ char tag[MY_FORMAT_TAG_SIZE]; unsigned long chankSize; }*myformat_t; void setMyFormat(myformat_t format);
MyFormat.c
#include "MyFormat.h" #include <string.h> // size_t, strcpy void setMyFormat(myformat_t format){ strcpy(format->tag, "data"); format->chankSize = MY_FORMAT_TAG_SIZE + sizeof(format->chankSize); }
そこで、ヘッダで定義されているフォーマットの型をOpaqueポインタに修正し、実装側で構造体を定義することにしてみます。
MyFormat.h
typedef struct tag_myformat *myformat_t; void setMyFormat(myformat_t format);
MyFormat.c
#include "MyFormat.h" #include <string.h> // size_t, strcpy static const size_t MY_FORMAT_TAG_SIZE = 4; struct tag_myformat{ char tag[MY_FORMAT_TAG_SIZE]; size_t chankSize; }; void setMyFormat(myformat_t format){ strcpy(format->tag, "data"); format->chankSize = MY_FORMAT_TAG_SIZE + sizeof(format->chankSize); }
型定義を実装側に移すことで型の変更・修正とともに手を加えなければならないファイルがMyFormat.cだけで済みますので先ほどより簡単になります。また型を外部から隠蔽したい場合にもこの手法で実現できることが分かります。
void*型の代用として利用する
Opaqueポインタを利用すると、指し示しているデータ型を外部から隠蔽することができますので、C言語をオブジェクト指向的に実装することができます。全ての型を表現するのにvoid*型がよく利用されますがこれをインターフェース上で定義するとどうなるでしょうか?
例えば次のような例を考えてみます。
FrameBuffer.h
typedef void *HANDLE; typedef void *HINSTANCE; HANDLE createFramwBuffer(); void destroyFrameBuffer(HANDLE handle);
FrameBuffer.c
struct HANDLE__ { int windowId; }; struct HINSTANCE__ { int dummy; }; static int ID_COUNTER = 0; HANDLE createFramwBuffer(){ HANDLE handle = (HANDLE)malloc(sizeof(struct HANDLE__)); HINSTANCE inst = handle; struct HANDLE__* casted_handle = (struct HANDLE__*)handle; casted_handle->windowId = (ID_COUNTER++); return handle; } void destroyFrameBuffer(HANDLE handle){ if( handle == NULL ){ free(handle); } }
まずはvoid*を引数にして受け取ってから独自フォーマットにキャストしなければならないという点があります。そして、例えば別の型を導入した場合、実装側では別の型であるにも関わらず、お互いvoid*型なので誤って代入してしまう危険性があります。そこでOpaque型を使って以下のように修正します。
FrameBuffer.h
typedef struct HANDLE__ *HANDLE; typedef struct HINSTANCE__ *HINSTANCE; HANDLE createFramwBuffer(); void destroyFrameBuffer(HANDLE handle);
FrameBuffer.c
#include "FrameBuffer.h" #include <stdlib.h> struct HANDLE__ { int windowId; }; struct HINSTANCE__ { int dummy; }; static int ID_COUNTER = 0; HANDLE createFramwBuffer(){ HANDLE handle = (HANDLE)malloc(sizeof(struct HANDLE__)); HINSTANCE inst = handle; // 型の違いを認識可能→警告 handle->windowId = (ID_COUNTER++); return handle; } void destroyFrameBuffer(HANDLE handle){ if( handle == NULL ){ free(handle); } }
こうすれば、具体的な型が未定義であっても、お互いに異なることは認識できるので、上で述べた危険性を回避することができます。またキャストせずにそのまま利用することもできます。
フォーマットが変わる可能性のあるデータ型を扱うようなライブラリを実装する際にはこのような手法が有効かもしれません。
Xcode4でメモリーリークを検出する手順
メモリーリーク
Xcode4でメモリーリークをチェックするには、実行中の各種状態を可視化できるinstrumentsというツールのメモリリーク画面で確認することができます。(instrumentを起動してみるにはProduct>Profileを選択してください)
Xcodeの左上Runボタンを長押しすると"Run","Test","Profile","Analyze"のうちどのモードで実行するか選択できますので、"Profile"を選択します。(実行ボタンがプロファイルのアイコンに変わります。)
次に、Product>Edit Scheme>Profile ObjectiveC>Info>Build Configuration:ここでは"Debug"にしておきます
Executable:実行ファイル(デフォルトでは自動選択されている)
Instrument:実行時に起動したいinstrumentの種類を聞く場合"Ask onLaunch"
メモリリーク画面を直接表示したい場合→"Leaks"を選択して"OK"を押します。
先ほど変更した左上の実行ボタンを押すか、Product>Profileを押すとinstrumentsが起動して、メモリーリークをリアルタイムにチェックできるようになります。
instruments>Leaks>Snapshots(左側メニュー最上位)>SnapshotInterval(sec):ここでは仮に1.0secにしておきます。
Snapshotを押すと現在の状態がグラフに反映されます。
instrument上でRecordを押すと再度実行されて実行結果が蓄積されます。InstrumentのView>Run Browser
で実行結果の履歴を編集することができます。
サンプル
故意にメモリーリークを発生させてみて確かめてみます。下の例ではallocを呼び出してreleaseしていません。
main.m
#import <Foundation/Foundation.h> @interface Sample : NSObject @end @implementation Sample -(void) dealloc { NSLog(@"dealoc was called" ); [super dealloc]; } @end static void checkMemoryLeak(); void checkMemoryLeak(){ Sample *obj = [[Sample alloc]init]; // 解放しない } int main (int argc, const char * argv[]) { checkMemoryLeak(); [NSThread sleepForTimeInterval:3]; // 3秒スリープする return 0; }
実行するとinstrumentが起動して、1s後にMemory Leak Discoveredの項目にオレンジ色のマークが表示され、メモリーリークが検出されたことが分かります。そして右下側"Leaked Object"欄に、メモリーリークの元となったオブジェクト"Sample"が表示されています。
リークサイズ
Leaked Object欄を見るとオブジェクトの先頭アドレスとリークしたサイズが表示されています。メンバを全く持っていないオブジェクトでも16Bytesメモリ領域が確保されているということですね。そこで、試しに余分に16バイトだけクラスにデータを持たせて再度実行すると今度は32Bytesと表示されていることが分かります。
@interface Sample : NSObject { char buffer[16]; } @end @implementation Sample -(void) dealloc { NSLog(@"dealoc was called" ); [super dealloc]; } @end
Objective-C2.0メモ メモリ管理
Objective-Cのメモリ管理について
C言語でメモリ管理をするにはmalloc, freeを使いますが、Objective-Cでは、オブジェクトの参照数を考慮したメモリ管理の仕組みが用意されています。現在いくつ参照されているのかを把握し続けるために、全てのオブジェクトは参照カウンタという値をメンバに持っています。参照カウンタの増減だけ指示すれば、参照がなくなった時点で自動的にメモリを解放してくれる仕組みになっていますので、より手軽にメモリを管理することができます。ただし参照カウンタの指示が適切になされていないとメモリリークが発生しうるので注意が必要です。カウンタの値は、NSObjectクラスのretainConterというメソッドで取得することができます。
※ちなみに、Objective-Cでallocとdeallocを使うとオブジェクトの生成と破棄のタイミングを自由にコントロールできますが、上で述べた仕組みがありますのであまり推奨されているわけではなさそうです。使うときはリークやアクセス違反が起こりやすいので注意する必要があります。
Sample *obj = [[Sample alloc]init]; [obj dealloc];
メモリ管理の仕組みを理解するには以下の3点に着目するとよいと思います。
・オブジェクトが生成されるタイミング
・オブジェクトが破棄されるタイミング
・ガーベージコレクションの規則とオブジェクトの参照数の変化
retainとrelease
オブジェクトの生成
allocメソッドによってオブジェクトが生成され、参照カウンタが1になります。
オブジェクトの破棄
参照カウンタが1の状態で、releaseを呼ぶとオブジェクトが破棄されます。
参照カウンタの変化
retainメソッドで参照カウンタが+1, releaseメソッドで-1されます。
以下、参照カウンタをインクリメント・デクリメントしたサンプルです。
main.m
#import <Foundation/Foundation.h> // sample class @interface Sample : NSObject @end @implementation Sample @end int main (int argc, const char * argv[]){ Sample *sample = [[Sample alloc]init]; // count 1 NSLog(@"retain counter = %lu", [sample retainCount]); [sample retain]; // count 2 NSLog(@"retain counter = %lu", [sample retainCount]); [sample release]; // count 1 NSLog(@"retain counter = %lu", [sample retainCount]); [sample release]; // count 0 return 0; }
メモリープールとガーベージコレクション
メモリープールとautorelease
生成したオブジェクトを自動的に破棄してくれる仕組みをガーベージコレクションと呼びますが、Objective-Cでも同様の仕組みを利用することができます。オブジェクト領域をメモリに確保したあと、AutoReleasePool(自動的に解放してくれる機能を持つメモリプール)に登録して、最後にプール自体を解放(drain)してしまうという手があります。こうしておくと、解放するタイミングを意識することなく手軽にオブジェクトを生成することができます。一方で、解放されるタイミングを任せてしまうので使用メモリ量をこと細かに制御できないという欠点もあると考えられます。
void checkMemoryLeak(){ NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; Sample *obj = [[Sample alloc]init]; [obj autorelease]; [pool drain]; }
autoreleaseが呼び出されると以前に作成したメモリプールを探しにいきます。ですのでスコープ外でプールを作成しておけば、そちらのメモリプールに登録することができます。main関数の始めにプールを準備し、終わりでプールを片付ければその間自由にautoreleaseを使うことができます。以下の例ではmain最後で破棄しているのでスリープした後にdeallocが呼び出されています。
#import <Foundation/Foundation.h> @interface Sample : NSObject @end @implementation Sample -(void) dealloc { NSLog(@"dealoc was called" ); [super dealloc]; } @end static void checkMemoryLeak(); int main (int argc, const char * argv[]) { NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; checkMemoryLeak(); NSLog(@"sleep"); [NSThread sleepForTimeInterval:3]; // sleep 3 seconds [pool drain]; // ここでdealloc呼び出し return 0; } void checkMemoryLeak(){ //NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; Sample *obj = [[Sample alloc]init]; [obj autorelease]; //[pool drain]; }
実行結果
sleep dealoc was called
オブジェクトが解放されるタイミングを早めるためには、より近いスコープ内にAutoReleasePoolを作成してスコープを出るときにプールを片付けるのが効果的です。こちらの例では、スリープよりも先にdeallocが呼び出されています。
int main (int argc, const char * argv[]) { NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; checkMemoryLeak(); NSLog(@"sleep"); [NSThread sleepForTimeInterval:3]; // sleep 3 seconds [pool drain]; return 0; } void checkMemoryLeak(){ NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; Sample *obj = [[Sample alloc]init]; [obj autorelease]; [pool drain]; // ここでdealloc呼び出し }
実行結果
dealoc was called sleep
ガーベージコレクションが有効な環境か無効なのかによって若干drainの動作が異なります。ガーベージコレクションが有効な環境ではdrainを実行すると、そのガーベージコレクションを起動して、適切なタイミングで破棄します。無効な環境では、登録されている全てのオブジェクトをreleaseします。
オブジェクトの生成
allocメソッドによってオブジェクトが生成され、参照カウンタが1になります。
オブジェクトの破棄
autoreleaseを実行すると有効なNSAutoreleasePoolを探して参照を自動的に登録します。
ガーベージコレクション有効→
NSAutoreleasePoolのdrainが呼び出された後、適切なタイミングでオブジェクトが破棄されます。
ガーベージコレクション無効→
NSAutoreleasePoolのdrainが呼び出されたとき、参照カウンタが1ならオブジェクトが破棄されます。
参照カウンタの変化
ガーベージコレクション無効→
同様に、NSAutoreleasePoolのdrainが呼び出されたときにカウンタが-1されます。
メモリ管理の例
いくつか具体例を追記しておきます。
クラス生成(releaseとautorelease)
#import <Foundation/Foundation.h> @interface Sample : NSObject @end @implementation Sample -(void) dealloc { NSLog(@"dealoc was called" ); [super dealloc]; } @end int main (int argc, const char * argv[]) { NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; Sample *obj = [[Sample alloc]init]; // 非auto Sample *obj_auto = [[[Sample alloc]init]autorelease]; // auto [obj release]; // obj破棄 [pool drain]; // obj_auto破棄 return 0; }
allocで生成したらそれに対応するreleaseを呼び出しているところがポイントです。
クラスが別のオブジェクトを持つ場合
ParentクラスがChildクラスをメンバに持っており、オブジェクトを引数とするメソッドを持っている例です。setterの実装では古いオブジェクトの参照カウントを減らして、逆に新しいオブジェクトをカウントアップしています。
Parentクラス
#import <Foundation/Foundation.h> #import "Child.h" @interface Parent : NSObject { Child* child; } -(id) initWithChild:(Child*) aChild; -(void) setChild:(Child*) aChild; @end @implementation Parent - (id)initWithChild:(Child*) aChild { self = [super init]; if (self) { [self setChild:aChild]; } return self; } // オブジェクトを引数とするメソッド -(void) setChild:(Child*) aChild { [child release]; // 古い値なのでリリースしておく //[child autorelease]; // 必要に応じてautoreleaseと使い分けてもよい [aChild retain]; // 新しい値を参照するのでカウントアップ child = aChild; } -(void) dealloc { NSLog(@"Parent dealoc was called" ); NSLog(@"retain counter = %lu",[child retainCount]); [child release]; // 最後に解放しておく [super dealloc]; } @end
Childクラス
#import <Foundation/Foundation.h> @interface Child : NSObject @end @implementation Child - (id)init { return [super init]; } -(void) dealloc { NSLog(@"Child dealoc was called" ); [super dealloc]; } @end
main
int main (int argc, const char * argv[]) { Child *child = [[Child alloc]init]; Parent *parent = [[Parent alloc]initWithChild:child]; [parent setChild:child]; [child release]; // alloc対応 [parent release]; // alloc対応 return 0; }