前言
在上一篇文章是的時候,objc_setProperty方法的實現并沒有體現strong和weak這兩個修飾詞,所以這兩個修飾詞是有另外的實現,而且是在上已層實現的;copy修飾詞的話,只有copyWithZone和mutableCopyWithZone方法的調用,沒有具體實現。
在看這么多的blog的時候,上過當,吃過虧。所以還是從源代碼入手去更好理解這三個修飾詞吧。
類的成員變量
分析strong和weak的實現之前,先看看對象的成員變量是怎么是進行賦值。
為什么呢?因為對象的屬性的setter本質就是對對象的成員變量進行復制,一般情況下,每一個對象的屬性就對應存在一個對象的成員變量。所以本質上即使在了解成員變量的strong和weak實現。
成員變量的getter
先看一下成員變量的結構體定義
//Ivar本質就是一個objc_ivar結構體,在objc_ivar結構體記錄對象成員變量的信息
//這是在runtime.h頭文件中的定義
typedef struct objc_ivar *Ivar;
struct objc_ivar {
//變量名
char * _Nullable ivar_name OBJC2_UNAVAILABLE;
//變量的數據類型
char * _Nullable ivar_type OBJC2_UNAVAILABLE;
//變量在對象指針的偏移量,也就是變量存放在內存的實際位置
int ivar_offset OBJC2_UNAVAILABLE;
#ifdef __LP64__
int space OBJC2_UNAVAILABLE;
#endif
}
//這是在objc-class-old.mm文件中的定義,和上面的objc_ivar的機構體是一樣的,只是名稱不一樣。
struct old_ivar {
char *ivar_name;
char *ivar_type;
int ivar_offset;
#ifdef __LP64__
int space;
#endif
};
//在objc-class-old.mm文件中的定義了這么幾個宏
//應該是做了一個新舊結構體的映射
#define oldprotocol(proto) ((struct old_protocol *)proto)
#define oldmethod(meth) ((struct old_method *)meth)
#define oldcategory(cat) ((struct old_category *)cat)
#define oldivar(ivar) ((struct old_ivar *)ivar)
#define oldproperty(prop) ((struct old_property *)prop)
//在objc-class-old.mm文件中,這個函數是獲取成員變量在對象的相對偏移量
ptrdiff_t ivar_getOffset(Ivar ivar) {
return oldivar(ivar)->ivar_offset;
}
//這是在objc-runtime-new中定義的函數
ptrdiff_t ivar_getOffset(Ivar ivar) {
if (!ivar) return 0;
return *ivar->offset;
}
在獲取成員變量,先了解上面成員變量的結構,可以看到在新舊系統中會有細微的差異,但是結構體的結構是一樣的。
下面就是成員變量獲取的實際實現了,下面就一句一句代碼的分析里面的邏輯
/**
查找類成員變量的函數
cls:要查找的類
ivar:要查找的變量,通過變量的結構體對這個變量進行描述,包括變量名、變量類型和變量的指針便宜量
ivarOffset:變量相對于對象所在位置的指針偏移量
memoryManagement:內存管理信息
*/
static void
_class_lookUpIvar(Class cls, Ivar ivar, ptrdiff_t& ivarOffset,
objc_ivar_memory_management_t& memoryManagement)
{
ivarOffset = ivar_getOffset(ivar);
// Look for ARC variables and ARC-style weak.
// Preflight the hasAutomaticIvars check
// because _class_getClassForIvar() may need to take locks.
//查找ARC成員變量和ARC內存管理的weak
//提前檢測是不是AutomaticIvars,其實就是是否開啟ARC
//
bool hasAutomaticIvars = NO;
for (Class c = cls; c; c = c->superclass) {
if (c->hasAutomaticIvars()) {
hasAutomaticIvars = YES;
break;
}
}
if (hasAutomaticIvars) {
Class ivarCls = _class_getClassForIvar(cls, ivar);
if (ivarCls->hasAutomaticIvars()) {
// ARC layout bitmaps encode the class's own ivars only.
// Use alignedInstanceStart() because unaligned bytes at the start
// of this class's ivars are not represented in the layout bitmap.
//ARC 只針對類自身的變量輸出位圖編碼
//使用alignedInstanceStart()是因為類的變量非位對齊其起始位置,并不代表在輸出位圖上
ptrdiff_t localOffset =
ivarOffset - ivarCls->alignedInstanceStart();
//查找成員變量的指針偏移量是否在類的變量輸出位置
if (isScanned(localOffset, class_getIvarLayout(ivarCls))) {
//如果是,那么就是強引用的管理方式,因為屬于類自身的變量,并返回
memoryManagement = objc_ivar_memoryStrong;
return;
}
//查找成員變量的指針偏移量是否在弱變量區域
if (isScanned(localOffset, class_getWeakIvarLayout(ivarCls))) {
//如果是,那么就是弱引用的管理方式,并返回
memoryManagement = objc_ivar_memoryWeak;
return;
}
// Unretained is only for true ARC classes.
//這個類是ARC管理的其他情況就是Unretained的變量
if (ivarCls->isARC()) {
memoryManagement = objc_ivar_memoryUnretained;
return;
}
}
}
//非ARC內存管理的,那么就賦值為未知內存管理方式
memoryManagement = objc_ivar_memoryUnknown;
}
/**
獲取對象的成員變量
obj:對象
ivar:成員變量的描述,包括成員變量的名稱、類型和偏移量
*/
id object_getIvar(id obj, Ivar ivar)
{
if (!obj || !ivar || obj->isTaggedPointer()) return nil;
//成員變量的指針偏移量,用來存放查找偏移量結果
ptrdiff_t offset;
//成員變量的內存管理方式,用來存放查找變量對應的內存管理方式
objc_ivar_memory_management_t memoryManagement;
//通過對象的類,去查找成員變量,并且將偏移指針存放在offset,內存方式存放在memoryManagement
_class_lookUpIvar(obj->ISA(), ivar, offset, memoryManagement);
//通過取到成員變量的偏移量,以對象為起始位置計算出其實際位置,獲取對象指針
id *location = (id *)((char *)obj + offset);
//如果是弱應用
if (memoryManagement == objc_ivar_memoryWeak) {
//則在弱應用表中找出對應的對象
return objc_loadWeak(location);
} else {
//否則,直接返回指向的對象
return *location;
}
}
通過以上這段代碼,可以得到下面幾點總結:
- ARC內存管理是在這一層實現的
成員變量的setter
/**
對對象成員變量進行賦值,這是成員變量賦值在runtime層的實現
obj:需要賦值的對象
name:成員遍歷那個的名稱
value:性質
assumeStrong:需要設置的內存管理方式
*/
static ALWAYS_INLINE
void _object_setIvar(id obj, Ivar ivar, id value, bool assumeStrong)
{
//空值判斷
if (!obj || !ivar || obj->isTaggedPointer()) return;
ptrdiff_t offset;
objc_ivar_memory_management_t memoryManagement;
//獲取對象成員變量的指針偏移量和內存管理方式
_class_lookUpIvar(obj->ISA(), ivar, offset, memoryManagement);
//如果內存管理方式為未知
if (memoryManagement == objc_ivar_memoryUnknown) {
//而且指定了strong強引用的,修正memoryManagement
if (assumeStrong) memoryManagement = objc_ivar_memoryStrong;
//沒指定為strong強引用的,修正為Unretained
else memoryManagement = objc_ivar_memoryUnretained;
}
//根據指針偏移量,獲取成員變量指向的對象指針
id *location = (id *)((char *)obj + offset);
switch (memoryManagement) {
//弱引用,則將新值存放在弱引用weak表
case objc_ivar_memoryWeak: objc_storeWeak(location, value); break;
//強引用,則將新值存放在強引用的strong表
case objc_ivar_memoryStrong: objc_storeStrong(location, value); break;
//Unretained的,則直接賦值
case objc_ivar_memoryUnretained: *location = value; break;
//這種情況不可能發生
case objc_ivar_memoryUnknown: _objc_fatal("impossible");
}
}
//非strong默認的成員變量進行賦值
void object_setIvar(id obj, Ivar ivar, id value)
{
return _object_setIvar(obj, ivar, value, false /*not strong default*/);
}
//對默認是strong的成員變量進行賦值
void object_setIvarWithStrongDefault(id obj, Ivar ivar, id value)
{
return _object_setIvar(obj, ivar, value, true /*strong default*/);
}
/**
對對象成員變量進行賦值,這是成員變量賦值在runtime層的實現
obj:需要賦值的對象
name:成員遍歷那個的名稱
value:性質
assumeStrong:需要設置的內存管理方式
*/
static ALWAYS_INLINE
Ivar _object_setInstanceVariable(id obj, const char *name, void *value,
bool assumeStrong)
{
Ivar ivar = nil;
if (obj && name && !obj->isTaggedPointer()) {
//通過給定的名字,查找成員變量
if ((ivar = _class_getVariable(obj->ISA(), name))) {
_object_setIvar(obj, ivar, (id)value, assumeStrong);
}
}
return ivar;
}
//給對象成員變量進行復制
Ivar object_setInstanceVariable(id obj, const char *name, void *value)
{
return _object_setInstanceVariable(obj, name, value, false);
}
//給對象用strong默認修飾的成員變量進行賦值
Ivar object_setInstanceVariableWithStrongDefault(id obj, const char *name,
void *value)
{
return _object_setInstanceVariable(obj, name, value, true);
}
由上面這段代碼可以看出來,可以總結出來下面幾點:
- 所有對成員變量的賦值,都是通過調用_object_setIvar函數實現,_object_setIvar函數是具體實現,通過參數區分功能
- 對象和對象的成員變量是放在一片連續的內存塊上面的,在編譯期在類中已經確定成員變量相對于對象的的位置
- weak和strong修飾的成員變量,分別存放在不同的表中
- Unretained修飾成員變量只做賦值操作
strong修飾詞
在上面成員變量的setter上可以看到,對于用strong修飾的成員變量的賦值,是調用objc_storeStrong函數來實現的,那么現在看看objc_storeStrong的具體實現是怎樣的
void
objc_storeStrong(id *location, id obj)
{
//將*location指向的對象賦值給prev
id prev = *location;
//如果新值和原來的值一致,則返回,無需繼續操作
if (obj == prev) {
return;
}
//對新值引用計數加一
objc_retain(obj);
//將對象指針指向新值
*location = obj;
//釋放舊值
objc_release(prev);
}
從源代碼上看,對于strong修飾的成員變量操作比較簡單,整個過程就是對新值引用計數加一,和對舊值進行釋放
weak修飾詞
strong修飾詞的成員變量賦值比較簡單,那么現在看看objc_storeWeak的源代碼,看看objc_storeWeak是怎么工作的
/**
localtion : 對象指針
nebObj:新值
*/
static id
storeWeak(id *location, objc_object *newObj)
{
assert(haveOld || haveNew);
//根據haveNew參數判斷是否有新值,如果有新值,而且新值指針指向的對象為空,則提示
if (!haveNew) assert(newObj == nil);
Class previouslyInitializedClass = nil;
//舊值變量,用于存放舊值
id oldObj;
//舊值的weak引用表,用于記錄該值的被引用情況
SideTable *oldTable;
//新值的weak引用表,用于記錄新值的被引用情況
SideTable *newTable;
/**
需要同時獲取舊值和新值的鎖
為了解決鎖定地址阻止鎖的順序問題
如果舊值正在修改,那么跳回retry重新做一遍流程
*/
retry:
if (haveOld) {
//如果haveOld的參數為真,那么根據location的對象指針獲取舊值引用
oldObj = *location;
//則根據oldObj對象在全局的一個hash表獲取SideTables類型的引用表
oldTable = &SideTables()[oldObj];
} else {
//否則將舊引用表置空
oldTable = nil;
}
if (haveNew) {
//根據haveNew為真,則根據新值的對象在全局的一個hash表獲取SideTables類型的引用表
newTable = &SideTables()[newObj];
} else {
//否則將新引用表置空
newTable = nil;
}
//同時對新引用表和舊引用表加鎖
SideTable::lockTwo<haveOld, haveNew>(oldTable, newTable);
//如果haveOld為真,而且location指針指向的對象并不是oldObj
if (haveOld && *location != oldObj) {
//那么可能舊值已經更改,解鎖兩個表,然后重試
SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);
goto retry;
}
// Prevent a deadlock between the weak reference machinery
// and the +initialize machinery by ensuring that no
// weakly-referenced object has an un-+initialized isa.
/**
通過保證沒有一個弱引用的對象是沒有一個沒有initialized的isa
來打斷弱引用架構和+initialize架構間的死鎖
*/
if (haveNew && newObj) {
//獲取新值對象的元類對象
Class cls = newObj->getIsa();
//如果新值的元類對象和之前初始化的不一致,且元類未初始化
if (cls != previouslyInitializedClass &&
!((objc_class *)cls)->isInitialized())
{
//解鎖兩個表
SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);
//初始化新值對象的元類對象
_class_initialize(_class_getNonMetaClass(cls, (id)newObj));
// If this class is finished with +initialize then we're good.
// If this class is still running +initialize on this thread
// (i.e. +initialize called storeWeak on an instance of itself)
// then we may proceed but it will appear initializing and
// not yet initialized to the check above.
// Instead set previouslyInitializedClass to recognize it on retry.
previouslyInitializedClass = cls;
//跳轉到重試
goto retry;
}
}
if (haveOld) {
//在舊值的weak表中清除舊值,如果weak表為空,同時在entry中刪除weak表
weak_unregister_no_lock(&oldTable->weak_table, oldObj, location);
}
if (haveNew) {
/**
將新值的weak表添加在entry中,并且將新值注冊到weak表中
weak_register_no_lock如果返回nil,則表示弱引用存放被拒絕了
1.類中實現了allowsWeakReference,并且返回NO,表示不允許弱引用
2.該對象正在調用deallocating進行釋放
*/
newObj = (objc_object *)
weak_register_no_lock(&newTable->weak_table, (id)newObj, location,
crashIfDeallocating);
//同時將是弱引用的標記寫在對象的refcount table的表中
if (newObj && !newObj->isTaggedPointer()) {
newObj->setWeaklyReferenced_nolock();
}
// Do not set *location anywhere else. That would introduce a race.
//將location對象指針指向新值
*location = (id)newObj;
}
else {
//沒有性質,什么都不做
}
//兩個表同時解鎖
SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);
//返回新值對象
return (id)newObj;
}
/**
這個函數存放一個新值到一個使用__weak修飾的變量
可以在任何地方指定一個__weak變量
location:弱引用自身的內存地址
newObj:這個弱引用指針需要指向的值
返回: 返回新值
*/
id
objc_storeWeak(id *location, id newObj)
{
return storeWeak<DoHaveOld, DoHaveNew, DoCrashIfDeallocating>
(location, (objc_object *)newObj);
}
通過上面的分析,weak修飾的成員變量的賦值,比strong修飾的成員變量賦值得多了??梢钥偨Y一下:
- objc_storeWeak函數不止是用來對對象成員變量的賦值,還包括所有使用__weak修飾符修飾的變量的賦值,以后還需要繼續看回這個方法
- 有一個全局的weak表存放在所有指向weak修飾的對象的指針的的指針,這個指針是該對象使用了weak修飾對象的引用的指針
- 在對weak修飾的變量進行賦值的時候,會將舊值的weak表釋放掉,并對創建新值的weak表,也就是說,在成員變量中大量使用weak變量的話,會一定程度上占用cpu的資源,會有效率損耗
- 在賦值過成功有加鎖和解鎖過程,所以整個賦值過程是線程安全的
后記
在了解strong和weak修飾的成員賦值的過程,發現strong修飾的成員變量只是增加所持有對象的循環引用,而weak修飾的成員變量,則相對復雜很多。在了解過程中,可能還是會有理解偏差,需要多次閱讀代碼再補充。
還有一個就是要盡快弄好一個High Sierra來打斷點調試真實的運行過程,現在只是在源代碼上面的分析理解。
后記的后記
終于將系統降級到High Sierra了,通過objc的源代碼進行調試,證明我的理解并沒有太大的差異。
weak修飾的成員變量進行賦值的調用關系如下
strong修飾的成員變量進行賦值的調用關系如下
終于正式了自己測瞎猜,哈哈。開心!