- Swift High Performance
- Kostiantyn Koval
- 702字
- 2021-08-05 16:36:25
Immutability
In the previous section, you learned how important it is to use immutable constants. There are more immutable types in Swift, and you should take advantage of them and use them. The advantages of immutability are as follows:
- It removes a bunch of issues related to unintentional value changes
- It is a safe multithreading access
- It makes reasoning about code easier
- There is an improvement in performance
By making types immutable, you add an extra level of security. You deny access to mutating an instance. In our journal app, it's not possible to change a person's name after an instance has been created. If, by accident, someone decides to assign a new value to the person's firstName
, the compiler will show an error:
var person = Person(firstName: "Jon", lastName: "Bosh") p.firstName = "Sam" // Error
However, there are situations when we need to update a variable. An example could be an array; suppose you need to add a new item to it. In our example, maybe the person wants to change a nickname in the app. There are two ways to do this, as follows:
- Mutating an existing instance
- Creating a new instance with updated information
Mutating an instance in place could lead to a dangerous, unpredictable effect, especially when you are mutating a reference instance type.
Note
Classes are reference types. "Reference type" means that many variables and constants can refer to the same instance data. Changes done to the instance data reflect in all variables.
Creating a new instance is a much safer operation. It doesn't have any impact on the existing instances in the system. After we have created a new instance, it may be necessary to notify other parts of the system about this change. This is a safer way of updating instance data. Let's look at how we can implement a nickname change in our Person
class. First, let's add a nickname to the Person
class:
class Person { let nickName: String … func changeNickName(nickName: String) -> Person { return Person(firstName: firstName, lastName: lastName, nickName: nickName) } } let sam = Person(firstName: "Sam", lastName: "Bosh", nickName:"sam") let rockky = sam.changeNickName("Rockky")
Because we made a sam
instance a constant, we can't assign a new value to it after changing nickName
. In this example, it would be better to make it a variable because we actually need to update it:
var sam = Person(firstName: "Sam", lastName: "Bosh", nickName:"BigSam") sam = sam.changeNickName("Rockky")
Multithreading
We get more and more core processors nowadays, and working with multithreading is a part of our life. We have GCD and NSOperation for performing work on multiple threads.
The main issue with multithreading is synchronizing read-and-write access to data without corrupting that data. As an example, let's create an array of journal entries and try to modify it in the background and main thread. This will lead to an application crash:
class DangerousWorker { var entries: [JournalEntry] init() { //Add test entries let entry = JournalEntry(title: "Walking", text: "I was walking in the loop") entries = Array(count: 100, repeatedValue: entry) } func dangerousMultithreading() { dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0)) { sleep(1) //emulate work self.entries.removeAll() } print("Start Main") for _ in 0..<entries.endIndex { entries.removeLast() //Crash sleep(1) //emulate work } } } let worker = DangerousWorker() worker.dangerousMultithreading()
These kinds of issues are really hard to find and debug. If you remove the sleep(1)
delay, the crash might not occur on some devices, depending on which thread is run first.
When you make your data immutable, it becomes read-only and all threads can read it simultaneously without any problems:
let entries: [JournalEntry] let entry = JournalEntry(title: "Walking", text: "I was walking") entries = Array(count: 100, repeatedValue: entry) // entries is immutable now, read-only dispatch_async(dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0)) { for entry in self.entries { print("\(entry) in BG") } } for entry in self.entries { print("\(entry) in BG") }
But we often need to make changes to the data. Instead of making changes directly to the source data, a better solution is to create new, updated data and pass the result to the caller thread. In this way, multiple threads can safely continue performing a read operation. We will take a look at multithreading data synchronization in Chapter 6, Architecting Applications for High Performance.
- Java 9 Concurrency Cookbook(Second Edition)
- 小創(chuàng)客玩轉(zhuǎn)圖形化編程
- Android Application Development Cookbook(Second Edition)
- 零基礎(chǔ)學MQL:基于EA的自動化交易編程
- HTML5與CSS3基礎(chǔ)教程(第8版)
- Java Web應(yīng)用開發(fā)給力起飛
- Mudbox 2013 Cookbook
- Python預(yù)測分析與機器學習
- Python應(yīng)用與實戰(zhàn)
- Learning D3.js 5 Mapping(Second Edition)
- Java 9:Building Robust Modular Applications
- Learn C Programming
- Learning RxJava
- Xamarin Cross-platform Application Development(Second Edition)
- C語言程序設(shè)計:現(xiàn)代方法(第2版)