SoFunction
Updated on 2025-04-11

Details of using KVO in Swift and internal implementation analysis (recommended)

What is KVO?

KVO is an implementation of the observer design pattern by Objective-C. [The other is: notification mechanism (notification), for details, refer to: iOS Interesting Talk about Design Patterns - Notification];
KVO provides a mechanism to specify an object to be observed (such as Class A). When a certain property of the object (such as the string name in A) changes, the object will be notified and handled accordingly; [and there is no need to add any extra code to the object to be observed, the KVO mechanism can be used]
In projects under the MVC design architecture, the KVO mechanism is very suitable for communication between mode model and view view.

For example: in the code, create attribute data in model class A, create observers in the controller, and once the attribute data changes, the observer receives a notification, and then use a callback method in the controller to process the update of View B; (The application in this article is such an example.)

Implementation principle?

KVO's API documentation in Apple is as follows:

Automatic key-value observing is implemented using a technique called isa-swizzling… When an observer is registered for an attribute of an object the isa pointer of the observed object is modified, pointing to an intermediate class rather than at the true class …
The implementation of KVO relies on the powerful Runtime of Objective-C [refer to: Several small examples of Runtime]. From the above Apple's documentation, we can see that Apple's implementation of the KVO mechanism is a brief introduction, and there is no description of the specific details. However, we can explore the underlying implementation principles of the KVO mechanism through the methods provided by Runtime. For this reason, Zuo Zuo summarized the relevant content from some information about KVO on the Internet:

Basic principles:

When observing an object A, the KVO mechanism dynamically creates a subclass of the current class of object A and overrides the setter method of the observed property keyPath for this new subclass. The setter method is then responsible for notifying the observed object's properties to change.

OK, the following key contents of this article:

At the beginning of the text, let’s talk about a small detail first. Declare a class in swift. You can integrate it from NSObject or choose to ignore it. What is the difference between the two. Based on my own experience, I came to the following conclusions. Please point out the shortcomings. exmpl: We declare such a class

class Person: NSObject {
 var name: String?
 override init() {
  ()
 }
}
The memory address printed in this category is0x00000fbd00007ffeefbfc240

This code will not report an error. It is a typical way to write the Swift legacy ObjC syntax, but if we remove the NSObject and print out its memory address, as follows

class Person {
 var name: String?
 init() {
  
 }
}
The memory address printed in this category is0x00007ffeefbfc240
  • The memory addresses are different. The memory addresses of class objects inherited from NSObject are obviously 8 more lengths. Why? The extra 8 spaces are to store the isa pointer in the ObjC object. If you are interested, you can study it.
  • Classes inherited from NSObject can use some slut operations in OC, such as KVC, KVO, and runtime. Otherwise, an error will be reported when using setValue-forKey.

There are many differences, and you can pay more attention to this difference in development. I personally prefer not to inherit NSObject, especially when I need to do some tricks like this, such as KVO.

KVO is a feature of an OC object attribute. Since it is oriented towards strings, you need to be particularly careful when developing. This kind of crash will only cause an error if it is executed. Declare the following class:

class Person: NSObject {
  @objc var age: Int?
  var name: String?
  var observation: NSKeyValueObservation?
  
  override init() {
  ()
   = observe(\, options: .new, changeHandler: { (person, change) in
   print("New value = ",  as Any)
  })
 }
}

Externally, we initialize an object and assign a value to the age as follows

let person = Person()
 = 18
(100, forKey: "age")

After the program is executed, (ÒωÓױ)! Why is there only one print? It's supposed to printNew value = 18andNew value = 100Yes, but it doesn't:laughing:. What's the problem? It turns out that if you need to listen to a value in Swift, you must remember 2 keywords.

  • @objc
  • dynamic

otherwise,

Without @objc program, it will trigger a crash when listening; without dynamic, the set method of the attribute will not take effect, so naturally there will be no print above, because the essence of KVO is the set method of the monitoring attribute, and the addition and deletion operations of variable arrays will not take effect;

But why can KVC operation take effect? ​​This is because the implementation process of KVC is

  • [person willChangeValueForKey:@"age"];
  • person->_age = 10;
  • [person didChangeValueForKey:@"age"];
  • And didChangeValueForKey: will call the observeValueForKeyPath:ofObject:change:context: method, which triggers KVO

So the correct way to write it should be

class Person: NSObject {
  @objc dynamic var age: Int?
  var name: String?
  var observation: NSKeyValueObservation?
  
  override init() {
  ()
   = observe(\, options: .new, changeHandler: { (person, change) in
   print("New value = ",  as Any)
  })
 }
}

This is the end of this article about the details of using KVO in Swift and the internal implementation analysis. For more related content on using KVO in Swift, please search for my previous articles or continue browsing the related articles below. I hope everyone will support me in the future!