background

Pointers are not needed in most cases for Swift development and are not recommended, but sometimes they are needed for more low-level writing. Recently, I happened to need Pointers in some libraries I was writing, but Swift does not have very detailed documentation on the use of Pointers, resulting in very laborious code writing, so I summarized. There are many articles in Runtime, but few in Swift, so I prepared Swift trilogy, introducing the underlying knowledge, some of which I have read and understood before, but are not very in-depth, so I will study while writing, respectively:

Swift Trilogy (I) : pointer usage Swift trilogy (II) : memory layout Swift trilogy (III) : method distribution

The first is this article. The second and third are not written yet, but they will be finished soon.

MemoryLayout

MemoryLayout<T>. Size // Memory size required by type T MemoryLayout<T>. Stride // Memory size allocated by type T (due to memory alignment, MemoryLayout<T>. Alignment // Cardinality of memory alignmentCopy the code

Pointer to the classification

  • Unsafe, isn’t really unsafe, just a sign for developers not to use it.
  • Write Access: Write is available.
  • Collection: Acts like a container for adding data.
  • Strideable: Pointers can be moved using the advanced function.
  • Typed: Whether the type (stereotype) needs to be specified.

C and Swift’s comparison table on Pointers:

C Swift annotations
const Type * UnsafePointer<Type> The pointer is mutable and the memory value to which the pointer points is immutable
Type * UnsafeMutablePointer<Type> The pointer and the memory to which the pointer points are variable
ClassType * const * UnsafePointer<UnsafePointer<Type>> Pointers to Pointers: Pointers are immutable, and the class to which they point is mutable
ClassType ** UnsafeMutablePointer<UnsafeMutablePointer<Type>> Pointers to Pointers: Both Pointers and the classes to which Pointers point are mutable
ClassType ** AutoreleasingUnsafeMutablePointer<Type> As a pointer parameter in the OC method
const void * UnsafeRawPointer Pointer to an area of memory of undetermined type
void * UnsafeMutableRawPointer Pointer to an area of memory of undetermined type
StructType * OpaquePointer Some custom types in C language do not have corresponding types in Swift
int a[] UnsafeBufferPointer/UnsafeMutableBufferPointer An array pointer

Typed pointer

Pointers in Swift are divided into two types: typed Pointer Specifies a pointer of a data type. Raw Pointer specifies a pointer that does not specify a data type (a native pointer).

typed pointer
UnsafePointer
UnsafeMutablePointer
UnsafeBufferPointer
UnsafeMutableBufferPointer

UnsafeMutablePointer

Memory referenced by UnsafeMutablePointe has three states:

  1. Not Allocated: Memory has Not been Allocated which means it is a null pointer or has been previously released
  2. Allocated but not initialized: Allocated memory has been Allocated but the value has not been initialized
  3. Allocated and initialized: Memory has been Allocated and the value has been initialized

allocate

// Bind the type and allocate memory
Allocate is a class method
// Capacity: Int Specifies the memory of the generic type corresponding to the capacity number applied to the system
let pointer = UnsafeMutablePointer<Int>.allocate(capacity: MemoryLayout<Int>.stride)
// let pointer = UnsafeMutablePointer<CCInfo>.allocate(capacity: MemoryLayout.stride(ofValue: CCInfo()))
Copy the code

initialize

// Initialize. For basic data types such as Int, Float, and Double, the default value 0 is allocated
pointer.initialize(to: 12)
/ / pointer. Pointee is 12

/ / assignment
pointer.pointee = 10

Copy the code

deinitialize

// Pair with initialize: deinitialize: used to destroy the object to which the pointer points
// The pointer still points to the previous value
pointer.deinitialize(count: 1)

Copy the code

deallocate

// allocate(capacity:) is used to allocate memory
pointer.deallocate()
Copy the code

Note that deinitialize is not necessary for “trivial values” such as Int that are mapped to Int in C, because these values are assigned to the constant segment. But for objects or struct instances like classes, memory leaks can occur if the pairing is not initialized and destroyed. So without special consideration, make sure initialize: pairing with Deinitialize is a good habit, regardless of what memory is init.

UnsafePointer

UnsafePointer is immutable, and const Pointers in C correspond to UnsafePointer (most commonly const char * in C strings).

  • The pointee attribute in UnsafePointer can only be get but not set.
  • There is no allocate method in UnsafePointer.

Initialization can create an UnsafePointer from UnsafeMutablePointer, OpaquePointer, or another UnsafePointer. Others are similar to UnsafeMutablePointer.

// Initializes a constant pointer to 'UnsafePointer' through another variable pointer
let pointer = UnsafeMutablePointer<Int>.allocate(capacity: MemoryLayout<Int>.stride)
pointer.pointee = 20
let pointer1 = UnsafePointer<Int>.init(pointer)
print(pointer1.pointee)  / / 20
Copy the code

The memory referenced by the pointer is accessed as a different type

withMemoryRebound

Temporarily rebind memory to another type.

var int8: Int8 = 123
let int8Pointer = UnsafeMutablePointer(&int8)
int8Pointer.withMemoryRebound(to: Int.self, capacity: 8) { ptr in
    print(ptr.pointee) / / 123
}
Copy the code

bindMemory

This method binds memory to the specified type and returns a pointer to UnsafeMutablePointer< specified type >, using the original pointer to memory.

let intPointer = UnsafeRawPointer(int8Pointer).bindMemory(to: Int.self, capacity: 1)
print(intPointer.pointee) / / 123

Copy the code

When converting a native pointer to a memory type using the bindMemory method, only one type can be bound at a time. For example, a native pointer to an Int cannot be bound to a Bool.

assumingMemoryBound

This method means that the original pointer is converted directly to a pointer to UnsafeMutablePointer.

let strPtr = UnsafeMutablePointer<CFString>.allocate(capacity: 1)
let rawPtr = UnsafeRawPointer(strPtr)
let intPtr = rawPtr.assumingMemoryBound(to: Int.self)
Copy the code

UnsafeBufferPointer

UnsafeBufferPointer INDICATES a set of consecutive data Pointers. BufferPointer implements Collection, so you can directly iterate over the operation data using various methods in Collection, filter, map… , Buffer can operate on a contiguous space, similar to the pointer to an array in C. But again, this UnsafeBufferPointer is a constant, it can only get data, it can’t modify the data through this pointer. With the matching is UnsafeMutableBufferPointer pointer.

var array = [1.2.3.4]
/ / traverse
let ptr = UnsafeBufferPointer.init(start: &array, count: array.count)
ptr.forEach { element in
    print(element) / / 1, 2, 3, 4
}

/ / traverse
array.withUnsafeBufferPointer {  ptr in
    ptr.forEach {
        print($0) / / 1, 2, 3, 4}}Copy the code

UnsafeBufferPointer can use the baseAddress attribute, which contains the baseAddress of the buffer.

let array: [Int8] = [65.66.67.0]
puts(array)  // ABC
array.withUnsafeBufferPointer { (ptr: UnsafeBufferPointer<Int8>) in
    puts(ptr.baseAddress! + 1) //BC
}

Copy the code
let count = 2
letpointer = UnsafeMutablePointer<Int>.allocate(capacity: count) pointer.initialize(repeating: 0, count: count) defer { pointer.deinitialize(count: Count) pointer.deallocate()} pointer.pointee = 42 // Pointer to a memory address 42 pointer.advanced(by: 1). Pointee = 6 // Int * * * a pointer to a pointer that starts at the address of the pointer and moves by 1 bit Advanced (by: 1).pointeelet bufferPointer = UnsafeBufferPointer(start: pointer, count: count)
for (index, value) in bufferPointer.enumerated() {
    print("value \(index): \(value)") // value 0: 42, value 1: 6
}

Copy the code

UnsafeMutableBufferPointer

Pointer variable sequences, UnsafeMutableBufferPointer has the ability to sequence changes:

let pointer = UnsafeMutablePointer<Int>.allocate(capacity: count)
letbufferPointer = UnsafeMutableBufferPointer<Int>.init(start: pointer, count: BufferPointer [0] = 120 bufferPointer[1] = 130 bufferPointer[1] = 130in
    print(a) // 120, 130, 120054000649232, 73, 105553129173888
}
print(bufferPointer.count) // 5

Copy the code

The situation is similar to that of UnsafeBufferPointer except that when initialized, UnsafeMutablePointer is required. Initialization cannot be done directly with an existing sequence. Note that if a sequence is initialized without assigning a value to each element, the value of each element will be random

Raw pointer

raw pointer
UnsafeRawPointer
UnsafeMutableRawPointer
UnsafeRawBufferPointer
UnsafeMutableRawBufferPointer

UnsafeMutableRawPointer

UnsafeMutableRawPointer A raw pointer used to access and manipulate untyped data.

// Allocate memory, byteCount: indicates the total number of bytes required, indicating the alignment of Int types
let pointer = UnsafeMutableRawPointer.allocate(byteCount: 4, alignment: MemoryLayout<Int>.alignment)

// Stores the given value in raw memory at the specified offset
pointer.storeBytes(of: 0x00060001.as: UInt32.self)

// the memory referenced from pointer is loaded with a UInt8 instance
let value = pointer.load(as: UInt8.self)
print(value) / / 1

// pointer.storeBytes(of: 42, as: Int.self)
// let value = pointer.load(as: Int.self)
// print(value) 42

let offsetPointer = pointer.advanced(by: 2)
// let offsetPoint = pointer + 2 // offset 2 bytes
let offsetValue = offsetPointer.load(as: UInt16.self) // Load the third and fourth bytes as UInt16 instances

print(offsetValue) / / 6
pointer.deallocate()

Copy the code

Note: The methods storeBytes and Load are used to store and read bytes, respectively.

UnsafeRawPointer

A raw pointer used to access untyped data. UnsafeRawPointer can only be obtained by other Pointers using the init method. Like UnsafePointer, there is no allocate static method. There are two binding methods similar to UnsafeMutableRawPointer, bindMemory and The assumption MemoryBound, which bind to the UnsafePointer pointer.

// Access different types of the same memory
var uint64: UInt64 = 257
let rawPointer = UnsafeRawPointer(UnsafeMutablePointer(&uint64))
let int64PointerT =  rawPointer.load(as: Int64.self)
let uint8Point = rawPointer.load(as: UInt8.self)

print(int64PointerT) / / 257
print(uint8Point) / / 1

// 257 = 1 0000 0001 while UInt8 means to store 8-bit unsigned integers, i.e. one byte size, 2^8 = 256, [0, 255], those beyond the 8-bit range cannot be loaded, so print 1

Copy the code

UnsafeRawBufferPointer and UnsafeMutableRawBufferPointer

Reference Swift memory assignment exploration 2: pointer in Swift usage description:

UnsafeRawBufferPointer and UnsafeMutableRawBufferPointer refer to is a series of memory area is not binding type. We can think of them as arrays that contain each byte before memory binding. At the bottom, basic data units can be copied effectively, and objects that are not retained and stong can be safely copied, as well as objects from THE C API. For primitive Swift types, some objects that contain references may fail to be copied, but we can use Pointers to copy their values, and the result is valid. If we force a copy of a delivery type, it won’t work unless we use APImemmove() in C. To operate

UnsafeRawBufferPointer and UnsafeMutableRawBufferPointer is memory view, even though we know that it points to area of memory, but it does not have the memory references. Copying a variable of type UnsafeRawBufferPointer does not copy its memory; But initializing a collection to a new collection copies the reference memory in the collection.

Conclusion:

  1. Each byte in memory is treated as a UInt8 value independent of the memory binding type, regardless of the type of value held in that memory.
  2. UnsafeRawBufferPointer/UnsafeMutableRawBufferPointer instance is raw bytes of the memory area of the view.
  3. Reading is a kind of from the original buffer from memory type operation, UnsafeMutableRawBufferPointer instance can be written to memory, can’t UnsafeRawBufferPointer instance.
  4. To type, memory must be bound to a type.
let pointer = UnsafeMutableRawBufferPointer.allocate(byteCount: 3, alignment: MemoryLayout<Int>.alignment)
pointer.copyBytes(from: [1.2.3])
pointer.forEach {
    print($0) / / 1, 2, 3
}

Copy the code

Memory Access

To access the underlying memory through typing operations, the memory must be bound to a simple type.

typed pointer
withUnsafePointer
withUnsafeMutablePointers
withUnsafeBytes
withUnsafeMutableBytes

withUnsafePointer/withUnsafeMutablePointer

Swift cannot operate by using ampersand to retrieve the address directly as in C. If we want to pointer a variable, we can use two helper methods withUnsafePointer or withUnsafeMutablePointer. The difference between withUnsafePointer and withUnsafeMutablePointer is that the converted pointer is immutable and the converted pointer is mutable.

Basic data types

var a = 0

withUnsafePointer(to: &a) { ptr in
    print(ptr) // 0x00007ffeeccb3b40
}

a = withUnsafePointer(to: &a) { ptr in
    returnVar b = 42 withUnsafeMutablePointer(to: &b) {PTRinPtr. pointee += 100 ptr.pointee += 100print(b)   // 142

var arr = [1, 2, 3]
withUnsafeMutablePointer(to: &arr) { ptr in
    ptr.pointee[0] = 10
}
print(arr)   // [10, 2, 3]

arr.withUnsafeBufferPointer { ptr in
    ptr.forEach{
        print("\ [$0)") / / 10 2, 3}} / / modify memory value. Arr withUnsafeMutableBufferPointer {PTRin
    ptr[0] = 100

    ptr.forEach {
        print("\ [$0)") // 100 2 3}}Copy the code

Gets a pointer to an instance of the struct type


struct User {
    var name: Int = 5

    init(name: Int = 5) {
        self.name = name
    }
}

var user = User(a)let pointer = withUnsafeMutablePointer(to: &user, {$0})
print(user) // user
pointer.pointee = User(name: 10)
print("\(pointer.pointee)") // User(name: 10)
print(user) // User(name: 10)

Copy the code

Get a pointer to an instance of class. Instead of using withUnsafePointer or withUnsafeMutablePointer, get a pointer to an instance of class using Unmanaged, Because we’re talking about getting Pointers to objects, we’re going to talk about it in passing.

func headPointerOfClass(a) -> UnsafeMutablePointer<Int8> {
    let opaquePointer = Unmanaged.passUnretained(self as AnyObject).toOpaque()
    let mutableTypedPointer = opaquePointer.bindMemory(to: Int8.self, capacity: MemoryLayout<Self>.stride)
    return UnsafeMutablePointer<Int8>(mutableTypedPointer)
}

Copy the code

withUnsafeBytes/withUnsafeMutableBytes

You can use withUnsafeBytes/withUnsafeMutableBytes for instance the number of bytes.

// Prints a string
let string = "hello"
letdata = string.data(using: .ascii) data? .withUnsafeBytes{ (ptr: (UnsafePointer<Int8>)) in
    print(ptr.pointee) // 104 = 'h'
    print(ptr.advanced(by: 1).pointee)  // 101 = 'e'
}


// Print the structure
struct SampleStruct {
    let number: UInt32
    let flag: Bool
}

MemoryLayout<SampleStruct>.size       // returns 5
MemoryLayout<SampleStruct>.alignment  // returns 4
MemoryLayout<SampleStruct>.stride     // returns 8

var sampleStruct = SampleStruct(number: 25, flag: true)

withUnsafeBytes(of: &sampleStruct) { bytes in
    for byte in bytes {
        print(byte) // 25 0 0 0 1}}let bytes = withUnsafeBytes(of: &sampleStruct) { bytes in
    return bytes // There are strange bugs here!
}

print("Horse is out of the barn!", bytes) // undefined !!! 

Copy the code

Note:

  1. Do not return a pointer from withUnsafeBytes.
  2. Never let Pointers escape the scope of withUnsafeBytes(of:). Such code becomes a ticking time bomb, and you never know when it will work and when it will crash.

Unmanaged<T> (Unmanaged object)

So Apple introduced Unmanaged to manage reference counts. Unmanaged can host Pointers passed by the C API. We can use Unmanaged to determine if it accepts the allocation of reference counts. In order to achieve an effect similar to automatic release; Also, if you don’t use reference counting, you can use the release function provided by Unmanaged to release it manually, which is much easier than doing it in a pointer. The Toll-free BRIDGING and Unmanaged tips article seems to get a lot of things wrong.

An Unmanaged instance encapsulates a CoreFoundation type T that holds a reference to that T object in scope.

There are two ways to declare an object as an unmanaged method:

  • PassRetained: Increases its reference count.
  • PassUnretained: Does not increase its reference count.

There are two ways to get a Swift value from an Unmanaged instance:

  • TakeRetainedValue () : Returns the Swift managed reference in this instance and reduces the number of references at the time of the call.
  • TakeUnretainedValue () : Returns the Swift managed reference in this instance without reducing the number of references.

Retained and retained, and takeRetainedValue and takeUnretainedValue, Apple proposed Ownership Policy:

  • If a function name contains Create or Copy, the caller obtains the object as well as its ownership. The return value Unmanaged requires calling the takeRetainedValue() method to obtain the object. There is no need to call CFRelease in Swift code to relinquish object ownership when the caller is no longer using the object, because Swift only supports ARC memory management, which is slightly different from OC.
  • If a function name contains Get, the caller gets the object without owning it, and the return value Unmanaged requires calling the takeUnretainedValue() method to Get the object.
let bestFriendID = ABRecordID(...)

// Create Rule - retained
let addressBook: ABAddressBook = ABAddressBookCreateWithOptions(nil, nil).takeRetainedValue()

if let// Get Rule - unretained bestFriendRecord: ABRecord = ABAddressBookGetPersonWithRecordID(addressBook, bestFriendID)? .takeUnretainedValue(), // Create Rule (Copy) - retained name = ABRecordCopyCompositeName(bestFriendRecord)? .takeRetainedValue() as? String { println("\(name): BFF!")
    // Rhonda Shorsheimer: BFF!
}

Copy the code

The names of these functions can tell you how to use Unmanaged, but most of the time you’re using C functions that aren’t named this way,

Alamofire NetworkReachabilityManager. Swift, there is a call C method using the fsound_unmanaged.

@discardableResult
    open func startListening() -> Bool {
        var context = SCNetworkReachabilityContext(version: 0, info: nil, retain: nil, release: nil, copyDescription: nil)
        context.info = Unmanaged.passUnretained(self).toOpaque()

        let callbackEnabled = SCNetworkReachabilitySetCallback(
            reachability,
            { (_, flags, info) in
                letreachability = Unmanaged<NetworkReachabilityManager>.fromOpaque(info!) .takeUnretainedValue() reachability.notifyListener(flags) }, &context )letqueueEnabled = SCNetworkReachabilitySetDispatchQueue(reachability, listenerQueue) listenerQueue.async { self.previousFlags = SCNetworkReachabilityFlags() self.notifyListener(self.flags ??  SCNetworkReachabilityFlags()) }return callbackEnabled && queueEnabled
    }

Copy the code

Since the self object is used within the current scope, within the startListening method, we can ensure that the object remains alive throughout use. PassUnretained and takeUnretainedValue are used.

class Person {
    func eat() {
        print(#file, #line, "eat now")
    }
}

func callbackFunc(userPtr: UnsafeMutableRawPointer?) {
    if userPtr == nil { return }
    letuser = Unmanaged<Person>.fromOpaque(userPtr!) .takeRetainedValue() user.eat() } class ViewController: UIViewController { override funcviewDidLoad() {
        super.viewDidLoad()
        
        var user = Person()
        let userPtr = Unmanaged<Person>.passRetained(user).toOpaque()
        callback(callbackFunc, userPtr)
    }
}

Copy the code

We saw that the user object was used outside of the current scope (viewDidLoad) due to the closure, so we need to use takeRetainedValue and passRetained.

let userPtr = Unmanaged<Person>.passRetained(user).toOpaque()

Copy the code

Using passRetained() creates a retained pointer to the same object, so that it can be retained and not destroyed when called in C. This method generates an Unmanaged instance variable. It then converts to UnsafeMutableRawPointer via the toOpaque() method.

 let user = Unmanaged<Person>.fromOpaque(userPtr!) .takeRetainedValue()Copy the code

Extract the User object using the opposite method of Unmanaged, which is more secure and ensures that the object is kept alive during the pass and retrieved directly.

Unmanaged objects that live beyond what the compiler considers lifetime, such as out of scope, must be retained manually using the passRetained method. Once you have manually retained an object, don’t forget to release it, either by calling the release method of an unmanaged object or by taking out the wrapped object with takeRetainedValue and handing its management back to ARC. However, you should never call release or takeRetainedValue on an unmanaged object constructed with passunrepeat. This will cause the original object to be released and throw an exception.

test

Unmanaged is still a bit difficult, I’ve seen this code elsewhere, you can try it on Playground, and if you know all the answers, feel free to leave a comment.

class SomeClass {
    let text: Int

    init(text: Int) {
        self.text = text
    }

    deinit {
        print("Deinit \(text)")}}do {
    let unmanaged = Unmanaged.passRetained(SomeClass(text: 0))
    unmanaged.release()
}

do {
    let _ = Unmanaged.passUnretained(SomeClass(text: 1))
}

do {
    let unmanaged = Unmanaged.passRetained(SomeClass(text: 2))

    let _ = unmanaged.retain()
    unmanaged.release()

    Unmanaged<SomeClass>.fromOpaque(unmanaged.toOpaque()).release()

    unmanaged.release()
}

do {
    let opaque = Unmanaged.passRetained(SomeClass(text: 3)).toOpaque()
    Unmanaged<SomeClass>.fromOpaque(opaque).release()
}

do {
    let unmanaged = Unmanaged.passRetained(SomeClass(text: 4))
    let _ = unmanaged.takeUnretainedValue()
    unmanaged.release()
}

do {
    let unmanaged = Unmanaged.passRetained(SomeClass(text: 5))
    let _ = unmanaged.takeRetainedValue()
}

Copy the code

A function pointer

There are callback functions in C, and swift can use function Pointers when calling such functions in C. In Swift you can decorate a closure with @convention

type annotations
@convention(swift) Indicating that this is a swift closure
@convention(block) Indicates that this is an OC-compatible block closure that can be passed to oc methods
@convention(c) Closure indicating that this is a c-compatible function pointer that can be passed to c methods

The second type is probably the most common of the three, and is used when you use Aspects in Swift, and when I use Aspects in Swift:

let wrappedBlock: @convention(block) (AspectInfo, Int, String) -> Void = { aspectInfo, id, name in

}
let block: AnyObject = unsafeBitCast(wrappedBlock, to: AnyObject.self)
test.hook(selector: #selector(Test.test(id:name:)), strategy: .before, block: )
Copy the code

unsafeBitCast

We can look at the source builtin.swift

/// - Parameters:
///   - x: The instance to cast to `type`. / / / -type: The type to cast `x` to. `type` and the type of `x` must have the
///     same size of memory representation and compatible memory layout.
/// - Returns: A new instance of type `U`, cast from `x`.
@inlinable // unsafe-performance
@_transparent
public func unsafeBitCast<T, U>(_ x: T, to type: U.Type) -> U {
  _precondition(MemoryLayout<T>.size == MemoryLayout<U>.size,
    "Can't unsafeBitCast between types of different sizes")
  return Builtin.reinterpretCast(x)
}
Copy the code

UnsafeBitCast is a very dangerous operation that forces a pointer to the memory to be bitwise converted to the type of the target with a simple size judgment. Because this conversion is done outside of Swift’s type management, the compiler cannot be sure that the resulting type is actually correct, and you must know exactly what you are doing.


let block: AnyObject = unsafeBitCast(wrappedBlock, to: AnyObject.self)
Copy the code

This code converts the closure of oc’s block to type Anyobject.

Unsafe Swift: Using messing With C UnsafeRawPointer Migration Swift memory assignment Pointer in Swift using Unmanaged in Swift 3.0 call C language API pointer in Swift using depth explore HandyJSON(a) Swift pointer use