Home>

What is the observer pattern What is the observer pattern?Have you ever subscribed to a newspaper?When subscribing to a newspaper,You don't have to go anywhereJust tell your publisher your personal address information and subscription information,The publisher knows how to deliver the relevant newspaper,This article introduces everyone to the iOS observer mode.Let's take a look with interested friends

What is the observer pattern?Let's make an analogy,It's like you order a newspaper.For example, i want to know that the U.S. has recently released some news,You might subscribe to an American weekly,And then once the United States has a new story,U.S. Weekly published one,And mail it to you,When you receive this newspaper,Then you will be able to learn about the latest developments in the United States.Actually this is the observer mode,a is interested in the change in b,As an observer registered as b,Notify a when b changes, telling b that there has been a change.This is a very typical observer usage,I call this usage the classic observer pattern.Of course, there is another observer mode, the general observer mode.

From a classic perspective,The observer mode is a mode of notifying changes,It is generally considered useful only in situations where the subject changes.The subject knows that there are observers,Set up a queue that will maintain observers;From a broad perspective,The observer pattern is a pattern that conveys changing data.A pattern that is used when you need to view object properties,The subject does not know the existence of the observer,More like onlookers.Need to know the status of the topic object,So even when the subject object has not changed,Observers may also visit subject objects.In other words, the generalized observer pattern,Is a pattern for passing data between different objects.

The observer pattern should be one of the design patterns used on a large scale in object-oriented programming.From a methodological perspective,Traditional epistemology holds thatThe world is made up of objects,We can understand the nature of the object through constant observation and understanding.The entire human cognitive model is based on the behavior of "observation".We keep interacting with other objects in the world,And observe to understand the world.Similarly, in the world of programs,Every instance we build,Also by constantly interacting with other objects (see the status of other objects,Or change the state of other objects), and by observing and responding to changes in other instances,Since the completion of the function.That is,Why is the observation model proposed separately?The reason for a special analysis-in my opinion he is the basis of many other design patterns,And it is an extremely important design pattern in programming.

Classic observer mode

The classical observer pattern is considered to be the behavior pattern of the object,It is also called a publish-subscribe mode, a model-view mode, a source-listener mode, or a dependents mode. The classic observer pattern defines a one-to-many dependency,Have multiple observer objects listen to a topic object at the same time.When this theme object changes state,Will notify all observers,Enable them to automatically update themselves or take some action accordingly.The example given at the beginning of the article is the application of the typical observer pattern.

And the implementation of the classic observer pattern that we may come into contact with in ios development,There are several types:nsnotificationcenter, kvo, delegate, etc.

Perceptual notification method

In classic observer mode,Because observers perceive different ways of changing subject objects,It can be divided into two modes:push model and pull model.

Push model

The topic object pushes the details of the topic to the observer,Whether or not the observer needs it,The information pushed is usually all or part of the data of the subject object.The push model achieves decoupling of observers and topic objects,There is no excessive dependency between the two.But the push model is broadcast every time,Send notifications to all observers.All observers passively accepted the notification.When there are too many notifications,Multiple observers receive at the same time,May have a greater impact on the network and memory (and sometimes io).

The typical push model implementation in ios is nsnotificationcenter and kvo.

nsnotificationcenter

nsnotificationcenter is a typical observer mode implementation with a dispatch center.With nsnotificationcenter as the center, the observer registers in the center and is interested in the change of a subject objectSubject objects broadcast changes through nsnotificationcenter.This model is a similar implementation in OC where articles start publishing and subscribing to newspapers.All observation and monitoring activities are registered with the same center,Changes to all objects are also broadcast outward through the same center.

snotificationcenter is like a hub,At the heart of the entire observer model,Schedules messages to pass between observers and listeners.

A complete observation process is shown in the figure above.Throughout the process,There are a few key classes (the order of introduction is the order of completion):

Observer observer, generally inherited from nsobject, registers the interest in a certain type of notification through the addobserver:selector:name:object interface of nsnotificationcenter.Be sure to pay attention when registering,nsnotificationcenter does not perform a reference count +1 operation on the observer. When we release the observer in the program,Be sure to report it out of the center.

-(void) handlemessage:(nsnotification *) nc {
//parse message content
nsdictionary * userinfo=[nc userinfo];
}
-(void) commoninit
{
//Register observer
[[nsnotificationcenter defaultcenter] addobserver:self selector:@selector (handlemessage :) name:kdztestnotificatonmessage object:nil];
}

Notification Center nsnotificationcenter, the hub of notifications.

Subject object,The object being observed,Send a certain type of notification via postnotificationname:object:userinfo:Broadcast changes.

-(void) postmessage
{
[[nsnotificationcenter defaultcenter] postnotificationname:kdztestnotificatonmessage object:nil userinfo:@{}];
}

Notification object nsnotification, when a notification comes,center will call the interface registered by the observer to broadcast the notification,The nsnotification object that stores the changes is also passed.

A few small problems that the notification center implemented by the apple version made me feel uncomfortable

When using nsnotificationcenter, from a programming perspective, we often don't just want to be able to achieve functions,You can also hope that the coding efficiency and the maintainability of the entire project are good.The observer mode implemented by nsnotificationcenter provided by Apple,The following disadvantages exist in maintainability and efficiency:

Every registered place needs to register a function at the same time,This will bring a lot of coding work.A careful analysis reveals thatIn fact, every function registered by each of our observers is almost the same.This is a disguised ctrlcv, which is typical ugly and difficult to maintain code.

Callback function for each observer,All need to unpack the message sent by the subject object.Parse the message out of userinfo by means of keyvalue,Then proceed.Just imagine,There are 100 places in the project, and at the same time, the operation of understanding the package in the function of responding to changes in the previous.When the later needs change needs to transmit one more content,It will be a maintenance disaster.

When using observer mode on a large scale,We often add a sentence to dealloc:

[[nsnotificationcenter defaultcenter] removeobserver:self]

In actual use,You will find that the performance of this function is relatively low.Throughout the startup process,Performed 10,000 removeobserver operations,

@implementation dzmessage
-(void) dealloc
{
[[nsnotificationcenter defaultcenter] removeobserver:self];
}
....
for (int i =;i<;i ++) {
dzmessage * message=[dzmessage new];
}

It can be seen from the figure below that this process consumes 23.4%of cpu, indicating that the efficiency of this function is still very low.

This is still the case in the presence of only one message type,If multiple message types are mixed in the entire notificationcenter,Then I'm afraid it will be disastrous for performance.

for (int i=0;i<10000;i ++) {
dzmessage * message=[dzmessage new];
[[nsnotificationcenter defaultcenter] addobserver:self selector:@selector (handle) name:[@ (i) stringvalue] object:nil];
}

After adding multiple message types,removeobserver occupies 63.9%of the cpu consumption during startup.

And because Apple does not provide the source code of the center, it is almost impossible to modify this center.

Improved version of dznotificationcenter

The github address is designed with the above uncomfortable parts in mind.Optimized:

The operation of unpacking to the execution function is encapsulated,You only need to provide the unpacking block of a certain message type and the protocol corresponding to the message type. When a message arrives,The message center will unify the package,And directly call the corresponding function of the observer.

Optimize the observer's maintenance mechanism (not yet finished) to improve the efficiency of finding and deleting observers.

The usage of dznotificationcenter is the same as where nsnotificationcenter registers and deregisters observers.The difference is thatWhen you use it, you need to provide a block to parse the message. You can provide it in two ways.

Ways to register directly

[dzdefaultnotificationcenter adddecodenotificationblock:^ sel (nsdictionary * userinfo, nsmutablearray * __ autoreleasing * params) {
nsstring * key=userinfo [@ "key"];
if (params!=null) {
* params=[nsmutablearray new];
}
[* params addobject:key];
return @selector (handletestmessagewithkey :);
} formessage:kdzmessagetest];

Implement the dznotificationinitdelegaete protocol. When observers are used on a large scale throughout the project,This method is recommended.This is conducive to unified management of all parsing methods.

-(dzdecodenotificationblock) decodenotification:(nsstring *) message forcenter:(dznotificationcenter *) center
{
if (message == kdzmessagetest) {
return ^ (nsdictionary * userinfo, nsmutablearray * __autoreleasing * params) {
nsstring * key=userinfo [@ "key"];
if (params!=null) {
* params=[nsmutablearray new];
}
[* params addobject:key];
return @selector (handleportmessage :);
};
}
return nil;
}

In the process of using,To ensure that the same function can be called back at the observer,Can implement protocol for a certain message type

@protocol dztestmessageinterface<nsobject>
-(void) handletestmessagewithkey:(nsstring *) key;
@end

This will ensure thatNo need to repeatedly spell function names and parse message content where observers are used.

@interface dzviewcontroller ()<dztestmessageinterface>
@end
@implementation dzviewcontroller
....
-(void) handletestmessagewithkey:(nsstring *) key
{
self.showlabel.text=[nsstring stringwithformat:@"get message with%@", key];
}
....

kvo

The full name of kvo is key-value observer, that is, key-value observer.It is an implementation of the observer model without a central hub.A topic object manages all observer objects that depend on it,It also actively notifies the observer object when its own state changes. Let's look at a complete example first:

static nsstring * const kkvopathkey [email protected]"key";
@implementation dzkvotest
-(void) setmessage:(dzmessage *) message
{
if (message!=_message) {
if (_message) {
[_message removeobserver:self forkeypath:kkvopathkey];
}
if (message) {
[message addobserver:self forkeypath:kkvopathkey options:nskeyvalueobservingoptionnew context:nil];
}
_message=message;
}
}
-(void) observevalueforkeypath:(nsstring *) keypath ofobject:(id) object change:(nsdictionary *) change context:(void *) context
{
if ([keypath isequal:kkvopathkey]&&object == _message) {
nslog (@ "get%@", change);
}
}
-(void) postmessage
{
_message.key [email protected]"asdfasd";
}
@end

Complete a complete change notification process,Go through the following processes:

Registered observer [message addobserver:self forkeypath:kkvopathkey options:nskeyvalueobservingoptionnew context:nil];

Change the value of a theme object property,I.e. trigger notification of change _message.key [email protected]"asdfasd";

In the specified callback function,Process received change notifications

-(void) observevalueforkeypath:(nsstring *) keypath ofobject:(id) object change:(nsdictionary *) change context:(void *) context
{
if ([keypath isequal:kkvopathkey]&&object == _message) {
nslog (@ "get%@", change);
}
}

Logout observer [_message removeobserver:self forkeypath:kkvopathkey];

kvo implementation principle

In general, for properties that use property, objc will automatically add a key-value observation function for it.You only need to write @property (noatomic, assign) float age to get the key observation function of age.And for a deeper discussion,kvo implementation principle we first implement kvo manually:

@implementation dzkvomanual
-(void) setage:(int) age
{
[self willchangevalueforkey:kkvopathage];
if (age!=_ age) {
_age=age;
}
[self didchangevalueforkey:kkvopathage];
}
//After verification, it will first call automaticallynotifies observersforkey:it will call automaticallynotifiesobserversofage when the function is not available. This function should be a compiler,A function added automatically,Use xcode to be prompted automatically.
It is really powerful.
//+ (bool) automaticallynotifies observersofage
//{
//return no;
//}
+ (bool) automaticallynotifies observersforkey:(nsstring *) key
{
if (key == kkvopathage) {
return no;
}
return [super automaticallynotifies observersforkey:key];
}
@end

First, you need to manually implement the setter method of the property, and call the willchangevalueforkey:and didchangevalueforkey methods before and after the setting operation. These two methods are used to notify the system that the key's property value is about to change and has changed;

Second, you must implement the class method automaticallynotifies observersforkey, and set in it not to automatically send notifications for the key (return no). Pay attention here,For other non-manually implemented keys, they must be transferred to super for processing.

Here's the manual implementation,The process of broadcasting the subject object changes is mainly implemented manually.How the follow-up broadcasts to the observer and how the observer responds to what we have n’t implemented,In fact, these two processes apple have been packaged well,Guess what,It should be that the subject object maintains a queue of observers,When its attributes change,When the notification is received,Find the observer queue for the relevant attribute,Call observevalueforkeypath:(nsstring *) keypath ofobject:(id) object change:(nsdictionary *) change context:(void *) context in order to broadcast the changes. One more question,Is that when the kvo is implemented automatically, does the system do the same thing as we do manually?

Automatically implement kvo and its principles

Let's take a closer look at what happened to an instance of the class dzmessage during the use of kvo:Before using kvo:

When the setter method is called and a breakpoint is hit:

Magically found that the isa pointer of the class has changed,Our original class was called dzmessage, and the class name became nskvonotifying_dzmessage after using kvo. This shows what objc does to our class at runtime.

We found some clues from Apple's document key-value observing implementation details.

automatic key-value observing is implemented using a technique called isa-swizzling. the isa pointer, as the name suggests, points to the object "s class which maintains a dispatch table. this dispatch table essentially contains pointers to the methods the class implements, among other data. 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. as a result the value of the isa pointer does not necessarily reflect the actual class of the instance. you should never rely on the isa pointer to determine class membership. instead, you should use the class method to determine the class of an object instance.

When an instance of a class uses kvo for the first time, the system will dynamically create a derived class of the class during runtime.The naming rules of this class are generally prefixed with nskvonotifying and suffixed with the original class name.And the prototype object's isa pointer points to the derived class.At the same time, the setter method using the kvo property is overloaded in the derived class, and the true notification mechanism is implemented in the overloaded setter method.Just as we previously implemented kvo manually. This is done based on setting the property to call the setter method, and through rewriting, we get the notification mechanism needed by kvo.Of course, the premise is to change the attribute value by following the kvo's attribute setting method.If only the member variables corresponding to the attributes are directly modified,It is impossible to achieve kvo.

The derived class also overrides the class method to "trick" the external caller that it was the original class.So this object becomes the object of the derived class,Therefore, a call to a setter on this object will call the rewritten setter, activating the key notification mechanism.In addition, the derived class also overrides the dealloc method to release resources.

Pull model

The pull model is when the subject object notifies the observer,Pass only a small amount of information or just notify of changes.If observers need more specific information,Observers actively pull data from subject objects.Compared to the push model,Pull model more freely,The observer just needs to know that something happened,As for when to get, what content to get, and even whether to get it can be decided independently.However, there are two problems:

If an observer responds too slowly,May miss previous notifications

The observer must keep a reference to the target object,But also need to understand the structure of the topic object,This makes the observer dependent on the subject object.

There may be some disadvantages to each design pattern,But they do solve the problem,There are also more useful places.When used,We need to weigh the pros and cons,Make a suitable choice.And the value of engineers is reflected inCan find the most effective one in the complex world of tools.And if the walnut is not broken,It's not that you have weak hands.But you chose the wrong tool,Who wants you to use a door clip?No hammer needed!

Of course, the above paragraph is off topic.Closer to home,In objc programming, a typical implementation of a pull model is delegate. Many people may disagree with me.Said that the delegate should be a delegation model.Okay i don't denyThe delegate is indeed an extremely typical implementation of the delegation pattern.But this does not preventHe is also an observer model.In fact, the design patterns are not clear-cut.When used and explained,As long as you can make sense,And just be able to solve the problem,No need to entangle their names.In the matter of notifying changes, delegates can indeed solve the problem.

Let's look at an example of an observer implementing a pull model using a delegate:

First implement a delegate to register observers,And callback function

@class dzclient;
@protocol dzclientchangeddelegate<nsobject>
-(void) client:(dzclient *) client didchangedcontent:(nsstring *) key;
@end
@interface dzclient:nsobject
@property (nonatomic, weak) id<dzclientchangeddelegate>delegate;
@property (nonatomic, strong) nsstring * key;
@end

Registered observer

//dzappdelegate
dzclient * client=[dzclient new];
client.delegate=self;
client.key [email protected]"aa";

When the properties of the theme object change,Notification of content changes

@implementation dzclient
-(void) setkey:(nsstring *) key
{
if (_key!=key) {
_key=key;
if ([_delegate respondstoselector:@selector (client:didchangedcontent :)]) {
[_delegate client:self didchangedcontent:@"key"];
}
}
}

After the observer receives notification of a change in the subject object,Take the initiative to pull the changed content.

//dzappdelegate
-(void) client:(dzclient *) client didchangedcontent:(nsstring *) key
{
if ([key isequal:@"key"]) {
nslog (@ "get changed key%@", client.key);
}
}

Generalized observer pattern

As mentioned above,After the observer passively accepts the observer pattern in the classical sense of the theme change,Let's look at the generalized observer pattern again.Of course the classic observer mode mentioned above,It is also a way to pass data.The broad observer covers the classic observer pattern.

Often we have data that needs to be passed between the "observer" and the "topic object".And in this case,The subject object may not be as hardworking as the subject object in classic observer mode,Broadcast constantly when changes occur.In the generalized observer model,Subject objects may be lazy,Instead, the observer constantly queries the status of the subject object,To learn what changed.

The server cs architecture that we are familiar with is always typical of the observer mode.The server is servo,Waiting for client access,The client accesses the server to get the latest content,Not the server's active push.

The reason is to come up with the concept of the broad observer pattern.To explore the nature of the observer pattern.So that we can better understand the observer pattern,And use it wisely.And we usually focus more on notifying changes,And the fundamental purpose of the observer is toBetween the observer and the subject,Pass on changing data.The data may be the change itself,It could also be something that changes,It might even be something else.

Thinking from the perspective of changing data transmission,There are countless models and strategies that can achieve this.Such as traditional network cs models, such as kvc and so on. Let's not discuss it in detail here.

The above is the ios observer design pattern introduced in this article.Hope you like it.

ios
  • Previous Follow me about global variables in javascript
  • Next jQuery implements the method of serializing form elements into json objects