协议、关联对象、KVO等Swift和OC的关系。
一、协议
1.1. 只能被class继承的协议
示例代码:
protocol Runnable1: AnyObject {
}
protocol Runnable2: class {
}
@objc protocol Runnable3 {
}
被@objc
修饰的协议,还可以暴露给OC去遵守实现。
1.2. 可选协议
正常情况下,Swift定义的协议内容都需要实现,如果需要可选实现,可以定义一个协议扩展,在扩展中空实现需要可选实现的协议。
也可以通过@objc
定义可选协议,这种协议只能被class
遵守。
示例代码:
@objc protocol Runnable {
@objc optional func run1()
func run2()
func run3()
}
class Dog: Runnable {
func run2() {
print("Dog run2")
}
func run3() {
print("Dog run3")
}
}
var d = Dog()
d.run2() // 输出:Dog run2
d.run3() // 输出:Dog run3
加上optional
就必须加@objc
。并且只能被类实现协议。
二、dynamic
被@objc、dynamic
修饰的内容会具有动态性,比如调用方法会走runtime
那一套流程。
示例代码:
class Dog: NSObject {
@objc dynamic func test1() {
}
func test2() {
}
}
var d = Dog()
d.test1()
d.test2()
test1
汇编(消息转发):
test2
汇编(虚表):
三、KVC/KVO
Swift支持KVC/KVO的条件:
- 属性所在的类、监听器最终继承自
NSObject
(因为OC的KVC/KVO走的是runtime
,而使用runtime
必然会用isa
,isa
又是NSObject
的) - 用
@objc dynamic
修饰对应的属性
示例代码:
class Observer: NSObject {
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
print("observeValue", change?[.newKey] as Any)
}
}
class Person: NSObject {
@objc dynamic var age: Int = 0
var observer: Observer = Observer()
override init() {
super.init()
self.addObserver(observer, forKeyPath: "age", options: .new, context: nil)
}
deinit {
self.removeObserver(observer, forKeyPath: "age")
}
}
var p = Person()
p.age = 20 // 输出:observeValue Optional(20)
p.setValue(30, forKey: "age") // 输出:observeValue Optional(30)
如果觉得上面的代码还需要初始化观察者对象比较麻烦,还可以使用block
方式的KVO。
示例代码:
class Person: NSObject {
@objc dynamic var age: Int = 0
var observation: NSKeyValueObservation?
override init() {
super.init()
observation = observe(\Person.age, options: .new) {
(person, change) in
print("Block", change.newValue as Any)
}
}
}
var p = Person()
p.age = 20 // 输出:Block Optional(20)
p.setValue(30, forKey: "age") // 输出:Block Optional(30)
注意监听的属性前面要加上斜杠\
。
四、关联对象
在Swift中,class
依然可以使用关联对象。
默认情况下,extension
不可以增加存储属性。借助关联对象,可以实现类似extension
为class
增加存储属性的效果。
示例代码:
class Person {
}
extension Person {
private static var AGE_KEY: Void?
var age: Int {
get {
(objc_getAssociatedObject(self, &Self.AGE_KEY) as? Int ) ?? 0
}
set {
objc_setAssociatedObject(self, &Self.AGE_KEY, newValue, .OBJC_ASSOCIATION_ASSIGN)
}
}
}
关联对象的本质就是键值对,但是需要我们自己绑定一个编译期已知地址值(静态存储属性)。由于仅需要地址绑定,所以为了内存空间考虑,静态存储属性使用Bool
类型或者Void?
类型最好(仅占用1个字节)。
五、资源名管理
在项目开发中,经常有可能会遇到一张图片好多地方在使用(图片名为标识),一个标题很多地方使用等等。其实我们可以把这些相同的资源统一起来做一个标识符,防止后期多处修改。
示例代码一:
let img = UIImage(named: "logo")
let btn = UIButton(type: .custom)
btn.setTitle("按钮", for: .normal)
performSegue(withIdentifier: "login_main", sender: self)
如上面示例代码,如果很多地方都用到了名字为logo
的图片,描述为按钮
的文字等,一旦遇到修改,简直是地狱一般(虽然可以全局修改,但是有可能会一键修改到工程死翘翘)。
遇到这种资源名管理,我们可以有多种处理方式。下面介绍下参考Android的资源名管理方式:
示例代码二:
enum R {
enum string: String {
case add = "按钮"
}
enum image: String {
case logo
}
enum segue: String {
case login_main
}
}
let img = UIImage(named: R.image.logo)
let btn = UIButton(type: .custom)
btn.setTitle(R.string.add, for: .normal)
performSegue(withIdentifier: R.segue.login_main, sender: self)
R
代表Resource
,Swift仿Android的写法,Swift主要利用了枚举的关联值。
示例代码三:
// 原始
let img = UIImage(named: "logo")
let font = UIFont(name: "Arial", size: 17)
// 封装资源管理
enum R {
enum image {
static var logo = UIImage(named: "logo")
}
enum font {
static func arial(_ size: CGFloat) -> UIFont? {
UIFont(name: "Arial", size: size)
}
}
}
// 使用资源管理
let img = R.image.logo
let font = R.font.arial(15)
上面对image
的封装有两点考量:
-
可以直接通过名称返回一个
Image
对象。 -
静态属性在内存中只有一份,后面任何地方再次用到时可直接从内存中加载数据(除非需要每次都加载新数据)。
更多优秀的思路参考:
- https://github.com/mac-cain13/R.swift
- https://github.com/SwiftGen/SwiftGen
转载:https://blog.csdn.net/yangbenben8866/article/details/116481836