iOS-@inerface的11條規範寫法

來源:互聯網
上載者:User

標籤:重載   imageview   而不是   tap   uitable   png   touch   target   詳細說明   

總結一些interface聲明時的規範,相關宏的介紹,定義方法時有用的修飾符,編寫注釋的規範,最終寫出一個合格的標頭檔。

  • 1.讀寫權限
    • 1.1執行個體變數的@public,@protected,@private關鍵字
    • 1.2屬性的readonly,readwrite關鍵字
  • 2.前向聲明
  • 3.只暴露必要的介面和實現
    • 3.1不要暴露任何只在類內部使用的私人方法
    • 3.2不要在標頭檔裡聲明類內部遵循的protocol
  • 4.nullability說明
  • 5.定義枚舉
    • 5.1 NS_ENUM
    • 5.2 NS_OPTIONS
    • 5.3 字串枚舉
  • 6.使用extern向外部提供唯讀常量
  • 7.向子類和category提供父類的私人屬性
  • 8.標明designated initializer
  • 9.API版本控制
    • 9.1 available
    • 9.2 unavailable
    • 9.3 deprecated
  • 10.額外的修飾符
    • 10.1泛型
    • 10.2 NS_REQUIRES_SUPER
    • 10.3 NS_NOESCAPE
  • 11.寫注釋
    • 11.1單行注釋
    • 11.2多行注釋
    • 11.3枚舉注釋
    • 11.4幾個注釋約定
1.讀寫權限

.h檔案裡的聲明是用於暴露給外部的介面,而類內部的私人方法、私人屬性和執行個體變數,應該放到.m檔案的interface extension裡。

1.1 執行個體變數的@public,@protected,@private關鍵字

這3個關鍵字用於修飾執行個體變數,不能用於修飾屬性。當錯誤地使用了執行個體變數時,Xcode會報錯提示。

關鍵字 說明
@private 作用範圍只能在自身類
@protected 作用範圍在自身類和繼承自己的子類,什麼都不寫,預設是此屬性。
@public 作用範圍最大,在任何地方。

範例程式碼:

//SearchManager.h@interface SearchManager : NSObject {    @public    NSInteger *state;    @public    NSInteger *timeout;    @protected id *searchAPI;    @private   id _privateIvar;}@end

由於會暴露私人變數,並且沒有@property的一些進階關鍵字,很少在標頭檔裡聲明執行個體變數。優先使用@property。

1.2 屬性的readonly,readwrite關鍵字

標頭檔中的屬性是用於描述這個對象的一系列特性集合。 聲明@property時,在.h裡使用readonly,讓外部只有讀的許可權,在.m裡使用readwrite,使內部擁有讀寫權限。

範例程式碼:

//SearchManager.h@interface SearchManager : NSObject@property (nonatomic, readonly) NSInteger * state;@end
//SearchManager.m@interface SearchManager : NSObject@property (nonatomic, readwrite) NSInteger * state;@end
2.前向聲明

當在@interface的介面裡用到了其他類,不要在.h裡直接匯入類的標頭檔,這樣會讓使用此標頭檔的地方也匯入這些不必要的其他標頭檔。正確的做法是使用關鍵字@class進行前向聲明。當然,如果是繼承了父類,還是需要import父類的標頭檔。 範例程式碼:

//SearchManager.h#import "SearchManagerBase.h"//匯入父類的標頭檔@class LocationModel;//前向聲明LocationModel類typedef void(^LocationSearchCompletionHandler)(LocationModel *location, NSError *error);@interface LocationSearchManager : SearchManagerBase- (void)searchLocationWithKeyword:(NSString *)keyword completionHandler:(LocationSearchCompletionHandler)completionHandler;@end

使用@class會告訴編譯器有這麼一個類存在,但是現在並不關心這個類的具體實現,等到調用者在.m裡使用的時候再import這個類即可。使用@class和@protocol分別聲明一個類和一個protocol。 使用前向引用的原因有兩個:

  • 提升編譯效率。 如果import了LocationModel.h,那麼當LocationModel.h的內容發生變化時,所有import了LocationModel.h的地方都需要重新編譯。如果.m引用了SearchManager.h,但是並沒有使用LocationModel,就會增加不必要的編譯,降低開發效率。
  • 解決交叉引用的問題。 如果類A的標頭檔import了B,類B的標頭檔import了A,這樣在編譯時間會報錯:“can not find interface declaration”,這是因為Objective-C不允許交叉引用。
3.只暴露必要的介面和實現3.1不要暴露任何只在類內部使用的私人方法

標頭檔裡只聲明那些給外部使用的公開方法,並且在設計時需要考慮到可測試性,遵循單一職責。 私人方法只定義在類內部,並且為了進行區別,建議在私人方法前加上首碼,例如- (void)p_myPrivateMethod。 由於Apple在它的編碼規範裡聲明了,Apple公司擁有底線的方法首碼,就像它擁有NS,UI這些類名首碼一樣,因此不建議我們的私人方法直接使用底線作為首碼。否則,當你在繼承Cocoa Touch的類時,有可能會覆蓋父類的私人方法,造成難以調試的錯誤。

3.2不要在標頭檔裡聲明類內部遵循的protocol

錯誤的範例程式碼:

//SearchManager.h@interface SearchManager : NSObject<NSCoding, UITableViewDelegate>@property (nonatomic, readonly) NSInteger * state;@end

UITableViewDelegate是類內部使用時遵循的protocol,沒有必要暴露給外部,因此應該放到.m檔案裡。 而NSCoding則描述了類的特性,用於告訴外部本類可以使用歸檔,因此應該放在標頭檔裡。

4.nullability說明

在聲明時,可以使用下列關鍵字描述對象是否可以為nil。

關鍵字 說明
nullable 可空,用於描述objc對象
nonnull 不可空,用於描述objc對象
null_unspecified 不確定,用於描述objc對象
null_resettable set可空,get不為空白。僅用於property
_Nullable 可空,用於描述C指標和block
_Nonnull 不可空,用於描述C指標和block
_Null_unspecified 不確定,用於描述C指標和block

範例程式碼:

//SearchManager.h#import "SearchManagerBase.h"@class LocationModel;typedef void(^LocationSearchCompletionHandler)(LocationModel *_Nullable location, NSError *_Nullable error);@interface LocationSearchManager : SearchManagerBase- (void)searchLocationWithKeyword:(nonnull NSString *)keyword completionHandler:(LocationSearchCompletionHandler _Nonnull)completionHandler;@end

如果向一個使用nonnull修飾的值賦空,編譯器會給出警告。 在開發時,大部分時候使用的都是nonnull,因此Apple提供了一對宏NS_ASSUME_NONNULL_BEGINNS_ASSUME_NONNULL_END來進行快速修飾,寫在兩個宏之間的屬性、方法,均會使用nonnull修飾。 範例程式碼:

//LocationSearchManager.h#import "SearchManagerBase.h"@class LocationModel;NS_ASSUME_NONNULL_BEGINtypedef void(^LocationSearchCompletionHandler)(LocationModel *_Nullable location, NSError *_Nullable error);@interface LocationSearchManager : SearchManagerBase- (void)searchLocationWithKeyword:(NSString *)keyword completionHandler:(LocationSearchCompletionHandler)completionHandler;@endNS_ASSUME_NONNULL_END
5.定義枚舉

關於NS_ENUM和NS_OPTIONS的區別,參考這裡。 簡單來說,NS_OPTIONS提供了按位元遮罩的功能。

5.1 NS_ENUM

範例程式碼:

typedef NS_ENUM(NSInteger,SearchState) {    SearchStateNotSearch,    SearchStateSearching,    SearchStateSearchFinished,    SearchStateSearchFailed};
5.2 NS_OPTIONS

範例程式碼,參考NSKeyValueObserving.h

typedef NS_OPTIONS(NSUInteger, NSKeyValueObservingOptions) {    NSKeyValueObservingOptionNew,    NSKeyValueObservingOptionOld,    NSKeyValueObservingOptionInitial,    NSKeyValueObservingOptionPrior};

在使用時就可以用|組合多個option:

[_webView addObserver:self forKeyPath:@"title" options:NSKeyValueObservingOptionInitial | NSKeyValueObservingOptionNew context:NULL];
5.3 字串枚舉

當使用字典作為參數傳遞,或者作為傳回值時,往往難以直接提供字典的key,現在使用字串枚舉即可解決這個問題。 範例程式碼,參考NSKeyValueObserving.h

//使用NS_STRING_ENUM宏,定義了一個枚舉類型typedef NSString * NSKeyValueChangeKey NS_STRING_ENUM;FOUNDATION_EXPORT NSKeyValueChangeKey const NSKeyValueChangeKindKey;FOUNDATION_EXPORT NSKeyValueChangeKey const NSKeyValueChangeNewKey;FOUNDATION_EXPORT NSKeyValueChangeKey const NSKeyValueChangeOldKey;FOUNDATION_EXPORT NSKeyValueChangeKey const NSKeyValueChangeIndexesKey;FOUNDATION_EXPORT NSKeyValueChangeKey const NSKeyValueChangeNotificationIsPriorKey;//使用泛型,聲明了change參數用到的key,是在NSKeyValueChangeKey的枚舉範圍中- (void)observeValueForKeyPath:(nullable NSString *)keyPath ofObject:(nullable id)object change:(nullable NSDictionary<NSKeyValueChangeKey, id> *)change context:(nullable void *)context;
6.使用extern向外部提供唯讀常量

這不關@interface的事,但是和標頭檔有關,就放在一起說明了。

//SearchManager.hextern NSString *const SearchErrorDomain;extern NSInteger SearchDefaultTimeout;@interface SearchManager : NSObject@end
//SearchManager.mNSString *const SearchErrorDomain = @"SearchErrorDomain";const NSInteger SearchDefaultTimeout = 20;@interface SearchManager()@end
7.向子類和category提供父類的私人屬性

由於類的標頭檔只存放那些暴露給外部的屬性和方法,在遇到這些情況時,會遇到障礙:

  • 在子類裡或者category裡,想要使用父類定義在.m裡的私人屬性。
  • 在類的標頭檔裡屬性是readonly,但是在子類或者category裡,需要readwrite許可權。 由於這些屬性並沒有暴露在標頭檔裡,因此需要另外建立一個私人標頭檔,用來存放這些需要暴露給子類和category的屬性。 可以參考Apple官方的UIGestureRecognizerSubclass.h。 範例程式碼:
//SearchManager.h@interface SearchManager : NSObject///外部存取,只有讀許可權@property (nonatomic, readonly) SearchState state;@end
//SearchManager.m@interface SearchManager()///內部使用,有讀寫權限@property (nonatomic, assign) SearchState state;///只在內部使用的私人屬性@property (nonatomic, strong) id searchAPI;@end
///暴露給子類和category的私人屬性和私人方法//SearchManagerInternal.h///限制使用此標頭檔,防止被別的類誤用#ifdef SEARCHMANAGER_PROTECTED_ACCESS#import "SearchManager.h"@interface SearchManager()///在internal.h裡,重新聲明為readwrite許可權@property (nonatomic, readwrite, assign) SearchState state;///暴露私人屬性@property (nonatomic, strong) id searchAPI;///暴露私人方法- (void)p_privateMethod;@end#else#error Only be included by SearchManager‘s subclass or category!#endif
///category的實現檔案//SearchManager+Category.m///聲明私人標頭檔的使用許可權#define SEARCHMANAGER_PROTECTED_ACCESS///匯入私人標頭檔#import "SearchManagerInternal.h"@implementation SearchManager(Category)- (void)categoryMethod {    //擁有了讀寫權限    self.state = SearchStateSearching;    //可以訪問私人屬性    [self.searchAPI startSearch];    //可以使用私人方法    [self p_privateMethod];}@end

SearchManagerInternal.h其實也是公開的,其他類也能夠匯入並使用,只能在開發時進行約定。如果想要限制其他類匯入,並且提示錯誤,Internal.h可以使用如下方式:

#ifdef MYCLASS_PROTECTED_ACCESS//聲明部分#else#error Only be included by MYCLASS‘s subclass or category!#endif

這樣在別的類內意外地匯入了Internal.h時就會產生編譯警告,並且無法直接使用。缺點是需要在所有使用到Internal.h的地方都#define MYCLASS_PROTECTED_ACCESS

8.標明designated initializer

指定初始化方法,即接收參數最多的那個初始化方法,其他初始化方法調用它即可,這樣設計的目的是為了保證所有初始化方法都正確地初始化執行個體變數。 在方法後面加上NS_DESIGNATED_INITIALIZER宏即可。這樣,當你子類化這個類時,在子類的初始化方法裡如果沒有正確地調用父類的designated initializer,編譯器就會給出警告。 執行個體代碼:

@interface WKWebView : UIView- (instancetype)initWithFrame:(CGRect)frame configuration:(WKWebViewConfiguration *)configuration NS_DESIGNATED_INITIALIZER;- (nullable instancetype)initWithCoder:(NSCoder *)coder NS_DESIGNATED_INITIALIZER;@end

關於designated initializer更詳細的說明,參考:

  • Objective-C 拾遺:designated initializer
  • 正確編寫Designated Initializer的幾個原則
9.API版本控制

在更新介面,或者開發framework時,需要標明版本資訊,告訴使用者此介面的平台限制、作業系統版本、是否可用、是否已棄用等。 蘋果給出了幾個內建的宏用於標明版本,Xcode在檢測到錯誤使用時會給出警告。只需要在方法名後面加上對應的宏即可。

9.1 available

聲明本介面最低支援的作業系統版本。 當你的介面使用了新系統的API,例如iOS8以上才有的UIAlertController,但是項目的deployment target卻是iOS7時,需要標明此介面的版本資訊,讓使用者進行相容。 樣本:

//SearchManager.htypedef NS_ENUM(NSInteger,SearchState) {    SearchStateNotSearch,    SearchStateSearching,    SearchStateSearchFinished,    SearchStateSearchFailed} NS_ENUM_AVAILABLE_IOS(2_0);//此枚舉在iOS2.0以上才能使用NS_CLASS_AVAILABLE_IOS(2_0) //此類在iOS2.0以上才能使用@interface SearchManager : NSObject- (void)reSearch NS_AVAILABLE_IOS(5_0);//此方法在iOS5.0以上才能使用@end

這幾個宏有對應平台的版本,例如NS_AVAILABLE_MAC, NS_AVAILABLE_IOS, NS_AVAILABLE_IPHONE。 iOS10開始提供了新的available宏API_AVAILABLE,用來統一macOS、iOS、watchOS、tvOS幾個平台。

API_AVAILABLE(macos(10.10))API_AVAILABLE(macos(10.9), ios(10.0))API_AVAILABLE(macos(10.4), ios(8.0), watchos(2.0), tvos(10.0))
9.2 unavailable

聲明此介面不可用,大多數時候是用於聲明所在平台限制。 樣本:

@interface SearchManager : NSObject- (void)searchInWatch NS_UNAVAILABLE;//不能用此介面- (void)searchInHostApp NS_EXTENSION_UNAVAILABLE_IOS;//extension裡不能用此介面- (void)search __TVOS_PROHIBITED;//tvOS裡不能用此介面,可修飾枚舉,類,方法,參數@end

iOS10開始提供了新的unavailable宏API_UNAVAILABLE:

API_UNAVAILABLE(macos)API_UNAVAILABLE(watchos, tvos)
9.3 deprecated

聲明此介面已經被棄用,可以同時加註釋註明替代介面。 當deployment target版本號碼設定成大於或等於方法被棄用的版本號碼時,Xcode會給出警告。 樣本:

//註明廢棄類NS_CLASS_DEPRECATED_IOS(2_0, 9_0, "UIAlertView is deprecated. Use UIAlertController with a preferredStyle of UIAlertControllerStyleAlert instead")@interface UIAlertView : UIView@end
//註明廢棄API@interface UIViewController : UIResponder- (void)viewDidUnload NS_DEPRECATED_IOS(3_0,6_0);@end
//註明廢棄枚舉typedef NS_ENUM(NSInteger, UIStatusBarStyle) {    UIStatusBarStyleDefault                                     = 0, // Dark content, for use on light backgrounds    UIStatusBarStyleLightContent     NS_ENUM_AVAILABLE_IOS(7_0) = 1, // Light content, for use on dark backgrounds        UIStatusBarStyleBlackTranslucent NS_ENUM_DEPRECATED_IOS(2_0, 7_0, "Use UIStatusBarStyleLightContent") = 1,    UIStatusBarStyleBlackOpaque      NS_ENUM_DEPRECATED_IOS(2_0, 7_0, "Use UIStatusBarStyleLightContent") = 2,}

iOS10開始提供了新的deprecated宏API_DEPRECATEDAPI_DEPRECATED_WITH_REPLACEMENT。前者可以註明棄用原因,後者可以註明替代介面。

API_DEPRECATED("No longer supported", macos(10.4, 10.8))API_DEPRECATED("No longer supported", macos(10.4, 10.8), ios(2.0, 3.0), watchos(2.0, 3.0), tvos(9.0, 10.0))API_DEPRECATED_WITH_REPLACEMENT("-setName:", tvos(10.0, 10.4), ios(9.0, 10.0))API_DEPRECATED_WITH_REPLACEMENT("SomeClassName", macos(10.4, 10.6), watchos(2.0, 3.0))
10.額外的修飾符10.1 泛型

在聲明時,對集合類型的對象增加泛型的修飾,就可以聲明集合記憶體儲的資料類型。 例如:

@property (nonatomic, strong) NSMutableArray<NSString *> *myArray;

當你向myArray裡放入一個非NSString *類型的對象時,編譯器會給出警告。

@property(nonatomic, strong) NSMutableArray<__kindof UIView *> * viewArray;

_kindof只限定了儲存類型為UIView,因此也可以儲存UIView的子類,例如UIButton。 更詳細的介紹,參考:Objective—C語言的新魅力——Nullability、泛型集合與類型延拓

10.2 NS_REQUIRES_SUPER

NS_REQUIRES_SUPER宏用於聲明子類在重載父類的這個方法時,需要調用父類的方法。例如:

- (void)viewWillAppear:(BOOL)animated NS_REQUIRES_SUPER;
10.3 NS_NOESCAPE

NS_NOESCAPE用於修飾方法中的block型別參數,例如:

@interface NSArray: NSObject- (NSArray *)sortedArrayUsingComparator:(NSComparator NS_NOESCAPE)cmptr@end

作用是告訴編譯器,cmptr這個block在sortedArrayUsingComparator:方法返回之前就會執行完畢,而不是被儲存起來在之後的某個時候再執行。 類似於這樣的實現:

- (void)performWithLock:(NS_NOESCAPE void (^)())block {  // exposed as @noescape to Swift    [myLock lock];    block();    [myLock unlock];}

編譯器知道之後,就會相應地做一些最佳化,例如去掉一些多餘的對self的捕獲、retain、release操作。因為block的存活範圍僅限於本方法內,沒有必要再在block內保留self了。 更詳細的介紹,參考這裡。

11.寫注釋

標頭檔就是文檔,需要讓使用者快速知道這個類的作用。一個好的方法名可以讓使用者快速理解,但大部分時候還是需要相應的注釋。 寫好格式化注釋後,當游標停留在方法名和屬性上時,在Xcode右側的Quick Help欄裡會出現注釋內容,按住option並單擊,也會彈出注釋框。

11.1單行注釋

直接在方法或者屬性聲明的上一行使用///,後面加註釋,同時相容Xcode和appleDoc。Xcode也支援//!,但是appleDoc不支援。

//SearchManagerBase.h///搜尋manager的基類@interface SearchManagerBase : NSObject///搜尋狀態@property (nonatomic, readonly) NSInteger * state;@end
11.2多行注釋

多行注釋使用:

/** 注釋內容*/

Xcode8提供了快速產生格式化注釋的快速鍵:option+command+/。如果方法有參數,會自動添加@param關鍵字,用於描述對應的參數。 Apple提供了官方的headDoc文法,但是很多都已經在Xcode中失效了,而且有些關鍵字也和appleDoc不相容。下面幾種列舉出了在Xcode中仍然有效一些關鍵字:

/** 示範蘋果headDoc的文法。這裡可以寫方法簡介  @brief 方法的簡介(appleDoc不支援此關鍵字) @discussion 方法的詳細說明  @code //範例程式碼(這個在Xcode裡常用,但是appleDoc不支援此關鍵字) UIView *view; @endcode  @bug       存在的bug的說明 @note      需要注意的提示 @warning   警告 @since     iOS7.0 @exception 方法會拋出的異常的說明  @attention 注意,從這裡開始往下的關鍵字,appleDoc都不支援 @author    編寫者 @copyright 著作權 @date      日期 @invariant 不變數 @post      後置條件 @pre       前置條件 @remarks   備忘 @todo      todo text @version   版本 */- (void)sampleMethod;

在Xcode中,就會顯示為這樣:

 

11.3 枚舉注釋

如果要給枚舉注釋,需要在每個枚舉值前注釋,按照如下格式:

///搜尋狀態typedef NS_ENUM(NSInteger,SearchState) {    ///沒有開始搜尋    SearchStateNotSearch,    ///搜尋中    SearchStateSearching,    ///搜尋結束    SearchStateSearchFinished,    ///搜尋失敗    SearchStateSearchFailed};
11.4 幾個注釋約定

需要注釋的內容:

  • 盡量為類添加描述,即便只有一句話。
  • 標明某些參數和屬性的預設值,比如逾時time。
  • 如果屬性是KVO相容的,即外部可以使用KVO監聽此屬性,則在屬性注釋裡聲明。
  • 回調block參數需要說明回調所在的線程,避免讓使用者在block裡進行多餘的線程判斷。
  • 如果需要的話,說明使用此API需要的前置條件,防止被錯誤地調用。
  • 對使用了method swizzling的API進行統一形式的標註,方便遇到runtime的bug時進行排查。
參考
  • blog.sunnyxx.com/2014/04/13/…
  • blog.sunnyxx.com/2015/06/12/… 

[email protected]的11條規範寫法

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.