查找所有的Cocoa单例类?

3

在Cocoa中,有几个系统类是单例模式,比如UIApplication、NSNotificationCenter等。现在我想找到所有的单例模式类,你有什么建议可以快速找到它们吗?

我正在处理一个巨大的代码库,需要将系统单例对象与自定义单例对象分离开来。


在Xcode中,你可以按下command-shift-O(字母“oh”,而不是数字零)并搜索“shared”,然后你会看到一个相当有希望的开端。你也可以搜索“default”和“main”,以找到其他类似于单例模式的对象。但也许你可以详细说明一下你所说的“我需要将系统单例对象与定制单例分离”的确切含义。什么是“系统单例”,什么是这个“定制单例”?请编辑/澄清你的问题,而不是发表下面的评论。我正在努力理解你试图解决的问题... - Rob
您还可以通过按下command-2来使用“符号导航器”,然后再搜索“default”。您需要取消选择搜索框旁边的“仅显示项目定义的符号”选项。然后,您可以快速浏览并专注于那些class属性。 - Rob
1个回答

7
目标-C运行时黑客技巧!很有趣!
在我继续之前,我要声明的是,我永远不会建议将这样的代码放入实际运行的代码中,如果您这样做了,这完全不是我的错。然而,出于教育目的,这可能会很有趣/有趣。
由于Objective-C本身没有“单例”这个概念,所以这不会是一个精确的科学。基本上,我们只是寻找具有某些暴露的前缀的类方法的Objective-C类。如果我们找到其中之一,那么我们很有可能拥有一个单例。
有了这个想法:
#import <Foundation/Foundation.h>
#import <objc/runtime.h>

static BOOL ClassIsSingleton(Class class) {
    unsigned int methodCount = 0;
    Method *methods = class_copyMethodList(object_getClass(class), &methodCount);

    @try {
        for (unsigned int i = 0; i < methodCount; i++) {
            Method eachMethod = methods[i];

            // only consider class methods with no arguments
            if (method_getNumberOfArguments(eachMethod) != 2) {
                continue;
            }

            char *returnType = method_copyReturnType(eachMethod);

            @try {
                // only consider class methods that return objects
                if (strcmp(returnType, @encode(id)) != 0) {
                    continue;
                }
            }
            @finally {
                free(returnType);
            }

            NSString *name = NSStringFromSelector(method_getName(methods[i]));

            // look for class methods with telltale prefixes
            if ([name hasPrefix:@"shared"]) {
                return YES;
            } else if ([name hasPrefix:@"standard"]) {
                return YES;
            } else if ([name hasPrefix:@"default"]) {
                return YES;
            } else if ([name hasPrefix:@"main"]) {
                return YES;
            } // feel free to add any additional prefixes here that I may have neglected
        }
    }
    @finally {
        free(methods);
    }

    return NO;
}

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        NSMutableArray *singletons = [NSMutableArray new];

        int classCount = objc_getClassList(NULL, 0);

        Class *classes = (Class *)malloc(classCount * sizeof(Class));

        @try {
            classCount = objc_getClassList(classes, classCount);

            for (int i = 0; i < classCount; i++) {
                Class eachClass = classes[i];

                if (ClassIsSingleton(eachClass)) {
                    [singletons addObject:NSStringFromClass(eachClass)];
                }
            }
        }
        @finally {
            free(classes);
        }

        NSLog(@"Singletons: %@", singletons);
    }
    return 0;
}

1
非常聪明!对于每个返回对象的类方法,调用它,检查该对象是否是该类的实例。如果是,则再次调用该方法并比较两个对象的强等性。如果它们相同,那么这是更好的证据表明您找到了一个单例。 - danh
1
那似乎是一个很好的想法,直到有人引入这样一种方法:+(NSError *)eraseTheHardDrive;:-P - Charles Srstka
我也不喜欢这样 - 在许多情况下,alloc方法返回一个对象,而init方法则切换到另一个对象。这就是为什么模式中说“self = [[class alloc] init..]”。对于这个问题,没有正确的答案,只有启发式方法可以帮助你接近答案。(注意:我不明白init返回“唯一对象”如何符合单例模式的定义) - Jeff Laing
但我的观点是,如果你正在寻找单例,就不需要调用eraseTheHardDrive。 - Jeff Laing
1
是的,但出于Rob列出的原因,它并不真正起作用。NSFileManager就是一个很好的例子;它有一个单例defaultInstance,但其init方法会产生唯一的对象。在我看来,最好只是寻找命名约定,特别是因为大多数单例都希望您使用特殊的类方法而不是init方法。 - Charles Srstka
显示剩余3条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接