You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Combine框架中实现RxSwift的methodInvoked功能?

在Combine中实现RxSwift的methodInvoked同款功能

RxSwift的methodInvoked通过方法交换机制,在目标方法执行时发送包含参数的事件。下面是基于Combine框架的同款实现:

核心实现代码

import Combine
import UIKit

// 定义Combine兼容协议,为UIViewController提供Combine扩展入口
public protocol CombineCompatible {}
extension UIViewController: CombineCompatible {}

public class CombineExtension<Base> {
    let base: Base
    init(_ base: Base) {
        self.base = base
    }
}

public extension CombineCompatible where Self: UIViewController {
    var combine: CombineExtension<Self> {
        get { CombineExtension(self) }
        set {}
    }
}

// 关联对象Key,用于存储每个Selector对应的Subject
private struct AssociatedKeys {
    static var methodInvokedSubjects = "methodInvokedSubjects"
}

public extension CombineExtension where Base: UIViewController {
    /// 监听指定Selector对应的方法调用,返回包含方法参数的Publisher
    /// - Parameter selector: 要监听的方法Selector
    /// - Returns: 发送方法参数数组的AnyPublisher
    func methodInvoked(_ selector: Selector) -> AnyPublisher<[Any], Never> {
        let subject = PassthroughSubject<[Any], Never>()
        
        // 存储Subject到关联对象,避免被提前释放
        var subjects = objc_getAssociatedObject(base, &AssociatedKeys.methodInvokedSubjects) as? [Selector: PassthroughSubject<[Any], Never>] ?? [:]
        subjects[selector] = subject
        objc_setAssociatedObject(base, &AssociatedKeys.methodInvokedSubjects, subjects, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
        
        guard let originalMethod = class_getInstanceMethod(type(of: base), selector) else {
            return subject.eraseToAnyPublisher()
        }
        
        // 生成唯一的Swizzle Selector,避免重复命名冲突
        let swizzledSelector = Selector("swizzled_\(selector)")
        let methodTypeEncoding = method_getTypeEncoding(originalMethod)
        
        // 检查是否已经完成方法交换,避免重复操作
        guard class_getInstanceMethod(type(of: base), swizzledSelector) == nil else {
            return subject.eraseToAnyPublisher()
        }
        
        // 动态生成Swizzle方法的实现
        let swizzledIMP: IMP = imp_implementationWithBlock({ [weak base] (self: UIViewController, _ cmd: Selector, args: UnsafeMutableRawPointer...) -> UnsafeMutableRawPointer? in
            // 调用原方法逻辑
            let originalIMP = method_getImplementation(originalMethod)
            let originalFunc: @convention(c) (UIViewController, Selector, UnsafeMutableRawPointer...) -> UnsafeMutableRawPointer? = unsafeBitCast(originalIMP, to: type(of: originalFunc))
            let result = originalFunc(self, selector, args)
            
            // 收集方法参数(跳过self和cmd两个默认参数)
            let argumentCount = method_getNumberOfArguments(originalMethod)
            var arguments = [Any]()
            for index in 2..<argumentCount {
                let argPtr = args[index - 2]
                let argType = method_copyArgumentType(originalMethod, index)!
                let argValue = unsafeBitCast(argPtr, to: AnyObject.self)
                arguments.append(argValue)
                free(argType)
            }
            
            // 发送参数事件到Subject
            if let subjects = objc_getAssociatedObject(self, &AssociatedKeys.methodInvokedSubjects) as? [Selector: PassthroughSubject<[Any], Never>] {
                subjects[selector]?.send(arguments)
            }
            
            return result
        })
        
        // 添加Swizzle方法并交换原方法实现
        if class_addMethod(type(of: base), swizzledSelector, swizzledIMP, methodTypeEncoding) {
            let swizzledMethod = class_getInstanceMethod(type(of: base), swizzledSelector)!
            method_exchangeImplementations(originalMethod, swizzledMethod)
        }
        
        return subject.eraseToAnyPublisher()
    }
    
    // 封装常用的ViewController生命周期方法
    var viewWillAppear: AnyPublisher<[Any], Never> {
        methodInvoked(#selector(Base.viewWillAppear(_:)))
    }
    
    var viewDidAppear: AnyPublisher<[Any], Never> {
        methodInvoked(#selector(Base.viewDidAppear(_:)))
    }
    
    var viewWillDisappear: AnyPublisher<[Any], Never> {
        methodInvoked(#selector(Base.viewWillDisappear(_:)))
    }
    
    var viewDidDisappear: AnyPublisher<[Any], Never> {
        methodInvoked(#selector(Base.viewDidDisappear(_:)))
    }
}

使用示例

在ViewController中直接通过combine属性监听生命周期事件:

class SampleViewController: UIViewController {
    private var cancellables = Set<AnyCancellable>()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        // 监听viewWillAppear事件
        combine.viewWillAppear
            .sink { args in
                let animated = args.first as? Bool ?? false
                print("viewWillAppear, animated: \(animated)")
            }
            .store(in: &cancellables)
    }
}

注意事项

  • 方法交换时机:实现中会在第一次调用methodInvoked时完成方法交换,避免了全局初始化时的潜在问题,但要注意同一Selector只会交换一次。
  • 内存管理:使用weak base避免循环引用,关联对象存储的Subject会随ViewController一起释放。
  • 参数兼容性:目前实现仅支持Objective-C兼容的方法(即带有@objc标记的方法),Swift纯方法无法通过Selector监听。

内容的提问来源于stack exchange,提问作者Jiss

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.11 17:35:14