Protocol, associated object, KVO relationship between Swift and OC.

One, the agreement

1.1. A protocol that can only be inherited by class

Sample code:

protocol Runnable1: AnyObject {}protocol Runnable2: class {}@objc protocol Runnable3 {}Copy the code

A protocol decorated with @objc can also be exposed to the OC to comply with the implementation.

1.2. Optional protocols

Under normal circumstances, all the protocol content defined by Swift needs to be implemented. If optional implementation is required, a protocol extension can be defined. In the extension, the optional implementation of the protocol is required.

You can also define an optional protocol via @objc that can only be observed by the class.

Sample code:

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

class Dog: Runnable {
    func run2(a) {
        print("Dog run2")}func run3(a) {
        print("Dog run3")}}var d = Dog()
d.run2() // Output: Dog run2
d.run3() // Output: Dog run3
Copy the code

If you add optional, you have to add @objc. And can only be implemented by classes.

Second, the dynamic

Things decorated with @objc or dynamic will be dynamic, such as calling a method through a Runtime process.

Sample code:

class Dog: NSObject {
    @objc dynamic func test1(a){}func test2(a){}}var d = Dog()
d.test1()
d.test2()
Copy the code

test1Assembly (message forwarding) :

test2Assembly (virtual table) :

Third, KVC/KVO

Conditions for Swift to support KVC/KVO:

  • The class, listener of the property, eventually inherits fromNSObject(Because OC’s KVC/KVO go isruntime, while the use ofruntimeIs bound to be withisa.isaagainNSObject)
  • with@objc dynamicModify the corresponding property

Sample code:

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(a)override init(a) {
        super.init(a)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)
Copy the code

If the above code is too cumbersome to initialize the observer object, you can also use block KVO.

Sample code:

class Person: NSObject {
    @objc dynamic var age: Int = 0
    var observation: NSKeyValueObservation?
    override init(a) {
        super.init()
        observation = observe(\Person.age, options: .new) {
            (person, change) in
            print("Block", change.newValue as Any)}}}var p = Person()
p.age = 20 // Output: Block Optional(20)
p.setValue(30, forKey: "age") // Output: Block Optional(30)
Copy the code

Note that the listening property is preceded by a slash \.

4. Associated objects

In Swift, classes can still use associated objects.

By default, Extension cannot add storage attributes. With associated objects, you can achieve a similar effect to extension adding storage attributes to a class.

Sample code:

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)}}}Copy the code

Associative objects are essentially key-value pairs, but we need to bind a compile-time address value (statically stored property) ourselves. Since only address binding is required, static storage properties use Bool or Void? For memory space purposes. Best type (only 1 byte).

5. Resource name management

In project development, it is often possible to encounter an image in many places (image name logo), a title in many places, and so on. In fact, we can unify these same resources to make an identifier, to prevent multiple changes later.

Example code 1:

let img = UIImage(named: "logo")
let btn = UIButton(type: .custom)
btn.setTitle("Button", for: .normal)

performSegue(withIdentifier: "login_main", sender: self)
Copy the code

Such as the above example code, if many places are used in the name of the LOGO picture, described as the text of the button, etc., once encountered modification, it is simply hell (although you can modify the global, but there may be a key to modify the project dead).

There are several ways to deal with this resource name management. The following describes the resource name management mode referring to Android:

Example code 2:

enum R {
    enum string: String {
        case add = "Button"
    }
    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)
Copy the code

R stands for Resource, Swift is a copy of Android, Swift mainly uses the associated values of enumeration.

Example code 3:

/ / the original
let img = UIImage(named: "logo")
let font = UIFont(name: "Arial", size: 17)

// Encapsulate resource management
enum R {
    enum image {
        static var logo = UIImage(named: "logo")}enum font {
        static func arial(_ size: CGFloat) -> UIFont? {
            UIFont(name: "Arial", size: size)
        }
    }
}

// Use resource management
let img = R.image.logo
let font = R.font.arial(15)
Copy the code

There are two considerations for image encapsulation above:

  1. You can return an Image object directly by name.

  2. Static attributes have only one copy in memory, and data can be loaded directly from memory when used again anywhere later (unless new data needs to be loaded every time).

More excellent ideas for reference:

  • Github.com/mac-cain13/…
  • Github.com/SwiftGen/Sw…

For more articles in this series, please pay attention to wechat official account [1024 Planet].