我有一个非常非常奇怪的错误,可能与内存管理有关(即使我正在使用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 delegate]是一个无法识别的选择器。
编辑:当self被设置为另一个东西时,它在webView上设置为右:didFinishLoadForFrame:,并且在performRequest上它总是SubFoo。
任何帮助都将不胜感激。
答案 0 :(得分:3)
首先,即使您在项目中使用ARC归零弱引用(@property (weak)
),其他项目和框架可能也不会(并且可能不是)使用归零弱引用。
换句话说,假设框架中的所有代理都是__unsafe_unretained
,除非:
weak
那就是说,让我们谈谈你的例子。您的对象所有权图表如下所示:
(注意:我不完全确定你的项目中哪个类使用SubFoo。基于通常的做法,我假设你有一个强烈引用SubFoo的类,并且该类也被设置为一个SubFooDelegate)
最终,您的SubFoo实例正在失去其最后一个强引用并且正在取消分配。在一个完美的启用ARC的世界中,WebView指向SubFoo的指针此时将无法使用。但是,它还不是一个完美的世界,WebView的frameLoadDelegate是__unsafe_unretained
。由于运行循环交互,WebView的寿命超过了SubFoo。 Web请求完成,并且取消引用死指针。
要解决此问题,您需要在SubFoo的dealloc方法中调用[aWebView setFrameLoadDelegate:nil];
。您还需要在重新分配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 :(得分:1)
暂时忘记self.delegate
错误,如果[self class]
产生了错误的结果,那就是红色鲱鱼!你的结果表明你在某种程度上是在瞎扯self
。
webView:didFinishLoadForFrame:
上的断点检查self
值并逐步完成。
评论跟进
对于实例方法的第一个语句中的自我错误,让我们说,不寻常(但并非不可能)。
当一个对象被设置为另一个委托时,确保委托对象的生命周期至少与它作为委托者的生命周期一样长,这一点很重要。引入ARC可以使以前工作的代码失败,因为它可能比MRC下的代码更早地释放代理。发生这种情况时,对代理人的调用通常会失败。
但是,对代理人的调用,您的错误不会失败;通话开始 - 您最终进入webView:didFinishLoadForFrame:
- 然后您发现self
无效。要实际调用实例方法通常需要self
的有效值,因为它用于确定要调用的方法实现。因此,self
通常在方法开始时有效!
但请注意“通常”......
因此,尽管您已成功覆盖了您的方法,但您的错误可能是由于没有对SubFoo
实例的强引用,您将其作为代理传递给aWebView
,并且时间{ {1}}被称为webView:didFinishLoadForFrame:
已经消失。
确保您对SubFoo
保持强烈反对。如果您只是想测试(这是不推荐的通用解决方案!)如果这是您的问题,您可以将其分配给本地静态(SubFoo instance
声明在{{1}内声明在static SubFoo *holdMe
中,它将至少在下次调用performRequest
之前保留一个强引用。如果这确实是问题所在,那么您需要找到一种很好的方法来维护适合您设计的参考。
答案 2 :(得分:1)
这是真正的问题:您正在方法的上下文中创建SubFoo对象。因此,在方法完成后,SubFoo正在发布(在WebView有时间加载之前)。
要解决此问题,您需要将正在创建的SubFoo对象分配给持久性对象,例如您正在创建它的类的实例变量。这样,对象将持续超出其创建方法的范围,并且所有对象都将按预期工作。
答案 3 :(得分:0)
正如CRD所提到的,我会说一个不正确的对象/错误的访问返回是一个对象被释放的标志。有时候它会被另一个对象取代,有时它并不是因为你得到了糟糕的访问异常。关于如何在self
发生这种情况,我会想到这是一个并发奇怪的情况(对象在另一个线程上被释放)。
确认这一点的最佳方法是在Instrument的NSZombie模板中运行您的代码,它会在您访问一个释放的对象时立即显示。它还显示何时保留/释放,因此您无需猜测。
答案 4 :(得分:0)
关于您的上述评论。
SubFoo *theClass = [[SubFoo alloc] init];
您必须将该类存储在
中@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:将来会在一些不分青红皂白的时间被调用。