在 Swift 中,@objc 是一个属性声明,它用来将 Swift 的方法、属性、类等暴露给 Objective-C 运行时。它使得这些方法或属性可以通过选择器(selectors) 或其他与 Objective-C 相关的机制被访问或调用。
使用 @objc 的场景
@objc 主要用于以下几种情况:
1、方法:当希望 Swift 中的方法被 Objective-C 代码或运行时系统(如定时器、通知、KVO 等)调用时,需要使用 @objc。
2、属性:当希望某个 Swift 属性在 Objective-C 中也可见时,需要使用 @objc。
3、类:当希望某个 Swift 类在 Objective-C 中可以实例化或使用时,需要使用 @objc。
常见用途
1、暴露方法给 Objective-C
在 Swift 中,一些 API(如 Timer, NotificationCenter 等)会使用选择器来动态调用方法。因此,如果要在这些 API 中使用 Swift 方法,就需要使用 @objc 来使方法可通过选择器访问。
@objc func myMethod() {
print("Method called")
}
Timer.scheduledTimer(timeInterval: 1.0,
target: self,
selector: #selector(myMethod),
userInfo: nil,
repeats: true)
这里的 @objc 表示 myMethod 会被暴露给 Objective-C 运行时,并且 #selector(myMethod) 可以在定时器中被调用。
2、暴露属性给 Objective-C
如果希望 Swift 中的属性在 Objective-C 中也可以访问,可以通过 @objc 来暴露它们。特别是需要与 Objective-C 中的框架进行交互时,可能会有用。
class MyClass: NSObject {
@objc var myProperty: String = "Hello"
}
let obj = MyClass()
print(obj.myProperty) // 在 Objective-C 代码中同样可以访问该属性
注意,类需要继承自 NSObject,这是因为 Objective-C 运行时要求暴露给它的方法和属性必须在 NSObject 继承体系内。
3、暴露类给 Objective-C
@objc 还可以用来将 Swift 类暴露给 Objective-C 代码。通常会看到这种用法与继承 NSObject 或使用 Objective-C 框架(如 CoreData、UIComponents 等)结合使用。
@objc class MyClass: NSObject {
@objc func doSomething() {
print("Doing something!")
}
}
这样做之后,MyClass 就可以在 Objective-C 代码中被实例化,并且它的 doSomething 方法也可以通过选择器访问。
@objc 与 @objcMembers
除了 @objc 之外,Swift 还提供了一个名为 @objcMembers 的属性声明。这是一个更简化的方式,可以让类的所有成员(方法、属性等)都自动暴露给 Objective-C,而不需要逐个添加 @objc。
@objcMembers class MyClass: NSObject {
var name: String = "John"
func greet() {
print("Hello, \(name)")
}
}
使用 @objcMembers 时,类中的所有方法和属性都会被暴露给 Objective-C,而不需要每个方法或属性都手动加上 @objc。
@objc 和 @objc optional
@objc optional 用来表示某个方法是可选的,这对于协议中的方法特别有用。只有在对象中实现了该方法时,才会调用它。
@objc protocol MyProtocol {
@objc optional func optionalMethod()
}
class MyClass: NSObject, MyProtocol {
func optionalMethod() {
print("This is an optional method")
}
}
let obj = MyClass()
obj.optionalMethod?() // 如果没有实现,调用也不会崩溃
@objc optional 是在定义协议时使用的,它让方法变成可选方法,不实现也不会导致运行时错误。
@objc 与类型限制
类(Class):@objc 只能用于继承自 NSObject 的类,否则会导致编译错误。
方法和属性:这些通常用于与 Objective-C 框架交互时,或需要与 Objective-C 运行时交互的情形。
总结
@objc 用来将 Swift 的方法、属性和类暴露给 Objective-C 运行时系统。
它常用于与 Objective-C 动态特性(如选择器、KVO、通知等)交互时。
@objcMembers 可以让一个类的所有成员方法和属性都自动暴露给 Objective-C,而不需要逐个标注 @objc。
扩展知识
为什么需要暴露给Objective-C
在 Swift 中,暴露给 Objective-C 的主要目的是为了与 Objective-C 代码或框架进行交互,或者利用一些需要动态调用的特性。这些特性大部分是 Objective-C 运行时机制提供的,Swift 本身虽然具有静态类型和强类型检查,但为了与大量现存的 Objective-C 代码库和框架兼容,某些功能仍然需要暴露给 Objective-C。
1、与现有的 Objective-C 代码库和框架兼容
Apple 的许多框架和 API(如 UIKit, Foundation)仍然是基于 Objective-C。如果在 Swift 中使用这些框架和 API,它们可能会依赖 Objective-C 的运行时机制,特别是选择器(selector)、KVO(Key-Value Observing)、通知(Notification)等动态功能。
如果希望通过通知(NotificationCenter)或定时器(Timer)来执行某些方法,Swift 的方法需要暴露给 Objective-C,才能在运行时被通过选择器调用。
2、运行时动态特性:选择器、KVO 和通知机制
选择器(Selectors):Objective-C 运行时使用选择器机制来动态调用方法。比如,Timer 和 NotificationCenter 这样的机制依赖选择器来调用方法。为了让这些功能在 Swift 中工作,需要用 @objc 声明方法,让它们可以通过选择器被调用。
Key-Value Observing (KVO):KVO 是一种基于动态派发的机制,通常需要通过 Objective-C 的运行时系统来监听对象的属性变化。当使用 KVO 时,属性和方法必须是暴露给 Objective-C 的。
NotificationCenter:在 Swift 中,可能会使用 NotificationCenter 来发送和接收通知。在使用通知时,通知的观察者需要注册一个选择器方法,这个方法需要暴露给 Objective-C。
3、动态特性:方法交换(Method Swizzling)
在 Objective-C 中,方法交换(Method Swizzling) 是一个常见的技术,用于动态改变方法的实现。虽然 Swift 可以通过某些手段进行类似的操作,但通常仍需要通过 @objc 将方法暴露给 Objective-C 才能进行有效的动态替换。
4、与混合编程兼容
如果项目是混合编程(即同时使用 Swift 和 Objective-C 代码),@objc 是必需的,因为它让 Swift 的方法和属性能够被 Objective-C 代码访问,或者通过 Objective-C 的机制进行调用。比如,Objective-C 中的类或者方法如果需要调用 Swift 中的功能,就需要通过 @objc 来暴露这些方法。
举例:为什么 @objc 是必要的?
1、Timer 和 选择器
@objc func myMethod() {
print("Method called")
}
Timer.scheduledTimer(timeInterval: 1.0,
target: self,
selector: #selector(myMethod),
userInfo: nil,
repeats: true)
在这个例子中,myMethod 需要暴露给 Objective-C 运行时,因为 Timer 是基于 Objective-C 的运行时机制来工作,使用了选择器(selector)来调用方法。如果没有 @objc,myMethod 将无法被 Timer 通过选择器调用,代码就不能正常工作。
2、Key-Value Observing(KVO)
class MyClass: NSObject {
@objc dynamic var myProperty: String = "Hello"
}
let obj = MyClass()
obj.addObserver(self, forKeyPath: "myProperty", options: .new, context: nil)
在 KVO 中,myProperty 被标记为 @objc dynamic,这是为了使其能够被 Objective-C 运行时的 KVO 机制观察。如果没有 @objc,KVO 将无法工作,因为 KVO 是基于动态派发的,需要通过 Objective-C 的运行时系统来观察属性的变化。
3、NotificationCenter
class MyClass: NSObject {
@objc func didReceiveNotification(_ notification: Notification) {
print("Notification received")
}
}
NotificationCenter.default.addObserver(
self,
selector: #selector(didReceiveNotification(_:)),
name: .someNotification,
object: nil
)
在通知中心中,需要通过选择器来注册观察者。方法 didReceiveNotification(_:) 需要暴露给 Objective-C 运行时,因为它会被 NotificationCenter 作为选择器调用。如果没有 @objc,它就无法在 Objective-C 运行时系统中被注册并触发。
什么时候不需要使用 @objc?
1、纯 Swift 编程:如果完全在 Swift 环境中编程,不涉及与 Objective-C 框架的交互,或者没有使用需要选择器机制的 API,那么不需要使用 @objc。
2、静态类型的功能:Swift 具有强类型和静态类型检查系统,在这些情况下,方法和属性通常不需要动态调用,因此也不需要暴露给 Objective-C。
小结
暴露给 Objective-C 主要是为了与现有的 Objective-C 代码和框架进行兼容,特别是当这些代码依赖于 Objective-C 运行时的动态特性(例如,选择器、KVO、通知等)时。
动态特性:像 Timer、NotificationCenter 和 KVO 都依赖于选择器(selector) 和动态方法派发,因此需要用 @objc 来暴露方法。混合编程:如果项目是混合编程的,Swift 和 Objective-C 需要互相调用或交互,那么 @objc 是不可避免的。