iOS中的预编译指令

iOS中的一些预编译指令。

开篇

举一个简单的的例子,老婆说了句“你给我滚出去睡沙发”。这句话的处理流程事这样的

程序的编译其实就是计算机在执行过程之前,把老婆的命令转换成电信号的过程。在计算机世界中,这个玩意儿叫过编译器(compiler),什么GCC啊clang啊什么什么的都是说的这个东西。看起来很高大上,其实就是一个翻译的东西。人把老婆的指令翻译成电波,编译器把各种语言翻译成01010101….

ps : 这篇文章是我在阅读了戴伟来的文章之后,根据原文整理的一个笔记。所以文中会有很多地方都摘录了文中的句子。感谢@戴伟来。如果你想直接看一下原文的话,最后有链接。

编译器的工作原理基本上都是三段式的,前端、优化器、后端。

前端负责解析源码,检查语法错误,并将其翻译成抽象的语法树;

优化器对这一中间代码进行优化,使代码更佳高效。

后端负责将优化过的代码转化成对应机器的代码。

LLVC编译器实际上是用C++写的,那C++的编译器呢?当然是汇编了,所以编译器和计算机语言的进步就是这样迭代发展的,再之后就是用高级语言写更高级的编译器,更高级的编译器就能编译更加高级的语言….那么问题来了,世界上计算机语言那么多,各种不同的架构,Intel ARM,怎么让编译语言分别产生不同的架构的执行码呢,这个时候就应该想到刚刚的三段式模型了,当我们要支持多种语言的时候,只需要添加多个前端就可以了。当我们要支持多种目标机器的时候,只需要添加多个后端就可以了。对于中间的优化器,我们可以使用通用的中间代码。gcc可以支持c、cpp、java….等语言的编译。

正文

由于文中作者使用的是Xcode6,但是现在我已经用到了Xcode7.3,而Xcode8页已经beta了。所以我讲使用我目前正在使用的Xcode7.3来进行本文。由于APPLE已经不在Xcode中内置GCC了,所以我就只能研究研究LLVM了。

如果对各种编译器的区别感兴趣的话可以看看下面两篇文章

如果阅读过优秀的源码,你一定会看到很多的#define #if #error之类的代码,预编译对程序之后的编译提供了很多方便以及优化,对于错误处理,包引用、跨平台等都有着极大的帮助。

包含文件

#include

#include" "#include< >的区别:#include“”包含和使用#include < >包含的不同之处就是使用<>包含时,预处理器会搜索C函数库头文件路径下的文件,而使用“”包含时首先搜索程序所在目录,其次搜索系统Path定义目录,如果还是找不到才会搜索C函数库头文件所在目录。

使用#include的时候包含文件的时候是不能递归包含的,例如a.h文件包含b.h,而b.h就不
能再包含a.h了;还有就是重复包含(比如a.h包含了b.h,然后main.c中又包含了a.h和b.h)虽然是允许的但是这会降低编译性能。那该怎么办呢?

  1. 使用#import替代include
  2. 使用宏判断(宏判断下面会详解),xcode很聪明,只要新建一个头文件a.h 里面就自动就生成了

#include_next

这个是非C标准库里面的预处理指令,我没有用过。

#import

OC特有的,智能的#include,解决了#include的重复包含问题。

宏定义

#define

1
2
//this defines PI
#define M_PI 3.141592

#define 关键字表面即将开始定义一个宏,M_PI是宏的名字,空格过后是宏的内容。类似这样的宏编译器会在语义分析之后讲M_PI替换为3.14159,这是宏的最基本用法。

还有一种是函数宏函数宏顾名思义,就是行为类似函数,可以接受参数的宏。具体来说,在定义的时候,如果我们在宏名字后面跟上一对括号的话,这个宏就变成了函数宏。从最简单的例子开始,比如我在开发中最常用的两个宏

1
2
#define WS(weakSelf) __weak __typeof(&*self)weakSelf = self;
#define SS(strongSelf) __strong typeof(&*weakSelf) strongSelf = weakSelf;

这两个宏的意思应该不用我说了吧,如果你没看懂而且你又喜欢用block的话,那建议你用instrument 的leaks看看是不是一串红点。

原文中还有一个MIN宏定义的正确用法

1
#define MIN(A,B) ({ __typeof__(A) __a = (A); __typeof__(B) __b = (B); __a < __b ? __a : __b; })

接下来再把我使用的log宏放出来

1
2
3
4
5
#ifdef DEBUG
# define PSLog(fmt, ...) NSLog((@"%s [%d 行] " fmt), __PRETTY_FUNCTION__, __LINE__, ##__VA_ARGS__);
#else
# define PSLog(...)
#endif

这个宏的意思请分别在release 和 debug下测试

#undef

当你使用了#define宏定义后,则在整个程序的运行周期内这个宏都是有效的,但有时候我们在某个逻辑里希望这个宏失效不想使用,则会使用

条件编译

#if #else #endif

如果#if 之后的条件语句成立的话编译#else里面的代码 反之编译 #else 之后的代码 #endif 结束语句
接下来是一些预定的宏

  • DEBUG DEBUG环境下返回YES
  • __has_feature(objc_arc) 开启ARC时返回YES
  • __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_7_0 如果手机系统小于7.0 返回YES,版本策略

#if defined #ifdef #ifndef #elif

  • #if defined (AAA) : 如果已经定义了AAA这个宏,返回YES,可用于复杂条件#if defined (AAA) && defined (BBB)或者#if defined (AAA) || VERSION > 12
  • #iddef (AAA): 如果已经定义了AAA这个宏,返回YES,单个条件
  • #ifndef : if not defined 的缩写
  • #elif : 跟#if一起使用,else if的缩写

错误、警告处理

#### #error

如果编译器遇到这货,马上就会罢工。

如果你在开发一些arc only的库,那么一下代码就有了作用

1
2
3
#if !__has_feature(objc_arc)
#error "我的低调不是你装逼的资本!这个库需要开启ARC,不然你别用!"
#endif

#warning

这个用法很简单,只要后面跟上你想警告的话就OK了,这样你就可以让编译器提醒这个警告。这个我经常用。但是还有一个更好用的TODO脚本,一会而我会放出来。

编译器控制

#pragma

这个应该算是使用的非常多的指令了吧,应该所有的程序员都应该知道代码#pragma mark的作用。

#pragma mark

这个真没有什么好说的了,记得#pragma mark#pragma mark -的区别就好了

#pragma非常复杂需要你对编译器底层非常的了解,只有当你开发一些比较底层的framework的时候才可能比较多用的。 Clang使用手册

#pragma message(“”)

可以输出调试信息

1
2
3
#pragma clang disgnostic push
#pragma clang disgnostic ignored "clang的参数" //屏蔽某类警告
#pragma clang disgnostic pop

1
2
3
4
5
#pragma clang disgnostic push
#pragma clang disgnostic ignored "-Wunused-variable"
int i = 1;
#pragma clang disgnostic pop

如果没有被使用的时候不会报出警告

关于警告一类的文章可以看看喵神王巍的博客。

其他

#####line
这个就没什么好说的,如果你自定义过NSLog 或者看到其他自定义的log并且点进去看过的话应该会看得到__line__这个东西吧,这表示本行语句在源文件中的位置信息。而#line就是可以改变当前行的行号在编译器中的表示。并且之后的行号也会相应的改变。比如

1
2
3
4
5
6
7
#include<stdio.h>
main(){
printf("%d\n",__line__);
#line 100 // 指定下一行的__line__为100
printf("%d\n",__line__);
printf("%d\n",__line__);
}

输出为,如果第一个printf在第三行的话

1
2
3
3
100
101

结语

刚刚开始写博客,这是我水的第二篇博文了,依然是那么的水,主要还是为了想要测试一下HEXO的玩法。以后希望自己能够在javascript 、python 、 React Native 这三个方向进步。写这个博客也是为了学习一下javescript,结果几乎没有用到js的知识。以后写一些有关于这三个方向的学习心得或者说是学习笔记吧。这篇文章发出来之后,博客的基本用法也就学的七七八八了,期待自己的进阶之旅,也期待自己在iOS的进阶之旅,也期待自己在is py rn这三个方向的入门之旅。

戴伟来地址

哦, 对了那个TODO是这样设置的:

第四步中的脚本为:

1
2
KEYWORDS="TODO:|FIXME:|\?\?\?:|\!\!\!:"
find "${SRCROOT}" \( -name "*.h" -or -name "*.m" \) -print0 | xargs -0 egrep --with-filename --line-number --only-matching "($KEYWORDS).*\$" | perl -p -e "s/($KEYWORDS)/ warning: \$1/"

原理是根据正则表达式去判断。

用起来是这样的:

CepheusSun wechat
订阅我的公众号,每次更新我都不一定会告诉你!
坚持原创技术分享,您的支持将鼓励我继续创作!
0%