Swift笔记26:Selector、@objc、dynamic、KVC\KVO、关联对象、资源名管理

选择器(Selector)

Swift中依然可以使用选择器,使用#selector(name)定义一个选择器
必须是被@objcMembers或@objc修饰的方法才可以定义选择器

perform(@Selector)和 @selector()本来就是Runtime,OC的。
没有添加@objc会提示:没有暴漏给OC的方法名是不能使用的

下面两例分别是perform(@Selector)和 @selector()

@objcMembers class Temp: NSObject {
    func test1(v1: Int) { print("test1") }
    func test2(v1: Int, v2: Int) { print("test2(v1:v2:)") }
    func test2(_ v1: Double, _ v2: Double) { print("test2(_:_:)") }
    func run() {
        perform(#selector(test1))
        perform(#selector(test1(v1:)))
        perform(#selector(test2(v1:v2:)))
        perform(#selector(test2(_:_:)))
        perform(#selector(test2 as (Double, Double) -> Void))
    }
}
var temp = Temp()
temp.run()
override func viewDidLoad() {
    super.viewDidLoad()
    let button = UIButton()
    view.addSubview(button)
    button.setTitle("测试", for: .normal)
    button.layer.borderWidth = 1
    button.layer.borderColor = RD_RandomColor().cgColor
    button.backgroundColor = RD_RandomColor()
    button.addTarget(self, action: #selector(btnMethod), for: UIControl.Event.touchUpInside)
    button.snp.makeConstraints { make in
        make.center.equalToSuperview()
        make.size.equalTo(CGSizeMake(200, 100))
    }
}

@objc func btnMethod() {
    print("点击了测试按钮")
}
@objc

1 OC调用swift,使用@objc修饰swift文件的属性或方法,选择性暴露给OC。
2 OC调用swift,可以通过 @objc 重命名Swift暴露给OC的符号名(类名、属性名、函数名等)
3 被 @objc 修饰的协议,还可以暴露给OC去遵守实现
4 可以通过 @objc 定义可选协议(这种协议只能被 class 遵守)
5 必须是被@objcMembers或@objc修饰的方法才可以定义选择器。
我们给button,手势,添加方法。也要用到@objc.原理同1,没有暴漏给OC的方法名是不能使用的。
perform(@Selector)和 @selector()本来就是Runtime,OC的。

只能被class继承的协议

方法一:protocol Runnable1: AnyObject {}
方法二:protocol Runnable2: class {}
方法三:@objc protocol Runnable3 {}

可选协议

可以通过 @objc 定义可选协议(这种协议只能被 class 遵守)

@objc protocol Runnable {
    func run1()
    @objc optional func run2()
    func run3()
}

class Dog: Runnable {
    func run3() { print("Dog run3") }
    func run1() { print("Dog run1") }
}

var d = Dog()
d.run1() // Dog run1
d.run3() // Dog run3
dynamic

被 @objc dynamic 修饰的内容会具有动态性,比如调用方法会走runtime那一套流程

class Dog: NSObject {
    @objc dynamic func test1() {}
    func test2() {}
}
KVC\KVO

Swift 支持 KVC \ KVO 的条件
1 属性所在的类、监听器最终继承自 NSObject
2 用 @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 = .init()
    override init() {
        super.init()
        addObserver(observer,
                    forKeyPath: "age",
                    options: .new,
                    context: nil)
    }

    deinit {
        self.removeObserver(observer,
                            forKeyPath: "age")
    }
}
var p = Person()
// observeValue Optional(20)
p.age = 20
// observeValue Optional(25)
p.setValue(25, forKey: "age")

block方式的KVO
class Person: NSObject {
    @objc dynamic var age: Int = 0
    var observation: NSKeyValueObservation?
    override init() {
        super.init()
        observation = observe(\Person.age, options: .new) {
            _, change in
            print(change.newValue as Any)
        }
    }
}
var p = Person()
// observeValue Optional(20)
p.age = 20
// observeValue Optional(25)
p.setValue(25, forKey: "age")
关联对象(Associated Object)

在Swift中,class依然可以使用关联对象
默认情况,extension不可以增加存储属性。借助关联对象,可以实现类似extension为class增加存储属性的效果
objc_getAssociatedObject是Foundation内的接口,纯Swift开发需要导入。
KEY建议用Void?或者BOOL类型,只用一个字节。因为我们只需要一个唯一的地址值做key。

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)
        }
    }
}
var p = Person()
print(p.age) // 0
p.age = 10
print(p.age) // 10
资源名管理

仿Android思路
方式一:我们通过枚举来管理各种资源的名称,再通过扩展设置需要的方法。

typealias AM = AssertsManager
enum AssertsManager {
    enum string: String {
        case add = "添加"
    }
    enum image: String {
        case logo = "pic"
    }
    enum segue: String{
        case login_main
    }
}
extension UIImage {
    convenience init?(_ name: AssertsManager.image) {
        self.init(named: name.rawValue)
    }
}
extension UIViewController {
    func performSegue(withIdentifier identifier: AssertsManager.segue, sender: Any?) {
        performSegue(withIdentifier: identifier.rawValue, sender: sender)
    }
}
extension UIButton {
    func setTitle(_ title: AssertsManager.string, for state: UIControl.State) {
        setTitle(title.rawValue, for: state)
    }
}


struct temp{
    let img = UIImage(AssertsManager.image.logo)
    let btn = UIButton(type: .custom)
}
btn.setTitle(R.string.add, for: .normal)
performSegue(withIdentifier: R.segue.login_main, sender: self)

方法二:

typealias AM = AssertsManager

enum AssertsManager {
    enum image {
        static var logo = UIImage(named: "tab4_select")
    }

    enum font {
        static func arial(_ size: CGFloat) -> UIFont? {
            UIFont(name: "Arial", size: size)
        }
    }
}
let imgView = UIImageView.init(frame: CGRect(x: 100, y: 200, width: 100, height: 100))
imgView.image = AM.image.logo
view.addSubview(imgView)

/* 使用示例
 let img = AM.image.logo          //let img = UIImage(named: "logo")
 let font = AM.font.arial(14)     //let font = UIFont(name: "Arial", size: 14)
 */

更多优秀开源参考
https://github.com/mac-cain13/R.swift
https://github.com/SwiftGen/SwiftGen

©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容