2

For QA purposes I want to ensure that instances of specific classes have been deallocated properly (so the actual count of instances is sufficient). I looked into the Objective-C runtime reference, but I could not find the appropriate function. I have checked similar questions but did not find a satisfying answer.

Edit I took TheCodingArt's mockup and completed it, the result can be obtained at https://www.generomobile.de/gmi/allocchecker.m The difficulty was to swizzle dealloc because ARC forbids passing the selector of dealloc for swizzling. I stumbled across this interesting swizzling tutorial at http://defagos.github.io/yet_another_article_about_method_swizzling/ NSString and other class clusters obviously are not freed by dealloc as one can see in the sample. But for my own classes it works our current IOS project and gives some interesting insights.

Cœur
  • 37,241
  • 25
  • 195
  • 267
Leo
  • 925
  • 10
  • 24
  • Use the *Instruments* leak tool. Don't be looking at no retain counts now else the Objective-C Elders will downvote you. – Droppy May 28 '15 at 14:23
  • The QA tests run outside the leak tool. I'm interestest in knowing if its possible to enumerate the instances of a particular class at runtime. Possible or not ? – Leo May 28 '15 at 14:28
  • Not without additional coding in the class (i.e. static array holding each (weak) instance). Sounds like a bit of a nightmare. – Droppy May 28 '15 at 14:29
  • I was hoping it can be done via the self introspection facilities of objective C, but apparently not. Of course adding a static counter in the classes of interest is the obvious method. – Leo May 28 '15 at 19:09

3 Answers3

3

One solution is to setup a static counter in your .m file. Increment the counter in the designated init method and decrement the counter in the dealloc method. Provide a class method to read the count value.

Don't do this as a rule. This should only be done for testing.

Lets say you want to track the instance count of SomeClass. You can do:

SomeClass.h

@interface SomeClass : NSObject

+ (NSInteger)instanceCount;

// everything else you need

@end

SomeClass.m

@import "SomeClass.h"

static NSInteger instanceCount = 0;

@implementation SomeClass

- (instancetype)init {
    self = [super init];
    if (self) {
        instanceCount++;
        // everything else you need
    }

    return self;
}

// all your other code

+ (NSInteger)instanceCount {
    return instanceCount;
}

- (void)dealloc {
    instanceCount--;
}

@end
Andy
  • 866
  • 9
  • 14
rmaddy
  • 314,917
  • 42
  • 532
  • 579
  • yep ... i have two statics : lifeTimeInstances, and numberOfInstances. Also for each instance i have an instance property instanceNumber. Invaluable time saver (macros, not in release build) coupled with some 'self obligated' class templates. – YvesLeBorg May 28 '15 at 14:45
  • Better yet, you could put this in a dictionary on a protocol for NSObject. You'd then be able to store [self class] as the key and an instantiation count as the object. – TheCodingArt May 28 '15 at 17:15
  • @TheCodingArt You should post an answer with your solution since it sounds much different than mine. – rmaddy May 28 '15 at 17:19
  • @TheCodingArt I'd be interest too in seeing a snippet – Leo May 28 '15 at 19:10
  • I meant category, apologies – TheCodingArt May 28 '15 at 19:17
  • I added a swizzling method category for example code via an answer. This will count anything (but whatever you exclude) that inherits from NSObject and add it into a count in a dictionary. Mind you, there are situations where you have to avoid an infinite loop via allocating the counting singleton object (hence my checks). I didn't want to put much time into this and just wanted to provide an example of what you could do. – TheCodingArt May 28 '15 at 20:24
2

Per request, I've mocked up a category that will keep count of allocated objects using method swizzling and a singleton. This was a quick mock up, so there are a few issues with it (one being that initializing anything that is contained within the storage class will cause an infinite loop). Mind you, this is for keeping track of many objects and should not be used in a production environment. The best methodology overall is to use the instruments tool.

#import "NSObject+Initializer.h"
#import <objc/runtime.h>

@interface ObjectCounter : NSObject

+ (instancetype)sharedObjectCounter;
@property (strong, nonatomic) NSMutableDictionary *objectCounterDictionary;

@end

@implementation ObjectCounter

+ (instancetype)sharedObjectCounter
{
    static ObjectCounter *objectCounter;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        objectCounter = [ObjectCounter new];
        objectCounter.objectCounterDictionary = [NSMutableDictionary new];
    });
    return objectCounter;
}

@end


@implementation NSObject (Initializer)



+ (NSNumber *)initializedCount
{
    NSLog(@"Dict: %@", [ObjectCounter sharedObjectCounter].objectCounterDictionary);
    return [ObjectCounter sharedObjectCounter].objectCounterDictionary[NSStringFromClass([self class])];
}

+ (id)alloc_swizzled
{
    NSLog(@"Swizzled");
    NSString *className = NSStringFromClass([self class]);
    if (![className isEqualToString:NSStringFromClass([NSMutableDictionary class])] && ![className isEqualToString:NSStringFromClass([ObjectCounter class])]) {
        ObjectCounter *counter = [ObjectCounter sharedObjectCounter];
        NSMutableDictionary *objectDictionary = counter.objectCounterDictionary;
        NSNumber *count = objectDictionary[className];
        count = count ? @(count.integerValue + 1) : @0;
        objectDictionary[className] = count;
    }

    return [self alloc_swizzled];
}


+ (void)load
{

    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        Class class = [self class];

        SEL originalSelector = @selector(alloc);
        SEL swizzledSelector = @selector(alloc_swizzled);

        Method originalMethod = class_getClassMethod(class, originalSelector);
        Method swizzledMethod = class_getClassMethod(class, swizzledSelector);

        BOOL didAddMethod = class_addMethod(class, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod));

        if (didAddMethod) {
            class_replaceMethod(class, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod));
        } else {
            method_exchangeImplementations(originalMethod, swizzledMethod);
        }
    });
}
TheCodingArt
  • 3,436
  • 4
  • 30
  • 53
  • I only made this per request, in no way is this a recommended methodology and if you read my answer you'll note that I note that. No need to down vote it.... – TheCodingArt Jun 11 '15 at 15:24
  • it took a while but I could finally take your idea.I avoided any ObjC classes for counting the instances (Used CFMutableDictionary), also I used fprintf for traces because NSLog allocates objC objects itself . One question to your mockup remains: why you use dispatch_once to init the swizzling? is there a situation where load can be called twice ? – Leo Nov 14 '15 at 13:35
  • The dispatch_once ensures thread safety and it only runs once. It's been a while but I believe I found a weird edge case where this was needed. Could have been an inheritance thing but I don't remember. Regardless, it ensures proper behavior. – TheCodingArt Nov 14 '15 at 13:49
  • Ok understand. But to be thread safe the access to the ObjectCounter in your mockup would need to be synchronized too,AFAIK NSMutableDictionary is not thread safe per se. – Leo Nov 16 '15 at 01:08
  • That's true and I think I originally had NSCache or something as a quick job (I really didn't spend too much time writing this and just needed a quick solution). I would have added a custom serial queue if I needed that functionality. In that case just wrap an add/remove method with a private instance variable of the dict where it always pipes to the serial queue when setting/getting variables. In the case I noted, I believe I saw an issue instantly and just addressed it this way. – TheCodingArt Nov 16 '15 at 01:10
  • According to this exchange I changed my sample to handle threads. Thanks for sharing ideas! – Leo Nov 22 '15 at 21:39
0

You can get a count without subclassing by adding a category and overriding alloc, This is really hacky but it's fine for testing and good for classes you don't own. I have no idea if this would work without ARC.

@implementation UILabel (LableCounter)
static NSInteger labelCount = 0;
+ (id)alloc
{
    labelCount++;
    return [super alloc];
}
-(void)dealloc{
    labelCount--;
}
@end  
dave234
  • 4,793
  • 1
  • 14
  • 29
  • 3
    No, do not do this. Categories must not attempt to override methods. It is undefined behavior. – rmaddy May 28 '15 at 17:18
  • 1
    [super alloc] in a category method is also bad, bad, bad! Say you instantiate an NSObject this way. In that case there is no `[super alloc]` because there's no superclass for NSObject! – bdrell May 28 '15 at 17:26
  • The only reason this hack works is that it's effectively calling up the inheritance chain to [NSObject alloc] in the category's alloc. So yes this wouldn't work for NSObject itself. – dave234 May 28 '15 at 17:35
  • Actually you can completely do this, you just have to swizzle the methods @rmaddy – TheCodingArt May 28 '15 at 19:18
  • 2
    @Dave The problem is that this hack doesn't work. Actually the bigger problem is that is might work or it might not work. There is no guarantee that the overridden methods provided in the category will actually be called. As I said earlier, overriding methods in a category is undefined behavior. It may or may not work. It must not be done. – rmaddy May 28 '15 at 20:05
  • See http://stackoverflow.com/questions/1085479/override-a-method-via-objc-category-and-call-the-default-implementation – rmaddy May 28 '15 at 20:08