5

カテゴリの dealloc メソッドでアクションを実行する必要があります。スウィズルを試してみましたが、うまくいきません (素晴らしいアイデアでもありません)。

誰かが尋ねた場合、答えはノーです。サブクラスは使用できません。これはカテゴリ専用です。

[NSTimer scheduledTimerWithTimeInterval:target:selector:userInfo:repeats:]orを使用して遅延に対してアクションを実行し、[self performSelector:withObject:afterDelay:]dealloc でキャンセルしたいと考えています。

最初の問題は、NSTimer私が望んでいないターゲットを保持することです。保持されませんが、メソッド[self performSelector:withObject:afterDelay:]を呼び出せるようにする必要があります。そうしないとクラッシュします。[NSObject cancelPreviousPerformRequestsWithTarget:selector:object:]dealloc

カテゴリでこれを行う方法について何か提案はありますか?

4

3 に答える 3

8

ランタイムを台無しにせずにクラスをサブクラス化する方が良いと思いますが、カテゴリでそれを行う必要があると確信している場合は、オプションを考えています。それでもランタイムが混乱しますが、スウィズリングよりも安全だと思います。

DeallocHookヘルパークラスを作成することを検討してください。たとえば、任意のクラスにアタッチできるヘルパークラスを呼び出して、割り当てが解除さNSObjectれたときにアクションを実行しNSObjectます。次に、次のようなことを行うことができます。

// Instead of directly messing with your class -dealloc method, attach
// the hook to your instance and do the cleanup in the callback 
[DeallocHook attachTo: yourObject 
             callback: ^{ [NSObject cancelPrevious... /* your code here */ ]; }];

DeallocHookを使用して実装できますobjc_setAssociatedObject

@interface DeallocHook : NSObject
@property (copy, nonatomic) dispatch_block_t callback;

+ (id) attachTo: (id) target callback: (dispatch_block_t) block;

@end

実装は次のようになります。

#import "DeallocHook.h"
#import <objc/runtime.h>

// Address of a static global var can be used as a key
static void *kDeallocHookAssociation = &kDeallocHookAssociation;

@implementation DeallocHook

+ (id) attachTo: (id) target callback: (dispatch_block_t) block
{
    DeallocHook *hook = [[DeallocHook alloc] initWithCallback: block];

    // The trick is that associations are released when your target
    // object gets deallocated, so our DeallocHook object will get
    // deallocated right after your object
    objc_setAssociatedObject(target, kDeallocHookAssociation, hook, OBJC_ASSOCIATION_RETAIN_NONATOMIC);

    return hook;
}


- (id) initWithCallback: (dispatch_block_t) block
{
    self = [super init];

    if (self != nil)
    {
        // Here we just copy the callback for later
        self.callback = block;
    }
    return self;
}


- (void) dealloc
{
    // And we place our callback within the -dealloc method
    // of your helper class.
    if (self.callback != nil)
        dispatch_async(dispatch_get_main_queue(), self.callback);
}

@end

連想参照の詳細については、Objective-Cランタイムに関するAppleのドキュメントを参照してください(ただし、ドキュメントはこの主題に関してあまり詳細ではないと思います)。

私はこれを徹底的にテストしていませんが、うまくいったようです。私があなたに調査する別の方向性を与えると思っただけです。

于 2013-02-10T10:43:45.073 に答える
4

これまでに見たことのない解決策に出くわしましたが、うまくいくようです...

よくあることですが、いくつかの状態変数が必要なカテゴリがあるので、次のように を使用しますobjc_setAssociatedObject

Memento *m = [[[Memento alloc] init] autorelease];
objc_setAssociatedObject(self, kMementoTagKey, m, OBJC_ASSOCIATION_RETAIN_NONATOMIC);

そして、自分のカテゴリが拡張されたインスタンスがいつdealloc編集されたかを知る必要がありました。私の場合、オブザーバーを on に設定しself、ある時点でそれらのオブザーバーを削除する必要があるためです。そうしないと、NSKVODeallocateBreakリークの警告が表示され、悪いことにつながる可能性があります。

retain関連付けられたオブジェクトが( を使用しているため) edされていたため、それらも dOBJC_ASSOCIATION_RETAIN_NONATOMICである必要があり、したがって ed であることに突然気付きました...実際、保存用に作成した単純なストレージ クラスにメソッドを実装していました。私の状態値。そして、私は次のように仮定しました:私の関連オブジェクトは、私のカテゴリーのインスタンスが解放される前に解放されなければなりません! したがって、関連付けられたオブジェクトに、所有者がedであることを認識したときに通知させることができます。関連付けられたオブジェクトを保持していたので、プロパティ ( !として指定されていない) を追加し、所有者を設定してから、関連付けられたオブジェクトのメソッドで所有者のメソッドを呼び出すだけで済みました。releasedeallocdeallocdeallocownerretaindealloc

これは、関連するビットを含む、私のカテゴリの .m ファイルの一部を変更したものです。

#import <objc/runtime.h> // So we can use objc_setAssociatedObject, etc.
#import "TargetClass+Category.h"

@interface TargetClass_CategoryMemento : NSObject
{
    GLfloat *_coef;
}
@property (nonatomic) GLfloat *coef;
@property (nonatomic, assign) id owner;
@end
@implementation TargetClass_CategoryMemento
-(id)init {
    if (self=[super init]) {
        _coef = (GLfloat *)malloc(sizeof(GLfloat) * 15);
    }
    return self;
};
-(void)dealloc {
    free(_coef);
    if (_owner != nil 
        && [_owner respondsToSelector:@selector(associatedObjectReportsDealloc)]) {
        [_owner associatedObjectReportsDealloc];
    }
    [super dealloc];
}
@end

@implementation TargetClass (Category)

static NSString *kMementoTagKey = @"TargetClass+Category_MementoTagKey";

-(TargetClass_CategoryMemento *)TargetClass_CategoryGetMemento
{
    TargetClass_CategoryMemento *m = objc_getAssociatedObject(self, kMementoTagKey);
    if (m) {
        return m;
    }
    // else
    m = [[[TargetClass_CategoryMemento alloc] init] autorelease];
    m.owner = self; // so we can let the owner know when we dealloc!
    objc_setAssociatedObject(self, kMementoTagKey, m,  OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    return m;
}

-(void) doStuff
{
    CCSprite_BlurableMemento *m = [self CCSprite_BlurableGetMemento];
    // do stuff you needed a category for, and store state values in m
}

-(void) associatedObjectReportsDealloc
{
    NSLog(@"My associated object is being dealloced!");
    // do stuff you need to do when your category instances are dealloced!
}

@end

ここで私がどこか (おそらく SO) で学んだパターンは、ファクトリ メソッドを使用して memento オブジェクトを取得または作成します。これで、memento に所有者が設定され、memento のメソッドがコールバックして、編集dealloc中であることを所有者に知らせます。dealloc

警告:

  • 明らかに、関連付けられたオブジェクトを で設定する必要がありOBJC_ASSOCIATION_RETAIN_NONATOMICます。そうしないと、自動的に保持および解放されません。
  • memento/state に関連付けられたオブジェクトがdealloc、所有者が編集されている以外の状況で編集される場合、これはより厄介になりますdealloc...しかし、おそらく、そのイベントを無視するようにいずれかのオブジェクトを訓練することができます。
  • ownerプロパティを として宣言することはできません。そうしないと、retain本当に強い参照ループが作成され、どちらのオブジェクトもdeallocedとして修飾されなくなります。
  • 所有者が完全に編集される前にOBJC_ASSOCIATION_RETAIN_NONATOMIC関連付けられたオブジェクトが必ず削除されることが文書化されているかどうかはわかりませんが、少なくとも直感的には、そのように発生するようであり、ほとんどそうであるに違いありません。releasedealloc
  • の dealloc メソッドの前またはassociatedObjectReportsDealloc呼び出されるかどうかはわかりません。これは重要かもしれません! 後で実行した場合、のメンバー オブジェクトにアクセスしようとすると、クラッシュします。そして、私の推測では、それはその後です。TargetClassTargetClass

オブジェクトを二重リンクしているため、これは少し面倒です。これらの参照をまっすぐに保つには細心の注意が必要です。ただし、スウィズリングやその他のランタイムへの干渉は関係ありません。これは、ランタイムの特定の動作に依存しているだけです。関連付けられたオブジェクトが既にある場合は、便利なソリューションのようです。場合によっては、独自deallocの をキャッチするためだけに作成する価値があるかもしれません!

于 2013-12-14T22:08:16.787 に答える
2

残念ながら、提案されたソリューションは機能しません。ターゲットを保持するため 、タイマーが無効になるまでターゲットは実行されません。ターゲットの保持カウントは常に 1 以上でホバリングし、タイマーが解放されるのを待ちます。までにタイマー到達する必要があります。(ARC以前は、タイマーをオーバーライドして破棄することができましたが、それは実際には良い解決策ではありません.)NSTimerdealloc deallocretainrelease

NSThreadにもこの問題があり、解決策は簡単です。少し再設計すると、スレッドのコントローラーが「モデル」から分離されます。スレッド (この場合はタイマー) を作成して所有するオブジェクトも、タイマーのターゲットにすべきではありません。次に、現在持っている保持サイクル (タイマーがタイマーを所有するオブジェクトを所有する) の代わりに、良い直線があります: コントローラーは、ターゲットを所有するタイマーを所有します。dealloc外部オブジェクトはコントローラーと対話するだけで済みます。割り当てが解除されると、オーバーライドやその他のメモリ管理メソッドを使用してゲームをプレイしなくても、タイマーをシャットダウンできます。

それがこれを処理する最善の方法です。何らかの理由でそれができない場合-カテゴリのオーバーライドについて話しているので、明らかにタイマーのターゲットであるクラスのコードがありません(ただし、おそらくまだ作成できますその場合でもコントローラー) -- 弱参照を使用できます。NSTimer残念ながら、そのターゲットへの弱参照を取得する方法はわかりませんが、GCD は を介し​​て公正な近似値を提供しますdispatch_after()。ターゲットへの弱参照を取得し、それを渡すブロックでのみ使用します。ブロックは弱い参照を介してオブジェクトを保持しません (方法NSTimerはそうです)。ブロックが実行される前にオブジェクトの割り当てが解除された場合、弱い参照はもちろん保持されるnilため、送信するメッセージを安全に書き込むことができます。

于 2013-02-11T20:56:42.260 に答える