iOS开发iOS知识收集iOS Developer - Tips

OC中的 __attribute__

2016-01-22  本文已影响4628人  9e2a4cfc9d34

引言

在我们编写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 ))

unused waringPicture

这个关键字的含义:如果某个函数使用了这个关键字,那么函数在被调用的时候,要检查或者使用返回值,某则编译器会进行警告。
使用场合:在把一些功能封装起来(或者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<stdio.h> 
__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) 会在这里被调用,即作用域的最后。
    }
}
参考

<http://nshipster.com/__attribute__/> 
<http://www.cnblogs.com/astwish/p/3460618.html>
<http://blog.sunnyxx.com/2014/09/15/objc-attribute-cleanup/>

上一篇下一篇

猜你喜欢

热点阅读