Swift-@objc\@objcMember\dynamic

1.@objc

将Swift中的元素暴露给OC运行时,以便在OC中调用Swift代码。

常见的,如Swift中给按钮添加点击事件时,需要给selector添加@objc标记。因为SEL是OC运行时中的特性,runtime根据SEL查找函数的实现。要将按钮回调函数暴露给OC运行时,需要将其标记为@objc,从而让OC运行时进行消息派发。

1
2
3
4
5
6
7
8
9
10
11
class A: UIViewController {

override func viewDidLoad() {
let btn = UIButton.init(type: .custom)
btn.addTarget(self, action: #selector(btnCallback), for: .touchUpInside)
}

@objc func btnCallback(){

}
}

OC工程中调用Swift代码时,也需要将Swift代码标记为@objc:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
// OC中定义SWift类 编译后会生成”工程名-Swift.h“头文件,里面包括了自动生成的OC版代码
import Foundation

class SwiftFile : NSObject {
@objc var name : String?
@objc let nick : String

@objc init(name:String,nick:String) {
self.name = name
self.nick = nick
super.init()
}

@objc func swiftInstanceMethod(name:String) -> String {
self.name = name
print("name is:\(name)")
return name;
}

@objc class func swiftClassMethod(nick:String) -> String {
print("nick is:\(nick)")
return nick
}
}


// OC工程引用swift代码
#import "AppDelegate.h"
#import "ASDF-Swift.h" //导入 swift 文件对应的 OC 版头文件

@implementation AppDelegate

- (BOOL)application:(UIApplication *)application
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
SwiftFile *sf = [[SwiftFile alloc] initWithName:@"swift" nick:@"sf"];
sf.name = @"Swift4.1";
[sf swiftInstanceMethodWithName:@"Hello"];
[SwiftFile swiftClassMethodWithNick:@"newNick"];

return YES;
}
@end

2.@objcMember

标记Swift中的类,将其所有元素都暴露给OC,等同于为所有元素加上@objc

注意:这些元素不包括Swift独有而OC中没有与之对应的元素类型,如Tuple。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@objcMembers
class Animal : NSObject {
func eat() { } // 隐式添加了 @objc

// 不会添加 @objc, 因为OC中没有元组
func drink() -> (Int, Int)? {
return nil
}
}

extension Animal {
func call() { } // 隐式添加了 @objc
}

class Cat : Animal {
func miao() { } // 隐式添加了 @objc
}

extension Cat {
func paw() { } // 隐式添加了 @objc
}

为何有这个关键字呢?Swift3及其之前版本中继承自NSObject的类,编译器会给所有的非private类及其成员加上@objc。swift4之后苹果不再默认这么做,但提供了@objcMember关键字作为替代方案,给被其标记的类及其成员添加@objc关键字。

3.dynamic

标记类的成员,使其属性方法等进行动态派发。

被标记为@objc的元素不一定会变成动态派发,Swift依然可能会将其优化为静态调用。如果在Swift中确实需要用到动态派发机制,比如KVO或者方法交换等特性时,则需要将其标记为 dynamic。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
class People: NSObject {  //需要继承自NSObject
@objc dynamic var name = "" //@objc在Swift4之后必须要添加
@objc dynamic var age = 20
}

class ViewController: UIViewController {

let p = People.init()

override func viewDidLoad() {
p.addObserver(self, forKeyPath: #keyPath(People.name), options: [.new], context: nil)
p.addObserver(self, forKeyPath: "age", options: [.new], context: nil)
}

override func viewWillAppear(_ animated: Bool) {
p.name = "kitt"
p.age = 21
}

//监听回调
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
if keyPath == "name" {
print("new Name:\(change![NSKeyValueChangeKey.newKey] ?? "Default")")
}
else if keyPath == "age" {
print("new age:\(change![NSKeyValueChangeKey.newKey] ?? "Default")")
}
}

deinit {
print("Deinit")
p.removeObserver(self, forKeyPath: "name")
}
}

如果People类中去掉dynamic关键字,则给属性赋值时p.age = 21不会触发监听的回调。


Swift-@objc\@objcMember\dynamic
https://davidlii.cn/2018/09/05/swift-dynamic.html
作者
Davidli
发布于
2018年9月5日
许可协议