attributeerror: __exit___利用__attribute__特性提高 APP 的鲁棒性

以前在学习Linux内核源代码时,经常看到__attribute__关键字,当时也没有太在意这个关键字,主要是因为有它和没它,并不影响阅读源码。当接触iOS开发后,再次看到这个关键字,促使我要去了解__attribute__这个神秘的关键字。

这也是我写这篇文章的初衷,向大家介绍代码中经常用到的__attribute__的一些属性。

1. __attribute__基本介绍

__attribute__ 是一个用于在声明时指定一些特性的编译器指令,可以让我们进行更多的错误检查以及高级优化工作。关于__attribute__具体解释,查了一下GNU C关于它的描述,摘录如下:

The syntax with which __attribute__ may be used, and the constructs to which attribute specifiers bind, for the C language. Some details may vary for C++ and Objective-C. Because of infelicities in the grammar for attributes, some forms described here may not be successfully parsed in all cases.

__attribute__语法格式为:

__attribute__ ((attribute-list))
attribute list is a possibly empty comma-separated sequence of attributes

__attribute__可以设置函数属性(Function Attribute)、变量属性(Variable Attribute)和类型属性(Type Attribute)。

通过命令行统计了一下SDK中Frameworks目录下头文件中包含__attribute__的个数。Frameworks目录:

/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS.sdk/System/Library/Frameworks

统计命令为:

find /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS.sdk/System/Library/Frameworks -type f | xargs -n 1 grep "__attribute__" > aaa.txt

然后用vim打开aaa.txt,为了便于统计的更准确,需要将__attrtbute__ ((替换成__attrtbute__((。在vim编辑模式下输入:

:%s/ (/(

接着在终端中输入:

cat aaa.txt | xargs -n 1 | grep "__attribute__" | sort

说明:Linux或者UNIX系统下的bash命令是很强大的,可以做很多事情。这里的需求就是分析Frameworks目录下所有的文件,包含__attribute__的行,并对他们进行排序。

统计结果如下:

v2-f5efd0d53d589b72813a464be6f918fe_b.jpg

根据结果,可以统计出系统定义visibility属性频率最高(不严谨,有些地方是用的宏,所以没有统计在内)

从上面的统计结果,选了一些有代表性的属性进行分析:

2. 函数属性

  • format (archetype, string-index, first-to-check)
The format attribute specifies that a function takes printf, scanf, strftime or strfmon style arguments that should be type-checked against a format string.
For Objective-C dialects, NSString (or __NSString__) is recognized in the same context. Declarations including these format attributes are parsed for correct syntax, however the result of checking of such format strings is not yet defined, and is not carried out by this version of the compiler.

系统NSLog的实现:

FOUNDATION_EXPORT void NSLog(NSString *format, ...) NS_FORMAT_FUNCTION(1,2) NS_NO_TAIL_CALL; 
FOUNDATION_EXPORT void NSLogv(NSString *format, va_list args) NS_FORMAT_FUNCTION(1,0) NS_NO_TAIL_CALL; 

其中NS_FORMAT_FUNCTION的定义为

// Marks APIs which format strings by taking a format string and optional varargs as arguments 
#if !defined(NS_FORMAT_FUNCTION) 
    #if (__GNUC__*10+__GNUC_MINOR__ >= 42) && (TARGET_OS_MAC || TARGET_OS_EMBEDDED) 
    #define NS_FORMAT_FUNCTION(F,A) __attribute__((format(__NSString__, F, A))) 
    #else 
    #define NS_FORMAT_FUNCTION(F,A) 
    #endif 
#endif 

其中,“archetype”指定是哪种风格,这里指定的是__NSString__;“string-index”指定传入函数的第几个参数是格式化字符串;“first-to-check”指定从函数的第几个参数开始按上述规则进行检查。仔细回想一下NSStirng格式化字符串是不是这种规则。那么对于NSLog在输出字符串时,如果没有遵循规则,编译器将会给出warning。

  • noreturn
A few standard library functions, such as abort and exit, cannot return. Some programs define their own functions that never return. You can declare them noreturn to tell the compiler this fact.

C库函数中的abort()和exit()的声明格式就采用了这种格式,如下所示:

extern void exit(int) __attribute__((noreturn)); 
extern void abort(void) __attribute__((noreturn)); 

AFNetworking 在网络请求线程的入口方法出使用了 noreturn 属性。这个方法用于产生专门用于网络请求的 NSThread,确保这个线程在应用整个生命周期内都能一直运行。具体位置为AFURLConnectionOperation.m文件中的第157行:

v2-653fa7c61d11c86b71a6b0310646f098_b.jpg


注意:最新版本的AFNetworking,已经不存在该文件了。截图截至旧版本的代码

  • objc_requires_super
Some Objective-C classes allow a subclass to override a particular method in a parent class but expect that the overriding method also calls the overridden method in the parent class.

意思就是对于这里方法,在子类里面需要调用父类的同名函数,也就是super一下。在UITableViewCell里面计算文本的高度或者动画时(复杂一点的场景),可能会调用updateConstraints方法

#ifndef NS_REQUIRES_SUPER 
#if __has_attribute(objc_requires_super) 
#define NS_REQUIRES_SUPER __attribute__((objc_requires_super)) 
#else 
#define NS_REQUIRES_SUPER 
#endif 
#endif 
 
@interface UIView (UIConstraintBasedLayoutCoreMethods) 
...... 
- (void)updateConstraints NS_AVAILABLE_IOS(6_0) NS_REQUIRES_SUPER; // Override this to adjust your special constraints during a constraints update pass 
...... 
@end 

由于该函数已经声明了需要调用父类的方法,如果在实现的时候没有调用,则会给出warning,如图所示:

v2-ebbb1dcb04fe959b8744e4282847f3ef_b.jpg


这里的NS_AVAILABLE_IOS对应的是另一个属性:availability

  • availability
The availability attribute can be placed on declarations to describe the lifecycle of that declaration relative to operating system versions.

在实际开发过程中,由于苹果提供的API有更新,有些API在高版本才有,如果封装的方法里面正好用到了该方法,则应该加上这个NS_AVAILABLE_IOS宏,方便接手的同事,或者组内成员在调用该方法时,知道限制。如果APP运行于低版本时,将会导致闪退。编译不会出错,因为是用高版本编译的。

  • objc_designated_initializer
初始化方法分为designated initializer和convenience initializer
  1. 如果是DESIGNATED_INITIALIZER的初始化方法,必须调用父类的DESIGNATED_INITIALIZER方法。
  2. 如果不是DESIGNATED_INITIALIZER的初始化方法,但是该类拥有DESIGNATED_INITIALIZER初始化方法,那么:必须调用该类的DESIGNATED_INITIALIZER方法或者非DESIGNATED_INITIALIZER方法,不可以调用父类的其他初始化方法。
  3. 如果一个类拥有DESIGNATED_INITIALIZER初始化方法,那它必须重载父类定义的DESIGNATED_INITIALIZER初始化方法。

一个典型的例子就是initWithFrame方法,其代码为:

#ifndef NS_DESIGNATED_INITIALIZER 
#if __has_attribute(objc_designated_initializer) 
#define NS_DESIGNATED_INITIALIZER __attribute__((objc_designated_initializer)) 
#else 
#define NS_DESIGNATED_INITIALIZER 
#endif 
#endif 
 
- (instancetype)initWithFrame:(CGRect)frame NS_DESIGNATED_INITIALIZER; 
- (nullable instancetype)initWithCoder:(NSCoder *)aDecoder NS_DESIGNATED_INITIALIZER; 

我们在实际的使用过程中,在调用initWithFrame函数中,都会[super initWithFrame]一下,可能大家都没有想过为什么要调用,如果不调用会怎样呢?

v2-7ca0a9b93bd88a113f107696bee92351_b.jpg


如果不调用父类的initWithFrame方法则会给出warning。

关于designated initializer和convenience initializer的区别,请参看你真的了解 NSObject 对象的初始化吗?

  • visibility ("visibility_type")
This attribute affects the linkage of the declaration to which it is attached. It can be applied to variables and types as well as functions.
There are four supported visibility_type values: default, hidden, protected or internal visibility.

default

Default visibility is the normal case for the object file format. This value is available for the visibility attribute to override other options that may change the assumed visibility of entities.On Darwin, default visibility means that the declaration is visible to other modules.

hidden

Hidden visibility indicates that the entity declared has a new form of linkage, which we call “hidden linkage”. Two declarations of an object with hidden linkage refer to the same object if they are in the same shared object.

internal

Internal visibility is like hidden visibility, but with additional processor specific semantics.

protected

Protected visibility is like default visibility except that it indicates that references within the defining module bind to the definition in that module. That is, the declared entity cannot be overridden by another module.

对于动态库,iOS上称为.tbd(以前为.dylb),Linux上为.so,如果用strings命令扫描任何一个so文件可以查看其里面的符号表,我们可以利用该属性对源代码里面的符号进行可见性控制,那些被打上hidden标记位的符号将不会出现在符号表中。

假设程序调用某函数A,A函数存在于两个动态链接库liba.so, libb.so中,并且程序执行需要链接这两个库,此时程序调用的A函数到底是来自于a还是b呢?

这取决于链接时的顺序,比如先链接liba.so,这时候通过liba.so的导出符号表就可以找到函数A的定义,并加入到符号表中,链接libb.so的时候,符号表中已经存在函数A,就不会再更新符号表,所以调用的始终是liba.so中的A函数。为了避免这种混乱,所以使用__attribute__((visibility("default")))__attribute__((visibility("hideen"))) 设置这个属性。

看起来是一个很牛叉的技术,然而这些可见性控制只能应用到代码中的C或者C++子集,不能应用到Objective-C的类和方法上。Objective-C类和消息名称由Objective-C运行环境来限制,而不是通过编译器,因此可见性的说明对它们是不起作用的。我们无法在动态库中隐藏所定义的Objective-C类或者方法。

我们之所以在Framework能搜出__attribute__((visibility("hideen"))) ,是因为基础库里面有不少是用C语言写的。

3. 变量属性

  • unused
This attribute, attached to a variable, means that the variable is meant to be possibly unused. GCC does not produce a warning for this variable.

如下面这一段代码,如果不加unused,编译器则会提示warning,标明notification没有被使用。此时,就可以通过这种方式解决

- (void)applicationWillTerminate:(NSNotification * __attribute__((unused)))notification { 
    [self flushLog]; 
} 
  • used
This attribute, attached to a variable with static storage, means that the variable must be emitted even if it appears that the variable is not referenced.
When applied to a static data member of a C++ class template, the attribute also means that the member is instantiated if the class itself is instantiated.

一旦标明为used,则这个变量必须被使用,不然编译器会给出warning,如下图所示:

v2-4f1966d2c579758b57ed00cd435031dc_b.jpg
  • section ("section-name")
Normally, the compiler places the objects it generates in sections like data and bss. Sometimes, however, you need additional sections, or you need certain particular variables to appear in special sections, for example to map to special hardware. The section attribute specifies that a variable (or function) lives in a particular section. For example, this small program uses several specific section names:

表示变量存储的区域,如存储于data段或者bss段。下面看一个例子:

#if DEPLOYMENT_TARGET_LINUX 
#define CONST_STRING_LITERAL_SECTION __attribute__((section(".cfstrlit.data"))) 
#else 
#define CONST_STRING_LITERAL_SECTION 
#endif 
 
#if __BIG_ENDIAN__ 
#define CFSTR(cStr) ({  
static struct __CFConstStr str CONST_STRING_LITERAL_SECTION = {{(uintptr_t)&__CFConstantStringClassReference, _CF_CONSTANT_OBJECT_STRONG_RC, 0, {0x00, 0x00, 0x07, 0xc8}, {0x00, 0x00, 0x00, 0x00}}, (uint8_t *)(cStr), sizeof(cStr) - 1};  
(CFStringRef)&str;  
}) 
#else 
#define CFSTR(cStr) ({  
static struct __CFConstStr str CONST_STRING_LITERAL_SECTION = {{(uintptr_t)&__CFConstantStringClassReference, _CF_CONSTANT_OBJECT_STRONG_RC, 0, {0xc8, 0x07, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00}}, (uint8_t *)(cStr), sizeof(cStr) - 1};  
(CFStringRef)&str;  
}) 
#endif 
  • aligned (alignment)
This attribute specifies a minimum alignment for the variable or structure field, measured in bytes.

表示字节对齐,一般用于底层编码,如socket通信、蓝牙传输数据等。

/* 
 m30, m31, and m32 correspond to the translation values tx, ty, and tz, respectively. 
 m[12], m[13], and m[14] correspond to the translation values tx, ty, and tz, respectively. 
 */ 
#if defined(__STRICT_ANSI__) 
struct _GLKMatrix4 
{ 
 float m[16]; 
} __attribute__((aligned(16))); 
typedef struct _GLKMatrix4 GLKMatrix4;     
#else 
union _GLKMatrix4 
{ 
    struct 
    { 
 float m00, m01, m02, m03; 
 float m10, m11, m12, m13; 
 float m20, m21, m22, m23; 
 float m30, m31, m32, m33; 
    }; 
 float m[16]; 
} __attribute__((aligned(16))); 
typedef union _GLKMatrix4 GLKMatrix4; 
#endif 

4. 类型属性

The keyword __attribute__ allows you to specify special attributes of types. Some type attributes apply only to struct and union types, while others can apply to any type defined via a typedef declaration.

这里我们不做过多的讲解,主要是因为跟上面的一样,查询GNU关于Type Attribute的介绍,常见的有以下这些:

  • aligned (alignment)
  • deprecated
  • deprecated (msg)
  • unused
  • visibility
  • packed
This attribute, attached to struct or union type definition, specifies that each member (other than zero-width bit-fields) of the structure or union is placed to minimize the memory required. When attached to an enum definition, it indicates that the smallest integral type should be used.

这里仅举一个packed的例子

struct my_unpacked_struct 
 { 
 char c; 
 int i; 
 }; 
 
struct __attribute__ ((__packed__)) my_packed_struct 
 { 
 char c; 
 int  i; 
     struct my_unpacked_struct s; 
  }; 

总结

如果大家是有心人,就会发现github上不少开源代码里面都使用了__attribute__的属性,在回想自己的代码,有没有做过这方面的限制或者优化,也许这就是我们前进的方向。

国内不少团队的代码里面都使用了__attribute__的属性,笔者以前在集成阿里云的oss服务时,就发现他们团队提供的SDK里面就使用了大量的__attribute__属性。

参考文献:

Clang 9 documentation:ATTRIBUTES IN CLANG
Attribute Syntax

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值