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:
- Not Allocated: Memory has Not been Allocated which means it is a null pointer or has been previously released
- Allocated but not initialized: Allocated memory has been Allocated but the value has not been initialized
- 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:
- 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.
- UnsafeRawBufferPointer/UnsafeMutableRawBufferPointer instance is raw bytes of the memory area of the view.
- Reading is a kind of from the original buffer from memory type operation, UnsafeMutableRawBufferPointer instance can be written to memory, can’t UnsafeRawBufferPointer instance.
- 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:
- Do not return a pointer from withUnsafeBytes.
- 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