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:
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...
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];