如何通過分類來給對(duì)象添加屬性

前言

我們都知道 category 可以為某個(gè)類來添加方法虱朵,但是是否可以添加屬性呢?如何添加钓账?

category 分析

category 可以為我們重寫類的方法碴犬,但是我們?nèi)绻ㄟ^他來添加屬性,會(huì)發(fā)生什么

首先梆暮,我們創(chuàng)建了一個(gè) Person 類服协。當(dāng)前此類只有一個(gè)屬性 name,而當(dāng)我們聲明一個(gè)屬性的時(shí)候啦粹,實(shí)際上是生成了一個(gè)成員變量和它的 set get 方法偿荷,這樣我們?cè)谑褂玫臅r(shí)候才能正常的拿到對(duì)應(yīng)的值窘游。

image.png

那么當(dāng)我們?cè)?category 里聲明這個(gè)屬性的時(shí)候,能否同樣給我們生成這些內(nèi)容呢跳纳?

我們創(chuàng)建一個(gè) Person 的 category 并為它添加一個(gè)屬性 age 忍饰, 然后我們來為這個(gè) age 賦值。

image.png
image.png

可以發(fā)現(xiàn)我們的程序崩潰了棒旗,崩潰堆棧信息如下:

2021-09-14 16:40:31.808475+0800 CategroyTest[40231:1531935] Hello, World!
2021-09-14 16:40:31.811430+0800 CategroyTest[40231:1531935] -[Person setAge:]: unrecognized selector sent to instance 0x10063cc60
2021-09-14 16:40:31.814593+0800 CategroyTest[40231:1531935] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[Person setAge:]: unrecognized selector sent to instance 0x10063cc60'
*** First throw call stack:
(
    0   CoreFoundation                      0x00007fff2066a83b __exceptionPreprocess + 242
    1   libobjc.A.dylib                     0x00007fff203a2d92 objc_exception_throw + 48
    2   CoreFoundation                      0x00007fff206ed34d -[NSObject(NSObject) __retain_OA] + 0
    3   CoreFoundation                      0x00007fff205d28cb ___forwarding___ + 1448
    4   CoreFoundation                      0x00007fff205d2298 _CF_forwarding_prep_0 + 120
    5   CategroyTest                        0x0000000100003e7c main + 92
    6   libdyld.dylib                       0x00007fff20512f3d start + 1
)
libc++abi: terminating with uncaught exception of type NSException
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[Person setAge:]: unrecognized selector sent to instance 0x10063cc60'
terminating with uncaught exception of type NSException

使用 .age 時(shí)崩潰如下

2021-09-14 16:44:33.699452+0800 CategroyTest[40267:1535013] Hello, World!
2021-09-14 16:44:33.699984+0800 CategroyTest[40267:1535013] -[Person age]: unrecognized selector sent to instance 0x10482c1c0
2021-09-14 16:44:33.700674+0800 CategroyTest[40267:1535013] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[Person age]: unrecognized selector sent to instance 0x10482c1c0'
*** First throw call stack:
(
    0   CoreFoundation                      0x00007fff2066a83b __exceptionPreprocess + 242
    1   libobjc.A.dylib                     0x00007fff203a2d92 objc_exception_throw + 48
    2   CoreFoundation                      0x00007fff206ed34d -[NSObject(NSObject) __retain_OA] + 0
    3   CoreFoundation                      0x00007fff205d28cb ___forwarding___ + 1448
    4   CoreFoundation                      0x00007fff205d2298 _CF_forwarding_prep_0 + 120
    5   CategroyTest                        0x0000000100003e77 main + 87
    6   libdyld.dylib                       0x00007fff20512f3d start + 1
)
libc++abi: terminating with uncaught exception of type NSException
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[Person age]: unrecognized selector sent to instance 0x10482c1c0'
terminating with uncaught exception of type NSException

通過堆棧信息我們可以發(fā)現(xiàn)喘批,我們雖然可以使用這個(gè)屬性撩荣,但實(shí)際上這個(gè)屬性并沒有生成對(duì)應(yīng)的 set get 方法, 所以會(huì)產(chǎn)生崩潰铣揉。

如何正確的給類添加屬性

這個(gè)時(shí)候我們就需要使用到 runtime api 為我們提供的一套機(jī)制

/** 
 * Sets an associated value for a given object using a given key and association policy.
 * 
 * @param object The source object for the association.
 * @param key The key for the association.
 * @param value The value to associate with the key key for object. Pass nil to clear an existing association.
 * @param policy The policy for the association. For possible values, see “Associative Object Behaviors.”
 * 
 * @see objc_setAssociatedObject
 * @see objc_removeAssociatedObjects
 */
OBJC_EXPORT void
objc_setAssociatedObject(id _Nonnull object, const void * _Nonnull key,
                         id _Nullable value, objc_AssociationPolicy policy)
    OBJC_AVAILABLE(10.6, 3.1, 9.0, 1.0, 2.0);

/** 
 * Returns the value associated with a given object for a given key.
 * 
 * @param object The source object for the association.
 * @param key The key for the association.
 * 
 * @return The value associated with the key \e key for \e object.
 * 
 * @see objc_setAssociatedObject
 */
OBJC_EXPORT id _Nullable
objc_getAssociatedObject(id _Nonnull object, const void * _Nonnull key)
    OBJC_AVAILABLE(10.6, 3.1, 9.0, 1.0, 2.0);

/** 
 * Removes all associations for a given object.
 * 
 * @param object An object that maintains associated objects.
 * 
 * @note The main purpose of this function is to make it easy to return an object 
 *  to a "pristine state”. You should not use this function for general removal of
 *  associations from objects, since it also removes associations that other clients
 *  may have added to the object. Typically you should use \c objc_setAssociatedObject 
 *  with a nil value to clear an association.
 * 
 * @see objc_setAssociatedObject
 * @see objc_getAssociatedObject
 */
OBJC_EXPORT void
objc_removeAssociatedObjects(id _Nonnull object)
    OBJC_AVAILABLE(10.6, 3.1, 9.0, 1.0, 2.0);

objc_setAssociatedObject

objc_setAssociatedObject(<#id  _Nonnull object#>, <#const void * _Nonnull key#>, <#id  _Nullable value#>, <#objc_AssociationPolicy policy#>)

這個(gè)方法是添加關(guān)聯(lián)對(duì)象。

參數(shù)一:指我們?yōu)檎l(shuí)來添加關(guān)聯(lián)對(duì)象餐曹,因?yàn)槲覀兪窃?category 中寫的逛拱,是為 person 添加對(duì)象,所以此處直接寫 self 即可台猴。
參數(shù)二:指的是我們存儲(chǔ)值得時(shí)候?qū)?yīng)的 Key 是什么朽合,當(dāng)我們需要添加多個(gè)屬性時(shí),需要用 key 來做區(qū)分饱狂,所以我們要保證 key 值唯一曹步,這樣添加多個(gè)屬性才能區(qū)分我們?nèi)〉玫哪膫€(gè)屬性的值。我們可以通過定義變量取其地址來定義

static const void* kAge = &kAge;
或者
static const char kAge;  // 使用的時(shí)候直接使用 &kAge, char 相比地址更節(jié)省內(nèi)存休讳。

參數(shù)三:指的是我們需要關(guān)聯(lián)的值是哪個(gè)讲婚?當(dāng)前我們?yōu)?age 添加,所以此處寫 age 即可俊柔,此處需要使用對(duì)象筹麸,所以我們使用 @(age);
參數(shù)四:指的是關(guān)聯(lián)策略,其實(shí)就是我們對(duì)象寫屬性時(shí)的內(nèi)存管理策略雏婶,此處我們用 OBJC_ASSOCIATION_ASSIGN

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. */
};

完整代碼如下:

Person+Test.h


#import "Person.h"

NS_ASSUME_NONNULL_BEGIN

@interface Person (Test)

@property (nonatomic,assign) int age;

@end

NS_ASSUME_NONNULL_END

Person+Test.m

#import "Person+Test.h"
#import <objc/runtime.h>

const void* kAge = &kAge;
@implementation Person (Test)

- (void)setAge:(int)age{
    objc_setAssociatedObject(self, kAge, @(age), OBJC_ASSOCIATION_ASSIGN);
}

- (int)age{
    return [objc_getAssociatedObject(self, kAge) intValue];
}
@end

這個(gè)時(shí)候可以看出我們已經(jīng)可以正常運(yùn)行了

image.png

原理解讀

Q:通過 category 存儲(chǔ)的內(nèi)容是否會(huì)存儲(chǔ)到原有對(duì)象的數(shù)據(jù)結(jié)構(gòu)中物赶?如果存儲(chǔ)的?
A:不會(huì)影響原來結(jié)構(gòu)

通過查看 runtime 源碼可以發(fā)現(xiàn)留晚,重要的類和數(shù)據(jù)結(jié)構(gòu)有下面幾個(gè)

AssociationsManager
AssocaitionsHashMap
AssociationsMap
ObjectAssociation

通過 AssociationsManager 來管理 AssocaitionsHashMap酵紫,
而 AssocaitionsHashMap 中則以 object 為 key,AssociationsMap 為 Value错维,
AssociationsMap 則以我們傳入的 key 為 key憨闰, ObjectAssociation 為 value,
ObjectAssociation 中則存儲(chǔ)了我們傳入的 value 和內(nèi)存管理規(guī)則需五。

其他

objc_removeAssociatedObjects 會(huì)移除所有關(guān)聯(lián)對(duì)象鹉动,如果只需要單獨(dú)移除某個(gè)只要在 objc_setAssociatedObject 時(shí) value 為 nil 即可。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末宏邮,一起剝皮案震驚了整個(gè)濱河市泽示,隨后出現(xiàn)的幾起案子缸血,更是在濱河造成了極大的恐慌,老刑警劉巖械筛,帶你破解...
    沈念sama閱讀 218,607評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件捎泻,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡埋哟,警方通過查閱死者的電腦和手機(jī)笆豁,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,239評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來赤赊,“玉大人闯狱,你說我怎么就攤上這事∨准疲” “怎么了哄孤?”我有些...
    開封第一講書人閱讀 164,960評(píng)論 0 355
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)吹截。 經(jīng)常有香客問我瘦陈,道長(zhǎng),這世上最難降的妖魔是什么波俄? 我笑而不...
    開封第一講書人閱讀 58,750評(píng)論 1 294
  • 正文 為了忘掉前任晨逝,我火速辦了婚禮,結(jié)果婚禮上懦铺,老公的妹妹穿的比我還像新娘捉貌。我一直安慰自己,他們只是感情好阀趴,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,764評(píng)論 6 392
  • 文/花漫 我一把揭開白布昏翰。 她就那樣靜靜地躺著,像睡著了一般刘急。 火紅的嫁衣襯著肌膚如雪棚菊。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,604評(píng)論 1 305
  • 那天叔汁,我揣著相機(jī)與錄音统求,去河邊找鬼。 笑死据块,一個(gè)胖子當(dāng)著我的面吹牛码邻,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播另假,決...
    沈念sama閱讀 40,347評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼像屋,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了边篮?” 一聲冷哼從身側(cè)響起己莺,我...
    開封第一講書人閱讀 39,253評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤奏甫,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后凌受,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體阵子,經(jīng)...
    沈念sama閱讀 45,702評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,893評(píng)論 3 336
  • 正文 我和宋清朗相戀三年胜蛉,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了挠进。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,015評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡誊册,死狀恐怖领突,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情解虱,我是刑警寧澤攘须,帶...
    沈念sama閱讀 35,734評(píng)論 5 346
  • 正文 年R本政府宣布漆撞,位于F島的核電站殴泰,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏浮驳。R本人自食惡果不足惜悍汛,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,352評(píng)論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望至会。 院中可真熱鬧离咐,春花似錦、人聲如沸奉件。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,934評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)县貌。三九已至术陶,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間煤痕,已是汗流浹背梧宫。 一陣腳步聲響...
    開封第一講書人閱讀 33,052評(píng)論 1 270
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留摆碉,地道東北人塘匣。 一個(gè)月前我還...
    沈念sama閱讀 48,216評(píng)論 3 371
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像巷帝,于是被迫代替她去往敵國(guó)和親忌卤。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,969評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容