Object-C與Swift的RunTime運行機制對比

【注】 文中關於Swift部分轉載自歐陽大哥2013的Swift5.0的Runtime機制淺析,爲了方便對比學習,我添加了OC RunTime的運行機制。html

Objective-C語言是一門以C語言爲基礎的面向對象編程語言,其提供的運行時(Runtime)機制使得它也能夠被認爲是一種動態語言。運行時的特徵之一就是對象方法的調用是在程序運行時才被肯定和執行的。系統提供的開放接口使得咱們能夠在程序運行的時候執行方法替換以便實現一些諸如系統監控、對象行爲改變、Hook等等的操做處理。然而這種開放性也存在着安全的隱患,咱們能夠藉助Runtime在AOP層面上作一些額外的操做,而這些額外的操做由於沒法進行管控, 因此有可能會輸出未知的結果。c++

多是蘋果意識到了這個問題,因此在推出的Swift語言中Runtime的能力獲得了限制,甚至能夠說是取消了這個能力,這就使得Swift成爲了一門靜態語言。Swift語言中對象的方法調用機制和OC語言徹底不一樣,Swift語言的對象方法調用基本上是在編譯連接時刻就被肯定的,能夠看作是一種硬編碼形式的調用實現。算法

Swfit中的對象方法調用機制加快了程序的運行速度,同時減小了程序包體積的大小。可是從另一個層面來看當編譯連接優化功能開啓時反而又會出現包體積增大的狀況。Swift在編譯連接期間採用的是空間換時間的優化策略,是以提升運行速度爲主要優化考慮點。編程

我將從下面4點進行對比分析swift

  • 一、類中定義的常規方法
  • 二、繼承父類方法調用
  • 三、extension中定義的方法
  • 四、成員變量的訪問

一、類中定義的常規方法

OC普通方法的調用

一個Class類包含如下幾種元素數組

  • 一、isa指針,存儲着Class、 Meta-Class 對象的內存地址
  • 二、superClass指向父類的指針
  • 三、cache方法緩存
  • 四、bits具體類的信息

bits & FAST_DATA_MASK 指向一個新的結構體Class_rw_t,裏面包含着methods方法列表properties屬性列表protocols協議列表class_ro_t類的初始化信息等一些類信息緩存

Class_rw_t裏面的methods方法列表properties屬性列表都是二維數組,是可讀可寫的,包含類的初始內容分類的內容安全

class_ro_t裏面的baseMethodList,baseProtocols,Ivars,baseProperties是一維數組,是只讀的,包含類的初始化內容bash

methods方法列表是存儲的一個個的method_t結構體。method_t是對方法的封裝app

struct method_t{
	SEL name;//函數名
	const char *types;//編碼(返回值類型,參數類型)
	IMP imp;//指向函數的指針(函數地址)
}
複製代碼

IMP表明函數的具體實現

typedef id _Nullable (*IMP)(id _Nonnull, SEL _Nonnull, ...); 
複製代碼

第一個參數是指向self的指針(若是是實例方法,則是類實例的內存地址;若是是類方法,則是指向元類的指針),第二個參數是方法選擇器(selector)

SEL

SEL表明方法名,通常叫作選擇器,底層結構跟char *相似

  • 能夠經過@selector()sel_registerName()得到
  • 能夠經過sel_getName()NSStringFromSelector()轉成字符串
  • 不一樣類中相同名字的方法,所對應的方法的選擇器是相同的
  • 具體實現typedef struct objc_selector *SEL

types

types包含了函數返回值,參數編碼的字符串

結構爲:返回值 參數1 參數2...參數N

iOS中提供了一個叫作@encode的指令,能夠將具體的類型表示成字符串編碼

例如

// "i24@0:8i16f20"
// 0id 8SEL 16int 20float  == 24
- (int)test:(int)age height:(float)height
複製代碼

每個方法都有兩個默認參數self_msg 咱們能夠查到id類型爲@SEL類型爲:

  • 一、第一個參數i返回值
  • 二、第二個參數@id 類型的self
  • 三、第三個參數:SEL 類型的_msg
  • 四、第四個參數iInt age
  • 五、第五個參數ffloat height

其中加載的數字實際上是跟所佔字節有關

  • 一、24 總共佔有多少字節
  • 二、@0id 類型的self的起始位置爲0
  • 三、:8 是由於id 類型的self佔字節爲8,因此SEL 類型的_msg`的起始位置爲8

Class內部結構中有一個方法緩存cache_t,用散列表(哈希表)來緩存曾經調用過的方法,能夠提升方法的查找速度。

cache_t結構體裏面有三個元素

  • buckets 散列表,是一個數組,數組裏面的每個元素就是一個bucket_t,bucket_t裏面存放兩個

    • _key SEL做爲key
    • _imp 函數的內存地址
  • _mask 散列表的長度

  • _occupied已經緩存的方法數量

爲何會用到方法緩存

這張圖片是咱們方法產找路徑,若是咱們的一個類有多個父類,須要調用父類方法,他的查找路徑爲

  • 一、先遍歷本身全部的方法
  • 二、若是在本身類中找不到方法,則遍歷父類全部方法,沒有查找到調用方法以前,一直重複該動做 若是每一次方法調用都是走這樣的步驟,對於系統級方法來講,其實仍是比較消耗資源的,爲了應對這個狀況。出現了方法緩存,調用過的方法,都放在緩存列表中,下次查找方法的時候,如今緩存中查找,若是緩存中查找不到,而後在執行上面的方法查找流程。

散列表結構

散列表的結構大概就像上面那樣,數組的下標是經過@selector(方法名)&_mask來求得,具體每個數組的元素是一個結構體,裏面包含兩個元素_imp@selector(方法名)做爲的key

一個值與&上一個_mask,得出的結果必定小於等於_mask值,而_mask值爲數組長度-1,因此任什麼時候候,也不會越界。

其實這就是散列表的算法,也有一些其餘的算法,取餘,一個值取餘&的效果是相同的。

Swift普通方法的調用

Swift爲每一個類都創建了一個被稱之爲虛表的數組結構,這個數組會保存着類中全部定義的常規成員方法函數的地址。每一個Swift類對象實例的內存佈局中的第一個數據成員和OC對象類似,保存有一個相似isa的數據成員。isa中保存着Swift類的描述信息。對於Swift類的類描述結構蘋果並未公開(也許有我並不知道),類的虛函數表保存在類描述結構的第0x50個字節的偏移處,每一個虛表條目中保存着一個常規方法的函數地址指針。每個對象方法調用的源代碼在編譯時就會轉化爲從虛表中取對應偏移位置的函數地址來實現間接的函數調用。下面是對於常規方法的調用Swift語言源代碼和C語言僞代碼實現:

////////Swift源代碼

//基類定義
class CA {
 open func foo1(_ a:Int){}
 open func foo1(_ a:Int, _ b:Int){}
 open func foo2(){}
}

//擴展
extension CA{
 open func extfoo(){} 
}

//派生類定義
class CB:CA{
 open func foo3(){}
 override open func foo1(_ a:Int){}
}

func testfunc(_ obj:CA){
 obj.foo1(10)
}

func main() {
 let objA = A()
 objA.foo1(10)
 objA.foo1(10,20)
 objA.foo2()
 objA.extfoo()

 let objB = B()
 objB.foo1(10)
 objB.foo1(10,20)
 objB.foo2()
 objB.foo3()
 objB.extfoo()

 testfunc(objA)
 testfunc(objB)
}

複製代碼
////////C僞代碼

//...........................................運行時定義部分

//Swift類描述。
struct swift_class {
    ...   //其餘的屬性,由於這裏不關心就不列出了
    //虛函數表恰好在結構體的第0x50的偏移位置。
    IMP vtable[0];
};


//...........................................源代碼中類的定義和方法的定義和實現部分


//基類定義
struct CA {
      struct swift_class *isa;
};

//派生類定義
struct CB {
   struct swift_class *isa;
};

//基類CA的方法函數的實現,這裏對全部方法名都進行修飾命名
void _$s3XXX2CAC4foo1yySiF(int a){}   //CA類中的foo1
void _$s3XXX2CAC4foo1yySi_SitF(int a, int b){} //CA類中的兩個參數的foo1
void _$s3XXX2CAC4foo2yyF(){}   //CA類中的foo2
void _$s3XXX2CAC6extfooyyF(){} //CA類中的extfoo函數  

//派生類CB的方法函數的實現。
void _$s3XXX2CBC4foo1yySiF(int a){}   //CB類中的foo1,重寫了基類的方法,可是名字不同了。
void _$s3XXX2CBC4foo3yyF(){}             //CB類中的foo3

 //構造基類的描述信息以及虛函數表
struct swift_class classCA;
classCA.vtable[3] = {&_$s3XXX2CAC4foo1yySiF, &_$s3XXX2CAC4foo1yySi_SitF, &_$s3XXX2CAC4foo2yyF};

//構造派生類的描述信息以及虛函數表,注意這裏虛函數表會將基類的函數也添加進來並且排列在前面。
struct swift_class classCB;
classCB.vtable[4] = {&_$s3XXX2CBC4foo1yySiF, &_$s3XXX2CAC4foo1yySi_SitF, &_$s3XXX2CAC4foo2yyF, &_$s3XXX2CBC4foo3yyF};

void testfunc(A *obj){
   obj->isa->vtable[0](10);   //間接調用實現多態的能力。
}


//...........................................源代碼中程序運行的部分

void main(){
   CA *objA = CA.__allocating_init(classCA);
   objA->isa = &classCA;
   asm("mov x20, objA")
   objA->isa->vtable[0](10);
   objA->isa->vtable[1](10,20);
   objA->isa->vtable[2]();
   _$s3XXX2CAC6extfooyyF()

  CB *objB = CB.__allocating_init(classCB);
  objB->isa = &classCB;
  asm("mov x20, objB");
  objB->isa->vtable[0](10);
  objB->isa->vtable[1](10,20);
  objB->isa->vtable[2]();
  objB->isa->vtable[3]();
   _$s3XXX2CAC6extfooyyF();

  testfunc(objA);
  testfunc(objB);

}

複製代碼

從上面的代碼中能夠看出一些特色:

  • 一、Swift類的常規方法中不會再有兩個隱藏的參數了,而是和字面定義保持一致。那麼問題就來了,方法調用時對象如何被引用和傳遞呢?在其餘語言中通常狀況下對象老是會做爲方法的第一個參數,在編譯階段生成的機器碼中,將對象存放在x0這個寄存器中(本文以arm64體系結構爲例)。而Swift則不一樣,對象再也不做爲第一個參數來進行傳遞了,而是在編譯階段生成的機器碼中,將對象存放在x20這個寄存器中(本文以arm64體系結構爲例)。這樣設計的一個目的使得代碼更加安全。

  • 二、每個方法調用都是經過讀取方法在虛表中的索引獲取到了方法函數的真實地址,而後再執行間接調用。在這個過程虛表索引的值是在編譯時就肯定了,所以再也不須要經過方法名來在運行時動態的去查找真實的地址來實現函數調用了。雖然索引的位置在編譯時肯定的,可是基類和派生類虛表中相同索引處的函數的地址確能夠不一致,當派生類重寫了父類的某個方法時,由於會分別生成兩個類的虛表,在相同索引位置保存不一樣的函數地址來實現多態的能力。

  • 三、每一個方法函數名字都和源代碼中不同了,緣由在於在編譯連接是系統對全部的方法名稱進行了重命名處理,這個處理稱爲命名修飾。之因此這樣作是爲了解決方法重載和運算符重載的問題。由於源代碼中重載的方法函數名稱都同樣只是參數和返回類型不同,所以沒法簡單的經過名字進行區分,而只能對名字進行修飾重命名。另一個緣由是Swift還提供了命名空間的概念,也就是使得能夠支持不一樣模塊之間是能夠存在相同名稱的方法或者函數。由於整個重命名中是會帶上模塊名稱的。下面就是Swift中對類的對象方法的重命名修飾規則: _$s<模塊名長度><模塊名><類名長度><類名>C<方法名長度><方法名>yy<參數類型1>_<參數類型2>_<參數類型N>F

就好比上面的CA類中的foo1兩個同名函數在編譯連接時刻就會被分別重命名爲:

//這裏面的XXX就是你工程模塊的名稱。
void _$s3XXX2CAC4foo1yySiF(int a){}   //CA類中的foo1
void _$s3XXX2CAC4foo1yySi_SitF(int a, int b){} //CA類中的兩個參數的foo1
複製代碼

下面這張圖就清晰的描述了Swift類的對象方法調用以及類描述信息。

二、繼承父類方法調用

OC繼承父類方法調用

OC對象主要能夠分爲3種

  • 一、instance對象(實例對象):instance實例對象就是經過alloc出來的對象,每次調用alloc都會產生新的instance對象
  • 二、class對象(類對象):每一個類的內存中有且只有一個類對象
  • 三、meta-class對象(元類對象):每一個類的內存中有且只有一個元類對象 實例對象的存儲信息
  • isa指針
  • 其餘成員變量

類對象的存儲信息

  • isa指針
  • superClass指針
  • 類的屬性信息(@property),類的對象方法信息(method),類的協議信息(protocol),類的成員變量信息(ivar)

元類的存儲信息

  • isa指針
  • superClass指針
  • 類的屬性信息(@property),類的對象方法信息(method),類的協議信息(protocol),類的成員變量信息(ivar)

元類和類的存儲結構是同樣的,可是用途不同

  • instance的isa指向class,當調用對象方法時,經過instance的isa找到class,最後找到對象方法的實現進行調用
  • class的isa指向meta-class,當調用類方法時,經過class的isa找到meta-class,最後找到類方法

總覽圖

  • 一、instance的isa指向class
  • 二、class的isa指向meta-class
  • 三、meta-class的isa指向基類的meta-class
  • 四、class的superclass指向父類的class,若是沒有父類,superclass指向nil
  • 五、meta-class的superclass指向父類的meta-class,基類的meta-class的superclass指向基類的class
  • 六、instance的調用軌跡:isa找class,方法不存在,就經過superclass找父類
  • 七、class調用類方法的軌跡:isa找到meta-class,方法不存在,就經過superclass找父類

Swift繼承OC類的派生類而且重寫了基類的方法

若是在Swift中的使用了OC類,好比還在使用的UIViewControllerUIView等等。而且還重寫了基類的方法,好比必定會重寫UIViewControllerviewDidLoad方法。對於這些類的重寫的方法定義信息仍是會保存在類的Class結構體中,而在調用上仍是採用OC語言的Runtime機制來實現,即經過objc_msgSend來調用。而若是在OC派生類中定義了一個新的方法的話則實現和調用機制就不會再採用OC的Runtime機制來完成了,好比說在UIView的派生類中定義了一個新方法foo,那麼這個新方法的調用和實現將與OC的Runtime機制沒有任何關係了!

////////Swift源代碼

//類定義
class MyUIView:UIView {
    open func foo(){}   //常規方法
    override func layoutSubviews() {}  //重寫OC方法
}

func main(){
  let obj = MyUIView()
  obj.layoutSubviews()   //調用OC類重寫的方法
  obj.foo()   //調用常規的方法。
}
複製代碼
////////C僞代碼

//...........................................運行時定義部分

//OC類的方法結構體
struct method_t {
   SEL name;
   IMP imp;
};

//Swift類描述
struct swift_class {
   ...   //其餘的屬性,由於這裏不關心就不列出了。
   struct method_t  methods[1];
   ...   //其餘的屬性,由於這裏不關心就不列出了。
   //虛函數表恰好在結構體的第0x50的偏移位置。
   IMP vtable[1];
};


//...........................................源代碼中類的定義和方法的定義和實現部分

//類定義
struct MyUIView {
     struct swift_class *isa;
}

//類的方法函數的實現
void layoutSubviews(id self, SEL _cmd){}
void foo(){}  //Swift類的常規方法中和源代碼的參數保持一致。

//類的描述信息構建,這些都是在編譯代碼時就明確了而且保存在數據段中。
struct swift_class classMyUIView;
classMyUIView.methods[0] = {"layoutSubviews", &layoutSubviews};
classMyUIView.vtable[0] = {&foo};


//...........................................源代碼中程序運行的部分

void main(){
 MyUIView *obj = MyUIView.__allocating_init(classMyUIView);
 obj->isa = &classMyUIView;
 //OC類重寫的方法layoutSubviews調用仍是用objc_msgSend來實現
 objc_msgSend(obj, @selector(layoutSubviews);
 //Swift方法調用時對象參數被放到x20寄存器中
 asm("mov x20, obj");
 //Swift的方法foo調用採用間接調用實現
 obj->isa->vtable[0]();
}
複製代碼

三、extension中定義的方法

OC分類的原理

對於分類的做用恐怕你們都是知道的吧,咱們研究一下分類的實現原理。

首先建立一個person類,而後在建立person類的兩個分類Person+eat&Person+Run。 研究原理咱們的思路就是

  • 一、生成c++文件,查看c++文件中的實現
  • 二、若是c++文件中實現介紹的不太具體就去查看源碼實現

咱們使用xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc Person+eat.m來生成c++代碼

咱們能夠找到分類都包含了哪些東西

struct _category_t {
	const char *name;
	struct _class_t *cls;
	const struct _method_list_t *instance_methods;
	const struct _method_list_t *class_methods;
	const struct _protocol_list_t *protocols;
	const struct _prop_list_t *properties;
};
複製代碼

咱們發現裏面並無對方法屬性協議等等的具體實現過程,那麼咱們在去源碼中查看一下相關實現過程。

源碼解讀順序

  • 一、objc-os.mm(runtime初始化的代碼)
    • _objc_init
    • map_images
    • map_images_nolock
  • 二、objc-runtime-new.mm
    • _read_images
    • remethodizeClass
    • attachCategories
    • attachLists
    • realloc、memmove、 memcpy

咱們按照源碼查找一路找到attachCategories方法,咱們發現這個方法就是對分類的實現。裏面第一句解釋Attach method lists and properties and protocols from categories to a class.將方法列表、屬性和協議從類別附加到類中。

static void 
attachCategories(Class cls, category_list *cats, bool flush_caches)
{
	if (!cats) return;
	if (PrintReplacedMethods) printReplacements(cls, cats);
	bool isMeta = cls->isMetaClass();
	//方法數組,這是一個二維數組
	/*
	[
	[method_t,method_t],
	[method_t,method_t]
	]
	*/
	method_list_t **mlists = (method_list_t **)
	malloc(cats->count * sizeof(*mlists));
	//屬性數組,這是一個二維數組
	property_list_t **proplists = (property_list_t **)
	malloc(cats->count * sizeof(*proplists));
	//協議數組,這是一個二維數組
	protocol_list_t **protolists = (protocol_list_t **)
	malloc(cats->count * sizeof(*protolists));

	int mcount = 0;
	int propcount = 0;
	int protocount = 0;
	int i = cats->count;
	bool fromBundle = NO;
	while (i--) {
    	//取出某個分類
    	auto& entry = cats->list[i];
    	//取出分類裏面的方法列表
    	method_list_t *mlist = entry.cat->methodsForMeta(isMeta);
    	if (mlist) {
    		mlists[mcount++] = mlist;
    		fromBundle |= entry.hi->isBundle();
    	}
    
    	property_list_t *proplist = 
    	entry.cat->propertiesForMeta(isMeta, entry.hi);
    	if (proplist) {
    		proplists[propcount++] = proplist;
    	}
    
    	protocol_list_t *protolist = entry.cat->protocols;
    	if (protolist) {
    		protolists[protocount++] = protolist;
    	}
	}
	//獲得對象裏面的數據
	auto rw = cls->data();

	prepareMethodLists(cls, mlists, mcount, NO, fromBundle);
	//將全部分類的對象方法,附加到類對象的方法列表中
	rw->methods.attachLists(mlists, mcount);
	free(mlists);
	if (flush_caches  &&  mcount > 0) flushCaches(cls);
	//將全部分類的屬性,附加到類對象的屬性列表中
	rw->properties.attachLists(proplists, propcount);
	free(proplists);
	//將全部分類的協議,附加到類對象的協議列表中
	rw->protocols.attachLists(protolists, protocount);
	free(protolists);
}
複製代碼

咱們發現rw->methods.attachLists(mlists, mcount);方法是實現將全部分類的對象方法,附加到類對象的方法列表中,其餘兩個屬性和協議都是調用這個方法,咱們分析一個就能夠了。

點擊進入attachLists方法,裏面有一個段實現代碼

if (hasArray()) {
	// many lists -> many lists
	uint32_t oldCount = array()->count;
	uint32_t newCount = oldCount + addedCount;
	setArray((array_t *)realloc(array(), array_t::byteSize(newCount)));
	array()->count = newCount;
	//array()->list 原來的方法列表
	memmove(array()->lists + addedCount, array()->lists, 
	oldCount * sizeof(array()->lists[0]));
	//addedList 全部分類的方法列表
	memcpy(array()->lists, addedLists, 
	addedCount * sizeof(array()->lists[0]));
}
複製代碼
  • 一、擴容,把類中的方法數組和分類中的方法數組計算出來
  • 二、memmove把類中方法放到數組的最後一位
  • 三、memcpy把分類中的方法放到數組的前面。

extension中定義的方法

若是是在Swift類的extension中定義的方法(重寫OC基類的方法除外)。那麼針對這個方法的調用老是會在編譯時就決定,也就是說在調用這類對象方法時,方法調用指令中的函數地址將會以硬編碼的形式存在在extension中定義的方法沒法在運行時作任何的替換和改變!並且方法函數的符號信息都不會保存到類的描述信息中去。這也就解釋了在Swift中派生類沒法重寫一個基類中extension定義的方法的緣由了。由於extension中的方法調用是硬編碼完成,沒法支持多態!下面的Swift源代碼以及C僞代碼實現說明了這個狀況

////////Swift源代碼

//類定義
class CA {
   open func foo(){}
}

//類的extension定義
extension CA {
  open func extfoo(){}
}

func main() {
 let obj = CA()
 obj.foo()
 obj.extfoo()
}
複製代碼
////////C僞代碼

//...........................................運行時定義部分


//Swift類描述。
struct  swift_class {
    ...   //其餘的屬性,由於這裏不關心就不列出了。
   //虛函數表恰好在結構體的第0x50的偏移位置。
    IMP vtable[1];
};


//...........................................源代碼中類的定義和方法的定義和實現部分


//類定義
struct CA {
      struct  swift_class *isa;
}

//類的方法函數的實現定義
void foo(){}
//類的extension的方法函數實現定義
void extfoo(){}

//類的描述信息構建,這些都是在編譯代碼時就明確了而且保存在數據段中。
//extension中定義的函數不會保存到虛函數表中。
struct swift_class classCA;
classCA.vtable[0] = {&foo};


//...........................................源代碼中程序運行的部分

void main(){
  CA *obj =  CA.__allocating_init(classCA)
  obj->isa = &classCA;
  asm("mov x20, obj");
  //Swift中常規方法foo調用採用間接調用實現
  obj->isa->vtable[0]();
  //Swift中extension方法extfoo調用直接硬編碼調用,而不是間接調用實現
  extfoo();
}

複製代碼

【注】extension中是能夠重寫OC基類的方法,可是不能重寫Swift類中的定義的方法。具體緣由根據上面的解釋就很是清楚了。

四、成員變量的訪問

OC成員變量的訪問

@property 的本質就是ivar + getter + setter

咱們建立一個person類,裏面建立一個屬性

@property (nonatomic,copy) NSString *name;
複製代碼

打印屬性信息

unsigned int count;
objc_property_t *propertyList = class_copyPropertyList([Person class], &count);
for (unsigned int i = 0; i< count; i++)
	{
	const char *name = property_getName(propertyList[i]);
	NSLog(@"__%@",[NSString stringWithUTF8String:name]);
	objc_property_t property = propertyList[i];
	const char *a = property_getAttributes(property);
	NSLog(@"屬性信息__%@",[NSString stringWithUTF8String:a]);
}
複製代碼

屬性信息中NSString咱們是知道了,可是T,C,N,V_name都是什麼意思呢。咱們查看官方介紹

//T@"NSString",C,N,V_name
//T 類型
//C copy
//N nonatomic
//V 實例變量
複製代碼

方法列表

u_int methodCount;
NSMutableArray *methodList = [NSMutableArray array];
Method *methods = class_copyMethodList([Person class], &methodCount);
for (int i = 0; i < methodCount; i++)
{
	SEL name = method_getName(methods[i]);
	NSString *strName = [NSString stringWithCString:sel_getName(name) encoding:NSUTF8StringEncoding];
	[methodList addObject:strName];
}
free(methods);

NSLog(@"方法列表:%@",methodList);
複製代碼

咱們並無寫name&setName:方法,因此這個getset方法應該就是runtime生成的。

property的實現

property的實現主要是利用runtime的兩個方法

class_addProperty(Class _Nullable cls, const char * _Nonnull name,
const objc_property_attribute_t * _Nullable attributes,
unsigned int attributeCount)

void
class_addMethods(Class _Nullable, struct objc_method_list * _Nonnull)
複製代碼

一、class_addProperty 生成屬性

/** 
* Adds a property to a class.
* 
* @param cls 修改的類
* @param name 屬性名字
* @param attributes 屬性數組
* @param attributeCount 屬性數組數量
* @return y 成功,n失敗
*/
OBJC_EXPORT BOOL class_addProperty(Class cls, const char *name, const objc_property_attribute_t *attributes, unsigned int attributeCount)
 
複製代碼

生成屬性

objc_property_attribute_t type = { "T", "@\"NSString\"" };
objc_property_attribute_t ownership = { "C", "" }; // C = copy
objc_property_attribute_t nonatomic = { "N", "" }; //nonatomic
objc_property_attribute_t backingivar  = { "V", "_name" };//V 實例變量
objc_property_attribute_t attrs[] = { type, ownership,nonatomic, backingivar };
class_addProperty([self class], "name", attrs, 4); 
複製代碼

二、class_addMethod 生成方法

NSString *nameGetter(id self, SEL _cmd) {
Ivar ivar = class_getInstanceVariable([self class], "_privateName");
return object_getIvar(self, ivar);
}
void nameSetter(id self, SEL _cmd, NSString *newName) {
Ivar ivar = class_getInstanceVariable([self class], "_privateName");
id oldName = object_getIvar(self, ivar);
if (oldName != newName) object_setIvar(self, ivar, [newName copy]);
}


//其中 「v@:」 表示返回值和參數

if(class_addMethod([self class],  NSSelectorFromString(@"name"), (IMP)nameGetter, "@@:"))
{
	NSLog(@"name get 方法添加成功");
}
else
{
	NSLog(@"name get 方法添加失敗");
}

if(class_addMethod([self class], NSSelectorFromString(@"setName:"), (IMP)nameSetter, "v@:@"))
{
	NSLog(@"name set 方法添加成功");
}
else
{
    NSLog(@"name set 方法添加失敗");
}

複製代碼

Swift類中成員變量的訪問

雖說OC類和Swift類的對象內存佈局很是類似,每一個對象實例的開始部分都是一個isa數據成員指向類的描述信息,而類中定義的屬性或者變量則通常會根據定義的順序依次排列在isa的後面。OC類還會爲全部成員變量,生成一張變量表信息,變量表的每一個條目記錄着每一個成員變量在對象內存中的偏移量。這樣在訪問對象的屬性時會經過偏移表中的偏移量來讀取偏移信息,而後再根據偏移量來讀取或設置對象的成員變量數據。在每一個OC類的get和set兩個屬性方法的實現中,對於屬性在類中的偏移量值的獲取都是經過硬編碼來完成,也就是說是在編譯連接時刻決定的。

對於Swift來講,對成員變量的訪問獲得更加的簡化。系統會對每一個成員變量生成get/set兩個函數來實現成員變量的訪問。系統不會再爲類的成員變量生成變量偏移信息表,所以對於成員變量的訪問就是直接在編譯連接時肯定成員變量在對象的偏移位置,這個偏移位置是硬編碼來肯定的。下面展現Swift源代碼和C僞代碼對數據成員訪問的實現:

////////Swift源代碼

class CA
{
   var a:Int = 10
   var b:Int = 20
}

void main()
{
    let obj = CA()
    obj.b = obj.a
}

複製代碼
////////C僞代碼

//...........................................運行時定義部分

//Swift類描述。
struct swift_class {
    ...   //其餘的屬性,由於這裏不關心就不列出了
    //虛函數表恰好在結構體的第0x50的偏移位置。
    IMP vtable[4];
};


//...........................................源代碼中類的定義和方法的定義和實現部分

//CA類的結構體定義也是CA類對象在內存中的佈局。
struct CA
{
   struct swift_class *isa;
   long  reserve;   //這裏的值目前老是2
   int a;
   int b;
};

//類CA的方法函數的實現。
int getA(){
    struct CA *obj = x20;   //取x20寄存器的值,也就是對象的值。
    return obj->a;
}
void setA(int a){
 struct CA *obj = x20;   //取x20寄存器的值,也就是對象的值。
 obj->a = a;
}
int getB(){
    struct CA *obj = x20;   //取x20寄存器的值,也就是對象的值。
    return obj->b;
}
void setB(int b){
 struct CA *obj = x20;   //取x20寄存器的值,也就是對象的值。
 obj->b = b;
}

struct swift_class classCA;
classCA.vtable[4] = {&getA,&setA,&getB, &setB};


//...........................................源代碼中程序運行的部分

void main(){
   CA *obj =  CA.__allocating_init(classCA);
   obj->isa = &classCA;
   obj->reserve = 2;
   obj->a = 10;
   obj->b = 20;
   asm("mov x20, obj");
   obj->isa->vtable[3](obj->isa->vtable[0]());  // obj.b = obj.a的實現
}

複製代碼

從上面的代碼能夠看出,Swift類會爲每一個定義的成員變量都生成一對get/set方法並保存到虛函數表中。全部對對象成員變量的方法的代碼都會轉化爲經過虛函數表來執行get/set相對應的方法。 下面是Swift類中成員變量的實現和內存結構佈局圖:

補充

Swift 類的方法以及全局函數

Swift類中定義的類方法和全局函數同樣,由於不存在對象做爲參數,所以在調用此類函數時也不會存在將對象保存到x20寄存器中這麼一說。同時源代碼中定義的函數的參數在編譯時也不會插入附加的參數。Swift語言會對全部符號進行重命名修飾,類方法和全局函數也不例外。這也就使得全局函數和類方法也支持名稱相同可是參數不一樣的函數定義。簡單的說就是類方法和全局函數就像C語言的普通函數同樣被實現和定義,全部對類方法和全局函數的調用都是在編譯連接時刻硬編碼爲函數地址調用來處理的

OC調用Swift類中的方法

若是應用程序是經過OC和Swift兩種語言混合開發完成的。那就必定會存在着OC語言代碼調用Swift語言代碼以及相反調用的狀況。對於Swift語言調用OC的代碼的處理方法是系統會爲工程創建一個橋聲明頭文件:項目工程名-Bridging-Header.h,全部Swift須要調用的OC語言方法都須要在這個頭文件中聲明。而對於OC語言調用Swift語言來講,則有必定的限制。由於Swift和OC的函數調用ABI規則不相同,OC語言只能建立Swift中從NSObject類中派生類對象,而方法調用則只能調用原NSObject類以及派生類中的全部方法以及被聲明爲@objc關鍵字的Swift對象方法。若是須要在OC語言中調用Swift語言定義的類和方法,則須要在OC語言文件中添加:#import "項目名-Swift.h"。當某個Swift方法被聲明爲@objc關鍵字時,在編譯時刻會生成兩個函數,一個是本體函數供Swift內部調用,另一個是跳板函數(trampoline)是供OC語言進行調用的。這個跳板函數信息會記錄在OC類的運行時類結構中,跳板函數的實現會對參數的傳遞規則進行轉換:把x0寄存器的值賦值給x20寄存器,而後把其餘參數依次轉化爲Swift的函數參數傳遞規則要求,最後再執行本地函數調用。整個過程的實現以下:

////////Swift源代碼

//Swift類定義
class MyUIView:UIView {
  @objc    
  open func foo(){}
}

func main() {
  let obj = MyUIView()
  obj.foo()
}

//////// OC源代碼
#import "工程-Swift.h"

void main() {
  MyUIView *obj = [MyUIView new];
  [obj foo];
}
複製代碼
////////C僞代碼

//...........................................運行時定義部分

//OC類的方法結構體
struct method_t {
    SEL name;
    IMP imp;
};

//Swift類描述
struct swift_class {
    ...   //其餘的屬性,由於這裏不關心就不列出了。
    struct method_t  methods[1];
    ...   //其餘的屬性,由於這裏不關心就不列出了。
    //虛函數表恰好在結構體的第0x50的偏移位置。
    IMP vtable[1];
};

//...........................................源代碼中類的定義和方法的定義和實現部分

//類定義
struct MyUIView {
      struct swift_class *isa;
}

//類的方法函數的實現

//本體函數foo的實現
void foo(){}
//跳板函數的實現
void trampoline_foo(id self, SEL _cmd){
     asm("mov x20, x0");
     self->isa->vtable[0](); //這裏調用本體函數foo
}

//類的描述信息構建,這些都是在編譯代碼時就明確了而且保存在數據段中。
struct swift_class classMyUIView;
classMyUIView.methods[0] = {"foo", &trampoline_foo};
classMyUIView.vtable[0] = {&foo};


//...........................................源代碼中程序運行的部分

//Swift代碼部分
void main()
{
  MyUIView *obj = MyUIView.__allocating_init(classMyUIView);
  obj->isa = &classMyUIView;
   asm("mov x20, obj");
   //Swift方法foo的調用採用間接調用實現。
   obj->isa->vtable[0]();
}

//OC代碼部分
void main()
{
  MyUIView *obj = objc_msgSend(objc_msgSend(classMyUIView, "alloc"), "init");
  obj->isa = &classMyUIView;
  //OC語言對foo的調用仍是用objc_msgSend來執行調用。
  //由於objc_msgSend最終會找到methods中的方法結構並調用trampoline_foo 
  //而trampoline_foo內部則直接調用foo來實現真實的調用。
  objc_msgSend(obj, @selector(foo));
}

複製代碼

下面的圖形展現了Swift中帶@objc關鍵字的方法實現,以及OC語言調用Swift對象方法的實現

文中關於Swift部分轉載自歐陽大哥2013的Swift5.0的Runtime機制淺析,爲了方便對比學習,我添加了OC RunTime的運行機制

相關文章
相關標籤/搜索