Clang Language Extensions

Xcode

本文是自《Clang Language Extensions》 中选取部分与Objective-C相关的内容翻译,由于作者水平有限,如存在理解错误或翻译不到位的地方,还请指正!

特性检查宏(Feature Checking Macros)

__has_builtin

此函数类型的宏传递一个函数名作为参数来判断该函数是否为内置函数。

1
2
3
4
5
6
7
8
9
#ifndef __has_builtin // Optional of course.
#define __has_builtin(x) 0 // Compatibility with non-clang compilers.
#endif
#if __has_builtin(__builtin_trap)
__builtin_trap();
#else
abort();
#endif

__has_feature & __has_extension

这两个函数类型的宏传递一个特性的名称作为参数,如果该特性同时被Clang和当前语言的标准所支持,__has_feature返回1,否则返回0。如果该特性被Clang和当前语言(不管是该语言的扩展还是标准)所支持,__has_extension返回1,否则返回0。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#ifndef __has_feature // Optional of course.
#define __has_feature(x) 0 // Compatibility with non-clang compilers.
#endif
#ifndef __has_extension
#define __has_extension __has_feature // Compatibility with pre-3.0 compilers.
#endif
#if __has_feature(objc_arc)
NSLog(@"under ARC");
#endif
#if __has_extension(blocks)
NSLog(@"support blocks");
#endif

出于向后兼容的考虑,__has_feature也可以用来检查非标准语法特性,如:不是以c_cxx_objc_等为前缀的特性。所以用__has_feature(blocks)来检查是否支持block也是可以的。如果设置-pedantic-errors选项,__has_extension__has_feature作用就是一样的。

__has_attribute

此宏传递一个属性名称用于检查该属性是否被支持。

1
2
3
4
5
6
7
8
9
#ifndef __has_attribute // Optional of course.
#define __has_attribute(x) 0 // Compatibility with non-clang compilers.
#endif
#if __has_attribute(always_inline)
#define ALWAYS_INLINE __attribute__((always_inline))
#else
#define ALWAYS_INLINE
#endif

传入的属性名也可以采用前后加__(下划线)的命名方式来防止命名冲突,所以这里__always_inline__always_inline是等价的。

文件引入检查宏(Include File Checking Macros)

并不是所有的系统都一定包含你所需要引入的文件,所以你可以使用__has_include__has_include_next宏在你#include之前检查你所需要引入的文件在当前系统是否存在。

__has_include

此宏传入一个你想引入文件的名称作为参数,如果该文件能够被引入则返回1,否则返回0。

1
2
3
4
// Note the two possible file name string formats.
#if __has_include("myinclude.h") && __has_include(<stdint.h>)
#include "myinclude.h"
#endif

为了兼容非clang编译器,你可以这样写:

1
2
3
4
5
6
// To avoid problem with non-clang compilers not having this macro.
#if defined(__has_include)
#if __has_include("myinclude.h")
#include "myinclude.h"
#endif
#endif

__has_include_next

此宏与__has_include功能相似,只不过会判断引入包含该文件的后面一个路径下的文件 是否可引入。

1
2
3
4
5
6
7
8
9
10
11
// Note the two possible file name string formats.
#if __has_include_next("myinclude.h") && __has_include_next(<stdint.h>)
# include_next "myinclude.h"
#endif
// To avoid problem with non-clang compilers not having this macro.
#if defined(__has_include_next)
#if __has_include_next("myinclude.h")
# include_next "myinclude.h"
#endif
#endif

__has_include_next和GNU扩展语句#include_next一样,只能在头文件中使用,如果在其他的位置使用会引起警告。

__has_warning

此宏传入一个字符串作为参数,该字符串表示一种警告类型,如果该警告有效返回true。

1
2
3
#if __has_warning("-Wformat")
...
#endif

内置宏(Builtin Macros)

__BASE_FILE__
返回当前文件的路径。

__COUNTER__
计数器,初始值为0,每次使用__COUNTER__时都会自动+1。(以文件为计数单元,即不同文件中的__COUNTER__值是独立计数的)

__INCLUDE_LEVEL__
当前文件被引用的深度,main文件时该值为0。

__TIMESTAMP__
返回当前文件最后一次修改的时间戳。

__clang__
布尔值,返回当前编译器是否支持clang。

__clang_major__
返回当前Clang的主版本号(如version4.6.3中的4)。

__clang_minor__
返回当前Clang的次版本号(如version4.6.3中的6)。

__clang_patchlevel__
返回当前Clang的补丁版本号(如version4.6.3中的3)。

__clang_version__
返回当前Clang的完整的版本号。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
if (__clang__) {
NSLog(@"__BASE_FILE__: %s", __BASE_FILE__);
NSLog(@"__COUNTER__: %d", __COUNTER__);
NSLog(@"__COUNTER__: %d", __COUNTER__);
NSLog(@"__COUNTER__: %d", __COUNTER__);
NSLog(@"__TIMESTAMP__: %s", __TIMESTAMP__);
NSLog(@"__INCLUDE_LEVEL__: %d", __INCLUDE_LEVEL__);
NSLog(@"__clang_major__: %d", __clang_major__);
NSLog(@"__clang_minor__: %d", __clang_minor__);
NSLog(@"__clang_patchlevel__: %d", __clang_patchlevel__);
NSLog(@"__clang_version__: %s", __clang_version__);
}
/*
__BASE_FILE__: /Users/tracy/Desktop/Clang/Clang/main.m
__COUNTER__: 0
__COUNTER__: 1
__COUNTER__: 2
__TIMESTAMP__: Fri Jul 26 14:24:35 2013
__INCLUDE_LEVEL__: 0
__clang_major__: 5
__clang_minor__: 0
__clang_patchlevel__: 0
__clang_version__: 5.0 (clang-500.1.65)
*/

除了以上几个宏之外,还有两个是我们经常会用到了__FUNCTION____LINE__,分别返回当前代码段所在的函数名和在当前文件中的行数,这个在打印log时比较有用。

带deprecated和unavailable属性的方法

方法或属性可以加上带deprecatedunavilable等属性来表示状态。如:

1
void explode(void) __attribute__((deprecated("extremely unsafe, use 'combust' instead!!!")));

如果一个方法被标记为deprecated,在使用时会有警告,如果是unavilable则会报错。

在iOS和OS X中,苹果也是使用该方法__attribute__进行API版本控制,只不过通常使用availability属性。

带属性的枚举类型(Attributes on Enumerators)

Clang允许给枚举值添加属性来标记某些枚举项是否可选。如:

1
2
3
4
5
6
enum OperationMode {
OM_Invalid,
OM_Normal,
OM_Terrified __attribute__((deprecated)),
OM_AbortOnError __attribute__((deprecated)) = 4
};

在iOS7 SDK中UIStatusBarStyle的定义如下:

1
2
3
4
5
6
typedef NS_ENUM(NSInteger, UIStatusBarStyle) {
UIStatusBarStyleDefault = 0, // Dark content, for use on light backgrounds
UIStatusBarStyleLightContent NS_ENUM_AVAILABLE_IOS(7_0) = 1, // Light content, for use on dark backgrounds
UIStatusBarStyleBlackTranslucent NS_ENUM_DEPRECATED_IOS(2_0, 7_0, "Use UIStatusBarStyleLightContent") = 1,
UIStatusBarStyleBlackOpaque NS_ENUM_DEPRECATED_IOS(2_0, 7_0, "Use UIStatusBarStyleLightContent") = 2,
};

其中 NS_ENUM_AVAILABLE_IOSNS_ENUM_DEPRECATED_IOS本质上也是使用的__attribute__()

availability属性

Clang提供了availability属性来描述申明对象的生命周期,如:

1
void f(void) __attribute__((availability(ios,introduced=4.0,deprecated=6.0,obsoleted=7.0)));

表明函数f在iOS4.0时被引入,在6.0是就不推荐使用,到iOS7.0就彻底废弃。

在方法重载的时候,子类的方法可以比父类的方法申明更大的有效范围:

1
2
3
4
5
6
7
8
9
@interface A
- (id)method __attribute__((availability(macosx,introduced=10.6)));
- (id)method2 __attribute__((availability(macosx,introduced=10.6)));
@end
@interface B : A
- (id)method __attribute__((availability(macosx,introduced=10.4))); //okay: method moved into base class later
- (id)method2 __attribute__((availability)(macosx,introduced=10.8)); //error: this method was available via the base class in 10.6
@end

Objective-C相关特性

相关返回类型(related result types)

按照Cocoa编码的惯例,Objective-C方法以一些关键字(如initalloc等)开头通常会返回一个当前类(Class)的实例对象,这些方法就具有相关返回类型,也就是一个方法返回一个与调用对象同类型的对象。

1
2
3
4
5
6
7
@interface NSObject
+ (id)alloc;
- (id)init;
@end
@interface NSArray : NSObject
@end

通常的初始化方式为:

1
NSArray *array = [[NSArray alloc] init];

表达式[NSArray alloc]返回的是NSArray*类型,因为alloc方法是一个隐性的相关返回类型(related result type)。同样[[NSArray alloc] init]也是一个NSArray*类型因为init也是一个相关返回类型。如果allocinit都不是相关返回类型,他们返回的就和申明的一样是id类型。

一个具有相关返回类型的方法可以使用instancetype类型作为返回类型,instancetype是一个上下文相关的关键字并只能作为Objective-C方法的返回类型。如:

1
2
3
@interface A
+ (instancetype)constructAnA;
@end

判断一个方法是否是相关返回类型,可以考虑方法的第一个单词(如:initWithObjects中的init),一个相关返回类型方法的返回值的类型和当前类相同,同时:

  • 方法的第一个单词是allocnew的类方法, 或者
  • 方法的第一个单词是autoreleaseinitretainself的实例方法。

苹果自己从iOS5.0之后就开始在使用instancetype作为相关返回类型方法的返回类型。
(更多关于instancetype理解可参考:这里这里

自动引用计数(ARC)

Clang提供了对Objective-C中自动引用计数的支持,这样你就不需要手动调用retain/release/autorelease等方法。与自动引用计数相关有两个宏可用:__has_feature(objc_arc)用来判断当前环境是否是ARC,__has_feature(objc_arc_weak)用来判断weak__weak关键字是否可用。

对象字面量和下标

Clang提供了对Objective-C中对象字面量和下标的支持,这些简化了Objective-C编码方式,让程序更简洁。 有几个宏与此相关:__has_feature(objc_array_literals)判断数组字面量是否支持,__has_feature(objc_dictionary_literals)判断字典字面量是否支持,__has_feature(objc_subscripting)判断对象下标是否支持。

Objective-C属性自动合成

Clang支持声明的属性自动合成,也就是只需要申明属性@property NSString *name;,就会自动为_name生产get/set方法。__has_feature(objc_default_synthesize_properties)可以检查属性自动合成在当前版本的Clang下是否支持。

objc_method_family属性

在Objective-C中方法的命名通常代表着方法类型,如以init开头的方法会被编译器默认为是初始化方法,在编译的时候会将该方法当成初始化方法对待。但是有时候我们并不想以编译器默认的方式给方法取名,或者编译器默认的方法类型与我们自己想表示的有出入。我们就可以使用__attribute__((objc_method_family(X)))来明确说明该方法的类型,其中X取值为:none, alloc, copy, init, mutableCopy, new。如:

1
- (NSString *)initMyStringValue __attribute__((objc_method_family(none)));

这样该方法在编译时就不会再被编译器当做为初始化方法了。

Objective-C对象引用属性

在Objective-C中,方法通常是遵循Cocoa Memory Management规范来对参数和返回值进行内存管理的。但是会有特许的情况,所以Clang提供了属性来标识对象引用情况。

使用 ns_returns_retained, ns_returns_not_retained, ns_returns_autoreleased, cf_returns_retained, cf_returns_not_retained等属性来说明方法中对返回值引用的情况。

1
2
3
id foo() __attribute__((ns_returns_retained));
- (NSString *)bar:(int)x __attribute__((ns_returns_autoreleased));

标记为*_returns_retained属性的返回的对象引用计数+1了,*_returns_not_retained属性标记的返回对象引用计数前后没有改变,*_returns_autorelased属性标记的返回对象引用计数+0,但是会在下一个自动释放池中被释放掉。

使用 ns_cousumedcf_consumed属性来标记在方法中会被+1的参数,ns_consumes_self属性在Objective-C方法中使用,标记在该方法中self对象的引用计数会被+1。

1
2
3
4
void foo(__attribute__((ns_consumed) NSString *string);
- (void)bar __attribute__((ns_consumed_self));
- (void)baz:(id) __attribute__((ns_consumed)) x;

你可以使用__has_feature()方法判断这些属性是否可用。