selector

4、iOS强化 --- 链接与符号(Symbol)

2021-03-09  本文已影响0人  Jax_YD

首先我们来认识一下什么是链接:

image.png

2、创建我们自己的xcconfig文件(这一步有不明白的同学可以阅读Xcode 多环境的配置这一批文章)
3、在xcconfig文件中输入脚本要用的一些参数(注意:1、这里不是shell指令,是Key-Value的形式,2、此时我们还没有写任何代码)

脚本中需要输入两个变量:
CMD : 指令

TTY:指定的终端(可以终端输入tty,会打印当前终端的信息)

同时我们还需要知道当前Mach-O的地址:

MACH_PATH=${BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)/${PRODUCT_NAME}
  • MACH_PATH:我们自己定义的变量,用来存储Mach-O的地址
  • ${BUILD_DIR}:当前编译的路径
  • $(CONFIGURATION):构建产品的目录
  • $(EFFECTIVE_PLATFORM_NAME)Mach-O所在的目录
  • ${PRODUCT_NAME}:项目名称,也就是Mach-O的名称

① 首先我们打印一下Mach header

// 查看mach-header
CMD = objdump --macho -private-header ${MACH_PATH}
image.png
这里大家可以看到Mach header的一些原始的信息。
② 接下来我们查看一下Mach-o里面的__TEXT段(因为:main函数被编译之后会放到__TEXT段的)
image.png
可以看到显示的__TEXT段、__text section的机器指令(如:55),后面跟着的汇编代码是给开发者看的。
在底层会有一个类似与字典的东西,会将汇编指令机器码一一对应起来
下面我们在main.m文件中添加一些代码,在来看一下__TEXT段(代码段):
/// 全局变量
int global_uninit_value;

int global_init_value = 10;
//__attribute__关键字主要是用来在函数或数据声明中设置其属性。给函数赋予属性的主要目的在于让编译器进行优化。
double defaule_x __attribute__((visibility("hidden")));
/// 静态变量 -》本地符号
static int static_uninit_value;

int main(int argc, const char * argv[]) {
    static_uninit_value = 10;
    NSLog(@"%d", static_uninit_value);
    return 0;
}
image.png
我们会发现__TEXT段多了很多东西。
可以看到NSLog直接就变成了callq 0x100003f90指令,一个有明确地址的指令。
全局符号与本地符号

结合上面的代码:

接下来我们来查看一下符号表:

// 查看符号表
CMD = objdump --syms ${MACH_PATH}
image.png

上图中有一点不同,不知道大家注意到没有:
defaule_x这个符号我们定义的是一个全局符号,但是最终它是一个本地符号,这是为什么呢?
因为我们是这样写的:

double defaule_x __attribute__((visibility("hidden")));

这里我们就要引入visibility属性:

// visibility属性,控制文件导出符号,限制符号可见性
/**
    -fvisibility:clang参数
    default:用它定义的符号将被导出。
    hidden:用它定义的符号将不被导出。
 */
// 隐藏 -> 本地
int hidden_y __attribute__((visibility("hidden"))) = 99;
// 符号
double default_y __attribute__((visibility("default"))) = 100;

这里说明一下,我们在开发中经常会遇到被Apple遗弃的方法,会有一条黄线的警告,其实也是用了这个属性。

导入符号 & 导出符号

还是上面的代码,我们用到了NSLog(它存在于Foundation动态库中);那么对于我们自己的可执行文件NSLog就是导入符号;对于Foundation动态库NSLog就是导出符号
这也就意味着导出符号全局符号
下面我们打印一下导出符号

// 查看导出符号
CMD = objdump --macho --exports-trie ${MACH_PATH}
image.png
可以看到正好对应的是我们设置的全局变量。
在日常开发中我们要注意:当我们把变量设置成全局变量的时候,也就意味着会被默认设置成导出符号
// 查看间接符号表
CMD = objdump --macho --indirect-symbols ${MACH_PATH}
image.png
// 查看导出符号
CMD = objdump --macho --exports-trie ${MACH_PATH}
image.png
OTHER_LDFLAGS=$(inherited) -Xlinker -unexported_symbol -Xlinker _OBJC_METACLASS_$_YSOneObject
image .png

我们成功的将符号_OBJC_METACLASS_$_YSOneObject设置成非导出符号
这样我们就可以对齐进行符号剥离,进一步减少动态库的体积;同时外界也就看不到我们的符号了。
⚠️ 开发中我们也不必一个一个的去将符号设置成不导出,我们可以指定一个文件来设置符号的导出属性,使用-unexported_symbol_list这个指令

OTHER_LDFLAGS=$(inherited) -Xlinker -S -Xlinker -map -Xlinker /Users/aaron/Desktop/test-02/Source.text
image.png
弱符号(Weak Symbol)

弱符号分为:

那么怎么理解上面的两句话呢?
首先我们来看在代码中怎么写:

// 弱引用
void weak_import_function(void) __attribute__((weak_import));

// 弱定义
// weak def
void weak_function(void)  __attribute__((weak));
// weak 本地符号
void weak_hidden_function(void) __attribute__((weak, visibility("hidden")));

首先我们来看一下:

CMD = objdump --macho --exports-trie ${MACH_PATH}
image.png
可以看到弱定义并不影响其作为导出符号
接着我们在其他地方在定义一个名字相同的全军符号,如下:
image.png
我们会发现,工程并不会报错。运行起来也没有问题,这正式我们上面说的:如果静态链接器或动态链接器为此符号找到另一个(非弱)定义,则弱定义将被忽略。

接下来我们再看一下:

void weak_import_function(void) __attribute__((weak_import));

同样的查看导出符号表

image.png
我们会发现导出符号表里面并没有该符号。
接下来我们实现以下该函数:
void weak_import_function(void) {
    NSLog(@"weak_import_function");
}

然后再查看一下导出符号表:

image.png
这也就是当动态链接器找不到它的定义,则将其定义为0,也就不会出现在导出符号表里面了。
if (weak_import_function) {
        weak_import_function();
        
        /**
         一些其他的业务
         */
    }

我们cmd + B会发现,工程报错:

image.png
也就是说说在链接的过程中,链接器找不到这个符号(不知道符号具体在哪个地方)。

这个时候我们可以告诉链接器,不要管这个符号,即使是未定义的也不要管,我这个符号是动态链接的,到时候会自己找到这个符号。那么我们可以通过下面的指令来达到这个目的:

OTHER_LDFLAGS=$(inherited) -Xlinker -U -Xlinker _weak_import_function

重新导出符号

举一个例子:
我们上面的代码在mian.m文件中用到的NSLog这个函数。然而NSLog对于当前工程来说是一个未定义符号

image.png
那么此时,我们如果想要使用我们这个库的工程,也能够使用NSLog,此时我们就需要将NSLog以别名的形式从新导出。
⚠️ 这里要注意:只能给间接符号表中的符号起别名。
OTHER_LDFLAGS=$(inherited) -Xlinker -alias -Xlinker _NSLog -Xlinker YS_NSLog

我们再来查看一下导出符号表:

image.png
可以看得到YS_NSLog作为导出符号,并且被标记为re-export

Swift 符号

首先我们在swift文件中定义一些结构体方法

struct YSSwiftStructSymbol {
    func testSwiftStructSymbol(o: Int) {}
}

private protocol YSSwiftProtocolSymbol: class {
    func testSwiftProtocolSymbol()
}

private class YSSwiftClassSymbol {
    func testSwiftSymbol() {}
}

接着我们查看一下现在的符号表:

// 查看符号表
CMD = objdump --syms ${MACH_PATH}
image.png
会发现这里面多了很多的符号。
那么我们来定向查找swift产生的符号:
CMD = objdump --syms ${MACH_PATH} | grep "SwiftClass"
image.png
这样就少了很多,并且全部都是本地符号。下面我们修改一下swift中方法的权限:
public class YSSwiftClassSymbol {
    func testSwiftSymbol() {}
}

再来查看一下swift符号:

image.png
可以看到有一些符号已经变成了全局符号

总结:Swift是一门静态语言,跟OC不一样。Swift在编译的时候就能确定符号的类别。

上一篇 下一篇

猜你喜欢

热点阅读