Source code

Application of single case in iOS development


Translation and modification from obj.io

Link: Avoiding Singleton Abuse

Introducer

Singleton (Singletons) is one of the core patterns of Cocoa. On iOS, singletons are very common, such as UIApplication, NSFileManager and so on. Although they are very convenient to use, there are actually many problems to be noted. So when you complete the dispatch_once code fragment next time, think about the consequences.


What is a single case?

In the book "design patterns", the definition of singleton is given.

Singleton mode: ensuring that a class has only one instance and provides a global access point to which it is accessed.

The singleton mode provides an access point for customers to generate unique instances for shared resources and access shared resources through it. This mode provides flexibility.

In Objective-C, you can use the following code to create a singleton:

 + (instancetype) sharedInstance
{
static dispatch_once_t once; 
static ID sharedInstance; 
dispatch_once (&once, ^{
sharedInstance = [[self;});

When a class can only have one instance and must access it from an access point, it is very convenient to use single examples, because the use of singletons ensures that the access points are unique, consistent and well known.


Problems in singletons

Global state

First of all, we should all reach a consensus that "global variable state" is dangerous because it makes the program difficult to understand and debug, and on the reduction of state codes, object-oriented programming should learn from functional programming.

For example, the following code:

 @implementation Math{
NSUInteger _a; 
NSUInteger _b;}} - (NSUInteger) computeSum
{
return _a + _b;}

This code wants to calculate the sum of _a and _B and return it. But in fact, there are many problems in this code.

  • _a and _b are not used as parameters in the computeSum method. Compared with finding interface and knowing which variable control method's output, finding implementation is more covert, and hiding is easier to erroneous.

  • When preparing to modify the values of _a and _b to let them call the computeSum method, programmers must clearly modify their values without affecting the correctness of other codes containing two values, and it is particularly difficult to make such judgments in multithreaded situations.

Compare the following code:

 + (NSUInteger) computeSumOf: (NSUInteger) a plus: (NSUInteger) b
{
return a + B;}

In this code, the affiliation of a and B is very clear. No need to change the status of the instance to call the method, and there is no need to worry about the side effects of calling this method.

What does this example have to do with singletons? In fact, the single case is the overall situation in sheep's clothing. A singleton can be used everywhere, and it does not need to clearly declare subordinations. Any module in the program can simply call [MySingleton sharedInstance] and get the access point of this single instance, which means that any side effects that occur when interacting with a singleton may affect a random piece of code in the program, such as:

 @interface MySingleton: NSObject

+ (instancetype) sharedInstance; NSUInteger; badMutableState; void (void) setBadMutableState: (NSUInteger) badMutableState; setBadMutableState: (());

In the above code, ConsumerA and ComsumerB are two completely independent modules in the program, but the method in ComsumerB will affect the behavior in ComsumerA, because the change of this state is passed through the singleton.

In this code, it is precisely because of the global and state of the singleton, which leads to the implied coupling between the two seemingly unrelated modules of ComsumerA and ComsumerB.


Object life cycle

Another major problem of singletons is their lifecycle.

For example, suppose that a app needs to be able to let users see their friends list function. Every friend has his own head. At the same time, we hope that app can download and cache these friends' heads. At this point, by learning the knowledge of singletons before, we will probably write the following code:

 @interface MyAppCache: NSObject

+ (instancetype) sharedCMyAppCache; NSData - (void) cacheProfileImage: (NSData *) imageData forUserId: (NSString *); imageData - (* *) ((*)) (* *)

This code looks completely without problems and runs well, so app continues to develop until one day, we decide to help app join the "logout" function. Suddenly, we found that user data was stored in the global singleton. When the user logs out, we want to clear these data and create a new MyAppCache for the new user when he logs in.

But the problem is in singleton, because the definition of singleton is: "create once, live forever". In fact, there are many ways to solve the above problem. We may destroy this single case when the user logs out.

 Static MyAppCache *myAppCache; *myAppCache + (instancetype) sharedMyAppCache
{
if (myAppCache) 
{
myAppCache = [[self alloc] init]; 
}
return myAppCache;}} + + (x) = =;}

The above code distorts the singleton pattern, but it works.

In fact, this method can be used to solve this problem, but it costs too much. The most important point is that we gave up the dispatch_once, which ensured the thread safety of the method invocation. Now all the codes that call [MyAppCache shareMyAppCache] will get the same variable, and need to clearly use the order of MyAppCache code execution. Just imagine that when the user came out, he happened to call this method to save the picture.

On the other hand, to implement this method, we need to make sure that the tearDown method is not invoked when the background task is not finished, or that the background tasks will be canceled when the tearDown method is implemented. Otherwise, another new MyAppCache will be created and the old data will be saved.

However, because a single case does not have a clear owner (because a single case manages its own life cycle by itself), it is very difficult to destroy a single case.

So you might think, "don't make MyAppCache single case." the problem is that the life cycle of an object may not be well established at the beginning of the project. If the life cycle of an object will match the life cycle of the whole program, it will greatly limit the scalability of the code, and it will be painful when the product needs change.

So all above is to clarify a view: "singletons should only maintain the overall state, and the life cycle of the state is consistent with the life cycle of the program". For the singletons already existing in the program, critical review is necessary.


Not conducive to testing

This part is mentioned in the last chapter, but I think testing is a very important part in software development, so we separate the content of this part into another chapter and add some personal opinions.

Because singletons have been alive throughout the life cycle of app, they have been alive even during the execution of tests, resulting in a test that may affect another test, which is a taboo in unit testing.

Therefore, it is necessary to effectively destroy a single case during the unit test and maintain the thread safety characteristics of a single case. But I mentioned in the above:

"But because a single case does not have a clear owner (because a single case manages its own life cycle), destroying a single case is very difficult. "

It seems that the two are self contradictory. In fact, they can choose to simplify the singleton. Instead of having all kinds of singletons, it is better to have only one "real" single instance ServiceRegistry, and to refer to other "potential" singletons by ServiceRegistry, so that the other singletons have a owner, which can destroy the singletons in time for unit testing, and ensure the independence of unit tests.

On the other hand, the existence of ServiceRegistry makes other "singletons" no longer singletons, so that in TDD, the single case that is difficult to mock will become simpler mock.

conclusion

We all know that the global variable state is not good, but when we use singletons, we inadvertently turn it into a global variable that we hate.

In object oriented programming, we need to reduce the scope of variable states as much as possible, while singletons run counter to this idea, hoping to think more about the next single case, and consider whether this variable is really worth being a singleton. If not, use the dependency injection mode instead.

For more information, check out my home page.

Author: Dywane

Link: https://www.jianshu.com/p/45600839410f

Fabulous ( Zero )

This article is composed of Contributor Creation, article address: Https://blog.isoyu.com/archives/ioskaifa-danlishiyongwenti.html
Use Knowledge sharing signature 4 The international license agreement is licensed. In addition to the reprint / provenance, all originals or translations of this site must be signed before retransmission. The final edit time is April, 22, 2019 at 10:30 pm.

Hot articles

Comment

[required]

Invisibility?

Please wait three seconds after submission, so as not to cause unsuccessful successes and duplication.