Objective C - Self Zeroing weak pointer unexpected

2019-05-10 19:18发布

问题:

I have recently upgraded from Mavericks to Yosemite and now my unit tests are failing. The problem boiled down to a typo in a weak pointer to string content. Please see the following sample code:

NSString* value1;
NSString* value2;
__weak NSString* weakValue1;
__weak NSString* weakValue2;
NSMutableString* resultText = [NSMutableString new];

@autoreleasepool
{
    value1 = [NSString stringWithFormat: @"Hello: %d", 1];
    value2 = [NSString stringWithFormat: @"Hello %d", 2];
    weakValue1 = value1;
    weakValue2 = value2;

    [resultText appendFormat: @"  value1 = %@", weakValue1];
    [resultText appendFormat: @"  value2 = %@", weakValue2];

    value1 = nil;
    value2 = nil;
}
[resultText appendFormat: @"  value1 = %@", weakValue1];
[resultText appendFormat: @"  value2 = %@", weakValue2];

NSLog( @"resultText = %@", resultText );

The output of this code is:

resultText =   value1 = Hello: 1  value2 = Hello 2  value1 = (null)  value2 = Hello 2

I would have expected:

resultText =   value1 = Hello: 1  value2 = Hello 2  value1 = (null)  value2 = (null)

the second value2 to also be (null) but it is not. Please notice the difference between the content of value1 and value2. value2 is missing the colon. I do not understand why weakValue2 does not self zero when value2 is set to nil. As soon as I put the colon back in the string I get the results I expect. I was not seeing this behaviour when running this code in Mavericks.

Does anyone know why this is happening?

I noticed in this question that the valid point of multithreading could cause a weak pointer to not self zero in time, but that is not what is happening here. Firstly, I am not multithreaded and I get the exact same result if I step through the code and view the variables rather than printing them with NSLog.

回答1:

NSString it is not concrete class, it is interface and factory for many other classes.

I've found that value1 is instance of class __NSCFString, but value2 is instance of class NSTaggedPointerString. Class NSTaggedPointerString does not support retain and release (I try to inject some blocks to it methods).

If you print retainCount for this instance, you'll get that result:

po [value2 retainCount] //18446744073709551615

If you remove symbol ":" from format, you'll get this result:

 resultText =   value1 = Hello 1  value2 = Hello 2  value1 = Hello 1  value2 = Hello 2

It's happened because above strings is instances of NSTaggedPointerString class. I think it is very strange behaviour.

P.S.

If you add ":" to value2 string, you'll get result:

resultText =   value1 = Hello: 1  value2 = Hello: 2  value1 = (null)  value2 = (null)

:)

UPD

If length of string less then 8, then it string will be cached to memory.

UPD-2

I've changed code to:

value1 = [NSString stringWithFormat: @"Heo: %d", 1];
    value2 = [NSString stringWithFormat: @"Heo: %d", 1];
    if (value2 == value1)
    {
        NSLog(@"same strings");
    }

I've had result: "same strings"



回答2:

The problem is that your use of weak references are colliding with implementation details of the system.

A weak reference will be nullified if-and-only-if the object it references is deallocated. The timing therein is not actually guaranteed beyond that as long as the reference is non-NULL, the object referred to is still viable.

A weak reference to a singleton (true singleton that is never deallocated) will never be nullified.

NSString, NSNumber, NSDate, and a handful of other classes are implemented as singletons for some values some of the time. Or a lot of the time, depending on the platform.

Thus, a weak reference to an instance of one of these classes may or may not be nullified because the instance may actually be a singleton.

Note that tagged pointers are, effectively, singletons by definition.

To fix?

Don't create weak references to system primitive/value classes.