What is the new pattern for releasing self with automatic reference counting?
Asked Answered
P

3

7

Using the NSObject method -(id)awakeAfterUsingCoder:(NSCoder *)decoder as an example, the documentation says:

Allows an object, after being decoded, to substitute another object for itself. For example, an object that represents a font might, upon being decoded, release itself and return an existing object having the same font description as itself. In this way, redundant objects can be eliminated.

Normally you would do

[self release];
return substitutedObject;

With ARC you have to leave this line out. Wouldn't this leak? Or should I just trust the NSCoder object to release the original object for me? If so why would you have to explicitly release self with non-ARC code in the first place?

I don't think self = nil is correct in light of what the compiler documentation says about self: http://clang.llvm.org/docs/AutomaticReferenceCounting.html#misc.self

Phrase answered 22/4, 2012 at 2:46 Comment(0)
L
2

As noted, you cannot write [self release];. Additionally, awakeAfterUsingCoder: is not an initializer -- you may not reassign self.

Wouldn't this leak?

Yes. Proved in program below.

Or should I just trust the NSCoder object to release the original object for me?

No.

One approach to avoid the leak exists below -- I would not call it "the new pattern", just the first approach that came to mind. It involves an explicit release of self and in this case an explicit retain of the result:

#import <Foundation/Foundation.h>

@interface MONBoolean : NSObject < NSCoding >

- (id)initWithBool:(bool)pBool;

- (bool)isTrue;
- (bool)isFalse;

@end

static NSString * const MONBoolean_KEY_value = @"MONBoolean_KEY_value";

@implementation MONBoolean
{
    bool value;
}

- (id)initWithBool:(bool)pBool
{
    self = [super init];
    if (0 != self) {
        value = pBool;
    }
    return self;
}

- (bool)isTrue
{
    return true == value;
}

- (bool)isFalse
{
    return false == value;
}

- (NSString *)description
{
    return [[NSString alloc] initWithFormat:@"<%s:%p> : %s", object_getClassName(self), self, self.isTrue ? "true" : "false"];
}

- (void)encodeWithCoder:(NSCoder *)aCoder
{
    [aCoder encodeBool:value forKey:MONBoolean_KEY_value];
}

- (id)initWithCoder:(NSCoder *)aDecoder
{
    self = [super init];
    if (0 != self) {
        value = [aDecoder decodeBoolForKey:MONBoolean_KEY_value];
    }
    return self;
}

- (id)awakeAfterUsingCoder:(NSCoder *)aDecoder
{
    const bool b = value;
    // cannot reassign self outside of an initializer.
    // if not released, will result in a leak:
    CFRelease((__bridge const void*)self);
    MONBoolean * result = [[MONBoolean alloc] initWithBool:b];
    // now we have to retain explicitly because this is
    // an autoreleasing method:
    CFRetain((__bridge const void*)result);
    return result;
}

@end

int main(int argc, const char * argv[])
{
    @autoreleasepool {
        MONBoolean * a = [[MONBoolean alloc] initWithBool:true];
        NSData * data = [NSKeyedArchiver archivedDataWithRootObject:a];
        MONBoolean * b = [NSKeyedUnarchiver unarchiveObjectWithData:data];
        NSLog(@"%@", b);
    }
    system("leaks NAME_OF_PROCESS_HERE");
    return 0;
}
Libove answered 22/4, 2012 at 6:32 Comment(6)
Can you clarify why you have to explicitly retain result? Surely that defeats the purpose of ARC. Thanks for answering everything else.Phrase
@moshy you're welcome. yes - i initially overlooked that detail. the reason is that awakeAfterUsingCoder: returns a non-owning (or autoreleased) reference. as such, ARC inserts a ref-count decrement for us for our return value. what we want to do is effectively transfer the reference from one object to another. i ran it in instruments -- no leaks. no zombies. without the explicit retain, a zombie would be messaged. without the explicit release - a leak. try it for yourself (place the @autorelease block in a while (1)).Libove
Could you make the method a member of the init family with __attribute__((objc_method_family(init))) and then reassign self?Attenuate
@HeathBorders potentially (try it). what the compiler uses to determine whether it is or is not valid to reassign self would need to acknowledge the attribute. generally, attributes should not be used in this context (imo), but it would have been good if apple had not allowed this bug to slip through. your solution would be one approach they could take.Libove
I implemented Yang's solution for embedded Nib replacement (with ARC) as described, but was getting memory access errors on the old 'self' when manual release was used. I see the following in NSObject.h, which mkes me suspect that object replacement is now handled correctly by awakeAfterUsingCoder: without a manual release and retain: - (id)awakeAfterUsingCoder:(NSCoder *)aDecoder NS_REPLACES_RECEIVER;Jdavie
@Jdavie yup - ns_consumes_self should fix it +1. just be careful because that attribute has not been specified for all SDKs shipped with Xcode (today), so some projects (including those which use earlier releases of Xcode) may still need to perform the extra footwork and testing seen in the answer. glad they have fixed it, though :)Libove
L
4

A similar issue arises in the context of NIB top-level objects on Mac OS X. The Resource Programming Guide says:

If the File’s Owner is not an instance of NSWindowController or NSViewController, then you need to decrement the reference count of the top level objects yourself. With manual reference counting, it was possible to achieve this by sending top-level objects a release message. You cannot do this with ARC. Instead, you cast references to top-level objects to a Core Foundation type and use CFRelease.

So, that technique can presumably be used in this situation, too. CFRelease((__bridge CFTypeRef)self);

Liz answered 22/4, 2012 at 14:19 Comment(0)
L
2

As noted, you cannot write [self release];. Additionally, awakeAfterUsingCoder: is not an initializer -- you may not reassign self.

Wouldn't this leak?

Yes. Proved in program below.

Or should I just trust the NSCoder object to release the original object for me?

No.

One approach to avoid the leak exists below -- I would not call it "the new pattern", just the first approach that came to mind. It involves an explicit release of self and in this case an explicit retain of the result:

#import <Foundation/Foundation.h>

@interface MONBoolean : NSObject < NSCoding >

- (id)initWithBool:(bool)pBool;

- (bool)isTrue;
- (bool)isFalse;

@end

static NSString * const MONBoolean_KEY_value = @"MONBoolean_KEY_value";

@implementation MONBoolean
{
    bool value;
}

- (id)initWithBool:(bool)pBool
{
    self = [super init];
    if (0 != self) {
        value = pBool;
    }
    return self;
}

- (bool)isTrue
{
    return true == value;
}

- (bool)isFalse
{
    return false == value;
}

- (NSString *)description
{
    return [[NSString alloc] initWithFormat:@"<%s:%p> : %s", object_getClassName(self), self, self.isTrue ? "true" : "false"];
}

- (void)encodeWithCoder:(NSCoder *)aCoder
{
    [aCoder encodeBool:value forKey:MONBoolean_KEY_value];
}

- (id)initWithCoder:(NSCoder *)aDecoder
{
    self = [super init];
    if (0 != self) {
        value = [aDecoder decodeBoolForKey:MONBoolean_KEY_value];
    }
    return self;
}

- (id)awakeAfterUsingCoder:(NSCoder *)aDecoder
{
    const bool b = value;
    // cannot reassign self outside of an initializer.
    // if not released, will result in a leak:
    CFRelease((__bridge const void*)self);
    MONBoolean * result = [[MONBoolean alloc] initWithBool:b];
    // now we have to retain explicitly because this is
    // an autoreleasing method:
    CFRetain((__bridge const void*)result);
    return result;
}

@end

int main(int argc, const char * argv[])
{
    @autoreleasepool {
        MONBoolean * a = [[MONBoolean alloc] initWithBool:true];
        NSData * data = [NSKeyedArchiver archivedDataWithRootObject:a];
        MONBoolean * b = [NSKeyedUnarchiver unarchiveObjectWithData:data];
        NSLog(@"%@", b);
    }
    system("leaks NAME_OF_PROCESS_HERE");
    return 0;
}
Libove answered 22/4, 2012 at 6:32 Comment(6)
Can you clarify why you have to explicitly retain result? Surely that defeats the purpose of ARC. Thanks for answering everything else.Phrase
@moshy you're welcome. yes - i initially overlooked that detail. the reason is that awakeAfterUsingCoder: returns a non-owning (or autoreleased) reference. as such, ARC inserts a ref-count decrement for us for our return value. what we want to do is effectively transfer the reference from one object to another. i ran it in instruments -- no leaks. no zombies. without the explicit retain, a zombie would be messaged. without the explicit release - a leak. try it for yourself (place the @autorelease block in a while (1)).Libove
Could you make the method a member of the init family with __attribute__((objc_method_family(init))) and then reassign self?Attenuate
@HeathBorders potentially (try it). what the compiler uses to determine whether it is or is not valid to reassign self would need to acknowledge the attribute. generally, attributes should not be used in this context (imo), but it would have been good if apple had not allowed this bug to slip through. your solution would be one approach they could take.Libove
I implemented Yang's solution for embedded Nib replacement (with ARC) as described, but was getting memory access errors on the old 'self' when manual release was used. I see the following in NSObject.h, which mkes me suspect that object replacement is now handled correctly by awakeAfterUsingCoder: without a manual release and retain: - (id)awakeAfterUsingCoder:(NSCoder *)aDecoder NS_REPLACES_RECEIVER;Jdavie
@Jdavie yup - ns_consumes_self should fix it +1. just be careful because that attribute has not been specified for all SDKs shipped with Xcode (today), so some projects (including those which use earlier releases of Xcode) may still need to perform the extra footwork and testing seen in the answer. glad they have fixed it, though :)Libove
G
0

I believe ARC is smart enough to keep track of all objects. So you should be able to just no say anything about memory and the app will release the object when it's no longer in use. Run it through the leaks profiler, just in case, but it should be okay.

Gipps answered 22/4, 2012 at 2:56 Comment(1)
But my understanding was that automatic reference counting is still just reference counting. It is not the same as garbage collection. Therefore it is still (theoretically) possible to have leaking objects when they're no longer in use. I'll run it through the profiler.Phrase

© 2022 - 2024 — McMap. All rights reserved.