📜 ⬆️ ⬇️

Design patterns, iOS view developer. Part 0. Singleton Single

I feel and forget.
I recorded and zapam'yatav.
I am afraid of i zrozumіv.
I am a master of science, now I am a master.
(V.V. Bublik)


A little introduction.


I knowingly made a quote in the Ukrainian language to the top of the post. The fact is that these are the words I heard from my programming teacher in the second year of university, and it is in this form that I still remember these words. As you can guess, this quote is a reference to the statement of Confucius, but it has a very important addition to the achievement of mastery.


And it was these words that led me to write this series of posts. The fact is that I am a novice iOS developer, and I really want to understand the design patterns. And I didn’t come up with a better way than to take the book Design Patterns by Eric and Elizabeth Freeman, and write examples of each pattern on Objective-C and Swift. This way I can better understand the essence of each pattern, as well as the features of both languages.


Content:


Part 0. Singleton Single
Part 1. Strategy
Part 2. The Observer


So let's start with the simplest pattern in my opinion.


Singleton, he - singleton.


The main task of a singleton is to provide the user with one and only one object of a certain class for the entire life cycle of the application. In iOS development, as for me, the best example of the need for such an object is the UIApplication class. It is logical that during the life of our application, we should have a single object of the UIApplication class.


So, let's look at what a singleton is in Objective-C and Swift using examples from the book.


Let's first learn how to create an object of any class at all. Very simple:


 // Objective-C [[MyClass alloc] init] 

 // Swift MyClass() 

And here the authors bring us to the idea that the above method can create as many objects of this class as you like. Thus, the first thing to do on the way to the singleton is to prohibit the creation of objects of our class from outside. This will help us private initializer.


And if in swift this is implemented trivially:


 // Swift class MyClass { private init() {} } 

So in objective-c, everything is not so simple at first glance. The point is that all obj-c classes have one common ancestor: NSObject , in which there is a public initializer. Therefore, in the header file of our class, you need to indicate that this method is unavailable for our class:


 // Objective-C @interface MyClass : NSObject - (instancetype)init UNAVAILABLE_ATTRIBUTE; @end 

Thus, an attempt to create an object of our class from outside causes an error at the compilation stage. Okay. Now, in objective-c, we have a ban on the creation of objects of our class. True, this is not a completely private initializer, but we will return to this in a couple of seconds.


So, in fact, we got a class whose objects cannot be created, because the constructor is private. And what to do with all this? We will create an object of our class within our own class. And we will use for this a static method (a method of a class, not an object):


 // Swift class MyClass { private init() {} static func shared() -> MyClass { return MyClass() } } 

 // Objective-C @implementation MyClass + (instancetype)sharedInstance { return [[MyClass alloc] init]; } @end 

And if again everything is simple and clear for swift, then with objective-c there is a problem with initialization:



It is logical, because we said earlier that - (instancetype)init not available. And it is not available even inside our class. What to do? Write your private initializer in the implementation file and use it in the static method:


 // Objective-C @implementation MyClass - (instancetype)initPrivate { self = [super init]; return self; } + (instancetype)sharedInstance { return [[MyClass alloc] initPrivate]; } @end 

(Yes, and do not forget to render the + (instancetype)sharedInstance to the header file, it should be public)


Now everything is compiled and we can get objects of our class in this way:


 // Objective-C [MyClass sharedInstance] 

 // Swift MyClass.shared() 

Our singleton is almost ready. It remains only to fix the static method so that the object is created only once:


 // Objective-C @implementation Singleton - (instancetype)initPrivate { self = [super init]; return self; } + (instancetype)sharedInstance { static Singleton *uniqueInstance = nil; if (nil == uniqueInstance) { uniqueInstance = [[Singleton alloc] initPrivate]; } return uniqueInstance; } @end 

 // Swift class Singleton { private static var uniqueInstance: Singleton? private init() {} static func shared() -> Singleton { if uniqueInstance == nil { uniqueInstance = Singleton() } return uniqueInstance! } } 

As you can see, for this we need a static variable, in which the once created object of our class will be stored. Each time when our static method is called, it is checked for nil and, if the object has already been created and written to this variable, it is not created anew. Our singleton is ready, cheers! :)


Now a few examples from the life of the book.


So, we have a chocolate factory and for cooking we use a high-tech chocolate-and-milk heater (I just love milk chocolate), which will be controlled by our software code:


 // Objective-C //   ChocolateBoiler.h @interface ChocolateBoiler : NSObject - (void)fill; - (void)drain; - (void)boil; - (BOOL)isEmpty; - (BOOL)isBoiled; @end //   ChocolateBoiler.m @interface ChocolateBoiler () @property (assign, nonatomic) BOOL empty; @property (assign, nonatomic) BOOL boiled; @end @implementation ChocolateBoiler - (instancetype)init { self = [super init]; if (self) { self.empty = YES; self.boiled = NO; } return self; } - (void)fill { if ([self isEmpty]) { // fill boiler with milk and chocolate self.empty = NO; self.boiled = NO; } } - (void)drain { if (![self isEmpty] && [self isBoiled]) { // drain out boiled milk and chocolate self.empty = YES; } } - (void)boil { if (![self isEmpty] && ![self isBoiled]) { // boil milk and chocolate self.boiled = YES; } } - (BOOL)isEmpty { return self.empty; } - (BOOL)isBoiled { return self.boiled; } @end 

 // Swift class ChocolateBoiler { private var empty: Bool private var boiled: Bool init() { self.empty = true self.boiled = false } func fill() { if isEmpty() { // fill boiler with milk and chocolate self.empty = false self.boiled = false } } func drain() { if !isEmpty() && isBoiled() { // drain out boiled milk and chocolate self.empty = true } } func boil() { if !isEmpty() && !isBoiled() { // boil milk and chocolate self.boiled = true } } func isEmpty() -> Bool { return empty } func isBoiled() -> Bool { return boiled } } 

As you can see, the heater is first filled with the mixture ( fill ), then brings it to the boil ( boil ), and then transfers it to the manufacture of milk chocolate bars ( drain ). To avoid problems, we need to be sure that there is only one instance of our class in our program that controls our heater, so we will make changes to the program code:


 // Objective-C @implementation ChocolateBoiler - (instancetype)initPrivate { self = [super init]; if (self) { self.empty = YES; self.boiled = NO; } return self; } + (instancetype)sharedInstance { static ChocolateBoiler *uniqueInstance = nil; if (nil == uniqueInstance) { uniqueInstance = [[ChocolateBoiler alloc] initPrivate]; } return uniqueInstance; } // other methods @end 

 // Swift class ChocolateBoiler { private var empty: Bool private var boiled: Bool private static var uniqueInstance: ChocolateBoiler? private init() { self.empty = true self.boiled = false } static func shared() -> ChocolateBoiler { if uniqueInstance == nil { uniqueInstance = ChocolateBoiler() } return uniqueInstance! } // other methods } 

So, everything is fine. We are 100% sure (100% accurate?) That we have only one object of our class and no unforeseen situations at the factory will occur. And if our code on objective-c looks pretty good, then swift does not look like enough swifty. Let's try to rewrite it a bit:


 // Swift class ChocolateBoiler { private var empty: Bool private var boiled: Bool static let shared = ChocolateBoiler() private init() { self.empty = true self.boiled = false } // other methods } 

The fact is that we can safely store our lonely object in the static constant shared and it is not at all necessary for us to write a whole method for this with checks for nil . The object itself will be created at the first call to this constant and written into it only once.


But what about multithreading?


Everything will work well smoothly until we want to apply work with threads in our program. How to make our singleton thread-safe?


And again: in the swift, as it turns out, absolutely no need to perform any additional actions. The constant is already thread-safe, because the value in it can be written only once and this will make the stream that gets to it first.


But in objective-c it is necessary to make adjustments to our static method:


 // Objective-C + (instancetype)sharedInstance { static ChocolateBoiler *uniqueInstance = nil; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ uniqueInstance = [[ChocolateBoiler alloc] initPrivate]; }); return uniqueInstance; } 

The block inside the dispatch_once guaranteed to be executed only once, when the very first stream reaches it, all other flows will wait for the block to complete.


Results summarize.


So, we figured out how to write singletones on objective-c and swift. I will give you the final code for the class Singleton in both languages:


 // Objective-C //   Singleton.h @interface Singleton : NSObject - (instancetype)init UNAVAILABLE_ATTRIBUTE; + (instancetype)sharedInstance; @end //   Singleton.m @implementation Singleton - (instancetype)initPrivate { self = [super init]; return self; } + (instancetype)sharedInstance { static Singleton *uniqueInstance = nil; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ uniqueInstance = [[Singleton alloc] initPrivate]; }); return uniqueInstance; } @end 

 // Swift class Singleton { static let shared = Singleton() private init() {} } 

P.S.


I want to ask all readers and commentators: if you saw any inaccuracy or know how to write any of the pieces of code I have given, write more correctly / more beautifully / more correctly - tell about it. I am writing here not at all in order to teach others, but in order to learn myself. After all, while we are learning, we remain young.


Thank you for attention.


')

Source: https://habr.com/ru/post/320728/


All Articles