iOS宏和__attribute__

本文目錄html

  1. iOS宏的經典用法
  2. Apple的習慣
  3. __attribute__

iOS宏的經典用法

1.常量宏、表達式宏

#define kTabBarH (49.0f)
#define kScreenH [UIScreen mainScreen].bounds.size.height
#define isScreenWidthEqual320 (fabs([UIScreen mainScreen].bounds.size.width - 320) < DBL_EPSILON)

2.帶參數的宏

// 例子1:一樣是一個表達式
#define isNilOrNull(obj) (obj == nil || [obj isEqual:[NSNull null]])
// 例子2:也能夠沒有參數值 使用的時候要加上"()",是在使用的時候單獨成爲一行語句的宏
#define MKAssertMainThread() NSAssert([NSThread isMainThread], @"This method must be called on the main thread")

3.函數宏(是一個沒有返回值的代碼塊,一般當作一行語句使用)

// do {} while(0) 通常是沒有返回值的,僅僅是代碼塊而已,使用do {} while(0)意思是讓它執行一次,至關於直接調用代碼塊
#define NSAssert(condition, desc, ...)  \
    do {                \
    __PRAGMA_PUSH_NO_EXTRA_ARG_WARNINGS \
    if (!(condition)) {     \
            NSString *__assert_file__ = [NSString stringWithUTF8String:__FILE__]; \
            __assert_file__ = __assert_file__ ? __assert_file__ : @"<Unknown File>"; \
        [[NSAssertionHandler currentHandler] handleFailureInMethod:_cmd \
        object:self file:__assert_file__ \
            lineNumber:__LINE__ description:(desc), ##__VA_ARGS__]; \
    }               \
        __PRAGMA_POP_NO_EXTRA_ARG_WARNINGS \
    } while(0)

4.內聯函數 (通常有返回值)

CG_INLINE CGRect
CGRectMake(CGFloat x, CGFloat y, CGFloat width, CGFloat height)
{
  CGRect rect;
  rect.origin.x = x; rect.origin.y = y;
  rect.size.width = width; rect.size.height = height;
  return rect;
}

NS_INLINE BOOL 
MKIsLeapYear() {
    NSCalendar *calendar = [NSCalendar currentCalendar];
    NSInteger year = [calendar component:NSCalendarUnitYear fromDate:[NSDate new]];
    return year % 100 == 0 ? year % 400 == 0 : year % 4 == 0;
}

5.變參宏 函數可變參數

// 例如能夠像以下幾種方式使用NSAssert這個宏
NSAssert(10 > 11, @"錯誤1:%@", @"err desc 1");
NSAssert(10 > 11, @"錯誤1:%@\n錯誤2:%@", @"err desc 1", @"err desc 2");

關於宏定義中的#和##的說明
#有兩個做用:
1.將變量直接轉化爲相應字面量的C語言字符串 如a=10 #a會轉化爲"10"
2.鏈接兩個C字符串,使用以下ios

#define toString(a) #a  // 轉爲字符串
#define printSquare(x) printf("the square of "  #x " is %d.\n",(x)*(x)) // 鏈接字符串

使用以下macos

printf("%s\n", toString(abc)); // abc
printSquare(3); // the square of 3 is 9.

##的常見用處是鏈接,它會將在它以前的語句、表達式等和它以後的語句表達式等直接鏈接。這裏有個特殊的規則,在逗號和__VA_ARGS__之間的雙井號,除了拼接先後文本以外,還有一個功能,那就是若是後方文本爲空,那麼它會將前面一個逗號吃掉。這個特性當且僅當上面說的條件成立時纔會生效,所以能夠說是特例。swift

// 用法1:代碼和表達式直接鏈接
#define combine(a, b) a##b 
#define exp(a, b) (long)(a##e##b)  
// 用法2:給表達式加前綴
#define addPrefixForVar(a) mk_##a 
// 用法3,用於鏈接printf-like方法的format語句和可變參數
#define format(format, ...) [NSString stringWithFormat:format, ##__VA_ARGS__]

使用示例以下:數組

NSLog(@"%zd", combine(10, 556)); // 10556
NSLog(@"%f", combine(10, .556)); // 10.556000
NSLog(@"%tu", exp(2, 3)); // 2000
    
// int x = 10;
int mk_x = 30;
NSLog(@"%d", addPrefixForVar(x));  // 30
    
NSLog(@"%@", format(@"%@_%@", @"good", @"morning"));  // good_morning
NSLog(@"%@", format(@"hello"));  // hello

對於使用##鏈接可變參數的用法,若是不加##會致使編譯沒法經過,這是由於: 這個##在逗號和__VA_ARGS__之間,後面的值不存在的時候 會忽略## 前面的,
也就是說:
當有## 調用format(format) 替換爲 [NSString stringWithFormat:format]
當沒有## 調用format(format) 替換爲 [NSString stringWithFormat:format ,]安全

還有一點要注意的是:###只能用在宏定義中,而不能使用在函數或者方法中。app

Apple使用宏的一些習慣

1.類的聲明除了引用的其餘頭文件 用下面一對宏標記
#define NS_ASSUME_NONNULL_BEGIN _Pragma("clang assume_nonnull begin")
#define NS_ASSUME_NONNULL_END   _Pragma("clang assume_nonnull end")

也可使用下面的兩句函數

#pragma clang assume_nonull begin
#pragma clang assume_nonull end

告訴clang編譯器這二者之間內容非空ui

2. 在類聲明前,方法聲明後都有可用性的標記宏

例如編碼

NS_CLASS_AVAILABLE   // 類以前
NS_AVAILABLE(_mac, _ios) // 方法以後
NS_DEPRECATED(_macIntro, _macDep, _iosIntro, _iosDep, ...) // 方法以後
OBJC_SWIFT_UNAVAILABLE("use 'xxx' instead") // 針對swift特殊說明取代它的類和方法

不一樣功能的類以前的可用性標記不一樣,例如NSAutoreleasePool以前

NS_AUTOMATED_REFCOUNT_UNAVAILABLE
@interface NSAutoreleasePool : NSObject{ ...

對於這些標記究竟幹了些什麼,請看本文第三部分__attribute__。

3.嵌套的宏(常常成對使用)
#define NS_DURING       @try {
#define NS_HANDLER      } @catch (NSException *localException) {
#define NS_ENDHANDLER       }
#define NS_VALUERETURN(v,t) return (v)
#define NS_VOIDRETURN       return


#if __clang__
#define __PRAGMA_PUSH_NO_EXTRA_ARG_WARNINGS \
    _Pragma("clang diagnostic push") \
    _Pragma("clang diagnostic ignored \"-Wformat-extra-args\"")

#define __PRAGMA_POP_NO_EXTRA_ARG_WARNINGS _Pragma("clang diagnostic pop")
#else
#define __PRAGMA_PUSH_NO_EXTRA_ARG_WARNINGS
#define __PRAGMA_POP_NO_EXTRA_ARG_WARNINGS
#endif
4.常用宏來配合條件編譯 (爲了適配當前的硬件環境,系統環境、語言環境、編譯環境)
#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


#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


#if defined(__cplusplus)
#define FOUNDATION_EXTERN extern "C"
#else
#define FOUNDATION_EXTERN extern
#endif
5. 由__attribute__定義的宏到處可見。

(本文在第三部分詳細介紹__attribute__.).
例以下面這些常見的宏都屬於這種:

// NSLog 方法後面使用的宏
#define NS_FORMAT_FUNCTION(F,A) __attribute__((format(__NSString__, F, A)))


//  這些都是在<Foundation/NSObjCRuntime.h>中定義的宏
#define NS_RETURNS_RETAINED __attribute__((ns_returns_retained))
#define NS_RETURNS_NOT_RETAINED __attribute__((ns_returns_not_retained))
#define NS_RETURNS_INNER_POINTER __attribute__((objc_returns_inner_pointer))
#define NS_AUTOMATED_REFCOUNT_UNAVAILABLE __attribute__((unavailable("not available in automatic reference counting mode")))
#define NS_AUTOMATED_REFCOUNT_WEAK_UNAVAILABLE __attribute__((objc_arc_weak_reference_unavailable))
#define NS_REQUIRES_PROPERTY_DEFINITIONS __attribute__((objc_requires_property_definitions)) 
#define NS_REPLACES_RECEIVER __attribute__((ns_consumes_self)) NS_RETURNS_RETAINED
#define NS_RELEASES_ARGUMENT __attribute__((ns_consumed))
#define NS_VALID_UNTIL_END_OF_SCOPE __attribute__((objc_precise_lifetime))
#define NS_ROOT_CLASS __attribute__((objc_root_class))
#define NS_REQUIRES_SUPER __attribute__((objc_requires_super))
#define NS_DESIGNATED_INITIALIZER __attribute__((objc_designated_initializer))
#define NS_PROTOCOL_REQUIRES_EXPLICIT_IMPLEMENTATION __attribute__((objc_protocol_requires_explicit_implementation))
#define NS_CLASS_AVAILABLE(_mac, _ios) __attribute__((visibility("default"))) NS_AVAILABLE(_mac, _ios)
#define NS_CLASS_DEPRECATED(_mac, _macDep, _ios, _iosDep, ...) __attribute__((visibility("default"))) NS_DEPRECATED(_mac, _macDep, _ios, _iosDep, __VA_ARGS__)
#define NS_SWIFT_NOTHROW __attribute__((swift_error(none)))
#define NS_INLINE static __inline__ __attribute__((always_inline))
#define NS_REQUIRES_NIL_TERMINATION __attribute__((sentinel(0,1)))

而咱們常常看到的這樣的幾個宏

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

這三個其實被定義在了<CoreFoundation/CFAvailability.h>中

#define CF_AVAILABLE(_mac, _ios) __attribute__((availability(macosx,introduced=_mac)))
#define CF_AVAILABLE_MAC(_mac) __attribute__((availability(macosx,introduced=_mac)))
#define CF_AVAILABLE_IOS(_ios) __attribute__((availability(macosx,unavailable)))

總之你會發現只要是不具有表達式或者代碼塊功能的宏,絕大多數都是由__attribute__定義的,那麼__attribute__究竟是什麼呢,請繼續:

__attribute__

GNU C 的一大特點就是__attribute__機制。__attribute__能夠設置函數屬性(Function Attribute )、變量屬性(Variable Attribute )和類型屬性(Type Attribute )。
__attribute__的書寫方式是:__attribute__後面會緊跟一對原括弧,括弧裏面是相應的__attribute__參數,格式如:
__attribute__((attribute-list))
其位置約束爲:放於聲明的尾部「;」 以前
那麼如今的問題就是什麼狀況下使用__attribute__,以及如何設置參數attribute-list,主要分爲三種狀況:
函數屬性(Function Attribute )、變量屬性(Variable Attribute )和類型屬性(Type Attribute )。

1.函數屬性(Function Attribute )

函數屬性能夠幫助開發者把一些特性添加到函數聲明中,從而可使編譯器在錯誤檢查方面的功能更強大。__attribute__機制也很容易同非GNU應用程序作到兼容之功效。

__attribute__((format))

例如NSLog聲明中使用到的宏:

#define NS_FORMAT_FUNCTION(F,A) __attribute__((format(__NSString__, F, A)))

__attribute__((format(__NSString__, F, A)))

該__attribute__屬性能夠給被聲明的函數加上相似printf或者scanf的特徵,它可使編譯器檢查函數聲明和函數實際調用參數之間的格式化字符串是否匹配。該功能十分有用,尤爲是處理一些很難發現的bug。對於format參數的使用以下
format (archetype, string-index, first-to-check)
第一參數須要傳遞「archetype」指定是哪一種風格;「string-index」指定傳入函數的第幾個參數是格式化字符串;「first-to-check」指定第一個可變參數所在的索引。
如NSLog對這個宏的使用以下:

FOUNDATION_EXPORT void NSLog(NSString *format, ...) NS_FORMAT_FUNCTION(1,2);

那麼它如何進行語法檢查,看下面的例子:

extern void myPrint(const char *format,...)__attribute__((format(printf,1,2)));

void test() {
    myPrint("i=%d\n",6);
    myPrint("i=%s\n",6);
    myPrint("i=%s\n","abc");
    myPrint("%s,%d,%d\n",1,2);
}

使用clang命令編譯一下:

clang -c main.m

結果會出現下面的警告

main.m:70:22: warning: format specifies type 'char *' but the argument has type 'int' [-Wformat]
    myPrint("i=%s\n",6);
               ~~    ^
               %d
main.m:72:26: warning: format specifies type 'char *' but the argument has type 'int' [-Wformat]
    myPrint("%s,%d,%d\n",1,2);
             ~~          ^
             %d
main.m:72:21: warning: more '%' conversions than data arguments [-Wformat]
    myPrint("%s,%d,%d\n",1,2);
                   ~^
3 warnings generated.

若是將
__attribute__((format(printf,1,2)))直接去掉,再次編譯,則不會有任何警告。

__attribute__((noreturn))

該屬性通知編譯器函數從不返回值。當遇到相似函數還未運行到return語句就須要退出來的狀況,該屬性能夠避免出現錯誤信息。C庫函數中的abort()和exit()的聲明格式就採用了這種格式。使用以下:

void fatal () __attribute__ ((noreturn));
          
void fatal (/* ... */) {
   /* ... */ 
   /* Print error message. */ 
   /* ... */
   exit (1);
}
__attribute__((constructor/destructor))

若函數被設定爲constructor屬性,則該函數會在main()函數執行以前被自動的執行。相似的,若函數被設定爲destructor屬性,則該 函數會在main()函數執行以後或者exit()被調用後被自動的執行。擁有此類屬性的函數常常隱式的用在程序的初始化數據方面。
例如:

static __attribute__((constructor)) void before() {
    
    printf("Hello");
}

static __attribute__((destructor)) void after() {
    printf(" World!\n");
}
int main(int argc, const char * argv[]) {
    printf("0000");
    return 0;
}

程序輸出結果是:

Hello0000 World!

若是多個函數使用了這個屬性,能夠爲它們設置優先級來決定執行的順序:

__attribute__((constructor(PRIORITY)))
__attribute__((destructor(PRIORITY)))

如:

static __attribute__((constructor(101))) void before1() {
    printf("before1\n");
}

static __attribute__((constructor(102))) void before2() {
    printf("before2\n");
}

static __attribute__((destructor(201))) void after1() {
    printf("after1\n");
}

static __attribute__((destructor(202))) void after2() {
    printf("after2\n");
}
int main(int argc, const char * argv[]) {
    printf("0000\n");
    return 0;
}

輸出的結果是:

before1
before2
0000
after2
after1

從輸出的信息看,前處理都是按照優先級前後執行的,然後處理則是相反的.
須要注意的是:優先級是有範圍的。0-100(包括100),是內部保留的,因此在編碼的時候須要注意.
另一個注意的點是,上面的函數沒有聲明而是直接實現這樣__attribute__就須要放到前面,應該多使用函數聲明和實現分離的方法:

static void before1() __attribute__((constructor(1)));

static void before1() {
    printf("before1\n");
}

其餘的函數屬性(Function Attribute )還有:noinline, always_inline, pure, const, nothrow, sentinel, format, format_arg, no_instrument_function, section, constructor, destructor, used, unused, deprecated, weak, malloc, alias, warn_unused_result, nonnull等等,能夠參考 GNU C Function Attribute查看它們的具體使用方法。

如何同時使用多個屬性:
能夠在同一個函數聲明裏使用多個__attribute__,而且實際應用中這種狀況是十分常見的。只須要把它們用','隔開就能夠,如:

__attribute__((noreturn, format(printf, 1, 2)))

2.類型屬性 (Type Attributes)

__attribute__ aligned

該屬性設定一個指定大小的對齊格式(以字節 爲單位),例如:

struct S {
    short f[3];
} __attribute__ ((aligned (8)));

typedef int more_aligned_int __attribute__ ((aligned (8)));

該聲明將強制編譯器確保(盡它所能)變量類型爲struct S 或者more_aligned_int的變量在分配空間時採用至少8字節對齊方式。
如上所述,你能夠手動指定對齊的格式,一樣,你也可使用默認的對齊方式。若是aligned 後面不緊跟一個指定的數字值,那麼編譯器將依據你的目標機器狀況使用最大最有益的對齊方式。例如:

struct S {
    short f[3];
} __attribute__ ((aligned));

這裏,若是sizeof(short)的大小爲2(byte),那麼,S的大小就爲6。取一個2的次方值,使得該值大於等於6,則該值爲8,因此編譯器將設置S類型的對齊方式爲8字節。
aligned 屬性使被設置的對象佔用更多的空間,相反的,使用packed 能夠減少對象佔用的空間。
須要注意的是,attribute屬性的效力與你的鏈接器也有關,若是你的鏈接器最大隻支持16字節對齊,那麼你此時定義32 字節對齊也是無濟於事的。

__attribute__ packed

使用該屬性對struct或者union類型進行定義,設定其類型的每個變量的內存約束。當用在enum類型定義時,暗示了應該使用最小完整的類型(it indicates that the smallest integral type should be used)。

下面的例子中,packed_struct 類型的變量數組中的值將會牢牢的靠在一塊兒,但內部的成員變量s不會被「pack」 ,若是但願內部的成員變量也被packed 的話,unpacked-struct也須要使用packed進行相應的約束。

struct my_unpacked_struct {
    char c;
    int i;
};

struct my_packed_struct {
    char c;
    int  i;
    struct my_unpacked_struct s;
}__attribute__ ((__packed__));

下面的例子中使用這個屬性定義了一些結構體及其變量,並給出了輸出結果和對結果的分析。
程序代碼爲:

struct p {
    int a;
    char b;
    short c;
}__attribute__((aligned(4))) pp;

struct m {
    char a;
    int b;
    short c;
}__attribute__((aligned(4))) mm;

struct o {
    int a;
    char b;
    short c;
}oo;

struct x {
    int a;
    char b;
    struct p px;
    short c;
}__attribute__((aligned(8))) xx;
int main() {
    printf("sizeof(int)=%d,sizeof(short)=%d.sizeof(char)=%d\n",sizeof(int),sizeof(short),sizeof(char));
    printf("pp=%d,mm=%d \n", sizeof(pp),sizeof(mm));
    printf("oo=%d,xx=%d \n", sizeof(oo),sizeof(xx));
    return 0;
}

輸出結果爲:

sizeof(int)=4,sizeof(short)=2.sizeof(char)=1
pp=8,mm=12 
oo=8,xx=24

分析:
1.sizeof(pp):
sizeof(a)+sizeof(b)+sizeof(c)=4+1+1=6<8 因此sizeof(pp)=8

2.sizeof(mm):
sizeof(a)+sizeof(b)+sizeof(c)=1+4+2=7
可是a後面須要用3個字節填充,可是b是4個字節,因此a佔用4字節,b佔用4 個字節,而c又要佔用4個字節。因此sizeof(mm)=12

3.sizeof(oo):
sizeof(a)+sizeof(b)+sizeof(c)=4+1+2=7
由於默認是以4字節對齊,因此sizeof(oo)=8

4.sizeof(xx):
sizeof(a)+ sizeof(b)=4+1=5
sizeof(pp)=8; 即xx是採用8字節對齊的,因此要在a,b後面添3個空餘字節,而後才能存儲px,
4+1+(3)+8+1=17
由於xx採用的對齊是8字節對齊,因此xx的大小一定是8的整數倍,即xx的大小是一個比17大又是8的倍數的一個最小值,17<24,因此sizeof(xx)=24.

其餘的函數屬性(Function Attribute )還有:aligned, packed, transparent_union, unused, deprecated and may_alias等等,能夠參考GNU C Function Attribute查看它們的具體使用方法。

3.變量屬性 (Variable Attribute)

變量屬性最經常使用的是aligned和packed(和上面介紹的用法相同),其餘變量屬性的用法請參考GNU C Variable Attribute

4.Clang特有的__attribute__

如同GCC編譯器, Clang也支持 __attribute__, 並且對它進行的一些擴展.
若是要檢查指定屬性的可用性,你可使用__has_attribute指令。如:

#if defined(__has_feature)
  #if __has_attribute(availability)
    // 若是`availability`屬性可使用,則....
  #endif
#endif

下面介紹一些clang特有的attribute

availability

該屬性描述了方法關於操做系統版本的適用性, 使用以下:

(void) __attribute__((availability(macosx,introduced=10.4,deprecated=10.6,obsoleted=10.7)));

他表示被修飾的方法在首次出如今 OS X Tiger(10.4),在OS X Snow Leopard(10.6)中廢棄,在 OS X Lion(10.7)移除。
clang能夠利用這些信息決定何時使用它使安全的。好比:clang在OS X Leopard()編譯代碼,調用這個方法是成功的,若是在OS X Snow Leopard編譯代碼,調用成功可是會發出警告指明這個方法已經廢棄,若是是在OS X Lion,調用會失敗,由於它再也不可用。
availability屬性是一個以逗號爲分隔的參數列表,以平臺的名稱開始,包含一些放在附加信息裏的一些里程碑式的聲明。
introduced:第一次出現的版本。
deprecated:聲明要廢棄的版本,意味着用戶要遷移爲其餘API
obsoleted:聲明移除的版本,意味着徹底移除,不再能使用它
unavailable:在這些平臺不可用
message:一些關於廢棄和移除的額外信息,clang發出警告的時候會提供這些信息,對用戶使用替代的API很是有用。
這個屬性支持的平臺:
ios,macosx。

overloadable

clang 在C語言中實現了對C++函數重載的支持,使用overloadable屬性能夠實現。例如:

#include <math.h>
float __attribute__((overloadable)) tgsin(float x) { return sinf(x); }
double __attribute__((overloadable)) tgsin(double x) { return sin(x); }
long double __attribute__((overloadable)) tgsin(long double x) { return sinl(x); }

要注意的是overloadable僅僅對函數有效, 你能夠重載方法,不過範圍侷限於返回值和參數是常規類型的如:id和void *。

相關文章
相關標籤/搜索