23

In Clang's Objective-C Automatic Reference Counting we see the following

For __weak objects, the lvalue is updated to point to the new pointee, unless the new pointee is an object currently undergoing deallocation, in which case the lvalue is updated to a null pointer. This must execute atomically with respect to other assignments to the object, to reads from the object, and to the final release of the new pointee.

In objc-weak.mm wee see the following chunk of code in weak_register_no_lock():

    if (deallocating) {
    if (crashIfDeallocating) {
        _objc_fatal("Cannot form weak reference to instance (%p) of "
                    "class %s. It is possible that this object was "
                    "over-released, or is in the process of deallocation.",
                    (void*)referent, object_getClassName((id)referent));
    } else {
        return nil;
    }
}

I set a breakpoint in my UIViewController subclass dealloc method and tried invoking [self allowsWeakReference] in lldb which resulted in NO value.

If we try to set self to weak property of another object the app will crash in accordance with the objc-weak.mm code.

The question is – why does this happen? Is the clang's specification wrong? Is is this a bug in objc implementation?


Here is a simple piece of code that will reproduce the crash:

//cc -fmodules -fobjc-arc -g crash.m -o crash
@import Foundation;

@interface Foo : NSObject
@end

@implementation Foo
- (void)dealloc {
  Foo * __weak weakSelf = self; // crashes on this line
}
@end

int main() {
  (void)[[Foo alloc] init];
  return 0;
}
Jeremy W. Sherman
  • 34,925
  • 5
  • 73
  • 108
Nikita Ilyasov
  • 490
  • 2
  • 13
  • So what is it you want to know; why your app crashes or why setting a weak pointer to a deallocating object results in null? Also why have you assumed the two things are related? – trojanfoe Mar 14 '16 at 15:22
  • I slightly edited my question to use the latest objc-weak.mm source. – Nikita Ilyasov Mar 14 '16 at 15:23
  • But you didn't answer my questions. – trojanfoe Mar 14 '16 at 15:24
  • Sorry, I pressed enter to quickly. I see two contradicting statements. The first one is that setting a weak pointer to a deallocating object should result in nil. But in the source code of objc-weak we see that it will result in crash instead. I was assuming that it is perfectly legal to store `self` to weak properties during the deallocation, but now I see that it isn't. Isn't it a bug? – Nikita Ilyasov Mar 14 '16 at 15:27
  • And what sets `crashIfDeallocating`? – trojanfoe Mar 14 '16 at 15:27
  • It is the argument in weak_register_no_lock function. Where it is coming from and on what it depends I don't know – Nikita Ilyasov Mar 14 '16 at 15:29
  • Well that seems to be the key as to weather it performs as documented or crashes as you claim. I would assume, therefore, that's passed as `false` most/all of the time. You'd have to do further tracing in the source code to actually see what affects it. – trojanfoe Mar 14 '16 at 15:31
  • I'm sure that it crashes because I reproduced it. I didn't find anything that suggests that crash is the expected behaviour. – Nikita Ilyasov Mar 14 '16 at 15:35
  • It doesn't sound like what you're testing is a scenario described by the first quote. You are setting a pointer from within an object that is being dealloc'd, not setting a pointer **to** an object being dealloc'd. – Avi Mar 15 '16 at 06:22
  • Frankly, I didn't quite get what you're saying. If I write `someObject.delegate = self` in the `dealloc` method, then I'm setting a pointer **to** an object that is currently undergoing deallocation. – Nikita Ilyasov Mar 15 '16 at 06:37
  • In `NSObject.mm`, you can see that `objc_initWeak` and `objc_storeWeak` pass crash = true, and there are also functions called `objc_initWeakOrNil` and `objc_storeWeakOrNil` that don't crash. I agree that this contradicts the ARC spec, where in the section for runtime functions at the end it explicitly says that `objc_initWeak` and `objc_storeWeak` are to store a null pointer if the pointee is undergoing deallocation. – user102008 Feb 09 '17 at 20:52
  • Looking at the history of [obcj4 source](https://opensource.apple.com/source/objc4/), this behavior began in version 680 (corresponding to OS X 10.11 and iOS 9), and didn't exist in the previous version 647, where there was only one version of the functions `objc_initWeak` and `objc_storeWeak`, and they don't crash. – user102008 Feb 09 '17 at 20:54
  • Likely needed for Swift … – Amin Negm-Awad Feb 16 '17 at 17:57
  • A weak reference doesn't have ownership of the object. When the variable it was referencing it discarded, the weak reference disappears and is set to nil. – user3564870 Mar 22 '17 at 03:53
  • It seems I misspoke earlier about this beginning in version 680. In earlier versions, there was only one version, but it seems from looking at the source of `weak_register_no_lock` that it always crashes when deallocating. – user102008 May 09 '17 at 02:58

1 Answers1

1

It's not a bug: it's obviously very intentional. It is a deviation from the spec, but it's an intentional one.

Based on the warning, it sounds like they wanted to make it easier to diagnose over-release scenarios, and catching objects that are being deallocated at the time might just be a side effect of that main goal.

They might also consider that, if you're trying to weakify self while being deallocated anyway, and you're not checking for a nil weakref (quite common - lots of block code repeatedly calls through a weakref that could go nil at any time!), you're setting yourself up for hard to debug bugs.

All that said, I'd love to see the notes behind that runtime change.

Jeremy W. Sherman
  • 34,925
  • 5
  • 73
  • 108