iOS @property 屬性

readWrite,readOnly
atomic,nonatomic
assign, strong,weak,copy,retain,unsafe_unretained
getter,setter

默認情況

iOS @property 默認屬性
數(shù)據(jù)類型
atomic assign readwrite
對象類型
atomic strong readwrite

setter和getter

盡量多的使用屬性(property)而不是實例變量(attribute)因為屬性(property)相比實例變量有很多的好處:
1.自動合成getter和setter方法。當聲明一個屬性(property)的時候,編譯器默認情況下會自動生成相關的getter和setter方法。
2.更好的聲明一組方法。因為訪問方法的命名約定,可以很清晰的看出getter和setter的用處。
3.屬性(property)關鍵詞能夠傳遞出相關行為的額外信息。屬性提供了一些可能會使用的特性來進行聲明,包括assign(vs copy),weak,strong,atomic(vs nonatomic),readwrite,readonly等。

屬性方法遵守一個簡單的命名約定。getter的名字與屬性名相同(如:屬性名為date則getter的名字也為date),setter的名字則是屬性名字加上set前綴并采用駝峰命名規(guī)則(如:屬性名為date則setter的名字為setDate)。布爾類型的屬性還可以定義一個以is開頭的getter方法,如:

@property (readonly, getter=isBlue) BOOL blue;

如果按照上面的方法聲明則以下所有訪問方式都正確:

if (color.blue) {}
if (color.isBlue) {}
if ([color isBlue]) {}

當決定什么東西可以作為一個屬性的時候,需要注意以下這些不屬于屬性:

init方法
copy和mutableCopy方法
類工廠方法
開啟某項操作并返回一個BOOL結果的方法
明確的改變了一個getter的內(nèi)部狀態(tài)的副作用方法

除此之外,在你的代碼中使用屬性特性的時候請考慮以下規(guī)則:

  • 一個可讀寫(read/write)的屬性有兩個訪問方法。
    setter方法是有一個參數(shù)的無返回值方法,
    getter方法是沒有參數(shù)的且有一個返回值的方法,返回值類型與屬性聲明的類型一致。
    如果將這組方法轉換成一個屬性,就可以用readwrite關鍵字來標記它(默認即為readwrite可不寫)。
  • 一個只讀(read-only)的屬性只有一個訪問方法。
    即getter方法,它不接受任何參數(shù),并且返回一個值。
    如果將這個方法轉換成一個屬性,就可以用readonly關鍵字標記它。
  • getter方法應當是冪等(idempotent)的(如果一個getter方法被調用兩次,那么第二次調用時返回的結果應該和第一調用時返回的結果相同)。然而,如果一個getter方法每次調用時,是被用于計算結果,這是可以接受的。

如何適配

識別出一組可以被轉換成一個屬性的方法,如這些方法:

- (NSColor *)backgroundColor;
- (void)setBackgroundColor:(NSColor *)color;

用@property語法和適當?shù)年P鍵字將它們定義成一個屬性:

@property (copy) NSColor *backgroundColor;

有關屬性關鍵詞和其他注意事項,可以閱讀Encapsulating Data
或者,你也可以使用Xcode中的modern Objective-C轉換器來自動轉換你的代碼。參考Refactoring Your Code Using Xcode

手動創(chuàng)建getter與setter

@interface Person : NSObject
{
    NSString *_name;
    NSUInteger _age;
}

- (void)setName:(NSString*)name;
- (NSString*)name;
- (void)setAge:(NSUInteger)age;
- (NSUInteger)age;

@end

@implementation Person

- (void)setName:(NSString*)name {
    _name = [name copy];
}

- (NSString*)name {
    return _name;
}

- (void)setAge:(NSUInteger)age {
    _age = age;
}

- (NSUInteger)age {
    return _age;
}

@end

上述代碼就是手動創(chuàng)建變量的getter和setter的實現(xiàn),getter和setter本質就是符合一定命名規(guī)范(前文Apple Official Property Introduction有講解)的實例方法。

具體使用方法如下
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Person *p = [[Person alloc] init];
        //函數(shù)調用name的setter
        [p setName:@"Jiaming Chen"];
        //函數(shù)調用age的setter
        [p setAge:22];
        //函數(shù)調用name和age的getter,輸出 Jiaming Chen 22
        NSLog(@"%@ %ld", [p name], [p age]);
    }
    return 0;
}

通過調用方式可以看出,setter和getter本質就是實例方法,可以通過函數(shù)調用的方式來使用。
為了方便使用,Objective-C允許使用點語法來訪問getter和setter。

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Person *p = [[Person alloc] init];
        //使用點語法訪問name的setter
        p.name = @"Jiaming Chen";
        //使用點語法訪問age的setter
        p.age = 22;
        //使用點語法訪問name和age的getter,輸出 Jiaming Chen 22
        NSLog(@"%@ %ld", p.name, p.age);
    }
    return 0;
}

使用點語法訪問的方式本質還是調用了我們手動創(chuàng)建的setter和getter。
當有很多變量需要設置時,這樣手工創(chuàng)建setter和getter的方式難免很繁瑣,因此合成存取方法就誕生了。

合成存取方法

@interface Person : NSObject

@property (nonatomic, copy) NSString* name;
@property (nonatomic, assign) NSUInteger age;

@end

@implementation Person

@synthesize name = _name;
@synthesize age = _age;

@end

在聲明一個屬性(property)的時候盡量使用Foundation框架的數(shù)據(jù)類型,如整型使用NSInteger或NSUInteger表示,時間間隔的浮點類型使用NSTimeInterval表示,這樣代碼數(shù)據(jù)類型更統(tǒng)一。
上面的代碼使用@property聲明兩個屬性name和age并為其設置了一些指示符(nonatomic,copy,assign等,下文會詳細介紹)。
@synthesize表示為這兩個屬性自動生成名為_name和_age的底層實例變量,并自動生成相關的getter和setter也可以不寫,編譯器默認會自動生成'_屬性名'的實例變量以及相關的getter和setter。
這里所說的編譯器自動生成的實例變量就如同我們在上文中手動創(chuàng)建setter和getter時聲明的變量_name和_age。也就是說編譯器會在編譯時會自動生成并使用_name和_age這兩個變量來存儲這兩個屬性,跟name和age沒什么關系了,只是我們在上層使用這兩個屬性的時候可以用name和age的點語法來訪問getter和setter。如果不想使用這兩個名字用于底層的存儲也可以任意命名,但最好按照官方的命名原則來命名。
也可以自定義getter和setter方法來覆蓋編譯器默認生成的方法,就如同手動創(chuàng)建getter和setter一樣。

@interface Person : NSObject

@property (nonatomic, copy) NSString* name;
@property (nonatomic, assign) NSUInteger age;

@end

@implementation Person

//編譯器會幫我們自動生成_name和_age這兩個實例變量,下面代碼就可以正常使用這兩個變量了
@synthesize name = _name;
@synthesize age = _age;

- (void)setName:(NSString*)name {
    //必須使用_name來賦值,使用self.name來設置值時編譯器會自動轉為調用該函數(shù),會導致無限遞歸
    //使用_name則是直接訪問底層的存儲屬性,不會調用該方法來賦值
    //這里使用copy是為了防止NSMutableString多態(tài)
    _name = [name copy];
}

- (NSString*)name {
    //必須使用_name來訪問屬性值,使用self.name來訪問值時編譯器會自動轉為調用該函數(shù),會造成無限遞歸
    return _name;
}

@end

使用自定義的getter和setter一般是用來實現(xiàn)懶加載(lazy load),在很多情況下很常用,比如:創(chuàng)建一個比較大的而又不一定會使用的對象,可以按照如下方法編寫。

@property (nonatomic, strong) CustomObject *customObject;

@synthesize customObject = _customObject;

- (CustomObject*) customObject {
    if (_customObject == nil) {
        //初始化操作,會調用setter方法
        self.customObject = [[CustomObject alloc] init];
        //如果按照如下方法編寫不會調用setter方法,如果自定義setter方法需要完成一些事情建議使用self.customObject的方式來設置
        //_customObject = [[CustomObject alloc] init];
    }
    return _customObject;
}

atomic和nonatomic

atomic和nonatomic用來決定編譯器生成的getter和setter是否為原子操作。
atomic:
系統(tǒng)生成的 getter/setter(會進行加鎖操作) 會保證 get、set 操作的完整性,不受其他線程影響。getter 還是能得到一個完好無損的對象(可以保證數(shù)據(jù)的完整性),但這個對象在多線程的情況下是不能確定的。

線程 A 調了 getter,
與此同時線程 B 、線程 C 都調了 setter
那最后線程 A get 到的值,有3種可能:
B、C set 之前原始的值、 B set 的值、C set 的值。
所以,atomic并不能保證對象的線程安全。

也就是說:如果有多個線程同時調用setter的話,不會出現(xiàn)某一個線程執(zhí)行完setter全部語句之前,另一個線程開始執(zhí)行setter情況,相當于函數(shù)頭尾加了鎖一樣,每次只能有一個線程調用對象的setter方法,所以可以保證數(shù)據(jù)的完整性。

atomic所說的線程安全(這個加鎖的操作)只是保證了getter和setter存取方法的線程安全,并不能保證整個對象是線程安全的。因為線程安全還有讀寫之外的其他操作(比如:如果當一個線程正在get或set時,又有另一個線程同時在進行release操作,可能會直接crash)
因為getter/setter方法有加鎖的緣故,所以在別的線程來讀寫這個屬性之前,會先執(zhí)行完當前操作
nonatomic:
(系統(tǒng)生成的getter/setter方法不會進行加鎖操作),nonatomic返回你的對象可能就不是完整的value。當多個線程同時訪問同一個屬性,會出現(xiàn)無法預料的結果。因此,在多線程的環(huán)境下原子操作是非常必要的,否則有可能會引起錯誤的結果。但僅僅使用atomic并不會使得對象線程安全,我們還要為對象線程添加lock來確保線程的安全。
nonatomic VS atomic
nonatomic的速度要比atomic的快。atomic是Objc使用的一種線程保護技術,這種機制是耗費系統(tǒng)資源的,且速度要慢。所以在iPhone這種小型設備上,通常使用nonatomic,而對象的線程安全問題則由程序員代碼控制。

atomic與nonatomic的本質區(qū)別其實也就是在setter方法上的操作不同
nonatomic對象、atomic對象setter和getter方法的實現(xiàn):
/// nonatomic對象
- (void)setCurrentImage:(UIImage *)currentImage
{
    if (_currentImage != currentImage) {
        [_currentImage release];
        _currentImage = [currentImage retain];
    }
}
- (UIImage *)currentImage
{
    return _currentImage;
}


/// atomic對象
- (void)setCurrentImage:(UIImage *)currentImage
{
    @synchronized(self) {
        if (_currentImage != currentImage) {
            [_currentImage release];
            _currentImage = [currentImage retain];
        }
    }
}

- (UIImage *)currentImage
{
    @synchronized(self) {
        return _currentImage;
    }
}
@synchronized 結構所做的事情跟鎖lock類似:它防止不同的線程同時執(zhí)行同一段代碼。

assign

assign表示對屬性只進行簡單的賦值操作,不更改所賦的新值的引用計數(shù),也不改變舊值的引用計數(shù),常用于標量類型,如NSInteger,NSUInteger,CGFloat,NSTimeInterval等。
assign也可以修飾對象如NSString等類型對象,上面說過使用assign修飾不會更改所賦的新值的引用計數(shù),也不改變舊值的引用計數(shù),如果當所賦的新值引用計數(shù)為0對象被銷毀時屬性并不知道,編譯器不會將該屬性置為nil,指針仍舊指向之前被銷毀的內(nèi)存,這時訪問該屬性會產(chǎn)生野指針錯誤并崩潰,因此使用assign修飾的類型一定要為標量類型。

1.這個修飾詞是直接賦值的意思 , 整型/浮點型等數(shù)據(jù)類型都用這個詞修飾 .
2.如果沒有使用 weak strong retain copy 修飾 , 那么默認就是使用 assign 了.
3.當然其實對象也可以用 assign 修飾 , 只是對象的計數(shù)器不會+1 . ( 與 strong 的區(qū)別 )
4.如果用來修飾對象屬性 , 那么當對象被銷毀后指針是不會指向 nil 的 . 所以會出現(xiàn)野指針錯誤 .

( 與weak的區(qū)別 )
1.在 ARC 中,在有可能出現(xiàn)循環(huán)引用的時候,往往要通過讓其中一端使用 weak 來解決,比如: delegate 代理屬性。

2.自身已經(jīng)對它進行一次強引用,沒有必要再強引用一次,此時也會使用 weak,自定義 IBOutlet 控件屬性一般也使用 weak;當然,也可以使用strong。

IBOutlet連出來的視圖屬性為什么可以被設置成weak?

因為父控件的subViews數(shù)組已經(jīng)對它有一個強引用。

不同點:

assign 可以用非 OC 對象,而 weak 必須用于 OC 對象。

weak 表明該屬性定義了一種“非擁有關系”。在屬性所指的對象銷毀時,屬性值會自動清空 nil。
@interface Person : NSObject

@property (nonatomic, assign) NSString* name;
@property (nonatomic, assign) NSUInteger age;

@end

@implementation Person

@synthesize name = _name;
@synthesize age = _age;

@end

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Person *p = [[Person alloc] init];
        //這里使用NSMutableString而不使用NSString是因為NSString會緩存字符串,后面置空的時候實際沒有被銷毀
        NSMutableString *s = [[NSMutableString alloc] initWithString:@"Jiaming Chen"];
        //設置p.name不會增加s的引用計數(shù),只是單純將s指向的地址賦給p.name
        p.name = s;
        //輸出兩個變量的內(nèi)存地址,可以看出是一致的
        NSLog(@"%p %p", p.name, s);
        //這里可以正常訪問name
        NSLog(@"%@ %ld", p.name, p.age);
        //將上述字符串置空,引用計數(shù)為0,對象被銷毀
        s = nil;
        //查看其地址時仍然可以訪問到,表示其仍然指向那一塊內(nèi)存
        NSLog(@"%p", p.name);
        //訪問內(nèi)容時發(fā)生野指針錯誤,程序崩潰。因為對象已經(jīng)被銷毀
        NSLog(@"%@ %ld", p.name, p.age);
    }
    return 0;
}

使用weak修飾的時候同樣不會增加所賦的新值的引用計數(shù),也不減少舊值的引用計數(shù),但當該值被銷毀時,weak修飾的屬性會被自動賦值為nil,這樣就可以避免野指針錯誤。
為了防止多態(tài)的影響,對NSString進行修飾時一般使用copy。

unsafe_unretained

使用unsafe_unretained修飾時效果與assign相同,不會增加新值的引用計數(shù),也不會減少舊值的引用計數(shù)(unretained)當所賦的值被銷毀時不會被置為nil可能會發(fā)生野指針錯誤(unsafe)。unsafe_unretained與assign的區(qū)別在于,unsafe_unretained只能修飾對象,不能修飾標量類型,而assign兩者均可修飾。

weak

weak是弱引用,用weak描述修飾或者所引用對象的計數(shù)器不會加一,并且會在引用的對象被釋放的時候自動被設置為nil,大大避免了野指針訪問壞內(nèi)存引起崩潰的情況,另外weak還可以用于解決循環(huán)引用。

weak原理概括

轉載:__weak的底層實現(xiàn)原理:http://www.itdecent.cn/p/48044cc54392

weak表其實是一個hash(哈希)表,Key是所指對象的地址,Value是weak指針的地址數(shù)組。

weak的底層實現(xiàn)的原理是什么?
Runtime維護了一個weak表,用于存儲指向某個對象的所有weak指針。weak表其實是一個hash表,Key是所指對象的地址,value是weak指針的地址(這個地址的值是所指對象指針的地址)數(shù)組。

為什么value是數(shù)組?
因為一個對象可能被多個弱引用指針指向

weak原理實現(xiàn)步驟

1. 初始化時
runtime會調用objc_initWeak函數(shù),objc_initWeak函數(shù)會初始化一個新的weak指針指向對象的地址。

示例代碼:

NSObject *obj = [[NSObject alloc] init];
 id __weak obj1 = obj;

當我們初始化一個weak變量時,runtime會調用 NSObject.mm 中的objc_initWeak函數(shù)。
這個函數(shù)在Clang中的聲明如下:

id objc_initWeak(id *object, id value);

而對于 objc_initWeak() 方法的實現(xiàn)如下:

id objc_initWeak(id *location, id newObj) {
// 查看對象實例是否有效,無效對象直接導致指針釋放
    if (!newObj) {
        *location = nil;
        return nil;
    }
    // 這里傳遞了三個 bool 數(shù)值
    // 使用 template 進行常量參數(shù)傳遞是為了優(yōu)化性能
    return storeWeakfalse/*old*/, true/*new*/, true/*crash*/>
    (location, (objc_object*)newObj);
}

這個函數(shù)僅僅是一個深層函數(shù)的調用入口,而一般的入口函數(shù)中,都會做一些簡單的判斷(例如 objc_msgSend 中的緩存判斷),這里判斷了其指針指向的類對象是否有效,無效直接釋放,不再往深層調用函數(shù)。否則,object將被注冊為一個指向value的__weak對象。而這事應該是objc_storeWeak函數(shù)干的。
注意:objc_initWeak函數(shù)有一個前提條件:就是object必須是一個沒有被注冊為__weak對象的有效指針。而value則可以是null,或者指向一個有效的對象。

  1. 添加引用時:

objc_initWeak函數(shù)會調用 objc_storeWeak() 函數(shù), objc_storeWeak() 的作用是更新指針指向,創(chuàng)建對應的弱引用表。

objc_storeWeak的函數(shù)聲明如下:

id objc_storeWeak(id *location, id value);

  1. 釋放時:
    調用clearDeallocating函數(shù)。clearDeallocating函數(shù)首先根據(jù)對象地址獲取所有weak指針地址的數(shù)組,然后遍歷這個數(shù)組把其中的數(shù)據(jù)設為nil,最后把這個entry從weak表中刪除,最后清理對象的記錄。
    當weak引用指向的對象被釋放時,又是如何去處理weak指針的呢?當釋放對象時,其基本流程如下:

1.調用objc_release
2.因為對象的引用計數(shù)為0,所以執(zhí)行dealloc
3.在dealloc中,調用了 _ objc _ rootDealloc函數(shù)
4.在 _ objc _ rootDealloc 中,調用了object _ dispose函數(shù)
5.調用objc_destructInstance
6.最后調用objc _ clear _ deallocating

objc _ clear _ deallocating該函數(shù)的動作如下:
從weak表中獲取廢棄對象的地址為鍵值的記錄
將包含在記錄中的所有附有 weak修飾符變量的地址,賦值為nil
將weak表中該記錄刪除
從引用計數(shù)表中刪除廢棄對象的地址為鍵值的記錄

NSString *name = [[NSString alloc] initWithString: @"Jiaming Chen"];
__weak NSString *weakStr = name;

當為weakStr這一weak類型的對象賦值時,編譯器會根據(jù)name的地址為key去查找weak哈希表,該表項的值為一個數(shù)組,將weakStr對象的地址加入到數(shù)組中,當name變量超出變量作用域或引用計數(shù)為0時,會執(zhí)行dealloc函數(shù),在執(zhí)行該函數(shù)時,編譯器會以name變量的地址去查找weak哈希表的值,并將數(shù)組里所有 weak對象全部賦值為nil。

strong

在ARC環(huán)境下,只要某一對象被一個strong指針指向,該對象就不會被銷毀。如果對象沒有被任何strong指針指向,那么就會被銷毀。在默認情況下,所有的實例變量和局部變量都是strong類型的。可以說strong類型的指針在行為上跟非ARC下的retain是比較相似的
strong表示屬性對所賦的值持有強引用表示一種“擁有關系”(owning relationship),會先保留新值即增加新值的引用計數(shù),然后再釋放舊值即減少舊值的引用計數(shù)。只能修飾對象。如果對一些對象需要保持強引用則使用strong。
weak表示對所賦的值對象持有弱引用表示一種“非擁有關系”(nonowning relationship),對新值不會增加引用計數(shù),也不會減少舊值的引用計數(shù)。所賦的值在引用計數(shù)為0被銷毀后,weak修飾的屬性會被自動置為nil能夠有效防止野指針錯誤。
weak常用在修飾delegate等防止循環(huán)引用的場景。

copy

預備知識
淺拷貝

只是將對象內(nèi)存地址多了一個引用,也就是說,拷貝結束之后,兩個對象的值不僅相同,而且對象所指的內(nèi)存地址都是一樣的。

深拷貝

拷貝一個對象的具體內(nèi)容,拷貝結束之后,兩個對象的值雖然是相同的,但是指向的內(nèi)存地址是不同的。兩個對象之間也互不影響,互不干擾。
這里講的真的是太好了??!參考:http://www.itdecent.cn/p/d01429a4b5c0

copy的作用

在非集合類對象中,對不可變對象進行copy操作,只僅僅是指針復制——淺復制,進行mutableCopy操作,是內(nèi)容復制——深復制。
對于不可變的集合類對象進行copy操作,只是改變了指針,其內(nèi)存地址并沒有發(fā)生變化;進行mutableCopy操作,內(nèi)存地址發(fā)生了變化,但是其中的元素內(nèi)存地址并沒有發(fā)生變化。
對于可變集合類對象,不管是進行copy操作還是mutableCopy操作,其內(nèi)存地址都發(fā)生了變化,但是其中的元素內(nèi)存地址都沒有發(fā)生變化,屬于單層深拷貝。

使用注意:
當將一個可變對象分別賦值給兩個使用不同修飾詞的屬性后,改變可變對象的內(nèi)容,使用strong修飾的會跟隨著改變,但使用copy修飾的沒有改變內(nèi)容。

那么,是不是NSMutableString等這些可變對象是不是也需要copy來修飾呢?答案是千萬不要這么干,我們可以測試一下:

@interface test()
 
@property (nonatomic, copy) NSMutableString *strCopy;
 
@end
 
/********************* test.m **********************/
NSMutableString *string = [NSMutableString stringWithFormat:@"abc"];
self.strCopy = string;
[self.strCopy appendString:@"def"]; // 在這一行會crash

因為copy是復制出一個不可變的對象,在不可變對象上運行可變對象的方法,就會找不到執(zhí)行方法

copy修飾的屬性會在內(nèi)存里拷貝一份對象,兩個指針指向不同的內(nèi)存地址。
一般用來修飾有對應可變類型子類的對象。
如:NSString/NSMutableString,NSArray/NSMutableArray,NSDictionary/NSMutableDictionary等。
為確保這些不可變對象因為可變子類對象影響,需要copy一份備份,如果不使用copy修飾,使用strong或assign等修飾則會因為多態(tài)導致屬性值被修改。
這里的copy還牽扯到NSCopying和NSMutableCopying協(xié)議,在下文會有簡要介紹。

@interface Person : NSObject

//使用strong修飾NSString
@property (nonatomic, strong) NSString* name;
@property (nonatomic, assign) NSUInteger age;

@end

@implementation Person

@synthesize name = _name;
@synthesize age = _age;

@end


int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Person *p = [[Person alloc] init];
        NSMutableString *s = [[NSMutableString alloc] initWithString:@"Jiaming Chen"];
        //將可變字符串賦值給p.name
        p.name = s;
        //輸出的地址和內(nèi)容均一致
        NSLog(@"%p %p %@ %@", p.name, s, p.name, s);
        //修改可變字符串s
        [s appendString:@" is a good guy"];
        //再次輸出p.name被影響
        NSLog(@"%p %p %@ %@", p.name, s, p.name, s);
    }
    return 0;
}

copy還被用來修飾block,在ARC環(huán)境下編譯器默認會用copy修飾, 一般情況下在block需要捕獲外界數(shù)據(jù)時該block就會被分配在堆區(qū),但在MRC環(huán)境下由于手動管理引用計數(shù),block一般被分配在棧區(qū),需要copy到堆區(qū)來防止野指針錯誤。
對于可變對象類型,如NSMutableString、NSMutableArray等則不可以使用copy修飾,因為Foundation框架提供的這些類都實現(xiàn)了NSCopying協(xié)議,使用copy方法返回的都是不可變對象,如果使用copy修飾符在對可變對象賦值時則會獲取一個不可變對象,接下來如果對這個對象進行可變對象的操作則會產(chǎn)生異常,因為OC沒有提供mutableCopy修飾符,對于可變對象使用strong修飾符即可。具體栗子如下:

@interface Person : NSObject

//使用copy修飾NSMutableString
@property (nonatomic, copy) NSMutableString* name;
@property (nonatomic, assign) NSUInteger age;

@end

@implementation Person

@synthesize name = _name;
@synthesize age = _age;

@end


int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Person *p = [[Person alloc] init];
        NSMutableString *s = [[NSMutableString alloc] initWithString:@"Jiaming Chen"];
        //將可變字符串賦值給p.name
        p.name = s;
        //輸出的地址不一致,內(nèi)容一致
        NSLog(@"%p %p %@ %@", p.name, s, p.name, s);
        //修改p.name,此時拋出異常
        [p.name appendString:@" is a good guy."];
    }
    return 0;
}

上面的栗子使用copy修飾可變對象,在進行賦值的時候會通過copy方法獲取一個不可變對象,因此p.name的地址和s的地址不同,而p.name運行時類型為NSString,調用appendString:方法會拋出異常。
所以,針對不可變對象使用copy修飾,針對可變對象使用strong修飾。

retain

在ARC環(huán)境下使用較少,在MRC下使用效果與strong一致。

copy的題外話

有時候我們需要copy一個對象,或是mutableCopy一個對象,這時需要遵守NSCopying和NSMutableCopying協(xié)議,來實現(xiàn)copyWithZone:和mutableCopyWithZone:兩個方法,而不是重寫copy和mutableCopy兩個方法。
Foundation框架中的很多數(shù)據(jù)類型已經(jīng)幫我們實現(xiàn)了上述兩個方法,因此我們可以使用copy方法和mutableCopy方法來復制一個對象,兩者的區(qū)別在于copy的返回值仍未不可變對象,mutableCopy的返回值為可變對象。



由上述表格可以看出,對于不可變類型,使用copy方法時是淺拷貝,只拷貝指針,因為內(nèi)容是不會變化的。使用mutableCopy時由于返回可變對象因此需要一份拷貝,供其他對象使用。對于可變類型,不管是copy還是mutableCopy均會進行深拷貝,所指向指針不同。
前文介紹copy修飾符的時候講過,在修飾NSString這樣的不可變對象的時候使用copy修飾,但其實當給對象賦一個NSString時仍舊只復制了指針而不是拷貝內(nèi)容,原因同上。

@interface Person : NSObject

//使用copy修飾
@property (nonatomic, copy) NSString* name;
@property (nonatomic, assign) NSUInteger age;

@end

@implementation Person

@synthesize name = _name;
@synthesize age = _age;

@end


int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Person *p = [[Person alloc] init];
        NSString *s = @"Jiaming Chen";
        p.name = s;
        //p.name的地址與s地址相同,不可變對象copy是淺拷貝
        NSLog(@"%p %p %@ %@", p.name, s, p.name, s);
    }
    return 0;
}

@property深入代碼理解

@property = ivar + getter + setter

ivar就是實例變量,編譯器會幫我們自動生成名字為'_屬性名'這樣的實例變量,同時也會自動生成getter和setter方法。

#import <Foundation/Foundation.h>

@interface Person : NSObject

@property (nonatomic, copy) NSString* cjmName;
@property (nonatomic, assign) NSUInteger cjmAge;

@end

@implementation Person

@synthesize cjmName = _cjmName;
@synthesize cjmAge = _cjmAge;

@end

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Person *p = [[Person alloc] init];
        p.cjmName = @"JIaming Chen";
        p.cjmAge = 22;
    }
    return 0;
}

使用上述命令后生成的.cpp文件中可以查找到如下部分的代碼

#ifndef _REWRITER_typedef_Person
#define _REWRITER_typedef_Person
typedef struct objc_object Person;
typedef struct {} _objc_exc_Person;
#endif

extern "C" unsigned long OBJC_IVAR_$_Person$_cjmName;
extern "C" unsigned long OBJC_IVAR_$_Person$_cjmAge;
struct Person_IMPL {
        struct NSObject_IMPL NSObject_IVARS;
        NSString *_cjmName;
        NSUInteger _cjmAge;
};


// @property (nonatomic, copy) NSString* cjmName;
// @property (nonatomic, assign) NSUInteger cjmAge;

/* @end */


// @implementation Person

// @synthesize cjmName = _cjmName;
static NSString * _I_Person_cjmName(Person * self, SEL _cmd) { return (*(NSString **)((char *)self + OBJC_IVAR_$_Person$_cjmName)); }
extern "C" __declspec(dllimport) void objc_setProperty (id, SEL, long, id, bool, bool);

static void _I_Person_setCjmName_(Person * self, SEL _cmd, NSString *cjmName) { objc_setProperty (self, _cmd, __OFFSETOFIVAR__(struct Person, _cjmName), (id)cjmName, 0, 1); }

// @synthesize cjmAge = _cjmAge;
static NSUInteger _I_Person_cjmAge(Person * self, SEL _cmd) { return (*(NSUInteger *)((char *)self + OBJC_IVAR_$_Person$_cjmAge)); }
static void _I_Person_setCjmAge_(Person * self, SEL _cmd, NSUInteger cjmAge) { (*(NSUInteger *)((char *)self + OBJC_IVAR_$_Person$_cjmAge)) = cjmAge; }


// @end


int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
        Person *p = ((Person *(*)(id, SEL))(void *)objc_msgSend)((id)((Person *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("Person"), sel_registerName("alloc")), sel_registerName("init"));
        ((void (*)(id, SEL, NSString *))(void *)objc_msgSend)((id)p, sel_registerName("setCjmName:"), (NSString *)&__NSConstantStringImpl__var_folders_1f_dz4kq57d4b19s4tfmds1mysh0000gn_T_main_4b2631_mi_0);
        ((void (*)(id, SEL, NSUInteger))(void *)objc_msgSend)((id)p, sel_registerName("setCjmAge:"), (NSUInteger)22);
    }
    return 0;
}

以上代碼就是編譯器為我們生成的C代碼,現(xiàn)在一一講解幾個比較重要的部分。
typedef struct objc_object Person;
編譯器將struct objc_object重命名為我們自定義的Person類,struct objc_object結構體只有一個類型為Class的isa指針變量

/// Represents an instance of a class.
struct objc_object {
    Class isa  OBJC_ISA_AVAILABILITY;
};

而這個Class就代表類對象。

/// An opaque type that represents an Objective-C class.
typedef struct objc_class *Class;

struct objc_class {
    Class isa  OBJC_ISA_AVAILABILITY;

/#if !__OBJC2__
    Class super_class                                        OBJC2_UNAVAILABLE;
    const char *name                                         OBJC2_UNAVAILABLE;
    long version                                             OBJC2_UNAVAILABLE;
    long info                                                OBJC2_UNAVAILABLE;
    long instance_size                                       OBJC2_UNAVAILABLE;
    struct objc_ivar_list *ivars                             OBJC2_UNAVAILABLE;
    struct objc_method_list **methodLists                    OBJC2_UNAVAILABLE;
    struct objc_cache *cache                                 OBJC2_UNAVAILABLE;
    struct objc_protocol_list *protocols                     OBJC2_UNAVAILABLE;
#endif

} OBJC2_UNAVAILABLE;

這個Person就是我們創(chuàng)建的類對象,這個類對象包含了Person類所需的所有東西,包括屬性、方法列表、版本號等一系列信息。

extern "C" unsigned long OBJC_IVAR_$_Person$_cjmName;
extern "C" unsigned long OBJC_IVAR_$_Person$_cjmAge;

定義了兩個unsigned long類型的變量,這兩個變量代表一個偏移量,值這兩個實例變量在內(nèi)存中存儲的偏移量,通過這兩個值就能夠在內(nèi)存中定位到這兩個實例變量的位置。
這兩個值是運行時計算出偏移量硬編碼(hard code)寫入的,這樣的好處在于,如果你使用了一個庫,這個庫的類定義比較舊,而鏈接的代碼使用的是版本較新的代碼,增加了幾個實例變量,你的程序運行時也不會報錯,因為偏移量是通過運行時計算出來的,仍舊能夠找到相應的位置。如果不使用合成存取方法定義實例變量而使用手工的方式創(chuàng)建,這個偏移量就是編譯器計算出硬編碼寫到代碼中的,如果類定義和鏈接庫的版本不一致則可能發(fā)生指針錯誤,因此鼓勵大家盡量都使用合成存取方法。

struct Person_IMPL {
        struct NSObject_IMPL NSObject_IVARS;
        NSString *_cjmName;
        NSUInteger _cjmAge;
};

該結構體就是Person類實現(xiàn),struct NSObject_IMPL結構體只有一個Class isa結構體指針變量,指向類對象,用于獲取Person類的方法列表、實例變量列表、屬性列表、版本等信息??梢钥闯觯诘讓哟a中編譯器幫我們自動生成了名為_cjmName和_cjmAge的兩個實例變量,如果我們修改@synthesize cjmName = _cjmName為其他名稱則這列會生成相應名稱的實例變量。

static NSString * _I_Person_cjmName(Person * self, SEL _cmd) { return (*(NSString **)((char *)self + OBJC_IVAR_$_Person$_cjmName)); }

這句代碼就是屬性cjmName的getter方法,可以看出,使用了OBJC_IVAR__Person_cjmName偏移量來計算實例變量的存儲位置并返回。

extern "C" __declspec(dllimport) void objc_setProperty (id, SEL, long, id, bool, bool);

static void _I_Person_setCjmName_(Person * self, SEL _cmd, NSString *cjmName) {
    objc_setProperty (self, _cmd, __OFFSETOFIVAR__(struct Person, _cjmName), (id)cjmName, 0, 1); 
}

這兩句代碼是屬性cjmName的setter方法,使用OFFSETOFIVAR(TYPE, MEMBER)宏定義來計算偏移量,上文指的偏移量都是通過該宏定義計算而來,計算出偏移量后使用objc_setProperty來設置實例變量_cjmName的值。
將屬性cjmName的修飾符改為strong后再次查看重寫的setter代碼:

static void _I_Person_setCjmName_(Person * self, SEL _cmd, NSString *cjmName) { 
    (*(NSString **)((char *)self + OBJC_IVAR_$_Person$_cjmName)) = cjmName; 
}

與上文代碼相比發(fā)現(xiàn),沒有聲明objc_setProperty方法也沒有使用該方法,而是直接計算出實例變量的偏移量后將指針賦給實例變量。由此就可以看出修飾符copy和strong底層代碼的區(qū)別。
同樣的可以將修飾符改為assign、unsafe_unretained、weak來查看生成的代碼,結果都同Strong一致,這就解釋了底層代碼是如何copy實例變量的。
再來看看以下幾個結構體:

struct _ivar_t {
        unsigned long int *offset;  // pointer to ivar offset location
        const char *name;
        const char *type;
        unsigned int alignment;
        unsigned int  size;
};

static struct /*_ivar_list_t*/ {
        unsigned int entsize;  // sizeof(struct _prop_t)
        unsigned int count;
        struct _ivar_t ivar_list[2];
} _OBJC_$_INSTANCE_VARIABLES_Person __attribute__ ((used, section ("__DATA,__objc_const"))) = {
        sizeof(_ivar_t),
        2,
        {{(unsigned long int *)&OBJC_IVAR_$_Person$_cjmName, "_cjmName", "@\"NSString\"", 3, 8},
         {(unsigned long int *)&OBJC_IVAR_$_Person$_cjmAge, "_cjmAge", "Q", 3, 8}}
};

struct _ivar_t結構體表示每一個實例變量,記錄了偏移值、名稱、類型、對齊方式和大小,用于描述每一個實例變量。
struct _ivar_list_t結構體表示類的實例變量列表,記錄了實例變量的大小、個數(shù)、以及每一個實例變量描述。
我們每在類中加入一個屬性,編譯器都會在_ivar_list_t變量中加入一個_ivar_t的實例變量描述。

struct _objc_method {
        struct objc_selector * _cmd;
        const char *method_type;
        void  *_imp;
};

static struct /*_method_list_t*/ {
        unsigned int entsize;  // sizeof(struct _objc_method)
        unsigned int method_count;
        struct _objc_method method_list[4];
} _OBJC_$_INSTANCE_METHODS_Person __attribute__ ((used, section ("__DATA,__objc_const"))) = {
        sizeof(_objc_method),
        4,
        {{(struct objc_selector *)"cjmName", "@16@0:8", (void *)_I_Person_cjmName},
        {(struct objc_selector *)"setCjmName:", "v24@0:8@16", (void *)_I_Person_setCjmName_},
        {(struct objc_selector *)"cjmAge", "Q16@0:8", (void *)_I_Person_cjmAge},
        {(struct objc_selector *)"setCjmAge:", "v24@0:8Q16", (void *)_I_Person_setCjmAge_}}
};

struct _objc_method結構體描述了每一個實例方法,包括一個SEL類型的指針、方法類型和方法實現(xiàn)。
struct _method_list_t結構體表示類的實例方法列表,記錄了每一個實例方法的大小、實例方法個數(shù)以及具體的實例方法描述,每加入一個屬性則會在_method_list_t中增加setter與getter方法的描述。

struct _prop_t {
        const char *name;
        const char *attributes;
};

static struct /*_prop_list_t*/ {
        unsigned int entsize;  // sizeof(struct _prop_t)
        unsigned int count_of_properties;
        struct _prop_t prop_list[2];
} _OBJC_$_PROP_LIST_Person __attribute__ ((used, section ("__DATA,__objc_const"))) = {
        sizeof(_prop_t),
        2,
        {{"cjmName","T@\"NSString\",C,N,V_cjmName"},
        {"cjmAge","TQ,N,V_cjmAge"}}
};

struct _prop_t結構體描述了每一個屬性,包括名稱和屬性值。
struct _prop_list_t結構體表示屬性列表,記錄了每一個屬性的大小、屬性個數(shù)以及具體的屬性描述,每加入一個屬性則會在_prop_list_t中增加_prop_t屬性描述。
從結構體中的值不難看出,屬性描述中的T@表示是類型對象后接類型名稱,C表示copy,N表示nonatomic,V_cjmName表示實例變量。

clang -rewrite-objc main.m
這個命令用于clang重寫.m文件為.cpp文件。

轉載:
iOS @property探究(一): 基礎詳解 http://www.itdecent.cn/p/646ae400fe7b
iOS @property探究(二): 深入理解 http://www.itdecent.cn/p/44d12884e24e
iOS 底層解析weak的實現(xiàn)原理(包含weak對象的初始化,引用,釋放的分析):http://www.cocoachina.com/ios/20170328/18962.html

最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

  • 〇、引言 2015年12月份學習“向組織揩油”系列課程,其中用到一個很重要的原則——SMART原則,當時我們簡單的...
    柒柒小醬閱讀 16,442評論 0 5

友情鏈接更多精彩內(nèi)容