iOS开发中那些你常见却看不懂的宏

FOUNDATION_EXTERN

定义

#if defined(__cplusplus)
#define FOUNDATION_EXTERN extern "C"
#else
#define FOUNDATION_EXTERN extern
#endif

表示 extern 全局变量,此时并没有分配内存,需要在.m文件中实现,此时为了支持C和C++混编(__cplusplus 是C++编译器内部定义的宏,在C++中,需要加 extern"C" 或包含在 extern "C" 块中),注意,此时外界是可以修改这个值,详细 extern 用法可自行查询相关资料,本文不详谈。

用法如下:

FOUNDATION_EXTERN NSString *name;// h文件
const NSString *name = @"gitKong";// m文件

FOUNDATION_EXPORT,FOUNDATION_IMPORT

定义

#if TARGET_OS_WIN32

#if defined(NSBUILDINGFOUNDATION)
#define FOUNDATION_EXPORT FOUNDATION_EXTERN __declspec(dllexport)
#else
#define FOUNDATION_EXPORT FOUNDATION_EXTERN __declspec(dllimport)
#endif

#define FOUNDATION_IMPORT FOUNDATION_EXTERN __declspec(dllimport)

#else
#define FOUNDATION_EXPORT  FOUNDATION_EXTERN
#define FOUNDATION_IMPORT FOUNDATION_EXTERN
#endif

两个宏也是extern 只是为了兼容 win32 同时适配C++编译器(其中__declspec(dllexport)__declspec(dllimport) 是C++定义的宏,可在 Mrcrosoft Developer 文档 查询)

用法如下:

用法跟 FOUNDATION_EXTERN 一样,不再举例。

NS_NONATOMIC_IOSONLY

定义

// Marks APIs whose iOS versions are nonatomic, that is cannot be set/get from multiple threads safely without additional synchronization
#if !defined(NS_NONATOMIC_IOSONLY)
#if TARGET_OS_IPHONE
#define NS_NONATOMIC_IOSONLY nonatomic
#else
#if __has_feature(objc_property_explicit_atomic)
#define NS_NONATOMIC_IOSONLY atomic
#else
#define NS_NONATOMIC_IOSONLY
#endif
#endif
#endif

表示iOS 上是 nonatomic,适配了非iOS,其中 __has_feature 表示当前新特性是否被Clang编译器支持和符合当前语言标准(可参考,其中 __has_feature 的参数可在 PPMacroExpansion_8cpp_so 这查看可知,objc_property_explicit_atomic 参数表示Clang编译器是否支持 atomic 关键字,因为OSX中不支持 nonatomic,因此OSX只有atomic

用法如下:

@property (NS_NONATOMIC_IOSONLY,copy) NSString *className;

NS_NONATOMIC_IPHONEONLY

定义

// Use NS_NONATOMIC_IOSONLY instead of this older macro
#if !defined(NS_NONATOMIC_IPHONEONLY)
#define NS_NONATOMIC_IPHONEONLY NS_NONATOMIC_IOSONLY
#endif

历史原因,等同 NS_NONATOMIC_IOSONLY,苹果建议使用NS_NONATOMIC_IOSONLY

用法如下:

用法和NS_NONATOMIC_IOSONLY 一样

NS_REQUIRES_SUPER

#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

表示标志子类继承这个方法时需要调用 super,否则给出编译警告,其中 objc_requires_super 可在 LLVM 的 AttributeReference 中查询,注意 protocol 中无效

摘自文档:

  • 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. For these cases, we provide an attribute to designate that a method requires a “call to super” in the overriding method in the subclass.
  • This attribute can only be applied the method declarations within a class, and not a protocol. Currently this attribute does not enforce any placement of where the call occurs in the overriding method (such as in the case of -dealloc where the call must appear at the end). It checks only that it exists.

用法如下:

- (void)oc_method_mustCallSuper NS_REQUIRES_SUPER;// 父类中声明
- (void)oc_method_mustCallSuper{
[super oc_method_mustCallSuper];// 子类中实现需要调用super
}

NS_DESIGNATED_INITIALIZER

定义

#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

指定类的初始化方法。指定初识化方法并不是对使用者。而是对内部的现实,其实就是必须调用父类的 designated initializer 方法,至于的作用,这里 有说明

摘自原文: To clarify the distinction between designated and secondary initializers clear, you can add the NS_DESIGNATED_INITIALIZER macro to any method in the init family, denoting it a designated initializer. Using this macro introduces a few restrictions:

  • The implementation of a designated initializer must chain to a superclass init method (with [super init…]) that is a designated initializer for the superclass.
  • The implementation of a secondary initializer (an initializer not marked as a designated initializer within a class that has at least one initializer marked as a designated initializer) must delegate to another initializer (with [self init…]).
  • If a class provides one or more designated initializers, it must implement all of the designated initializers of its superclass.

用法如下:

- (instancetype)initWithClassName:(NSString *)name NS_DESIGNATED_INITIALIZER;// 方法声明

- (instancetype)initWithClassName:(NSString *)name{// 方法实现
    if (self = [super init]) {
    self.className = name;
}
    return self;
}

此时编译器就会出现警告,原因很简单,本类中实现了 NS_DESIGNATED_INITIALIZER 那么必须实现父类的 NS_DESIGNATED_INITIALIZER 方法


实现父类的 init 方法,因为 init 也是NS_DESIGNATED_INITIALIZER 修饰

- (instancetype)init{
    self.className = @"";
    return self;
}

当然,此时毫无疑问会出现编译警告,原因很简单,本类中实现了 NS_DESIGNATED_INITIALIZER ,那么构造方法中必须调用NS_DESIGNATED_INITIALIZER 方法,因此这里就有个大坑,操作不慎容易造成循环调用,下面会解释


实现父类的 init方法并调用NS_DESIGNATED_INITIALIZER 修饰的方法

- (instancetype)init{
return [self initWithClassName:@"gitKong"];
}

- (instancetype)initWithClassName:(NSString *)name{
if (self = [super init]) {
    self.className = name;
}
    return self;
}

此时编译警告就没了,也能正常使用,不过注意 initWithClassName 方法不要调用 [self init] ,当然此时编译器很智能提示你,NS_DESIGNATED_INITIALIZER 修饰的方法只能使用 super 调用其他NS_DESIGNATED_INITIALIZER 修饰的方法,如果你忽略这个警告,那么就会出现循环调用了,更多具体的说明可参考 Clang 拾遗之objc_designated_initializer

NS_UNAVAILABLE、UNAVAILABLE_ATTRIBUTE

定义

#if !defined(NS_UNAVAILABLE)
#define NS_UNAVAILABLE UNAVAILABLE_ATTRIBUTE
#endif
/*
* only certain compilers support __attribute__((unavailable))
*/
#if defined(__GNUC__) && ((__GNUC__ >= 4) || ((__GNUC__ == 3) && (__GNUC_MINOR__ >= 1)))
#define UNAVAILABLE_ATTRIBUTE __attribute__((unavailable))
#else
#define UNAVAILABLE_ATTRIBUTE
#endif

两个宏代表同一个东西,可以修饰任何东西(包括类、方法、属性、变量等等),表示当前编译器并不支持,或者说当前平台不支持或者无效,其中 __attribute__上文也有介绍,是 GCC 编译器特有的,用作描述详细信息,unavailable 语义上就是表示无效的,可以在 LLVM 的 AttributeReference 中查询。当然,苹果也解释了,只有某些编译器支持 __attribute__((unavailable))

摘自原文:This declaration is never available on this platform.

用法如下:

- (void)sayHi NS_UNAVAILABLE;

NS_AVAILABLE、NS_DEPRECATED 等

定义

#include <CoreFoundation/CFAvailability.h>

#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)

#define NS_DEPRECATED(_macIntro, _macDep, _iosIntro, _iosDep, ...) CF_DEPRECATED(_macIntro, _macDep, _iosIntro, _iosDep, __VA_ARGS__)
#define NS_DEPRECATED_MAC(_macIntro, _macDep, ...) CF_DEPRECATED_MAC(_macIntro, _macDep, __VA_ARGS__)
#define NS_DEPRECATED_IOS(_iosIntro, _iosDep, ...) CF_DEPRECATED_IOS(_iosIntro, _iosDep, __VA_ARGS__)

#define NS_DEPRECATED_WITH_REPLACEMENT_MAC(_rep, _macIntroduced, _macDeprecated) API_DEPRECATED_WITH_REPLACEMENT(_rep, macosx(_macIntroduced, _macDeprecated)) API_UNAVAILABLE(ios, watchos, tvos)

#define NS_ENUM_AVAILABLE(_mac, _ios) CF_ENUM_AVAILABLE(_mac, _ios)
#define NS_ENUM_AVAILABLE_MAC(_mac) CF_ENUM_AVAILABLE_MAC(_mac)
#define NS_ENUM_AVAILABLE_IOS(_ios) CF_ENUM_AVAILABLE_IOS(_ios)

#define NS_ENUM_DEPRECATED(_macIntro, _macDep, _iosIntro, _iosDep, ...) CF_ENUM_DEPRECATED(_macIntro, _macDep, _iosIntro, _iosDep, __VA_ARGS__)
#define NS_ENUM_DEPRECATED_MAC(_macIntro, _macDep, ...) CF_ENUM_DEPRECATED_MAC(_macIntro, _macDep, __VA_ARGS__)
#define NS_ENUM_DEPRECATED_IOS(_iosIntro, _iosDep, ...) CF_ENUM_DEPRECATED_IOS(_iosIntro, _iosDep, __VA_ARGS__)

#define NS_AVAILABLE_IPHONE(_ios) CF_AVAILABLE_IOS(_ios)
#define NS_DEPRECATED_IPHONE(_iosIntro, _iosDep) CF_DEPRECATED_IOS(_iosIntro, _iosDep)

...

其中很多涉及到都是AVAILABLEDEPRECATED,其实都表示当前API在不同的操作系统(这里只谈苹果的操作系统)的可用版本、废弃版本、淘汰版本,其中关键就是 availability,在 LLVM 的 AttributeReference 中有详细参数说明以及其用法举例,因此本文就不再啰嗦。还有一个 visibility ,表示编译符号可见性,提供两个值 defaulthidden ,这里有文章:控制符号的可见性 作了详细的说明,文中说了, visibility 可见性控制只能应用到代码中的C或者C++子集,不能应用到Objective-C的类和方法上。

NS_ENUM、NS_OPTIONS

定义

/* NS_ENUM supports the use of one or two arguments. The first argument is always the integer type used for the values of the enum. The second argument is an optional type name for the macro. When specifying a type name, you must precede the macro with 'typedef' like so:

typedef NS_ENUM(NSInteger, NSComparisonResult) {
...
};

If you do not specify a type name, do not use 'typedef'. For example:

NS_ENUM(NSInteger) {
...
};
*/
#define NS_ENUM(...) CF_ENUM(__VA_ARGS__)
#define NS_OPTIONS(_type, _name) CF_OPTIONS(_type, _name)

具体的定义在 CFAvaliability.h ,都是表示枚举,区别在于 NS_ENUM 是通用枚举,而 NS_OPTIONS 则是位移枚举,位移枚举的值就是用位移值表示,其中内部对编译器做了适配, __cplusplus 表示支持C++编译器,__has_feature(objc_fixed_enum) 表示当前编译器是否支持固定的没定义类型,参考 LLVM 的 LanguageExtensions

摘自原文:Use __has_feature(objc_fixed_enum) to determine whether support for fixed underlying types is available in Objective-C .

用法就不再举例,相信大家都用烂了~

NS_ASSUME_NONNULL_BEGIN、NS_ASSUME_NONNULL_END

定义

#define NS_ASSUME_NONNULL_BEGIN _Pragma("clang assume_nonnull begin")
#define NS_ASSUME_NONNULL_END   _Pragma("clang assume_nonnull end")

表示当前包在里面的属性都标记为 nonnull,如果需要其他处理,则单独使用其他(例如:nullable)标记,_Pragma("string")#pragma 字符串行为完全相同,在C/C++标准中,#pragma是一条预处理的指令(preprocessor directive),在C++11中,标准定义了与预处理指令#pragma功能相同的操作符_Pragma。简单地说,#pragma是用来向编译器传达语言标准以外的一些信息,例如我们平时经常使用的 #pragma mark - <#name#>,可参考 C/C++ 预处理器参考,那么上面的其实就是等同:

#pragma clang assume_nonnull begin
#pragma clang assume_nonnull end

用法如下:(Xcode10创建文件默认添加)

//NS_ASSUME_NONNULL_BEGIN
#pragma clang assume_nonnull begin
@interface MacrosIntroduction:NSObject
@property (nonatomic,assign) FLSystemEnum systemEnums;
@property (nullable, nonatomic,copy) NSString *xxx;
@end
#pragma clang assume_nonnull end
//NS_ASSUME_NONNULL_END

最后

参考资料:

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,602评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,442评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,878评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,306评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,330评论 5 373
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,071评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,382评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,006评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,512评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,965评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,094评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,732评论 4 323
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,283评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,286评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,512评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,536评论 2 354
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,828评论 2 345

推荐阅读更多精彩内容