OC語法相關(guān)
1煮纵、一般什么情況下用分類
- 用分類可以聲明私有屬性行疏,但是一般情況下不會用分類(category),而是用類擴(kuò)張(extention)
- 可以用分類拆解體積龐大的類酿联,比如常把AppDelegate 利用分類拆分成推送贞让,分享,登錄等不同功能模塊的分類
- 可以用于Hook第三方庫的方法续镇,如果你想不修改別人的代碼销部,但是需要修改他們提供的方法,完全可以為需要修改的方法所在類編寫一個分類之后hook酱虎。
2江咳、分類和類擴(kuò)張的區(qū)別
- extension相當(dāng)于一個頭文件而已,extension(類擴(kuò)張)只能編寫方法聲明爹土,所編寫的方法和屬性都需要在.m文件實現(xiàn)踩身。如果你在extension中書寫了屬性(property)胀茵,那么必需要把extension文件import到.m文件中挟阻,讓系統(tǒng)幫你生成setter和getter方法附鸽,或者導(dǎo)入之后自己手動生成setter方法和getter方法。
- 分類(category)有自己的數(shù)據(jù)結(jié)構(gòu)objc_category熄浓,可以編寫方法和關(guān)聯(lián)屬性省撑。分類編寫的方法會在runtime的時候俯在,調(diào)用remethodizeClass 把方法整合到類對象中娃惯。
- 如果類A有兩個分類并且有同名的分類方法趾浅,那么后編譯的分類的方法會插入到類對象方法列表的前面。之后如果調(diào)用該方法揪荣,會出發(fā)msg_Send方法查找往史,后編譯的分類同名方法會被優(yōu)先查找到佛舱。所以會產(chǎn)生類似后編譯分類的同名方法覆蓋其他分類的同名方法現(xiàn)象请祖。
- 分類的+load方法會在類的+load方法后調(diào)用。分類的+load方法調(diào)用順序和編譯順序有關(guān)
- 父類的+load方法調(diào)用之后再調(diào)用子類的+load方法刷晋,父類和子類的+load方法調(diào)用完之后再調(diào)用分類的+load方法(調(diào)用順序只和編譯順序有關(guān))
- initialize方法會在類第一次接受到消息的時候調(diào)用慎陵,調(diào)用順序是父類->子類。如果分類有重寫initialize方法捏悬,則會覆蓋父類的initialize润梯。如果多個分類重寫了initialize方法纺铭,那么就會調(diào)用最后編譯分類的initialize方法。
- 利用關(guān)聯(lián)對象實現(xiàn)的setter和getter方法扫倡,可以被KVO顿痪。
繼承關(guān)系 BeautyCat -> Cat - > Animal()
類和分類 Animal油够,Animal+bb石咬,Animal+aa卖哎,Cat,Cat+bb焕窝,Cat+aa维贺,BeautyCat溯泣,BeautyCat+aa
測試結(jié)果:
/*------------------------------------------------------*
2021-04-15 11:17:46.141459+0800 cccExtention[40555:7540175] +[Animal load]
2021-04-15 11:17:46.159957+0800 cccExtention[40555:7540175] +[Cat load]
2021-04-15 11:17:46.161076+0800 cccExtention[40555:7540175] +[BeautyCat load]
2021-04-15 11:17:46.161541+0800 cccExtention[40555:7540175] +[BeautyCat(aa) load]
2021-04-15 11:17:46.163238+0800 cccExtention[40555:7540175] +[Animal(aa) load]
2021-04-15 11:17:46.163901+0800 cccExtention[40555:7540175] +[Animal(bb) load]
2021-04-15 11:17:46.164747+0800 cccExtention[40555:7540175] +[Cat(aa) load]
2021-04-15 11:17:46.166138+0800 cccExtention[40555:7540175] +[Cat(bb) load]
2021-04-15 11:17:46.252135+0800 cccExtention[40555:7540175] +[Animal(bb) initialize]
2021-04-15 11:17:46.252318+0800 cccExtention[40555:7540175] +[Cat(bb) initialize]
2021-04-15 11:17:46.253647+0800 cccExtention[40555:7540175] +[BeautyCat(aa) initialize]
疑問
- category分類的屬性是存儲在管理對象上的垃沦,那么為什么在attachCategories方法中還需要把property_list_t整合到類中呢?
- 在obj4-781 版本中靶剑,attachCategories方法對一個類的分類數(shù)量做了限制(64個)池充,如果多出64個會怎么處理?
- xcode 14 如何編譯運(yùn)行obj4-818.2?
3阐污、關(guān)聯(lián)對象
- 系統(tǒng)維護(hù)了一個全局 AssociationsManager專門用于管理所有關(guān)聯(lián)對象
- 通過類指針(利用哈希查找)可以快速在AssociationsManager中的AssociationHashMap找到該類的關(guān)聯(lián)對象存儲結(jié)構(gòu)ObjectAssociationMap咱圆。
- ObjectAssociationMap 的key就是我們調(diào)用objc_setAssociatedObject 設(shè)置的key序苏,value 是一個ObjcAssociation 對象
- ObjcAssociation對象包含關(guān)聯(lián)屬性的值和內(nèi)存管理方案
- 可以通過把objc_setAssociatedObject方法的參數(shù)value設(shè)置為nil,刪除某個關(guān)聯(lián)對象值.
ObjcAssociation對象 在runtime源碼的定義
class ObjcAssociation {
uintptr_t _policy; // 內(nèi)存管理策略()
id _value; // 存儲的值
};
policy 可以取的值
typedef OBJC_ENUM(uintptr_t, objc_AssociationPolicy) {
OBJC_ASSOCIATION_ASSIGN = 0, /**< Specifies a weak reference to the associated object. */
OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1, /**< Specifies a strong reference to the associated object.
* The association is not made atomically. */
OBJC_ASSOCIATION_COPY_NONATOMIC = 3, /**< Specifies that the associated object is copied.
* The association is not made atomically. */
OBJC_ASSOCIATION_RETAIN = 01401, /**< Specifies a strong reference to the associated object.
* The association is made atomically. */
OBJC_ASSOCIATION_COPY = 01403 /**< Specifies that the associated object is copied.
* The association is made atomically. */
};
示例代碼
-(void)setVersion:(NSString * _Nonnull)version
{
objc_setAssociatedObject(self, @selector(version), version, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
-(NSString *)version
{
NSString *ver = objc_getAssociatedObject(self, _cmd);
return ver;
}
4、KVO
- KVO 是key-value observing的縮寫。
- KVO是 objective-c 對觀察者模式的實現(xiàn)桶错。
- 如果一個實例對象ABC添加了KVO胀蛮,那么系統(tǒng)在運(yùn)行時會為這個對象生成一個中間類——NSKVONotifying_ABC粪狼,之后把實例對象ABC的isa指針指向NSKVONotifying_ABC,NSKVONotifying_ABC的superClass指針指向原本類對象狡刘。所以當(dāng)調(diào)用set方法的時候困鸥,會先調(diào)用NSKVONotifying_ABC的set方法。在NSKVONotifying_ABC 的set方法中會調(diào)用willChangeValueForKey 和didChangeValueForKey兩個方法购城。具體實現(xiàn)如下
-(void)setName:(NSString *)name
{
[self willChangeValueForKey:@"name"];
[super setName:name]; //原來的setter方法
[self didChangeValueForKey:@"name"]; // 調(diào)用observeValueForKeyPath 方法
}
示例代碼
//添加監(jiān)測網(wǎng)頁加載進(jìn)度的觀察者
[self.webView addObserver:self
forKeyPath:NSStringFromSelector(@selector(estimatedProgress))
options:0
context:nil];
-(void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary<NSKeyValueChangeKey,id> *)change
context:(void *)context{
if ([keyPath isEqualToString:NSStringFromSelector(@selector(estimatedProgress))]
&& object == _webView) {
NSLog(@"網(wǎng)頁加載進(jìn)度 = %f",_webView.estimatedProgress);
}
}
5、KVC
流程圖
- +(BOOL)accessInstanceVariablesDirectly 返回YES漆诽,告訴系統(tǒng)尋找相似的key锣枝,返回NO告訴系統(tǒng)不找相應(yīng)的key,默認(rèn)返回YES,valueForKey方法和setValueForKey方法當(dāng)沒找到key都會導(dǎo)致崩潰
6供鸠、isa走位圖
7楞捂、 屬性關(guān)鍵字有哪些趋厉?可以分為幾類?
- 屬性關(guān)鍵大體上可以分為三類——讀寫相關(guān)繁堡,引用計數(shù)器相關(guān),原子性操作相關(guān)闻牡。
讀寫相關(guān)
- readonly绳矩,表示這個屬性在外部緊緊可讀
- readwrite埋酬,表示這個屬性在外部可讀可寫
- 如果你要申明一個屬性對外部可讀,對內(nèi)部可讀可寫拳球,那么可以在.h文件用readonly珍特,之后再.m文件再用readwrite
引用計數(shù)器內(nèi)存管理相關(guān)
- copy屬性關(guān)鍵字常用于修飾不可變對象,比如NSString對象和NSArray對象莱找。不可以對象的copy操作屬于淺拷貝嗜桌,所以不可變對象用strong修飾也是沒問題的骨宠。So,用retain也一樣桦卒。
- retain 強(qiáng)引用持有對象匿又,在ARC環(huán)境下,用strong取代retain裕偿。
- strong 強(qiáng)引用持有對象痛单,會讓對象的引用計數(shù)器+1
- weak 弱引用桦他,不會導(dǎo)致對象的引用計數(shù)器增加谆棱,并且對象釋放的時候圆仔,會把指針置為nil坪郭。
- assign 一般來說,用于修飾基本數(shù)據(jù)結(jié)構(gòu)嗦锐,在MRC環(huán)境下也可以用于修飾對象沪曙,但是不會導(dǎo)致對象的引用計數(shù)器增加,所以類似 unsafe_unretain碳默,是不安全的缘眶。
- unsafe_unretain MRC環(huán)境下的屬性關(guān)鍵字巷懈,引用對象,但是不會導(dǎo)致對象引用的計算器+1凑保,也就是普通的指針(對比C++的智能指針)割岛。
線程安全相關(guān)
- nonatomic犯助,非原子操作剂买,默認(rèn)也是這個屬性。所以默認(rèn)情況下婚肆,屬性讀寫是沒有線程安全的坐慰。
- atomic,原子操作赞咙,被這個關(guān)鍵字修飾的屬性攀操,讀寫是線程安全。但是歹垫,如何是容器類對象颠放,那么修改容器類對象的內(nèi)容依然是線程不安全的,所以最好還是自己加鎖實現(xiàn)線程安全代碼若贮,而不是用atomic谴麦。
8伸头、objc源碼常見的數(shù)據(jù)結(jié)構(gòu)
isa_t
isa_t 是一個聯(lián)合體,部分源碼如下
union isa_t
{
isa_t() { }
isa_t(uintptr_t value) : bits(value) { }
Class cls;
uintptr_t bits;
#if SUPPORT_PACKED_ISA
# if __arm64__
# define ISA_MASK 0x0000000ffffffff8ULL
# define ISA_MAGIC_MASK 0x000003f000000001ULL
# define ISA_MAGIC_VALUE 0x000001a000000001ULL
struct {
uintptr_t nonpointer : 1;
uintptr_t has_assoc : 1;
uintptr_t has_cxx_dtor : 1;
uintptr_t shiftcls : 33; // MACH_VM_MAX_ADDRESS 0x1000000000
uintptr_t magic : 6;
uintptr_t weakly_referenced : 1;
uintptr_t deallocating : 1;
uintptr_t has_sidetable_rc : 1;
uintptr_t extra_rc : 19;
# define RC_ONE (1ULL<<45)
# define RC_HALF (1ULL<<18)
};
};
可以看出isa_t 只有兩個數(shù)據(jù)成員面哼,一個是指針類型Class cls魔策,一個是unsigned long類型uintptr_t bits河胎,所以整個聯(lián)合體的內(nèi)存只有64位游岳。
- nonpointer 表示是否對isa開啟指針優(yōu)化 。0代表是純isa指針喷户,1代表除了地址外访锻,還包含了類的一些信息闹获、對象的引用計數(shù)等昌罩。
- has_assoc 表示是否存在關(guān)聯(lián)對象灾馒。
- has_cxx_dtor:該對象是否有C++或Objc的析構(gòu)器睬罗,如果有析構(gòu)函數(shù),則需要做一些析構(gòu)的邏輯處理古涧,如果沒有花盐,則可以更快的釋放對象。
- shiftcls 存在類指針的值柒昏,開啟指針優(yōu)化的情況下职祷,arm64位中有33位來存儲類的指針
- magic:判斷當(dāng)前對象是真的對象還是一段沒有初始化的空間(有待研究)
- weakly_referenced:是否被指向或者曾經(jīng)指向一個ARC的弱變量届囚,沒有弱引用的對象釋放的更快
- deallocating:是否正在釋放
- has_sidetable_rc:當(dāng)對象引用計數(shù)大于10時意系,則需要進(jìn)位
- extra_rc:表示該對象的引用計數(shù)值,實際上是引用計數(shù)減一痰催。例如:如果引用計數(shù)為10作郭,那么extra_rc為9弦疮。如果引用計數(shù)大于10胁塞,則需要使用has_sidetable_rc
objc_object 結(jié)構(gòu)體
struct objc_object {
private:
isa_t isa;
public:
.....
}
可以看到objc_object在源碼中只有一個私有成員變量isa压语,類型是isa_t胎食,這個結(jié)構(gòu)類型是一個聯(lián)合體允懂,里面只包含64位數(shù)據(jù)蕾总,但是會根據(jù)不同的情況,64位數(shù)據(jù)表示方式有所區(qū)別递雀。
接下來就是這個結(jié)構(gòu)體提供給外部使用的方法
//部分objc_object 結(jié)構(gòu)體中的方法如下
//初始化方法
void initIsa(Class cls /*nonpointer=false*/);
void initClassIsa(Class cls /*nonpointer=maybe*/);
void initProtocolIsa(Class cls /*nonpointer=maybe*/);
void initInstanceIsa(Class cls, bool hasCxxDtor);
//isa 類型判斷方法
bool hasNonpointerIsa();
bool isTaggedPointer();
bool isBasicTaggedPointer();
bool isExtTaggedPointer();
//內(nèi)存管理相關(guān)方法
id retain();
void release();
id autorelease();
Class 類型
Class 類型在runtime源碼定義為
typedef struct objc_class *Class;
objc_class 結(jié)構(gòu)體
objc_class 繼承自 objc_object蚀浆,在runtime的部分代碼如下
// objc_class繼承于objc_object,因此
// objc_class中也有isa結(jié)構(gòu)體
struct objc_class : objc_object {
Class superclass;
cache_t cache; // formerly cache pointer and vtable
class_data_bits_t bits; // class_rw_t * plus custom rr/alloc flags
class_rw_t *data() {
return bits.data();
}
void setData(class_rw_t *newData) {
bits.setData(newData);
}
......
bool isARC() {
return data()->ro->flags & RO_IS_ARC;
}
......
}
objc_class 結(jié)構(gòu)體其實就是我們平時所說的類對象市俊,有指向父類的isa指針(superclass)秕衙,有方法緩存cache_t,有類的方法鹦牛,屬性勇吊,協(xié)議(保存在class_rw_t中)等信息汉规。
cache_t 結(jié)構(gòu)體
先看看cache_t 結(jié)構(gòu)體的源碼
struct cache_t {
struct bucket_t *_buckets; //散列表
mask_t _mask; //散列表的長度 -1
mask_t _occupied; //已經(jīng)緩存的數(shù)量
......
}
bucket_t 在arm64的定義為
struct bucket_t {
private:
// IMP-first is better for arm64e ptrauth and no worse for arm64.
// SEL-first is better for armv7* and i386 and x86_64.
#if __arm64__
MethodCacheIMP _imp;
cache_key_t _key;
#else
可以看到cache_t 是利用散列表來緩存bucket_t對象,而bucket_t保存了緩存key和方法調(diào)用的 IMP指針
class_data_bits_t 結(jié)構(gòu)體
struct class_data_bits_t {
// Values are the FAST_ flags above.
uintptr_t bits;
......
}
- 相當(dāng)于 unsigned long bits; 占64位 bits實際上是一個地址(是一個對象的指針晶伦,可以指向class_ro_t婚陪,也可以指向class_rw_t)频祝,其實是對class_rw_t 結(jié)構(gòu)體的一個簡單封裝,提供一些操作class_rw_t的方法
- 為什么要這個設(shè)計沽一,有待驗證铣缠??
class_rw_t 結(jié)構(gòu)體
struct class_rw_t {
// Be warned that Symbolication knows the layout of this structure.
uint32_t flags;
uint32_t version;
const class_ro_t *ro;
// 方法信息
method_array_t methods;
// 屬性信息
property_array_t properties;
// 協(xié)議信息
protocol_array_t protocols;
......
}
可以看到class_rw_t 結(jié)構(gòu)體的rw(readwrite)表示可讀寫的意思拙友,這里保存了類的方法遗契,屬性病曾,協(xié)議等信息泰涂,class_ro_t 結(jié)構(gòu)體也保存了以上信息,但是他是不可讀寫的(ro,readonly)从绘,所以在運(yùn)行時會把分類的方法是牢,協(xié)議等信息動態(tài)添加到這里。
method_t 結(jié)構(gòu)體
struct method_t {
SEL name;
const char *types;
MethodListIMP imp;
struct SortBySELAddress :
public std::binary_function<const method_t&,
const method_t&, bool>
{
bool operator() (const method_t& lhs,
const method_t& rhs)
{ return lhs.name < rhs.name; }
};
};
method_t 有三個成員變量
- SEL name 也就是我們調(diào)用方法的時候批什,傳遞的選擇子selector
- const char *types; 方法的返回值和參數(shù)驻债,比如@v等字符串(類型編碼)
- MethodListIMP imp; 函數(shù)體(IMP)
property_t結(jié)構(gòu)體
struct property_t {
const char *name;
const char *attributes;
};
- name 屬性名字
- attributes 屬性修飾符(nonatomic,readonly,strong等)
9合呐、@dynamic和@synthesize
這兩個關(guān)鍵字都是和@property 相關(guān)的笙以,如果這兩個關(guān)鍵字都沒有寫,只寫了@property來聲明屬性翩伪,那么默認(rèn)就是@synthesize var = _var;
如果添加了@dynamic 關(guān)鍵字缘屹,那么就是告訴編譯器自己手動生成setter和getter方法(如果是readonly就只需生成getter)侠仇,如何你沒有生動生成逻炊,那么運(yùn)行時就不會報錯。
額外:
.m文件反編譯cpp文件的指令
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc -fobjc-arc -fobjc-runtime=ios-8.0.0 main.m
UI相關(guān)
1豹休、UIView和CALayer的關(guān)系
持續(xù)更新ing