基于 ARC 的应用程序中过早释放

发布于 2025-01-02 20:13:44 字数 3691 浏览 4 评论 0原文

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

代码的问题是,

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

方法中的调试输出永远不会被调用。

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

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

    [ftpStream 打开];
    

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

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

     *** -[FTPUploads respondsToSelector:]:消息发送到已释放的实例 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;
    }
}

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

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

I have a problem that seems to be a premature release of an in-use object in an ARC based app. I'm trying to create a folder on an FTP server. The relevant parts of code are below; i'll describe the problem first.

problem with the code is, that the debug output in the

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

method is never called.

Instead, i just get an _EXC_BAD_ACCESS_ error.
While debugging i found out two things:

  1. the error only appears if the following line of code (createDir method) is executed:

    [ftpStream open];
    

if that message isn't sent, the rest of the code doesn't really make sense - but it doesn't crash either...

  1. I tracked the EXC_BAD_ACCESS down with NSZombieEnabled: With zombie objects enabled, the GDB produces the following debugger info:

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

The referred address 0x9166590 is the address of my FTPUploads object.
It looks like the streams delegate is deallocated before it can handle messages.

Why does the system deallocate an in-use object? How can i prevent it from being deallocated prematurely?

code:

FTPUploads.h excerpt:

#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 excerpt

#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: added creation code that is used in the class UploadDatasetVC:

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

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

羅雙樹 2025-01-09 20:13:44

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

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

FTPUploads dealloc 方法中将 ftpStream.delegate 属性设置为 nil 也不是一个坏主意,因为这样可以防止对象过早释放时发生崩溃。

It looks to me like the only reference to your FTPUploads object is the delegate property on the stream. This won't retain your object, so if nothing else has a reference to the object, the object will be dealloced. A.R.C. doesn't try to prevent this scenario.

What you need to do is have the code that allocates the FTPUploads object keep a reference to the object until it completes.

It also wouldn't be a bad idea to set the ftpStream.delegate property to nil in your FTPUploads dealloc method, as this will prevent a crash if the object is dealloced prematurely.

勿忘心安 2025-01-09 20:13:44

问题是您的 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() 的调用。

The problem is that your ftpStream object is being deallocated. You create it with CFWriteStreamCreateWithFTPURL(), then release it with CFRelease(). You used a __bridge cast, which basically means "don't do any memory management on this assignment". So ARC didn't retain it when you assigned it to ftpStream. Since your intention was to transfer ownership from CF to ARC, that was the wrong cast to use.

You actually wanted either __bridge_retained or __bridge_transfer. I can never remember which is which, though. Luckily, there's another option—the CFBridgingRetain() and CFBridgingRelease() macros. They resolve down to those same bridging casts, but are named far more clearly.

In this case, you want CF to release it, but bridge it over to ARC. So you want CFBridgingRelease(). That will tell ARC to take ownership of the object, and then do a CFRelease. In short, replace this:

ftpStream = (__bridge NSOutputStream *) writeStreamRef;

with this:

ftpStream = CFBridgingRelease(writeStreamRef);

And then remove the call to CFRelease() a few lines later.

我最亲爱的 2025-01-09 20:13:44

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

My guess is that you should either wait until the stream is finished to do CFRelease(writeStreamRef), or do a __bridge_transfer to transfer the ownership over to ftpStream before you release your writeStreamRef

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文