1

UIViewにUILabelテキストの行があり、NSTimerを介して定期的に更新されます。このコードは、画面の下部近くにステータス項目を頻繁に書き込むことになっています。データはその管理外から来ています。

UILabelがリリースされていないように見えるため、私のアプリは非常に高速にメモリを使い果たします。Deallocが呼び出されることはないようです。

これが私のコードの非常に圧縮されたバージョンです(わかりやすくするためにエラーチェックなどは削除されています)。

ファイル:SbarLeakAppDelegate.h

#import <UIKit/UIKit.h>
#import "Status.h"

@interface SbarLeakAppDelegate : NSObject 
{
    UIWindow *window;
Model *model;
}
@end

ファイル:SbarLeakAppDelegate.m

#import "SbarLeakAppDelegate.h"

@implementation SbarLeakAppDelegate
- (void)applicationDidFinishLaunching:(UIApplication *)application 
{       
    model=[Model sharedModel];

    Status * st=[[Status alloc] initWithFrame:CGRectMake(0.0, 420.0, 320.0, 12.0)];
    [window addSubview:st];
    [st release];

    [window makeKeyAndVisible];
}

- (void)dealloc 
{
    [window release];
    [super dealloc];
}
@end

File:Status.h

#import <UIKit/UIKit.h>
#import "Model.h"

@interface Status : UIView 
{
    Model *model;
    UILabel * title;
}
@end

File:Status.mこれが問題のある場所です。UILabelはリリースされていないようで、文字列もリリースされている可能性があります。

#import "Status.h"

@implementation Status

- (id)initWithFrame:(CGRect)frame 
{
self=[super initWithFrame:frame];
model=[Model sharedModel];
[NSTimer scheduledTimerWithTimeInterval:.200 target:self  selector:@selector(setNeedsDisplay) userInfo:nil repeats:YES];
return self;
}

- (void)drawRect:(CGRect)rect 
{
title =[[UILabel alloc] initWithFrame:CGRectMake(0.0f, 0.0f, 320.0f, 12.0f)];
title.text = [NSString stringWithFormat:@"Tick  %d", [model n]] ;
[self addSubview:title];
[title release];
}

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

ファイル:Model.h(これと次はデータソースであるため、完全を期すためにのみ含まれています。)実行するのは、毎秒カウンターを更新することだけです。

#import <Foundation/Foundation.h>
@interface Model : NSObject 
{
int n;
}

@property int n;
+(Model *) sharedModel;
-(void) inc;
@end

ファイル:Model.m

#import "Model.h"


@implementation Model

static Model * sharedModel = nil;

+ (Model *) sharedModel
{
if (sharedModel == nil)
    sharedModel = [[self alloc] init];
return sharedModel; 
}

@synthesize n;
-(id) init
{
self=[super init];
[NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(inc) userInfo:nil repeats:YES];
return self;
}

-(void) inc
{
n++;
}
@end
4

3 に答える 3

5

問題は、Status UIView から UILabel を決して削除しないことです。drawRect の保持カウントを見てみましょう。

(void)drawRect:(CGRect)rect {
   title =[[UILabel alloc] initWithFrame:CGRectMake(0.0f, 0.0f, 320.0f, 12.0f)];

ここでは、alloc を使用して UILabel を作成しました。これにより、保持カウントが 1 のオブジェクトが作成されます。

[self addSubview:title];
[title release];

UILabel を Status ビューに追加すると、タイトルの保持カウントが 2 に増加します。次のリリースでは、最終的な保持カウントは 1 になります。オブジェクトがそのスーパービューから削除されることはないため、オブジェクトの割り当てが解除されることはありません。

基本的に、メモリがなくなるまで、タイマーが起動するたびに、1 つの UILabel を別の UILabel の上に追加します。

以下で提案されているように、ビューがロードされたときに一度 UILabel を作成し、UILabel のテキストを [model n] で更新する必要があります。

ハウスキーピングのメモとして、dealloc メソッドで残っているオブジェクトを適切に解放していることを確認することもできます。「モデル」と「タイトル」は、「モデル」が SbarLeakAppDelegate にある必要があるのと同様に、「ステータス」dealloc で解放する必要があります。

お役に立てれば。

編集 [1]:

この時点で、メモリの問題はかなりうまく処理されているようです。使用している 2 つのタイマーに代わる別の方法を提案したかっただけです。

Status オブジェクトで実行しているタイマーは、0.2 秒ごとに起動します。「モデル」値 n を実際にインクリメントするタイマーは、1 秒に 1 回だけ起動します。ステータスビューのより定期的な「リフレッシュレート」を確保するためにこれを行っていると思いますが、データを変更せずに毎秒 4 ~ 5 回ビューを再描画している可能性があります。ビューは非常に単純であるため、これは目立たないかもしれませんが、NSNotification のようなものを検討することをお勧めします。

NSNotification を使用すると、値 'n' が変更されるたびにモデルによって発行される特定の種類の通知を Status オブジェクトに「観察」させることができます。(この場合、1 秒あたり約 1 回)。

コールバック メソッドを指定して、通知を受信したときに通知を処理することもできます。この方法では、モデル データが実際に変更されたときにのみ -setNeedsDisplay を呼び出します。

于 2009-07-17T04:35:32.900 に答える
3

コードには 2 つの問題があります。

問題1

-drawRect では、ビューが描画されるたびにサブビューをビュー階層に追加します。これは 2 つの理由で間違っています。

  • ビューが描画されるたびに、サブビューの数が 1 ずつ増えます
  • 描画時にビュー階層を変更しています - これは正しくありません。

問題 2

タイマーはターゲットを保持します。Status オブジェクトの初期化子で、自分自身を対象とするタイマーを作成します。タイマーが無効になるまで、タイマーとビューの間に保持サイクルがあるため、ビューは割り当て解除されません。

タイマーを使用してビューを無効にするというアプローチが本当に問題の正しい解決策である場合は、保持サイクルを断ち切るための明示的な手順を実行する必要があります。

これを行う 1 つの方法は、ビューがウィンドウに配置されるときに -viewDidMoveToWindow: でタイマーをスケジュールし [1]、ビューがウィンドウから削除されるときにタイマーを無効にすることです。

[1] どのウィンドウにも表示されていないビューを定期的に無効にすることは、そうでなければ意味がありません。

于 2009-07-17T13:40:38.937 に答える
2

ビュー コントローラで NSTimer を使用して -setNeedsDisplay を呼び出す代わりに、" title.text = [NSString stringWithFormat:@"Tick %d", [model n]] ;" を呼び出すメソッドを作成してみませんか? そうすれば、タイマーが起動するたびにラベルを再作成する代わりに、表示される値を更新するだけで済みます。

于 2009-07-17T03:43:40.483 に答える