【问题标题】:premature dealloc in ARC based app基于 ARC 的应用程序中的过早释放
【发布时间】:2012-02-05 14:59:09
【问题描述】:

我有一个问题,似乎是在基于 ARC 的应用程序中过早释放正在使用的对象。我正在尝试在 FTP 服务器上创建一个文件夹。代码的相关部分如下;我先描述一下问题。

代码的问题是,在

中的调试输出
- (void)stream:(NSStream *)aStream handleEvent:(NSStreamEvent)eventCode

永远不会调用方法。

相反,我只是收到一个 _EXC_BAD_ACCESS_ 错误。 在调试时我发现了两件事:

  1. 只有在执行以下代码行(createDir 方法)时才会出现错误:

    [ftpStream open];
    

如果未发送该消息,则其余代码实际上没有意义 - 但它也不会崩溃...

  1. 我使用 NSZombieEnabled 跟踪 EXC_BAD_ACCESS:启用僵尸对象后,GDB 会生成以下调试器信息:

     *** -[FTPUploads respondsToSelector:]: message sent to deallocated instance 0x9166590
    

引用地址 0x9166590 是我的 FTPUploads 对象的地址。 看起来流委托在它可以处理消息之前就被释放了。

为什么系统会释放一个正在使用的对象?我怎样才能防止它被过早地释放?

代码:

FTPUploads.h 摘录:

#import <Foundation/Foundation.h>

enum UploadMode {

    UploadModeCreateDir, 
    UploadModeUploadeData
};

@class UploadDatasetVC;

@interface FTPUploads : NSObject<NSStreamDelegate> {

    @private
    NSString *uploadDir;
    NSString *ftpUser;
    NSString *ftpPass;

    NSString *datasetDir;
    NSArray *files;

    /* FTP Upload fields */
    NSInputStream *fileStream;
    NSOutputStream *ftpStream;
    // some more fields...
    enum UploadMode uploadMode;
    UploadDatasetVC *callback;
}

- (id) initWithTimeseriesID: (int) aTimeseriesID 
             fromDatasetDir: (NSString *) aDir
                  withFiles: (NSArray *) filesArg 
          andCallbackObject: (UploadDatasetVC *) aCallback;

- (void) createDir;

@end

FTPUploads.m 摘录

#import "FTPUploads.h"
#import "UploadDatasetVC"

@implementation FTPUploads

- (id) initWithTimeseriesID: (int) aTimeseriesID 
             fromDatasetDir: (NSString *) aDir
                  withFiles: (NSArray *) filesArg 
          andCallbackObject: (UploadDatasetVC *) aCallback {

    self = [super init];

    if (self) {

        uploadDir = [NSString stringWithFormat: @"ftp://aServer.org/%i/", aTimeseriesID];
        ftpUser = @"aUser";
        ftpPass = @"aPass";

            datasetDir = aDir;
            files = filesArg;

        bufferOffset = 0;
        bufferLimit = 0;

        index = 0;

        callback = aCallback;
    }

    return self;
}

- (void) createDir {

    uploadMode = UploadModeCreateDir;
    NSURL *destinationDirURL = [NSURL URLWithString: uploadDir];

    CFWriteStreamRef writeStreamRef = CFWriteStreamCreateWithFTPURL(NULL, (__bridge CFURLRef) destinationDirURL);
    assert(writeStreamRef != NULL);

    ftpStream = (__bridge_transfer NSOutputStream *) writeStreamRef;
    [ftpStream setProperty: ftpUser forKey: (id)kCFStreamPropertyFTPUserName];
    [ftpStream setProperty: ftpPass forKey: (id)kCFStreamPropertyFTPPassword];

    ftpStream.delegate = self;
    [ftpStream scheduleInRunLoop: [NSRunLoop currentRunLoop] forMode: NSDefaultRunLoopMode];
    // open stream
    [ftpStream open];

    CFRelease(writeStreamRef);
}

- (void)stream:(NSStream *)aStream handleEvent:(NSStreamEvent)eventCode {

    NSLog(@"aStream has an event: %i", eventCode);

    switch (eventCode) {
        // all cases handled properly
        default:
            // no event
            NSLog(@"default mode; no event");
            break;
    }
}

EDIT:添加了在UploadDatasetVC类中使用的创建代码:

FTPUploads *uploads = [[FTPUploads alloc] initWithTimeseriesID: timeseries_id 
                                                fromDatasetDir: datasetDir 
                                                     withFiles: files 
                                             andCallbackObject: self];
[uploads createDir];

【问题讨论】:

  • 对象通常不保留(或者在 arc 的情况下,保留对其委托的强引用)。在我看来,问题在于创建您的 FtpUploads 对象的代码以及它随后对它的作用
  • 我将该代码添加到问题的另一个 sn-p 中。另请参阅我对第一个答案的评论。
  • 然后会发生什么?如果上传只是一个局部变量,那么当它超出范围时,ARC 会删除它
  • 是的,你完全正确;请参阅下面的@grahamparks 答案,了解我为解决此问题所做的工作。使它成为一个类变量是不够的,所以我为上传创建了一个属性。

标签: objective-c automatic-ref-counting dealloc


【解决方案1】:

在我看来,对 FTPUploads 对象的唯一引用是流上的 delegate 属性。这不会保留您的对象,因此如果没有其他对象引用该对象,则该对象将被释放。弧。不会试图阻止这种情况。

您需要做的是让分配FTPUploads 对象的代码保持对该对象的引用,直到它完成。

在您的 FTPUploads dealloc 方法中将 ftpStream.delegate 属性设置为 nil 也不是一个坏主意,因为如果对象被过早释放,这将防止崩溃。

【讨论】:

  • 没有;创建 FTPUploads 对象的调用代码包含对该对象的第一个引用?在我将“self”设置为委托的“createDir”方法中 - 好的。但是第一个引用是在 UploadDatasetVC 中创建的,我在其中创建了 hte FTPUploads 对象:'FTPUploads *uploads = [[FTPUploads alloc] initWithTimeseriesID: timeseries_id fromDatasetDir: datasetDir withFiles: files andCallbackObject: self]; [上传 createDir];'
  • 大多数委托属性设置为“弱”或“分配”,这意味着它们不计入保留计数。您的 uploads 变量只有在连接持续存在的情况下才会计算在内,我猜它不会。
  • 好的,一种解决方法是将使用的 FTPUploads 对象保存为具有强引用的属性。这对我最有效;将以下行添加到 UploadDataset.h:'@property (strong) FTPUploads *uploads;'并相应地调整 UploadDataset.h 中的对象创建
【解决方案2】:

问题是您的ftpStream 对象正在被释放。你用CFWriteStreamCreateWithFTPURL() 创建它,然后用CFRelease() 释放它。您使用了__bridge 演员表,这基本上意味着“不要对此分配进行任何内存管理”。因此,当您将其分配给ftpStream 时,ARC 并没有保留它。由于您的意图是将所有权从 CF 转移到 ARC,所以使用错误的演员表。

您实际上想要__bridge_retained__bridge_transfer。不过,我永远不记得哪个是哪个。幸运的是,还有另一个选项 - CFBridgingRetain()CFBridgingRelease() 宏。它们分解为相同的桥接演员,但名称要清楚得多。

在这种情况下,您希望 CF 释放它,但将其桥接到 ARC。所以你想要CFBridgingRelease()。这将告诉 ARC 获取对象的所有权,然后执行 CFRelease。简而言之,替换这个:

ftpStream = (__bridge NSOutputStream *) writeStreamRef;

用这个:

ftpStream = CFBridgingRelease(writeStreamRef);

然后在几行之后删除对CFRelease() 的调用。

【讨论】:

  • 错误。正如我原来的问题所述,问题是被释放的 FTPUploads 对象 - 而不是 ftpStream!这只是我的代码中的另一个错误,但不是原始问题。谢谢你的提示
  • 哦,真的。好吧,在你修复它之后(我看到你已经完成了),这将是你的下一个问题。
【解决方案3】:

我的猜测是,您应该等到流完成后再执行 CFRelease(writeStreamRef),或者在释放 writeStreamRef 之前执行 __bridge_transfer 将所有权转移到 ftpStream

【讨论】:

  • 猜错了。原因是错误地将 FTPUploads 声明为仅作为局部变量(请参阅原始问题中的 UploadDatasetVC 创建代码)。对于我错误的“__bridge_transfer”,您仍然是对的,这将被证明是一个错误。我刚刚修好了,谢谢!
猜你喜欢
  • 2012-03-12
  • 2015-08-01
  • 1970-01-01
  • 1970-01-01
  • 2013-08-27
  • 1970-01-01
  • 2012-03-18
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多