iOS 中特殊变量定义

2021-04-07  本文已影响0人  大成小栈

1. 关于宏

预编译

编译器在编译源码之前会进行预编译,在预编译之前也会进行一些操作,如:删除 反斜线+换行符的组合; 将各种形式的注释用空格替代等等。预编译在处理#define的时会从#开始,一直执行到遇到的第一个换行符(写代码的时候换行的作用)为止

所以,正常情况下#define只会允许一行的宏定义。但是因为预编译之前会删除反斜线+换行符的组合,所以我们可以利用反斜线+换行符来定义多行宏,这样在预编译阶段的逻辑上#define定义就成了一行的宏了。

#define在预处理阶段只进行文本的替换(相当于把代码拷贝粘贴),不会进行具体的计算,而且是直接替换(内联函数!!!),这个和函数有着很大的区别。

// 宏(#define)的基本语法:
#define 宏名 主体;
   ↓   ↓    ↓
#define PI  3.1415926
常见宏定义(#define)的形式
// 类对象宏:类对象宏一般用来定义数据常量或字符串常量。
#define PI   3.1415926
#define NAME @"SunSatan"

// 类函数宏:类函数宏就是宏名类似函数名,宏的主体为函数,
// 并可以帮助主体函数接受参数,使用起来就像是函数一样。
#define Log(x) NSLog(@"this is test: x = %@", x)

类函数宏和函数的区别是, 类函数宏的参数不指定类型, 具体的参数类型在调用宏的时候由传入的参数决定,这就可能会遇到类型错误的问题。

例:

#define power(x) x*x
 
int x = 2;
int pow_1 = power(x);
int pow_2 = power(x+1);

// 猜猜pow_1和pow_2的值分别为多少?
// 结果是pow_1=4 ,pow_2=5。

这个结果是不是和预想的不一样,pow_2不应该等于9吗,为什么是5?
注:宏真正的效果就是进行文本的替换

宏(#define)中操作符的使用:

# 为字符串化操作符,需要放置在参数前,将类函数宏中传入的参数用""括起来变成了c语言的字符串。

#define Log(x) #x

// 代码替换后:
// Log(x) -> "x"

## 为符号连接操作符,需要放置在参数前,参数就会和##前面的符号连接起来。

#define single(name) +(instancetype)share##name;

// 代码替换后:
// single(SunSatan) -> +(instancetype)shareSunSatan;

2. 静态、常量、全局

static

用static来修饰一个变量时,它的生命周期、作用域会发生变化;其内存只被分配一次并存储到全局变量区,可供所有对象使用,其值在下次调用时仍维持上次的值。
有时,我们为了避免重复定义一个变量,也可以使用static。例如tableView的cell重用机制中,我们会定义一个cellIdentifier ...

// .m中,仅本文件可见,不可再被extern
static int num;    

@implementation 
@end 
const

const修饰的是其右边的值,只可初始化时赋值一次,const右边的这个整体的值不能再改变,只可读。const 在*前/后的区别如下:

// 两种写法等价,指针内容不可变
const NSString *name = @"bac";
NSString const * name = @"abc";
 // 指针地址不可变 
NSString * const str = @"abc";
extern

主要是用来引用全局变量,它的原理是先在本文件中查找,查找不到再到其他文件中查找。

//// .h中
@interface PDConst : NSObject

extern NSString *const appBaseURL;

@end

////.m中
@implementation PDConst

NSString *const currentBaseURL = @"http://192....";

@end

3. 高级应用场景

//static和const联合使用是用来替代宏,把一个经常使用的字符串常量,定义成静态全局只读变量
//使用const修饰key,表示key只读,不允许修改
static NSString *const key = @“name”;
//如果const修饰 *key1,表示 *key1 只读,key1还是能改变的
static NSString const *key1 = @“myName”;

场景:封装一个三方库,其头文件中的代码如下,在引用了它的类中,我们能够使用DDLogVerbose(...)来打印log(当然三方库的其他文件中已经处理好了log的逻辑),那么我们应该怎么运行呢?

#ifndef LOG_LEVEL_DEF
    #define LOG_LEVEL_DEF ddLogLevel
#endif

typedef NS_OPTIONS(NSUInteger, DDLogFlag){
    DDLogFlagError      = (1 << 0),
    DDLogFlagWarning    = (1 << 1),
    DDLogFlagInfo       = (1 << 2),
    DDLogFlagDebug      = (1 << 3),
    DDLogFlagVerbose    = (1 << 4)
};


typedef NS_ENUM(NSUInteger, DDLogLevel){
    DDLogLevelOff       = 0,
    DDLogLevelError     = (DDLogFlagError),
    DDLogLevelWarning   = (DDLogLevelError   | DDLogFlagWarning),
    DDLogLevelInfo      = (DDLogLevelWarning | DDLogFlagInfo),
    DDLogLevelDebug     = (DDLogLevelInfo    | DDLogFlagDebug),
    DDLogLevelVerbose   = (DDLogLevelDebug   | DDLogFlagVerbose),
    DDLogLevelAll       = NSUIntegerMax
};

#define SerLog(format,...) [ServiceLogManager customLogWithFormatString:[NSString stringWithFormat:format, ##__VA_ARGS__]]

#define DDLogVerbose if (LOG_LEVEL_DEF & DDLogFlagVerbose) SerLog
#define DDLogDebug   if (LOG_LEVEL_DEF & DDLogFlagDebug)   SerLog
#define DDLogWarn    if (LOG_LEVEL_DEF & DDLogFlagWarning) SerLog
#define DDLogInfo    if (LOG_LEVEL_DEF & DDLogFlagInfo)    SerLog
#define DDLogError   if (LOG_LEVEL_DEF & DDLogFlagError)   SerLog

在调用打log方法之前,如,在调用DDLogVerbose的文件内,我们首先要给定一个ddLogLevel变量:

static const DDLogLevel ddLogLevel = DDLogLevelVerbose;

这样,三方库中的宏在被调用处,预编译时就能自动将LOG_LEVEL_DEF文本替换为ddLogLevel变量。若不给定这个变量会报这样的错误,Use of undeclared identifier 'ddLogLevel',你可能不理解ddLogLevel为什么定义在三方库外面,却被三方库自动读取到?

替换的逻辑很简单:宏的替换动作不是在三方库源码内被进行的,而是在调用DDLogVerbose的位置,宏被展开,进而就直接在那个位置被替换掉。

似乎据此思路,我们可以做一些看起来很奇妙的事情🤩!

参考文章:
https://blog.csdn.net/qq_36557133/article/details/86476686
http://blog.sina.com.cn/s/blog_134451adb0102wfrn.html
https://my.oschina.net/u/4410805/blog/3391545
感谢!

上一篇下一篇

猜你喜欢

热点阅读