Search code examples
objective-cautomatic-ref-countingobjective-c-runtime

Objective-C ARC Strong and Weak references "object will be released after assignment" warning - but it doesn't get released


I'm doing this simple Objective-C exercise to understand ARC better, it's a program which should display R2D2 when the references are set to Strong, and fail when one is set to Weak. However, the code still compiles, even if there is a warning that says the object will be released.

main.m

#import <Foundation/Foundation.h>
#import "Robot.h"

int main(int argc, const char * argv[])
{
    @autoreleasepool {

        Robot *robot = [[Robot alloc]init];

        robot.firstString =[[NSString alloc]initWithFormat:@"R%d",2];
        robot.secondString = [[NSString alloc]initWithFormat:@"D%d",2];

        NSLog(@"%@%@",robot.firstString,robot.secondString);

    }
    return 0;
}

Robot.m

#import "Robot.h"

@implementation Robot

@end

Robot.h

#import <Foundation/Foundation.h>

@interface Robot : NSObject

@property (strong) NSString *firstString;
@property (weak) NSString *secondString;

@end

Thank you


Solution

  • Oh, this is a fun one. You're running into a tagged pointer.

    @interface Robot : NSObject
    
    @property (strong) NSString *firstString;
    @property (weak) NSString *secondString;
    
    @end
    
    @implementation Robot
    @end
    
    int main(int argc, const char * argv[])
    {
        @autoreleasepool {
    
            Robot *robot = [[Robot alloc]init];
    
            robot.firstString =[[NSString alloc]initWithFormat:@"R%d",2];
            robot.secondString = [[NSString alloc]initWithFormat:@"D%d",2];
    
            NSLog(@"%@ %@",robot.firstString,robot.secondString);
            NSLog(@"%p %p",robot.firstString,robot.secondString);
    
            robot.secondString = [[NSString alloc]initWithFormat:@"argy bargy foo foo %d",2];
    
            NSLog(@"%@ %@",robot.firstString,robot.secondString);
            NSLog(@"%p %p",robot.firstString,robot.secondString);
    
        }
        return 0;
    }
    

    The output:

     R2 D2
     0x325225 0x324425
     R2 (null)
     0x325225 0x0
    

    Note that your weak string has an odd address. But allocations cannot fall on odd addresses! In fact, allocations on the heap are generally aligned by 16 bytes (though statically allocated special case strings may be even address only).

    That odd address means there is a 1 in bit 0. That indicates a tagged pointer is in use. That is, the string is encoded in the address of the object and the runtime detects that the low order bit is set and, thus, special cases it to a tagged pointer.

    In fact, if you look up 0x32 and 0x44 in an ASCII table, the former is the character 2 and the latter is the character D. The 0x25 is the length of the string (2) and the class index (+ the low bit).

    So, you never see the weak reference go to zero, because there was no allocation involved. Without an allocation, there can never be a deallocation and, thus, the string reference can never be invalid.

    The second string-- @"argy bargy foo foo 2-- doesn't fit in a tagged pointer and, thus, is a heap allocation.