NSObjCRuntime.h中你不知道的宏

前言

通過閱讀別人的優秀源碼,你會發現別人的開源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

表示可變函數最后一個參數需要添加 NULLnil ,適配 win32 , __APPLE_CC__ 表示 Apple GCC編譯器,值為6000,至于定義中為啥判斷5549,筆者也找不到說法,GCCXcode4
之后已經改用 LLVM 了,可參考 Clang 中 __APPLE_CC__ 宏解釋;sentinel 是哨兵的意思,__attribute__((sentinel)) 就是說,可變參數函數最后需要一個 NULLnil 作為參數,sentinel(0,0) 第一個參數0表示需要一個 NULLnil,1表示需要兩個,依次類增,第二個參數只能是0或1(execle 的時候就是1)。可查看 GCC 中 Function-Attributes 詳細解釋

截圖如下:


Paste_Image.png

用法如下: 注意,如果使用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);
}
缺少NULL報警告

  • 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 的類型,它可以是 printfscanfstrftime或者strfmonCocoa開發者還可以使用__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];
}
修飾C函數實現報警告
參數不是String類型
只約束了第一個參數,因此第二個參數不受限制
必須要返回`String type`

  • __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_retainedobjc_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_source00865 行開始


  • 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{
    
}
無法在ARC中使用

  • 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 or getter 方法的時候就會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
沒有實現 `@dynamic` or `@synthesize`

  • 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];
}
沒有使用 `NS_REPLACES_RECEIVER` 修飾

系統例子: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 the NS_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_CLASSNS_ROOT_CLASS 一樣,都是 __attribute__((objc_root_class))

NS_ROOT_CLASS
@interface MacrosIntroduction

@end

此時可以實現類似 NSObjectNSProxy 一樣,注意,此時去掉 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
}
不調用super,編譯器警告
protocol 中無效
  • 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 方法

必須實現父類的 `NS_DESIGNATED_INITIALIZER` 方法

實現父類的 init 方法,因為 init 也是NS_DESIGNATED_INITIALIZER 修飾

- (instancetype)init{
    self.className = @"";
    return self;
}

當然,此時毫無疑問會出現編譯警告,原因很簡單,本類中實現了 NS_DESIGNATED_INITIALIZER ,那么構造方法中必須調用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

編譯警告:必須使用super

  • 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>// 準守協議
編譯警告:`@required` 修飾的協議方法沒有實現

  • 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)

...

其中很多涉及到都是AVAILABLEDEPRECATED,其實都表示當前API在不同的操作系統(這里只談蘋果的操作系統)的可用版本、廢棄版本、淘汰版本,其中關鍵就是 availability,在 LLVM 的 AttributeReference 中有詳細參數說明以及其用法舉例,因此本文就不再啰嗦。還有一個 visibility ,表示編譯符號可見性,提供兩個值 defaulthidden ,這里有文章:控制符號的可見性 作了詳細的說明,文中說了, 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) 筆者暫時沒查閱到具體介紹,不過很容易就猜出,一個是表示枚舉,一個表示結構體

Not Found

用法如下:

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_NOESCAPEswift中有逃逸概念,默認閉包是noescape)、NS_SWIFT_NOTHROW(意思就是在swift中沒有錯誤拋出) ,從語義上就能看什么作用,這里就不再做詳細分析。

NSObjCRuntime.h 最后還有一下基本運算的宏,例如大家熟悉的 YES or NO定義、Min or MAXABS


  • 最后

    • 花了不少時間,資料總算整理好了,通過整理這份資料,也了解了不少編譯器方面的知識,希望能幫到大家。

    • 如果文中有不對的地方,或者有什么建議,請務必提出喔,喜歡我的文章,可以點個like,加個關注,我會不定時更新文章。


參考資料:

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,363評論 6 532
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,497評論 3 416
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,305評論 0 374
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,962評論 1 311
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,727評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,193評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,257評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,411評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,945評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,777評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,978評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,519評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,216評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,642評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,878評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,657評論 3 391
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,960評論 2 373

推薦閱讀更多精彩內容