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.
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
Related
I need some clarifications on a crash I'm encountering using NSArray, blocks and Manual Reference Counting. My goal is to store blocks on a collection (NSArray in this case) in order to reuse them in the future.
I've setup a small sample to replicate the issue. In particular, I have a class Item that looks like the following:
#import <Foundation/Foundation.h>
typedef void(^MyBlock)();
#interface Item : NSObject
- (instancetype)initWithBlocks:(NSArray*)blocks;
#end
#import "Item.h"
#interface Item ()
#property (nonatomic, strong) NSArray *blocks;
#end
#implementation Item
- (instancetype)initWithBlocks:(NSArray*)blocks
{
self = [super init];
if (self) {
NSMutableArray *temp = [NSMutableArray array];
for (MyBlock block in blocks) {
[temp addObject:[[block copy] autorelease]];
}
_blocks = [temp copy];
}
return self;
}
The usage is described below (I'm using in the app delegate).
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
__block typeof(self) weakSelf = self;
MyBlock myBlock1 = ^() {
[weakSelf doSomething1];
};
MyBlock myBlock2 = ^() {
[weakSelf doSomething1];
};
NSArray *blocks = #[myBlock1, myBlock2];
// As MartinR suggested the code crashes even
// if the following line is commented
Item *item = [[Item alloc] initWithBlocks:blocks];
}
If I run the app, it crashes with an EXC_BAD_INSTRUCTION (note that I've already enabled All Exceptions breakpoints). In particular, the app stops in the main.
int main(int argc, const char * argv[]) {
return NSApplicationMain(argc, argv);
}
Note: As suggested by Ken Thomases, if you use bt command on llvm console, you are to see the back trace. In this case it shows the following:
-[__NSArrayI dealloc]
If I comment the [weakSelf doSomethingX]; it works without crashes (it does not mean that is correct).
Modifying the code a little bit like the following, all runs ok.
// Item does not do anymore the copy/autorelease dance
// since used in the declaration of the blocks
- (instancetype)initWithBlocks:(NSArray*)blocks
{
self = [super init];
if (self) {
_blocks = [blocks retain];
}
return self;
}
and
__block typeof(self) weakSelf = self;
MyBlock myBlock1 = [[^() {
[weakSelf doSomething1];
} copy] autorelease];
MyBlock myBlock2 = [[^() {
[weakSelf doSomething1];
} copy] autorelease];
NSArray *blocks = #[myBlock1, myBlock2];
Item *item = [[Item alloc] initWithBlocks:blocks];
What is the point here? I think I'm missing something but I don't know what.
Update 1
Ok. I'll try to recap my thoughts based on the comments with #Martin R and #Ken Thomases.
A block, by default, is created on stack if a copy message is not sent to it (ARC does this for us) in order to move it on the heap. So, the situation in this case is the following. I create an autorelease array and I add two blocks where retain is called in a implicit manner. When the applicationDidFinishLaunching method finishes is execution, the blocks, since created on the stack (they are automatic variables) disappear. In a later moment, the array called blocks will be released since has been marked as autorelease. So, it will crash since it will send a release object to blocks that do not exist anymore.
So, my question is the following: What does it mean to send a retain message to a block that is on the stack? Why the array is the source of the crash (see the back trace)? In other words, since a block is on the stack, will it bump the retain count of it? And when it goes out of scope? In addiction, why if I comment the [weakSelf doSomething1] line the code works without problems? Not very clear to me this part.
You are sticking an object from the stack into an autoreleased array. BOOM ensues.
Consider:
typedef void(^MyBlock)();
int main(int argc, char *argv[]) {
#autoreleasepool {
NSObject *o = [NSObject new];
MyBlock myBlock1 = ^() {
[o doSomething1];
};
NSLog(#"o %p", o);
NSLog(#"b %p", myBlock1);
NSLog(#"b retain %p", [myBlock1 retain]);
NSLog(#"b copy %p", [myBlock1 copy]);
NSLog(#"s %p", ^{});
sleep(1000000);
}
}
Compiled/run as -i386 (because the #s are smaller and more obvious):
a.out[11729:555819] o 0x7b6510f0
a.out[11729:555819] b 0xbff2dc30
a.out[11729:555819] b retain 0xbff2dc30
a.out[11729:555819] b copy 0x7b6511a0
a.out[11748:572916] s 0x67048
Since the object is at 0x7b, we can assume that is the heap. 0xb is really high memory and, thus, the stack.
The retain doesn't cause a copy (because doing so would have invariably led to leaks) and retain on a stack based object is meaningless.
If you change the [o doSomething1]; to [nil doSomething1]; then that becomes a static block and that lives in readonly mapped memory (readonly-executable pages from the mach-o's TEXT segment) and, thus, there is no allocation to deallocate and retain/release/autorelease are no-ops.
As you can see, the static block ended up around 0x67048 (this number may change from run to run, btw, for a variety of reasons. Low in memory.
In fact, because of the sleep(), we can run vmmap against the a.out process and see:
==== Writable regions for process 11772
REGION TYPE START - END [ VSIZE] PRT/MAX SHRMOD REGION DETAIL
__DATA 00067000-00068000 [ 4K] rw-/rwx SM=ZER /tmp/a.out
That is, the static block was in the first 4K segment of mapped writable regions from the mach-o file. Note that this doesn't mean the code is in that writable region (SECURITY HOLE if it were). The code is in the TEXT segment mapped into the readable regions.
Before ARC I had the following code that retains the delegate while an async operation is in progress:
- (void)startAsyncWork
{
[_delegate retain];
// calls executeAsyncWork asynchronously
}
- (void)executeAsyncWork
{
// when finished, calls stopAsyncWork
}
- (void)stopAsyncWork
{
[_delegate release];
}
What is the equivalent to this pattern with ARC?
I have occasionally needed to manually retain and release things (sometimes just for debugging) and came up with the following macros:
#define AntiARCRetain(...) void *retainedThing = (__bridge_retained void *)__VA_ARGS__; retainedThing = retainedThing
#define AntiARCRelease(...) void *retainedThing = (__bridge void *) __VA_ARGS__; id unretainedThing = (__bridge_transfer id)retainedThing; unretainedThing = nil
This works by using the __bridge_retained and __bridge_transfer to cast things to and from (void *) which causes things to be retained, or to create a strong reference without calling retain.
Have fun, but be careful!
Why not just assign your delegate object to a strong ivar for the duration of the asynchronous task?
Or have a local variable in executeAsyncWork
- (void)executeAsyncWork
{
id localCopy = _delegate;
if (localCopy != nil) // since this method is async, the delegate might have gone
{
// do work on local copy
}
}
Something like this:
- (void)startAsyncWork
{
id<YourProtocol> delegate = _delegate;
dispatch_async(/* some queue */, ^{
// do work
[delegate doSomething];
}
}
The block will retain the delegate as long as needed...
I am coding Objective-C using the Cocos2D framework, and I have a singleton used for multiple purposes. One new purposes is to get and set character's "states" which are strings. I've recently made an NSDictionary for this purpose, but I have issues with the program freezing up when a method inside the singleton is called.
Here's the singleton code. I'm just leaving in the character state stuff:
.h
#interface ExGlobal : NSObject {
NSArray *charStates_keys;
NSArray *charStates_objects;
NSMutableDictionary *charStates;
}
#property(nonatomic, retain) NSMutableDictionary *charStates;
+(ExGlobal*)sharedSingleton;
- (NSString *)charState:(NSString *)charName;
- (void)set_charState:(NSString *)value forCharName:(NSString *)charName;
#end
.m
#import "ExGlobal.h"
#implementation ExGlobal
#synthesize charStates;
static ExGlobal* _sharedSingleton = nil;
+(ExGlobal*)sharedSingleton {
#synchronized([ExGlobal class]) {
if (!_sharedSingleton) {
[[self alloc] init];
}
return _sharedSingleton;
}
return nil;
}
+(id)alloc {
#synchronized([ExGlobal class]) {
NSAssert(_sharedSingleton == nil, #"Attempted to allocate a second instance of a singleton.");
_sharedSingleton = [super alloc];
return _sharedSingleton;
}
return nil;
}
-(id)init {
self = [super init];
if (self != nil) {
// initialize stuff here
exitName = #"ruinsSkyMid";
sceneChangeKind = #"reborn";
charStates = [[NSMutableDictionary alloc] init];
charStates_keys = [NSArray arrayWithObjects:#"Feathers", #"Hummus", nil];
charStates_objects = [NSArray arrayWithObjects:#"at wall", #"with Feathers", nil];
charStates = [NSMutableDictionary dictionaryWithObjects:charStates_objects forKeys:charStates_keys];
}
return self;
}
- (NSString *)charState:(NSString *)charName{
NSString *value = [charStates objectForKey:charName];
return value;
}
- (void)set_charState:(NSString *)charState forCharName:(NSString *)charName{
[charStates setObject:charState forKey:charName];
}
- (void)dealloc {
//I know it doesn't get called, but just in case
[charStates release];
[super dealloc];
}
#end
It's unclear to me what exactly the issue is when it freezes. When this happens, all I get in the console is:
Program received signal: “EXC_BAD_ACCESS”.
warning: Unable to read symbols for /Developer/Platforms/iPhoneOS.platform/DeviceSupport/4.3.5 (8L1)/Symbols/Developer/usr/lib/libXcodeDebuggerSupport.dylib (file not found).
Previous frame inner to this frame (gdb could not unwind past this frame)
Previous frame inner to this frame (gdb could not unwind past this frame)
Which I'm sure doesn't help finding the issue. I found if I redefine charStates_keys, charStates_objects and charStates inside both the charState and set_charState methods, it seems to work without freezing, except set_charState does not change the state.
It isn't freezing, it is crashing. Hence the EXC_BAD_ACCESS. It looks like your Xcode installation is borked, too, as the two messages following should not happen.
Note that methods should not have _s in the name; not a cause of the problem, but a comment on following convention.
You aren't retaining charStates and that is likely the cause of the crash.
Not an answer as such but I didn't have enough space in the comments field above to post this, but it might be useful.
As bbum already said, your lack of retaining charStates is likely the problem.
If you are confused about when to retain and not retain objects there's a really good book called "Learn Objective-C on the Mac" and I know it's a Mac book but most of it applies to iPhone too. On page 171 of chapter 9 (Memory Management) it talks about the "Memory Management Rules" and how if you are confused about when to retain or not then you don't understand the simple rules of Objective C memory management.
Essentially if you create an object using new, alloc or copy, then the retain count is automatically set to 1 so the object is retained and does not require you to retain it and will require a subsequent release to deallocate.
If you create the object any other way then the object will be an autoreleased object.
Obviously these rules only apply within the standard iOS libraries and can't necessarily be applied to third party libraries.
I recommend anyone who doesn't fully understand memory management in Objective C read this book. I found highly enlightening even for my iPhone work.
Hope that helps/.
As a relative Objective-C beginner, I'm obviously still not grasping certain memory management rules. I can't figure out how to make this not crash:
#interface MyClass { NSArray *playerArray4th; }
- (void) viewDidLoad { playerArray4th = [self getAudioPlayersForSoundFile:#"rimshot" ofType:#"aif"]; }
- (NSArray*) getAudioPlayersForSoundFile:(NSString*)soundFileName ofType:(NSString*)soundFileType {
//code instantiating objects....
NSArray *toRet = [[NSArray alloc] initWithObjects:toRetTickPlayer,toRetTickPlayerCopy,toRetTickPlayerCopy2,toRetTickPlayerCopy3, nil];
return toRet;
}
Then later, in a different function:
NSArray *currentArray = playerArray4th;
[currentArray release];
currentArray = nil;
currentArray = [self getAudioPlayersForSoundFile:fileName ofType:ofType];
And it crashes when trying to access the array again:
- (void) playSound:(NSString*)soundType {
AVAudioPlayer *currentPlayer;
if ([soundType isEqualToString:#"4th"]) {
if (playerArray4thCounter >= [playerArray4th count]) playerArray4thCounter = 0;
NSLog(#"Playing from array: %#",playerArray4th);
currentPlayer = [playerArray4th objectAtIndex:playerArray4thCounter];
playerArray4thCounter++;
}
}
Try to learn about properties and about using getters and setters. Don't take shortcuts unless you know exactly what's going on.
So define the playerArray4th property in your header file:
#property (nonatomic,retain) NSArray *playerArray4th;
And then in your .m file create getter/setter:
#synthesize playerArray4th;
Then, always use self.playerArray4th for assigning and getting the variable. The prior objects will be released when needed.
So this will not leak:
self.playerArray4th = [NSArray arrayWithObjects:#"text",#"text",nil];
self.playerArray4th = [NSArray arrayWithObjects:#"new array",#"text",nil];
because the second assignment releases the first array.
Furthermore, read about using autorelease. In short, if you alloc, copy or new, you should either release or autorelease. There's a lot to read about this here on SO which I will not repeat here now.
Don't forget to put self.playerArray4th = nil; in your dealloc method.
this program crashes if i uncomment the release statements. i get that i am overreleasing and realized that quickly. but, just to test zombies, i turned them on (NSZombiesEnabled = YES and CFZombieLevel = 16) and the program runs fine and throws no exceptions.
what gives? i thought turning on zombies would have just told me what a doofus i am...not fix it.
#import "AppController.h"
#implementation AppController
-(IBAction)countCharacters:(id)sender {
//did a button do this?
if(![sender isKindOfClass:[NSButton class]]) {
NSLog(#"%# is not a button", sender);
return;
}
//proceed
NSString *userString = [textField stringValue];
NSNumber *count = [NSNumber numberWithInt:[userString length]];
NSString *outputString = [NSString stringWithFormat:#"'%#' has %# characters.",
userString, count];
//[userString release];
//[count release];
[labelField setStringValue:outputString];
//[outputString release];
}
#end
It's because you do not own the objects you try to release (you don't hold a reference to them). Their ownership is given to the "nearest" NSAutoreleasePool.
You can read about object ownership here. As a quick reference, usually, you aren't the owner if you didn't call the alloc method yourself to create the object or if you didn't retain it. Retaining an object makes you an owner; calling release means you give up ownership (and will deallocate the object if it has no more owners).
You must not release objects for which you don't have ownership. Your current code without release is exactly what you need.
Well, zombies will tell you when a free'd object receives a release correct? So, if your not sending the release (you commented it out) you won't see the zombies complaining?
Your NSString/NSNumber methods are all convenience methods, and you don't have to release them. So -yeah, You solved the problem yourself.