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];
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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With