Странные проблемы с памятью, с включенным ARC - PullRequest
0 голосов
/ 16 марта 2012

У меня очень, очень странная ошибка, возможно, связанная с управлением памятью (даже если я использую ARC).

У меня есть мои AppDelegate, Foo и SubFoo (которые являются подклассомFoo).

Foo.h

@protocol FooDelegate <NSObject>

- (void)didReceiveDownloadRequest:(NSURLRequest *)downloadRequest;

@end

@interface Foo : NSObject {
    __weak id <FooDelegate> delegate;
}

- (void)performRequest;

@property (nonatomic, weak) id <FooDelegate> delegate;
@property (nonatomic, retain) NSString *fileIdentifier;

Foo.m

@implementation Foo

@synthesize delegate, fileIdentifier;

- (id)init {
    if ((self = [super init])) {
        self.delegate = nil; // I tried leaving this line out, same result.
        NSLog(@"I am %p.", self);
    }

    return self;
}

- (void)performRequest {
    // Bah.
}

@end

SubFoo.h

@interface SubFoo : Foo {
    WebView *aWebView;
}

SubFoo.m

- (void)performRequest {
    if (self.fileIdentifier) {
        aWebView = [[WebView alloc] init];
        [aWebView setFrameLoadDelegate:self];
        [[aWebView mainFrame] loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"theURL"]];
    }
}

- (void)webView:(WebView *)sender didFinishLoadForFrame:(WebFrame *)frame {
    NSLog(@"Finished loading.");

    // ...

    NSLog(@"Class Name: %@", NSStringFromClass([self class]));
    NSLog(@"Memory Location of delegate: %p", self.delegate);

    // ...
}

Иногда имя класса в webView: didFinishLoadForFrame: возвращает совершенно другой класс (вместо SubFoo он возвращает случайные классы, такие как NSSet, NSArray, иногда даже возвращает CFXPreferencesSearchListSource), в других случаях он просто падаетс EXC_BAD_ACCESS, и когда он возвращает случайный класс для имени класса: он возвращает [randomClassName делегат] нераспознанный селектор.

EDIT : когда self устанавливается в другую вещь, онустанавливается правильно в webView: didFinishLoadForFrame:, а в executeRequest это ВСЕГДА SubFoo.

Любая помощь здесь будет принята.

Ответы [ 5 ]

3 голосов
/ 19 марта 2012

Во-первых, даже если вы используете ARC для обнуления слабых ссылок в вашем проекте (@property (weak)), другие проекты и структуры могут не использовать (и, вероятно, не используют) обнуление слабых ссылок.

Другими словами, предположим, что все делегаты в рамках являются __unsafe_unretained, если:

  1. Свойство делегата объявлено weak в заголовке
  2. В документации / заголовке явно указано иное

Тем не менее, давайте поговорим о вашем примере. Ваша диаграмма владения объектом выглядит примерно так:

Object ownership chart

(Примечание: я не совсем уверен, какой класс в вашем проекте использует SubFoo. Исходя из общепринятой практики, я предполагаю, что у вас есть класс с сильной ссылкой на SubFoo, и этот класс также настроен на SubFooDelegate)

В конечном счете, ваш экземпляр SubFoo теряет свою последнюю сильную ссылку и освобождается. В идеальном мире, поддерживающем ARC, указатель WebView на SubFoo в это время будет нулевым. Однако это еще не идеальный мир, а frameLoadDelegate в WebView равен __unsafe_unretained. Из-за взаимодействия цикла выполнения WebView переживает SubFoo. Веб-запрос завершается, и мертвый указатель разыменовывается.

Чтобы это исправить, вам нужно вызвать [aWebView setFrameLoadDelegate:nil]; в методе dealloc SubFoo. Вы также должны вызывать его при переназначении aWebView, так как вы теряете след старого aWebView:

SubFoo.m

@implementation SubFoo

- (void)dealloc {
    [aWebView setFrameLoadDelegate:nil];
    // Also nil out any other unsafe-unretained references
}

- (void)performRequest {
    if (self.fileIdentifier) {
        [aWebView setFrameLoadDelegate:nil]; // Protects us if performRequest is called twice.  Is a no-op if aWebView is nil
        aWebView = [[WebView alloc] init];
        [aWebView setFrameLoadDelegate:self];
        [[aWebView mainFrame] loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"theURL"]];
    }
}

- (void)webView:(WebView *)sender didFinishLoadForFrame:(WebFrame *)frame {
    // ...
}
1 голос
/ 07 июня 2015

Вот реальная проблема: вы создаете объект SubFoo в контексте метода. Таким образом, после завершения метода SubFoo освобождается (до того, как его WebView успеет загрузиться).

Чтобы это исправить, вам нужно присвоить объект SubFoo, который вы создаете, чему-то постоянному, например, переменной экземпляра класса, из которого вы его создаете. Таким образом, объект будет оставаться за рамками метода, в котором он был создан, и все будет работать так, как ожидалось.

1 голос
/ 16 марта 2012

Забудьте пока ошибку self.delegate, это красная сельдь, если [self class] дает неправильный результат!Ваши результаты свидетельствуют о том, что вы каким-то образом ударяете self.

Точка останова на webView:didFinishLoadForFrame: проверьте значение self и выполните шаг.1013 * То, что self ошибается в первом утверждении метода экземпляра, скажем, необычно (но не невозможно).

Когда объект задан как делегат другого, важно убедиться, что делегатВремя жизни объекта не менее того, для которого он выступает в качестве делегата.Введение ARC может привести к сбою ранее работающего кода, поскольку он может освободить делегата раньше, чем код выполнялся в MRC.Когда это происходит, вызов делегату обычно завершается неудачей.

Однако ваша ошибка не сбивается при вызове делегата;звонок начинается - вы заканчиваете webView:didFinishLoadForFrame: - и затем обнаруживаете, что self недействителен.Для фактического вызова метода экземпляра обычно требуется допустимое значение для self, поскольку оно используется для определения реализации метода для вызова.Следовательно, self обычно действует в начале метода!

Но обратите внимание на "обычно" ...

Так что, несмотря на то, что вы успешно достигли своего метода, ваша ошибкавозможно, из-за отсутствия сильной ссылки на ваш SubFoo экземпляр, вы передаете его как делегат aWebView, и к тому времени, когда webView:didFinishLoadForFrame: называется, ваш SubFoo исчез.

Makeуверен, что вы держите сильный реф на ваш SubFoo instance.Если вы просто хотите проверить (это , а не рекомендуемое общее решение!), Если это ваша проблема, вы можете просто назначить ее локальной статической (static SubFoo *holdMe скажем, объявленной внутри performRequest) в performRequest, которая будет держать сильную ссылку, по крайней мере, до следующего вызова performRequest.Если это действительно является проблемой, вам нужно найти хороший способ сохранить ссылку, которая соответствует вашему дизайну.

0 голосов
/ 22 марта 2012

Относительно вашего комментария выше.

SubFoo *theClass = [[SubFoo alloc] init];

Вы должны хранить theClass в

@property (strong)  SubFoo *mySubFoo;

Если вы объявите его таким образом:

{
    SubFoo *theClass = [[SubFoo alloc] init];
}

Получаетсявыпущен на закрывающей скобке.Эта часть точки ARC, когда эта переменная выходит из области видимости, освобождается.Если вы хотите, чтобы он плавал в эфире, вы можете использовать

{
    __weak SubFoo *theClass = [[SubFoo alloc] init];
}

, и он не будет освобожден, но это приведет к утечке памяти, если вы не будете тщательно управлять всеми слабыми ссылками.В случае, если он не был выпущен в -performRequest, я предполагаю, что запрос выглядит так:

{
    SubFoo *theClass = [[SubFoo alloc] init];
    [theClass performRequest];
}

wheras -webView: didFinishLoadForFrame: вызывается в какое-то неизбирательное время в будущем.

0 голосов
/ 19 марта 2012

Как упоминалось в CRD, я бы сказал, что возвращение неверного объекта / неверного доступа является признаком освобождения объекта. Иногда он заменяется другим объектом, иногда это не так, вы получаете исключение плохого доступа. Что касается того, как это могло произойти с self, я бы предположил, что это странный случай параллелизма (объект освобождается в другом потоке).

Лучший способ подтвердить это - запустить ваш код в шаблоне NSZombie для инструмента, он покажет вам, как только вы получите доступ к освобожденному объекту. Он также показывает, когда он был сохранен / выпущен, так что вам не нужно угадывать.

...