大家好,今天小编来为大家解答以下的问题,关于58同城类似网站源码分享,像58同城一样的网站这个很多人还不知道,现在让我们一起来看看吧!
作者|赵志、曾庆隆、顾梦奇、王强、赵发出品|CSDN(ID:CSDNnews)
2019年3月25日,苹果发布了Swift5.0版本,宣布了ABI稳定,并且Swiftruntime和标准库已经植入系统中,而且苹果新出文档都用Swift,SampleCode也是Swift,可以看出Swift是苹果扶持与研发的重点方向。
目前国内外各大公司都在相继试水,只要关注Swift在国内iOS生态圈现状,你就会发现,Swift在国内App应用的比重逐渐升高。对于新App来说,可以直接用纯Swift进行开发,而对于老App来说,绝大部分以前都是用OC开发的,因此Swift/OC混编是一个必然面临的问题。
CSDN付费下载自视觉中国
关于Swift和OC间如何混编,业内也已经有很多相关文章详细讲解,简单来说OC/Swift调用Swift,最终通过SwiftModule进行,而Swift调用OC时,则是通过ClangModule,当然也可以通过ClangModule进行OC对OC的调用。58同城于2020年正式上线首个Swift/OC(Objective-C,以下简称OC)项目,与此同时,也在全公司范围内开展了一个多部门协作项目——混天项目,主要目标:
一是提供混编的基础设施建设,如提供通过的Module化方案;
二是扩展各工具链的混编能力,如对无用类检测工具WBBlades(https://github.com/wuba/WBBlades)进行Swift能力的扩展;
三是对已有的基础库进行Module化和Swift适配;
四是将混编开发在各App和各业务线中推广和落地。
我们在Module化实践中发现,实际数据与苹果官方Module编译时间数据不一致,于是我们通过Clang源码和数据相结合的方式对ClangModule进行了深入研究,找到了耗时的原因。由于Swift/OC混编下需要Module化的支持,同时借鉴业内HeaderMap方案让OC调用OC时避开Module化调用,将编译时间优化了约35%,较好地解决了在Module化下的编译时间问题。
ClangModule初探
ClangModule在2012LLVMDevelopersMeeting上第一次被提出,主要用来解决C语言预处理的各种问题。Modules试图通过隔离特定库的接口并且编译一次生成高效的序列化文件来避免C预处理器重复解析Header的问题。在探究ClangModule之前,我们先了解一下预处理的前世今生。一个源代码文件到经过编译输出为目标文件主要分为下面几个阶段:源文件在经过Clang前端包含:词法分析(Lexicalanalysis)、语法分析(Syntacticanalysis)、语义分析(Semanticanalysis)。最后输出与平台无关的IR(LLVMIRgenerator)进而交给后端进行优化生成汇编输出目标文件。词法分析(Lexicalanalysis)作为前端的第一个步骤负责处理源代码的文本输入,具体步骤就是将语言结构拆分为一组单词和记号(token),跳过注释,空格等无意义的字符,并将一些保留关键字转义为定义好的类型。词法分析过程中遇到源代码“import指令来使用外部的API。那么这些使用的方式在预处理阶段是怎么处理的呢?针对编译器遇到import”header.h”这种导入方式时候,import或者import与include是一种特殊“复制”效果的原因。那么在这种预处理器的机制在工程中编译中会存在什么问题呢?苹果官方在2012的WWDC视频上同样给了我们解答:HeaderFragility(健壮性)和InherentlyNon-Scalable(不可扩展性)。来看下面一段代码,在PodBTestObj类的文件中定义一个ClassName字符串的宏,然后在导入PoBClass1.h头文件,在PoBClass1.h的头文件中同样定义一个结构体名为ClassName,这里与我们在PodBTestObj类中定义的宏同名。预处理的特殊的“复制”机制,在预处理阶段会发生下图所见的结果:这样的问题相信在日常开发中并不罕见,而为了解决这种重名的问题,我们常规的手法只能通过增加前缀或者提前约定规则等方式来解决。视频中同时指出这种机制在应对大型工程编译过程中的所带来的消耗问题。假设有N个源文件的工程,那么每个源文件引用M个头文件,由于预处理的这种机制,我们在针对处理每个源文件的编译过程中会对引入的M个头文件进行展开,经历一遍遍的词法分析-语法分析-语义分析的过程。那么你能想象一下针对系统头文件的引入在预处理阶段将会是一个多么庞大的开销!那么针对C语言预处理器存在的问题,苹果有哪些方案可以优化这些存在的问题呢?
2.2PCH(PrecompiledHeaders)
PCH(PrecompilePrefixHeaderFile)文件,也就是预编译头文件,其文件里的内容能被项目中的其他所有源文件访问。日常开发中,通常放一些通用的宏和头文件,方便编写代码,提高效率。关于PCH的概述,苹果是这样定义的:whichusesaserializedrepresentationofClang’sinternaldatastructures,encodedwiththeLLVMbitstreamformat.(使用Clang内部数据结构序列化表示,采用的LLVM字节流表示)。它的设计理念当项目中几乎每个源文件中都包含一组通用的头文件时,将该组头文件写入PCH文件中。在编译项目中的流程中,每个源文件的处理都会首先去加载PCH文件的内容,所以一旦PCH编译完成,后续源文件在处理引入的外部文件时候会复用PCH编译后的内容,从而加快编译速度。PCH文件中存放我们所需要的外部头文件的信息(包括不局限于声明、定义等)。它以特殊二进制形式进行存储,而在每个源代码编译处理外部头文件信息时候,不需要每次进行头文件的展开和“复制”重复操作。而只需要“懒加载”预编译好的PCH内容即可。存储内容方面它存放着序列化的AST文件。AST文件本身包含Clang的抽象语法树和支持数据结构的序列化表示,它们使用与LLVM’sbitcodefileformat.相同的压缩位流进行存储。关于ASTFile文件的存储结构你可以在官方文档有详细的了解。它作为苹果一种优化方案被提出,但是实际的工程中源代码的引用关系是很复杂的,所以找出一组几乎所有源文件都包含的头文件基本不可能,同时对于代码更新维护更是一个挑战。其次在被包含头文件改动下,因为PCH会被所有源文件引入,会带来代码“污染”的问题。同时一旦PCH文件发生改动,会导致大面积的源代码重编造成编译时间的浪费。
2.3Modules
上述我们简单回顾了一些C语言预处理的机制以及为解决编译消耗引入PCH的方案,但是在一定程度上PCH方案也存在很大的缺陷。因此在2012LLVMDeveloper’sMeeting首次提出了Modules的概念。那么Module到底是什么呢?Module简单来说可以认为它是对一个组件的抽象描述,包含组件的接口和实现。Module机制推出主要用来解决上述所阐述的预处理问题,想要探究ClangModule的实现,首先需要去开启Module。那么针对iOS工程怎么开启Module呢?只需要打开编译选项中:对!你没看错,仅仅需要在Xcode的编译选项中修改配置即可。而在代码的使用上几乎可以不用修改代码,开启Module之后,通过引用头文件的方式可以继续沿用include机制提供的访问外界库API的方式存在的伸缩性和健壮性的问题。Modules提供了更为健壮,更高效的语义模型来替换之前textualpreprocessor改进对库的API访问方式。苹果官方文档中针对Module的解读有以下几个优势:
扩展性:每个Module只会编译一次,将Module导入Translantionunit的时间是恒定的。对于库API的访问只会解析一次,将”开头的预处理指令,我们对针对HeaderName文件进行真实路径查找,并对要导入的文件进行同样的词法,语法,语义等操作。在开启Module化之后,头文件查找流程与之前有什么区别呢?在不修改代码的基础上编译器又是怎么识别为语义化模型导入(Moduleimport)呢?如下图所示:在初始化预处理之前,会针对buildsetting中设置的HeaderSearchpath,FrameworkSearchPath等编译参数解析赋值给SearchDirs。在Clang的源码中HeaderSearch类负责具体头文件的查找工作,HeaderSearch类中持有的SearchDirs存放着当前编译文件所需要的头文件搜索路径。其中对于一个头文件的搜索分三种情况:hmap,HeaderSearchPath以及Frameworkssearchpath。而SearchDirs的赋值发生在编译实体(CompilerInstance)初始化预处理器时,而这些参数的来源则是在Xcode工程Buildsetting中的相关编译参数。编译器在查询头文件具体磁盘路径的过程中,会通过Header.h或者PodName/Header.h与SearchDirs集合中的路径拼接判断该路径下是否存在我们要查找的头文件。当前循环的SearchDirs对应的元素中根据类型:(HeaderSearchPath,Frameworks,HeaderMap)进行相应的查询流程。上文提到过针对开启Module的组件不需要额外的修改头文件导入的代码,编译器自动识别我们的头文件导入是否属于Module,而判断Header导入是否属于Moduleimport就发生在查找头文件路径中。上述代码我们会注意到针对Framework与常规的目录查找中,会透传一个参数SuggestedModule。我们进一步向下跟踪SuggestModule的赋值过程,在查找到头文件的磁盘路径之后,编译器会进行该文件目录或者父级目录路径作为Key去UmbrellaDirs查找该头文件的是否有对应的Module存在。如果能查询到则赋值SuggestModule(ModuleMap::KnownHader(Module*,NormalHeader))。下图为查询并赋值SuggestModule的流程。相信你看到上面的源码,你又会出现新的疑惑。UmbrellaDirs是什么?前面提到过使用开启Module组件的Target中会新增-fmodule-map-file的参数,编译器在解析编译参数时加载MoudleMapFile,读取使用ModuleMapLanguage书写的ModuleMap文件,解析文件的内容。编译器在编译工程源代码时候通过-fmodule-map-file参数读取我们要使用的Module,并把ModuleMap文件所在的路径作为key,我们要使用的Module作为Value,赋值给UmbrellaDirs。预处理器在解析外界引入的头文件时候,会判断头文件路径下或者头文件路径父级目录是否存在ModuleMap文件,如果存在则SuggestModule有值。头文件查找的流程至此结束。SuggestModule的值是编译器决定使用ModuleImport还是“文本导入”的关键因素。预处理器处理头文件导入,会去查找头文件在磁盘上的绝对路径,如果SuggestModule有值,编译器会调用ModuleLoader加载需要的Module,而不开启Module的组件头文件,编译器则会进入该文件进行新的词法分析等流程。至此,相信读到这里大家对ModuleMap、Umbrella文件以及-fmodule-map-path有了一定的认知。而且我们也跟踪了为什么编译器可以做到不修改代码的“智能”的帮助代码在rd
Hmap工具:https://github.com/milend/hmap
llvm-bcanalyzer:https://llvm.org/docs/CommandGuide/llvm-bcanalyzer.html
bitstreamformat:https://llvm.org/docs/BitCodeFormat.html
PCH结构:https://clang.llvm.org/docs/PCHInternals.html#pchinternals-modules
Modules:https://clang.llvm.org/docs/Modules.html
文章分享结束,58同城类似网站源码分享和像58同城一样的网站的答案你都知道了吗?欢迎再次光临本站哦!
