以前的三篇文章都講的是interface和setter/getter,這一篇就講一下ivar。ide
@interface MyViewController :UIViewController { NSString *name; } @end
.m文件中,你會發現若是你使用 self.name,Xcode會報錯,提示你使用->,改爲self->name就能夠了。由於OC中,點語法是表示調用方法,而上面的代碼中沒有name這個方法。
因此在oc中點語法其實就是調用對象的setter和getter方法的一種快捷方式, self.name = myName 徹底等價於 [self setName:myName];函數
那麼成員變量是什麼時候分配內存,又儲存在何處呢?ui
因此咱們就要先分析一下objc_class結構體。atom
首先咱們知道,OC中,全部的對象均可以認爲是id類型。那麼id類型是什麼呢?spa
typedef struct objc_class *Class; typedef struct objc_object { Class isa; } *id;
根據runtime源碼能夠看到,id是指向Class類型的指針。
而Class類型是objc_class結構的指針,因而咱們能夠看到objc_class結構體的定義:ssr
struct objc_class { Class superclass; const char *name; uint32_t version; uint32_t info; uint32_t instance_size; struct old_ivar_list *ivars; struct old_method_list **methodLists; Cache cache; struct old_protocol_list *protocols; // CLS_EXT only const uint8_t *ivar_layout; struct old_class_ext *ext; }; // runtime版本不一樣會有修改,可是本質屬性大體如此
能夠看到Objective-C對象系統的基石:struct objc_class。
其中,咱們能夠很快地發現struct objc_ivar_list *ivars,這個就是成員變量列表。指針
struct objc_ivar { char *ivar_name; char *ivar_type; int ivar_offset; int space; }; struct objc_ivar_list { int ivar_count; int space; struct objc_ivar ivar_list[1]; }
再深刻看就能看到ivar真正的定義了,名字,type,基地址偏移量,消耗空間。
實際上在objc_class結構體中,有ivar_layout這麼一個東西。code
顧名思義存放的是變量的位置屬性,與之對應的還有一個weakIvarLayout變量,不過在默認結構中沒有出現。這兩個屬性用來記錄ivar哪些是strong或者weak,而這個記錄操做在runtime階段已經被肯定好。
具體的東西能夠參考sunnyxx孫源大神的文章Objective-C Class Ivar Layout 探索對象
因此,咱們幾乎能夠肯定,ivar的確是在runtime時期就已經被肯定。類型,空間,位置,三者齊全。blog
因此,這也就是爲何分類不能簡單地用@property來添加成員變量的緣由。
仍是sunnyxx大神的文章objc category的祕密,其中對OC分類的本質探索很是透徹。
先看一下分類的結構:
struct category_t { const char *name; /// 類名 classref_t cls; /// 類指針 struct method_list_t *instanceMethods; /// 實例方法 struct method_list_t *classMethods; /// 類方法 struct protocol_list_t *protocols; /// 擴展的協議 struct property_list_t *instanceProperties; /// 擴展屬性 method_list_t *methodsForMeta(bool isMeta) { ... } property_list_t *propertiesForMeta(bool isMeta) { ... } };
能夠看到,分類結構自己是不存在ivar的容器的,因此天然沒有成員變量的位置。所以也很天然地沒有辦法自動生成setter和getter。
OC自己是一門原型語言,對象和類原型很像。類對象執行alloc方法就像是原型模式中的copy操做同樣,類保存了copy所需的實例信息,這些信息內存信息在runtime加載時就被固定了,沒有擴充Ivar的條件。
OC固然也沒有封死動態添加成員變量這條路,由於咱們有_object_set_associative_reference函數能夠用。
那麼它的原理又是什麼呢?
void _object_set_associative_reference(id object, void *key, id value, uintptr_t policy) { // retain the new value (if any) outside the lock. ObjcAssociation old_association(0, nil); id new_value = value ? acquireValue(value, policy) : nil; { AssociationsManager manager; AssociationsHashMap &associations(manager.associations()); disguised_ptr_t disguised_object = DISGUISE(object); // disguised_ptr_t是包裝的unsigned long類型 if (new_value) { // break any existing association. AssociationsHashMap::iterator i = associations.find(disguised_object); if (i != associations.end()) { // secondary table exists ObjectAssociationMap *refs = i->second; ObjectAssociationMap::iterator j = refs->find(key); if (j != refs->end()) { old_association = j->second; j->second = ObjcAssociation(policy, new_value); } else { (*refs)[key] = ObjcAssociation(policy, new_value); } } else { // create the new association (first time). ObjectAssociationMap *refs = new ObjectAssociationMap; associations[disguised_object] = refs; (*refs)[key] = ObjcAssociation(policy, new_value); object->setHasAssociatedObjects(); } } else { // setting the association to nil breaks the association. AssociationsHashMap::iterator i = associations.find(disguised_object); if (i != associations.end()) { ObjectAssociationMap *refs = i->second; ObjectAssociationMap::iterator j = refs->find(key); if (j != refs->end()) { old_association = j->second; refs->erase(j); } } } } // release the old value (outside of the lock). if (old_association.hasValue()) ReleaseValue()(old_association); }
經過代碼能夠比較清楚的看出來,大概思路是經過維護Map,經過對象來生成一個惟一的 unsigned long 的變量來做爲橫座標,查找到以後,再經過key作縱座標去查找,這樣就能找到對應的變量,也就是說只要在 某個對象 中key是惟一的,就能設置和獲取對應的變量,這樣就與class無關。
類的結構在runtime時期就已經肯定了,因此按照類結構的角度來看,類中的成員變量的地址都是基於類對象自身地址進行偏移的。
@interface Person: NSObject { NSString * _name; NSString * _sex; char _ch; } @property(nonatomic, copy) NSString *pName; @property(nonatomic, copy) NSString *pSex; @end @implementation Person - (instancetype)init { if (self = [super init]) { NSLog(@"%p, %p, %p, %p, %p, %p, %p", self, &_name, &_sex, &_ch, _pName, _pSex); } return self; } @end
後面三個地址確實相差爲8位,可是在類對象self和第一個成員變量之間相差的地址是10位。這0x10位的地址偏移,實際上就是isa指針的偏移。
指針在64位系統中佔8位地址很正常,可是char類型的成員變量同樣也是偏移了8位,明明char類型只須要1bit。這其實是爲了內存對齊。
實際上在給類添加成員變量時,會調用這個函數:
BOOL class_addIvar(Class cls, const char *name, size_t size, uint8_t alignment, const char *type)
alignment參數就是表明內存對齊方式。
uint32_t offset = cls->unalignedInstanceSize(); uint32_t alignMask = (1<<alignment)-1; offset = (offset + alignMask) & ~alignMask;
上面這段代碼就是地址偏移計算
蘋果規定了某個變量它的偏移默認爲1 <<
alignment,而在上下文中這個值爲指針長度。所以,OC中類結構地址的偏移計算與結構體仍是有不一樣的,只要是小於8bit長度的地址,統一歸爲8bit偏移
具體的綁定ivar都經過addIvar這個函數,包括@synthesize關鍵字的綁定,具體@synthesize綁定成員變量只須要看一下class_addIvar的具體實現便可。
其實經過對成員變量綁定的分析,還有一個類似的問題:爲何OC中的集合類,例如NSArray, NSDictionary等等,都只能存對象而不能存基本類型呢?其實答案不固定,可是思想基本都一致,你們本身思考思考就好。