将保留的对象分配给__unsafe_unretained变量,但为什么不崩溃?

时间:2013-12-09 07:13:44

标签: ios iphone automatic-ref-counting

据我所知,str1在分配后被释放。我只收到警告,但为什么不崩溃?

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
    // Override point for customization after application launch.
    UIViewController * vc = [[UIViewController alloc] init] ;
    self.window.rootViewController = vc ;
    [self.window makeKeyAndVisible];

    NSString * __unsafe_unretained str1 = [[NSString alloc] initWithFormat:@"First Name: %@", @"cc"] ;
    // I think it should crash here !
    NSLog(@"string: %u", [str1 length]) ;

    NSString * __weak str2 = [[NSString alloc] initWithFormat:@"First Name: %@", @"cc"] ;
    // nil for str2
    NSLog(@"string: %@", str2) ;

    return YES;
}

如果我修改这样的代码可能很清楚。

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
    // Override point for customization after application launch.
    UIViewController * vc = [[UIViewController alloc] init] ;
    self.window.rootViewController = vc ;
    [self.window makeKeyAndVisible];

    NSString * __unsafe_unretained str1 = [[NSString alloc] initWithFormat:@"First Name: %@", @"cc"] ;
    // I think it should crash here !
    NSLog(@"string: %u", [str1 length]) ;

    TestObject * __unsafe_unretained obj1 = [[TestObject alloc] init] ;
    NSLog(@"obj1:%@", obj1) ;

    return YES;
}

TestObject.m:

@implementation TestObject

- (void)dealloc
{
    NSLog(@"%@, %@", NSStringFromSelector(_cmd), self) ;
}

@end

日志

2013-12-09 15:49:08.625 xxx[23950:a0b] string: 14
2013-12-09 15:49:08.627 xxx[23950:a0b] dealloc, <TestObject: 0x8970ff0>
2013-12-09 15:49:08.627 xxx[23950:a0b] obj1:<TestObject: 0x8970ff0>

1 个答案:

答案 0 :(得分:3)

那里发生了两件可能的事情。

Aaron Brager介绍了其中一个 - 如果一个对象在自动释放池中,则池直到下一个循环运行循环才会消耗,因此该对象将被授予临时缓冲。然而,ARC能够忽略许多自动释放的实例,并且在这种情况下正在这样做。

所以答案是内存尚未被覆盖,因此您的调用出现才能正常工作。但那只是一个意外。如果我们对事情进行重新排序,我们会得到一个非常不同的结果:

    NSString * __unsafe_unretained str1 = [[NSString alloc] initWithFormat:@"First Name: %@", @"cc"];

    // I think it should crash here !

    TestObject * __unsafe_unretained obj1 = [[TestObject alloc] init];
    NSLog(@"obj1:%@", obj1);

    //oops, crash!
    NSLog(@"string: %u", [str1 length]);

在这种情况下,str1指向的内存被分配的TestObject覆盖,因此第一个指针大小的字节从指向NSString的isa变为TestClass的isa。这意味着要求长度将失败。但是obj1和str1一样无效......那个内存现在在空闲列表中,只是等待重用。实际上它包含随机垃圾,它恰好发生垃圾“看起来”是一个有效的Objective-C对象,直到它被重用。

如果你向TestClass添加一个长度方法,我的例子似乎再次起作用!不要被欺骗...... C标准说访问无效指针是未定义的行为,这意味着编译器可以自由格式化硬盘,删除所有联系人,或者做任何事情。它甚至可以在程序中的第一条指令执行之前执行此操作,因为包含未定义操作的程序完全未定义。 (它直到它做了一件疯狂的事情时才定义,它完全没有定义)。

所以短版本是:你执行了一个未定义的操作(访问free'd指针),而不是格式化你的硬盘,编译器决定假装你的程序有意义。这仅仅是编译器优化的一个假象,以及如何使用内存,但结果与打印“Hippo”和退出一样毫无意义。(或者更实际的是,使用Zombies乐器和/或后卫启用malloc,您会发现原始代码示例立即崩溃)。