如何在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
相关产品推荐
相关产品推荐

