How to enforce using `-retainCount` method and `-d

2019-04-17 13:17发布

问题:

Under ARC, compiler will forbid using any method or selector of -retainCount, -retain, -dealloc, -release, and -autorelease.

But sometimes I want to know the retain counts at runtime, or using method swizzling to swap the -dealloc method of NSObject to do something.

Is it possible to suppress (or bypass) the compiler complaining just a couple of lines of code? I don't want to modify ARC environment for whole project or whole file. I think preprocessor can do it, but how?


Additions:

Thanks guys to give me a lesson about the use of -retainCount. But I wonder whether it is possible to enforce invoking/using those forbidden methods/selectors.

I know Instruments is a powerful tool to do this job. But I am still curious about those question.

Why do I want to use -retainCount:

When using block, if you don't specify a __weak identifier on the outside variables, the block will automatically retain those outside objects in the block after the block is copied into the heap. So you need to use a weak self to avoid retain cycle, for example:

__weak typeof(self) weakSelf = self;
self.completionBlock = ^{
    [weakSelf doSomething];
};

However, it will still cause retain cycle when you only use instance variables in the copied block (YES, although you didn't use any keyword self in the block).

For example, under Non-ARC:

// Current self's retain count is 1
NSLog(@"self retainCount: %d", [self retainCount]);

// Create a completion block
CompletionBlock completionBlock = ^{
    // Using instance vaiable in the block will strongly retain the `self` object after copying this block into heap.
    [_delegate doSomething];
};

// Current self's retain count is still 1
NSLog(@"self retainCount: %d", [self retainCount]);

// This will cuase retain cycle after copying the block.
self.completionBlock = completionBlock;

// Current self's retain count is 2 now.
NSLog(@"self retainCount: %d", [self retainCount]);

Without using -retainCount before/after the copied block code, I don't think this retain cycle caused by using instance variables in the completion block will be discovered easily.

Why do I want to use -dealloc:

I want to know whether I can use method swizzling to monitor which object will be deallocated by logging messages on the Xcode console when the -dealloc is invoked. I want to replace the original implementation of -dealloc of NSObject.

回答1:

Agreed 100% with the other commenters about the fact that you do not want to use -retainCount. To your other question, however, about -dealloc:

You also do not want to swizzle -dealloc. If you think you want to swizzle it, you don't understand how it works. There are a lot of optimizations going on there; you can't just mess with it. But, as @bbum hints at, you can easily get notifications when objects are deallocated, and this can be very useful.

You attach an associated object to the thing you want to watch. When the thing you want to watch goes away, so does the associated object, and you can override its dealloc to perform whatever action you want. Obviously you need to be a little careful, because you're in the middle of a dealloc, but you can generally do most anything you'd need to here. Most importantly for many cases, you can put a breakpoint here or add a logging statement, so you can see where the object was released. Here's a simple example.

With ARC

const char kWatcherKey;

@interface Watcher : NSObject
@end

#import <objc/runtime.h>

@implementation Watcher

- (void)dealloc {
  NSLog(@"HEY! The thing I was watching is going away!");
}

@end

NSObject *something = [NSObject new];
objc_setAssociatedObject(something, &kWatcherKey, [Watcher new], 
                         OBJC_ASSOCIATION_RETAIN);

Without ARC

const char kWatcherKey;

@interface Watcher : NSObject
- (void)lastRetainDone;
@end

#import <objc/runtime.h>
// turn off ARC!
@implementation Watcher
{
    BOOL noMoreRetainsAllowed;
}

- (void)lastRetainDone {
   noMoreRetainsAllowed = YES;
}

- (id) retain {
     if (noMoreRetainsAllowed) abort();
     return [super retain];
}

- (void)dealloc {
  NSLog(@"HEY! The thing I was watching is going away!");
  [super dealloc];
}

@end

...

NSObject *something = [NSObject new];
Watcher *watcher = [Watcher new];
objc_setAssociatedObject(something, &kWatcherKey, watcher, 
                         OBJC_ASSOCIATION_RETAIN);
[watcher lastRetainDone];
[watcher release];

Now, when something goes away, -[Watcher dealloc] will fire and log for you. Very easy. Completely supported and documented.


EDIT:

Without using -retainCount before/after the copied block code, I don't think this retain cycle caused by using instance variables in the completion block will be discovered easily.

You are somewhat correct here, but there are two lessons to be learned, and neither is to use retainCount (which won't actually help you in this case anyway because retainCount can very often be something you don't expect).

  • The first lesson is: Do not allow any warnings in ObjC code. The situation you're describing will generally create a compiler warning in recent versions of clang. So it's actually quite easy to discover in many cases. The way you've separated it into multiple assignments, the compiler may miss it, but the lesson there is to change your coding style to help the compiler help you.
  • The second lesson is: don't access ivars directly outside of init and dealloc. This is one of the many little surprises that can cause. Use accessors.


回答2:

That's not recommened at all, I dont know your intentions but they dont sound very safe.

The use of retainCount is not recommended.

From AppleDocs:

This method is of no value in debugging memory management issues. Because any number of framework objects may have retained an object in order to hold references to it, while at the same time autorelease pools may be holding any number of deferred releases on an object, it is very unlikely that you can get useful information from this method

And, if there's any doubt, check this link:

http://whentouseretaincount.com/

Whatever you are trying to do, please dont.

For future references, I'm going to add some linsk to help you understand the process of how memory works in iOS. Even if you use ARC, this is a must know (remember that ARC is NOT a garbage collector)

Beginning ARC in iOS 5 Tutorial Part 1

Understand memory management under ARC

Memory Management Tutorial for iOS

Advance Memory Managment

And, of course, once you understand how memory works is time to learn how to profile it with instruments:

Instruments User Guide