Apple hopes to minimize the appearance of pointers in Swift, so pointers are mapped to a generic type in Swift, and are relatively abstract. This has caused difficulties in using pointers in Swift to a certain extent, especially for developers who are not familiar with pointers and do not have much experience in pointer operations (including myself). Using pointers in Swift is indeed a challenge. In this article, I hope to start with the most basic usage and summarize some common ways and scenarios for using pointers in Swift. This article assumes that you at least know what a pointer is. If you are not clear about the concept of pointers themselves, you can take a look at this five-minute C pointer tutorial (or its Chinese version), which should be very helpful. Preliminary In Swift, pointers are represented by a special type, UnsafePointer<T>. Following the consistent immutability principle of Cocoa, UnsafePointer<T> is also immutable. Of course, it also has a mutable variant, UnsafeMutablePointer<T>. Most of the time, pointers in C are introduced into Swift in these two types: const-modified pointers in C correspond to UnsafePointer (the most common should be const char * of C string), and other mutable pointers correspond to UnsafeMutablePointer. In addition, there are UnsafeBufferPointer<T> that represents a set of continuous data pointers in Swift, COpaquePointer that represents an opaque pointer of an incomplete structure, and so on. In addition, you may have noticed that the pointer types that can determine the content are all generic structs. We can use this generic type to constrain the type pointed to by the pointer to provide a certain degree of security. For an UnsafePointer<T> type, we can get its value through the memory property. If the pointer is a mutable UnsafeMutablePointer<T> type, we can also assign it through memory. For example, if we want to write a counter that uses pointers to directly operate memory, we can do this:
This is similar to the use of pointers in C. By adding an & symbol in front of the variable name, we can pass the pointer to this variable to the method that accepts a pointer as a parameter. In the incrementor above, we change the content pointed to by the pointer by directly manipulating the memory attribute. Similar to this approach is the use of Swift's inout keyword. When we pass a variable into a function with an inout parameter, we also use the & symbol to represent the address. The difference is that inside the function body we do not need to deal with pointer types, but can operate directly on the parameter.
Although the meaning of & when passing parameters is the same as in C, that is, the "address of a variable", in Swift we cannot directly obtain an instance of UnsafePointer through this symbol. It should be noted that this is different from C:
Pointer initialization and memory management Although we cannot directly get the address of an existing object in Swift, we can still create a new UnsafeMutablePointer object. Unlike the automatic memory management of other objects in Swift, pointer management requires us to manually apply for and release memory. The memory of an UnsafeMutablePointer has three possible states: The memory has not been allocated, which means it is a null pointer or it has been freed before. Memory is allocated, but the value is not initialized yet Memory is allocated and the value is initialized Only the pointer in the third state can be guaranteed to be used normally. The initialization method (init) of UnsafeMutablePointer completes the work of converting from other types to UnsafeMutablePointer. If we want to create a new pointer, we need to use the class method alloc:. This method accepts a num: Int as a parameter and will apply to the system for num number of memory of the corresponding generic type. The following code applies for a memory of the size of Int and returns a pointer to this memory:
The next thing to do is to initialize the contents of this pointer. We can use the initialize: method to complete the initialization:
After completing the initialization, we can use memory to operate the memory value pointed to by the pointer. After use, we must release the pointer's contents and the pointer itself as soon as possible. Destroy is used in conjunction with initialize: to destroy the object pointed to by the pointer, while dealloc: is used in conjunction with alloc: to release the memory previously allocated. They should both be used in conjunction:
Note that for "ordinary values" such as Int that are mapped to int in C, destroy is not necessary because these values are allocated in the constant segment. However, for objects like classes or structure instances, memory leaks will occur if initialization and destruction are not guaranteed to be paired. So if there is no special consideration, it is a good habit to ensure that initialize: and destroy are paired regardless of what is in the memory. Pointer to array When passing an array as a parameter to a C API in Swift, Swift has already helped us complete the conversion. There is a good example in Apple's official blog:
For general C APIs that accept const arrays, the required type is UnsafePointer, while non-const arrays correspond to UnsafeMutablePointer. When using, for const parameters, we directly pass in Swift arrays (a and b in the above example); and for mutable arrays, add & in front and pass them in (result in the above example). Swift simplifies parameter passing and makes it very convenient to use. However, if we want to use pointers to directly operate arrays like we did with memory before, we need to use a special type: UnsafeMutableBufferPointer. Buffer Pointer is a pointer to a continuous memory segment, usually used to express collection types such as arrays or dictionaries.
Pointer manipulation and conversion withUnsafePointer As we said above, in Swift, we cannot use the & symbol to directly obtain the address for operation like in C. If we want to perform pointer operations on a variable, we can use the auxiliary method withUnsafePointer. This method accepts two parameters, the first is any type of inout, and the second is a closure. Swift will convert the first input into a pointer, and then use this converted Unsafe pointer as a parameter to call the closure. It looks like this:
Here we actually do the same thing as the incrementor at the beginning of the article, the difference is that there is no need to convert the value to a pointer through a method call. The benefit of doing this is obvious for pointer operations that will only be performed once, and it can express the intention of "we just want to do something with this pointer" more clearly. unsafeBitCast unsafeBitCast is a very dangerous operation, which will force a pointer to be converted bitwise to the target type. Because this conversion is performed outside of Swift's type management, the compiler cannot ensure that the resulting type is indeed correct, and you must know exactly what you are doing. For example:
Because NSArray can store any NSObject object, when we use CFArrayGetValueAtIndex to get a value from it, the result will be an UnsafePointer<Void>. Since we know that it stores a String object, we can directly force it to be converted to CFString. A more common use case for unsafeBitCast is to convert between different types of pointers. Because the size of a pointer is fixed, there is no fatal problem in converting the pointer type. This is very common when working with some C APIs. For example, many C APIs require the input to be void *, which corresponds to UnsafePointer<Void> in Swift. We can convert any pointer to UnsafePointer in the following way.
Summarize Swift is designed with safety as an important principle. Although it may be a bit long-winded, I still want to reiterate that direct use and manipulation of pointers in Swift should be used as a last resort, and they can never ensure safety. Migrating from traditional C code and the Objective-C code that works seamlessly with it to Swift is not a small project, and our code base will definitely have some places that collaborate with C from time to time. We can certainly choose to rewrite some old code in Swift, but for parts that are critical to safety or performance, we may have no choice but to continue using C APIs. If we want to continue using those APIs, it will be helpful to understand some basic knowledge of Swift pointer operations and usage. For new code, try to avoid using types starting with Unsafe, which means you can avoid a lot of unnecessary trouble. The biggest benefit Swift brings to developers is that it allows us to use more advanced programming ideas to develop faster and more focused. Only by respecting this idea can we better enjoy the advantages brought by this new language. Obviously, this idea does not include using UnsafePointer everywhere :) |
<<: objc.io#21#Photo framework
>>: Loss of “WeChat” trademark: Why didn’t Tencent take it seriously?
Do you still remember the "burning platform&...
Bidding promotion is charged according to the num...
On the afternoon of August 25, China National Nuc...
Tesla CEO Elon Musk has previously stated that th...
Recently, two artificial intelligence systems hav...
This course is Xie Mengyuan's English for beg...
After the new MacBook Pro was launched, it was pl...
In the busy city life, how long has it been since...
Author: Haiyueming Studio Weather radar and weath...
Tabs are also called tabs (hereinafter referred t...
There have always been comments about the poor an...
Brief introduction of Yang Chunhu's Baoding t...
1. To develop a marketing style for your company,...
India's Ministry of Electronics and Informati...
At present, the new generation of information and...