type
Post
status
Published
date
Mar 4, 2016
slug
summary
tags
iOS
category
技术分享
icon
password
我们在 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:

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 来要求子类需要调用父类方法等等。