iOS 屬性修飾詞和權(quán)限控制詞

Swift 權(quán)限控制詞

概述

swift3.0中,新增了fileprivate和open權(quán)限。swift4.0中,對fileprivate和private的訪問范圍做了調(diào)整。訪問控制權(quán)限,限制了不同源文件和module之間的訪問。這個特性使你可以隱藏一些代碼的實現(xiàn),和明確一些可以訪問和使用的接口。

module 和 源文件

swift的訪問控制模型是基于module和源文件的。
一個module是一個獨立的代碼建造單元,例如一個framework或者一個可以被其他module通過swift關(guān)鍵字import的單元。
一個源文件是在一個module中單獨的swift源碼(或是APP或framework里單獨的文件)。雖然常見方式,是在分開的源文件里定義單獨的類型,但一個源文件也可以包含多個類型、方法等的定義。

訪問級別

swift提供了5個不同的訪問級別,權(quán)限最高的是open,其次依次是public、internal、fileprivate,最低是private。默認使用的是internal。

open & public

使用open和public定義的實體,在它們定義的module的任意文件中皆可使用。其他module如果import了此實體,則其他module的源文件也可使用。一般在framework中指定的公開接口里,使用open和public級別。

** open 和 public 的區(qū)別**

  • public或更低權(quán)限的類,只能在其定義的module中子類化
  • public或更低權(quán)限的類的成員,只能在其定義的module中被重寫或子類化
  • open權(quán)限的類,可在其定義的module,或者import了其的module中子類化
  • open權(quán)限的類的成員,可在其定義的module,或者import了其的module中重寫或子類化

internal

intenal修飾的實體在其定義的module中皆可使用,但是在其他module中無法使用。一般定義APP或者framework內(nèi)部結(jié)構(gòu)的時候,會使用internal級別

fileprivate

fileprivate定義的實體,只能在其源文件中使用,當其只在整個源文件中使用的時候,使用fileprivate級別

private

private定義的實體,只能在定義的范圍內(nèi),和其在同一文件的extension中使用,當其只在當前聲明范圍內(nèi)使用的時候,使用private級別

使用原則

子類:子類的訪問級別不可以高于父類,但子類重寫的方法的訪問級別可以高于父類
枚舉:枚舉的每個值都和它們所屬枚舉有相同的權(quán)限,且不能單獨為某個值定義權(quán)限
協(xié)議:協(xié)議可以指定訪問級別,并且對于協(xié)議的每一項來說,都和協(xié)議的訪問級別相同,不能單獨為協(xié)議的某個方法指定不同的訪問級別

參考:
Swift訪問控制權(quán)限

OC 屬性修飾詞

OC的屬性修飾詞可分為以下幾類:

  1. 原子性:nonatomic和atomic
  2. 內(nèi)存管理:assign、strong、weak、copy、retain和unsafe_unretained
  3. 讀寫屬性:setter、getter、readwrite、readonly
  4. 可空性:nonnull、nullable、null_unspecified

1. 原子性:nonatomic和atomic

nonatomic

非原子性,一般屬性都用 nonatomic 進行修飾,因為 atomic 非常耗時。

atomic

原子性,會在getter和setter時自動加互斥鎖,來保證屬性的賦值和取值是線程安全的。但不保證操作和訪問。
比如說 atomic 修飾的是一個數(shù)組的話,那么我們對數(shù)組進行賦值和取值是可以保證線程安全的。但是如果我們對數(shù)組進行操作,比如說給數(shù)組添加對象或者移除對象,是不在 atomic 的負責范圍之內(nèi)的,所以給被 atomic 修飾的數(shù)組添加對象或者移除對象是沒辦法保證線程安全的。

2. 內(nèi)存管理:assign、retain、strong、weak、copy和unsafe_unretained

assign

1)既可以修飾基本數(shù)據(jù)類型,也可以修飾對象類型;
2)setter方法的實現(xiàn)是直接賦值,用于基本數(shù)據(jù)類型;
3)修飾基本數(shù)據(jù)類型,NSInteger, CGFloat, int, float, BOOL等;
4)修飾對象類型時,引用計數(shù)不加一;
5)會產(chǎn)生懸垂指針(懸垂指針:assign 修飾的對象在被釋放之后,指針仍然指向原對象地址,該指針變?yōu)閼掖怪羔?。這時候如果繼續(xù)通過該指針訪問原對象的話,就可能導(dǎo)致程序崩潰);

retain

1)MRC下使用,ARC下使用strong;
2)修飾強引用,將指針原來指向的舊對象釋放掉,并指向新的對象。同時將新對象的引用計數(shù)加1;
3)setter方法的實現(xiàn)是,release舊值,retain新值,用于OC對象類型;

strong

1)ARC下才能使用;
2)原理同retain;
3)在修飾block時,strong相當于copy,而retain相當于assign;

weak

1)ARC下才能使用;
2)只能修飾對象類型;
3)修飾弱引用,不增加引用計數(shù)。用于避免循環(huán)引用;
4)weak修飾的對象在被釋放之后,會自動將指針置為nil,不會產(chǎn)生懸垂指針;

copy

1)setter方法的實現(xiàn)是,release舊值,copy新值,用于NSString,block等類型;
2)當修飾可變類型的屬性時,如NSMutableArray、NSMutableDictionary、NSMutableString,用strong;
3)當修飾不可變類型的屬性時,如NSArray、NSDictionary、NSString,用copy;

unsafe_unretained

1)既可以修飾基本數(shù)據(jù)類型,也可以修飾對象類型;
2)MRC下經(jīng)常使用,ARC下基本不用;
3)原理同weak,區(qū)別是unsafe_unretained會產(chǎn)生懸垂指針;

3. 讀寫屬性:readwrite、readonly、setter、getter

readwrite

可讀可寫(默認),同時生成 setter 方法和 getter 方法的聲明和實現(xiàn)。

readonly

只讀,只生成 getter 方法的聲明和實現(xiàn)。

setter

可以指定生成的 setter 方法名,如 setter = setName。

getter

可以指定生成的 getter 方法名,如 getter = getName。

4. 可空性

蘋果在 Xcode 6.3 引入的一個 Objective-C 的新特性nullability annotations。這些關(guān)鍵字可以用于屬性、方法返回值和參數(shù)中,來指定對象的可空性,這樣編寫代碼的時候就會智能提示。在 Swift 中可以使用?!來表示一個對象是optional的還是non-optional,如UIView?UIView!。而在 Objective-C 中則沒有這一區(qū)分,UIView即可表示這個對象是optional,也可表示是non-optioanl。這樣就會造成一個問題:在 Swift 與 Objective-C 混編時,Swift 編譯器并不知道一個 Objective-C 對象到底是optional還是non-optional,因此這種情況下編譯器會隱式地將 Objective-C 的對象當成是non-optional。引入nullability annotations一方面為了讓 iOS 程序員平滑地從 Objective-C 過渡到 Swift,另一方面也促使開發(fā)者在編寫 Objective-C 代碼時更加規(guī)范,減少同事之間的溝通成本。

關(guān)鍵字__nullable__nonnull是蘋果在 Xcode 6.3 中發(fā)行的。由于與第三方庫的潛在沖突,蘋果在 Xcode 7 中將它們更改為_Nullable_Nonnull。但是,為了與 Xcode 6.3 兼容,蘋果預(yù)定義了宏__nullable__nonnull來擴展為新名稱。同時蘋果同樣還支持沒有下劃線的寫法nullablenonnull,它們的區(qū)別在與放置位置不同。

注意:此類關(guān)鍵詞僅僅提供警告,并不會報編譯錯誤。只能用于聲明對象類型,不能聲明基本數(shù)據(jù)類型。

nullable、_Nullable 、__nullable

對象可以為空,區(qū)別在于放置位置不同

nonnull、_Nonnull、__nonnull

對象不能為空,區(qū)別在于放置位置不同

null_unspecified、_Null_unspecified 、__null_unspecified

未指定是否可為空,區(qū)別在于放置位置不同

null_resettable

1)getter 方法不能返回為空,setter 方法可以為空;
2)必須重寫 setter 或 getter 方法做非空處理。否則會報警告Synthesized setter 'setName:' for null_resettable property 'name' does not handle nil

使用示例

  • 聲明屬性
@property (nonatomic, copy, nullable) NSString * param;
@property (nonatomic, copy) NSString * _Nullable param;
@property (nonatomic, copy) NSString * __nullable param;
  • 修飾方法返回值
- (nullable NSString *)method;
- (NSString * _Nullable)method;
- (NSString * __nullable)method;
  • 修飾方法參數(shù)
- (void)methodWithParam:(nullable NSString *) param;
- (void)methodWithParam:(NSString * _Nullable) param;
- (void)methodWithParam:(NSString * __nullable) param;
  • 例外情況:對于雙指針類型對象、Block 的返回值Block 的參數(shù)等,這時候就不能用nonnull/nullable修飾,只能用帶下劃線的__nonnull/__nullable或者 _Nonnull/_Nullable
- (void)methodWithError:(NSError * _Nullable * _Nullable)error
- (void)methodWithError:(NSError * __nullable * __nullable)error;

- (void)methodWithBlock:(nullable id __nonnull  (^)(id __nullable params))block;

Audited Regions:Nonnull 區(qū)域設(shè)置

如果每個屬性或每個方法都去指定nonnull和nullable,將是一件非常繁瑣的事。蘋果為了減輕我們的工作量,專門提供了兩個宏:NS_ASSUME_NONNULL_BEGIN和NS_ASSUME_NONNULL_END。在這兩個宏之間的代碼,所有簡單指針類型都被假定為nonnull,因此我們只需要去指定那些nullable指針類型即可。示例代碼如下:

NS_ASSUME_NONNULL_BEGIN
@interface AAPLList : NSObject <NSCoding, NSCopying>
// ...
- (nullable AAPLListItem *)itemWithName:(NSString *)name;
- (NSInteger)indexOfItem:(AAPLListItem *)item;

@property (copy, nullable) NSString *name;
@property (copy, readonly) NSArray *allItems;
// ...
@end
NS_ASSUME_NONNULL_END

// --------------

self.list.name = nil;   // okay

AAPLListItem *matchingItem = [self.list itemWithName:nil];  // warning!

使用規(guī)范

1)對于屬性、方法返回值、方法參數(shù)的修飾,使用:nonnull/nullable;
2)對于 C 函數(shù)的參數(shù)、Block 的參數(shù)、Block 返回值的修飾,使用:_Nonnull/_Nullable,建議棄用__nonnull/__nullable;

為了安全起見,蘋果還制定了以下幾條規(guī)則:

1)typedef類型的的可空性通常依賴于上下文,即使在 Audited Regions 中也不能假定它為nonnull;
2)對于復(fù)雜的指針類型(如id *)必須明確指定它的可空性。例如,指定一個指向nullable對象的nonnull指針,可以使用_Nullable id * _Nonnull
3)特殊類型的NSError **經(jīng)常用于通過方法參數(shù)返回錯誤,因此始終假定它是指向nullableNSError對象的nullable的指針。

5. 所有權(quán)修飾符:__strong、__weak、__unsafe_unretained、__autoreleasing

__strong

1)強引用持有對象,可以對應(yīng) strong、retain、copy 關(guān)鍵字;
2)編譯器將為 strong、retain、copy 修飾的屬性生成帶 __strong 所有權(quán)修飾符的實例變量。

__weak

1)弱引用持有對象,對應(yīng) weak 關(guān)鍵字,ARC下用來防止循環(huán)引用。
2)編譯器將為 weak 修飾的屬性生成帶 __weak 所有權(quán)修飾符的實例變量。

__unsafe_unretained

1)弱引用持有對象,對應(yīng) unsafe_unretained、assign 關(guān)鍵字,MRC下用來防止循環(huán)引用。
2)編譯器將為 unsafe_unretained 修飾的屬性生成帶 __unsafe_unretained 所有權(quán)修飾符的實例變量。
3)與 __weak 相比,它不需要遍歷 weak 表來檢查對象是否 nil,性能上要更好一些。但是它會產(chǎn)生懸垂指針。

__autoreleasing

1)在 MRC 中我們可以給對象發(fā)送 autorelease 消息來將它注冊到 autoreleasepool 中,而在 ARC 中我們可以使用 __autoreleasing 修飾符修飾對象將對象注冊到 autoreleasepool 中。

相關(guān)面試題

Q:atomic 修飾的屬性是怎么樣保存線程安全的?

答: 編譯器會自動生成互斥鎖,對 setter 和 getter 方法進行加鎖,可以保證屬性的賦值和取值原子性操作是線程安全的,但不包括操作和訪問。比如說atomic修飾的是一個數(shù)組的話,那么我們對數(shù)組進行賦值和取值是可以保證線程安全的。但是如果我們對數(shù)組進行操作,比如說給數(shù)組添加對象或者移除對象,是不在atomic的負責范圍之內(nèi)的,所以給被atomic修飾的數(shù)組添加對象或者移除對象是沒辦法保證線程安全的。

Q:什么時候使用 weak/__weak 關(guān)鍵字?

1)ARC 中為了避免循環(huán)引用而使用,可以讓相互引用的對象中的一個使用weak/__weak弱引用修飾,常用于對delegate和block的修飾;
2)Interface Builder 中 IBOutlet 修飾的控件一般也是用weak。

Q:assign 和 weak 關(guān)鍵字的區(qū)別有哪些?

1)weak只能修飾對象,而assign既可以修飾對象也可以修飾基本數(shù)據(jù)類型;
2)assign修飾的對象在被釋放后,指針仍然指向原對象地址;而weak修飾的對象在被釋放之后會自動置指針為 nil;
3)相同點:在修飾對象的時候,assign和weak都不改變對象的引用計數(shù)。

Q:assign可以用于OC對象嗎?

答:不可以,assign一般用于修飾基本數(shù)據(jù)類型和oc數(shù)據(jù)類型。
基本數(shù)據(jù)類型和oc數(shù)據(jù)類型的內(nèi)存被分配在棧上,而對象的內(nèi)存被分配到堆上,棧的內(nèi)存是由系統(tǒng)分配和釋放的。堆的內(nèi)存則由程序員管理。
所以,如果對象被釋放了,該指針仍然指向?qū)ο蟮膬?nèi)存地址,不會設(shè)置為nil,而當這個內(nèi)存地址再被使用時,就有可能會產(chǎn)生崩潰。

Q:weak如何實現(xiàn)自動賦nil?

答:runtime維護了一個weak表。用來存儲某個對象的所有弱引用指針。weak表本質(zhì)是一個哈希表,key是對象的內(nèi)存地址,value是該對象的所有弱引用指針的地址的數(shù)組。當對象被釋放時,會根據(jù)對象的內(nèi)存地址,從weak表中獲取所有弱引用指針的地址數(shù)組,然后遍歷數(shù)組,將其地址中的數(shù)據(jù)置為nil,最后再將此keyvalue對從weak表移除。

Q:以下代碼會出現(xiàn)什么問題?(深淺拷貝)

@property (copy) NSMutableArray *array;

答: 不論賦值過來的是NSMutableArray還是NSArray對象,進行copy操作后都是NSArray對象(深拷貝)。由于屬性被聲明為NSMutableArray類型,就不可避免的會有調(diào)用方去調(diào)用它的添加對象、移除對象等一些方法,此時由于copy的結(jié)果是NSArray不可變對象,對NSArray對象調(diào)用添加對象、移除對象等方法,就會產(chǎn)生程序異常。

參考:
OC - 屬性關(guān)鍵字和所有權(quán)修飾符
iOS內(nèi)存管理(6)--NSArray與NSMutableArray用copy修飾還是strong
OC中的屬性關(guān)鍵字

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

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