不管你是iOS新手還是老鳥,property
這個(gè)東西是iOSer再熟悉不過的東西了砂蔽。而關(guān)于property
的相關(guān)知識(shí)點(diǎn)洼怔,諸如property = _ivar + set方法 + get方法
這些,網(wǎng)上相關(guān)文章很多左驾,也寫得很詳細(xì)镣隶,這里不會(huì)去做解釋。
這篇文章是我今晚在學(xué)習(xí)的時(shí)候诡右,發(fā)現(xiàn)的一個(gè)細(xì)節(jié)點(diǎn)安岂,而且還是自己很長一段時(shí)間以來犯的錯(cuò)誤,竟無從察覺...?? 所以記錄下來稻爬,給自己提個(gè)醒嗜闻,加深下印象。當(dāng)然桅锄,也希望能對(duì)跟我一樣慢知的童鞋,有所幫助样眠。????
拋磚引玉
先來看下面這個(gè)demo代碼
@interface Person: NSObject
@property (nonatomic, copy) NSString *name;
@property (nonatomic, strong) NSNumber *age;
@property (nonatomic, assign) float weight;
@end
@implementation Person
- (void)setName:(NSString *)name {
_name = name;
// do something
}
@end
回想一下友瘤,你平時(shí)重寫set方法的時(shí)候,是不是這樣寫檐束?這樣寫會(huì)有什么問題辫秧?(注意name的property attribute)
順藤摸瓜
我們都知道,編譯器會(huì)幫我們生成上面三個(gè)property對(duì)應(yīng)的實(shí)例變量被丧,以及三對(duì)set/get
方法盟戏,那么我們不妨來看看,系統(tǒng)為我們提供的默認(rèn)實(shí)現(xiàn)是怎樣的甥桂?
static NSString * _I_Person_name(Person * self, SEL _cmd) { return (*(NSString **)((char *)self + OBJC_IVAR_$_Person$_name)); }
extern "C" __declspec(dllimport) void objc_setProperty (id, SEL, long, id, bool, bool);
static void _I_Person_setName_(Person * self, SEL _cmd, NSString *name) { objc_setProperty (self, _cmd, __OFFSETOFIVAR__(struct Person, _name), (id)name, 0, 1); }
static NSNumber * _I_Person_eyes(Person * self, SEL _cmd) { return (*(NSNumber **)((char *)self + OBJC_IVAR_$_Person$_eyes)); }
static void _I_Person_setEyes_(Person * self, SEL _cmd, NSNumber *eyes) { (*(NSNumber **)((char *)self + OBJC_IVAR_$_Person$_eyes)) = eyes; }
static float _I_Person_weight(Person * self, SEL _cmd) { return (*(float *)((char *)self + OBJC_IVAR_$_Person$_weight)); }
static void _I_Person_setWeight_(Person * self, SEL _cmd, float weight) { (*(float *)((char *)self + OBJC_IVAR_$_Person$_weight)) = weight; }
這里稍作解釋下柿究,我們知道,通過alloc生成一個(gè)類的實(shí)例對(duì)象黄选,比如上面的Person
蝇摸,那么在內(nèi)存上就會(huì)分配一塊實(shí)例對(duì)象的內(nèi)存空間,里面存放著isa
以及三個(gè)ivar
的值办陷,而self
也就是這塊內(nèi)存空間的首地址貌夕;那么上面的OBJC_IVAR_$_Person$_name
就很好理解了,就是_name
相對(duì)于這塊內(nèi)存空間的偏移量民镜,其他的也是如此啡专;而__OFFSETOFIVAR__(struct Person, _name)
這個(gè)函數(shù)作用,其實(shí)求偏移量制圈;
extern "C" unsigned long int OBJC_IVAR_$_Person$_name __attribute__ ((used, section ("__DATA,__objc_ivar"))) = __OFFSETOFIVAR__(struct Person, _name);
可能細(xì)心的童鞋就會(huì)發(fā)現(xiàn)了们童,name
的set
方法實(shí)現(xiàn)辱揭,是跟其他兩個(gè)不一樣的。age
跟weight
很好理解病附,就是通過偏移量獲取到相應(yīng)內(nèi)存的地址问窃,然后直接把新的值設(shè)置進(jìn)去。但name的set方法里面卻是調(diào)用了一個(gè)叫objc_setProperty
的函數(shù)完沪,前面的幾個(gè)參數(shù)我們都很好理解跟猜到域庇,但后面的0跟1,又是什么意思覆积?
跳到該函數(shù)的申明听皿,我們才發(fā)現(xiàn),原來是代表atomic
跟 shouldCopy
OBJC_EXPORT void objc_setProperty(id self, SEL _cmd, ptrdiff_t offset, id newValue, BOOL atomic, signed char shouldCopy)OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0);
順帶提一下宽档,還有幾個(gè)跟它相似的函數(shù)尉姨,提供了參數(shù)默認(rèn)實(shí)現(xiàn)的版本
OBJC_EXPORT void objc_setProperty_atomic(id self, SEL _cmd, id newValue, ptrdiff_t offset) OBJC_AVAILABLE(10.8, 6.0, 9.0, 1.0);
OBJC_EXPORT void objc_setProperty_nonatomic(id self, SEL _cmd, id newValue, ptrdiff_t offset) OBJC_AVAILABLE(10.8, 6.0, 9.0, 1.0);
OBJC_EXPORT void objc_setProperty_atomic_copy(id self, SEL _cmd, id newValue, ptrdiff_t offset) OBJC_AVAILABLE(10.8, 6.0, 9.0, 1.0);
OBJC_EXPORT void objc_setProperty_nonatomic_copy(id self, SEL _cmd, id newValue, ptrdiff_t offset) OBJC_AVAILABLE(10.8, 6.0, 9.0, 1.0);
其實(shí)有點(diǎn)不懂,上面name的set方法實(shí)現(xiàn)吗冤,直接調(diào)用objc_setProperty_nonatomic_copy 不就好了又厉?
如果你去看它們的實(shí)現(xiàn),最終都是調(diào)用同一個(gè)函數(shù)實(shí)現(xiàn)椎瘟,完整實(shí)現(xiàn)如下:
static inline void reallySetProperty(id self, SEL _cmd, id newValue, ptrdiff_t offset, bool atomic, bool copy, bool mutableCopy)
{
if (offset == 0) { // 直接設(shè)置self
object_setClass(self, newValue);
return;
}
id oldValue;
id *slot = (id*) ((char*)self + offset); // 獲取到舊值
if (copy) { // copy attribute
newValue = [newValue copyWithZone:nil];
} else if (mutableCopy) {
newValue = [newValue mutableCopyWithZone:nil];
} else { // 除copy之外的其他attribute
if (*slot == newValue) return; // 如果新值跟舊值是同一個(gè)覆致,直接return
newValue = objc_retain(newValue);
}
if (!atomic) {
oldValue = *slot;
*slot = newValue;
} else { // 如果是atomic attribute,進(jìn)行加鎖
spinlock_t& slotlock = PropertyLocks[slot];
slotlock.lock();
oldValue = *slot;
*slot = newValue;
slotlock.unlock();
}
objc_release(oldValue); // 釋放舊值
}
來到這里肺蔚,結(jié)合前面我們看到name
的set
方法實(shí)現(xiàn)煌妈,當(dāng)我們?yōu)?code>name賦值新的值時(shí),系統(tǒng)就會(huì)copy
一份新值賦值給name
宣羊,最后釋放掉原先的舊值璧诵。這也是當(dāng)我們的property的設(shè)置copy
的attribute時(shí),才會(huì)出現(xiàn)的操作仇冯,其他諸如strong
assign
之類的之宿,都是直接把新值寫入相應(yīng)的內(nèi)存地址.
勿忘初心
那么回到一開始的問題,那樣重寫set方法會(huì)出現(xiàn)什么問題赞枕?相信不少童鞋也都能理解到了澈缺。
我們先來看下,那些寫炕婶,最終的實(shí)現(xiàn)是怎樣的姐赡?
static void _I_Person_setName_(Person * self, SEL _cmd, NSString *name) {
(*(NSString **)((char *)self + OBJC_IVAR_$_Person$_name)) = name;
}
很不幸,并沒有如我們所希望的柠掂,先copy
再賦值项滑,而是直接把值附給了_name
,這將會(huì)導(dǎo)致一個(gè)問題涯贞,也是我們用copy
的初衷想避免的問題
NSMutableString *name = [[NSMutableString alloc] initWithString:@"你為什么叫阿水枪狂?"];
Person *person = [[Person alloc] init];
person.name = name;
NSLog(@"前值 person.name: %@", person.name);
[name appendString:@"我不知道危喉,他們給我起的名"];
NSLog(@"后值 person.name: %@", person.name);
// 打印如下:
前值 person.name: 你為什么叫阿水?
后值 person.name: 你為什么叫阿水州疾?我不知道辜限,他們給我起的名
這樣會(huì)導(dǎo)致,外界傳進(jìn)來的那個(gè)值發(fā)生改變時(shí)严蓖,我們的name
也跟著變了薄嫡,而我們初衷就是要避免這種情況的發(fā)生。
所以颗胡,正確的寫法應(yīng)該是我們自己主動(dòng)去copy
一次
- (void)setName:(NSString *)name {
_name = [name copy];
}
好了毫深,到這里就結(jié)束了。
其實(shí)很簡單的一個(gè)知識(shí)點(diǎn)毒姨,啰啰嗦嗦硬是擼了這么多(別以為我不知道你是為了湊字?jǐn)?shù)的-. -)
哑蔫,其實(shí)也就是把我從發(fā)現(xiàn)問題到倒去驗(yàn)證的整一個(gè)過程記錄下來,畢竟好記性不如爛筆頭弧呐,寫下來以后忘了自己還能看看闸迷,哈哈??