Cocoa中常用的宏定义

我们在 Cocoa 的头文件中常会看到很多宏定义,这里列举几个可能会用到的

NS_AVAILABLE

这个宏定义常用于 API 版本控制,表明函数/属性的使用版本,是否废弃等情况,用法如下:

该方法在 iOS 4.0 中引入

+ (void)animateWithDuration:(NSTimeInterval)duration 
animations:(void (^)(void))animations
completion:(void (^ __nullable)(BOOL finished))completion NS_AVAILABLE_IOS(4_0);

该方法在 OS X 10.6 和 iOS 4.0 中引入

- (void)enumerateObjectsUsingBlock:(void (^)(ObjectType obj, NSUInteger idx, BOOL *stop))block NS_AVAILABLE(10_6, 4_0);

该方法在 iOS 2.0 中引入在 iOS 6.0 后废弃

- (CGSize)sizeWithFont:(UIFont *)font NS_DEPRECATED_IOS(2_0, 7_0, "Use -sizeWithAttributes:") __TVOS_PROHIBITED;

该方法在 OS X 10.0 以及 iOS 2.0 引入,在 OS X 10.6 以及 iOS 4.0 后废弃

- (void)removeObjectsFromIndices:(NSUInteger *)indices 
numIndices:(NSUInteger)cnt NS_DEPRECATED(10_0, 10_6, 2_0, 4_0);

NS_ASSUME_NONNULL

这个属性是 objective-c 3.0中引入的新功能,应该是为了和 swift 做兼容的。因为 swift 是有严格的类型检查的,而在objc一向是动态性很强的语言,所以为了和 swift 做兼容,也引入了一些类型检查相关的属性,这就是其中一个

NS_ASSUME_NONNULL_BEGIN
@interface Father : NSObject
@property (nonatomic, copy) NSString *name;
+ (instancetype)fatherWithName:(NSString *)name age:(NSInteger)age;
@end
NS_ASSUME_NONNULL_END

使用这个宏把整个类包起来,表明类中所有指针类型都不能显示地置为 nil。

比如

// 这里会给出警告,因为这句代码显示地给 NS_ASSUME_NONNULL_BEGIN 范围内的指针变量赋值为 nil
Father *father = [Father fatherWithName:nil age:10];
// 但是如果这么写,xCode 不会给出警告:
NSString *name = nil;
Father *father = [Father fatherWithName:name age:10];

// 同理,对 NS_ASSUME_NONNULL_BEGIN 范围内的属性显示赋值为 nil,也会有警告:
father.name = nil;
// 这么写就没有警告:
NSString *name = nil;
father.name = name;

可见,NS_ASSUME_NONNULL_BEGIN 只对 显式赋值nil 这种情况编译器会给出警告,所以为了安全起见,代码中依然要对 nil 进行安全判断。

我们通常把这个宏和 nullable 一起使用,比如 AFNetworking 里面的用法

NS_ASSUME_NONNULL_BEGIN
@interface AFHTTPSessionManager : AFURLSessionManager <NSSecureCoding, NSCopying>
@property (readonly, nonatomic, strong, nullable) NSURL *baseURL;

...

- (nullable NSURLSessionDataTask *)GET:(NSString *)URLString
parameters:(nullable id)parameters
progress:(nullable void (^)(NSProgress *downloadProgress)) downloadProgress
success:(nullable void (^)(NSURLSessionDataTask *task, id _Nullable responseObject))success
failure:(nullable void (^)(NSURLSessionDataTask * _Nullable task, NSError *error))failure;

...

@end
NS_ASSUME_NONNULL_END

深入挖掘

我们深入AVAILABLE相关宏内部,发现其使用了 __attribute__ 这个编译命令,先看 NS_AVAILABLE 的相关定义

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

在 iOS 平台上,CF_AVAILABLE 是这样定义的:

#define CF_AVAILABLE(_mac, _ios) __attribute__((availability(ios,introduced=_ios)))
#define CF_AVAILABLE_MAC(_mac) __attribute__((availability(ios,unavailable)))
#define CF_AVAILABLE_IOS(_ios) __attribute__((availability(ios,introduced=_ios)))
#define CF_DEPRECATED(_macIntro, _macDep, _iosIntro, _iosDep, ...) __attribute__((availability(ios,introduced=_iosIntro,deprecated=_iosDep,message="" __VA_ARGS__)))
#define CF_DEPRECATED_MAC(_macIntro, _macDep, ...) __attribute__((availability(ios,unavailable)))
#define CF_DEPRECATED_IOS(_iosIntro, _iosDep, ...) __attribute__((availability(ios,introduced=_iosIntro,deprecated=_iosDep,message="" __VA_ARGS__)))

下面展开一个方法

+ (void)animateWithDuration:(NSTimeInterval)duration 
animations:(void (^)(void))animations
completion:(void (^ __nullable)(BOOL finished))completion NS_AVAILABLE_IOS(4_0);

展开后

+ (void)animateWithDuration:(NSTimeInterval)duration 
animations:(void (^)(void))animations
completion:(void (^ __nullable)(BOOL finished))completion __attribute__((availability(ios,introduced=4_0)));

__attribute__

关键字 __attribute__ 是一个编译命令,它可以在变量、函数、类型定义时提供一些「属性」,用来增加一些特殊的用法。这篇文章着重介绍下函数属性和变量属性。

函数属性

availability

availability 遍布于众多的 Cocoa 头文件中,提供对「方法调用」的「控制力度」。

上面已写过这里不再赘述。

objc_requires_super (OC 方法)

父类方法声明时,加上:

- (void)fatherMethod __attribute__((objc_requires_super));

表示子类重写该方法时,一定要调用 super 函数,否则 xCode 给出警告。

枚举中的 attribute

枚举中可能有些过时的,可以加上 deprecated 来注明,如果用户使用该值,xCode 会给出warning。

typedef NS_ENUM(NSInteger, MyEnum) {
MyEnumA,
MyEnumB,
MyEnumC __attribute__((deprecated)), // 标明 MyEnumC 不建议使用
};

sentinel (OC 方法、C 函数)

明确变参函数需要 nil 参数作为分界

- (void)endWithNil:(nonnull id)first, ... __attribute__((sentinel));

输入代码会自动在后面补一个 nil:
Cocoa中常用的宏定义

warn_unused_result (OC 方法、C 函数)

表示用户一定要使用函数的返回值,否则给出 warning。

比如定义一个函数,并用 warn_unused_result 修饰:

- (int)mustCheckReturnValue __attribute__((warn_unused_result)) {
return 0;
}

如果这样调用,xCode 会给出警告:

[self mustCheckReturnValue]; // Xode 给出警告:Ignoring return value of function declared with warn_unused_result attribute

提示你,此函数的返回值很重要,你应该加以关注:

NSLog(@"%d", [self mustCheckReturnValue]); // 这里简单打印下,也算是「使用」了返回值:),警告消失

nonnull (OC 方法、C 函数)

这个上面同样说过,不再赘述

变量属性

cleanup

表示在变量作用域即将结束时,自动执行一个指定的方法。

举个例子:

// 注意这里的参数必须为「变量的地址」的类型
void cleanupInt(int *ip) {
// 通过指针依然可以访问变量
NSLog(@"%d,", *ip);
}

- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.

int i __attribute__((cleanup(cleanupInt))) = 10;
// 这里定义一个整型值,加上cleanup属性,并且填入上面的函数 cleanupInt。
// 在 i 的作用域——也就是 viewDidLoad 函数——即将结束时,自动执行 cleanupInt 函数。
}

当然,除了纯量类型,cleanup 也可以修饰 OC 对象:

void stringCleanUp(NSString **pString) {
NSLog(@"%@", *pString);
}

- (void)viewDidLoad {
[super viewDidLoad];

__autoreleasing NSString *string __attribute__((cleanup(stringCleanUp))) = @"end";
// 对于指向objc对象的指针,默认加上 __autoreleasing 修饰,造成类型不匹配,这里强转一下
// 在 string 的作用域——也就是 viewDidLoad 函数——即将结束时,自动执行 stringCleanUp 函数。
}

如果修饰 block:

void blockCleanUp(void (^*block)()) {
(*block)();
}

__autoreleasing void (^block)() __attribute__((cleanup(blockCleanUp))) = ^{
NSLog(@"end");
};

上述代码直接在 blockCleanUp函数里调用这个block,也就是说,某些我们想做的事情,可以不必依赖于 cleanup 函数,而在 block 里面直接写出来就好。比如某些成对的操作:

UIGraphicsBeginImageContext(size);
// some code
UIGraphicsEndImageContext(); // 中间有许多代码,导致这句忘了写怎么办?

可以考虑写成:

UIGraphicsBeginImageContext(size);
__autoreleasing void (^block)() __attribute__((cleanup(blockCleanUp)))= ^{
UIGraphicsEndImageContext();
};
// some code

像这样子把成对的操作用这种方式写在一起,防止遗忘,把具体操作移到下方。使用时要一定注意自己的业务场景以及变量的作用域。

objc_runtime_name

修改类在 runtime 中的名称

__attribute__((objc_runtime_name("NewFather")))
@interface Father : NSObject
@end

之后 Father 类在 runtime 中真实名称就变成了 NewFather,通过:

Class class1 = NSClassFromString(@”Father”); 是获取不到类的,必须要通过:

Class class1 = NSClassFromString(@”NewFather”); 才可以。

而且通过:Father *fa = [[Father alloc] init]; 创建一个实例,实例的 isa 为 NewFather。

unused

明确变量即使不使用,也不会产生 warning。平时如果这么定义:

NSInteger i = 10;

xCode会产生警告,因为 i 以后没有使用过。

这么写可以去除这个警告:

NSInteger i __attribute__((unused)) = 10;

deprecated

表示变量已经不建议使用。

@property (nonatomic, copy) NSString *str __attribute__ ((deprecated));

访问 str 时会给出警告。

__attribute__ 到底对谁有用

__attribute__ 是在编译阶段起作用的,给函数、变量提供了更多的错误检查、版本控制等能力。其实平时代码中用的并不多,但是如果你在项目中负责公共组件的维护,有些还是很有用的,比如,当接口更新时,用 availability 去标明接口的使用版本、是否废弃;用 objc_requires_super 来要求子类需要调用父类方法等等。

参考

在系统头文件中总是看到奇奇怪怪的宏,它们究竟是什么意思?
objc arc的简单探索
Common-Function-Attributes
黑魔法attribute((cleanup))