我有一个问题似乎是在基于ARC的应用程序中过早发布正在使用的对象。我正在尝试在FTP服务器上创建一个文件夹。代码的相关部分如下;我将首先描述问题。
代码的问题是,
中的调试输出- (void)stream:(NSStream *)aStream handleEvent:(NSStreamEvent)eventCode
永远不会调用方法。
相反,我只是得到_EXC_BAD_ACCESS_错误。 调试时我发现了两件事:
只有在执行以下代码行(createDir方法)时才会出现错误:
[ftpStream open];
如果没有发送该消息,其余的代码实际上没有意义 - 但它也不会崩溃......
我使用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;
}
}
编辑:添加了在类UploadDatasetVC中使用的创建代码:
FTPUploads *uploads = [[FTPUploads alloc] initWithTimeseriesID: timeseries_id
fromDatasetDir: datasetDir
withFiles: files
andCallbackObject: self];
[uploads createDir];
答案 0 :(得分:3)
在我看来,对FTPUploads
对象的唯一引用是流上的delegate
属性。这不会保留您的对象,因此如果没有其他内容对该对象有引用,则该对象将被释放。弧。并不试图阻止这种情况。
您需要做的是让分配FTPUploads
对象的代码保持对对象的引用,直到它完成。
在ftpStream.delegate
dealloc方法中将FTPUploads
属性设置为nil也不是一个坏主意,因为如果对象被过早释放,这将防止崩溃。
答案 1 :(得分:1)
问题是您的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()
的调用。
答案 2 :(得分:0)
我的猜测是,您应该等到流完成后才能CFRelease(writeStreamRef)
,或者在发布ftpStream
之前执行__bridge_transfer将所有权转移到writeStreamRef