【问题标题】:Swizzling +[NSObject initialize] method to log each class instantiationSwizzling +[NSObject initialize] 方法来记录每个类的实例化
【发布时间】:2022-08-15 00:49:52
【问题描述】:

基本上,我想在每次实例化类对象时打印。以下代码显示了意图。

@interface NSObject (ILogger)
+ (void)initialize;
@end

@implementation NSObject (ILogger)
+ (void)initialize
{
    NSLog(@\"Initializing %s\", class_getName([self class]));
}
@end

这不起作用,因为NSObject 已经有一个+initialize 方法,所以这种方法会导致未定义的行为。编译器还警告该问题:warning: category is implementing a method which will also be implemented by its primary class

一个想法是以某种方式调配+[NSObject initialize] 并进行日志记录。我该如何安全地做到这一点?

编辑:

也许我使用了错误的术语,但目标是知道应用程序中是否使用了一个类。如果一个类的对象很多,不需要每次都记录,一次就够了。

  • \"但目标是知道应用程序中是否使用了一个类。\" 这在一般情况下绝对是不可能的。 Objective-C 允许(并且通常使用)各种各样的恶作剧来创建对象。许多看起来像 NSObjects 的东西实际上从未调用过 -init(或 +alloc)。作为最著名的例子,考虑免费桥接类型。它们根本不是 ObjC 对象,即使它们看起来很像。我会考虑最接近 The Dreams Wind 的答案,但它不会接近完成,所以这取决于你真正想用这些信息做什么。
  • 除了@RobNapier,inititlize 的想法很容易被打破,如果某些类有自己的这个方法的实现而不调用super 实现。相反,我建议使用工具(观察一段时间内分配的所有对象并检查它们是否属于您感兴趣的类型)或 xcode 内存图。调试工具提供比运行时操作可能提供的更深入的分析。
  • 我同意如果没有详尽的启发式方法,这在一般情况下可能是不可能的。让它工作“足够好”可能需要一段时间,这对我的用例来说很好。谢谢你们的cmets。

标签: ios objective-c swizzling


【解决方案1】:

编辑答案后

您对使用 +[NSObject initialize] 方法跟踪类的首次使用是正确的。我不知道有什么更合适的。调酒看起来像这样:

#import <objc/runtime.h>

@implementation NSObject (InitializeLog)

+ (void)load {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        Method originalMethod = class_getClassMethod(self, @selector(initialize));
        Method swizzledMethod = class_getClassMethod(self, @selector(tdw_initializeLog));

        method_exchangeImplementations(originalMethod, swizzledMethod);

    });
}

+ (void)tdw_initializeLog {
    const char *className = class_getName(self);
    printf("Initializing %s\n", className);
    [self tdw_initializeLog];
}

@end

有几点需要建议:

  1. 如果派生类实现了这个方法并且没有调用[super initialize];initialize 不会回退到NSObject 实现(上面已经打乱了)。因此,对于从 Cocoa 类继承的任何自定义类,要么不实现此方法,要么在实现中的某处调用 [super initialize];
    + (void)initialize {
        [super initialize];
        ...
    }
    
    1. Cocoa 类很少像看起来那样简单。相当多的接口和类隐藏在同一个名称下,有时日志会有些误导(例如,代替NSNumber,您将得到NSValue 类报告)。因此,对任何从基金会课程中登出的人都持保留态度,并始终仔细检查它的来源(也准备好根本不会报告这些课程)。

    2. 第一次使用NSLog 也会触发一些类来初始化自己,并让它们调用+[NSObject initialize]。为了避免无限循环或 bad_access 错误,我决定使用printf 在我的实现中记录初始化的事实。


    原始答案

    + (void)initialize 方法与对象实例化关系不大,因为每个 Objective-C 都会调用它班级在您的客户端代码第一次使用它之前不久。如果给定类的子类没有实现此方法并且以后永远不会调用它,则它可能会被多次调用。因此,如果您想跟踪,这只是一个糟糕的选择对象实例化。

    但是,您可能仍然需要使用一些选项来跟踪对象实例化的情况。

    热血沸腾-[NSObject init]

    首先,我会考虑NSObjectinit 方法:

    #import <objc/runtime.h>
    
    @implementation NSObject (InitLog)
    
    + (void)load {
        static dispatch_once_t onceToken;
        dispatch_once(&onceToken, ^{
            Method originalMethod = class_getInstanceMethod(self, @selector(init));
            Method swizzledMethod = class_getInstanceMethod(self, @selector(initLog_tdw));
    
            method_exchangeImplementations(originalMethod, swizzledMethod);
    
        });
    }
    
    - (instancetype)initLog_tdw {
        self = [self initLog_tdw];
        if (self) {
            const char *className = class_getName([self class]);
            NSLog(@"Instantiating %s", className);
        }
        return self;
    }
    
    @end
    

    只要实例回退到-[NSObject init] 方法,它就可以正常工作。不幸的是,很多 Cocoa 类都没有这样做。考虑以下场景:

    NSObject *obj = [NSObject new]; // NSLog prints "Instantiating NSObject"
    NSString *hiddenStr = [[NSMutableString alloc] initWithString:@"Test"]; // NSLog is silent
    NSURL *url = [[NSURL alloc] initWithString:@"http://www.google.com"]; // NSLog is silent
    

    -[NSURL initWithString:]-[NSMutableString initWithString:] 以某种方式避免了 NSObject 的默认构造函数被调用。它仍然适用于没有任何花哨初始化的任何自定义类:

    @implementation TDWObject
    
    - (instancetype)initWithNum:(int)num {
        self = [super init];
        if (self) {
            _myNum = num;
        }
        return self;
    }
    
    @end
    
    TDWObject *customObj = [TDWObject new]; // NSLog prints "Instantiating TDWObject"
    TDWObject *customObjWithNum = [[TDWObject alloc] initWithNum:2]; // NSLog prints "Instantiating TDWObject"
    

    热血沸腾+[NSObject alloc]

    或者,您可以调整 alloc 方法:

    #import <objc/runtime.h>
    
    @implementation NSObject (AllocLog)
    
    + (void)load {
        static dispatch_once_t onceToken;
        dispatch_once(&onceToken, ^{
            Method originalMethod = class_getClassMethod(self, @selector(alloc));
            Method swizzledMethod = class_getClassMethod(self, @selector(tdw_allocLog));
    
            method_exchangeImplementations(originalMethod, swizzledMethod);
    
        });
    }
    
    + (instancetype)tdw_allocLog {
        id allocatedObject = [self tdw_allocLog];
        if (allocatedObject) {
            const char *className = class_getName([allocatedObject class]);
            NSLog(@"Allocating %s", className);
        }
        return allocatedObject;
    }
    
    @end
    

    它将拦截几乎所有 Cocoa 类的实例化(例外必须是一些结构方法,在这些方法中会发生特定于类的优化,例如 +[NSNumber numberWith..] 方法族),但还有其他问题需要注意。从alloc 方法返回的分配实例并不总是那么简单。例如。对于NSMutableString 上面的示例NSLog 将打印NSPlaceholderMutableString

    TDWObject *customObj = [TDWObject new]; // NSLog prints "Allocating TDWObject"
    TDWObject *customObjWithNum = [[TDWObject alloc] initWithNum:2]; // NSLog prints "Allocating TDWObject"
    NSObject *obj = [NSObject new]; // NSLog prints "Allocating NSObject"
    NSString *hiddenStr = [[NSMutableString alloc] initWithString:@"Test"]; // NSLog prints "Allocating NSPlaceholderMutableString"
    NSURL *url = [[NSURL alloc] initWithString:@"http://www.google.com"]; // NSLog prints "Allocating NSURL"
    

    这是因为 Foundation 框架 uses Class Cluster design pattern heavilyalloc 返回的实例通常是某种抽象工厂,后来 Cocoa 类利用它们来生成所请求类的具体实例。


    这两种方法都有自己的缺点,但我很难想出更简洁和可靠的方法。

【讨论】:

  • 感谢您的解释和回答!如果创建了多个实例,这种方法不会在同一个类上多次调用 NSLog 吗?例如,在创建相同类型的对象数组时。使用+initialize,每个类的日志只会打印一次。
  • @AK我编辑了我的答案以提供有关您要查找的内容的信息。
  • 工作得很好!非常感谢!将这个答案扩展到 swift 也会很好。
  • @AK对于 Swift,它不会是一个通用的解决方案,因为您可以在 Swift 中定义新类而无需子类化任何可可类(在这种情况下为NSObject
  • 啊太糟糕了,对于我的用例;)然后是 nvm
【解决方案2】:

我认为如果只需要记录,可以使用断点来执行此操作,我没有使用initialize 对其进行测试,但在我使用dealloc 的情况下确实有效,请注意,它可能会打印出比你实际需要的多得多并且速度慢下降表现:

  • 在 Xcode 中,转到断点导航器 (Cmd+8)
  • 在屏幕左下方,点击“+”,然后从菜单中选择“符号断点...”
  • 填写表格:
    • 符号:-[NSObject initialize]
    • 操作:选择“日志消息”
    • 输入:--- init @(id)[$arg1 description]@ @(id)[$arg1 title]@
    • 选择“将消息记录到控制台”
    • 选中“评估操作后自动继续”,这样 Xcode 就不会在断点处停止

【讨论】:

  • 这可能与 XCode 一起用于调试某些东西,但这种方法对于跟踪应用程序中的所有初始化对象是不可扩展的。
  • 没错,因为它会大大降低性能,但它既简单又安全。如果你想否则唯一的方法是使用method_exchangeImplementations
  • 澄清一下:+initialize 与创建对象实例无关。在这里你需要+alloc+initialize 在第一次使用类时被调用。此外,除了出于教育原因之外,以这种方式跟踪所有对象似乎不切实际。即便如此,也有更适合的分析工具。
  • >> +initialize 在第一次使用类时被调用。是的,这就是 +initialize 有用的原因,因为它只会在第一次创建对象时打印日志。
【解决方案3】:

我发现的唯一可能的解决方案是 swizzling -[NSObject init] 这仅在一个小型测试项目中进行了测试

我有一篇关于 swizzling 的文章,也许你会觉得有趣 medium article

extension NSObject {

    static let swizzleInit: Void = {
        DispatchQueue.once(token: "NSObject.initialize.swizzle") {
            let originalSelector = Selector("init")
            let swizzledSelector = #selector(swizzledInitialize)
            guard let originalMethod = class_getInstanceMethod(NSObject.self, originalSelector),
                  let swizzledMethod = class_getInstanceMethod(NSObject.self, swizzledSelector)
            else {
                debugPrint("Error while swizzling")
                return
            }
            method_exchangeImplementations(originalMethod, swizzledMethod)
        }
    }()

    @objc
    private func swizzledInitialize() -> Self? {
        debugPrint("\(Self.self) has been initialized")
        return swizzledInitialize()
    }
}

DispatchQueue.onceDispatchQueue.once gist 中实现

然后在应用委托...

class AppDelegate: UIResponder, UIApplicationDelegate {



    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        NSObject.swizzleInit
        // Override point for customization after application launch.
        return true
    }

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-05-18
    • 2012-04-04
    相关资源
    最近更新 更多