11

ARC を使用した非常に単純なテスト アプリがあります。ビュー コントローラーの 1 つに UITableView が含まれています。行アニメーション(insertRowsAtIndexPathsまたはdeleteRowsAtIndexPaths)UITableView(およびすべてのセル)を作成した後、割り当てが解除されることはありません。を使用する reloadDataと、正常に動作します。iOS 6 では問題ありません。iOS 7.0 のみです。このメモリリークを修正する方法はありますか?

-(void)expand {

    expanded = !expanded;

    NSArray* paths = [NSArray arrayWithObjects:[NSIndexPath indexPathForRow:0 inSection:0], [NSIndexPath indexPathForRow:1 inSection:0],nil];

    if (expanded) {
        //[table_view reloadData];
        [table_view insertRowsAtIndexPaths:paths withRowAnimation:UITableViewRowAnimationMiddle];
    } else {
        //[table_view reloadData];
        [table_view deleteRowsAtIndexPaths:paths withRowAnimation:UITableViewRowAnimationMiddle];
    }
}

-(int)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {

    return expanded ? 2 : 0;
}

table_view は、クラス TableView (UITableView のサブクラス) の一種です。

@implementation TableView

static int totalTableView;

- (id)initWithFrame:(CGRect)frame style:(UITableViewStyle)style
{
    if (self = [super initWithFrame:frame style:style]) {

        totalTableView++;
        NSLog(@"init tableView (%d)", totalTableView);
    }
    return self;
}

-(void)dealloc {

    totalTableView--;
    NSLog(@"dealloc tableView (%d)", totalTableView);
}

@end
4

3 に答える 3

8

もう少し深く掘り下げると (ARC を無効にし、tableview をサブクラス化し、retain/release/dealloc メソッドをオーバーライドしてからログ/ブレークポイントを配置する)、アニメーション完了ブロックで何か問題が発生し、リークが発生する可能性があることがわかります。 .
iOS 7 でのセルの挿入/削除後にテーブルビューが完了ブロックからあまりにも多くの保持を受け取るように見えますが、iOS 6 ではそうではありません (iOS 6 では UITableView はまだブロック アニメーションを使用していません - スタック トレースでも確認できます)。 .

だから私はテーブルビューのアニメーション完了ブロックのライフサイクルを UIView から汚い方法で引き継ごうとしています: メソッドの入れ替えです。そして、これは実際に問題を解決します。
しかし、それはもっと多くのことを行うので、私はまだより洗練されたソリューションを探しています.

したがって、UIView を拡張します。

@interface UIView (iOS7UITableViewLeak)
+ (void)fixed_animateWithDuration:(NSTimeInterval)duration delay:(NSTimeInterval)delay options:(UIViewAnimationOptions)options animations:(void (^)(void))animations completion:(void (^)(BOOL finished))completion;
+ (void)swizzleStaticSelector:(SEL)selOrig withSelector:(SEL)selNew;
@end
#import <objc/runtime.h>

typedef void (^CompletionBlock)(BOOL finished);

@implementation UIView (iOS7UITableViewLeak)

+ (void)fixed_animateWithDuration:(NSTimeInterval)duration delay:(NSTimeInterval)delay options:(UIViewAnimationOptions)options animations:(void (^)(void))animations completion:(void (^)(BOOL finished))completion {
    __block CompletionBlock completionBlock = [completion copy];
    [UIView fixed_animateWithDuration:duration delay:delay options:options animations:animations completion:^(BOOL finished) {
        if (completionBlock) completionBlock(finished);
        [completionBlock autorelease];
    }];
}

+ (void)swizzleStaticSelector:(SEL)selOrig withSelector:(SEL)selNew {
    Method origMethod = class_getClassMethod([self class], selOrig);
    Method newMethod = class_getClassMethod([self class], selNew);
    method_exchangeImplementations(origMethod, newMethod);
}

@end

ご覧のとおり、元の完了ブロックはanimateWithDuration:メソッドに直接渡されず、ラッパー ブロックから正しく解放されます (これがないと、テーブルビューでリークが発生します)。少し奇妙に見えますが、問題は解決します。

App Delegate のdidFinishLaunchingWithOptions:または必要な場所で、元のアニメーションの実装を新しいものに置き換えます。

[UIView swizzleStaticSelector:@selector(animateWithDuration:delay:options:animations:completion:) withSelector:@selector(fixed_animateWithDuration:delay:options:animations:completion:)];

その後、すべての呼び出しが[UIView animateWithDuration:...]この変更された実装につながります。

于 2013-09-25T14:45:52.380 に答える