Research on the memory model of Swift object (i.)

Source: Internet
Author: User

Memorylayout Basic Use Method

HandyJSONIs Swift one of the JSON open source libraries that work with data, similar JOSNModel to the ability to directly convert data into JSON class instances for use in code.

Because Swift it is a static language, there is no OC such flexible Runtime mechanism, in order to achieve a similar JSONModel effect, a new way to bypass the HandyJSON dependency on the direct operation of the Runtime instance of the memory of the instance properties to assign value, so as to obtain a fully initialized instance.

This article will briefly introduce the principle of implementation by exploring the mechanism of Swift object memory model HandyJSON .

Memory allocation

MemoryLayoutis Swift3.0 a tool class that is used to calculate the amount of memory the data occupies. The basic usage is as follows:

MemoryLayout<Int>.size   //8

let a: Int = 10
MemoryLayout.size(ofValue: a)   //8
Memorylayout Property Introduction

MemoryLayoutThere are three very useful properties, all of which are Int types:

Alignment & Alignment (OFVALUE:T)

This property is a property that is related to memory alignment. Many computer systems impose restrictions on the legal address of a basic data type, requiring that the address of a data type object must be a multiple of a value K(usually 2, 4, or 8). This alignment restriction simplifies the hardware design that forms the interface between the processor and the memory system. The alignment principle is that the address of any K-byte base object must be a multiple of k.

Memorylayout\<t\>.alignment represents the memory alignment principle for data type T. And under the 64bit system, the maximum memory alignment principle is 8byte.

Size & size (ofvalue:t)

A T data type instance occupies the size of contiguous memory bytes.

Stride & Stride (ofvalue:t)

In an array of type T, the size of contiguous bytes of memory occupied by any one of the elements from the start address to the end address stride .

Note: There are four elements of type T in the array, although each t element has size a size of bytes, but because of the limitations of memory alignment, each t type element actually consumes a byte of memory stride , whereas stride - size a byte is a memory space wasted by each element because of memory alignment.

Memorylayout of basic data types
Value type
Memorylayout<int>.size//8
Memorylayout<int>.alignment//8
Memorylayout<int>.stride//8

Memorylayout<string>.size//24
Memorylayout<string>.alignment//8
Memorylayout<string>.stride//24

Reference type T
Memorylayout<t>.size//8
Memorylayout<t>.alignment//8
Memorylayout<t>.stride//8

Pointer type
Memorylayout<unsafemutablepointer<t>>.size//8
Memorylayout<unsafemutablepointer<t>>.alignment//8
Memorylayout<unsafemutablepointer<t>>.stride//8

Memorylayout<unsafemutablebufferpointer<t>>.size//16
Memorylayout<unsafemutablebufferpointer<t>>.alignment//16
Memorylayout<unsafemutablebufferpointer<t>>.stride//16
Swift pointers commonly used swift pointer types

In this article is mainly related to the use of several pointers, in this simple analogy introduced.

    • Unsafepointer

      unsafePointer<T>Equal to const T * .
      • Unsafemutablepointer

        unsafeMutablePointer<T>Equivalent toT *
      • Unsaferawpointer
        unsafeRawPointerEquivalent toconst void *

      • Unsafemutablerawpointer
        unsafeMutableRawPointerEquivalent tovoid *

Swift gets a pointer to an object
Final Func withunsafemutablepointers<r> (_ Body: (Unsafemutablepointer
Basic data types
var a:t = T () var apointer = a.withunsafemutablepointer{return $}

Gets a pointer to the struct type instance from Handyjson
Func headpointerofstruct (), unsafemutablepointer<int8> {
Return Withunsafemutablepointer (to: &self) {
Return Unsafemutablerawpointer ($). Bindmemory (To:Int8.self, Capacity:memorylayout<self>.stride)}}

Gets a pointer to the class type instance from Handyjson
Func Headpointerofclass (), unsafemutablepointer<int8> {Let opaquepointer = unmanaged.passunretained (self as Anyobject). Toopaque () Let Mutabletypedpointer = Opaquepointer.bindmemory (to:Int8.self, Capacity:memorylayout<sel F>.stride)
Return unsafemutablepointer<int8> (Mutabletypedpointer)}
Struct memory model

In Swift, a struct is a value type, and a struct temporary variable without a reference type is stored on the stack:

struct Point {    var a: Double    var b: Double}MemoryLayout<Point>.size     //16

Memory model

Look at another situation:

struct Point {    var a: Double?    var b: Double}MemoryLayout<Point>.size    //24

As you can see, if the property a becomes an optional type, the entire Point type is incremented by 8 bytes. But in fact, the optional type only adds one byte:

MemoryLayout<Double>.size               //8
MemoryLayout<Optional<Double>>.size     //9

The reason that the property is an optional a value after the Point type adds 8 bytes of storage space, or because of the memory alignment limit of the ghost:

Since the Optional<Double> first 9 bytes are occupied, causing the second lattice to have 7 bytes, and property B as Double type alignment 8, the storage of the B attribute can only start at the 16th byte, which causes the entire Point type of storage to become 24byte, where 7 A byte is wasted.

So, from the above example, we can conclude thatSwift's optional type is a waste of memory space.

Manipulating memory modifying the value of a property of a struct type instance struct Demo

A simple structure is shown below, and we will use this struct to complete an example operation:

Enum Kind {
Case Wolf
Case Fox
Case Dog
Case Sheep}struct Animal {
private var a:int = 1//8 byte var b:string = "Animal"//24 byte var c:kind =. Wolf//1 byte               var d:string? byte var e:int8 = 8//1 byte//returns pointer to Animal instance head func headpointerofstruct (), Unsafemuta blepointer<int8> {
Return Withunsafemutablepointer (to: &self) {
Return Unsafemutablerawpointer ($). Bindmemory (To:Int8.self, Capacity:memorylayout<self>.stride)} func prin TA () {print ("Animal a:\ (A)")}}
Operation

First of all we need to initialize an Animal instance:

let animal = Animal()     // a: 1, b: "animal", c: .wolf, d: nil, e: 8

Get animal the pointer to the point:

let animalPtr: unsafeMutablePointer<Int8> = animal.headPointerOfStruct()

Now in the memory situation:

PS: From the figure can see Animal the type of 8 + + 8 + + size + 1 = 8, alginment for 8 + stride 24 + 8 + 32 = 72.

If we want to modify the property value of an instance through memory, we animal need to get to the memory area where its property value resides, and then modify the value of the memory area to achieve the purpose of modifying animal the property value:

//将之前得到的指向 animal 实例的指针转化为 rawPointer 指针类型,方便我们进行指针偏移操作
let animalRawPtr = unsafeMutableRawPointer(animalPtr)let intValueFromJson = 100

let aPtr = animalRawPtr.advance(by: 0).assumingMemoryBound(to: Int.self)aPtr.pointee          // 1
animal.printA()       //Animal a: 1
aPtr.initialize(to: intValueFromJson)aPtr.pointee          // 100
animal.printA()       //Animal a:100

By doing this, we successfully animal changed the value of a Int type property from 1 to 100, and this property is also a private property.

Code Analysis

First, a pointer animalPtr is a pointer to a type, or a pointer to a type that represents the Int8 byte animal first byte of the memory in which the instance resides. To get the animal properties of an instance a , you need a pointer of a Int type that animalPtr is obviously not compliant as a pointer to a Int8 type.

So, let's first convert the animalptr to a unsafeMutableRawPointer type (equivalent to C the void * type in). Because the property has an a offset of 0 in memory, it is offset by 0 bytes. The method is then used assumingMemoryBound(to: Type) to get a pointer to the same address but the type is the specified type Type (in this case Int ). So, we get a animal pointer to the instance's first address but type type Int .

assumingMemoryBound(to:)Method is described in the document as follows:

Returns A typed pointer to the memory referenced by this pointer, assuming that the memory was already bound to the Specifi Ed Type

The default chunk of memory is already bound to a data type (in this case the green memory area is the Int type, so we can default to this block area as a Int type) and return a pointer to this data type in this block memory area (in this case, we will Int.self pass in as a type parameter, and returns a pointer to the type of the Green memory area Int ).

So, through the assumingMemoryBound(to: Int.self) method we get the a pointer to the type of the property Int aPtr .

In Swift, the pointer has a property called, and we can get the value of the pointer in the pointee memory by this property, similar C to the value in the *Pointer cursor.

Because the animal default value is 1 when the instance is initialized a , the value at this point aPtr.pointee is also 1.

After that, we use the initialize(to:) method to reinitialize the aPtr pointed memory area, which is the green area on the way, and change its value to 100. Thus, the operation to modify the value of the property through memory a is complete.

The idea of modifying the value of the subsequent property is the same, first by the pointer offset to a pointer to animalRawPtr a property start address, and then to the block memory region through the assumingMemoryBound(to:) method of the pointer type conversion, and then the conversion of the good pointer by re-initialization of this block memory area to rewrite the value of the memory area To complete the modification operation.

Class memory model

classis a reference type, the generated instance is distributed over the heap memory area, and the stack (stack) holds only a pointer to an instance in the heap. Because of the dynamic nature of the reference type and the reason for ARC, the class type instance needs to have a separate area to store type information and reference counts.

class Human {    var age: Int?    var name: String?    var nicknames: [String] = [String]()

   //返回指向 Human 实例头部的指针    func headPointerOfClass() -> UnsafeMutablePointer<Int8> {        let opaquePointer = Unmanaged.passUnretained(self as AnyObject).toOpaque()        let mutableTypedPointer = opaquePointer.bindMemory(to: Int8.self, capacity: MemoryLayout<Human>.stride)        
       return UnsafeMutablePointer<Int8>(mutableTypedPointer)    }}MemoryLayout<Human>.size       //8

Human Class Memory Distribution:

The type information area is 4byte on a 32bit machine and is 8 byte on a 64bit machine. The reference count occupies 8 byte. So, on the heap, the address of the Class property starts with the 16th byte.

Manipulating memory modifies the value of a Class type instance property

As with modifying struct the value of a Type property, the only difference is that when you get the first address on the class instance heap, because the Type field and the reference count field exist, you need to offset 16 bytes to reach the memory start address of the first property. The following example shows nicknames how to modify a property:

let human = Human()let arrFormJson = ["goudan","zhaosi", "wangwu"]

//拿到指向 human 堆内存的 void * 指针
let humanRawPtr = unsafeMutableRawPointer(human.headerPointerOfClass())

//nicknames 数组在内存中偏移 64byte 的位置(16 + 16 + 32)
let humanNickNamesPtr =  humanRawPtr.advance(by: 64).assumingMemoryBound(to: Array<String>.self)human.nicknames           //[]

humanNickNamePtr.initialize(arrFormJson)human.nicknames           //["goudan","zhaosi", "wangwu"]
Play a game array properties in Class type

As the Human type of memory shows, human an instance holding an nicknames array actually holds only one Array<String> type of pointer, which is the area in the diagram nicknames . The real array is in another contiguous piece of memory in the heap. Here's how to get the contiguous memory area that really holds the array data.

In C, pointers to arrays actually point to the first element in the array, such as arrPointer a pointer to an array in C, so that we can get to the first element of the array, that *arrPointer is, the arrPointer pointer is pointing to the first element of the array, And the type of the pointer and the element type of the array are the same.

In the same vein, it is also applicable in Swift. In this case, the nicknames memory area contains a pointer to an array of String type, that is, the pointer to the String first element of the type array. So, the type of the pointer should be unsafeMuatblePointer<String> , so we can get pointers to the array in the following ways:

let firstElementPtr = humanRawPtr.advance(by: 64).assumingMemoryBound(to: unsafeMutablePointer<String>.self).pointee

So, in theory, I can use firstElementPtr the pointee property to get the first element of the array "Goudan", look at the code:

After running on the Playground not as we expected to show the "Goudan", is our theory wrong, this unscientific! In the spirit of breaking the sand pot to ask the end, the problem can not solve the spirit of sleep, and really found out a little rule:

By directly getting to the address and comparison of the original array arrFormJson firstElementPtr we find that the point that we get to the firstElementPtr address is always arrFromJson 32byte lower than the real address of the original array (after the multi-round test of the blogger, no matter what type of array, the two ways to obtain the address is always poor 32 bytes).

As you can see, a 0x6080000CE870 0x6080000CE850 0x20 byte is 32 bytes in decimal.

So, by the way we get to the firstElementPtr real address of the pointer pointing to this,

PS: Although the reason to understand, but the array at the beginning of the 32 bytes Bo master So far did not understand what is to do, have the understanding of children's shoes can inform bloggers.

So what we need to do is to firstElementPtr offset 32 bytes and then take the value to get the values in the array.

The role of Class type trickery type

First assume the following code:

class Drawable {    func draw() {    }}class Point: Drawable {    var x: Double = 1    var y: Double = 1    func draw() {        print("Point")    }}class Line: Drawable {    var x1: Double = 1    var y1: Double = 1    var x2: Double = 2    var y2: Double = 2     func draw() {        print("Line")    }}var arr: [Drawable] = [Point(), Line()]for d in arr {    d.draw()     //问题来了,Swift 是如何判断该调用哪一个方法的呢?}

In Swift, class-type method distributions are distributed dynamically through v-table. Swift generates a type information for each kind of type and places it in the static memory area, and the type pointer of each instance of the class type points to the type information of this kind in the static memory region. When an instance of a class invokes a method, it first finds the type information of that kind through the instance's type pointer, and then obtains the address of the method through the v-table in the message, and jumps to the corresponding method's implementation address to execute the method.

What happens when you replace the Type?

From the above analysis, we know that a method distribution of a class type is determined by the type pointer of the head, and if we point to the type pointer of a class instance to another type will there be anything interesting to happen? haha ~ together to try ~

Class Wolf {var name:string = "Wolf" Func Soul () {print ("My soul Is Wolf")}

Func Headpointerofclass (), unsafemutablepointer<int8> {Let opaquepointer = unmanaged.passunretained (sel F as Anyobject). Toopaque () Let Mutabletypedpointer = Opaquepointer.bindmemory (to:Int8.self, Capacity:memorylayout <wolf>.stride)
Return unsafemutablepointer<int8> (Mutabletypedpointer)}}

Class Fox {var name:string = "Fox" Func Soul () {print ("My soul Is Fox")}
Func Headpointerofclass (), unsafemutablepointer<int8> {Let opaquepointer = unmanaged.passunretained (sel F as Anyobject). Toopaque () Let Mutabletypedpointer = Opaquepointer.bindmemory (to:Int8.self, Capacity:memorylayout <fox>.stride)
Return unsafemutablepointer<int8> (Mutabletypedpointer)}}

You can see Wolf Fox that the memory structure of the two classes is identical except for the type of the above and two classes. Then we can use these two classes to do the testing:

let wolf = Wolf()var wolfPtr = UnsafeMutableRawPointer(wolf.headPointerOfClass())let fox = Fox()var foxPtr = UnsafeMutableRawPointer(fox.headPointerOfClass())foxPtr.advanced(by: 0).bindMemory(to: UnsafeMutablePointer<Wolf.Type>.self, capacity: 1).initialize(to: wolfPtr.advanced(by: 0).assumingMemoryBound(to: UnsafeMutablePointer<Wolf.Type>.self).pointee)print(type(of: fox))        //Wolf
fox.name                    //"fox"
fox.soul()                  //my soul is wolf

Magical thing happened, a Fox type instance unexpectedly called Wolf type method, haha ~ If there is any fun play, we can continue to explore ~

Reference articles

Swift Advanced memory model and method scheduling
Pointers in Swift use the
Using the array from Swift to see OBJECTIVE-C

Https://mp.weixin.qq.com/s/zIkB9KnAt1YPWGOOwyqY3Q


Research on the memory model of Swift object (i.)

Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.