總結一些interface聲明時的規範,相關宏的介紹,定義方法時有用的修飾符,編寫註釋的規範,最終寫出一個合格的頭文件。ios
.h文件裏的聲明是用於暴露給外部的接口,而類內部的私有方法、私有屬性和實例變量,應該放到.m文件的interface extension裏。git
這3個關鍵字用於修飾實例變量,不能用於修飾屬性。當錯誤地使用了實例變量時,Xcode會報錯提示。github
關鍵字 | 說明 |
---|---|
@private | 做用範圍只能在自身類 |
@protected | 做用範圍在自身類和繼承本身的子類,什麼都不寫,默認是此屬性。 |
@public | 做用範圍最大,在任何地方。 |
示例代碼:web
//SearchManager.h
@interface SearchManager : NSObject {
@public NSInteger *state;
@public NSInteger *timeout;
@protected id *searchAPI;
@private id _privateIvar;
}
@end
複製代碼
因爲會暴露私有變量,而且沒有@property的一些高級關鍵字,不多在頭文件裏聲明實例變量。優先使用@property。macos
頭文件中的屬性是用於描述這個對象的一系列特性集合。 聲明@property時,在.h裏使用readonly,讓外部只有讀的權限,在.m裏使用readwrite,使內部擁有讀寫權限。swift
示例代碼:api
//SearchManager.h @interface SearchManager : NSObject @property (nonatomic, readonly) NSInteger * state; @end 複製代碼
//SearchManager.m
@interface SearchManager : NSObject
@property (nonatomic, readwrite) NSInteger * state;
@end
複製代碼
當在@interface的接口裏用到了其餘類,不要在.h裏直接導入類的頭文件,這樣會讓使用此頭文件的地方也導入這些沒必要要的其餘頭文件。正確的作法是使用關鍵字@class
進行前向聲明。固然,若是是繼承了父類,仍是須要import父類的頭文件。 示例代碼:bash
//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。 使用前向引用的緣由有兩個:markdown
LocationModel.h
,那麼當LocationModel.h
的內容發生變化時,全部import了LocationModel.h
的地方都須要從新編譯。若是.m引用了SearchManager.h
,可是並無使用LocationModel
,就會增長沒必要要的編譯,下降開發效率。頭文件裏只聲明那些給外部使用的公開方法,而且在設計時須要考慮到可測試性,遵循單一職責。 私有方法只定義在類內部,而且爲了進行區別,建議在私有方法前加上前綴,例如- (void)p_myPrivateMethod
。 因爲Apple在它的編碼規範裏聲明瞭,Apple公司擁有下劃線的方法前綴,就像它擁有NS
,UI
這些類名前綴同樣,所以不建議咱們的私有方法直接使用下劃線做爲前綴。不然,當你在繼承Cocoa Touch的類時,有可能會覆蓋父類的私有方法,形成難以調試的錯誤。app
錯誤的示例代碼:
//SearchManager.h @interface SearchManager : NSObject<NSCoding, UITableViewDelegate> @property (nonatomic, readonly) NSInteger * state; @end 複製代碼
UITableViewDelegate
是類內部使用時遵循的protocol,沒有必要暴露給外部,所以應該放到.m文件裏。 而NSCoding
則描述了類的特性,用於告訴外部本類可使用歸檔,所以應該放在頭文件裏。
在聲明時,可使用下列關鍵字描述對象是否能夠爲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_BEGIN
和NS_ASSUME_NONNULL_END
來進行快速修飾,寫在兩個宏之間的屬性、方法,均會使用nonnull
修飾。 示例代碼:
//LocationSearchManager.h #import "SearchManagerBase.h" @class LocationModel; NS_ASSUME_NONNULL_BEGIN typedef void(^LocationSearchCompletionHandler)(LocationModel *_Nullable location, NSError *_Nullable error); @interface LocationSearchManager : SearchManagerBase - (void)searchLocationWithKeyword:(NSString *)keyword completionHandler:(LocationSearchCompletionHandler)completionHandler; @end NS_ASSUME_NONNULL_END 複製代碼
關於NS_ENUM和NS_OPTIONS的區別,參考這裏。 簡單來講,NS_OPTIONS提供了按位掩碼的功能。
示例代碼:
typedef NS_ENUM(NSInteger,SearchState) {
SearchStateNotSearch,
SearchStateSearching,
SearchStateSearchFinished,
SearchStateSearchFailed
};
複製代碼
示例代碼,參考NSKeyValueObserving.h
:
typedef NS_OPTIONS(NSUInteger, NSKeyValueObservingOptions) {
NSKeyValueObservingOptionNew,
NSKeyValueObservingOptionOld,
NSKeyValueObservingOptionInitial,
NSKeyValueObservingOptionPrior
};
複製代碼
在使用時就能夠用|
組合多個option:
[_webView addObserver:self forKeyPath:@"title" options:NSKeyValueObservingOptionInitial | NSKeyValueObservingOptionNew context:NULL]; 複製代碼
當使用字典做爲參數傳遞,或者做爲返回值時,每每難以直接提供字典的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;
複製代碼
這不關@interface的事,可是和頭文件有關,就放在一塊兒說明了。
//SearchManager.h
extern NSString *const SearchErrorDomain;
extern NSInteger SearchDefaultTimeout;
@interface SearchManager : NSObject
@end
複製代碼
//SearchManager.m NSString *const SearchErrorDomain = @"SearchErrorDomain"; const NSInteger SearchDefaultTimeout = 20; @interface SearchManager() @end 複製代碼
因爲類的頭文件只存放那些暴露給外部的屬性和方法,在遇到這些狀況時,會遇到障礙:
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
。
指定初始化方法,即接收參數最多的那個初始化方法,其餘初始化方法調用它便可,這樣設計的目的是爲了保證全部初始化方法都正確地初始化實例變量。 在方法後面加上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更詳細的說明,參考:
在更新接口,或者開發framework時,須要標明版本信息,告訴使用者此接口的平臺限制、操做系統版本、是否可用、是否已棄用等。 蘋果給出了幾個自帶的宏用於標明版本,Xcode在檢測到錯誤使用時會給出警告。只須要在方法名後面加上對應的宏便可。
聲明本接口最低支持的操做系統版本。 當你的接口使用了新系統的API,例如iOS8以上纔有的UIAlertController,可是項目的deployment target倒是iOS7時,須要標明此接口的版本信息,讓使用者進行兼容。 示例:
//SearchManager.h
typedef 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))
複製代碼
聲明此接口不可用,大多數時候是用於聲明所在平臺限制。 示例:
@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)
複製代碼
聲明此接口已經被棄用,能夠同時加註釋註明替代接口。 當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_DEPRECATED
和API_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)) 複製代碼
在聲明時,對集合類型的對象增長泛型的修飾,就能夠聲明集合內存儲的數據類型。 例如:
@property (nonatomic, strong) NSMutableArray<NSString *> *myArray;
複製代碼
當你向myArray
裏放入一個非NSString *
類型的對象時,編譯器會給出警告。
@property(nonatomic, strong) NSMutableArray<__kindof UIView *> * viewArray;
複製代碼
_kindof
只限定了存儲類型爲UIView
,所以也能夠存儲UIView
的子類,例如UIButton
。 更詳細的介紹,參考:Objective—C語言的新魅力——Nullability、泛型集合與類型延拓
NS_REQUIRES_SUPER
宏用於聲明子類在重載父類的這個方法時,須要調用父類的方法。例如:
- (void)viewWillAppear:(BOOL)animated NS_REQUIRES_SUPER;
複製代碼
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
了。 更詳細的介紹,參考這裏。
頭文件就是文檔,須要讓使用者快速知道這個類的做用。一個好的方法名可讓使用者快速理解,但大部分時候仍是須要相應的註釋。 寫好格式化註釋後,當光標停留在方法名和屬性上時,在Xcode右側的Quick Help欄裏會出現註釋內容,按住option
並單擊,也會彈出註釋框。
直接在方法或者屬性聲明的上一行使用///
,後面加註釋,同時兼容Xcode和appleDoc。Xcode也支持//!
,可是appleDoc不支持。
//SearchManagerBase.h ///搜索manager的基類 @interface SearchManagerBase : NSObject ///搜索狀態 @property (nonatomic, readonly) NSInteger * state; @end 複製代碼
多行註釋使用:
/**
註釋內容
*/
複製代碼
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中,就會顯示爲這樣:
若是要給枚舉註釋,須要在每一個枚舉值前註釋,按照以下格式:
///搜索狀態
typedef NS_ENUM(NSInteger,SearchState) {
///沒有開始搜索
SearchStateNotSearch,
///搜索中
SearchStateSearching,
///搜索結束
SearchStateSearchFinished,
///搜索失敗
SearchStateSearchFailed
};
複製代碼
須要註釋的內容: