手撕iOS底層10 -- strong©&weak底層瞭解

0X01 - strong & copy & weak

@interface LGPerson : NSObject
@property (nonatomic, copy) NSString *name;
@end

@implementation LGPerson
@end

@interface Teacher : LGPerson
{
    NSString *age;
}
@property (nonatomic, strong) NSString *sex;
@property (nonatomic, copy) NSString *other;

@end

@implementation Teacher

@end
複製代碼

如上示例代碼, 使用clang -rewrite-objc main.m命令, 查看Objective-C源代碼的c實現html

#ifndef _REWRITER_typedef_LGPerson
#define _REWRITER_typedef_LGPerson
typedef struct objc_object LGPerson;
typedef struct {} _objc_exc_LGPerson;
#endif

extern "C" unsigned long OBJC_IVAR_$_LGPerson$_name;
struct LGPerson_IMPL {
	struct NSObject_IMPL NSObject_IVARS; // 繼承自NSObject
	NSString *_name;
};

// @property (nonatomic, copy) NSString *name;
/* @end */


// @implementation LGPerson
// 屬性name的get方法
static NSString * _I_LGPerson_name(LGPerson * self, SEL _cmd) { return (*(NSString **)((char *)self + OBJC_IVAR_$_LGPerson$_name)); }

extern "C" __declspec(dllimport) void objc_setProperty (id, SEL, long, id, bool, bool);
// 屬性name的set方法 copy修飾
static void _I_LGPerson_setName_(LGPerson * self, SEL _cmd, NSString *name) { objc_setProperty (self, _cmd, __OFFSETOFIVAR__(struct LGPerson, _name), (id)name, 0, 1); }
// @end


#ifndef _REWRITER_typedef_Teacher
#define _REWRITER_typedef_Teacher
typedef struct objc_object Teacher;
typedef struct {} _objc_exc_Teacher;
#endif

extern "C" unsigned long OBJC_IVAR_$_Teacher$_sex;
extern "C" unsigned long OBJC_IVAR_$_Teacher$_other;
struct Teacher_IMPL {
	struct LGPerson_IMPL LGPerson_IVARS;// 繼承自LGPerson
  // 成員變量
	NSString *age; 
  // 屬性的帶下劃線的成員變量
	NSString *_sex; 
	NSString *_other;
};

// @property (nonatomic, strong) NSString *sex;
// @property (nonatomic, copy) NSString *other;

/* @end */

// @implementation Teacher


static NSString * _I_Teacher_sex(Teacher * self, SEL _cmd) { return (*(NSString **)((char *)self + OBJC_IVAR_$_Teacher$_sex)); }
// 屬性sex的set方法 strong修飾
static void _I_Teacher_setSex_(Teacher * self, SEL _cmd, NSString *sex) { (*(NSString **)((char *)self + OBJC_IVAR_$_Teacher$_sex)) = sex; }

static NSString * _I_Teacher_other(Teacher * self, SEL _cmd) { return (*(NSString **)((char *)self + OBJC_IVAR_$_Teacher$_other)); }
// 屬性other的set方法 copy修飾
static void _I_Teacher_setOther_(Teacher * self, SEL _cmd, NSString *other) { objc_setProperty (self, _cmd, __OFFSETOFIVAR__(struct Teacher, _other), (id)other, 0, 1); }
// @end
複製代碼

經過查看c代碼的實現, 發現copystrong修飾的屬性在set方法不同?objective-c

copy調用objc_setProperty, 而strong並無,答案仍是去LLVM的找答案?markdown

打開LLVM的開源工程, 在項目裏搜索objc_setProperyapp

這裏的映射關係很直觀的能夠看出來:ide

屬性用atomic && copy修飾使用objc_setProperty_atomic_copyoop

屬性用atomic && 沒有copy修飾使用objc_setProperty_atomic優化

屬性不是atomic && copy修飾使用objc_setProperty_nonatomic_copyui

其它修飾使用 objc_setProperty_atomic_copythis

在項目中,系統沒有setName這個方法,調用setName()會直接sel被hook掉, 走到底層objc_setPro這裏去編碼

objc-781的源碼裏,對應的方法如上。


copy修飾的 經過斷點底層是調用的objc_storeStrong

strong修飾的底層調用的也是objc_storeStrong,源碼中看objc_storeStrong

void objc_storeStrong(id *location, id obj) {
    id prev = *location;
    if (obj == prev) {
        return;
    }
    objc_retain(obj); // 對新值retain
    *location = obj;
    objc_release(prev);// 對舊值的release
}

複製代碼

而後在LLVM項目中搜索objc_storeStrong

EmitARCStoreStrongCall方法裏找到,再去找那裏調用了這個方法, 在GenerateCopyHelperFunction方法裏

在上邊這個代碼裏, 有一個switch, 分好多種枚舉BlockCaptureEntityKind狀況, 針對strongweak有不一樣的處理

/// Represents a type of copy/destroy operation that should be performed for an
/// entity that's captured by a block.
enum class BlockCaptureEntityKind {
  CXXRecord, // Copy or destroy
  AddressDiscriminatedPointerAuth,
  ARCWeak,
  ARCStrong,
  NonTrivialCStruct,
  BlockObject, // Assign or release
  None
};
複製代碼
  • 若是是strong修飾,就會執行EmitARCStoreStrongCall方法

  • 若是是weak修飾,就會執行EmitARCCopyWeak,在底層調用objc_initWeak

    /// void \@objc_copyWeak(i8** %dest, i8** %src)
    /// Disregards the current value in %dest. Essentially
    /// objc_release(objc_initWeak(dest, objc_readWeakRetained(src)))
    void CodeGenFunction::EmitARCCopyWeak(Address dst, Address src) {
      emitARCCopyOperation(*this, dst, src,
                           CGM.getObjCEntrypoints().objc_copyWeak,
                           llvm::Intrinsic::objc_copyWeak);
    }
    
    複製代碼

0x02 - 結論

  1. copystrong修飾的屬性在底層編譯是不同的,由編譯器LLVM對全部屬性做了優化處理, 其中copy是經過objc_setProperty賦值, strong是經過基地址(self)+偏移來賦值,(也就是指針偏移到成員變量所在的位置)而後還原成strong類型
// 源碼c實現
static void _I_Teacher_setSex_(Teacher * self, SEL _cmd, NSString *sex) { (*(NSString **)((char *)self + OBJC_IVAR_$_Teacher$_sex)) = sex; } 
//-----------------------------------
// objc-781源碼 
void objc_storeStrong(id *location, id obj) {
    id prev = *location;
    if (obj == prev) {
        return;
    }
    objc_retain(obj); // 對新值retain
    *location = obj;
    objc_release(prev);// 對舊值的release
}
複製代碼

上邊倆段代碼作的功能是同樣的,

  1. strongcopy在底層調用objc_storeStrong, 本質都是對新值retain,對舊值release
  2. weak在底層是調用的objc_initWeak

0x03 - 方法簽名

編譯器將每一個方法的返回值和參數類型編碼爲一個字符串, 並將其與方法的selector關聯在一塊兒, 可使用@encode編譯器指令來獲取它,給定一個類型,@encode返回這個類型的字符串編碼,任何可使用sizeof()操做參數,均可以使用@encode

Objective-C的源代碼 c實現裏,有這樣的一些代碼和符號, 打開經過clang -rewrite-objc main.m生成的main.cpp文件,

{{(struct objc_selector *)"name", "@16@0:8", (void *)_I_LGPerson_name},
	{(struct objc_selector *)"setName:", "v24@0:8@16", (void *)_I_LGPerson_setName_},
	{(struct objc_selector *)"name", "@16@0:8", (void *)_I_LGPerson_name},
	{(struct objc_selector *)"setName:", "v24@0:8@16", (void *)_I_LGPerson_setName_}}
複製代碼

@16@0:8v24@0:8@16等這些表明什麼?能夠經過Type Encodings - 官方文檔這裏來了解具體的知識

v24@0:8@16爲🌰例,

  • v表明沒有返回值
  • 24表示總共佔用的字節數
  • @表示第一個參數id的類型是一個對象
  • 0表示從0開始,佔8個字節
  • : 表示表明SEL第二個參數 從8開始,佔8個字節
  • @第三個@表明set方法到參數類型, 從16開始, 佔8個,

一共是24個字節, 就是v24後邊的總字節數

更多類型能夠對應官網上到這個表。

0x03 - 屬性的attribute

方法有編碼類型, 屬性也有編碼類型

{{"sex","T@\"NSString\",&,N,V_sex"},
	{"other","T@\"NSString\",C,N,V_other"}}
複製代碼
  • T表示類型
  • @表示變量類型
  • &表示引用類型
  • N表示是nonatomic
  • V表示variable變量,也就是下劃線的變量_sex
  • c表示copy

參考地址Property Type and Functions

歡迎大佬留言指正😄,碼字不易,以爲好給個贊👍 有任何表達或者理解失誤請留言交流;共同進步;

相關文章
相關標籤/搜索