日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

Swift中的类和结构体(2)

發布時間:2023/12/8 编程问答 45 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Swift中的类和结构体(2) 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

Swift中的類和結構體(2)

  • 異變方法
  • 方法調度
  • 影響函數派發方式

異變方法

在Swift中,值類型屬性不能被自身的實例方法修改,編譯器不會通過編譯,報錯Left side of mutating operator isn't mutable: 'self' is immutable,自身是不能修改自身的。

當加上mutating關鍵字后就可以通過編譯

struct LLPerson {var x = 0.0var y = 0.0//方法默認會有self參數,類似于OC中方法會有self和_cmd一樣func test() {let temp = xprint(temp)}//默認會有參數selfmutating func moveBy(x deltaX: Double, y deltaY: Double) {x += deltaXy += deltaY} }

可以通過sil來進行分析:

// LLPerson相當于self // LLPerson.test() sil hidden @$s4main8LLPersonV4testyyF : $@convention(method) (LLPerson) -> () { // %0 "self" // users: %2, %1 bb0(%0 : $LLPerson): //$ LLPerson, let, name "self" 是直接傳入的值類型selfdebug_value %0 : $LLPerson, let, name "self", argno 1 // id: %1

在test方法中,相當于傳入了值類型self,而在moveBy方法中

// 傳入了@inout關鍵字的LLPerson,及@inout關鍵字的self // LLPerson.moveBy(x:y:) sil hidden @$s4main8LLPersonV6moveBy1x1yySd_SdtF : $@convention(method) (Double, Double, @inout LLPerson) -> () { // %0 "deltaX" // users: %10, %3 // %1 "deltaY" // users: %20, %4 // %2 "self" // users: %16, %6, %5 bb0(%0 : $Double, %1 : $Double, %2 : $*LLPerson):debug_value %0 : $Double, let, name "deltaX", argno 1 // id: %3debug_value %1 : $Double, let, name "deltaY", argno 2 // id: %4 // 從這里可以看出,是*LLPerson類型的self,即LLPerson的指針debug_value_addr %2 : $*LLPerson, var, name "self", argno 3 // id: %5

在moveBy方法中傳入了*LLPerson,即指針self。
SIL 文檔的解釋為:

An @inout parameter is indirect. The address must be of an initialized object.(當前參數 類型是間接的,傳遞的是已經初始化過的地址)

異變方法的本質:
對于變異方法,傳入的 self 被標記為 inout 參數。無論在 mutating 方法內部發生什么,都會影響外部依賴類型的一切。
inout輸入輸出參數:
如果我們想函數能夠修改一個形式參數的值,而且希望這些改變在函數結束之后依然生效,那么就需要將形式參數定義為輸入輸出形式參數 。在形式參數定義開始的時候在前邊添加一個 inout關鍵字可以定義一個輸入輸出形式參數。

var person = LLPerson() // 相當于傳入了&self,即一個地址 person.moveBy(x: 30.0, y: 20)

我們可以在函數中看這個inout:

var age = 10 //添加了inout func modifyage(_ age: inout Int) {// 如果使用temp,只是一個普通的賦值,不會改變外部agevar temp = agetemp += 1 } // 必須使用&age modifyage(&age) print(age)


當我們這樣時:

var age = 10 // 相當去傳入了&self func modifyage(_ age: inout Int) {age += 1 }modifyage(&age) print(age)


相當于我們在寫C函數時:

void test(int a) {a += 1; }int main(){int a = 10; //定義值類型int test(a); //傳入值類型printf("%d\n", a);return 0; }


我們修改不了a的值。
但當我們這樣時:

void test(int *a) {*a += 1; }int main(){int a = 10;test(&a); // 我們傳入一個地址,即&aprintf("%d\n", a);return 0; }


我們是可以修改a的值的。

方法調度

在OC中,方法調度是objc_mgsend的方式。而在Swift中,我們可以通過代碼和匯編來探究。

class LLTeacher {func teach() {print("teach")}func teach1() {print("teach1")}func teach2() {print("teach2")} }let teacher = LLTeacher() teacher.teach() teacher.teach1() teacher.teach2()

我們通過斷點調試,獲取相關匯編代碼

0x1026d7888 <+116>: bl 0x1000077c4 ; _23456.LLTeacher.__allocating_init() -> _23456.LLTeacher at ViewController.swift:10 // __allocating_init返回LLTeacher 返回值存儲到x0寄存器中 x0的第一個8字節存儲的是metadata 0x1026d788c <+120>: str x0, [sp, #0x20] // 將x0的值保存到棧中 0x1026d7890 <+124>: ldr x8, [x0] // 將x0的地址給x8 0x1026d7894 <+128>: ldr x8, [x8, #0x50] // x8加上0x50個字節的地址給x8 即metadate + 0x50就是 teach 這時候拿到了teach 0x1026d7898 <+132>: mov x20, x0 0x1026d789c <+136>: str x0, [sp, #0x8] 0x1026d78a0 <+140>: blr x8 // 執行teach 0x1026d78a4 <+144>: ldr x8, [sp, #0x8] 0x1026d78a8 <+148>: ldr x9, [x8] 0x1026d78ac <+152>: ldr x9, [x9, #0x58] // 0x50 0x58 相差8字節,正好是指針8字節,所以這些函數時連續的內存空間 0x1026d78b0 <+156>: mov x20, x8 0x1026d78b4 <+160>: blr x9 // 執行teach1 0x1026d78b8 <+164>: ldr x8, [sp, #0x8] 0x1026d78bc <+168>: ldr x9, [x8] 0x1026d78c0 <+172>: ldr x9, [x9, #0x60] 0x1026d78c4 <+176>: mov x20, x8 0x1026d78c8 <+180>: blr x9 // 執行teach2 0x1026d78cc <+184>: ldr x0, [sp, #0x8] 0x1026d78d0 <+188>: bl 0x10000992c ; symbol stub for: swift_release

寄存器x0讀取出來,正好是metadata。

通過讀取x8寄存器的值,可以得出,位置為teach方法

匯編相關資料可以參考:常用指令

所以我們可以猜測:teach函數的調用過程是找到 Metadata ,確定函數地址(metadata + 偏移量),然后執行函數,是基于函數表的調度。
我們還可以通過sil查看一些相關內容:

class LLTeacher {func teach()func teach1()func teach2()@objc deinitinit() } sil_vtable LLTeacher { // 一種vtable的形式存儲#LLTeacher.teach: (LLTeacher) -> () -> () : @$s14ViewController9LLTeacherC5teachyyF // LLTeacher.teach()#LLTeacher.teach1: (LLTeacher) -> () -> () : @$s14ViewController9LLTeacherC6teach1yyF // LLTeacher.teach1()#LLTeacher.teach2: (LLTeacher) -> () -> () : @$s14ViewController9LLTeacherC6teach2yyF // LLTeacher.teach2()#LLTeacher.init!allocator: (LLTeacher.Type) -> () -> LLTeacher : @$s14ViewController9LLTeacherCACycfC // LLTeacher.__allocating_init()#LLTeacher.deinit!deallocator: @$s14ViewController9LLTeacherCfD // LLTeacher.__deallocating_deinit }

一種vtable的形式存儲。
同時,我們可以從源碼來看一下相關的結構。上一章節我們獲取到Metdata的一個結構:

struct Metadata { //metadata結構var kind: Intvar superClass: Any.Typevar cacheData: (Int, Int)var data: Intvar classFlags: Int32var instanceAddressPoint: UInt32var instanceSize: UInt32var instanceAlignmentMask: UInt16var reserved: UInt16var classSize: UInt32var classAddressPoint: UInt32var typeDescriptor: UnsafeMutableRawPointervar iVarDestroyer: UnsafeRawPointer }

從這里我們需要關注 typeDescriptor,不管是Class,Struct,Enum都有自己 的 Descriptor,就是對類的一個詳細描述。我們通過對源碼的分析,可以得出typeDescriptor的結構

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 size: UInt32 V-Table //V-Table位置 }

下面我們通過mach-o文件來查看V-Table的位置。

我們通過MachOView打開mach-o文件:

下面我們通過內存地址,來查找V-Table:
首先,我們拿到存放Swift類的前四個字節地址0xFFFFFBA8 + 0x0000BBCC

0xFFFFFBA8 + 0x0000BBCC ------------------0x10000B774 //通過這個虛擬內存地址就可以查找到Descriptor的位置

接著我們通過0x10000B774減去虛擬內存基地址0x100000000拿到Descriptor的位置

0x10000B774 + 0x100000000 ------------------0xB774 //Descriptor的位置

下面,我們通過MachOView查找到0xB774(Descriptor)的位置:

在接下來,我們通過Descriptor的結構,去標記V-Table的位置:

這時候我們獲取了V-Table的位置,即teach的地址0x0000B7A8。

我們通過0x0000B7A8 + ASLR(隨機偏移地址,即程序運行的基地址)。

通過image list獲取程序運行的基地址是0x0000000104790000:

0x0000B7A8 + ASLR,我們可以獲取teach函數在內存中的地址:

0x0000B7A8 + 0x0000000104790000 ----------------------0x10479B7A8 //teach函數在內存中的地址

我們通過源碼,可以獲取函數在內存中的結構為:

struct TargetMethodDescriptor {MethodDescriptor Flags; //4字節//存儲的offsetTargetRelativeDriectPointer<Runtime, void> Impl; }

接下來,我們通過0x10479B7A8(TargetMethodDescriptor的地址) + 0x4( Flags) + 0xFFFFC234(Impl offset),就可以得到teach的函數地址。

0x10479B7A80x4 + 0xFFFFC234 -----------------------0x2047979E0 - 0x100000000 //還需要減去虛擬內存基地址0x100000000 -----------------------0x1047979E0 //teach的函數地址

我們通過上面的匯編,可以讀取x8寄存器里面的內容,就是我們計算的地址:

Mahco的一些知識:
Mahco: Mach-O 其實是Mach Object文件格式的縮寫,是 mac 以及 iOS上可執行文件的格 式, 類似于 windows 上的 PE 格式 (Portable Executable ), linux 上的 elf 格式 (Executable and Linking Format) 。常見的 .o,.a .dylib Framework,dyld .dsym。
Macho文件格式:

首先是文件頭,表明該文件是 Mach-O 格式,指定目標架構,還有一些其他的文件屬性信 息,文件頭信息影響后續的文件結構安排
Load commands是一張包含很多內容的表。內容包括區域的位置、符號表、動態符號表等。

Data 區主要就是負責代碼和數據記錄的。Mach-O 是以 Segment 這種結構來組織數據 的,一個 Segment 可以包含 0 個或多個 Section。根據 Segment 是映射的哪一個 Load Command,Segment 中 section 就可以被解讀為是是代碼,常量或者一些其他的數據類 型。在裝載在內存中時,也是根據 Segment 做內存映射的。

當我們將class改為struct時

struct LLTeacher {func teach() {print("teach")}func teach1() {print("teach1")}func teach2() {print("teach2")} }let teacher = LLTeacher() teacher.teach() teacher.teach1() teacher.teach2()

通過匯編我們發現,結構體的函數調用是直接靜態派發,即編譯的時候就已經確定了地址。

當我們使用extension時,調用方式也是使用的靜態派發,包括類的extension也是如此。

extension LLPerson {func teach3() {print("teach3")} }

這樣做的好處是,不需要再V-Table中插入extension中的方法,減少消耗。

最后總結方法調度方式:

類型調度方式extension
值類型靜態派發靜態派發
函數表派發靜態派發
NSObject子類函數表派發靜態派發

影響函數派發方式

final:添加了 final 關鍵字的函數無法被重寫,使用靜態派發,不會在vtable 中出現,且對 objc 運行時不可見。

final func teach() {print("teach") }

dynamic:函數均可添加 dynamic 關鍵字,為非objc類和值類型的函數賦予動態性,但派發方式還是函數表派發。配合@dynamicReplacement(for:)使用

@objc:該關鍵字可以將Swift函數暴露給Objc運行時,依舊是函數表派發。

@objc + dynamic:消息派發的方式

class LLPerson: NSObject {@objc dynamic func teach() {print("teach")} }


會暴露函數給OC使用,可以使用methed-swizzing等

SWIFT_CLASS("_TtC9_234567898LLPerson") @interface LLPerson : NSObject - (void)teach; - (nonnull instancetype)init OBJC_DESIGNATED_INITIALIZER; @end

總結

以上是生活随笔為你收集整理的Swift中的类和结构体(2)的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。