IOS宏用法

iOS宏的经典用法
Apple的习惯
attribute

iOS宏的经典用法
1.常量宏、表达式宏

define kTabBarH (49.0)

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) 一般是没有返回值的,仅仅是代码块而已#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 CGRectCGRectMake(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会转化为"a"2.连接两个C字符串,使用如下

define toString(a) #a // 转为字符串#define printSquare(x) printf("the square of " #x " is %d./n",(x)*(x)) // 连接字符串

使用如下
printf("%s/n", toString(abc)); // abcprintSquare(3); // the square of 3 is 9.

的常见用处是连接,它会将在它之前的语句、表达式等和它之后的语句表达式等直接连接。
// 用法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)); // 10556NSLog(@"%f", combine(10, .556)); // 10.556000NSLog(@"%tu", exp(2, 3)); // 2000 // int x = 10;int mk_x = 30;NSLog(@"%d", addPrefixForVar(x)); // 30 NSLog(@"%@", format(@"%@_%@", @"good", @"morning"));NSLog(@"%@", format(@"hello"));

对于使用##
连接可变参数的用法,如果不加##
会导致编译无法通过,这是因为: ##后面的值不存在的时候 会忽略## 前面的,
也就是说:当有## 调用format(format) 替换为 [NSString stringWithFormat:format]当没有## 调用format(format) 替换为 [NSString stringWithFormat:format ,]
还有一点要注意的是:#
和##
只能用在宏定义中,而不能使用在函数或者方法中。
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编译器这两者之间内容非空

  1. 在类声明前,方法声明后都有可用性的标记宏
    例如
    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

  1. 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); ~~ ^ %dmain.m:72:26: warning: format specifies type 'char *' but the argument has type 'int' [-Wformat] myPrint("%s,%d,%d/n",1,2); ~~ ^ %dmain.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;}

输出的结果是:
before1before20000after2after1

从输出的信息看,前处理都是按照优先级先后执行的,而后处理则是相反的.需要注意的是:优先级是有范围的。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的文档查看它们的具体使用方法。
如何同时使用多个属性:可以在同一个函数声明里使用多个attribute,并且实际应用中这种情况是十分常见的。只需要把它们用','隔开就可以,如:
attribute((noreturn, format(printf, 1, 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)=1pp=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=5sizeof(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的文档查看它们的具体使用方法。
变量属性 (Variable Attribute)
变量属性最常用的是aligned和packed其他的用法请参考 GNU C的文档。
Clang特有的attribute
如同GCC编译器, Clang也支持 attribute, 而且对它进行的一些扩展.如果要检查指定属性的可用性,你可以使用__has_attribute指令。下面介绍一些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:声明要废弃的版本,意味着用户要迁移为其他APIobsoleted:声明移除的版本,意味着完全移除,再也不能使用它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 *。

推荐阅读更多精彩内容

  • 前言 通过阅读别人的优秀源码,你会发现别人的开源API设计中,有一些宏你是经常忽略的,或者你不知道的。通过这些宏,...
    gitKong阅读 4,857评论 5 40
  • Lancy's Blog Blog Archives About MeTwitterWeiboGitHubRSS ...
    其实也没有阅读 5,158评论 0 24
  • 发现写博客想写明白也是一件不容易的事情。 这次拿YYKIt 源码 分析分析。希望这次能写的更好些。 YYKit 系...
    充满活力的早晨阅读 5,829评论 4 16
  • http://www.open-open.com/lib/view/open1390651437117.html ...
    Xtuphe阅读 1,023评论 0 10
  • 前言 Blocks是C语言的扩充功能,而Apple 在OS X Snow Leopard 和 iOS 4中引入了这...
    小人不才阅读 3,588评论 0 23
  • 最近我对学习李笑来老师的财富自由之路的课程进行复盘,通过复盘来检验自己的学习成效,反思自己的不足之处,记录自己的心...
    九九81难阅读 214评论 2 3
  • 使用NSTimer可能会碰到循环引用的问题。特别是当类具有NSTimer类型的成员变量,并且需要反复执行计时任务时...
    黄龙辉阅读 5,695评论 12 51
  • 你说过你后悔遇见了我 你说过让我离开你身边 你说爱情 像掉落在悬崖下面的针 会突然的就消失不见了 你说隔壁的花匠已...
    不像冬天丶阅读 104评论 4 2
  • 八戒,不戒… 看着黑夜, 枕着的却是一脸无奈。 我亲爱的朋友, 我冷落了你也冷落了我自己。 而你呢? 也在忽略, ...
    阿木洛克人阅读 185评论 0 0