Why are Pointers unsafe

  • For example, when we create an object, we need to allocate memory space in the heap. But this memory space has a finite declaration period, which means that if we use a pointer to this memory space, if the current memory space reaches its lifetime (reference count is 0), then our current pointer becomes undefined.
  • The memory space we create is bounded, for example we create an array of size 10, which we access through a pointerindex = 11Is crossing the line and visiting an unknown space.
  • Pointer types inconsistent with memory value types are also unsafe.

Second, the pointer

Pointers in Swift fall into two categories

  1. typed pointer: Specifies the data type pointer, that isUnsafePointer<T>, where T representsThe generic
  2. raw pointer: pointer to an unspecified data type (native pointer), that isUnsafeRawPointer

Comparison with Pointers in OC:

OC Swift paraphrase
const T * unsafePointer<T> Pointers and what they point toimmutable
T * unsafeMutablePointer Pointers and what they point tovariable
const void * unsafeRawPointer The memory region to which the pointer points is undetermined
void * unsafeMutableRawPointer Same as above

2.1 Use of native Pointers

Let’s see how we can use raw Pointer to store 4 integer data. We use UnsafeMutableRawPointer:

/ / open the memory, 32 bytes allocated space is 8 bytes (Int), the size of alignment is 8 bytes aligned let p = UnsafeMutableRawPointer. The allocate (byteCount: 32, alignment: 8) // Save value for I in 0.. <4 {// Specify the number of current moves, that is, I * 8 p.anced (by: I * 8).storeBytes(of: I + 1, as: int.self)} // The value for I in 0.. Print ("index: \(I), value: ") print("index: \(I), value: ") \(value)")} // Allocate ()Copy the code

Run the program to view the execution result, and the value can be printed normally:

MemoryLayout can be used to dynamically obtain the stride size when moving the number of steps. Modify the code as follows:

p.advanced(by: i * MemoryLayout<Int>.stride).storeBytes(of: i + 1, as: Int.self)
Copy the code

MemoryLayout

. Size // size refers to the actual size of the current type MemoryLayout

. Stride // stride translation is a stride, MemoryLayout

. Alignment // Indicates the alignment mode of memory, whether the alignment is 1-byte or 4-byte


Run the program and still print normally:

2.2 Use of type Pointers

We can use the withUnsafePointer(to:) method to get the address of the basic data type:

Var age = 18 let p = withUnsafePointer(to: &age) {PTR in return PTR} print(p) 0x0000000100008058Copy the code

2.2.1 How do I Access a Value pointed to by a Pointer

We can access variable values via the pointer’s pointee property:

Var age = 18 let p = withUnsafePointer(to: &age){$0} print(p.pointeeCopy the code

2.2.2 How do I Change the Value pointed to by a Pointer

  1. Indirect changes

    Var age = 18 age = withUnsafePointer(to: &age) {PTR in return ptr.pointee + 12} print(age) The following output is displayed: 30Copy the code
  2. Through withUnsafeMutablePointer

    Var age = 18 withUnsafeMutablePointer(to: &age) {PTR in ptr.pointee += 12} print(ageCopy the code
  3. Create UnsafeMutablePointer by allocate

    Var age = 18 let PTR = UnsafeMutablePointer<Int>. Allocate (capacity: 1) // Initialize PTR. Initialize (to: Age) ptr.deinitialize(count: 1) ptr.pointee += 12 print(ptr.pointee) // Release ptr.deallocate(Copy the code

    When creating UnsafeMutablePointer by allocate, note the following

    • initialize 与 deinitializeUse in pairs
    • deinitializeIn thecountAt the time of applicationcapacityNeed to beconsistent
    • Must be after usedeallocate

2.2.3 Accessing structure objects through Pointers

Let’s define a structure

Struct SSLTeacher {var age = 18 var height = 1.85} var t = SSLTeacher()Copy the code

We then create a pointer using UnsafeMutablePointer and access the structure object T in three ways:

Let PTR = UnsafeMutablePointer<SSLTeacher>. Allocate (capacity: allocate) PTR. Initialize (to: SSLTeacher()) [1] = SSLTeacher(age: 20, height: Print (PTR [0]) print(PTR [1]) print(PTR. Pointee) print(PTR +1).pointee Forerunner () print(ptr.pointee) // The precursor must be the same as the allocation ptr.deinitialize(count: // Locate ptr.deallocate()Copy the code

Run the program and view the execution result:

2.3 Macho pointer operation case

Next, we will familiarize ourselves with the operation of Pointers through examples. We will use Pointers to read property names in Macho, class names, and methods in vTable.

2.3.1 Obtaining a Pointer to a classDescriptor

To get a pointer to a classDescriptor, run the following code:

class SSLTeacher{ var age: Int = 18 var name: String = "SSL"} var mhHeaderPtr = _dyLD_GET_image_header (0 mhHeaderPtr_IntRepresentation = UInt64(bitPattern: Int64(Int(bitPattern: Var setCommond64Ptr = getSegByName ("__LINKEDIT") var linkBaseAddress: UInt64 = 0 if let vmaddr = setCommond64Ptr? .pointee.vmaddr, let fileOff = setCommond64Ptr? .pointe. fileoff{linkBaseAddress = vmaddr-fileoff} //__swift5_types section pFile address var size: UInt = 0 var ptr = getsectdata("__TEXT", "__swift5_types", &size) // __swift5_types section pFile address in Macho UInt64 = 0 if let unwrappedPtr = ptr{ let intRepresentation = UInt64(bitPattern: Int64(Int(bitPattern: UnwrappedPtr)) offset = intrepresentation-linkBaseAddress} // DataLO's memory address var dataLoAddress = Offset // DataLO pointer var dataLoAddressPtr = withUnsafePointer(to: &dataloAddress){return $0} var dataLoContent = UnsafePointer<UInt32>. Init (bitPattern: Int(exactly: dataLoAddress) ?? 0)? .pointee // Descriptor Address in Macho Let typeDescOffset = UInt64(dataLoContent!) + offset -linkBaseAddress // Descriptor address var typeDescAddress = typeDescOffset + mhHeaderPtr_IntRepresentation //print(typeDescAddress) struct TargetClassDescriptor{ var flags: UInt32 var parent: UInt32 var name: Int32 var accessFunctionPointer: Int32 var fieldDescriptor: Int32 var superClassType: Int32 var metadataNegativeSizeInWords: UInt32 var metadataPositiveSizeInWords: UInt32 var numImmediateMembers: UInt32 var numFields: UInt32 var fieldOffsetVectorOffset: UInt32 var Offset: UInt32 var methods: UInt32} // Get pointer to Descriptor Let classDescriptor = UnsafePointer<TargetClassDescriptor>. Init (bitPattern: Int(exactly: typeDescAddress) ?? 0)? . Pointee/print/print (classDescriptor) / / * * * * * * * * * * the output * * * * * * * * * * Optional (SwiftTest. TargetClassDescriptor (flags:...). )Copy the code
  • VM Address: Virtual Memory Address, the Virtual Memory Address of the segment, its location in Memory
  • VM Size: Virtual Memory Size, the Virtual Memory Size of the segment
  • File Offset: indicates the Offset of the segment in virtual memory
  • File Size: the Size of the segment in virtual memory
  • Address Space Layout Random is a security protection technology against buffer overflow. By randomizing the Layout of linear areas such as heap, stack and shared library mapping, it increases the difficulty of the attacker to predict the destination Address and prevents the attacker from locating the attack code. A technique to prevent overflow attacks
  • Relevant Macho:

2.3.2 Getting the class name

Add the following code to print the class name:

if let name = classDescriptor? .name{ let nameOffset = Int64(name) + Int64(typeDescOffset) + 8 let nameAddress = nameOffset + Int64(mhHeaderPtr_IntRepresentation) if let cChar = UnsafePointer<CChar>.init(bitPattern: Int (nameAddress)) {print (String (cstrings: cChar))}} / / * * * * * * * * * * the output * * * * * * * * * * SSLTeacherCopy the code

2.3.3 Obtaining the Attribute Name

Add the following code to print the property name:

/ / fieldDescriptor attribute address let filedDescriptorRelaticveAddress = typeDescOffset + 16 + mhHeaderPtr_IntRepresentation struct  FieldDescriptor { var mangledTypeName: Int32 var superclass: Int32 var Kind: UInt16 var fieldRecordSize: UInt16 var numFields: UInt32 // var fieldRecords: [FieldRecord] } struct FieldRecord{ var Flags: UInt32 var mangledTypeName: Int32 var fieldName: UInt32} // fieldDescriptor offset let fieldDescriptorOffset = UnsafePointer<UInt32>. Init (bitPattern: Int(exactly: filedDescriptorRelaticveAddress) ?? 0)? The pointee / / fieldDescriptor memory address let fieldDescriptorAddress = filedDescriptorRelaticveAddress + UInt64(fieldDescriptorOffset!) // fieldDescriptor pointer let fieldDescriptor = UnsafePointer< fieldDescriptor >. Init (bitPattern: Int(exactly: fieldDescriptorAddress) ?? 0)? .pointee // Get attribute name for I in 0.. <fieldDescriptor! .numfields {// FieldRecord size is 12, so the stride is 12 let stride: UInt64 = UInt64(I * 12) // Move 16 bytes to fieldRecords let fieldRecordAddress = fieldDescriptorAddress + Stride + 16 let fieldNameRelactiveAddress = UInt64(2 * 4) + fieldRecordAddress - linkBaseAddress + mhHeaderPtr_IntRepresentation let offset = UnsafePointer<UInt32>.init(bitPattern: Int(exactly: fieldNameRelactiveAddress) ?? 0)? .pointee let fieldNameAddress = fieldNameRelactiveAddress + UInt64(offset!) - linkBaseAddress if let cChar = UnsafePointer<CChar>.init(bitPattern: Int(fieldNameAddress)){ print(String(cString: CChar)}} / / * * * * * * * * * * the output * * * * * * * * * * age nameCopy the code

2.3.4 Obtaining the Method Name

Modify the SSLTeacher class to add three methods and add the code to print the method names:

@interface SSLTest : NSObject + (void)callImp:(IMP)imp; @end @implementation SSLTest + (void)callImp:(IMP)imp { imp(); } @end class SSLTeacher { func teach1() { print("testch1"); } func teach2() { print("testch2"); } func teach3() { print("testch3"); }} let numVTables = classDescriptor? .methods struct VTable { var kind: UInt32 var offset: UInt32 } for i in 0.. <numVTables! {// Calculate offset let vTableOffSet = Int(typeDescOffset) + MemoryLayout<TargetClassDescriptor>. Size + Int(I) * MemoryLayout<VTable>. Size // Obtain the VTable address. Let vTableAddress = mhHeaderPtr_IntRepresentation + UInt64(vTableOffSet) let vTable = UnsafePointer<VTable>.init(bitPattern: Int(exactly: vTableAddress) ?? 0)? ImpAddress = vTableAddress + 4 + UInt64(vTable! .offset) - linkBaseAddress SSLTest.callImp(IMP(bitPattern: UInt(impAddress))!) ; } / / * * * * * * * * * * the output * * * * * * * * * * testch1 testch2 testch3Copy the code

3. Memory binding

Swift provides three different apis to bind/rebind Pointers:

3.1 assumingMemoryBound(to:)

The purpose of this API is to tell the compiler what type to expect (allowing the compiler to bypass type checking without actually converting the type), as shown in the following example:

  • The above error says willA tuple typeThe pointer assigned toThe Int typePointer, type mismatch
  • But in fact tuples are value types, so essentially the memory space holds data of type Int
  • The following throughassumingMemoryBound(to:)Function to modify

3.2 bindMemory (to: capacity:)

Used to change the type of memory binding, and if the memory does not already have a type binding, it will be bound to that type for the first time. Otherwise, rebind the type and all the values in memory will change to that type.

Func testPointer(_ p: UnsafePointer<Int>) {print(p[0]) print(p[1])} let tuple = (10,20) withUnsafePointer(to: tuple){ (tupleStr: UnsafePointer<(Int, Int)>) in testPointer(UnsafeRawPointer(tupleStr).bindMemory(to: Int. Self, capacity: 1))Copy the code

3.3 withMemoryRebound (to: capacity: body:)

WithMemoryRebound (to: Capacity :body:) is used to temporarily bind the memory type. See the following example

func testPointer(_ p: UnsafePointer<Int8>) { print(p) } let uint8Ptr = UnsafePointer<UInt8>.init(bitPattern: 10) uint8Ptr? .withMemoryRebound(to: Int8.self, capacity: 1) { (int8Ptr: UnsafePointer<Int8>) in testPointer(int8Ptr) }Copy the code

Four, strong reference

Swift uses an automatic reference counting (ARC) mechanism to track and manage memory.

Add the following code first:

class SSLTeacher { var age: Int = 18 var name: String = "Kody"} var t = SSLTeacher() print(unmanaged.passunretained (t as AnyObject).toopaque ()) // Fixed the memory address of the instance object NSLog("end")Copy the code

Breakpoint debugging, printing memory:

The last 8 bytes of the first 16 bytes of the memory address of the instance object are used to store the reference count. The value in this case is 0x3.

4.1 Source code analysis refCounts

Start with the definition of reference counts. Open the source code to find refCounts in heapObject.h:

#define SWIFT_HEAPOBJECT_NON_OBJC_MEMBERS       \
  InlineRefCounts refCounts
Copy the code

RefCounts is of type InlineRefCounts.

typedef RefCounts<InlineRefCountBits> InlineRefCounts; typedef RefCounts<SideTableRefCountBits> SideTableRefCounts; template <typename RefCountBits> class RefCounts { std::atomic<RefCountBits> refCounts; . }Copy the code

InlineRefCounts is an alias to RefCounts

. RefCounts is a template class that accepts InlineRefCountBits:

Take a look at the definition of InlineRefCountBits:

typedef RefCountBitsT<RefCountIsInline> InlineRefCountBits;
Copy the code

RefCountBitsT is another template class. RefCountIsInline is either true or false:

Note the bits member variable, which is defined by Type in RefCountBitsInt.

Click on Type to view:

What is the current reference count when we create an instance object?

Check the refCounts:

This is the RefCountBitsT method:

  • StrongExtraCount is 0, unownedCount is 1
  • StrongExtraRefCountShift is 33, PureSwiftDeallocShift is 0, and UnownedRefCountShift is 1
  • So: 0 << 33 =0, 1 << 0 =1, 1 << 1 =2
  • 2 = 0 | 1 |0x0000000000000003, which explains where the reference count is above0x3The reason why

4.2 Memory distribution for reference counting

Create 3 breakpoints and print reference count memory at breakpoints:

  • As can be seen from above, when the strong reference is 1, the reference count is stored at high because it is moved 33 bits to the left33
  • When the strong reference is 2, the reference count is stored at high because it has been moved 33 bits to the left34

The memory distribution of reference counting is as follows:

  • UnownedRefCount: no primary reference count
  • IsDeinitingMask: Whether destructing is taking place
  • StrongExtraRefCount: Strong application count

4.3 Adding strong Reference Counts

Open the source code and find the related increment function for strong reference counting:

From the above function, we can see that the strong reference count is incremented by moving 1 33 bits to the left and then adding it to the bits, which is consistent with our analysis of reference count memory distribution above.

Weak references

Looking at the code below, teacher and subject generate circular references. In Swift, there are weak references and undirected references to resolve circular references

5.1 Overview of Weak References

class SSLTeacher { var age: Int = 18 var name: String = "ssl" var subject:SSLSubject? } class SSLSubject { var subjectName: String var subjectTeacher: SSLTeacher init(_ subjectName: String, _ subjectTeacher: SSLTeacher) { self.subjectName = subjectName self.subjectTeacher = subjectTeacher } } var teacher = SSLTeacher() var Subject = sslsubject.init (" math ",teacher) teacherCopy the code

A weak reference does not maintain a strong reference to the instance it references, and thus does not prevent ARC from releasing the referenced instance. This feature prevents references from becoming circular strong references. When declaring an attribute or variable, prefix it with the weak keyword to indicate a weak reference.

Since a weak reference does not strongly retain a reference to the instance, it is possible that the instance was freed and the weak reference still refers to the instance. Therefore, ARC automatically sets weak references to nil when the referenced instance is freed. Since weak references need to allow their value to be nil, they must be of optional type.

Weak Example codes for using weak:

class SSLTeacher {
    var age: Int = 18
    var name: String = "ssl"
    weak var subject:SSLSubject?
}
Copy the code

5.2 Source analysis SideTable derivation

What function is called when you add the weak keyword

You can see that the swift_weakInit function is called, which is available in heapObject.cpp

Declaring a weak variable is equivalent to defining a WeakReference object. Click to see nativeInit

Weak essentially creates a SideTable that calls the allocateSideTable function.

5.3 Source analysis SideTable

Check the allocateSideTable:

HeapObjectSideTableEntry vs. InlineRefCounts

View the definition of HeapObjectSideTableEntry:

HeapObjectSideTableEntry Uses SideTableRefCounts to store reference counts.

typedef RefCounts<InlineRefCountBits> InlineRefCounts; typedef RefCounts<SideTableRefCountBits> SideTableRefCounts; class alignas(sizeof(void*) * 2) SideTableRefCountBits : public RefCountBitsT<RefCountNotInline> { uint32_t weakBits; . }Copy the code

As you can see, SideTableRefCounts and InlineRefCounts share the same template class RefCounts

. They both inherit from RefCountBitsT. The subclass SideTableRefCounts has an additional weakBits member variable.

The following uses LLDB debugging to check the storage of reference counts in different cases

  • 0xc0000000200c437cIs the value after weak reference
  • Here is the weak reference creation function
    • Puts the address of the current side object into a 64-bit bitfield
    • Two identifier bits, 62 and 63, are 1
  • will0xc0000000200c437cReverse operation, get hash table memory address0x100621BE0
  • View the structure of the hash table through LLDB debugging
    • 0x0000000000000003Is a strongly referenced value
    • 0x0000000000000002Is the value of a weak reference

Unreferenced

Like weak references, an ownerless reference does not strongly reference an instance. Unlike weak references, however, an ownerless reference assumes that the instance will always have a value.

When we access an undirected reference like this, it’s kind of like accessing a wild pointer, because we always assume a value, so we crash the program

According to the official Apple documentation, we must use weak when we know that the life cycles of two objects are not related. In contrast, non-strongly referenced objects that have the same or longer lifetime as strongly referenced objects should use unowned.

Circular references to closures

7.1 Closure Circular Reference in Swift

Closures capture external variables by default in Swift, see the following code:

Var age = 18 let closure = {age += 1} Closure () print(ageCopy the code

As you can see from the results, changes made to variables inside the closure will change the values of the original variables outside.

There is also a problem. If we define a closure inside a class, the current closure will capture our current instance object while accessing properties.

Look at the following code. The code in deinit is printed, and here is the case when the instance is released normally:

class SSLTeacher { var age: Int = 18 deinit {print("SSLTeacher deinit")}} func testARC() {let t = SSLTeacher()} testARC(Copy the code

Teacher and closure generate circular references, and the code in deinit is not printed:

class SSLTeacher {
    var age: Int = 18
    var testClosure:(() -> ())?
    
    deinit {
        print("SSLTeacher deinit")
    }
}

func testARC() {
    let t = SSLTeacher()
    
    t.testClosure = {
        t.age += 1
    }
}
testARC()
Copy the code

The destructor is not called, indicating that the teacher is not released, resulting in a circular reference.

7.2 Resolving Circular References

Weak resolve circular references:

class SSLTeacher { var age: Int = 18 var testClosure:(() -> ())? deinit { print("SSLTeacher deinit") } } func testARC() { let t = SSLTeacher() t.testClosure = { [weak t] in t! .age += 1}} testARC() Output: SSLTeacher deinitCopy the code

As you can see, with the weak modifier, the contents of the closure will print normally, and we can also solve this problem by using unowned:

func testARC() {
    let t = SSLTeacher()
    
    t.testClosure = { [unowned t] in
        t.age += 1
    }
}

testARC()
Copy the code

7.3 Capturing a List

What is a capture list? By default, closure expressions capture constants and variables from the range around them and strongly reference those values. You can use the capture list to show controls on how values are captured in closures.

Before the argument list, the capture list is written as a comma-enclosed list of expressions, enclosed in square brackets. If you use a capture list, you must use the in keyword.

Variables in the parameter list are not strongly referenced, see the following example code:

Closure () var age = 1 var height = 0.0let closure = {[age] in print(age) print(height)} age = 10 height = 1.85 closure() // The output is 1, 1.85Copy the code