在 protocol 中使用 property 只會生成 setter 和 getter 方法聲明琼讽,我們使用屬性的目的痢站,是希望遵守我協(xié)議的對象能實(shí)現(xiàn)該屬性。在實(shí)現(xiàn) protocol 的類中如果要使用 property 對應(yīng)的實(shí)例變量,則需要做一下 @synthesize var = _var;齿穗。
在 category 中增加屬性的目的主要為了解耦,在很多第三方框架中會使用饺律。在 category 中使用 @property 只會生成 setter 和 getter 方法的聲明窃页,并不會自動生成實(shí)例變量以及存取方法,Xcode 會警告需要手動實(shí)現(xiàn) setter 和 getter 方法。這是因?yàn)?category 它是在運(yùn)行時決定的脖卖。在編譯時乒省,對象的內(nèi)存布局已經(jīng)確定,如果添加實(shí)例變量就會破壞類的內(nèi)部布局畦木,這對編譯型語言來說是災(zāi)難性的袖扛。所以一般使用 runtime 中的關(guān)聯(lián)對象為已經(jīng)存在的類添加屬性。關(guān)聯(lián)對象類似于成員變量十籍,不過是在運(yùn)行時添加的蛆封。在 runtime 中所有的關(guān)聯(lián)對象都由 AssociationsManager 管理。AssociationsManager 里面是由一個靜態(tài) AssociationsHashMap 來存儲所有的關(guān)聯(lián)對象的勾栗。這相當(dāng)于把所有對象的關(guān)聯(lián)對象都存在一個全局 map 里面惨篱。而 map 的 key 是這個對象的指針地址(任意兩個不同對象的指針地址一定是不同的),而這個 map 的 value 又是另外一個 AssociationsHashMap围俘,里面保存了關(guān)聯(lián)對象的 KV 對砸讳。runtime 的銷毀對象函數(shù) objc_destructInstance里面會判斷這個對象有沒有關(guān)聯(lián)對象,如果有楷拳,會調(diào)用 _object_remove_assocations 做關(guān)聯(lián)對象的清理工作绣夺。
如果我們真的需要給 category 增加屬性的實(shí)現(xiàn),需要借助于運(yùn)行時的兩個函數(shù):
設(shè)置關(guān)聯(lián)對象
objc_setAssociatedObject(<#id object#>, <#const void *key#>, <#id value#>, <#objc_AssociationPolicy policy#>)
1.第一個參數(shù): id object : 需要傳入的是 : 對象的主分支
2.第二個參數(shù): const void *key : 是一個 static 類型的 關(guān)鍵字,這里根據(jù)開發(fā)者自身來定義就行(盡量寫的有根據(jù)一點(diǎn),避免以后忘記是干啥用的)
3.第三個參數(shù): id value : 傳入的是: 對象的子分支
4.第四個參數(shù): objc_AssociationPolicy policy :是當(dāng)前關(guān)聯(lián)對象的類型 strong,weak,copy (枚舉類型:開發(fā)者可以點(diǎn)進(jìn)去看)
①主分支:如果將一個label控件和控制器關(guān)聯(lián)上,而且放在控制器上面,那么這個控制器對象 self 或者 self.view 就充當(dāng)主分支
②子分支 : 那么這個label 就充當(dāng) 子分支
③如果 控制器上有兩個控件, 一個Label, 一個 View, 那么想這兩個控件 弄上關(guān)聯(lián)關(guān)系, 這兩個控件隨意一個做主分支,一個做子分支(這個根據(jù)開發(fā)場景而定)
獲取關(guān)聯(lián)對象
objc_getAssociatedObject(<#id object#>, <#const void *key#>)就相對來說容易理解一點(diǎn)了
1.第一個參數(shù) : 主分支
2.第二個參數(shù) : 關(guān)鍵字
示例:
// MyView+MyCategory.h
#import "MyView.h"
@interface MyView (MyCategory)
// 在 Category 中定義屬性:
@property (assign, nonatomic) int32_t viewIndex;
@end
// MyView+MyCategory.m
#import "MyView+MyCategory.h"
#import <objc/runtime.h>
// 標(biāo)記屬性的 Key:
static const void *ViewIndexKey = &ViewIndexKey;
@implementation MyView (MyCategory)
@dynamic viewIndex;
- (void)setViewIndex:(int32_t)viewIndex {
objc_setAssociatedObject(self, ViewIndexKey, @(viewIndex), OBJC_ASSOCIATION_ASSIGN);
}
- (int32_t)viewIndex {
return [objc_getAssociatedObject(self, ViewIndexKey) intValue];
}
@end
另外
// 釋放關(guān)聯(lián)對象
// 第三個參數(shù), 設(shè)為 nil, 則將 self 與 nil 關(guān)聯(lián)... 也等同于 : 沒關(guān)聯(lián)任何對象
objc_setAssociatedObject(self, &overView, nil, OBJC_ASSOCIATION_ASSIGN);
// 移除 所有關(guān)聯(lián)對象 : 這個方法 相當(dāng)于 初始化 arr 對象一樣(并不是初始化arr這個指針?biāo)赶虻膬?nèi)存地址)
objc_removeAssociatedObjects(self);
擴(kuò)展一下:到別人代碼塊里頭會用到 _cmd 這個關(guān)鍵字
此關(guān)鍵字代表的是什么?
- (UILabel *)textLabel{
return objc_getAssociatedObject(self, _cmd);
}
上面代碼 直接通過 objc_getAssociatedObject
方法獲取到 textLabel 關(guān)聯(lián)的關(guān)鍵字, 或者 對象本身 或者 Bool 等等
而關(guān)鍵在于下面一個句代碼:
- (void)setTextLabel:(UILabel *)textLabel{
objc_setAssociatedObject(self, @selector(textLabel), textLabel, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
_cmd
:第一句返回的時候,相當(dāng)于返回一個 id對象 ,而關(guān)鍵字則是 : textLabel ,它是根據(jù)當(dāng)前方法,直接使用方法的名稱欢揖,即 - (***)textLabel 方法陶耍,并且能保證改名稱不重復(fù).(第一句代碼方法名 : textLabel ,因?yàn)橐粋€類中不可能用相同的方法名)
第二句代碼中出現(xiàn) @selector(textLabel)
,這里它就調(diào)用第一句代碼了, @selector
直接返回SEL,則獲取到textLabel的方法名,則為第一句代碼返回的值.
1:上面兩個方法 寫在一個 單獨(dú)的View中,在這個類的 .h中,創(chuàng)建一個 weak(若引用)的textLabel,而在.m文件中 initWithFrame中直接創(chuàng)建self.textLabel = [[UILabel alloc] initWithFrame:self.bounds];并添加到這個view上
2:那么按照常理來說, 這里應(yīng)該報(bào)警告, 但是 在控制器中打印 這個類的 textLabel , 你會發(fā)現(xiàn)是有內(nèi)存的, 如果把第一句和第二句代碼注釋掉, 打印則沒有內(nèi)存.