前言
通過閱讀別人的優秀源碼,你會發現別人的開源API設計中,有一些宏你是經常忽略的,或者你不知道的。通過這些宏,可以讓你的設計的API更加完善,當然看上去也會更加高端~舉個栗子:
其實這些宏,大部分都在NSObjCRuntime.h
中,下面聽我細細分析,當然,文章篇幅過長,如果你有精力有恒心一口氣看完,給你個贊;更多的,本文可做參考文檔,用作查詢,已同步到 個人博客 推薦去我個人博客查看,目錄更加清晰喔~
-
FOUNDATION_EXTERN
#if defined(__cplusplus)
#define FOUNDATION_EXTERN extern "C"
#else
#define FOUNDATION_EXTERN extern
#endif
表示
extern
全局變量,此時并沒有分配內存,需要在.m文件中實現,此時為了支持C和C++混編(__cplusplus
是C++編譯器內部定義的宏,在C++中,需要加
extern"C"
或包含在extern "C"
塊中),注意,此時外界是可以修改這個值,詳細extern
用法可自行查詢相關資料,本文不詳談。
用法如下:
FOUNDATION_EXTERN NSString *name;// h文件
const NSString *name = @"gitKong";// m文件
-
FOUNDATION_EXPORT 、FOUNDATION_IMPORT
#if TARGET_OS_WIN32
#if defined(NSBUILDINGFOUNDATION)
#define FOUNDATION_EXPORT FOUNDATION_EXTERN __declspec(dllexport)
#else
#define FOUNDATION_EXPORT FOUNDATION_EXTERN __declspec(dllimport)
#endif
#define FOUNDATION_IMPORT FOUNDATION_EXTERN __declspec(dllimport)
#else
#define FOUNDATION_EXPORT FOUNDATION_EXTERN
#define FOUNDATION_IMPORT FOUNDATION_EXTERN
#endif
兩個宏也是
extern
只是為了兼容win32
同時適配C++編譯器(其中__declspec(dllexport)
和__declspec(dllimport)
是C++定義的宏,可在 Mrcrosoft Developer 文檔 查詢)
用法如下:
用法跟 FOUNDATION_EXTERN
一樣,不再舉例。
-
NS_INLINE
#if !defined(NS_INLINE)
#if defined(__GNUC__)
#define NS_INLINE static __inline__ __attribute__((always_inline))
#elif defined(__MWERKS__) || defined(__cplusplus)
#define NS_INLINE static inline
#elif defined(_MSC_VER)
#define NS_INLINE static __inline
#elif TARGET_OS_WIN32
#define NS_INLINE static __inline__
#endif
#endif
內聯函數 inline static修飾,針對當前文件,兼容 win32,適配多個編譯器環境(
__GNUC__
是GCC編譯器定義的一個宏;__MWERKS__
是Metrowerks C/C++編譯器標識宏;_MSC_VER
是Microsoft Visual C++編譯器標識宏,其中:__inline__
、inline
、__inline
、__attribute__((always_inline))
只是不同編譯器對應的keyword而已;GNU C的一大特色就是__attribute__
機制。__attribute__
可以設置 函數屬性、變量屬性 和 類型屬性;例如:函數聲明中的__attribute__((noreturn))
,就是告訴編譯器這個函數不會返回給調用者,以便編譯器在優化時去掉不必要的函數返回代碼)
用法如下:
NS_INLINE int add(){
// do something
}
-
FOUNDATION_STATIC_INLINE
#if !defined(FOUNDATION_STATIC_INLINE)
#define FOUNDATION_STATIC_INLINE static __inline__
#endif
表示static內聯函數,可以看出,Xcode 的LLVM編譯器(前身clang)使用
__inline__
(Xcode 4之前是GCC)
用法跟 NS_INLINE
一樣,不再舉例。
-
FOUNDATION_EXTERN_INLINE
#if !defined(FOUNDATION_EXTERN_INLINE)
#define FOUNDATION_EXTERN_INLINE extern __inline__
#endif
表示全局內聯函數
用法跟 NS_INLINE
一樣,不再舉例。
-
NS_REQUIRES_NIL_TERMINATION
#if !defined(NS_REQUIRES_NIL_TERMINATION)
#if TARGET_OS_WIN32
#define NS_REQUIRES_NIL_TERMINATION
#else
#if defined(__APPLE_CC__) && (__APPLE_CC__ >= 5549)
#define NS_REQUIRES_NIL_TERMINATION __attribute__((sentinel(0,1)))
#else
#define NS_REQUIRES_NIL_TERMINATION __attribute__((sentinel))
#endif
#endif
#endif
表示可變函數最后一個參數需要添加
NULL
或nil
,適配win32
,__APPLE_CC__
表示 Apple GCC編譯器,值為6000,至于定義中為啥判斷5549,筆者也找不到說法,GCC
在Xcode4
之后已經改用LLVM
了,可參考 Clang 中__APPLE_CC__
宏解釋;sentinel 是哨兵的意思,__attribute__((sentinel))
就是說,可變參數函數最后需要一個NULL
或nil
作為參數,sentinel(0,0)
第一個參數0表示需要一個NULL
或nil
,1表示需要兩個,依次類增,第二個參數只能是0或1(execle 的時候就是1)。可查看 GCC 中 Function-Attributes 詳細解釋
截圖如下:
用法如下: 注意,如果使用C函數,NS_REQUIRES_NIL_TERMINATION
需要修飾聲明,修飾函數定義會報警告
// __attribute__((sentinel)) 用于聲明,用于實現會警告,此時就提示最后一個參數需要添加NULL
void mutableParams(NSString *first, ...) NS_REQUIRES_NIL_TERMINATION;
void mutableParams(NSString *first, ...){
va_list args;
va_start(args, first);
NSString *next;
while ((next = va_arg(args, NSString *))) {
NSLog(@"%@",next);
}
va_end(args);
}
-
NS_BLOCKS_AVAILABLE
#if !defined(NS_BLOCKS_AVAILABLE)
#if __BLOCKS__ && (MAC_OS_X_VERSION_10_6 <= MAC_OS_X_VERSION_MAX_ALLOWED || __IPHONE_4_0 <= __IPHONE_OS_VERSION_MAX_ALLOWED)
#define NS_BLOCKS_AVAILABLE 1
#else
#define NS_BLOCKS_AVAILABLE 0
#endif
#endif
表示當前的 block 是否有效,iOS 4.0之后都適用的(因為block 是 iOS 4.0 之后才有的)
用法如下:
#if NS_BLOCKS_AVAILABLE
if(self.completionBlock){
self.completionBlock();
}
#else
// do something
#endif
-
NS_NONATOMIC_IOSONLY
// Marks APIs whose iOS versions are nonatomic, that is cannot be set/get from multiple threads safely without additional synchronization
#if !defined(NS_NONATOMIC_IOSONLY)
#if TARGET_OS_IPHONE
#define NS_NONATOMIC_IOSONLY nonatomic
#else
#if __has_feature(objc_property_explicit_atomic)
#define NS_NONATOMIC_IOSONLY atomic
#else
#define NS_NONATOMIC_IOSONLY
#endif
#endif
#endif
表示iOS 上是
nonatomic
,適配了非iOS,其中__has_feature
表示當前新特性是否被Clang編譯器支持和符合當前語言標準(可參考,其中__has_feature
的參數可在 PPMacroExpansion_8cpp_so 這查看可知,objc_property_explicit_atomic
參數表示Clang編譯器是否支持atomic
關鍵字,因為OSX中不支持nonatomic
,因此OSX只有atomic
)
用法如下:
@property (NS_NONATOMIC_IOSONLY,copy) NSString *className;
-
NS_NONATOMIC_IPHONEONLY
// Use NS_NONATOMIC_IOSONLY instead of this older macro
#if !defined(NS_NONATOMIC_IPHONEONLY)
#define NS_NONATOMIC_IPHONEONLY NS_NONATOMIC_IOSONLY
#endif
歷史原因,等同
NS_NONATOMIC_IOSONLY
,蘋果建議使用NS_NONATOMIC_IOSONLY
用法和NS_NONATOMIC_IOSONLY
一樣
-
NS_FORMAT_FUNCTION(F,A)
// Marks APIs which format strings by taking a format string and optional varargs as arguments
#if !defined(NS_FORMAT_FUNCTION)
#if (__GNUC__*10+__GNUC_MINOR__ >= 42) && (TARGET_OS_MAC || TARGET_OS_EMBEDDED)
#define NS_FORMAT_FUNCTION(F,A) __attribute__((format(__NSString__, F, A)))
#else
#define NS_FORMAT_FUNCTION(F,A)
#endif
#endif
- 表示是
Format string
方案來實現不定參數(不同于哨兵方案NS_REQUIRES_NIL_TERMINATION
(va_list)) GCC 3.2.1 will define__GNUC__
to 3,__GNUC_MINOR__
to 2 可參考
-
__attribute__((format))
標示一個可變參函數使用了Format string
,從而在編譯時對其進行檢查,其定義為format (archetype, string-index, first-to-check)
,其中archetype
代表format string
的類型,它可以是printf
,scanf
,strftime
或者strfmon
,Cocoa
開發者還可以使用__NSString__
來指定其使用和[NSString stringWithFormat:]
與NSLog()
一致的Format string
規則。string-index
代表Format string
是第幾個參數,first-to-check
則代表了可變參數列表從第幾個參數開始 - F 就是
Format string
是第F個參數,A 就是代表可變參數列表是從第A個參數開始
用法如下:
- (void)append:(NSString *)format,...NS_FORMAT_FUNCTION(1,2){
// do something
}
[self append:@"%@%@",@"hello",@"gitKong"];
如何使用Format string 方式實現系統的 stringWithFormat
,而使用 Sentinel value
(哨兵)方式,很簡單:
- (NSString *)append:(NSString *)format,...NS_FORMAT_FUNCTION(1,2){
va_list ap;
va_start(ap, format);
NSString *information = [[NSString alloc] initWithFormat:format arguments:ap];
va_end(ap);
return information;
}
-
NS_FORMAT_ARGUMENT(A)
// Marks APIs which are often used to process (take and return) format strings, so they can be used in place of a constant format string parameter in APIs
#if !defined(NS_FORMAT_ARGUMENT)
#if defined(__clang__)
#define NS_FORMAT_ARGUMENT(A) __attribute__ ((format_arg(A)))
#else
#define NS_FORMAT_ARGUMENT(A)
#endif
#endif
表示函數參數個數,A 表示約束的參數下標,一般默認從1開始,不能超過真實參數個數(可查),被約束的參數必須是
String type
,__clang__
代表 Clang編譯器,只限于此編譯器才有效,注意:
- 使用此宏后修飾的API必須有返回值,而且返回值必須是
String type
。 - 如果修飾的是C函數,最好在函數聲明中修飾。
- 如果參數被約束,那么參數必須是一個
String type
用法如下:
NSString * testArgument(NSString *name,NSString *age,NSString *sex) NS_FORMAT_ARGUMENT(3);
NSString * testArgument(NSString *name,NSString *age,NSString *sex){
return @"gitKong";
}
- (NSString *)testArg:(NSString *)name age:(NSString *)age NS_FORMAT_ARGUMENT(1){
return [NSString stringWithFormat:@"hello %@,i am %@ years old",name,age];
}
-
__has_attribute、__has_extension、__has_feature
// Some compilers provide the capability to test if certain features are available. This macro provides a compatibility path for other compilers.
#ifndef __has_feature
#define __has_feature(x) 0
#endif
#ifndef __has_extension
#define __has_extension(x) 0
#endif
// Some compilers provide the capability to test if certain attributes are available. This macro provides a compatibility path for other compilers.
#ifndef __has_attribute
#define __has_attribute(x) 0
#endif
如同GCC編譯器, Clang也支持
__attribute__
, 而且對它進行的一些擴展,如果要檢查指定屬性的可用性,你可以使用__has_attribute
指令,可在LLVM
的 LanguageExtensions 中查詢;ns_returns_not_retained
和objc_returns_inner_pointer
可查詢 LLVM
的 AutomaticReferenceCounting
摘自文檔(針對
__attribute__
):This function-like macro takes a single identifier argument that is the name of a GNU-style attribute. It evaluates to 1 if the attribute is supported by the current compilation target, or 0 if not.
- 其中
__has_attribute(x)
、__has_feature(x)
為 0,表示 Compatibility with non-clang compilers,意思就是沒有Clang編碼__has_extension
為 0,表示 Compatibility with pre-3.0 compilers,意思就是當前的feature 在當前語音中不被Clang 編譯器支持
摘自文檔(針對
__has_feature(x)
、__has_extension
):These function-like macros take a single identifier argument that is the name of a feature.__has_feature
evaluates to 1 if the feature is both supported by Clang and standardized in the current language standard or 0 if not (but see below), while__has_extension
evaluates to 1 if the feature is supported by Clang in the current language (either as a language extension or a standard language feature) or 0 if not.
用法如下:
__has_attribute(x)
其實跟 GCC編譯器的 __attribute__
類似,__attribute__
的 key 可查詢 Attributes Type
__has_feature(x)
、 __has_extension
中的 key 可查詢 Clang API Documentation 的PPMacroExpansion_8cpp_source 在 00865
行開始
-
NS_RETURNS_RETAINED、NS_RETURNS_NOT_RETAINED
// Marks methods and functions which return an object that needs to be released by the caller but whose names are not consistent with Cocoa naming rules. The recommended fix to this is to rename the methods or functions, but this macro can be used to let the clang static analyzer know of any exceptions that cannot be fixed.
// This macro is ONLY to be used in exceptional circumstances, not to annotate functions which conform to the Cocoa naming rules.
#if __has_feature(attribute_ns_returns_retained)
#define NS_RETURNS_RETAINED __attribute__((ns_returns_retained))
#else
#define NS_RETURNS_RETAINED
#endif
// Marks methods and functions which return an object that may need to be retained by the caller but whose names are not consistent with Cocoa naming rules. The recommended fix to this is to rename the methods or functions, but this macro can be used to let the clang static analyzer know of any exceptions that cannot be fixed.
// This macro is ONLY to be used in exceptional circumstances, not to annotate functions which conform to the Cocoa naming rules.
#if __has_feature(attribute_ns_returns_not_retained)
#define NS_RETURNS_NOT_RETAINED __attribute__((ns_returns_not_retained))
#else
#define NS_RETURNS_NOT_RETAINED
#endif
表示函數返回值是一個是否需要被release的對象,官方解釋了,這個宏除非特殊情況,不要自己使用,對于準守Cocoa 的命名規范的,這里有篇 sunnyxx 的文章 說明:"編譯器約定,對于
alloc
,init
,copy
,mutableCopy
,new
這幾個家族的方法,后面默認加NS_RETURNS_RETAINED
標識;而其他不指名標識的family的方法默認添加NS_RETURNS_NOT_RETAINED
標識" ,這個也可在LLVM ARC-Method families 中查詢到
用法如下:(摘自sunnyxx 的文章)
@interface Sark : NSObject
+ (instancetype)sarkWithMark:(NSString *)mark NS_RETURNS_NOT_RETAINED; // 1
- (instancetype)initWithMark:(NSString *)mark NS_RETURNS_RETAINED; // 2
@end
-
NS_RETURNS_INNER_POINTER
#ifndef NS_RETURNS_INNER_POINTER
#if __has_attribute(objc_returns_inner_pointer)
#define NS_RETURNS_INNER_POINTER __attribute__((objc_returns_inner_pointer))
#else
#define NS_RETURNS_INNER_POINTER
#endif
#endif
表示返回一個內部C指針,
objc_returns_inner_pointer
可在LLVM ARC 中查詢
摘自文檔:An Objective-C method returning a non-retainable pointer may be annotated with the
objc_returns_inner_pointer
attribute to indicate that it returns a handle to the internal data of an object, and that this reference will be invalidated if the object is destroyed. When such a message is sent to an object, the object’s lifetime will be extended until at least the earliest of:
- the last use of the returned pointer, or any pointer derived from it, in the calling function or
- the autorelease pool is restored to a previous state.
用法如下:(摘自系統API)
@property (nullable, readonly) const char *UTF8String NS_RETURNS_INNER_POINTER; // Convenience to return null-terminated UTF8 representation
-
NS_AUTOMATED_REFCOUNT_UNAVAILABLE
// Marks methods and functions which cannot be used when compiling in automatic reference counting mode.
#if __has_feature(objc_arc)
#define NS_AUTOMATED_REFCOUNT_UNAVAILABLE __attribute__((unavailable("not available in automatic reference counting mode")))
#else
#define NS_AUTOMATED_REFCOUNT_UNAVAILABLE
#endif
表示函數method在ARC中無效,
objc_arc
表示是ARC環境, 可在Clang的PPMacroExpansion_8cpp_source中查詢.Case("objc_arc", LangOpts.ObjCAutoRefCount)
用法如下:
void c_method_test() NS_AUTOMATED_REFCOUNT_UNAVAILABLE{
}
- (void)oc_method_test NS_AUTOMATED_REFCOUNT_UNAVAILABLE{
}
-
NS_AUTOMATED_REFCOUNT_WEAK_UNAVAILABLE
// Marks classes which cannot participate in the ARC weak reference feature.
#if __has_attribute(objc_arc_weak_reference_unavailable)
#define NS_AUTOMATED_REFCOUNT_WEAK_UNAVAILABLE __attribute__((objc_arc_weak_reference_unavailable))
#else
#define NS_AUTOMATED_REFCOUNT_WEAK_UNAVAILABLE
#endif
表示標記的類在ARC中無法進行弱引用,
__has_attribute
是 Clang編譯器對GCC編譯器的拓展,表示詳細描述,objc_arc_weak_reference_unavailable
可在 LLVM 的ARC 查詢
用法如下:
NS_AUTOMATED_REFCOUNT_WEAK_UNAVAILABLE @interface MacrosIntroduction : NSObject
@end
-
NS_REQUIRES_PROPERTY_DEFINITIONS
// Marks classes that must specify @dynamic or @synthesize for properties in their @implementation (property getters & setters will not be synthesized unless the @synthesize directive is used)
#if __has_attribute(objc_requires_property_definitions)
#define NS_REQUIRES_PROPERTY_DEFINITIONS __attribute__((objc_requires_property_definitions))
#else
#define NS_REQUIRES_PROPERTY_DEFINITIONS
#endif
表示標記的類必須在
@implementation
中為其屬性實現@dynamic
or@synthesize
,其中objc_requires_property_definitions
可在ConceptClang API Documentation -- Extended 中的AttrParsedAttrKinds.inc 查詢,如果不實現,使用屬性的setter
orgetter
方法的時候就會crash:Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[MacrosIntroduction setClassName:]: unrecognized selector sent to instance 0x60000001d2e0'
用法如下:
NS_REQUIRES_PROPERTY_DEFINITIONS @interface MacrosIntroduction : NSObject
@property (NS_NONATOMIC_IPHONEONLY,copy) NSString *className;
@end
@implementation MacrosIntroduction
@synthesize className;
@end
-
NS_REPLACES_RECEIVER
// Decorates methods in which the receiver may be replaced with the result of the method.
#if __has_feature(attribute_ns_consumes_self)
#define NS_REPLACES_RECEIVER __attribute__((ns_consumes_self)) NS_RETURNS_RETAINED
#else
#define NS_REPLACES_RECEIVER
#endif
替換接收者,就是替換成另一個對象,類似
self = [super init]
一樣,可在
Clang 中的annotations 查詢
用法如下:
- (instancetype)hello NS_REPLACES_RECEIVER;// 此時方法命名不規范,只是作演示效果
- (instancetype)hello{
return self = [super init];
}
系統例子:(NSObject (NSCoderMethods)
)
- (nullable id)awakeAfterUsingCoder:(NSCoder *)aDecoder NS_REPLACES_RECEIVER;
上面的方法在 sunnyxx 的 xib的動態橋接 一文中有說明,也舉了個好例
-
NS_RELEASES_ARGUMENT
#if __has_feature(attribute_ns_consumed)
#define NS_RELEASES_ARGUMENT __attribute__((ns_consumed))
#else
#define NS_RELEASES_ARGUMENT
#endif
其實在ARC 下默認的參數都會準守這個,當函數或者方法執行完畢后,就會調用對象參數的
release
方法,其中ns_consumed
可查詢 clang-analyzer annotations
摘自文檔:The 'ns_consumed' attribute can be placed on a specific parameter in either the declaration of a function or an Objective-C method. It indicates to the static analyzer that a release message is implicitly sent to the parameter upon completion of the call to the given function or method. The Foundation framework defines a macro
NS_RELEASES_ARGUMENT
that is functionally equivalent to theNS_CONSUMED
macro shown below.
用法如下:
- (void)oc_method_test1:(NSString *)name age:(NS_RELEASES_ARGUMENT NSString *)age{
NSLog(@"age = %@",age);
}
-
NS_VALID_UNTIL_END_OF_SCOPE
// Mark local variables of type 'id' or pointer-to-ObjC-object-type so that values stored into that local variable are not aggressively released by the compiler during optimization, but are held until either the variable is assigned to again, or the end of the scope (such as a compound statement, or method definition) of the local variable.
#ifndef NS_VALID_UNTIL_END_OF_SCOPE
#if __has_attribute(objc_precise_lifetime)
#define NS_VALID_UNTIL_END_OF_SCOPE __attribute__((objc_precise_lifetime))
#else
#define NS_VALID_UNTIL_END_OF_SCOPE
#endif
#endif
表明存儲在某些局部變量中的值在優化時不應該被編譯器強制釋放,翻譯官方:局部變量標記為id類型或者是指向ObjC對象類型的指針,以便存儲在這些局部變量中的值在優化時不會被編譯器強制釋放。相反,這些值會在變量再次被賦值之前或者局部變量的作用域結束之前都會被保存。其中
objc_precise_lifetime
可在 LLVM ARC 可查
- 至于具體的應用場景也說不出,反正遇到了就知道有這個東西可以防止提前釋放。
用法如下:
- (void)oc_method_test {
NS_VALID_UNTIL_END_OF_SCOPE id className = @"xxx";
}
-
NS_ROOT_CLASS
// Annotate classes which are root classes as really being root classes
#ifndef NS_ROOT_CLASS
#if __has_attribute(objc_root_class)
#define NS_ROOT_CLASS __attribute__((objc_root_class))
#else
#define NS_ROOT_CLASS
#endif
#endif
表示當前修飾的類就是根類
用法如下:(NSObject 類就是 OBJC_ROOT_CLASS
跟 NS_ROOT_CLASS
一樣,都是 __attribute__((objc_root_class))
)
NS_ROOT_CLASS
@interface MacrosIntroduction
@end
此時可以實現類似 NSObject
、NSProxy
一樣,注意,此時去掉 NS_ROOT_CLASS
就會編譯失敗
拓展:
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wobjc-root-class"
同樣可以避免編譯失敗,但此時的作用是,忽略編譯器對根類的警告,意味著這樣做不安全,但是 __attribute__((objc_root_class))
在以前的 GCC Objective-C extension
編譯器是無法識別
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wobjc-root-class"
@interface MacrosOtherClass
@end
那么此時就好玩了,我完全可以仿造一個類似 NSObject
的類,至于對象的實例化,其實就是需要一個 isa
指針,指向起元類,看 NSObject
類定義就發現也是有這么一個 isa
指針,這里有篇 文章,就是講解這個東西,對象的創建沒有那么簡單,可以參考霜大佬的 Objc 對象的今生今世,大家有興趣可以嘗試嘗試,筆者試了,發現并沒有想象中那么簡單,待研究,有機會再開新文章介紹。
-
NS_REQUIRES_SUPER
#ifndef NS_REQUIRES_SUPER
#if __has_attribute(objc_requires_super)
#define NS_REQUIRES_SUPER __attribute__((objc_requires_super))
#else
#define NS_REQUIRES_SUPER
#endif
#endif
表示標志子類繼承這個方法時需要調用
super
,否則給出編譯警告,其中objc_requires_super
可在 LLVM 的 AttributeReference 中查詢,注意protocol
中無效
摘自文檔:
- Some Objective-C classes allow a subclass to override a particular method in a parent class but expect that the overriding method also calls the overridden method in the parent class. For these cases, we provide an attribute to designate that a method requires a “call to super” in the overriding method in the subclass.
- This attribute can only be applied the method declarations within a class, and not a protocol. Currently this attribute does not enforce any placement of where the call occurs in the overriding method (such as in the case of -dealloc where the call must appear at the end). It checks only that it exists.
用法如下:
- (void)oc_method_mustCallSuper NS_REQUIRES_SUPER;// 父類中聲明
- (void)oc_method_mustCallSuper{
[super oc_method_mustCallSuper];// 子類中實現需要調用super
}
-
NS_DESIGNATED_INITIALIZER
#ifndef NS_DESIGNATED_INITIALIZER
#if __has_attribute(objc_designated_initializer)
#define NS_DESIGNATED_INITIALIZER __attribute__((objc_designated_initializer))
#else
#define NS_DESIGNATED_INITIALIZER
#endif
#endif
指定類的初始化方法。指定初識化方法并不是對使用者。而是對內部的現實,其實就是必須調用父類的
designated initializer
方法,至于的作用,這里 有說明
摘自原文:
To clarify the distinction between designated and secondary initializers clear, you can add the NS_DESIGNATED_INITIALIZER macro to any method in the init family, denoting it a designated initializer. Using this macro introduces a few restrictions:
- The implementation of a designated initializer must chain to a superclass init method (with [super init...]) that is a designated initializer for the superclass.
- The implementation of a secondary initializer (an initializer not marked as a designated initializer within a class that has at least one initializer marked as a designated initializer) must delegate to another initializer (with [self init...]).
- If a class provides one or more designated initializers, it must implement all of the designated initializers of its superclass.
用法如下:
- (instancetype)initWithClassName:(NSString *)name NS_DESIGNATED_INITIALIZER;// 方法聲明
- (instancetype)initWithClassName:(NSString *)name{// 方法實現
if (self = [super init]) {
self.className = name;
}
return self;
}
此時編譯器就會出現警告,原因很簡單,本類中實現了
NS_DESIGNATED_INITIALIZER
那么必須實現父類的NS_DESIGNATED_INITIALIZER
方法
實現父類的 init
方法,因為 init
也是NS_DESIGNATED_INITIALIZER
修飾
- (instancetype)init{
self.className = @"";
return self;
}
當然,此時毫無疑問會出現編譯警告,原因很簡單,本類中實現了
NS_DESIGNATED_INITIALIZER
,那么構造方法中必須調用NS_DESIGNATED_INITIALIZER
方法,因此這里就有個大坑,操作不慎容易造成循環調用,下面會解釋
實現父類的 init
方法并調用NS_DESIGNATED_INITIALIZER
修飾的方法
- (instancetype)init{
return [self initWithClassName:@"gitKong"];
}
- (instancetype)initWithClassName:(NSString *)name{
if (self = [super init]) {
self.className = name;
}
return self;
}
此時編譯警告就沒了,也能正常使用,不過注意
initWithClassName
方法不要調用[self init]
,當然此時編譯器很智能提示你,NS_DESIGNATED_INITIALIZER
修飾的方法只能使用super
調用其他NS_DESIGNATED_INITIALIZER
修飾的方法,如果你忽略這個警告,那么就會出現循環調用了,更多具體的說明可參考 Clang 拾遺之objc_designated_initializer
-
NS_PROTOCOL_REQUIRES_EXPLICIT_IMPLEMENTATION
#ifndef NS_PROTOCOL_REQUIRES_EXPLICIT_IMPLEMENTATION
#if __has_attribute(objc_protocol_requires_explicit_implementation)
#define NS_PROTOCOL_REQUIRES_EXPLICIT_IMPLEMENTATION __attribute__((objc_protocol_requires_explicit_implementation))
#else
#define NS_PROTOCOL_REQUIRES_EXPLICIT_IMPLEMENTATION
#endif
#endif
準守使用
NS_PROTOCOL_REQUIRES_EXPLICIT_IMPLEMENTATION
修飾的協議后,當前類必須實現協議方法,如果此時當前類的父類已經準守協議并實現了協議方法,當前類則無須實現,編譯器不會報警告。當然如果協議方法是@optional
修飾,就不需要實現,編譯器不會報警告。另外可參考 這里,還有這里有比較詳細的 例子。
摘自原文:
- Per more discussion, 'objc_protocol_requires_explicit_implementation' is
refinement that it mainly adds that requirement that a protocol must be
explicitly satisfied at the moment the first class in the class hierarchy
conforms to it. Any subclasses that also conform to that protocol,
either directly or via conforming to a protocol that inherits that protocol,
do not need to re-implement that protocol. - Doing this requires first doing a pass on the super class hierarchy,
gathering the set of protocols conformed to by the super classes,
and then culling those out when determining conformance. This
two-pass algorithm could be generalized for all protocol checking,
and could possibly be a performance win in some cases. For now
we restrict this change to protocols with this attribute to isolate
the change in logic (especially as the design continues to evolve).
用法如下:
NS_PROTOCOL_REQUIRES_EXPLICIT_IMPLEMENTATION
@protocol MacrosIntroductionProtocol <NSObject>
@optional
- (void)sayHello;
@required
- (void)sayHi;
@end
NS_REQUIRES_PROPERTY_DEFINITIONS @interface MacrosIntroduction:NSObject<MacrosIntroductionProtocol>// 準守協議
-
NS_NO_TAIL_CALL
#if __has_attribute(not_tail_called)
#define NS_NO_TAIL_CALL __attribute__((not_tail_called))
#else
#define NS_NO_TAIL_CALL
#endif
直譯過來就是不是最后調用,對于間接調用(下面介紹)的方法是無效的,而且不能使用來修飾虛函數、OC的方法、被
always_inline
修飾的函數,可參考 LLVM 的 AttributeReference,至于詳細的應該
摘自原文:The not_tail_called attribute prevents tail-call optimization on statically bound calls. It has no effect on indirect calls. Virtual functions, objective-c methods, and functions marked as always_inline cannot be marked as not_tail_called.
用法如下:
void NS_NO_TAIL_CALL print(){
printf("---\n");
}
系統的 NSLog
其實也是用 NS_NO_TAIL_CALL
修飾,至于具體用法和效果,筆者也沒找到相關詳細介紹
FOUNDATION_EXPORT void NSLog(NSString *format, ...) NS_FORMAT_FUNCTION(1,2) NS_NO_TAIL_CALL;
什么是間接調用,其實就是用變量存儲起來,OC里面用閉包,因為函數不是一等公民,swift的話就是一等公民了
void hi(){
void (*func)() = &print;
(*func)();// === func() === print()
}
-
NS_UNAVAILABLE、UNAVAILABLE_ATTRIBUTE
#if !defined(NS_UNAVAILABLE)
#define NS_UNAVAILABLE UNAVAILABLE_ATTRIBUTE
#endif
/*
* only certain compilers support __attribute__((unavailable))
*/
#if defined(__GNUC__) && ((__GNUC__ >= 4) || ((__GNUC__ == 3) && (__GNUC_MINOR__ >= 1)))
#define UNAVAILABLE_ATTRIBUTE __attribute__((unavailable))
#else
#define UNAVAILABLE_ATTRIBUTE
#endif
兩個宏代表同一個東西,可以修飾任何東西(包括類、方法、屬性、變量等等),表示當前編譯器并不支持,或者說當前平臺不支持或者無效,其中
__attribute__
上文也有介紹,是GCC
編譯器特有的,用作描述詳細信息,unavailable
語義上就是表示無效的,可以在 LLVM 的 AttributeReference 中查詢。當然,蘋果也解釋了,只有某些編譯器支持__attribute__((unavailable))
摘自原文:This declaration is never available on this platform.
用法如下:
- (void)sayHi NS_UNAVAILABLE;
-
NS_AVAILABLE、NS_DEPRECATED 等
#include <CoreFoundation/CFAvailability.h>
#define NS_AVAILABLE(_mac, _ios) CF_AVAILABLE(_mac, _ios)
#define NS_AVAILABLE_MAC(_mac) CF_AVAILABLE_MAC(_mac)
#define NS_AVAILABLE_IOS(_ios) CF_AVAILABLE_IOS(_ios)
#define NS_DEPRECATED(_macIntro, _macDep, _iosIntro, _iosDep, ...) CF_DEPRECATED(_macIntro, _macDep, _iosIntro, _iosDep, __VA_ARGS__)
#define NS_DEPRECATED_MAC(_macIntro, _macDep, ...) CF_DEPRECATED_MAC(_macIntro, _macDep, __VA_ARGS__)
#define NS_DEPRECATED_IOS(_iosIntro, _iosDep, ...) CF_DEPRECATED_IOS(_iosIntro, _iosDep, __VA_ARGS__)
#define NS_DEPRECATED_WITH_REPLACEMENT_MAC(_rep, _macIntroduced, _macDeprecated) API_DEPRECATED_WITH_REPLACEMENT(_rep, macosx(_macIntroduced, _macDeprecated)) API_UNAVAILABLE(ios, watchos, tvos)
#define NS_ENUM_AVAILABLE(_mac, _ios) CF_ENUM_AVAILABLE(_mac, _ios)
#define NS_ENUM_AVAILABLE_MAC(_mac) CF_ENUM_AVAILABLE_MAC(_mac)
#define NS_ENUM_AVAILABLE_IOS(_ios) CF_ENUM_AVAILABLE_IOS(_ios)
#define NS_ENUM_DEPRECATED(_macIntro, _macDep, _iosIntro, _iosDep, ...) CF_ENUM_DEPRECATED(_macIntro, _macDep, _iosIntro, _iosDep, __VA_ARGS__)
#define NS_ENUM_DEPRECATED_MAC(_macIntro, _macDep, ...) CF_ENUM_DEPRECATED_MAC(_macIntro, _macDep, __VA_ARGS__)
#define NS_ENUM_DEPRECATED_IOS(_iosIntro, _iosDep, ...) CF_ENUM_DEPRECATED_IOS(_iosIntro, _iosDep, __VA_ARGS__)
#define NS_AVAILABLE_IPHONE(_ios) CF_AVAILABLE_IOS(_ios)
#define NS_DEPRECATED_IPHONE(_iosIntro, _iosDep) CF_DEPRECATED_IOS(_iosIntro, _iosDep)
...
其中很多涉及到都是
AVAILABLE
和DEPRECATED
,其實都表示當前API在不同的操作系統(這里只談蘋果的操作系統)的可用版本、廢棄版本、淘汰版本,其中關鍵就是availability
,在 LLVM 的 AttributeReference 中有詳細參數說明以及其用法舉例,因此本文就不再啰嗦。還有一個visibility
,表示編譯符號可見性,提供兩個值default
和hidden
,這里有文章:控制符號的可見性 作了詳細的說明,文中說了,visibility
可見性控制只能應用到代碼中的C或者C++子集,不能應用到Objective-C的類和方法上。
-
NS_ENUM、NS_OPTIONS
/* NS_ENUM supports the use of one or two arguments. The first argument is always the integer type used for the values of the enum. The second argument is an optional type name for the macro. When specifying a type name, you must precede the macro with 'typedef' like so:
typedef NS_ENUM(NSInteger, NSComparisonResult) {
...
};
If you do not specify a type name, do not use 'typedef'. For example:
NS_ENUM(NSInteger) {
...
};
*/
#define NS_ENUM(...) CF_ENUM(__VA_ARGS__)
#define NS_OPTIONS(_type, _name) CF_OPTIONS(_type, _name)
具體的定義在
CFAvaliability.h
,都是表示枚舉,區別在于NS_ENUM
是通用枚舉,而NS_OPTIONS
則是位移枚舉,位移枚舉的值就是用位移值表示,其中內部對編譯器做了適配,__cplusplus
表示支持C++編譯器,__has_feature(objc_fixed_enum)
表示當前編譯器是否支持固定的沒定義類型,參考 LLVM 的 LanguageExtensions
摘自原文:Use
__has_feature(objc_fixed_enum)
to determine whether support for fixed underlying types is available in Objective-C.
用法就不再舉例,相信大家都用爛了~
-
CF_STRING_ENUM、CF_EXTENSIBLE_STRING_ENUM
#ifndef CF_STRING_ENUM
#if __has_attribute(swift_wrapper)
#define _CF_TYPED_ENUM __attribute__((swift_wrapper(enum)))
#else
#define _CF_TYPED_ENUM
#endif
#define CF_STRING_ENUM _CF_TYPED_ENUM
#endif
#ifndef CF_EXTENSIBLE_STRING_ENUM
#if __has_attribute(swift_wrapper)
#define _CF_TYPED_EXTENSIBLE_ENUM __attribute__((swift_wrapper(struct)))
#else
#define _CF_TYPED_EXTENSIBLE_ENUM
#endif
#define CF_EXTENSIBLE_STRING_ENUM _CF_TYPED_EXTENSIBLE_ENUM
#endif
/* */
#define _NS_TYPED_ENUM _CF_TYPED_ENUM
#define _NS_TYPED_EXTENSIBLE_ENUM _CF_TYPED_EXTENSIBLE_ENUM
#define NS_STRING_ENUM _NS_TYPED_ENUM
#define NS_EXTENSIBLE_STRING_ENUM _NS_TYPED_EXTENSIBLE_ENUM
這兩個宏是iOS 10引入的,可查 CoreFoundation Changes for Objective-C,可惜官方沒有提供具體說明,不過從語義上看應該能猜到,這兩個宏就是為了兼容
swift
的,其中swift_wrapper(enum)
和swift_wrapper(struct)
筆者暫時沒查閱到具體介紹,不過很容易就猜出,一個是表示枚舉,一個表示結構體
用法如下:
typedef CF_STRING_ENUM NS_ENUM(NSInteger ,FLSystemEnum){
FLSystemEnumOne,
FLSystemEnumTwo = 1
} ;
-
NS_ASSUME_NONNULL_BEGIN、NS_ASSUME_NONNULL_END
#define NS_ASSUME_NONNULL_BEGIN _Pragma("clang assume_nonnull begin")
#define NS_ASSUME_NONNULL_END _Pragma("clang assume_nonnull end")
表示當前包在里面的屬性都標記為
nonnull
,如果需要其他處理,則單獨使用其他(例如:nullable
)標記,_Pragma("string")
與#pragma
字符串行為完全相同,在C/C++標準中,#pragma
是一條預處理的指令(preprocessor directive),在C++11中,標準定義了與預處理指令#pragma
功能相同的操作符_Pragma
。簡單地說,#pragma
是用來向編譯器傳達語言標準以外的一些信息,例如我們平時經常使用的#pragma mark - <#name#>
,可參考 C/C++ 預處理器參考,那么上面的其實就是等同:
#pragma clang assume_nonnull begin
#pragma clang assume_nonnull end
用法如下:
//NS_ASSUME_NONNULL_BEGIN
#pragma clang assume_nonnull begin
@interface MacrosIntroduction:NSObject
@property (nonatomic,assign) FLSystemEnum systemEnums;
@property (nullable, nonatomic,copy) NSString *xxx;
@end
#pragma clang assume_nonnull end
//NS_ASSUME_NONNULL_END
-
NS_SWIFT_NAME
#define NS_SWIFT_NAME(_name) CF_SWIFT_NAME(_name)
利用這個宏,可間接使用C函數,具體可參考 蘋果官方 Guides and Sample Code,里面有具體例子講解
摘自原文:
C APIs, such as the Core Foundation framework, often provide functions that create, access, or modify C structures. You can use the CF_SWIFT_NAME macro in your own code to have Swift import C functions as members of the imported structure type.
用法官方文檔有詳細例子,此處不再舉例
還有一些針對 swift 的宏,例如 NS_SWIFT_UNAVAILABLE
(表示在swift
中無效)、NS_NOESCAPE
(swift
中有逃逸概念,默認閉包是noescape)、NS_SWIFT_NOTHROW
(意思就是在swift
中沒有錯誤拋出) ,從語義上就能看什么作用,這里就不再做詳細分析。
NSObjCRuntime.h
最后還有一下基本運算的宏,例如大家熟悉的 YES
or NO
定義、Min
or MAX
、ABS
等
-
最后
花了不少時間,資料總算整理好了,通過整理這份資料,也了解了不少編譯器方面的知識,希望能幫到大家。
如果文中有不對的地方,或者有什么建議,請務必提出喔,喜歡我的文章,可以點個like,加個關注,我會不定時更新文章。
參考資料:
https://gcc.gnu.org/onlinedocs/gcc-4.0.0/gcc/Type-Attributes.html#Type-Attributes
https://gcc.gnu.org/onlinedocs/gcc-4.0.0/gcc/Variable-Attributes.html#Variable-Attributes
https://gcc.gnu.org/onlinedocs/gcc-4.0.0/gcc/Function-Attributes.html#Function-Attributes
https://www.crest.iu.edu/projects/conceptcpp/docs/html-ext/AttrParsedAttrKinds_8inc_source.html
http://docs.huihoo.com/doxygen/clang/r222231/PPMacroExpansion_8cpp_source.html
http://gracelancy.com/blog/2014/05/05/variable-argument-lists/
http://clang-developers.42468.n3.nabble.com/APPLE-CC-macro-td4046240.html#a4046243
https://uranusjr.com/blog/post/53/objective-c-class-without-nsobject/