- 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.
- 程序員面試白皮書
- iOS 9 Game Development Essentials
- Full-Stack React Projects
- 大學計算機基礎(第2版)(微課版)
- Learning Vaadin 7(Second Edition)
- Swift Playgrounds少兒趣編程
- Android項目實戰:手機安全衛士開發案例解析
- MySQL入門很輕松(微課超值版)
- 快速入門與進階:Creo 4·0全實例精講
- Mastering PowerCLI
- Modular Programming with JavaScript
- Laravel Design Patterns and Best Practices
- 走近SDN/NFV
- 計算機輔助設計與繪圖技術(AutoCAD 2014教程)(第三版)
- Switching to Angular 2