将对象分配给Objective-C中的弱引用?

7
根据iOS中的ARC,一个对象必须至少有一个强引用才能保留在内存中,当没有强引用(即引用计数为0)时,该对象将从内存中释放,我们将无法再访问该对象。
但是我在我的代码中遇到了奇怪的行为。
我在代码中给一个弱引用NSString赋值时,当我写[[NSString alloc] init];时,Xcode会发出警告。
__weak NSString *str;
str = [[NSString alloc] init];

将保留对象分配给弱属性,对象将在分配后释放。
如果我这样做,Xcode就不会发出任何警告。
__weak NSString *str;
str = @"abcd";
NSLog(@"%@", str);

无警告截图

输出:abcd

输出截图

我的问题是:

为什么输出为"abcd",即使str是一个弱引用变量。谁在内存中保存这个值为"abcd"NSString对象?


Objective-C字符串字面量的处理方式不同。它们是保存在内存中的常量,因此它们不受任何正常内存管理规则的影响。 - rmaddy
可能是 https://dev59.com/L2gu5IYBdhLWcg3w_cBV 的重复。 - rmaddy
我认为这不是重复的,因为了解为什么在一个情况下有警告而在另一个情况下没有警告很有趣。 - rob mayoff
2个回答

6
当你使用str = @"abcd"这种方式时,编译器无法识别该代码模式是否返回一个新分配的对象,因此不会触发有关将新对象直接赋值给__weak变量的警告。
此外,像@"abcd"这样的字符串字面量存储在程序的可执行文件中,它永远不会被释放。 retainrelease操作实际上不会改变其保留计数。它的保留计数设置为一个神奇数字,表示一个不朽的对象。因此,您的__weak变量str实际上没有被设置为nil,因为它所引用的对象没有被释放。这就是为什么它打印出abcd的原因。
事实上,如果您分配一个字符串字面量(而不是其他类型的字面量,比如数组字面量@[a, b, c]),clang特意抑制警告。请参阅clang源代码中的注释:
static bool checkUnsafeAssignLiteral(Sema &S, SourceLocation Loc,
                                     Expr *RHS, bool isProperty) {
  // Check if RHS is an Objective-C object literal, which also can get
  // immediately zapped in a weak reference.  Note that we explicitly
  // allow ObjCStringLiterals, since those are designed to never really die.
  RHS = RHS->IgnoreParenImpCasts();

  // This enum needs to match with the 'select' in
  // warn_objc_arc_literal_assign (off-by-1).
  Sema::ObjCLiteralKind Kind = S.CheckLiteralKind(RHS);
  if (Kind == Sema::LK_String || Kind == Sema::LK_None)
    return false;

  S.Diag(Loc, diag::warn_arc_literal_assign)
    << (unsigned) Kind
    << (isProperty ? 0 : 1)
    << RHS->getSourceRange();

  return true;
}

如果我们把类型更改为NSArray并使用数组字面量,我们会收到一个警告:

array literal assignment warning

接下来...当您说str = [[NSString alloc] init]时,您会收到警告,因为编译器认识到[[NSString alloc] init]是通常返回新对象的代码模式。

然而,在特定的情况下[[NSString alloc] init],你会发现str仍然没有被设置为nil。这是因为-[NSString init]被特殊处理为返回全局空字符串对象。它实际上不会在每次调用时创建一个新对象。

    __weak NSString *str;
    str = [[NSString alloc] init];
    NSLog(@"%ld %p [%@]", CFGetRetainCount((__bridge CFTypeRef)str), str, str);

输出:

2018-01-24 01:00:22.963109-0600 test[3668:166594] 1152921504606846975 0x7fffe55b19c0 []

1152921504606846975是一个特殊的保留计数,表示该对象是不可释放的。


那么这意味着在两种情况下对象永远不会被解除分配? - Prashant Tukadiya
是的,在这两种情况下,您都将“str”设置为不可变对象。 - rob mayoff
@robmayoff 这意味着,@"abcd"本身是一个对象,只要它所定义的范围(类或方法)存在,它就会一直留在内存中。但是,一旦其父级不再存在,它将被置为空值。或者由于保留计数是如此之大,它永远不会消失并继续占用内存?因为即使父级不存在,它也只会将保留计数减少1。 - nr5

0
#define TLog(_var) ({ NSString *name = @#_var; NSLog(@"%@: %@ -> %p: %@ retainCount:%ld", name, [_var class], _var, _var, CFGetRetainCount((__bridge CFTypeRef)(_var))); })

__weak NSString *str;
str = @"abcd";
NSLog(@"%@",str
      );
TLog(str);

After debug with your code I found that [str class] is NSCFConstantString and it's retainCount is 1152921504606846975.

for the retainCount in Objective-C, If the object's retainCount equals 1152921504606846975, it means "unlimited retainCount", this object can not be released though it is assigning to weak reference.

All __NSCFConstantString object's retainCount is 1152921504606846975, which means __NSCFConstantString will not be released whether it is __weak. The NSString created using the *str = @"abcd"; will be the same object if they are same value whether how many times to be written.


1152921504606846975并不真正意味着“无限制”。它是一种实现工件,可能随时更改。通常情况下,retainCount是完全无用的。 - bbum
1
不用担心。通常,应该忽略从框架中获取的对象所使用的类和分配类型。最好将其视为增量引用计数。即强引用为+1,当强引用超出范围或被nil时,为-1。让系统自己处理,如果需要调试,请使用对象图检查器确定是什么在保持强引用。 - bbum

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接