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

如何通过代码改造规避#selector与@objc的使用?

问题描述

我编写了一个简单测试示例来阐述需求:有时我们需要编写依赖selector的代码,而这类selector要求对应的函数添加@objc修饰。我的目标是通过代码重构,尽可能规避@objc修饰符的使用,更理想的是完全不再使用selector。示例代码如下:

class MyTestAppClass {
    @objc func test() {
        print("Hello, world!")
    }
}

let test = NSMenuItem(title: "Test", action: #selector(MyTestAppClass.test), keyEquivalent: "t")

如示例所示,NSMenuItem需要传入selector,对应的函数必须添加@objc修饰。我希望优先避免使用selector,若无法实现则至少规避@objc。请问是否存在可行的实现方案?能否将代码改造成更具Swift风格的版本?


可行方案

1. 闭包封装:彻底摆脱selector与@objc

Cocoa的NSMenuItem基于Objective-C的selector机制,但我们可以通过扩展封装闭包回调,让上层代码用Swift原生的闭包处理点击事件,完全不需要@objc或手动管理selector。

实现逻辑是利用关联对象存储闭包,用一个私有的@objc方法作为桥接触发闭包——这个桥接方法是唯一需要@objc的地方,上层代码完全感知不到:

import Cocoa

private var ActionClosureAssociatedKey: UInt8 = 0

extension NSMenuItem {
    // 新增便利构造器,直接接收闭包
    convenience init(title: String, keyEquivalent: String, action: @escaping () -> Void) {
        self.init(title: title, action: #selector(triggerAction), keyEquivalent: keyEquivalent)
        // 将闭包绑定到NSMenuItem实例
        objc_setAssociatedObject(self, &ActionClosureAssociatedKey, action, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
    }
    
    // 私有桥接方法,触发闭包
    @objc private func triggerAction() {
        guard let action = objc_getAssociatedObject(self, &ActionClosureAssociatedKey) as? () -> Void else {
            return
        }
        action()
    }
}

// 改造后的使用示例
class MyTestAppClass {
    func test() { // 完全不需要@objc修饰
        print("Hello, world!")
    }
}

let testInstance = MyTestAppClass()
let testMenuItem = NSMenuItem(title: "Test", keyEquivalent: "t") {
    testInstance.test()
}

这种方式的优势:

  • MyTestAppClass的方法无需任何Objective-C相关修饰,保持纯Swift风格
  • 创建NSMenuItem时直接传入闭包,逻辑更直观,避免了selector字符串匹配的风险
  • 闭包可以直接捕获上下文变量,灵活性远超selector

2. 隐式暴露方法:规避手动添加@objc(备选)

如果必须保留selector机制,可以通过让类继承NSObject,让Swift自动将方法暴露给Objective-C,无需手动添加@objc:

class MyTestAppClass: NSObject { // 继承NSObject
    func test() { // 无需@objc修饰
        print("Hello, world!")
    }
}

let testMenuItem = NSMenuItem(title: "Test", action: #selector(MyTestAppClass.test), keyEquivalent: "t")

不过这种方式本质上还是依赖Objective-C的selector机制,只是省去了手动写@objc的步骤,风格上不如闭包方案纯粹。


总结

优先选择闭包封装方案,它完全脱离了Objective-C的selector约束,代码更符合Swift的函数式编程风格,同时彻底规避了@objc修饰符的使用,是最理想的改造方式。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.31 13:25:50