Swift 能进行无感知埋点 吗?

传统 OC 无感埋点依赖分类 +load 方法,在应用启动早期自动完成方法交换,实现完全无侵入。但 Swift 直接移除了 +load 支持,且默认方法采用静态派发,直接复用旧方案会出现漏统计、交换失效等问题。

  • 场景1:自定义业务类埋点(纯 Swift 实现)

仅针对项目内自己编写的类,用 Swift 原生特性即可实现,无需任何 OC 代码:

// 全局单次触发器,启动时自动执行
final class SwizzleTrigger {
    static let runOnce: Void = { print("埋点系统启动") }()
}

extension BaseViewController {
    // 原方法标记为动态派发
    @objc dynamic func viewAppear(_ animated: Bool) {
        self.viewAppear(animated)
    }
    
    // 原生替换方法
    @_dynamicReplacement(for: viewAppear(_:))
    private func hookedViewAppear(_ animated: Bool) {
        hookedViewAppear(animated)
        BuriedPointManager.trackPage("\(Self.self)") // 埋点采集
    }
}

// App启动入口触发一次即可
func application(_ app: UIApplication, didFinishLaunchingWithOptions opts: ...) -> Bool {
    _ = SwizzleTrigger.runOnce
    return true
}

这个方案仅能作用于你自己编写的、标记了 @objc dynamic 的方法,无法直接 Hook UIKit 等系统框架的原生方法,因为系统方法没有开启动态派发,@_dynamicReplacement 对其完全无效。

  • 场景2:系统类全局埋点(工业级稳定方案)

针对 UIKit 系统类(如 UIViewController、UIButton)的全量埋点,用极少量 OC 代码提供 +load 入口,核心逻辑全在 Swift 中实现,是目前业界标准方案:

  1. 新建极简 OC 分类,仅提供启动入口:
import "YourProject-Swift.h"
@implementation UIViewController (BuriedBoot)
+ (void)load {
    static dispatch_once_t once;
    dispatch_once(&once, ^{ [SwiftBuriedManager setupSwizzle]; });
}
@end
  1. Swift 中实现完整埋点逻辑:
import ObjectiveC
final class SwiftBuriedManager: NSObject {
    @objc static func setupSwizzle() {
        let original = selector(UIViewController.viewWillAppear(_:))
        let swizzled = selector(UIViewController.bp_viewWillAppear(_:))
        guard let oriM = class_getInstanceMethod(UIViewController.self, original),
              let swiM = class_getInstanceMethod(UIViewController.self, swizzled)
        else { return }
        method_exchangeImplementations(oriM, swiM)
    }
}

extension UIViewController {
    @objc private func bp_viewWillAppear(_ animated: Bool) {
        self.bp_viewWillAppear(animated)
        BuriedPoint.reportPage(NSStringFromClass(type(of: self)))
    }
}

这里用到的 OC 分类的 +load 执行顺序完全遵循 Objective-C Runtime 规则,不同继承链的类之间的分类 +load 执行顺序,完全由 Xcode Compile Sources 里的文件编译顺序决定,不要在不同分类的 +load 之间做逻辑依赖,避免后续调整文件顺序后埋点逻辑失效。

3条必守准则

  1. 不要在 AppDelegate 中才执行交换,会漏掉启动阶段的页面埋点,导致统计数据失真
  2. 所有交换逻辑必须用 dispatch_once 包裹,避免重复交换导致方法调用栈混乱
  3. 埋点采集代码保持轻量,所有上报操作异步执行,不阻塞主线程影响页面滑动性能

极端不推荐的兜底情况
如果你坚决拒绝引入任何 OC 代码,只能在 AppDelegate 的 didFinishLaunchingWithOptions 中直接调用 Swift 封装的 Runtime 交换函数。但这个方案有明确缺陷:执行时机晚于 OC +load,部分在 main 函数之前就初始化的系统类、启动页会完全漏掉埋点,仅适合小型 Demo 项目使用,绝对不建议在生产环境中落地。

©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容