This is my first article of 2017. It happens that you have read it. It is a kind of fate. Also wish you a healthy New Year and progress in the New Year! Is this an Easter egg? Wait, aren’t the eggs at the end?
01. KVO principle
KVO is the shorthand for key-value observing, and its principle is roughly as follows:
- 1. When an object has an observer, create a subclass of the object(class starting with NSKVONotifying_).
* 2. For each observed property, override the setter method ** 3. Call the following method in the overridden setter method to notify the observer: ** -willChangeValueForKey: **-didChangeValueForKey: *4. Remove overridden methods when an observer is removed- 5. Remove dynamically created subclasses when no observer is observing any property
These KVO principles in a large online search, after my careful testing, found that are worth discussing, so I specially wrote an article to explain my starting from the code to summarize the principle of KVO article [iOS] explore the principle of KVO with code (really original).
Here is a brief exploration of ObjC Kvo by Didi architect Sunnyxx. The principle of KVO is explained with detailed code.
The scenarios we use KVO in general are basically listening for changes in the value of an attribute. For example, if you have a Person class that has a weight attribute height, you can use KVO if you want to listen for changes in height.
But have you ever seen a situation where height is modified by the keyword readonly? I ran into it and couldn’t find it on Google, so we’re going to talk about it today.
02. Under what circumstances did you encounter this problem?
If you are a regular reader of mine and have seen a frame I wrote earlier
JPVideoPlayer source code, there is a detail, I was seriously thinking for a long time, tried four different ways to determine the implementation. Many of you may not have read it, but you can read my previous article in Jane’s Book:
This section describes how to encapsulate a video player that implements side-down playback and caching. 02, [iOS] imitation micro-blog video side below broadcast slide TableView automatic play tells how to achieve in the TableView slide play video, and is smooth, do not block the thread, without any stuck slide play video. We’ll also talk about the strategy used to determine which cell should play the video when the tableView scrolls.
Let me briefly describe the scenario for this problem. When we play the video, the image is displayed on an instance of AVPlayerLayer, so the framework needs the developer to pass in a video image carrier, showView, to display the video image, Add the AVPlayerLayer instance object to the showView layer.
Because JPVideoPlayer is a singleton, the framework should not hold the video carrier showView in strong form to prevent memory leaks when showView cannot dealloc after its parent control dealloc. So the framework’s holding of the showView is weak.
/** * The view of video will play on. */ @property (nonatomic, weak)UIView *showView;Copy the code
Now there is a use scenario, the user opens an interface, the interface needs to play the video, and then when the user closes the interface, the video needs to stop playing. This would certainly allow the developer to stop the video playing in the dealloc method of the interface, but I want to do it inside the framework without the developer having to worry about it.
So the task is to listen to showView’s dealloc and stop the video.
03. Solutions
I came up with four solutions to tackle this task. Let’s see.
03.1 Scheme 1: Hook
This is the easiest one for experienced developers to come up with. However, I did not adopt it in the end. I have a principle of “do not use hooks unless it is absolutely necessary, and the fewer hooks the better, especially in the framework”. If you are interested in hook (method interchange), you can read my previous short book article [iOS]1 line of code fast integration button delay handling (Hook practice).
If you want to hook the implementation, you can probably simply describe the process.
- Reload UIView’s categories
load
Method, write yourself in this methoddealloc
Methodological and systematicdealloc
Method to exchange. - In custom
dealloc
Method to determine the currentdealloc
的view
Is not currently hosting the video imageshowView
If so, notifyJPVideoPlayer
Stop the video.
As a note of caution, if you find that your hook method doesn’t work, you might want to check if the third party framework you introduced in your project has the same hook method as yours.
03.2 Scenario 2: Rewrite removeFromSuperLayer
If we focus on AVPlayerLayer, the image layer, we can also inherit AVPlayerLayer custom JPPlayerLayer, and then create custom JPPlayerLayer instance object to display the video image. Then override the removeFromSuperLayer method in the JPPlayerLayer instance object and expect to listen for the showView release in that method.
But the plan was fundamentally rejected.
The reason is that in our scenario, the removeFromSuperLayer method of the JPPlayerLayer instance object is not called first when showView Dealloc is called. Imagine that we now have a red redView and a green greenView, and we add the red redView to the greenView, and then when our green greenView dealloc, RedView will not receive removeFromSuperView calls.
3.3 Plan 3: KVO
This brings us back to where we started with KVO, so let’s start with an example.
In the project, we create a class Person and a Dog class, followed by the.h and.m files for Person.
#import <Foundation/Foundation.h>
@class Dog;
@interface Person : NSObject
/** dog */
@property(nonatomic, weak, readonly)Dog *aDog; // take care of a Dog -(void)careDog:(Dog *) Dog; @end#import "Person.h"
@interface Person()
@end
@implementation Person
-(void)careDog:(Dog *)dog{
_aDog = dog;
}
@end
Copy the code
M: I have a dog, but it’s not my dog. My friend left me with a weak dog. Man didn’t have a dog at first, so his friend gave him a foster dog. The implementation of boarding a dog is in the.m file.
#import "ViewController.h"
#import "Person.h"
#import "Dog.h"@interface ViewController () /** Person */ @property(nonatomic, strong)Person *aPerson; /** Dog */ @property(nonatomic, strong)Dog *aDog; @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; self.aPerson = [Person new]; [self.aPerson addObserver:selfforKeyPath:@"aDog" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:nil];
self.aDog = [Dog new];
[self.aPerson careDog:self.aDog];
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context{
NSLog(@"% @ % @ % @ % @", object, keyPath, change, context);
}
Copy the code
Now use KVO to detect changes in this person’s dog. But after the following line of code is executed, the console does not print anything.
[self.aPerson careDog:self.aDog];
Copy the code
In the meantime, I wrote the following line of code in the ‘touchesBegan’ method, clicked the screen, and didn’t print anything.
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
self.aDog = nil;
}
Copy the code
Why is that? The observeValueForKeyPath method is also implemented, but the change in the aDog value is not monitored. The problem lies in the keyword readonly. Remember the KVO principle above?
For each property observed, override its setter method. Call the following methods in the overridden setter method to notify the observer:
-willChangeValueForKey:
-didChangeValueForKey:
Copy the code
The readonly keyword causes the corresponding property to have no setter methods. So the next two methods are not added to the setter methods either. So, the wire’s gone, too.
Going back to where we started, we’re going to use KVO to listen for changes in the superLayer property of the AVPlayerLayer instance object, which is the showView dealloc, and if showView releases, The superLayer property of the AVPlayerLayer instance object will become nil, and the listener will be notified to stop playing the video.
Let’s look at the official header for the SuperLayer property of the AVPlayerLayer instance object:
/* The receiver's superlayer object. Implicitly changed to match the * hierarchy described by the `sublayers' properties.
*/
@property(nullable, readonly) CALayer *superlayer;
Copy the code
Unfortunately, it is readonly. So it is the same case as the example above, and the superlayer changes cannot be detected.
03.4 Scheme 4: Use timer NSTimer
Having rejected the above three options, I took the dumbest and most reliable approach to the problem. I decided whether to stop the video by adding a timer to check whether the showView was released.
The timer? You might think it’s a waste of resources. However, the timer I mean is not always running, the timer in the frame is bound to the video, if a video starts to play, a timer will be started, if the video stops, the timer will also be empty, does not occupy resources in the background.
04. How to use KVO to listen for readonly attributes?
Finally, what to do if you do have a situation where the property must be readonly and you want to use KVO to listen. This scheme can only be used for attributes of classes you create, but does not work for attributes of the system.
CareDog :(Dog *) Dog {[self willChangeValueForKey:@"aDog"];
_aDog = dog;
[self didChangeValueForKey:@"aDog"];
}
Copy the code
// Plan 2 is provided by an animated dragon
-(void)careDog:(id)dog{
[self setValue:dog forKey:@"dog"];
}
Copy the code
Scheme one is to help the system complete the two notifying observer methods it should have added to the setter methods.