- 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.
- JavaScript修煉之道
- Java EE框架整合開發入門到實戰:Spring+Spring MVC+MyBatis(微課版)
- jQuery EasyUI網站開發實戰
- Easy Web Development with WaveMaker
- Unity Game Development Scripting
- 利用Python進行數據分析(原書第3版)
- Working with Odoo
- 大話Java:程序設計從入門到精通
- Android傳感器開發與智能設備案例實戰
- Hands-On Kubernetes on Windows
- Deep Learning with R Cookbook
- Troubleshooting Citrix XenApp?
- Instant Automapper
- Design Patterns and Best Practices in Java
- 前端程序員面試算法寶典