引言
在我们编写OC代码的时候经常可以看到这样的警告
一个是方法被废弃了,一个是我们输入的参数不合理。我们知道 编译时异常,要比运行时异常好的多。
那么编译器是如何知道这写内容呢?
我们点击方法,进入头文件中看一下。
FOUNDATION_EXPORT void NSLog(NSString *format, ...) NS_FORMAT_FUNCTION(1,2);
//注意后方的宏定义,我们点击过去之后查看一下
#define NS_FORMAT_FUNCTION(F,A) __attribute__((format(__NSString__, F, A)))
看一下这句代码
__attribute__((format(__NSString__,F, A)))
这句的意思是,参数的第F位是格式化字符串,从A位开始我们开始检查
所以在图二的位置就会有参数不正确的警告。下面我们来系统的认识一下__attribute__
__attribute__ 简单介绍
__attribute__ 是 GNU C 的一大特色。
__attribute__ 语法格式为:
\_\_attribute\_\_ ((attribute-list))
__attribute__ 书写特征是 前后都有两个下划线,并切后面会紧跟一对原括弧,括弧里面是相应的
__attribute__ 参数。
其位置约束为:放于声明的尾部“ ;” 之前。
在 __attribute__ 被加入GC之前还有一个小故事
In fact, when __attribute__ was first introduced to GCC, it was faced with some resistance by some who suggested that #pragma be used exclusively for the same purposes.
There were, however, two very good reasons why __attribute__ was added:
It was impossible to generate #pragma commands from a macro (before the C99 _Pragma operator).
There is no telling what the same #pragma might mean in another compiler.
Quoth the GCC Documentation for Function Attributes:
These two reasons applied to almost any application that might have been proposed for #pragma. It was basically a mistake to use #pragma for anything.
Indeed, if you look at modern Objective-C–in the headers of Apple frameworks and well-engineered open-source projects–__attribute__ is used for myriad purposes. (By contrast, #pragma’s main claim to fame these days is decoration: #pragma mark)
当然上面的 __attribute__ 使用方法是在GCC中使用的在OC中有些是禁用的。下面是我遇到的一些OC中使用的例子和用法。如果您发现了有其他的用法,还请您在下方的评论告诉我。去我的微博@我
__attribute__((format()))
//C中的使用方法
extern int my_printf (void *my_object, const char *my_format, ...) __attribute__((format(printf, 2, 3)));
//这个的意思是第二个参数my_format参数是一个格式化字符串,从第三个参数开始检查
//在Objective-C 中我们使用__string来禁代替format NSString +stringWithFormat: 和 NSLog()都是一个很好的例子
FOUNDATION_EXPORT void NSLog(NSString *format, ...) NS_FORMAT_FUNCTION(1,2);
+ (instancetype)stringWithFormat:(NSString *)format, ... NS_FORMAT_FUNCTION(1,2);
__attribute__((noreturn))
一些标准库函数,如中止和退出,不能返回。
noreturn属性指定了其他函数,它永远不会返回。
例如AFNetworking中就有这个用法
//AFURLConnectionOperation.m
+ (void) __attribute__((noreturn)) networkRequestThreadEntryPoint:(id)__unused object {
do {
@autoreleasepool {
[[NSRunLoop currentRunLoop] run];
}
} while (YES);
}
__attribute__((availability))
此种用法我们间的也比较多,多用于废弃方法
- (CGSize)sizeWithFont:(UIFont *)font NS_DEPRECATED_IOS(2_0, 7_0, "Use -sizeWithAttributes:") __TVOS_PROHIBITED;
//来看一下 后边的宏
#define NS_DEPRECATED_IOS(_iosIntro, _iosDep, ...) CF_DEPRECATED_IOS(_iosIntro, _iosDep, __VA_ARGS__)
define CF_DEPRECATED_IOS(_iosIntro, _iosDep, ...) __attribute__((availability(ios,introduced=_iosIntro,deprecated=_iosDep,message="" __VA_ARGS__)))
//宏展开以后如下
__attribute__((availability(ios,introduced=2_0,deprecated=7_0,message=""__VA_ARGS__)));
//iOS即是iOS平台
//introduced 从哪个版本开始使用
//deprecated 从哪个版本开始弃用
//警告的消息
//其实还可以再加一个参数例如
void f(void) __attribute__((availability(macosx,introduced=10.4,deprecated=10.6,obsoleted=10.7)));
//obsoleted完全禁止使用的版本
在swift中也有类似的用法
@available(iOS 6.0, *)
public var minimumScaleFactor: CGFloat // default is 0.0
__attribute__((unused ))
这个关键字的含义:如果某个函数使用了这个关键字,那么函数在被调用的时候,要检查或者使用返回值,某则编译器会进行警告。
使用场合:在把一些功能封装起来(或者SDK的编写)时候,如果对返回值的使用比较重要,那么使用这个关键字提醒编译器要检查返回值是否被利用。
当我们将返回值赋予一个变量使用时就不会有waring了
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib
BOOL result = [self TestFunc:0];
result = YES;
}
-(BOOL)TestFunc:(NSInteger) num __attribute__ ((warn_unused_result)){
return num > 0?YES:NO;
}
__attribute__((constructor)) 在main函数之前的调用
请看下面的代码
#include
__attribute__((constructor)) void before_main() {
printf("before main\n");
}
__attribute__((destructor)) void after_main() {
printf("after main\n");
}
int main(int argc, char **argv) {
printf("in main\n");
return 0;
}
输出结果如下
before main
in main
after main
__attribute__((constructor)) //确保此函数在 在main函数被调用之前调用
__attribute__((destructor)) // 确保此函数在 在main函数被调用之后调
__attribute__((cleanup())) 用于修饰一个变量,在它的作用域结束时可以自动执行一个指定的方法
在看mantle的源码是看到了这种用法
typedef void (^mtl_cleanupBlock_t)();
#define metamacro_concat_(A, B) A ## B
#define metamacro_concat(A, B) \
metamacro_concat_(A, B)
#define onExit \
try {} @finally {} \
__strong mtl_cleanupBlock_t metamacro_concat(mtl_exitBlock_, __LINE__) __attribute__((cleanup(mtl_executeCleanupBlock), unused)) = ^
+ (void)enumeratePropertiesUsingBlock:(void (^)(objc_property_t property, BOOL *stop))block {
Class cls = self;
BOOL stop = NO;
while (!stop && ![cls isEqual:MTLModel.class]) {
unsigned count = 0;
objc_property_t *properties = class_copyPropertyList(cls, &count);
cls = cls.superclass;
if (properties == NULL) continue;
//注意这里的用法
@onExit {
free(properties);
};
for (unsigned i = 0; i < count; i++) {
block(properties[i], &stop);
if (stop) break;
}
}
}
//@onExit 宏展开之后
@try {} @finally {}
__strong mtl_cleanupBlock_t mtl_exitBlock___LINE__ __attribute__((cleanup(mtl_executeCleanupBlock), unused)) = ^{
free(properties);
};
//可以保证 程序在即将运行出 propertties的作用时释放 properties
随后在网上搜了一下看到了我就叫Sunny怎么了 的一篇博客,非常感谢sunny的分享。
他的博客中提到了Reactive Cocoa中相同的用法。
我把他博客中的一些内容摘抄如下
// 指定一个cleanup方法,注意入参是所修饰变量的地址,类型要一样
// 对于指向objc对象的指针(id *),如果不强制声明__strong默认是__autoreleasing,造成类型不匹配
static void stringCleanUp(__strong NSString **string) {
NSLog(@"%@", *string);
}
// 在某个方法中:
{
__strong NSString *string attribute((cleanup(stringCleanUp))) = @"sunnyxx";
__strong NSString *__string attribute((cleanup(stringCleanUp))) = @"sunnyxx2222";
} // 当运行到这个作用域结束时,自动调用stringCleanUp//输出是sunnyxx2222 sunnyxx
假如一个作用域内有若干个cleanup的变量,他们的调用顺序是先入后出的栈式顺序; 而且,cleanup是先于这个对象的dealloc调用的。
既然attribute((cleanup(...)))可以用来修饰变量,block当然也是其中之一,写一个block的cleanup函数非常有趣:
// void(^block)(void)的指针是void(^*block)(void) static void blockCleanUp(__strong void(^*block)(void)) { (*block)(); } 于是在一个作用域里声明一个block: { // 加了个`unused`的attribute用来消除`unused variable`的warning __strong void(^block)(void) __attribute__((cleanup(blockCleanUp), unused)) = ^{ NSLog(@"I'm dying..."); }; } // 这里输出"I'm dying..."
这里不得不提万能的Reactive Cocoa中神奇的@onExit方法,其实正是上面的写法,简单定义个宏:
#define onExit\ __strong void(^block)(void) __attribute__((cleanup(blockCleanUp), unused)) = ^
用这个宏就能让一些很重要的资源或者IO流等在代码离开作用范围之前释放掉或者关闭掉。
在swift中也有类似的用法
其实swift中也类似的用法 错误处理(Error Handling)
指定清理操作
可以使用defer语句在即将离开当前代码块时执行一系列语句。该语句让你能执行一些必要的清理工作,不管是以何种方式离开当前代码块的——无论是由于抛出错误而离开,还是由于诸如return或者break的语句。例如,你可以用defer语句来确保文件描述符得以关闭,以及手动分配的内存得以释放。
defer语句将代码的执行延迟到当前的作用域退出之前。该语句由defer关键字和要被延迟执行的语句组成。延迟执行的语句不能包含任何控制转移语句,例如break或是return语句,或是抛出一个错误。延迟执行的操作会按照它们被指定时的顺序的相反顺序执行——也就是说,第一条defer语句中的代码会在第二条defer语句中的代码被执行之后才执行,以此类推。
func processFile(filename: String) throws { if exists(filename) { let file = open(filename) defer { close(file) } while let line = try file.readline() { // 处理文件。 } // close(file) 会在这里被调用,即作用域的最后。 } }
参考