文章主要来源于网络收集,总结关于iOS的系统宏,方便查阅。如有侵犯某人版权请联系。

为什么要写这篇文章

我们在阅读优秀的源码的时候,经常可以看到一些系统的宏定义。也许对于新手来说这些定义根本没有必要,或者自己也很少使用。总之,看起来总觉得不是太好理解。那么这些定义有必要存在吗?它们都有什么作用呢?

首先说明,这些定义很有必要,它们的作用比我们想象中的要大很多。

  1. 提高代码的严谨性 我们都知道,在软件开发中,一个严谨健壮的代码对于程序的稳定性是多么的重要。因为,我们作为一个开发者,平时的主要工作是在规定时间内尽力完成业务需求。虽然,后期会有测试人员帮助我们找出软件的bug,但面对成千上万的用户,测试人员是无法模拟出软件的各种使用场景的。由此可见,一个稳定的软件是多么的来之不易。
  2. 提高代码的可读性 我在上期的博客里面总结了iOS的编码规范,目的就是提高代码的可读性。我们不可能保证公司所有的开发人员开发的习惯一样,每个人都会有自己的一套编码习惯。这就像我们都会用筷子吃饭,但是拿筷子的姿势各不相同。当我们在项目交接的时候,不可避免的会出现大眼瞪小眼。系统的宏会辅助我们对自己编码思想做出表达,这里的辅助小到log,大到整个工程的风格。
  3. 提高代码的编写速度 我们不可能保证我们的大脑随时跟着项目处于运行状态,当面对旧代码的时候,一个良好的宏可能就会帮助我们理解当时写这段代码的想法。

相信看到上面的总结可能大家会对宏有一个良好的影响,下面我们列举一下系统宏的主要用法。


打印相关

##VA_ARGS

VA_ARGS是一个可变参数的宏,很少人知道这个宏,这个可变参数的宏是新的C99规范中新增的,目前似乎只有gcc支持(VC6.0的编译器不支持)。红前面加上##的作用在于,当可变参数的个数为0时,这里的##起到把前面多余的“,”去掉。否则编译器会出错。实现思想就是宏定义中参数列表的最后一个参数为省略号(也就是三个点)。

FILE

FILE宏在预编译时会替换成当前的源文件名

LINE

LINE宏在预编译时会替换成当前行号

FUNCTION

FUNCTION宏在预编译时会替换成当前函数的函数名称

DEBUG

DEBUG用来判断当前的运行模式为debug还是release,注意:在release模式下,所有的debug信息将不会打印和不会中断运行模式

下面是完整的打印模板:

#ifdef DEBUG
#define NSLog(format, ...) printf("[%s] %s [第%d行] %s\n", __TIME__, __FUNCTION__, __LINE__, [[NSString stringWithFormat:format, ## __VA_ARGS__] UTF8String]);
#else
#define NSLog(format, ...)
#endif

方法相关

NS_REQUIRES_NIL_TERMINATION

告知编译器需要一个结尾的参数,告知编译器参数的列表已经到最后一个不要再继续执行下去了。

例:

+ (instancetype)arrayWithObjects:(id)firstObj, ... NS_REQUIRES_NIL_TERMINATION;

NS_UNAVAILABLE\NS_DESIGNATED_INITIALIZER

当面对多个初始化方法时,外部调用者往往会手足无措,不知道哪个才是正确的初始化方法,对此,苹果提供了两个关键字:NS_DESIGNATED_INITIALIZER与NS_UNAVAILABLE来帮助我们约束定义方式,使得接口描述更加清晰。

NS_DESIGNATED_INITIALIZER: 这个宏用来指定构造器,往往是想告诉调用者要用这个方法去初始化(构造)类对象。

这个构造宏比较特殊,这里做一个简单的说明。假如我们将构造方法用这个宏来进行标记,那么我们应该确保方法里的参数能够正确的被赋值。

一个子类如果有自己的designed initializer,则必须实现父类的designed initializer。比如一个继承于NSObject的类,要想有自己的designed initializer,就必须重写init方法。并在init方法中,调用自己的designed initializer,而不是调用super的初始化方法。

一个子类的designed initializer方法,在调用super的时候,也应该改调用父类的designd initializer方法。

NS_UNAVAILABLE:这个宏的用法十分的简单粗暴,这里直接禁用该方法为初始化方法。

NSAssert

NSAssert:是指在开发期间使用的、让程序在运行时进行自检的代码(通常是一个子程序或宏)。断言为真,则表明程序运行正常;而断言为假,则意味着它已经在代码中发现了意料之外的错误。断言对于大型复杂程序或可靠性要求极高的程序来说尤为有用。

  1. 用错误处理代码来处理预期会发生的状况,用断言来处理绝不应该发生的状况。
  2. 避免把需要执行的代码放到断言中
  3. 用断言来注解并验证前条件和后条件
  4. 对于高健壮性的代码,应该先使用断言再处理错误

这里需要注意的是断言可能会导致程序提前中断运行

NSString *name = nil
NSAssert(name != nil,@"名字不能为空");

当程序运行到断言的部分,判断name是否为空,若为空,则断言将被执行,程序crash,并打印出断言内容。

一般情况下,我们在release模式下是要禁用断言检查,防止程序意外崩溃。

方法:

在Build Settings菜单,找到Preprocessor Macros项,Preprocessor Macros项下面有一个选择,用于程序生成配置:Debug版和Release版。选择Release项,设置NS_BLOCK_ASSERTIONS,不进行断言检查。

NS_AVAILABLE_IOS

NS_AVAILABLE_IOS(9_0):9_0代表iOS系统,它会告诉编译器该属性或方法在iOS9及以上可以使用,如果系统版本低于提示版本,程序在编译时就会crash。

NS_REQUIRES_SUPER

NS_REQUIRES_SUPER:用来修饰方法,表示子类override父类的方法时,必须在方法内部调用super的这个方法。

如果子类真的不想去掉用super用NS_REQUIRES_SUPER修饰的方法,又不想出现警告,那么可以用下面的方式处理:

#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wobjc-missing-super-calls"
方法实现
#pragma clang diagnostic pop

DEPRECATED_ATTRIBUTE

DEPRECATED_ATTRIBUTE:他告诉编译器该方法被弃用了

NS_ASSUME_NONNULL_BEGIN NS_ASSUME_NONNULL_END

NS_ASSUME_NONNULL_BEGIN NS_ASSUME_NONNULL_END:表示,在这个两个宏之间的代码,所有简单指针对象都被假定为nonnull,即:非空。

系统相关

TARGET_IPHONE_SIMULATOR\TARGET_OS_IPHONE

TARGET_IPHONE_SIMULATOR\TARGET_OS_IPHONE:判断当前设备是模拟器还是真机

比如:

#if TARGET_IPHONE_SIMULATOR
    // 模拟器
#elif TARGET_OS_IPHONE
    // 真机
#endif

__IPHONE_OS_VERSION_MAX_ALLOWED __IPHONE_OS_VERSION_MIN_REQUIRED

__IPHONE_OS_VERSION_MIN_REQUIRED:判断当前设备系统

比如:

#if __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_8_0
    // 这里写设备系统大于8.0 以上的代码
#else
    // 这里写设备系统小于8.0以上的代码
#endif


#if __IPHONE_OS_VERSION_MIN_REQUIRED <= __IPHONE_7_0
    // 这里写设备系统小于7.0以上的代码
#else
    // 这里写设备系统大于7.0以上的代码
#endif

__has_feature

__has_feature:判断当前App是否是ARC模式

比如:

#if !__has_feature(objc_arc)
#error app is ARC only. Either turn on ARC for the project or use -fobjc-arc flag
#endif

has_include

has_include:判断是否可以引入某个类

比如:

#if __has_include(<MyFramework/MyFramework.h>)
#import <MyFramework/MyFramework.h>
#else
#import "MyFramework.h"
#endif

参考文献:

iOS日常宏定义大全

iOS宏定义学习


你远道而来这人世间,想必也是因为热爱吧 ||