【问题标题】:Weak NSString variable is not nil after setting the only strong reference to nil将唯一的强引用设置为 nil 后,弱 NSString 变量不为 nil
【发布时间】:2012-06-10 23:32:17
【问题描述】:

我对这段代码有疑问:

__strong NSString *yourString = @"Your String"; 
__weak NSString *myString = yourString;
yourString = nil;
__unsafe_unretained NSString *theirString = myString;
NSLog(@"%p %@", yourString, yourString);
NSLog(@"%p %@", myString, myString);
NSLog(@"%p %@", theirString, theirString);

我希望此时所有指针都是nil,但事实并非如此,我不明白为什么。 第一个(强)指针是nil,但其他两个不是。这是为什么呢?

【问题讨论】:

    标签: objective-c memory-management nsstring automatic-ref-counting weak-references


    【解决方案1】:

    tl;博士: 问题是字符串文字永远不会被释放,所以你的弱指针仍然指向它。


    理论

    强变量将保留它们指向的值。

    Weak 变量不会保留其值,并且在释放该值时,它们会将其指针设置为 nil(为了安全起见)。

    Unsafe unretained 值(您可能可以通过名称读取)不会保留该值,如果它被解除分配,它们不会对此采取任何措施,可能指向一块坏内存


    文字和常量

    当你使用@"literal string" 创建一个字符串时,它变成一个永远不会改变的字符串字面量。如果您在应用程序的许多地方使用相同的字符串,它始终是相同的对象。字符串文字不会消失。使用[[NSString alloc] initWithString:@"literal string"] 不会有什么不同。因为它变成了指向文字字符串的指针。然而值得注意的是[[NSString alloc] initWithFormat:@"literal string"]; 的工作方式不同,将释放其字符串对象。

    逐行:

    __strong NSString *yourString = @"Your String"; 
    

    您正在创建一个指向字符串的强指针。这将确保价值不会消失。 在你的情况下它有点特别,因为字符串是一个字符串文字,技术上不会被释放。

    __weak NSString *myString = yourString;
    

    你创建了一个弱指针,指向与你的强指针相同的东西。如果此时强指针指向别的东西,它指向的值将被释放,那么弱指针将改变它的值,使其指向nil。现在它仍然指向与强指针相同。

    yourString = nil;
    

    您的强指针指向nil。没有任何东西指向旧字符串,所以如果不是因为它是一个文字字符串,它应该被释放。如果您对自己创建的其他对象尝试完全相同的操作,weak 变量将更改为指向nil。但是,由于字符串文字是文字并且不会消失。弱变量仍将指向它。

    __unsafe_unretained NSString *theirString = myString;
    

    创建了一个新的未保留指针,指向指向字符串文字的弱指针。

    NSLog(@"%p %@", yourString, yourString);
    NSLog(@"%p %@", myString, myString);
    NSLog(@"%p %@", theirString, theirString);
    

    你打印了所有的字符串,但很困惑为什么第一个值是 nil 而其他两个不是。


    相关阅读:

    What's the difference between a string constant and a string literal?

    【讨论】:

    • 非常感谢您的解释,所以,如果我这样做:__strong NSString *yourString = [[NSString alloc] initWithString:@"Your String"];,在此变量设置为 nil 之后, myString 的指针将是 nil no ?
    • 对不起,没有。这也将指向字符串文字,这意味着指向强指针时的弱指针将指向不会消失的字符串文字。
    • 哦,我试过用这个函数initWithUTF8String初始化YourString,结果很好。只是最后一个问题,由于什么原因,无法获得相同的结果:“initWithString”?
    • 抱歉,我无法很好地回答为什么会这样。这就是事情的运作方式。我只能猜测为什么 initWithUTF8String 的工作方式不同,但这只是我的猜测。
    • 这是一个实现细节,但-initWithString: 基本上是释放调用它的对象、复制参数并返回该副本。作为另一种优化,不可变值对象通常实现-copy 来执行-retain,因为您是否拥有“真实”副本或只是再次获取相同的对象都没有关系。由于原件是不可变的,而副本是不可变的,所以它们总是相同的。最后,保留字符串文字没有任何作用,因为它是永久性的。所有组合,-initWithString:,给定一个文字,返回相同的文字。
    【解决方案2】:

    只有在自动释放池耗尽后,weak 属性才会被设置为 nil。

    尝试:

    @autoreleasepool {
        _strong NSString *yourString = @"Your String"; 
        __weak NSString *myString = yourString;
        yourString = nil;
        __unsafe_unretained NSString *theirString = myString;
    }
    
    NSLog(@"%p %@", yourString, yourString);
    NSLog(@"%p %@", myString, myString);
    NSLog(@"%p %@", theirString, theirString);
    

    【讨论】:

      【解决方案3】:

      大卫的回答是 100% 正确的。我刚刚添加了四个使用 GHUnit 的显式示例。

      对象引用的生命周期限定符行为。

      使用NSObject 作为所有对象的代理,生命周期限定符的行为符合预期。

      - (void) test_usingNSObjects
      {
          NSObject *value1 = [[NSObject alloc] init];
          NSObject *value2 = [[NSObject alloc] init];
          NSObject *value3 = [[NSObject alloc] init];
          __strong NSObject *sRefToValue = value1;
          __weak NSObject *wRefToValue = value2;
          __unsafe_unretained NSObject *uRefToValue = value3;
      
          value1 = value2 = value3 = nil;
      
          GHAssertNotNil(sRefToValue,
                         @"Strong reference to the object that was originally \
                         assigned to value1.  Even though value1 was set to nil, the \
                         strong reference to the object keeps the object from being \
                         destroyed.");
      
          GHAssertNil(wRefToValue,
                      @"Weak reference to the object that was originally assigned to \
                      value2.  When value2 was set to nil, the weak reference does \
                      not prevent the object from being destroyed. The weak \
                      reference is also set to nil.");
      
          // Removing the #ifdef and #endif lines will result in a EXC_BAD_ACCESS
          // signal.  Receiving a EXC_BAD_ACCESS signal is the expected behavior for
          // that code.
      #ifdef RECIEVE_EXC_BAD_ACCESS
          GHAssertNotNil(uRefToValue,
                         @"Unsafe unretained reference to the object that was \
                         originally assigned to value3.  When value3 was set to nil, \
                         the unsafe unretained reference does not prevent the object \
                         from being destroyed. The unsafe unretained reference is \
                         unaltered and the reference is invalid.  Accessing the \
                         reference will result in EXC_BAD_ACCESS signal.");
      #endif
      
          // To avoid future EXC_BAD_ACCESS signals.
          uRefToValue = nil;
      }
      

      文字 NSStrings (@"something") 的生命周期限定符行为。

      这与test_usingNSObjects 基本相同,但不是使用NSObject,而是使用分配了文字字符串的NSString。由于文字字符串不会像其他对象一样被破坏,因此观察到 __weak 和 __unsafe_unretained 变量的不同行为。

      - (void) test_usingLiteralNSStrings
      {
          NSString *value1 = @"string 1";
          NSString *value2 = @"string 2";
          NSString *value3 = @"string 3";
          __strong NSString *sRefToValue = value1;
          __weak NSString *wRefToValue = value2;
          __unsafe_unretained NSString *uRefToValue = value3;
      
          value1 = value2 = value3 = nil;
      
          GHAssertNotNil(sRefToValue,
                         @"Strong reference to the object that was originally \
                         assigned to value1.  Even though value1 was set to nil, \
                         literal strings are not destroyed.");
      
          GHAssertNotNil(wRefToValue,
                         @"Weak reference to the object that was originally assigned \
                         to value2.  Even though value2 was set to nil, \
                         literal strings are not destroyed so the weak reference is \
                         still valid.");
      
          GHAssertNotNil(uRefToValue,
                         @"Unsafe unretained reference to the object that was \
                         originally assigned to value3.  Even though value3 was set \
                         to nil, literal strings are not destroyed so the unsafe \
                         unretained reference is still valid.");
      }
      

      非文字 NSStrings 的生命周期限定符行为。

      这与test_usingNSObjects 基本相同,但不是使用NSObject,而是使用分配了非文字字符串的NSString。由于非文字字符串与其他对象一样被销毁,因此行为与在test_usingNSObjects 中观察到的相同。

      - (void) test_usingNonliteralNSStrings
      {
          NSString *value1 = [[NSString alloc] initWithFormat:@"string 1"];
          NSString *value2 = [[NSString alloc] initWithFormat:@"string 2"];
          NSString *value3 = [[NSString alloc] initWithFormat:@"string 3"];
          __strong NSString *sRefToValue = value1;
          __weak NSString *wRefToValue = value2;
          __unsafe_unretained NSString *uRefToValue = value3;
      
          value1 = value2 = value3 = nil;
      
          GHAssertNotNil(sRefToValue,
                         @"Strong reference to the object that was originally \
                         assigned to value1.  Even though value1 was set to nil, the \
                         strong reference to the object keeps the object from being \
                         destroyed.");
      
          GHAssertNil(wRefToValue,
                      @"Weak reference to the object that was originally assigned to \
                      value2.  When value2 was set to nil, the weak reference does \
                      not prevent the object from being destroyed. The weak \
                      reference is also set to nil.");
      
          // Removing the #ifdef and #endif lines will result in a EXC_BAD_ACCESS
          // signal.  Receiving a EXC_BAD_ACCESS signal is the expected behavior for
          // that code.
      #ifdef RECIEVE_EXC_BAD_ACCESS
          GHAssertNotNil(uRefToValue,
                         @"Unsafe unretained reference to the object that was \
                         originally assigned to value3.  When value3 was set to nil, \
                         the unsafe unretained reference does not prevent the object \
                         from being destroyed. The unsafe unretained reference is \
                         unaltered and the reference is invalid.  Accessing the \
                         reference will result in EXC_BAD_ACCESS signal.");
      #endif
      
          // To avoid future EXC_BAD_ACCESS signals.
          uRefToValue = nil;
      }
      

      NSString 创作 - 文字与非文字。

      显示以各种方式创建的字符串,无论它们是文字还是非文字。

      - (void) test_stringCreation
      {
          NSString *literalString = @"literalString";
          NSString *referenced = literalString;
          NSString *copy = [literalString copy];
          NSString *initWithString = [[NSString alloc] initWithString:literalString];
          NSString *initWithFormat = [[NSString alloc] initWithFormat:@"%@", literalString];
      
          // Testing that the memory addresses of referenced objects are the same.
          GHAssertEquals(literalString, @"literalString", @"literal");
          GHAssertEquals(referenced, @"literalString", @"literal");
          GHAssertEquals(copy, @"literalString", @"literal");
          GHAssertEquals(initWithString, @"literalString", @"literal");
          GHAssertNotEquals(initWithFormat, @"literalString",
                            @"nonliteral - referenced objects' memory addresses are \
                            different.");
      
          // Testing that the objects referenced are equal, i.e. isEqual: .
          GHAssertEqualObjects(literalString, @"literalString", nil);
          GHAssertEqualObjects(referenced, @"literalString", nil);
          GHAssertEqualObjects(copy, @"literalString", nil);
          GHAssertEqualObjects(initWithString, @"literalString", nil);
          GHAssertEqualObjects(initWithFormat, @"literalString", nil);
      
          // Testing that the strings referenced are the same, i.e. isEqualToString: .
          GHAssertEqualStrings(literalString, @"literalString", nil);
          GHAssertEqualStrings(referenced, @"literalString", nil);
          GHAssertEqualStrings(copy, @"literalString", nil);
          GHAssertEqualStrings(initWithString, @"literalString", nil);
          GHAssertEqualStrings(initWithFormat, @"literalString", nil);
      }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2014-10-11
        • 1970-01-01
        • 2016-05-01
        • 1970-01-01
        相关资源
        最近更新 更多