[iOS] Singleton, notification, proxy

1 Singleton mode

1.1 What is a singleton

The singleton mode is equivalent to a global variable in the entire project, that is, wherever the instance variables of this class are needed, they can be obtained through the singleton method. And once you create a singleton class, no matter how many times you use it, This singleton method is initialized in each interface to obtain objects. All their objects point to the same memory storage space (that is, the singleton class guarantees that the instance object of the class is the only one that exists).

1.2 Advantages and Disadvantages of Singleton Pattern

  • advantage

    • A class is instantiated only once, providing controlled access to a unique instance.
    • Save system resources.
    • A variable number of instances is allowed.
  • shortcoming

    • Having only one object in a class may cause excessive responsibilities and violate the "single responsibility principle" to a certain extent.
    • Since there is no abstraction layer in the singleton pattern, it is very difficult to extend the singleton class.
    • Abuse of singletons will bring about some negative problems. For example, in order to save resources, the database connection pool object is designed as a singleton class, which may cause too many programs to share the connection pool object and cause connection pool overflow; if the instantiated object is not used for a long time, If used, the system will consider it as garbage and be recycled, which will result in the loss of object state.

1.3 Implementation of singleton

There are two types of singleton implementations: lazy man style and hungry man style.

  • Lazy Man Style: As the name suggests, you will not instantiate a class unless absolutely necessary, that is to say, you will only instantiate a class instance when you use it for the first time.
  • Hungry Chinese style: If you are hungry, you will definitely be hungry, so instantiate it when the singleton class is loaded.

Features and Options:

  • Due to the need for thread synchronization, when the amount of access is relatively large, or there are many threads that may be accessed, using the hungry implementation can achieve better performance, which is to exchange space for time.
  • When the number of visits is small, the lazy implementation is adopted, which is to exchange time for space.

1.3.1 Lazy Man Style

  1. use@synchronized
static id manager = nil;
+ (instancetype)shareInstance {
    
    
	// 防止多次加锁
    if (!manager) {
    
    
        @synchronized (self) {
    
    
            if (!manager) {
    
    
                manager = [[super allocWithZone:NULL] init];
            }
        }
    }
    return manager;
}

The first if(!manager) judgment is to avoid multiple locks caused by multiple accesses after the object is created, which wastes performance. The second if(!manager) judgment is to judge whether the singleton exists at this time, and recreate it if it does not exist.

  1. Use GCD
+ (instancetype)shareInstance {
    
    
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
    
    
        manager = [[super allocWithZone:NULL] init];
    });
    return manager;
}

dispatch_once does not use heavyweight synchronization mechanism, its performance is better than the former, and it is more efficient.

dispatch_onceRegardless of whether multi-threading or single-threading is used, it is only executed once, ensuring performance while ensuring safety.

dispatch_onceMainly based on onceTokenthe value to decide how to execute the code:

  • When onceToken0, the thread executes the code dispatch_oncein block.
  • When onceToken-1, the thread skips the code dispatch_oncein block.
  • For onceTokenother values, the thread is blocked, waiting for onceTokenthe value to change.

dispatch_onceThe execution process:

  1. When the thread calls shareInstance, this time onceTokenis 0, the code in is executed, and the value in this time dispatch_onceis other value.blockonceToken
  2. At this time, if another thread calls shareInstancethe method again, onceTokenthe value will be another value and the thread will be blocked.
  3. When blockthe thread blockfinishes , onceTokenit becomes -1. Other threads are no longer blocked and skipped block.
  4. The next time it is called shareInstance, onceTokenit will be -1 and skip directly block.

1.3.2 Hungry Chinese Style

It is created when the class is loaded. Because a class will only be loaded once in the entire life cycle, it must only have one thread accessing it. Creating it at this time is thread-safe and there is no need to use thread locks. to ensure that it is not created multiple times.

static id manager = nil;

+ (void)load {
    
    
    [super load];
    manager = [[super allocWithZone:NULL] init];
}

+ (instancetype)shareInstance {
    
    
    return manager;
}

- (instancetype)copyWithZone:(NSZone *)zone {
    
    
    return manager;
}

- (instancetype)mutableCopyWithZone:(NSZone *)zone {
    
    
    return manager;
}

+ (instancetype)allocWithZone:(struct _NSZone *)zone {
    
    
    return manager;
}

1.4 About copying

According to the definition of a singleton, we can only allow one singleton object to exist, so we must control the creation of new objects of the singleton class to ensure the uniqueness of the object. So we have to override some methods that can create objects by ourselves, for example copy. mutableCopyIn order to completely ensure that users cannot create new objects of this singleton class, we usually override XXXWithZone:the method.

- (instancetype)copyWithZone:(NSZone *)zone {
    
    
    return manager;
}

- (instancetype)mutableCopyWithZone:(NSZone *)zone {
    
    
    return manager;
}

+ (instancetype)allocWithZone:(struct _NSZone *)zone {
    
    
    return manager;
}

2 notifications

2.1 A few points

  • Both the observer and the observed do not need to know each other. They only need to NSNotificationCenterfind the class corresponding to the notification through the mark, and then call the method of the class.
  • And in NSNotificationCenter, observers can only subscribe to a specific notification and perform corresponding operations in alignment, without having to update all notifications sent by a certain class.
  • NSNotificationCenterThe calls to observers are not random, but are executed one by one following the registration order and are synchronized within the thread.

2.2 Notification usage steps

The overall process is divided into three steps:

  1. Where parameters are to be passed, send notifications to the notification center:
[[NSNotificationCenter defaultCenter] postNotificationName:@"temp" object:nil userInfo:@{
    
    @"content": self.myTextField.text}];
  1. Register notifications where parameters are received and implement the definition method
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(Notificate:) name:@"temp" object:nil];
  1. Remove notifications when they are no longer needed
[[NSNotificationCenter defaultCenter] removeObserver:self];

2.3 Some questions

2.3.1 Are notifications sent synchronously or asynchronously? Are the threads that send messages and receive messages the same thread?

The notification center sends notifications to observers synchronously. It can also use notification queue ( NSNotificationQueue) to send notifications asynchronously.

After the notification is thrown, the observer will not continue execution until the observer completes the notification event processing (it can be tested by sleeping for 3 seconds), which means that the process is synchronous by default; when the notification is sent, the notification The center will wait until all observernotifications have been received and processed before returning poster.

The thread that receives the notification is the same thread that sends the notification. That is to say, if you want to update the UI when receiving a notification, you need to pay attention to whether the thread sending the notification is the main thread.

- (void)viewDidLoad {
    
    
    [super viewDidLoad];
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(test) name:@"NotificationName" object:nil];
}

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    
    
    dispatch_queue_t queue = dispatch_queue_create("test.queue", DISPATCH_QUEUE_SERIAL);
    dispatch_async(queue, ^{
    
    
        NSLog(@"--currect thread:%@", [NSThread currentThread]);
        NSLog(@"Begin post notification");
        [[NSNotificationCenter defaultCenter] postNotificationName:@"NotificationName" object:nil];
        NSLog(@"End");
    });
}

- (void)test {
    
    
    NSLog(@"--current thread:%@", [NSThread currentThread]);
    NSLog(@"Handle notification and sleep 3s");
    sleep(3);
}

Insert image description here

2.3.2 How to send notifications asynchronously?

  1. Let the notification event handling method be executed in the child thread.
- (void)viewDidLoad {
    
    
    [super viewDidLoad];
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(test) name:@"NotificationName" object:nil];
}

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    
    
    NSLog(@"--currect thread:%@", [NSThread currentThread]);
    NSLog(@"Begin post notification");
    [[NSNotificationCenter defaultCenter] postNotificationName:@"NotificationName" object:nil];
    NSLog(@"End");
}

- (void)test {
    
    
    dispatch_queue_t queue = dispatch_queue_create("test.queue", DISPATCH_QUEUE_SERIAL);
    dispatch_async(queue, ^{
    
    
        NSLog(@"--currect thread:%@", [NSThread currentThread]);
        NSLog(@"Handle notification and sleep 3s");
        sleep(3);
        NSLog(@"Test End");
    });
}

Insert image description here

  1. You can put the notification into the queue through the and methods to achieve asynchronous sending. After putting the notification into the queue, these methods will immediately return control to the calling NSNotificationQueueobject enqueueNotification: postingStyle:.enqueueNotification: postingStyle: coalesceMask: forModes:
- (void)viewDidLoad {
    
    
    [super viewDidLoad];
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(test) name:@"NotificationName" object:nil];
}

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    
    
    NSLog(@"--current thread:%@", [NSThread currentThread]);
    NSLog(@"Begin post notification");
    NSNotification *notification = [NSNotification notificationWithName:@"NotificationName" object:nil];
    [[NSNotificationQueue defaultQueue] enqueueNotification:notification postingStyle:NSPostASAP];
    NSLog(@"End");
}

- (void)test {
    
    
    NSLog(@"--current thread:%@", [NSThread currentThread]);
    NSLog(@"Handle notification and sleep 3s");
    sleep(3);
    NSLog(@"Test End");
}

Insert image description here

2.3.3 The relationship between NSNotificationQueue and runloop?

postringStyleParameters define runloopthe relationship between notification calls and status.
Three optional parameters for this parameter:

1.: NSPostWhenIdleThe notification callback method waits until the current thread runloopenters the waiting state before calling it.
2.: NSPostASAPThe notification callback method is called when the current thread runloopstarts to receive the event source.
3.: NSPostNowIn fact, it is the same as adding a notification directly using the default notification center. The notification calls the callback method immediately.

2.3.3 Will it crash if the notification is not removed when the page is destroyed?

Before the observer object is released, you need to call removeOberverthe method to remove the observer from the notification center, otherwise the program may crash. But starting from iOS9, even if the observer object is not removed, the program will not be abnormal.

This is because after iOS9, the observers held by the notification center unsafe_unretainedare changed from references weakto references. Even if the observer is not manually removed, the reference held by the observer will be automatically cleared after the observer is recycled. However, observers registered through addObserverForName:object: queue:usingBlock:the method need to be released manually, because the notification center holds strong references to them.

2.3.4 What will be the consequences of adding the same notification multiple times? What about multiple removal notifications?

  • If you add the same notification multiple times, the observer method will be called multiple times.
  • Removed multiple times, no problem.

2.3.5 Why can I register with an empty name when registering notifications, but not when sending notifications?

  • When registering for notifications, an empty notification name means listening for all notifications. This can be useful in certain situations, especially when you want to listen to all notifications sent by an object, not just specific notification names.
  • You cannot use an empty notification name when sending a notification. This is because you need to specify a specific notification name when sending a notification so that the notification center can correctly send the notification to the corresponding observer. If you use an empty notification name when sending a notification, the notification center will not be able to determine which observers the notification should be sent to, which will cause the notification to not be processed correctly.

2.3.6 What is object used for? Can it be used to pass values?

When adding an observer, objectthe parameter specifies the sender of the notification. Only when the sender of the notification objectis the same as the parameter specified when adding the observer, the notification will be received. If a specific objectobject is specified when the observer is added, then when sending a notification, the notification objectparameters must match the parameters when the observer is added object, otherwise the observer will not receive the notification. If you need to pass a value userInfo, please use it instead object.

For example, the following methods will not receive notifications:

// 添加观察
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleNotification:) name:@"TestNotification" object:@1];
// 通知发送
[NSNotificationCenter.defaultCenter postNotificationName:@"TestNotification" object:nil];

This is because when adding an observer, objectthe parameter is specified @1, which is an NSNumberobject of type. When sending a notification, the specified objectparameters are nil.

3 agents

3.1 Steps to use the agent

  1. Declare a protocol in B view controller
@protocol MyViewControllerDelegate <NSObject>

- (void)changUILabelText:(NSString *)string;

@end
  1. Declare a proxy property in B view controller
@property (nonatomic, weak) id<MyViewControllerDelegate> delegate;
  1. Write the method executed by the proxy where you want to return the value in view controller B.
[self.delegate changeUILabelText:self.myTextField.text];
  1. Sign the proxy agreement in the A view controller
@interface ViewController : UIViewController <MyViewControllerDelegate>

@property (nonatomic, strong) MyViewController *myView;

@end
  1. Signing agent in A
    self.myView = [[MyViewController alloc] init];
    self.myView.delegate = self;
  1. Implement protocol methods
- (void)changUILabelText:(NSString *)string {
    
    
    NSLog(@"%@", string);
}

3.2 Circular reference of agents

The circular reference of the agent means that in the agent mode, due to strong mutual references, a circular reference relationship is formed between two objects (usually the principal and the agent), resulting in memory leaks. Due to this circular reference, when the principal and the agent strongly reference each other, their reference counts cannot be reduced to 0, causing the memory they occupy not to be released correctly, causing a memory leak.

Insert image description here

To avoid circular references to proxies, you can use one of the following methods:

  1. Use weak reference: In the property declaration of the principal, set the reference of the agent to a weak reference, so that a circular reference will not be formed. For example: @property (nonatomic, weak) id delegate;. However, it is necessary to ensure that the agent will not be released early during use.
  2. Use the agent's life cycle control: When holding a reference to the agent in the principal, you can release the reference to the agent at an appropriate time according to the specific situation to avoid circular references.
  3. Use blocks or notifications to replace the proxy mode: In some cases, you can use blocks or notifications to replace the proxy mode to avoid circular reference problems.
    Choosing the appropriate method depends on the specific business logic and requirements, ensuring that the use of proxy mode does not cause circular references and memory leaks.

4. KVO\KVC\Singleton Mode\Notification\Agent\Block

4.1 The difference between agency and notification

  • Efficiency: Agent is higher than notification;
  • Association: The agent is a strong association, and both the principal and the agent know each other. Notifications are weakly related, and there is no need to know who sent them or who received them;
  • The agent is a one-to-one relationship, and the notification is a one-to-many relationship; if the agent wants to send messages to multiple classes, it can be achieved by adding agents to the collection class and then traversing, or by message forwarding.
  • The general behavior of the agent needs to be completed by others, and the notification is a global notification;

4.2 The difference between KVO and notifications

  • Same: both are one-to-many relationships;
  • Difference: Notification requires the observer to proactively send a notification first, and the observer registers to listen and then respond. Compared with KVO, there is one more step to send a notification;
  • Monitoring scope: KVO monitors changes in a value. Notifications are not limited to monitoring changes in attributes. They can also monitor a variety of status changes. The notifications have a wide monitoring range and are more flexible to use;
  • Usage scenario: The general usage scenario of KVO is to monitor data changes, and the notification is a global notification;

4.3 The difference between block and agent

  • Similarity: block and proxy are both callback methods. The usage scenarios are the same.
  • difference:
    • Block concentrates code blocks, while proxy disperses code blocks, so block is more suitable for lightweight and simple callbacks, such as network transmission, while proxy is suitable for situations with many public interfaces. This makes it easier to decouple the code architecture;
    • Block running costs are high. When the block is popped from the stack, the used data needs to be copied from the stack memory to the heap memory. Of course, if it is an object, it will be counted up and will be eliminated after use or when the block is set to nil. The agent only saves an object pointer and calls back directly, with no additional consumption. Compared with C's function pointer, it only performs an additional table lookup action. ;

Summary table:

method delegate NSNotification KVO block
advantage 1. The logic is clear 2. The code is highly readable 3. The compiler will check whether all methods are implemented 4. A controller can have multiple protocols 5. Reduce code coupling 1. The amount of code is small 2. One-to-many can be realized 3. Value transfer is convenient and fast 1. Can easily synchronize two objects 2. Can observe the current value and previous value 3. Can respond to state changes of objects not created by us, that is, internal objects, without changing the implementation of internal objects (SDK objects) 1. Clear logic 2. When the same function needs to be used multiple times, it saves the amount of code 3. Block can be stored in attributes 4. Enhances code readability 5. Cooperates with GCD to solve multi-threading problems
shortcoming 1. The definition requires a lot of code. 2. The delegate needs to be set to nil when releasing, otherwise calling the released delegate will crash. 3. A controller has multiple delegates of the same protocol. It is difficult to distinguish. 4. Cross-layer value transfer and monitoring will make the program hierarchical. Confusion 5. Easy to cause circular references 1. The compiler will not find out whether it can be processed correctly 2. When releasing the registered object, you need to cancel the registration, otherwise it may crash 3. The logic is not clear 1. The observed attributes use "string", and the compiler will not issue warnings and checks (the compiler cannot catch spelling errors or other error types at compile time) 2. The attribute needs to be modified again after reconstruction (attribute name change, attribute type change , attribute removal or addition) 1. It may cause circular reference. 2. The code in the block will be automatically retained. It may easily cause memory leaks.
Recommended usage scenarios The controller communicates with any other object, one-to-many situations in the callback method, and UI response events What needs to be processed in the code is very simple, communication between two unrelated objects Need to monitor an attribute Callback method, simple value transfer

5 Summary of design patterns

  • KVO/Notification-------> Observer Mode

The observer pattern defines a one-to-many dependency relationship, allowing multiple observer objects to monitor a certain subject object at the same time. When this topic object changes state, it notifies all observer objects, allowing them to update themselves automatically.
Advantages: decoupling,
interface isolation principle, open-closed principle

  • KVC --------> KVC mode

KVC provides a more flexible and powerful way to access properties, allowing developers to operate the properties and collections of objects in a simple way, reducing a lot of duplicate code and complexity. It is an important feature in the Cocoa framework, bringing convenience and efficiency to developers. However, it should be noted that when using KVC, you need to ensure that the attributes of the object comply with the KVC specification, that is, the attributes correspond to keys through certain naming rules, so that KVC can be used correctly for attribute access and operations.

  • singleton pattern

Resource sharing is achieved by taking advantage of the special feature that the application has only one instance object of this class.
Advantages: Simple to use, delayed evaluation, easy to cross modules
Disadvantages: This memory cannot be released until the program exits
Single Responsibility Principle

  • proxy mode

The principal gives the agent a task that it does not want to complete, and the principal needs to notify the agent before it can handle it.
Advantages: Decoupling
Open-closed principle
Example: tableview data source and proxy

  • strategy pattern

The strategy pattern defines a series of algorithms, encapsulates each algorithm, and makes them interchangeable. The Strategy pattern allows the algorithm to change independently of the clients using it.
Advantages: Make algorithm changes independent of the user who uses the algorithm
. Interface isolation principle, multiple use of combinations, less use of inheritance, programming for interfaces rather than implementation.
Examples: Judgment of account and password input format, NSArray's sortedArrayUsingSelector, etc.

  • MVC pattern

Divide program writing into three layers, namely model, view, and controller. Each layer has its own responsibilities to complete its own work.
Advantages: The MVC model makes the system clear in hierarchy, clear in responsibilities, and easy to maintain. It is open
to expansion but closed to modification.

  • MVVM pattern

The MVVM mode is used to solve the complex situation of C-layer code in the MVC mode (too many network requests and business logic processing). Compared with MVC, it has an extra layer of ViweModel (business processing and data conversion) layer, which is specially used for for processing data.
When the function is simple, MVVM will add a lot of code, so for simple functions, MVC is more convenient.

  • Three factory modes

The corresponding instance is returned by giving the parameters, completely hiding the principle of its implementation from the user.
Advantages: easy to replace, abstract programming,
dependency inversion principle

Design patterns: MVC pattern, singleton pattern, observer pattern, MVVM pattern, factory pattern, proxy pattern, strategy pattern, adapter pattern,
template pattern, appearance pattern, creation pattern

If you want to learn more, check out this: iOS Design Patterns

Guess you like

Origin blog.csdn.net/m0_63852285/article/details/131962081
Recommended