`
chenniaoc
  • 浏览: 39906 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

Cocoaのメモリ管理(3)

阅读更多
保持と解除という方法は、理屈は分かるし簡単そうに見えます。しかし、実際にやってみると意外と難しいことがわかります。そこでCocoaでは少し楽をするための仕組みを導入しています。簡単に言えば、とりあえずなんでも入れておけるごみ袋を用意して、不要になった時点でごみ袋ごと捨てちゃうという方法です。このごみ袋にあたるのが、NSAutoreleasePoolというクラスです。
Application KitにおけるNSAutoreleasePool

さて、Cocoaの重要なフレームワークの一つであるApplication Kitの話から始めたいと思います。Application Kitは、主にGUIを持つアプリケーションを作成するためのフレームワークです。このフレームワークを利用して作ったアプリケーション(つまり、ぶっちゃけた話、ProjectBuilderとInterfaceBuilderを使って作られたNSApplicationを利用するアプリケーション) には、デフォルトで一つNSAutoreleasePoolオブジェクトが生成されています。さらに、NSApplicationが提供するイベントループ内でも、NSAutoreleasePoolオブジェクトが生成され、ループが一回転するごとに解放され、新しいNSAutoreleasePoolオブジェクトを生成します。つまり、NSAutoreleasePoolそのものを知らなくてもとりあえず使える仕組みが提供されているわけです。このごみ袋にオブジェクトを入れる方法が、NSObjectに用意されているautoreleaseメソッドです。Foundation KitとApplication Kitが提供するクラスは、基本的にNSAutoreleasePoolがある環境での動作を想定しています。

- (id)autorelease

Application Kitに準拠したプログラムの場合、任意のオブジェクトにautoreleaseを送っておくと、このオブジェクトはデフォルトの NSAutoreleasePoolオブジェクトに登録されます。そして、このプログラムが終了する直前にデフォルトの NSAutoreleasePoolオブジェクトとともに、登録されたオブジェクトがすべて解放されます。つまりautoreleaseは、retain & releaseの仕組みを使わないで、楽々とメモリ管理を行う仕組みなのです。

- (void)methodA
{
    id obj = [[Foo alloc] init];

    // objとして参照されているオブジェクトをNSAutoreleasePoolに登録する。
    [obj autorelease];
    // このメソッドを抜けると、変数objによる参照はなくなるが、
    // 割り当てられたメモリは、イベントループのNSAutoreleasePoolが
    // 解放される時に解放される。
}

NSAutoreleasePoolの特徴

ApplicationKitが提供するNSAutoreleasePoolを使いこなすにあたって、いくつかの注意すべき特徴があります。

    * デフォルトのNSAutoreleasePoolは、プログラムの最後に(つまり、メインスレッドの末端で)解放されるので、基本的にこれに登録されたオブジェクトに割り当てられたメモリはプログラムの最後まで解放されない。
    * イベントループ内で毎回生成されるNSAutoreleasePoolは、ループが一回転するごとに解放されるので、基本的にこれに登録されたオブジェクトに割り当てられたメモリはイベント処理が終わると解放される。
    * autoreleaseされたオブジェクトが、基本的にそのオブジェクトが登録されたNSAutoreleasePoolがreleaseされるまで解放されないということは、逆に言えば、NSAutoreleasePoolが生きている間は、オブジェクトの生存が保障されるということを意味している。
    * ただし、releaseメソッドを送ることで、このようなオブジェクトでもプログラムの途中で解放することは可能である。(これは、推奨されない方法である。基本的に、同じオブジェクトに対してautoreleaseとreleaseを併用することは混乱の元になるので、止めるべきである。 deallocについてはいわずもがな。オブジェクトのプログラマブルな解放のためには、後述する方法を用いるべきである。)
    * 例外発生時には、NSAutoreleasePool(と、登録されたオブジェクト)は解放されるので、メモリリークは起こらない。(どっちにしてもプログラムが終了するので、メモリは解放される。)
    * デフォルトのNSAutoreleasePoolだけで動作するようなプログラム場合、参照が切れたオブジェクトは、基本的にメモリリークと同じ状況になる。(あえて言うなら、知った上でのメモリリーク。通常のプログラムではあまり気にする必要がない。)
    * NSAutoreleasePoolに登録されているオブジェクトへのアクセス手段はないので、参照の管理には注意が必要である。

自前のNSAutoreleasePoolの利用

さて、長々と引き伸ばしてきた話題に入ろうと思います。まず、Application Kitが用意しているNSAutoreleasePoolだけでは、一度のイベント処理で大量にオブジェクトを生成するような処理に対応するのが難しい場合があります。また、そもそもApplication Kitを利用しないプログラム(たとえば、コマンドラインで動作するツールなど)は、この仕組みが提供されていないわけです。そこで、自前の NSAutoreleasePoolを作成して利用する意味が出てくるのです。ここであれこれいうより、まずはサンプルコードで説明しましょう。以下のコード内のobj2を見てわかるように、実はautoreleaseメソッドを呼び出すと、スレッド内で一番最近作られた NSAutoreleasePoolにオブジェクトが登録されます。つまり、テンポラリなオブジェクトは、このように自前の NSAutoreleasePoolオブジェクトを適宜用意してやれば、任意の時点でまとめて解放できるのです。

- (void)methodA
{
    id obj1;
    id obj2;
    id arp;

    obj1 = [[Foo alloc] init];
    obj2 = [[Foo alloc] init];

    [obj1 autorelease]; // obj1は、デフォルトのNSAutoreleasePoolに登録される。
    arp = [[NSAutoreleasePool alloc] init];
    [obj2 autorelease]; // obj2は、arpに登録される。
    [arp release]; //arp解放。この時点でobj2は、解放される。

    // obj1は、上位のNSAutoreleasePoolがreleaseされるまで解放されない。
}

理解を深めるためにもう一つ例を示しておきます。

- (void)methodA
{
    id temp;
    id arp;
    int i;

    for (i = 0; i < 10; i++) {
        arp = [[NSAutoreleasePool alloc] init];
        temp = [[[Foo alloc] init] autorelease];
                            // tempで現在参照されているオブジェクトは、
                            // arpに登録される。
        [arp release];      // arp解放。
                            // この時点でtempで参照されるオブジェクトは解放される。
    }
    // この時点で10個のオブジェクトが解放されています。
}

入れ子になったNSAutoreleasePoolの利用

もうお分かりかと思いますが、NSAutoreleasePoolはいくらでも入れ子することができるのです。スレッド上で一番最近作られた NSAutoreleasePoolオブジェクトがカレントのNSAutoreleasePoolオブジェクトとして機能するわけです。さて、まずは自前のNSAutoreleasePoolを活用する前に、いくつかの注意点を示しておきます。

    * NSAutoreleasePoolオブジェクトにはretainを送ってはならない。
    * NSAutoreleasePoolオブジェクトにはautoreleaseを送ってはならない。
    * 無用なバグと混乱を避けるために、NSAutoreleasePoolオブジェクトの生成と解放は、同じ文脈上で行うべきである。(たとえば、前述の例のように、ループ内の処理の前後など)
    * retain回数 + 1 = autorelease回数がプログラム全体で成り立つことを検証する。

さて、スレッドの流れを把握しているなら、NSAutoreleasePoolオブジェクトを複数のメソッドで使うことが可能です。 Application Kit標準のNSAutoreleasePoolオブジェクトも基本的にこの方法の例に他なりません。まずは簡単なサンプルを示します。この例では、 obj2がそれにあたります。

- (void)methodA
{
    id obj1;
    id arp1;


    arp1 = [[NSAutoreleasePool alloc] init];
    obj1 = [[[Foo alloc] init] autorelease]; // obj1は、arp1に登録される。
    [self methodB];
    [arp1 release];     //arp解放。この時点でobj1、obj2は、解放される。
}

- (void)methodB
{
    id obj2;
    id obj3;
    id arp2;

    obj2 = [[Foo alloc] init];
    obj3 = [[Foo alloc] init];

    [obj2 autorelease]; // obj2は、呼出し元のarp1に登録される。
    arp2 = [[NSAutoreleasePool alloc] init];
    [obj3 autorelease]; // obj3は、arp2に登録される。
    [arp2 release];     //arp2解放。この時点でobj3は、解放される。

    // obj2は、arp1解放まで解放されない。
}

いままでは単純な例でしたが、最後に少し複雑な例を示します。それは、メソッドに戻り値がある場合の扱いについてです。以下のサンプルを見て下さい。このコードには、重要な項目が隠されています。つまり、NSAutoreleasePoolオブジェクトは、登録されているオブジェクトに対する登録回数を管理していて、自身が解放される時に、その登録回数分のreleaseを各オブジェクトに送っているだけだということです。つまり登録回数がそのオブジェクトの保持数より少なければ、NSAutoreleasePoolオブジェクトが解放された後でも、そのオブジェクトは生き続けます。この例では、retにretainを送ることでretの寿命を引き伸ばしています。

- (void)methodA
{
    id obj;
    id arp1;

    arp1 = [[NSAutoreleasePool alloc] init];
    obj = [self methodB:1];
    [arp1 release];    // arp1解放。この時点でobjすなわちretは、解放される。
}

- (id)methodB:(int)i
{
    id ret;
    id arp2;

    arp2 = [[NSAutoreleasePool alloc] init];
    ret = [[Foo alloc] init]; // retの保持数は1
    [ret autorelease];
    if (i == 1) {
        [ret retain]; // retの保持数は2になる。
                      // autoreleaseではなく、retainを呼ぶことで、
                      // arp2の寿命を超えて、arp1(もしくは、
                      // もっと外)の文脈まで、retで参照されるオブジェクトの
                      // 寿命を延ばすことができる。
    } else {
        ret = nil;
    }
    [arp2 release];   // 判定で真だったら、この時点でretの保持数は1になる。
                      // 判定で偽だったら、この時点でretは解放される。

    return [ret autorelease]; // retを生成したので、autoreleaseを送ってから返す。
}

autoreleaseか、retain/releaseか

autoreleaseか、retain/releaseかという問題ですが、今まで見てきたようにそんなに大差はありません。状況に応じて、選択、または併用するとよいでしょう。比較的小さなあまりオブジェクトを作らないプログラムでは、Application Kit標準のNSAutoreleasePoolオブジェクトを使うことで、autoreleaseだけでプログラミングするのもよいでしょう。しかし、自前のNSAutoreleasePoolオブジェクトを使うような局面なら、retain/releaseでも同じようなものでしょう。ただ、 NSAutoreleasePoolオブジェクトを使うことでソースコードのカスタマイズが容易になるという利点はあるかも知れません。なぜなら、オブジェクトは、NSAutoreleasePoolオブジェクトが少なくとも解放されるまで有効なのですから。
retain/release/autoreleaseの適用方針
以下に垣内さん、白山さん、高橋さんから教えていただいた適用方針をまとめたものを示します。

    * 基本としてオブジェクトのオーナシップを意識する。すなわち、以下の鉄則を厳守する。

      【鉄則1】自分で生成したオブジェクトは、自分で解放する
      【鉄則2】他人が生成したオブジェクトは、気に留めない
      【鉄則3】他人が生成したオブジェクトが必要なら、必ず保持(retain)して、必要にならなくなった時点で、必ず解除(release or autorelease)する

    * 可能な限り alloc-init系の生成メソッドは使わない。【鉄則2からの派生】

    * alloc、init…を使う場合は必ず autoreleaseを入れる。【鉄則1】

    aFoo = [[[Foo alloc] init] autorelease];

    * alloc-init系以外の生成用クラスメソッドの場合は何もしない。それらのクラスメソッドは内部でalloc-init系の生成メソッドを呼んでいるため、鉄則1が適用されていると考えるべきである。【鉄則2】

    aFoo = [Foo foo];

    * インスタンス変数・グローバル変数・スタティック変数に代入して、参照を残す場合は、retainをかける。【鉄則3】

    globalFoo = [aFoo retain];

    * インスタンス変数・グローバル変数・スタティック変数の値を消す場合には、かならずreleaseかautoreleaseを入れる。【鉄則3】

//この例ではinstanceFooは、このクラスのインスタンス変数と仮定する。
- (void )setFoo:newFoo
{
    [instanceFoo release];
    instanceFoo = [newFoo retain];
    return;
}

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

    * 自分でクラスを作るときは、NSStringの+string... NSArrayの+array... のようなそのクラス専用の生成用クラスメソッドを作成して利用させる。【鉄則2の応用】

// 生成用の引数がない場合
+ foo 
{
    return [[[Foo alloc] init] autorelease];
}

// 生成用の引数が必要な場合
+ fooWithBar: bar
{
    return [[[Foo alloc] initWithBar:bar] autorelease];
}

    * 必ずループ内部でNSAutoreleasePoolを適用する。

    for (i = 0; i < 10; i++) {
        arp = [[NSAutoreleasePool alloc] init];
        temp = [[[Foo alloc] init] autorelease];
        [arp release];
    }
分享到:
评论

相关推荐

    可可文件:接触确认アプリCOCOAの非公式ドキュメント

    【可可文件:接触确认アプリCOCOAの非公式ドキュメント】 COCOA(Contact Confirming Application)是由日本政府推出的COVID-19接触通知应用,旨在帮助追踪新冠病毒的传播,减少感染风险。这款应用程序利用蓝牙技术...

    Cocoa基本原理指南(Cocoa Fundamentals Guide)

    3. **Foundation框架**:这是Cocoa的基础,提供了许多基础数据类型、集合类、字符串处理、线程管理等基本服务。理解Foundation框架对于使用Cocoa进行开发至关重要。 4. **AppKit和UIKit**:在Mac OS X上,Cocoa的...

    cocoa编程之菜鸟入门

    ### Cocoa编程之菜鸟入门 #### 一、Cocoa与Mac OS X应用开发 - **Cocoa框架简介**:Cocoa是苹果为Mac OS X提供的一个应用程序框架,它提供了一套丰富的API来帮助开发者构建高性能的应用程序。Cocoa的核心是...

    苹果开发之Cocoa编程原书第4版

    Foundation是Cocoa的基础,提供了一组用于数据管理、文件操作、网络通信等核心功能的类。AppKit(在Mac上)或UIKit(在iOS上)则专注于用户界面的设计和交互。 1. **Objective-C与Swift选择**:Cocoa最初是基于...

    cocoa 内存管理程序教程

    ### Cocoa内存管理程序教程知识点详解 #### 一、前言 Cocoa框架是苹果公司为开发者提供的用于构建macOS及iOS应用的重要工具集之一。它不仅提供了丰富的API来帮助开发者快速构建应用程序,还包含了对内存管理的支持...

    Cocoa基本原理指南

    Cocoa框架由多个框架组成,每个框架都面向不同的开发需求,如用户界面、网络通信、数据管理等。这些框架是开发者在MacOSX上编程的基石,它们提供了丰富的API和预构建的组件。熟悉和掌握Cocoa框架,可以大大提高开发...

    Learning Cocoa With Objective-C

    《Learning Cocoa With Objective-C》是一本专为初学者设计的Cocoa编程教程,它深入浅出地介绍了如何在苹果的Mac OS X和iOS平台上开发应用程序。这本书以其丰富的图文并茂的讲解方式,使得复杂的编程概念变得易于...

    cocoa框架深入了解

    此外,Cocoa框架还提供了一种称为自动引用计数(Automatic Reference Counting, ARC)的技术,它由编译器自动插入`retain`、`release`和`autorelease`指令,简化了内存管理,减少了手动管理内存的错误。然而,理解...

    Learn Cocoa on the Mac, 2nd Edition

    - **Cocoa的角色**:可以将Cocoa视为程序员的助手,它帮助管理与Mac开发相关的大量杂务。这使得开发者能够专注于编写使自己的应用程序独一无二的核心代码。 - **Cocoa框架的学习路径**:本书将详细介绍Cocoa框架及其...

    swift Cocoa编程教程

    3. **Cocoa设计模式**:Cocoa遵循Model-View-Controller (MVC)架构模式,将应用程序分为模型、视图和控制器三个部分。理解MVC的原理和实践是Cocoa编程的关键。 4. **Interface Builder**:这是Apple的可视化设计...

    cocoa design patterns.pdf

    本书以MVC(模型-视图-控制器)设计模式为核心,介绍了Cocoa框架中广泛使用的设计模式,并指导读者如何在实际的项目开发中应用这些设计模式,以构建更加健壮、可维护和易于管理的软件系统。 在设计模式领域,MVC是...

    iOS and macOS Performance Tuning Cocoa, Cocoa Touch, Objective-C, and Swift

    《iOS和macOS性能优化:Cocoa,Cocoa Touch,Objective-C和Swift》是一本深入探讨移动设备和桌面操作系统性能提升的专业书籍。本书主要针对苹果的两大操作系统平台,即iOS和macOS,提供了丰富的实践经验和专业指导,...

    Cocoa Programming for Mac OS X

    - **Foundation 框架**:这是 Cocoa 的基础框架之一,包含了大量处理基本数据类型(如字符串、数组等)和文件管理等功能的类。它是 Cocoa 应用程序的核心组成部分。 - **AppKit 框架**:专注于图形用户界面的构建,...

    Cocoa 基本原理指南.pdf (中文)

    - **Cocoa对象**:深入理解Cocoa对象的行为、接口及其生命周期管理,是构建高效稳定应用程序的关键。 - **设计模式应用**:Cocoa采用了一系列成熟的设计模式,如MVC(模型-视图-控制器)等,帮助开发者更好地组织...

Global site tag (gtag.js) - Google Analytics