Like the comment and hope it gets better and better with your help
Author: @ios growth refers to north, this article was first published in the public number iOS growth refers to north, welcome to the correction
Please contact me if there is a need to reprint, remember to contact oh!!
This is the fifth day of my participation in Gwen Challenge
In the previous article on iOS memory allocation, I did not discuss allocation in Swift. This article briefly describes how structures and classes are allocated on the stack in Swift.
To understand memory allocation in Swift, you need the following basic knowledge:
- Why is Swift a memory safe language? then
unsafe
What does it mean? - Value type and reference type
- The stack and heap
- Basic use of MemoryLayout
Basic knowledge of
Memory safety[1]The Memory (Safety)
By default, Swift blocks potentially unsafe behavior.
Swift ensure that:
-
Variables are initialized before use, and memory is not accessed after release
-
Check the array index for out-of-bounds errors.
-
In order to achieve memory security, Swift also ensures that multiple accesses to the same memory area do not conflict by requiring the code modifying a location in memory to have exclusive access to that memory [2].
-
Avoid undefined Behaviour (Unexpected Behaviour)
let array = [10.1.10.2.10.3.10.4]
// let b = array.first + 1.0
let b = array.first! + 1.0
//let c = array[4] + 1.0
//Fatal error: Index out of range
let c = array[2] + 1.0
Copy the code
var stepSize = 1
func increment(_ number: inout Int) {
number + = stepSize
}
increment(&stepSize)
// Ensure that multiple accesses to the same memory region do not conflict
// Simultaneous accesses to **, but modification requires exclusive access.
// Previous access (a modification) started at **
// Current access (a read) started at:
Copy the code
When the compiler or Debug reports an error or crash, Swift tells you that it is not safe to use it.
unsafe
The Swift,
Unsafe sounds like a terrible word, but in actual engineering we might have trade-offs like this.
- We need to implement some dark magic to meet our needs or engineering analysis tools, and the closer you get to the bottom the less protection you have for security
- When interacting with other insecure languages, such as C cluster code, the choice of language is a trade-off, a design for a requirement.
The unsafe part of Swift is called UnsafePointer.
Before we learn about the insecure apis provided by Swift, we need to understand the basic use of MemoryLayout.
Value type and reference type
Object types in Swift are divided into two parts, value types and reference types. Why do we need to know this before we learn about Swift’s memory allocation? Memory is allocated differently for value types and reference types.
The stack and heap
When learning about memory allocation, we need to know what a heap is and what a stack is, or we need to know whether code is distributed on the stack or the heap.
Memory in iOS can be roughly divided into code area, global/static area, constant area, heap area, stack area, its address from low to high.
Is there an area higher than the stack area in the code we write? Is the cool aidian iOS system class information on the stack? Indicates that there is also an area of system-shared library memory on top of the stack.
MemoryLayout
To understand the layout in Swift, we need to take advantage of the API provided by Swift, MemoryLayout, which tells us at run time the Size, Alignment, and Stride of the provided type.
- Size: indicates the continuous memory usage of the type, in bytes.
- Alignment: Indicates the default memory Alignment, in bytes.
- Stride: The number of bytes from the beginning of the previous instance to the beginning of the next instance when stored in contiguous memory or array.
Its usage is very simple, I understand that for the two apis, one is for object MemoryLayout < T >. The size/stride/alignment, one is for instance MemoryLayout. Size/stride/alignment (ofValue: point)
MemoryLayout<Int>.size // 8
MemoryLayout<Int>.alignment // 8
MemoryLayout<Int>.stride // 8
MemoryLayout<Bool>.size // 1
MemoryLayout<Bool>.alignment // 1
MemoryLayout<Bool>.stride // 1
MemoryLayout<Double>.size // 8
MemoryLayout<Double>.alignment // 8
MemoryLayout<Double>.stride // 8
struct Point {
let x: Int
let y: Int
let isFilled: Bool
}
MemoryLayout<Point>.size / / 17
MemoryLayout<Point>.stride / / 24
MemoryLayout<Point>.alignment / / 8
var point = Point(x: 3, y: 4, isFilled: true)
MemoryLayout.size(ofValue: point)/ / 17
MemoryLayout.alignment(ofValue: point) / / 24
MemoryLayout.stride(ofValue: point) / / 8
Copy the code
MemoryLayout is our main tool for analyzing memory in Swift. As mentioned earlier, value types and reference types are not the same in memory allocation, which we will analyze in turn. Then what are the differences between Size, Alignment and Stride
Unsafe Swift: A Road to Memory[3]
In a continuous memory segment (our test code is generally on the stack), Size refers to the Size of the memory occupied by its attribute/member variable; Alignment generally refers to the Size occupied by the attribute/member variable; Stride refers to the Size occupied by the attribute/member variable in a continuous memory segment, which is less than the number of bits to fill zeros.
UnsafePointer
What is UnsafePointer?
UnsafePointer[4] is a Swift API for accessing Pointers to specific types of data.
Types that works with direct memory, get the prefix Unsafe
The format is:
Unsafe [Mutable] [Raw] [Buffer] Pointer [<Type>
]
- Mutable: mutability and immutability, as in Swift
let
和var
The keyword distinguishes variable variability, and a similar scheme is used in Pointers to allow developers to control the variability of Pointers, the writability of the block of memory to which they point. - Raw: indicates that it points to a byte. Adding or not is the difference between a Raw pointer and a generic pointer
- Buffer: Essentially a pointer plus a size
count
, a sequence of contiguous memory blocks. <Type>
: represents a generic type pointer
The combination of Mutable, Raw, and Buffer makes the eight combinative Unsafe Pointer apis, which can be used as needed to achieve a goal.
- UnsafePointer
<T>
- UnsafeMutablePointer
<T>
- UnsafeRawPointer
- UnsafeMutableRawPointer
- UnsafeBufferPointer
<T>
- UnsafeMutableBufferPointer
<T>
- UnsafeRawBufferPointer
- UnsafeMutableRawBufferPointer
Exploring Swift Memory Layout[5] is what you can do to explore Unsafe*Pointer.
Unsafe*Pointer, the Mems tool that MJ uses to print memory messages in her Swift masters class, is also addressed via Unsafe*Pointer. In the future, we will use this tool for memory analysis.
At this point, we have the basic concepts and two tools to aid in memory analysis, MemoryLayout and Mems.
The Swift MemoryLayout
As I mentioned earlier, value types and reference types behave differently in memory, so let’s examine them one by one.
This section refers to Memory Layout in Swift [6]
Analyze memory for value types
In Swift, the memory of a value type (that is, our custom conformance type) can generally be calculated based on the size of its attributes, based on byte alignment.
Calculates the value type size
In the following code example, when we have a base type of Int and a Bool
struct Example {
let foo: Int / / 8
let bar: Bool / / 1
}
// Basically, the memory size of a structure is the same as the size of its instance
let example = Example(foo: 8, bar: true)
print(MemoryLayout<Example>.size) / / 9
print(MemoryLayout<Example>.stride) / / 16
print(MemoryLayout<Example>.alignment) / / 8
Copy the code
Its style in a contiguity of Memory should look like the following: use the Debug tool View Memory of example
We can simply calculate the Size, Alignment and Stride of Example. That is, the Size of Size is equal to the sum of Int + Bool. The maximum number of bytes occupied by Int is 8 bytes. Therefore, based on this as the Size of alignment, 7 bytes of padding is finally supplemented as padding.
When using compound types of value types, do we all evaluate in this way?
In fact, not exactly, let’s swap bar and foo:
struct Example {
let bar: Bool / / 1
let foo: Int / / 8
}
// Basically, the memory size of a structure is the same as the size of its instance
let example = Example(bar: true, foo:8)
print(MemoryLayout<Example>.size) / / 16
print(MemoryLayout<Example>.stride) / / 16
print(MemoryLayout<Example>.alignment) / / 8
Copy the code
Its Size value is 16, is it as confusing as when I first saw it? Why does the Size increase?
Its style in a contiguity of Memory should look like the following: use the Debug tool View Memory of example
Size is the Size of a sequence of contiguous memory. Swift does not optimize alignment as objective-C does, but rather arranges attributes according to their current order.
At present, it seems to have no impact, because the size of its Stride is also 16 bytes.
But consider the following two classes:
struct Example1 {
let foo: Int
let bar: Bool
let bar1: Bool
} // Int, Bool, Bool
struct Example2 {
let bar: Bool
let foo: Int
let bar1: Bool
} // Bool, Int, Bool
Copy the code
Let’s analyze it:
For Example1, its Size value is 8 bytes, plus 1 byte, plus 1 byte, adding up to 10 bytes, and its Stride Size is 16 bytes from an 8-byte alignment, plus 6 bytes for padding.
For Example1, its Size value is 8 bytes, plus 8 bytes, plus 1 byte, adding up to 17 bytes, and its Stride Size is 24 bytes based on alignment of 8 bytes, There is a 7-byte complement between the first Bool and Int, followed by a 7-byte complement.
print(MemoryLayout<Example1>.size) / / 10
print(MemoryLayout<Example1>.stride) / / 16
print(MemoryLayout<Example2>.size) / / 17
print(MemoryLayout<Example2>.stride) / / 24
Copy the code
If you are really memory sensitive or interacting with insecure libraries, it is recommended that you use the layout that makes the most sense.
Analyze memory for reference types
Note that the above calculation applies only to value types, and we cannot compare the calculation with reference type memory.
MemoryLayout is also used to analyze classes of reference type
class Point {
var x: Int
var y: Int
init(_ xI: Int._ yI: Int) {
x = xI
y = yI
}
}
print("Point.size".MemoryLayout<Point>.size) / / 8
print("Point.stride".MemoryLayout<Point>.stride) / / 8
let point = Point(10.20)
// Use memoryLayout.size (ofValue: _) to get the size of the instance
print("point.size".MemoryLayout.size(ofValue: point)) / / 8
print("point.stride".MemoryLayout.stride(ofValue: point)) / / 8
Copy the code
Why is that?
In general, value types (struct, Int, Bool, Float, etc.) exist in the stack, while reference types (classes) are allocated in the heap, although this is not 100% true.
We use MemoryLayout to measure only the pointer of Point, and the size of the pointer and the size of the stride are both 8 bytes.
How big is an instance object of a reference type?
In the previous article on iOS memory allocation, we explained that the minimum size of an object in iOS is 16 bytes and its size is a multiple of 16. The same applies to Swift reference types.
How do you prove it?
Called using assembly proofmalloc
methods
The source code for malloc has been analyzed many times in the previous article about iOS memory allocation. Here we just need to prove that the allocation process calls the malloc method.
Analyze which methods are executed when we initialize a reference type (class) by looking at assembly code:
Specify specific nouns such as alloc and malloc in the process, which means the system is being allocated
Locate the initialization method:
Use the LLDB commands si and Finish to quickly skip some code, but use caution
Please carefully go to swift_slowAlloc method
The mallioc_zone_malloc method is present in its implementation
We won’t go into what the mallioc_zone_malloc method actually does. Of course, you can also view Swift source code for the corresponding analysis.
usingUnsafe*Pointer
Methods to analyze
Just as in objective-C you can use class_getInstanceSize to get the minimum memory used by a class object, so in Swift you can use:
class Empty {}
print(class_getInstanceSize(Empty.self)) / / 16
class Point {
var x: Int / / 8
var y: Int / / 8
init(_ xI: Int._ yI: Int) {
x = xI
y = yI
}
}
print(class_getInstanceSize(Point.self)) / / 32
Copy the code
Unlike the Objective-C runtime, the simplest Empty class in Swift is also 16 bytes in size, with 8 bytes in addition to the ISA pointer for the reference count.
Addressing Unsafe*Pointer, the system’s native malloc and Unsafe*Pointer are used to analyze memory.
import Foundation
class Point {
var x: Int
var y: Int
init(_ xI: Int._ yI: Int) {
x = xI
y = yI
}
}
let point = Point(10.20)
let ptr = UnsafeRawPointer(bitPattern: unsafeBitCast(point, to: UInt.self))!
print(malloc_size(ptr)) / / 32
Copy the code
other
Whether you use value types or reference types, the alignment is based on the order of the attributes and is not optimized for alignment.
class Example1 {
let foo: Int = 10
let bar: Bool = true
let bar1: Bool = false
} // Int, Bool, Bool
print(class_getInstanceSize(Example1.self)) / / 32
let example1 = Example1(a)let ptr1 = UnsafeRawPointer(bitPattern: unsafeBitCast(example1, to: UInt.self))!
print(malloc_size(ptr1)) / / 32
class Example2 {
let bar: Bool = true
let foo: Int = 10
let bar1: Bool = false
} // Bool, Int, Bool
print(class_getInstanceSize(Example2.self)) / / 40
let example2 = Example2(a)let ptr2 = UnsafeRawPointer(bitPattern: unsafeBitCast(example2, to: UInt.self))!
print(malloc_size(ptr2)) / / 48
Copy the code
Good at memory analysis tools for analysis, can write more reasonable memory applications.
conclusion
After the shameless analysis of objective-C NSObject alloc process last time, I have a simple analysis of Swift structure/class initialization.
In this article, MemoryLayout, as well as Unsafe*Pointer and malloc_size, are introduced to analyze the size of value types and reference types.
Because memory is expensive, complex objects are created in the proper order of their attributes to get the most reasonable size.
The resources
Memory security: docs.swift.org/swift-book/…
Swift 5 Principle of compulsory exclusivity: juejin.cn/post/684490…
Broadening Swift: A Road to Memory: medium.com/swlh/unsafe…
UnsafePointer:developer.apple.com/documentati…
GOTO 2016 • Exploring Swift Memory Layout • Mike Ash: www.youtube.com/watch?v=ERY…
Memory Layout in Swift: theswiftdev.com/memory-layo…
If you have any questions, please comment directly, and feel free to express anything wrong with the article. If you wish, you can spread the word by sharing this article.
Thank you for reading this! 🚀