重寫Property的set方法捌朴,跟我犯同樣錯(cuò)誤吴攒?

不管你是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)了们童,nameset方法實(shí)現(xiàn)辱揭,是跟其他兩個(gè)不一樣的。ageweight很好理解病附,就是通過偏移量獲取到相應(yīng)內(nèi)存的地址问窃,然后直接把新的值設(shè)置進(jìn)去。但name的set方法里面卻是調(diào)用了一個(gè)叫objc_setProperty的函數(shù)完沪,前面的幾個(gè)參數(shù)我們都很好理解跟猜到域庇,但后面的0跟1,又是什么意思覆积?
跳到該函數(shù)的申明听皿,我們才發(fā)現(xiàn),原來是代表atomicshouldCopy

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é)合前面我們看到nameset方法實(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è)過程記錄下來,畢竟好記性不如爛筆頭弧呐,寫下來以后忘了自己還能看看闸迷,哈哈??

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市泉懦,隨后出現(xiàn)的幾起案子稿黍,更是在濱河造成了極大的恐慌,老刑警劉巖崩哩,帶你破解...
    沈念sama閱讀 218,546評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異言沐,居然都是意外死亡邓嘹,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,224評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門险胰,熙熙樓的掌柜王于貴愁眉苦臉地迎上來汹押,“玉大人,你說我怎么就攤上這事起便∨锛郑” “怎么了?”我有些...
    開封第一講書人閱讀 164,911評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵榆综,是天一觀的道長妙痹。 經(jīng)常有香客問我,道長鼻疮,這世上最難降的妖魔是什么怯伊? 我笑而不...
    開封第一講書人閱讀 58,737評(píng)論 1 294
  • 正文 為了忘掉前任,我火速辦了婚禮判沟,結(jié)果婚禮上耿芹,老公的妹妹穿的比我還像新娘崭篡。我一直安慰自己,他們只是感情好吧秕,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,753評(píng)論 6 392
  • 文/花漫 我一把揭開白布琉闪。 她就那樣靜靜地躺著,像睡著了一般砸彬。 火紅的嫁衣襯著肌膚如雪颠毙。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,598評(píng)論 1 305
  • 那天拿霉,我揣著相機(jī)與錄音吟秩,去河邊找鬼。 笑死绽淘,一個(gè)胖子當(dāng)著我的面吹牛涵防,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播沪铭,決...
    沈念sama閱讀 40,338評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼壮池,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了杀怠?” 一聲冷哼從身側(cè)響起椰憋,我...
    開封第一講書人閱讀 39,249評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎赔退,沒想到半個(gè)月后橙依,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,696評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡硕旗,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,888評(píng)論 3 336
  • 正文 我和宋清朗相戀三年窗骑,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片漆枚。...
    茶點(diǎn)故事閱讀 40,013評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡创译,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出墙基,到底是詐尸還是另有隱情软族,我是刑警寧澤,帶...
    沈念sama閱讀 35,731評(píng)論 5 346
  • 正文 年R本政府宣布残制,位于F島的核電站立砸,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏痘拆。R本人自食惡果不足惜仰禽,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,348評(píng)論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧吐葵,春花似錦规揪、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,929評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至凤藏,卻和暖如春奸忽,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背揖庄。 一陣腳步聲響...
    開封第一講書人閱讀 33,048評(píng)論 1 270
  • 我被黑心中介騙來泰國打工栗菜, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人蹄梢。 一個(gè)月前我還...
    沈念sama閱讀 48,203評(píng)論 3 370
  • 正文 我出身青樓疙筹,卻偏偏與公主長得像,于是被迫代替她去往敵國和親禁炒。 傳聞我的和親對(duì)象是個(gè)殘疾皇子而咆,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,960評(píng)論 2 355

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

  • 1.ios高性能編程 (1).內(nèi)層 最小的內(nèi)層平均值和峰值(2).耗電量 高效的算法和數(shù)據(jù)結(jié)構(gòu)(3).初始化時(shí)...
    歐辰_OSR閱讀 29,392評(píng)論 8 265
  • Swift1> Swift和OC的區(qū)別1.1> Swift沒有地址/指針的概念1.2> 泛型1.3> 類型嚴(yán)謹(jǐn) 對(duì)...
    cosWriter閱讀 11,103評(píng)論 1 32
  • 老師說暴备,頭發(fā)眼睛眉毛的顏色最好是一致的。
    陽光貝拉閱讀 314評(píng)論 0 3
  • 春雨淡入们豌,愛意漸濃 秋風(fēng)蕭瑟涯捻,愛意繁盛 日出日落,循環(huán)往復(fù) 夏陽灼烈望迎,愛意濃郁 冬雪飄零汰瘫,愛意化境 世界運(yùn)轉(zhuǎn),周而...
    一粟谷閱讀 258評(píng)論 0 0
  • 這周按照不持有的生活里對(duì)房間和衣柜重新打掃整理擂煞,發(fā)現(xiàn)自己在衣物的消費(fèi)上還有很大可改進(jìn)的空間,換季總是覺得沒有衣服穿...
    fishyoho閱讀 191評(píng)論 0 0