1. Memory allocation 1.1 value type memory allocation
Value types that are fixed long in Swift are stored on the stack and do not involve memory on the heap. Variable-length value types (strings, type of values with variable lengths) allocate heap memory.
- This is equivalent to a "benefit", which means that you can use a value type to complete the execution of a method more quickly.
- An instance of a value type saves only its internal storage properties, and the instances that are assigned by "=" are stored separately from each other.
- The assignment of a value type is a copy, and for a fixed-length value type, the cost of such a copy is completed in constant time because the required memory space is stationary.
struct Point { var x: Double var y: Double}
let point1 = Point(x: 3, y: 5)var point2 = point1print(point1) // Point(x: 3.0, y: 5.0)print(point2) // Point(x: 3.0, y: 5.0)
The above example is actually allocated on the stack as.
栈point1 x: 3.0 y: 5.0point2 x: 3.0 y: 5.0
If you try to modify point2
a property, only the value saved in the address on the stack will be modified point2
x
, and the value will not be affected point1
.
point2.x = 5print(point1) // Point(x: 3.0, y: 5.0)print(point2) // Point(x: 5.0, y: 5.0)
栈point1 x: 3.0 y: 5.0point2 x: 5.0 y: 5.0
1.2 Memory allocations for reference types
The storage properties of the reference type are not stored directly on the stack, and the system creates a space on the stack to hold the instance pointers, and the pointers on the stack are responsible for finding the appropriate objects on the heap.
- The assignment of a reference type does not occur "copy", and when you try to modify the value of the example, the pointer of the instance "directs" you to the heap and then modifies the contents on the heap.
Point
the definitions below are modified into classes.
class Point { var x: Double var y: Double init(x: Double, y: Double) { self.x = x self.y = y }}
let point1 = Point(x: 3, y: 5)let point2 = point1print(point1.x, point1.y) // 3.0 5.0print(point2.x, point2.y) // 3.0 5.0
Because Point
it is a class, so Point
the storage properties can not be stored directly on the stack, the system will open up on the stack of two pointers to save point1
the length point2
of the pointer, the stack of pointers to the heap to find the corresponding object, point1
and point2
two instances of the storage properties will be stored on the heap.
When you use "=" To assign a value, a pointer is generated on the stack point2
, pointing to the point2
point1
same address of the heap as the pointer.
栈 堆point1 [ ] --| |--> 类型信息point2 [ ] --| 引用计数 x: 3 y: 5
After the pointer is generated on the stack, the contents of the point1
point2
pointer are empty, then the memory is allocated to the heap, the heap is locked, the appropriate memory space is found, the target memory is allocated and the heap is unlocked, and the first address of the memory fragment in the heap is stored in a pointer on the stack.
Compared to saving on the stack point1
and point2
, the heap needs more memory space, in addition to save x
and y
space, in the head also requires two 8-byte space, a pointer to the type information to index the class address, a "reference count" to save the object.
When you try to modify point2
the value, point2
the pointer "directs" you to the heap, and then modifies the contents of the heap, which point1
is also modified.
point2.x = 5print(point1.x, point1.y) // 5.0 5.0print(point2.x, point2.y) // 5.0 5.0
We call point1
point2
this relationship "shared" with each other. "Sharing" is an attribute of a reference type and, in many cases, a problem, the root cause of the "shared" pattern is that we cannot guarantee the immutability of an object of a reference type.
2, variability and immutability
2.1 Variability and immutability of reference types
For objects of reference types, when you need an immutable object, you cannot control the immutability of its properties by means of a keyword.
-
When you create an instance of the point
class, you want it to be immutable, so use the let
keyword declaration, but let
can only constrain the content on the stack, that is, Even if you use the let
keyword for a type instance, you can only guarantee that its pointer address does not change, but that its properties cannot be constrained.
class Point {var x:double var y:double init (x:double, y:double) {self.x = x self.y = y}}
let point1 = Point (X:3, y:5) Let Point2 = Point (x:0, y:0) print (point1.x, POINT1.Y)//3.0 5.0print (point2.x, POINT2.Y)//0.0 0.0point1 = Point2 A compile error occurred and the Point1 pointer could not be modified point1.x = 0//Because the X attribute is defined using VAR, it can be modified by print (point1.x, poi NT1.Y)//0.0 5.0print (point2.x, POINT2.Y)//0.0 0.0
If you set all the properties to be immutable, it does guarantee that the reference type is immutable, and many languages are designed to do so.
class Point { let x: Double let y: Double init(x: Double, y: Double) { self.x = x self.y = y }}
let point1 = Point(x: 3, y: 5)print(point1.x, point1.y) // 3.0 5.0point1.x = 0 // 发生编译错误,x 属性是不可变的
The new problem is that if you want to modify Point
the properties, you can only re-build an object and assign a value, which means that there is no necessary locking, addressing and memory recycling process, greatly wasting the performance of the system.
let point1 = Point(x: 3, y: 5)point1 = Point(x: 0, y: 5)
2.2 Variability and immutability of value types
Because properties of value types are stored on the stack, they can be constrained by the let
keyword.
You can declare a property of a value type var
, guaranteeing its flexibility, when you want an instance of that type to be an immutable object, use a let
declarative object, even if the object's properties are mutable, but the object as a whole is immutable, so you cannot modify the properties of the instance.
struct Point { var x: Double var y: Double}
let point1 = Point(x: 3, y: 5)print(point1.x, point1.y) // 3.0 5.0point1.x = 0 // 编辑报错,因为 point1 是不可变的
Because the assignment is "copy", the variability limit of the old object does not affect the new object.
let point1 = Point(x: 3, y: 5)var point2 = point1 // 赋值时发生拷贝print(point1.x, point1.y) // 3.0 5.0print(point2.x, point2.y) // 3.0 5.0point2.x = 0 // 编译通过,因为 point2 是可变的print(point1.x, point1.y) // 0.0 5.0print(point2.x, point2.y) // 0.0 5.0
3. Sharing of reference types
"Sharing" is an attribute of a reference type and, in many cases, a problem, the root cause of the "shared" pattern is that we cannot guarantee the immutability of an object of a reference type.
The following shows the shares in the app type.
// 标签class Tag { var price: Double init(price: Double) { self.price = price }}// 商品class Merchandise { var tag: Tag var description: String init(tag: Tag, description: String) { self.tag = tag self.description = description }}
let tag = Tag(price: 8.0)let tomato = Merchandise(tag: tag, description: "tomato")print("tomato: \(tomato.tag.price)") // tomato: 8.0// 修改标签tag.price = 3.0// 新商品let potato = Merchandise(tag: tag, description: "potato")print("tomato: \(tomato.tag.price)") // tomato: 3.0print("potato: \(potato.tag.price)") // potato: 3.0
The scenario described in this example is "sharing", where you modify the part you want (the price of potatoes), but it causes unexpected changes (the price of tomatoes) because tomatoes and potatoes share a label instance.
Semantic sharing is caused by memory addresses in a real-world memory environment. The objects in the example above are reference types, and since we have created only three objects, the system allocates three memory addresses on the heap, respectively, to save tomato
, potato
and tag
.
栈 堆tamoto Tag --| description | tag |--> price: 3.0 |patoto Tag --| description
In the OC era, there are no such rich value types to use, many types are reference types, so using reference types requires a security policy that does not generate "sharing," which is one of them.
First create a Label object, put the price you need on the label, then call the method on the label and copy()
pass the returned copy object to the product.
let tag = Tag(price: 8.0)let tomato = Merchandise(tag: tag.copy(), description: "tomato")print("tomato: \(tomato.tag.price)") // tomato: 8.0
When you pass the execution to the tag
copy
Merchandise
constructor, the memory allocation is as follows.
栈 堆tamoto Tag -----> Copied tag description price: 8.0 tag price: 8.0
If there is a new product on the shelves, you can continue to use the "copy" to label.
let tag = Tag(price: 8.0)let tomato = Merchandise(tag: tag.copy(), description: "tomato")print("tomato: \(tomato.tag.price)") // tomato: 8.0// 修改标签tag.price = 3.0// 新商品let potato = Merchandise(tag: tag.copy(), description: "potato")print("tomato: \(tomato.tag.price)") // tomato: 8.0print("potato: \(potato.tag.price)") // potato: 3.0
Now in-memory allocations.
栈 堆tamoto Tag -----> Copied tag description price: 8.0 tag price: 3.0patoto Tag -----> Copied tag description price: 3.0
This copy is called a "protective copy", and in the protected copy mode, no "sharing" is generated.
4. Copy of variable-length value type
Variable-length value types do not keep everything on the stack like fixed-length value types, because the memory space on the stack is contiguous, and you always open and release the stack's memory by moving the tail pointer. In Swift, collection types and string types are value types, and identity information for variable-length value types is preserved on the stack, while internal elements of variable-length value types remain on the heap.
The fixed-length value type does not occur "shared," which is well understood, since each assignment will open up new stack memory, but for a variable-length value type, how does it handle the heap memory used to keep the inner elements? In WWWDC2015 's No. 414 video, Apple revealed a copy of the fixed-length value type: The copy rules for variable-length value types are more complex than "copy" and reference-type "protected copies" of fixed-length value types, using a technique called copy-on-write, The literal understanding is that it is only copied at the time of writing.
There are a number of swift native variable-length value types in Swift 3.0, which use copy-on-write technology to improve performance when copying, such as Date, Data, Measurement, URL, Urlsession, Urlcomponents, Indexpath.
5. Sharing with reference types
Sharing is not always harmful, and one of the benefits of sharing is that the memory space on the heap is reused, especially for objects with large memory footprint (compared to slices). So if the object on the heap is not modified in the "shared" state, then we should reuse the object to avoid creating duplicate objects on the heap, what you need to do is create an object and then pass the object's pointer to the object's referrer, simply by using "share" to implement a "cache" policy.
If you use a lot of repetitive content in your application, such as using many similar images, if you call the method in every place you want, you UIImage(named:)
create a lot of duplicate content, so we need to create all the images we use, and then select the desired images from them. Obviously, in this scenario, the dictionary is best used as a container for cached images, and the dictionary key values are used as image index information. This is one of the classic use cases of reference types, and the dictionary's key values are "identity information" for each image, and you can see how important "identity information" is in this example.
enum Color: String { case red case blue case green}enum Shape: String { case circle case square case triangle}
let imageArray = ["redsquare": UIImage(named: "redsquare"), ...]func searchImage(color: Color, shape: Shape) -> UIImage { let key = color.rawValue + shape.rawValue return imageArray[key]!!}
-
A variable-length value type actually saves the memory on the heap, so creating a variable-length value type will inevitably lock up the heap and allocate memory, and one of the purposes of using the cache is to avoid excessive heap memory operations, which in the previous example we habitually put String
As the key value for the dictionary, but String
is a variable-length value type that triggers a memory allocation on the heap when the key
is generated in searchimage
.
If you want to continue searchImage
the performance of the promotion, you can use the fixed-length value type as the key value so that the memory on the heap is not accessed when the key value is synthesized. One thing to note is that the fixed-length value type you are using must satisfy the Hashable
protocol to be the key value for the dictionary.
enum Color: Equatable { case red case blue case green}enum Shape: Equatable { case circle case square case triangle}struct PrivateKey: Hashable { var color: Color = .red var shape: Shape = .circle internal var hsahValue: Int { return color.hashValue + shape.hashValue }}
let imageArray = [PrivateKey(color: .red, shape: .square): UIImage(named: "redsquare"), PrivateKey(color: .blue, shape: .circle): UIImage(named: "bluecircle")]func searchImage(privateKey: PrivateKey) -> UIImage { return imageArray[privateKey]!!}
Memory management for Swift value types and reference types