Pregunta

I have a method called in various places called "cancelAllPendingDownloads" This is a general method that cancels various jobs and updates internal counters.

Problem happens when it is called within the dealloc method

-(void)dealloc
{
  [self cancelAllPendingDownloads]; // want to cancel some jobs
}

-(void)cancelAllPendingDownloads // updates some internals
{
    __weak __typeof__(self) weakSelf = self; // This line gets a EXC_BAD_INSTRUCTION error in runtime
   for(Download *dl in self.downloads)
   {
      dl.completionHandler = ^{ // want to replace the previous block
        weakSelf.dlcounter--;
      }
      [dl cancel];
   }
}

Not sure why it fails in the dealloc method as "self" still exists

When I change the code to

__typeof__(self) strongSelf = self; //everything works fine
__weak __typeof__(self) weakSelf = strongSelf; (or "self") BAD_INSTRUCTION error

The error happens on the second line

¿Fue útil?

Solución

Just to make the "you are not supposed" or "You can't" part of the other good answers more precise:

The runtime function for storing a weak reference is objc_storeWeak(), and the Clang/ARC documentation states:

id objc_storeWeak(id *object, id value);

... If value is a null pointer or the object to which it points has begun deallocation, object is assigned null and unregistered as a __weak object. Otherwise, object is registered as a __weak object or has its registration updated to point to value.

Since the self object has already begun deallocation, weakSelf should be set to NULL (and therefore is not of any use).

However, there seems to be a bug (as discussed here http://www.cocoabuilder.com/archive/cocoa/312530-cannot-form-weak-reference-to.html) that objc_storeWeak() crashes in this case, instead of returning NULL.

Otros consejos

If an object is in dealloc state, you are not supposed to create any new references to it. Consider the object as already destroyed. Don't use it in a callback/delegate any more.

Note that dlcounter won't ever be read. Just cancel the connections without reading the results.

TL;DR
- How can I reference __weak self in dealloc method?
- Don't reference it.

You can't initialize a week (or a strong) reference to self in the dealloc method and use it elsewhere - it's too late, the object will be inevitably destroyed.

However, you might try this:

-(void)dealloc
{
    NSArray* localDownloads = self.downloads;
    for(Download* dl in localDownloads) {
        [dl cancel];
    }
}

It should be clear that there are better places to invoke cancellation, for example, in a view controller, you may override viewWillDisappear:.

I am assuming you are using ARC for your project.

Straight from Apple: Apple Talked about Weak and Strong

__strong is the default. An object remains “alive” as long as 
there is a strong pointer to it.
__weak specifies a reference that does not keep the referenced object alive. 
A weak reference is set to nil when there are no strong references to the object.

This is an Article Explaining Dealloc: Dealloc Method Explained and More

This method will be called after the final release of the object 
but before it is deallocated or any of its instance variables are destroyed. 
The superclass’s implementation of dealloc will be called automatically when 
the method returns.

After this being pointed out... I highly recommend you revise your code design because there is no reason for you to call a weak typeof(self) to solve your problem of cancelling those downloads at dealloc or any type of deallocing that involves _weak_typeof__self for that matter.

What I can recommend though is that that class that you are trying to cancel those downloads frin, make it keep track of those downloads with a Download UniqueID and just stop them or delete them at dealloc. Its simpler and easier to manage rather than that wierd call to __weak self and all that code you are doing.

In short: you can use a __strong reference to self in dealloc instead of __weak for your purposes but if and only if that strong reference won't outlive the end of dealloc. Otherwise, I would advise using __unsafe_unretained, which is still unsafe if it outlives the dealloc but is clearer to read.

Longer: I had a similar situation where the object (view controller) during dealloc should unsubscribe from notifications. That's a custom notifications system and unsubscribing requires creating an object with a reference to the entity that's being unsubscribed. I ended up with the same situation: in dealloc there's no way to create that object because it required a weak reference which caused a crash (here's some stupid demo code, not something you would have in production):

@interface Dummy : NSObject

@property(nonatomic, weak) id weakProperty;
@property(nonatomic, strong) id strongProperty;
@property(nonatomic, unsafe_unretained) id unsafeProperty;

- (instancetype)initWithWeakStuff:(id)stuff;
- (instancetype)initWithStrongStuff:(id)stuff;
- (instancetype)initWithUnsafeStuff:(id)stuff;

@end

@implementation Dummy

- (instancetype)initWithWeakStuff:(id)stuff {
  self = [super init];
  if (self) {
    _weakProperty = stuff;
  }
  return self;
}

- (instancetype)initWithStrongStuff:(id)stuff {
  self = [super init];
  if (self) {
    _strongProperty = stuff;
  }
  return self;
}

- (instancetype)initWithUnsafeStuff:(id)stuff {
  self = [super init];
  if (self) {
    _unsafeProperty = stuff;
  }
  return self;
}

- (void)dealloc {
}

@end

@interface ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad {
  [super viewDidLoad];
  // Do any additional setup after loading the view, typically from a nib.
}

- (void)didReceiveMemoryWarning {
  [super didReceiveMemoryWarning];
  // Dispose of any resources that can be recreated.
}

- (void)dealloc {
  Dummy *dummy = [[Dummy alloc] initWithStrongStuff:self];
  [[NSNotificationCenter defaultCenter]
      postNotificationName:@"some notification"
                    object:dummy]; // do something with it
}

@end

If, on the other hand, the reference was strong, all seems to work well (during dealloc). The problem would arise if that newly created object would outlive self:

- (void)dealloc {
  Dummy *dummy = [[Dummy alloc] initWithStrongStuff:self];

  dispatch_async(dispatch_get_main_queue(), ^{
    [[NSNotificationCenter defaultCenter]
        postNotificationName:@"some notification"
                      object:dummy]; // do something with it

  }); //Crash at the end of the block during dummy's dealloc
}

This would mean that whenever the dummy object would need to dealloc it would try to decrease the ref count of its strongProperty. And at that point the ViewController has been deallocated and released already. However, IMHO the "safest" way to proceed is to use unsafe_unretained in this case. Technically it's the same as using assign: pointer will be assigned regardless of memory management and that reference will not need to be released when it goes out of scope. But using unsafe_unretained tells the readers of your code (or future you) that you were aware of the risk and there must have been a reason to do what you did.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top