浅谈iOS编译过程

LLVM Clang前端编译与调试 LLVM Clang前端编译与调试 iOS 关于编译 o 一、Objective-C 编译过程 o 为什么需要重新编译? o 编译步骤 o 二、编译步骤的详细说明 o 1.预处理 o 2.编译 o 词法分析 o 语法分析 o clang static analyzer o 3.生成 o LLVM IR o LLVM Backend o 三、编译完成生成的文件 o Link Map File o dSYM 文件 o Mach-O o 四、总结 o 五、推荐学习 技术学习有两种方向,一种是不断向前,了解前沿和趋 阅读详情

引言

维基百科:
编译语言(英语:Compiled language)是一种以编译器来实现的编程语言。它不像解释型语言一样,由解释器将代码一句一句运行,而是以编译器,先将代码编译为机器码,再加以运行。理论上,任何编程语言都可以是编译式,或直译式的。它们之间的区别,仅与程序的应用有关。

一般可以将编程语言分为两种,编译语言和直译式语言。

前言

iOS开发使用Object-C和Swift编译语言,两者都需要通过编译器(Clang + LLVM)把代码编译器生成机器码,机器码可以直接在CPU上执行。接下来详细介绍一下这两种语言的优缺点,可以更深入的理解为什么移动端开发会采用编译语言。

编译语言和直译式语言两种编程语言的优缺点:

编译语言/直译式语言优缺点比较:

编译/直译式优点缺点
编译语言运行速度快(被预先编译成机器码,可以直接运行)开发、调试比较长(程序开发速度,以及除错时间时间较长)
直译式语言开发、调试比较短(程序开发速度,以及除错时间时间较长。应用程序不能脱离其解释器,但这种方式比较灵活,可以动态地调整、修改应用程序)运行速度慢(源代码一边由相应语言的解释器翻译”成目标代码(机器语言),一边执行)

编译语言: 像C++、Objective-C(Swift)、C、Java等都是编译语言,必须通过编译器生成机器码,机器码可以直接在CPU上执行,所以你执行效率较高。而每次运行都需要编译器生成机器码再运行,故编写、调试、排错比较繁琐。

直译式语言:像JavaScript、Python、PHP等都是直译式语言,不需要经过编译的过程,而是在执行的时候通过一个中间的解释器将代码解释为CPU可以执行的代码。所以,较编译语言来说,直译式语言效率低一些,但是编写比较灵活。

总之,由于移动端(iOS、Android)设备性能限制的情况下,采用编译语言进行开发是一种比较好的方式

一、iOS编译器

在讲解编译过程之前,需要了解苹果公司采用的编译器,编译器采用哪种方式进行编译,才能更深入的理解编译底层的整个过程。

1.Xcode编译器发展史

Xcode3 以前: GCC;
Xcode3: 增加LLVM,GCC(前端) + LLVM(后端);
Xcode4.2: 出现Clang - LLVM 3.0成为默认编译器;
Xcode4.6: LLVM 升级到4.2版本;
Xcode5: GCC被废弃,新的编译器是LLVM 5.0,从GCC过渡到Clang-LLVM的时代正式完成

  • 1.1.为什么苹果的Xcode会使用Clang+LLVM取代GCC?

    • GCC最初是作为CNU(GNU是“GNU is Not Unix”)操作系统的编译器编写的,是一套由 GNU 开发的编程语言编译器,不属于苹果维护也不能完全控制开发进程,Apple为Objective-C增加许多新特性,但是GCC开发者对这些支持却不友好;Apple需要做模块化,GCC开发者却拖着迟迟不实现。
  • 1.2.GCC被取代的历史必然

    • 随着Apple对其IDE(也就是Xcode)性能的要求越来越高,苹果公司需要找到一个可以控制的编译器。而在在科技的历史长河中,LLVM项目于2000年在伊利诺伊大学厄巴纳 - 香槟分校开始,由Vikram Adve和Chris Lattner领导。 LLVM最初是作为研究基础设施开发的,用于研究静态和动态编程语言的动态编译技术。 LLVM是根据伊利诺伊大学/ NCSA开源许可证发布的,是一个许可的免费软件许可证。
  • 1.3.GCC被取代

    • 2005年,Apple Inc.聘请了Lattner并组建了一个团队,致力于LLVM系统,以便在Apple的开发系统中实现各种用途。 LLVM是Apple最新的macOS和iOS开发工具中不可或缺的一部分。
2.为什么选择LLVM编译器
2.1编译器
2.1.1经典编译器设计简介

传统静态编译器(如大多数C编译器)最流行的设计是三阶段设计,其主要组件是前端,优化器和后端。 前端解析源代码,检查它是否有错误,并构建一个特定于语言的抽象语法树(AST)来表示输入代码。 AST可选地转换为新的表示以进行优化,优化器和后端在代码上运行。
1111.png

2.1.2GCC编译器设计简介(同样采用三相设计)

GCC编译器的三相设计中最成功的实现是对Java、 .Net语言的编译解析。
222.png

2.1.3 LLVM编译器设计简介(实现三相设计)

在基于LLVM的编译器中,前端负责解析,验证和诊断输入代码中的错误,然后将解析的代码转换为LLVM IR(通常情况。但是也有例外,通过构建AST然后将AST转换为LLVM IR)。该IR可选地通过一系列改进代码的分析和优化过程提供,然后被发送到代码生成器以生成本机机器代码,如图下图所示。
3333.png

为什么要使用三相设计?优势在哪?

首先解决了一个很大的问题:假如有N种语言(C、OC、C++、Swift…)的前端,同时也有M个架构(模拟器、arm64、x86…)的Target,是否就需要 N × M 个编译器?
三相架构的价值就体现出来了,通过共享优化器的中转,很好的解决了这个问题。
假如你需要增加一种语言,只需要增加一种前端;假如你需要增加一种处理器架构,也只需要增加一种后端,而其他的地方都不需要改动。这复用思想很牛逼吧。

2.2 LLVM编译器的组成

LLVM项目是模块化和可重用的编译器和工具链技术的集合。LLVM主要的子项目有一下几个:

1.LLVM核心库:
LLVM提供一个独立的链接代码优化器为许多流行CPU(以及一些不太常见的CPU)的代码生成支持。这些库是围绕一个指定良好的代码表示构建的,称为LLVM中间表示(“LLVM IR”)。LLVM还可以充当JIT编译器 - 它支持x86 / x86_64和PPC / PPC64程序集生成,并具有针对编译速度的快速代码优化。。
2.LLVM IR 生成器Clang:
Clang是一个“LLVM原生”C / C ++ / Objective-C编译器,旨在提供惊人的快速编译(例如,在调试配置中编译Objective-C代码时比GCC快3倍),非常有用的错误和警告消息以及提供构建优秀源代码工具的平台。
3.LLDB项目:
LLDB项目以LLVM和Clang提供的库为基础,提供了一个出色的本机调试器。它使用Clang AST和表达式解析器,LLVM JIT,LLVM反汇编程序等,以便提供“正常工作”的体验。在加载符号时,它也比GDB快速且内存效率更高。
4.libc ++和libc++:
libc ++和libc++ ABI项目提供了C ++标准库的标准符合性和高性能实现,包括对C ++ 11的完全支持。
5.lld项目:
lld项目旨在成为clang / llvm的内置链接器。目前,clang必须调用系统链接器来生成可执行文件。

其他的就不再详细介绍了,详情可以参考(LLVM和Clang)

总之,LLVM是Apple主导的开源框架,并提供一套使用于Apple平台的LLVM编译器,同时提供优秀的性能,所以Apple采用LLVM的方式进行编译

3. Clang + LLVM 的编译简单过程
3.1.Clang + LLVM 编译过程

LLVM采用三相设计,前端Clang负责解析,验证和诊断输入代码中的错误,然后将解析的代码转换为LLVM IR,后端LLVM编译把IR通过一系列改进代码的分析和优化过程提供,然后被发送到代码生成器以生成本机机器代码。

简单的流程如下图:

444.png

3.2 Clang 编译前端

编译器前端的任务是进行:语法分析,语义分析,生成中间代码(intermediate representation )。在这个过程中,会进行类型检查,如果发现错误或者警告会标注出来在哪一行。

555.png

3.3 LLVM 编译后端

编译器后端会进行机器无关的代码优化,生成机器语言,并且进行机器相关的代码优化。iOS的编译过程,后端的处理如下:

LVVM优化器会进行BitCode的生成,链接期优化等等。

666.png

LLVM机器码生成器会针对不同的架构,比如arm64等生成不同的机器码。

777.png

二、LLVM 编译详细过程原理

####1.简述LLVM的使用场景
通过上方的讲解,可能对LLVM有一个简单的了解,但是感觉LLVM是底层编译器的内容,和开发没有太大的关系,在使用过程中基本上用不到,感觉遥不可及,其实LLVM在项目的使用过程中一直都在使用,只是没有发现。

Clang是LLVM的一个前端,在Xcode编译iOS项目的时候,都是使用的LLVM,其实在编写代码以及调试的时候都在接触LLVM提供的功能,例如:代码的亮度(Clang)、实时代码检查(Clang)、代码提示(Clang)、debug断点调试(LLDB)。

2.项目编译过程简介

下面来简单的讲讲整个 iOS 项目的编译过程,其中可能会有一些疑问,先保留着,后面会详细解释

我们的项目是一个 target,一个编译目标,它拥有自己的文件和编译规则,在我们的项目中可以存在多个子项目,这在编译的时候就导致了使用了 Cocoapods 或者拥有多个 target 的项目会先编译依赖库。这些库都和我们的项目编译流程一致。Cocoapods 的原理解释将在文章后面一部分进行解释。

iOS 项目的编译过程

1.写入辅助文件:将项目的文件结构对应表、将要执行的脚本、项目依赖库的文件结构对应表写成文件,方便后面使用;并且创建一个 .app 包,后面编译后的文件都会被放入包中;
2.运行预设脚本:Cocoapods 会预设一些脚本,当然你也可以自己预设一些脚本来运行。这些脚本都在 Build Phases 中可以看到;
3.编译文件:针对每一个文件进行编译,生成可执行文件 Mach-O,这过程 LLVM 的完整流程,前端、优化器、后端;
4.链接文件:将项目中的多个可执行文件合并成一个文件;
5.拷贝资源文件:将项目中的资源文件拷贝到目标包;
6.编译 storyboard 文件:storyboard 文件也是会被编译的;
7.链接 storyboard 文件:将编译后的 storyboard 文件链接成一个文件;
8.编译 Asset 文件:我们的图片如果使用 Assets.xcassets 来管理图片,那么这些图片将会被编译成机器码,除了 icon 和 launchImage;
9.运行 Cocoapods 脚本:将在编译项目之前已经编译好的依赖库和相关资源拷贝到包中。
10.生成 .app 包
11.将 Swift 标准库拷贝到包中
12.对包进行签名
13.完成打包

简单的Demo的Build过程图和App 内部的结构888.jpeg999.jpeg

在上述流程中:2 - 9 步骤的数量和顺序并不固定,这个过程可以在 Build Phases 中指定。Phases:阶段、步骤。这个 Tab 的意思就是编译步骤。其实不仅我们的整个编译步骤和顺序可以被设定,包括编译过程中的编译规则(Build Rules)和具体步骤的参数(Build Settings),在对应的 Tab 都可以看到。关于整个编译流程的日志和设定,可以查看这篇文章:Build 过程,跟着它的步骤来查看自己的项目将有助于你理解整个编译流程。后面也会详细讲解这些内容。

3.文件编译过程
3.1. 预处理
  • 预处理顾名思义是预先处理,那预处理都做了哪些事情呢?内容如下。
    • (1) import 头文件替换
      代码中会有很多 #import 宏,预处理的第一步就是将 import 引入的文件代码放入对应文件。
    • (2) macro 宏展开
      带参数宏和不带参数宏
    • (3)处理其他的预编译指令(其实预编译过程也是出了预编译指令的过程)

条件编译语句也是在预处理阶段完成,并且条件编译只允许编译源程序中满足条件的程序段,使生成的目标程序较短,从而减少了内存的开销并提高了程序的效率,如以下代码就只会保留一个return语句:

#if DEBUG        
     return YES;
#else
     return NO;
#endif
  • (4)总之
    简单来说,“#”这个符号是编译器预处理的标志
//  Clang 方法: 在终端执行一下

$clang -E main.m 

// main.m
#import <Foundation/Foundation.h>
#define aa 10
int main(){ 
    NSObject *obj = [[NSObject alloc] init];
    id __weak obj1 = obj;
    NSLog(@"------%@--%d--",[obj1 class],aa);  
}
 

// 编译后的代码
int main(){
    NSObject *obj = [[NSObject alloc] init];
    id __attribute__((objc_ownership(weak))) obj1 = obj;
    NSLog(@"------%@--%d--",[obj1 class],10);
}
   
3.2. Lexical Analysis - 词法分析(输出token流)

使用 clang 命令 clang -Xclang -dump-tokens main.m 转化后的代码如下(去掉了#import <Foundation/Foundation.h>的内容):

  • 词法分析,只需要将源代码以字符文本的形式转化成Token流的形式,不涉及交验语义,不需要递归,是线性的。

什么是token流呢?可以这么理解:就是有"类型",有"值"的一些小单元。

词法分析其实是编译器开始工作真正意义上的第一个步骤,其所做的工作主要为将输入的代码转换为一系列符合特定语言的词法单元,这些词法单元类型包括了关键字,操作符,变量等等。

可以通过下发被编译过的代码对应main.m文件,把所有的内容都一一对应起来。

// 编译前

int main(){
    NSObject *obj = [[NSObject alloc] init];
    id __attribute__((objc_ownership(weak))) obj1 = obj;
    NSLog(@"------%@--%d--",[obj1 class],10);
}
//编译后

int 'int'	 [StartOfLine]	Loc=<main3.m:11:1>
identifier 'main'	 [LeadingSpace]	Loc=<main3.m:11:5>
l_paren '('		Loc=<main3.m:11:9>
r_paren ')'		Loc=<main3.m:11:10>
l_brace '{'		Loc=<main3.m:11:11>
identifier 'NSObject'	 [StartOfLine] [LeadingSpace]	Loc=<main3.m:13:5>
star '*'	 [LeadingSpace]	Loc=<main3.m:13:14>
identifier 'obj'		Loc=<main3.m:13:15>
equal '='	 [LeadingSpace]	Loc=<main3.m:13:19>
l_square '['	 [LeadingSpace]	Loc=<main3.m:13:21>
l_square '['		Loc=<main3.m:13:22>
identifier 'NSObject'		Loc=<main3.m:13:23>
identifier 'alloc'	 [LeadingSpace]	Loc=<main3.m:13:32>
r_square ']'		Loc=<main3.m:13:37>
identifier 'init'	 [LeadingSpace]	Loc=<main3.m:13:39>
r_square ']'		Loc=<main3.m:13:43>
semi ';'		Loc=<main3.m:13:44>
identifier 'id'	 [StartOfLine] [LeadingSpace]	Loc=<main3.m:14:5>
__attribute '__attribute__'	 [LeadingSpace]	Loc=<main3.m:14:8 <Spelling=<built-in>:309:16>>
l_paren '('		Loc=<main3.m:14:8 <Spelling=<built-in>:309:29>>
l_paren '('		Loc=<main3.m:14:8 <Spelling=<built-in>:309:30>>
identifier 'objc_ownership'		Loc=<main3.m:14:8 <Spelling=<built-in>:309:31>>
l_paren '('		Loc=<main3.m:14:8 <Spelling=<built-in>:309:45>>
identifier 'weak'		Loc=<main3.m:14:8 <Spelling=<built-in>:309:46>>
r_paren ')'		Loc=<main3.m:14:8 <Spelling=<built-in>:309:50>>
r_paren ')'		Loc=<main3.m:14:8 <Spelling=<built-in>:309:51>>
r_paren ')'		Loc=<main3.m:14:8 <Spelling=<built-in>:309:52>>
identifier 'obj1'	 [LeadingSpace]	Loc=<main3.m:14:15>
equal '='	 [LeadingSpace]	Loc=<main3.m:14:20>
identifier 'obj'	 [LeadingSpace]	Loc=<main3.m:14:22>
semi ';'		Loc=<main3.m:14:25>
identifier 'NSLog'	 [StartOfLine] [LeadingSpace]	Loc=<main3.m:15:5>
l_paren '('		Loc=<main3.m:15:10>
at '@'		Loc=<main3.m:15:11>
string_literal '"------%@--%d--"'		Loc=<main3.m:15:12>
comma ','		Loc=<main3.m:15:28>
l_square '['		Loc=<main3.m:15:29>
identifier 'obj1'		Loc=<main3.m:15:30>
identifier 'class'	 [LeadingSpace]	Loc=<main3.m:15:35>
r_square ']'		Loc=<main3.m:15:40>
comma ','		Loc=<main3.m:15:41>
numeric_constant '10'		Loc=<main3.m:15:42 <Spelling=main3.m:10:12>>
r_paren ')'		Loc=<main3.m:15:44>
semi ';'		Loc=<main3.m:15:45>
r_brace '}'	 [StartOfLine]	Loc=<main3.m:17:1>
eof ''		Loc=<main3.m:17:2>

这里,每一个符号都会标记出来其位置,这个位置是宏展开之前的位置,这样后面如果发现报错,就可以正确的提示错误位置了。

3.3.Semantic Analysis - 语法分析(输出(AST)抽象语法树)

对代码进行标记并不是Clang最终的目的,而是一个Clang的一个过程,其实标记代码为了让代码更便于转化成机器语言,标记代码转化成抽象语法树(abstract syntax tree – AST)是一个必经之路。

3.3.1 AST

使用 clang 命令 clang -Xclang -ast-dump -fsyntax-only main.m,转化后的树如下

-FunctionDecl 0x7fbbea20f538 <main3.m:11:1, line:17:1> line:11:5 main 'int ()'
  `-CompoundStmt 0x7fbbea20fa40 <col:11, line:17:1>
    |-DeclStmt 0x7fbbea20f6b8 <line:13:5, col:44>
    | `-VarDecl 0x7fbbea20f5e8 <col:5, col:43> col:15 used obj 'NSObject *' cinit
    |   `-ObjCMessageExpr 0x7fbbea20f688 <col:21, col:43> 'NSObject *' selector=init
    |     `-ObjCMessageExpr 0x7fbbea20f658 <col:22, col:37> 'NSObject *' selector=alloc class='NSObject'
    |-DeclStmt 0x7fbbea20f830 <line:14:5, col:25>
    | `-VarDecl 0x7fbbea20f778 <col:5, col:22> col:15 used obj1 '__weak id':'__weak id' cinit
    |   `-ImplicitCastExpr 0x7fbbea20f818 <col:22> 'id':'id' <BitCast>
    |     `-ImplicitCastExpr 0x7fbbea20f800 <col:22> 'NSObject *' <LValueToRValue>
    |       `-DeclRefExpr 0x7fbbea20f7d8 <col:22> 'NSObject *' lvalue Var 0x7fbbea20f5e8 'obj' 'NSObject *'
    `-ExprWithCleanups 0x7fbbea20fa28 <line:15:5, col:44> 'void'
      `-CallExpr 0x7fbbea20f9d0 <col:5, col:44> 'void'
        |-ImplicitCastExpr 0x7fbbea20f9b8 <col:5> 'void (*)(id, ...)' <FunctionToPointerDecay>
        | `-DeclRefExpr 0x7fbbea20f848 <col:5> 'void (id, ...)' Function 0x7fbbe94469e0 'NSLog' 'void (id, ...)'
        |-ImplicitCastExpr 0x7fbbea20fa10 <col:11, col:12> 'id':'id' <BitCast>
        | `-ObjCStringLiteral 0x7fbbea20f8a8 <col:11, col:12> 'NSString *'
        |   `-StringLiteral 0x7fbbea20f870 <col:12> 'char [15]' lvalue "------%@--%d--"
        |-ObjCMessageExpr 0x7fbbea20f908 <col:29, col:40> 'Class':'Class' selector=class
        | `-ImplicitCastExpr 0x7fbbea20f8f0 <col:30> 'id':'id' <LValueToRValue>
        |   `-DeclRefExpr 0x7fbbea20f8c8 <col:30> '__weak id':'__weak id' lvalue Var 0x7fbbea20f778 'obj1' '__weak id':'__weak id'
        `-IntegerLiteral 0x7fbbea20f938 <line:10:12> 'int' 10

这个main方法的抽象树可以看出来树顶是FunctionDecl:方法声明(Function Declaration)。

这里因为截取了部分代码,其实并不是整个树的树顶。真正的树顶描述应该是:TranslationUnitDecl。

详细的AST语法不多介绍,关于 AST 的详细解释可以查看:Introduction to the Clang AST。

3.3.2 静态分析
  • 通过语法树进行代码静态分析,找出非语法性错误
  • 模拟代码执行路径,分析出control-flow graph(CFG) 【MRC时代会分析出引用计数的错误】
  • 预置了常用Checker(检查器)
3.4. CodeGen - (Intermediate Representation,简称IR)IR中间代码生成

当通过Clang语法解析,代码没有出现报错,Clang前端就将进入最后一步:生成LLVM IR中间代码,并将生成的LLVM IR代码递交给优化器。

使用命令 clang -S -emit-llvm main3.m -o main.ll生成LLVM 中间代码LLVM IR

// mian3.m代码
#import <Foundation/Foundation.h>
#define aa 10
int main(){
    
    NSObject *obj = [[NSObject alloc] init];
    NSLog(@"------%@--%d--",[obj class],aa);
    
}

; ModuleID = 'main3.m'
source_filename = "main3.m"
target datalayout = "e-m:o-i64:64-f80:128-n8:16:32:64-S128"
target triple = "x86_64-apple-macosx10.13.0"

%0 = type opaque
%struct._class_t = type { %struct._class_t*, %struct._class_t*, %struct._objc_cache*, i8* (i8*, i8*)**, %struct._class_ro_t* }
%struct._objc_cache = type opaque
%struct._class_ro_t = type { i32, i32, i32, i8*, i8*, %struct.__method_list_t*, %struct._objc_protocol_list*, %struct._ivar_list_t*, i8*, %struct._prop_list_t* }
%struct.__method_list_t = type { i32, i32, [0 x %struct._objc_method] }
%struct._objc_method = type { i8*, i8*, i8* }
%struct._objc_protocol_list = type { i64, [0 x %struct._protocol_t*] }
%struct._protocol_t = type { i8*, i8*, %struct._objc_protocol_list*, %struct.__method_list_t*, %struct.__method_list_t*, %struct.__method_list_t*, %struct.__method_list_t*, %struct._prop_list_t*, i32, i32, i8**, i8*, %struct._prop_list_t* }
%struct._ivar_list_t = type { i32, i32, [0 x %struct._ivar_t] }
%struct._ivar_t = type { i64*, i8*, i8*, i32, i32 }
%struct._prop_list_t = type { i32, i32, [0 x %struct._prop_t] }
%struct._prop_t = type { i8*, i8* }
%struct.__NSConstantString_tag = type { i32*, i32, i8*, i64 }

@"OBJC_CLASS_$_NSObject" = external global %struct._class_t
@"OBJC_CLASSLIST_REFERENCES_$_" = private global %struct._class_t* @"OBJC_CLASS_$_NSObject", section "__DATA,__objc_classrefs,regular,no_dead_strip", align 8
@OBJC_METH_VAR_NAME_ = private unnamed_addr constant [6 x i8] c"alloc\00", section "__TEXT,__objc_methname,cstring_literals", align 1
@OBJC_SELECTOR_REFERENCES_ = private externally_initialized global i8* getelementptr inbounds ([6 x i8], [6 x i8]* @OBJC_METH_VAR_NAME_, i32 0, i32 0), section "__DATA,__objc_selrefs,literal_pointers,no_dead_strip", align 8
@OBJC_METH_VAR_NAME_.1 = private unnamed_addr constant [5 x i8] c"init\00", section "__TEXT,__objc_methname,cstring_literals", align 1
@OBJC_SELECTOR_REFERENCES_.2 = private externally_initialized global i8* getelementptr inbounds ([5 x i8], [5 x i8]* @OBJC_METH_VAR_NAME_.1, i32 0, i32 0), section "__DATA,__objc_selrefs,literal_pointers,no_dead_strip", align 8
@__CFConstantStringClassReference = external global [0 x i32]
@.str = private unnamed_addr constant [15 x i8] c"------%@--%d--\00", section "__TEXT,__cstring,cstring_literals", align 1
@_unnamed_cfstring_ = private global %struct.__NSConstantString_tag { i32* getelementptr inbounds ([0 x i32], [0 x i32]* @__CFConstantStringClassReference, i32 0, i32 0), i32 1992, i8* getelementptr inbounds ([15 x i8], [15 x i8]* @.str, i32 0, i32 0), i64 14 }, section "__DATA,__cfstring", align 8
@OBJC_METH_VAR_NAME_.3 = private unnamed_addr constant [6 x i8] c"class\00", section "__TEXT,__objc_methname,cstring_literals", align 1
@OBJC_SELECTOR_REFERENCES_.4 = private externally_initialized global i8* getelementptr inbounds ([6 x i8], [6 x i8]* @OBJC_METH_VAR_NAME_.3, i32 0, i32 0), section "__DATA,__objc_selrefs,literal_pointers,no_dead_strip", align 8
@llvm.compiler.used = appending global [7 x i8*] [i8* bitcast (%struct._class_t** @"OBJC_CLASSLIST_REFERENCES_$_" to i8*), i8* getelementptr inbounds ([6 x i8], [6 x i8]* @OBJC_METH_VAR_NAME_, i32 0, i32 0), i8* bitcast (i8** @OBJC_SELECTOR_REFERENCES_ to i8*), i8* getelementptr inbounds ([5 x i8], [5 x i8]* @OBJC_METH_VAR_NAME_.1, i32 0, i32 0), i8* bitcast (i8** @OBJC_SELECTOR_REFERENCES_.2 to i8*), i8* getelementptr inbounds ([6 x i8], [6 x i8]* @OBJC_METH_VAR_NAME_.3, i32 0, i32 0), i8* bitcast (i8** @OBJC_SELECTOR_REFERENCES_.4 to i8*)], section "llvm.metadata"

; Function Attrs: noinline optnone ssp uwtable
define i32 @main() #0 {
  %1 = alloca %0*, align 8
  %2 = load %struct._class_t*, %struct._class_t** @"OBJC_CLASSLIST_REFERENCES_$_", align 8
  %3 = load i8*, i8** @OBJC_SELECTOR_REFERENCES_, align 8, !invariant.load !8
  %4 = bitcast %struct._class_t* %2 to i8*
  %5 = call i8* bitcast (i8* (i8*, i8*, ...)* @objc_msgSend to i8* (i8*, i8*)*)(i8* %4, i8* %3)
  %6 = bitcast i8* %5 to %0*
  %7 = load i8*, i8** @OBJC_SELECTOR_REFERENCES_.2, align 8, !invariant.load !8
  %8 = bitcast %0* %6 to i8*
  %9 = call i8* bitcast (i8* (i8*, i8*, ...)* @objc_msgSend to i8* (i8*, i8*)*)(i8* %8, i8* %7)
  %10 = bitcast i8* %9 to %0*
  store %0* %10, %0** %1, align 8
  %11 = load %0*, %0** %1, align 8
  %12 = load i8*, i8** @OBJC_SELECTOR_REFERENCES_.4, align 8, !invariant.load !8
  %13 = bitcast %0* %11 to i8*
  %14 = call i8* bitcast (i8* (i8*, i8*, ...)* @objc_msgSend to i8* (i8*, i8*)*)(i8* %13, i8* %12)
  notail call void (i8*, ...) @NSLog(i8* bitcast (%struct.__NSConstantString_tag* @_unnamed_cfstring_ to i8*), i8* %14, i32 10)
  ret i32 0
}

; Function Attrs: nonlazybind
declare i8* @objc_msgSend(i8*, i8*, ...) #1

declare void @NSLog(i8*, ...) #2

attributes #0 = { noinline optnone ssp uwtable "correctly-rounded-divide-sqrt-fp-math"="false" "disable-tail-calls"="false" "less-precise-fpmad"="false" "no-frame-pointer-elim"="true" "no-frame-pointer-elim-non-leaf" "no-infs-fp-math"="false" "no-jump-tables"="false" "no-nans-fp-math"="false" "no-signed-zeros-fp-math"="false" "no-trapping-math"="false" "stack-protector-buffer-size"="8" "target-cpu"="penryn" "target-features"="+cx16,+fxsr,+mmx,+sse,+sse2,+sse3,+sse4.1,+ssse3,+x87" "unsafe-fp-math"="false" "use-soft-float"="false" }
attributes #1 = { nonlazybind }
attributes #2 = { "correctly-rounded-divide-sqrt-fp-math"="false" "disable-tail-calls"="false" "less-precise-fpmad"="false" "no-frame-pointer-elim"="true" "no-frame-pointer-elim-non-leaf" "no-infs-fp-math"="false" "no-nans-fp-math"="false" "no-signed-zeros-fp-math"="false" "no-trapping-math"="false" "stack-protector-buffer-size"="8" "target-cpu"="penryn" "target-features"="+cx16,+fxsr,+mmx,+sse,+sse2,+sse3,+sse4.1,+ssse3,+x87" "unsafe-fp-math"="false" "use-soft-float"="false" }

!llvm.module.flags = !{!0, !1, !2, !3, !4, !5, !6}
!llvm.ident = !{!7}

!0 = !{i32 1, !"Objective-C Version", i32 2}
!1 = !{i32 1, !"Objective-C Image Info Version", i32 0}
!2 = !{i32 1, !"Objective-C Image Info Section", !"__DATA,__objc_imageinfo,regular,no_dead_strip"}
!3 = !{i32 4, !"Objective-C Garbage Collection", i32 0}
!4 = !{i32 1, !"Objective-C Class Properties", i32 64}
!5 = !{i32 1, !"wchar_size", i32 4}
!6 = !{i32 7, !"PIC Level", i32 2}
!7 = !{!"Apple LLVM version 9.1.0 (clang-902.0.39.2)"}
!8 = !{}

在这里简单介绍一些 LLVM IR 的指令:

%:局部变量
@:全局变量
alloca:分配内存堆栈
i32:32 位的整数
i32**:一个指向 32 位 int 值的指针的指针
align 4:向 4 个字节对齐,即便数据没有占用 4 个字节,也要为其分配四个字节
call:调用

LLVM IR 是Frontend的输出,也是LLVM Backend的输入,前后端的桥接语言, 更具生成的文件解析,其实生成的LLVM IR对Runtime进行桥接的一个文件

1.Class/Meta Class/Protocol/Category内存结构生成,并存放在指定section中(如Class:_DATA,_objc_classrefs)
2.Method/lvar/Property内存结构生成
3.组成method_list/ivar_list/property_list并填入Class
4.Non-Fragile ABI:为每个Ivar合成OBJC_IVAR_$_ 偏移值常量
5.存取Ivar的语句(ivar = 123; int a = ivar;)转写成base + OBJC_IVAR$_的形式
6.将语法树中的ObjcMessageExpr翻译成相应版本的objc_msgSend,7.对super关键字的调用翻译成objc_msgSendSuper
8.根据修饰符strong/weak/copy/atomic合成@property 自动实现的 setter/getter
9.处理@synthesize
10.生成block_layout的数据结构
11.变量的capture(__block/__weak)
12.生成_block_invoke函数
13.ARC:分析对象引用关系,将objc_storeStrong/objc_storeWeak等ARC代码插入
14.将ObjCAutoreleasePoolStmt转译成objc_autoreleasePoolPush/Pop
15.实现自动调用[super dealloc]
16为每个拥有ivar的Class合成.cxx_destructor方法来自动释放类的成员变量,代替MRC时代的“self.xxx = nil”
3.5. Optimize - 优化IR

使用命令 clang -O3 -S -emit-llvm main3.m -o main3.ll

; ModuleID = 'main3.m'
source_filename = "main3.m"
target datalayout = "e-m:o-i64:64-f80:128-n8:16:32:64-S128"
target triple = "x86_64-apple-macosx10.13.0"

%struct._class_t = type { %struct._class_t*, %struct._class_t*, %struct._objc_cache*, i8* (i8*, i8*)**, %struct._class_ro_t* }
%struct._objc_cache = type opaque
%struct._class_ro_t = type { i32, i32, i32, i8*, i8*, %struct.__method_list_t*, %struct._objc_protocol_list*, %struct._ivar_list_t*, i8*, %struct._prop_list_t* }
%struct.__method_list_t = type { i32, i32, [0 x %struct._objc_method] }
%struct._objc_method = type { i8*, i8*, i8* }
%struct._objc_protocol_list = type { i64, [0 x %struct._protocol_t*] }
%struct._protocol_t = type { i8*, i8*, %struct._objc_protocol_list*, %struct.__method_list_t*, %struct.__method_list_t*, %struct.__method_list_t*, %struct.__method_list_t*, %struct._prop_list_t*, i32, i32, i8**, i8*, %struct._prop_list_t* }
%struct._ivar_list_t = type { i32, i32, [0 x %struct._ivar_t] }
%struct._ivar_t = type { i64*, i8*, i8*, i32, i32 }
%struct._prop_list_t = type { i32, i32, [0 x %struct._prop_t] }
%struct._prop_t = type { i8*, i8* }
%struct.__NSConstantString_tag = type { i32*, i32, i8*, i64 }

@"OBJC_CLASS_$_NSObject" = external global %struct._class_t
@"OBJC_CLASSLIST_REFERENCES_$_" = private global %struct._class_t* @"OBJC_CLASS_$_NSObject", section "__DATA,__objc_classrefs,regular,no_dead_strip", align 8
@OBJC_METH_VAR_NAME_ = private unnamed_addr constant [6 x i8] c"alloc\00", section "__TEXT,__objc_methname,cstring_literals", align 1
@OBJC_SELECTOR_REFERENCES_ = private externally_initialized global i8* getelementptr inbounds ([6 x i8], [6 x i8]* @OBJC_METH_VAR_NAME_, i64 0, i64 0), section "__DATA,__objc_selrefs,literal_pointers,no_dead_strip", align 8
@OBJC_METH_VAR_NAME_.1 = private unnamed_addr constant [5 x i8] c"init\00", section "__TEXT,__objc_methname,cstring_literals", align 1
@OBJC_SELECTOR_REFERENCES_.2 = private externally_initialized global i8* getelementptr inbounds ([5 x i8], [5 x i8]* @OBJC_METH_VAR_NAME_.1, i64 0, i64 0), section "__DATA,__objc_selrefs,literal_pointers,no_dead_strip", align 8
@__CFConstantStringClassReference = external global [0 x i32]
@.str = private unnamed_addr constant [15 x i8] c"------%@--%d--\00", section "__TEXT,__cstring,cstring_literals", align 1
@_unnamed_cfstring_ = private global %struct.__NSConstantString_tag { i32* getelementptr inbounds ([0 x i32], [0 x i32]* @__CFConstantStringClassReference, i32 0, i32 0), i32 1992, i8* getelementptr inbounds ([15 x i8], [15 x i8]* @.str, i32 0, i32 0), i64 14 }, section "__DATA,__cfstring", align 8
@OBJC_METH_VAR_NAME_.3 = private unnamed_addr constant [6 x i8] c"class\00", section "__TEXT,__objc_methname,cstring_literals", align 1
@OBJC_SELECTOR_REFERENCES_.4 = private externally_initialized global i8* getelementptr inbounds ([6 x i8], [6 x i8]* @OBJC_METH_VAR_NAME_.3, i64 0, i64 0), section "__DATA,__objc_selrefs,literal_pointers,no_dead_strip", align 8
@llvm.compiler.used = appending global [7 x i8*] [i8* bitcast (%struct._class_t** @"OBJC_CLASSLIST_REFERENCES_$_" to i8*), i8* getelementptr inbounds ([6 x i8], [6 x i8]* @OBJC_METH_VAR_NAME_, i32 0, i32 0), i8* getelementptr inbounds ([5 x i8], [5 x i8]* @OBJC_METH_VAR_NAME_.1, i32 0, i32 0), i8* getelementptr inbounds ([6 x i8], [6 x i8]* @OBJC_METH_VAR_NAME_.3, i32 0, i32 0), i8* bitcast (i8** @OBJC_SELECTOR_REFERENCES_ to i8*), i8* bitcast (i8** @OBJC_SELECTOR_REFERENCES_.2 to i8*), i8* bitcast (i8** @OBJC_SELECTOR_REFERENCES_.4 to i8*)], section "llvm.metadata"

; Function Attrs: ssp uwtable
define i32 @main() local_unnamed_addr #0 {
  %1 = load i8*, i8** bitcast (%struct._class_t** @"OBJC_CLASSLIST_REFERENCES_$_" to i8**), align 8
  %2 = load i8*, i8** @OBJC_SELECTOR_REFERENCES_, align 8, !invariant.load !8
  %3 = tail call i8* bitcast (i8* (i8*, i8*, ...)* @objc_msgSend to i8* (i8*, i8*)*)(i8* %1, i8* %2)
  %4 = load i8*, i8** @OBJC_SELECTOR_REFERENCES_.2, align 8, !invariant.load !8
  %5 = tail call i8* bitcast (i8* (i8*, i8*, ...)* @objc_msgSend to i8* (i8*, i8*)*)(i8* %3, i8* %4)
  %6 = load i8*, i8** @OBJC_SELECTOR_REFERENCES_.4, align 8, !invariant.load !8
  %7 = tail call i8* bitcast (i8* (i8*, i8*, ...)* @objc_msgSend to i8* (i8*, i8*)*)(i8* %5, i8* %6)
  notail call void (i8*, ...) @NSLog(i8* bitcast (%struct.__NSConstantString_tag* @_unnamed_cfstring_ to i8*), i8* %7, i32 10)
  ret i32 0
}

; Function Attrs: nonlazybind
declare i8* @objc_msgSend(i8*, i8*, ...) local_unnamed_addr #1

declare void @NSLog(i8*, ...) local_unnamed_addr #2

attributes #0 = { ssp uwtable "correctly-rounded-divide-sqrt-fp-math"="false" "disable-tail-calls"="false" "less-precise-fpmad"="false" "no-frame-pointer-elim"="true" "no-frame-pointer-elim-non-leaf" "no-infs-fp-math"="false" "no-jump-tables"="false" "no-nans-fp-math"="false" "no-signed-zeros-fp-math"="false" "no-trapping-math"="false" "stack-protector-buffer-size"="8" "target-cpu"="penryn" "target-features"="+cx16,+fxsr,+mmx,+sse,+sse2,+sse3,+sse4.1,+ssse3,+x87" "unsafe-fp-math"="false" "use-soft-float"="false" }
attributes #1 = { nonlazybind }
attributes #2 = { "correctly-rounded-divide-sqrt-fp-math"="false" "disable-tail-calls"="false" "less-precise-fpmad"="false" "no-frame-pointer-elim"="true" "no-frame-pointer-elim-non-leaf" "no-infs-fp-math"="false" "no-nans-fp-math"="false" "no-signed-zeros-fp-math"="false" "no-trapping-math"="false" "stack-protector-buffer-size"="8" "target-cpu"="penryn" "target-features"="+cx16,+fxsr,+mmx,+sse,+sse2,+sse3,+sse4.1,+ssse3,+x87" "unsafe-fp-math"="false" "use-soft-float"="false" }

!llvm.module.flags = !{!0, !1, !2, !3, !4, !5, !6}
!llvm.ident = !{!7}

!0 = !{i32 1, !"Objective-C Version", i32 2}
!1 = !{i32 1, !"Objective-C Image Info Version", i32 0}
!2 = !{i32 1, !"Objective-C Image Info Section", !"__DATA,__objc_imageinfo,regular,no_dead_strip"}
!3 = !{i32 4, !"Objective-C Garbage Collection", i32 0}
!4 = !{i32 1, !"Objective-C Class Properties", i32 64}
!5 = !{i32 1, !"wchar_size", i32 4}
!6 = !{i32 7, !"PIC Level", i32 2}
!7 = !{!"Apple LLVM version 9.1.0 (clang-902.0.39.2)"}
!8 = !{}

这一步骤的优化是非常重要的,很多直接转换来的代码是不合适且消耗内存的,因为是直接转换,所以必然会有这样的问题,而优化放在这一步的好处在于前端不需要考虑任何优化过程,减少了前端的开发工作。

3.6. 生成Target相关汇编

使用命令 clang -S -o - main3.m | open -f可以查看生成的汇编代码:

	.section	__TEXT,__text,regular,pure_instructions
	.macosx_version_min 10, 13
	.globl	_main                   ## -- Begin function main
	.p2align	4, 0x90
_main:                                  ## @main
	.cfi_startproc
## BB#0:
	pushq	%rbp
Lcfi0:
	.cfi_def_cfa_offset 16
Lcfi1:
	.cfi_offset %rbp, -16
	movq	%rsp, %rbp
Lcfi2:
	.cfi_def_cfa_register %rbp
	subq	$16, %rsp
	movq	L_OBJC_CLASSLIST_REFERENCES_$_(%rip), %rax
	movq	L_OBJC_SELECTOR_REFERENCES_(%rip), %rsi
	movq	%rax, %rdi
	callq	_objc_msgSend
	movq	L_OBJC_SELECTOR_REFERENCES_.2(%rip), %rsi
	movq	%rax, %rdi
	callq	_objc_msgSend
	movq	%rax, -8(%rbp)
	movq	-8(%rbp), %rax
	movq	L_OBJC_SELECTOR_REFERENCES_.4(%rip), %rsi
	movq	%rax, %rdi
	callq	_objc_msgSend
	leaq	L__unnamed_cfstring_(%rip), %rsi
	movl	$10, %edx
	movq	%rsi, %rdi
	movq	%rax, %rsi
	movb	$0, %al
	callq	_NSLog
	xorl	%eax, %eax
	addq	$16, %rsp
	popq	%rbp
	retq
	.cfi_endproc
                                        ## -- End function
	.section	__DATA,__objc_classrefs,regular,no_dead_strip
	.p2align	3               ## @"OBJC_CLASSLIST_REFERENCES_$_"
L_OBJC_CLASSLIST_REFERENCES_$_:
	.quad	_OBJC_CLASS_$_NSObject

	.section	__TEXT,__objc_methname,cstring_literals
L_OBJC_METH_VAR_NAME_:                  ## @OBJC_METH_VAR_NAME_
	.asciz	"alloc"

	.section	__DATA,__objc_selrefs,literal_pointers,no_dead_strip
	.p2align	3               ## @OBJC_SELECTOR_REFERENCES_
L_OBJC_SELECTOR_REFERENCES_:
	.quad	L_OBJC_METH_VAR_NAME_

	.section	__TEXT,__objc_methname,cstring_literals
L_OBJC_METH_VAR_NAME_.1:                ## @OBJC_METH_VAR_NAME_.1
	.asciz	"init"

	.section	__DATA,__objc_selrefs,literal_pointers,no_dead_strip
	.p2align	3               ## @OBJC_SELECTOR_REFERENCES_.2
L_OBJC_SELECTOR_REFERENCES_.2:
	.quad	L_OBJC_METH_VAR_NAME_.1

	.section	__TEXT,__cstring,cstring_literals
L_.str:                                 ## @.str
	.asciz	"------%@--%d--"

	.section	__DATA,__cfstring
	.p2align	3               ## @_unnamed_cfstring_
L__unnamed_cfstring_:
	.quad	___CFConstantStringClassReference
	.long	1992                    ## 0x7c8
	.space	4
	.quad	L_.str
	.quad	14                      ## 0xe

	.section	__TEXT,__objc_methname,cstring_literals
L_OBJC_METH_VAR_NAME_.3:                ## @OBJC_METH_VAR_NAME_.3
	.asciz	"class"

	.section	__DATA,__objc_selrefs,literal_pointers,no_dead_strip
	.p2align	3               ## @OBJC_SELECTOR_REFERENCES_.4
L_OBJC_SELECTOR_REFERENCES_.4:
	.quad	L_OBJC_METH_VAR_NAME_.3

	.section	__DATA,__objc_imageinfo,regular,no_dead_strip
L_OBJC_IMAGE_INFO:
	.long	0
	.long	64


.subsections_via_symbols

注意代码中的 .section 指令,它指定了接下来会执行的代码段。

在这篇文章中,详细解释了这些汇编指令或代码到底是如何工作的:Mach-O 可执行文件。

3.7. Link生成Executable

在最后,LLVM 将会把这些汇编代码输出成二进制的可执行文件,使用命令 clang main3.m -o main.out 即可查看

Segment __PAGEZERO: 0x100000000 (vmaddr 0x0 fileoff 0)
Segment __TEXT: 0x1000 (vmaddr 0x100000000 fileoff 0)
	Section __text: 0x34 (addr 0x100000f50 offset 3920)
	Section __stubs: 0x6 (addr 0x100000f84 offset 3972)
	Section __stub_helper: 0x1a (addr 0x100000f8c offset 3980)
	Section __cstring: 0xe (addr 0x100000fa6 offset 4006)
	Section __unwind_info: 0x48 (addr 0x100000fb4 offset 4020)
	total 0xaa
Segment __DATA: 0x1000 (vmaddr 0x100001000 fileoff 4096)
	Section __nl_symbol_ptr: 0x10 (addr 0x100001000 offset 4096)
	Section __la_symbol_ptr: 0x8 (addr 0x100001010 offset 4112)
	total 0x18
Segment __LINKEDIT: 0x1000 (vmaddr 0x100002000 fileoff 8192)
total 0x100003000

上面的代码中,每个 segment 的意义也不一样:

  • __PAGEZERO segment 它的大小为 4GB。这 4GB 并不是文件的真实大小,但是规定了进程地址空间的前 4GB 被映射为 不可执行、不可写和不可读。
  • __TEXT segment 包含了被执行的代码。它被以只读和可执行的方式映射。进程被允许执行这些代码,但是不能修改。
  • __DATA segment 以可读写和不可执行的方式映射。它包含了将会被更改的数据。
  • __LINKEDIT segment 指出了 link edit 表(包含符号和字符串的动态链接器表)的地址,里面包含了加载程序的元数据,例如函数的名称和地址。

关于 section 中的内容的研究可以查看:
Mach-O 可执行文件
Mach-O 可执行文件2

PARSING MACH-O FILES

关于更详细的编译过程可以查看:
深入剖析 iOS 编译 Clang LLVM

####4. Swift的编译过程
在 Swift 编译器结构 的官方文档中描述了 Swift 编译器是如何工作的,分为如下步骤:

  • 解析:解析器是一个简单的递归下降解析器(在 lib / Parse 中实现),带有集成的手动编码词法分析器。解析器负责生成没有任何语义或类型信息的抽象语法树(AST),并针对输入源的语法问题发出警告或错误。
  • 语意分析:语义分析(在 lib / Sema 中实现)负责解析 AST 并将其转换为格式良好的完全检查形式的 AST,并在源代码中发出语义问题的警告或错误。语义分析包括类型推断,如果成功,则所得到的代码是类型检查安全的 AST 。
  • Clang导入器:Clang导入器(在 lib / ClangImporter 中实现)导入Clang模块,并将它们导出的 C 或 Objective-C API 映射到相应的 Swift API中。结果导入的 AST 可以通过语义分析来引用。
  • SIL生成:Swift中间语言(Swift Intermediate Language,简称SIL)是一种高级的,Swift特有的中间语言,适用于 Swift 代码的进一步分析和优化。SIL 生成阶段(在 lib / SILGen 中实现)将类型检查的 AST 降低到所谓的 “原始” SIL。SIL的设计描述在 docs/ SIL.rst 中可以看到。
  • SIL优化:在SIL优化(在 lib/Analysis,lib/ ARC,lib/LoopTransforms,和 lib/Transforms 中实现)执行额外的高级别,Swift 特有的优化的程序,包括(例如)自动引用计数优化,虚拟化和通用专业化。
  • LLVM IR生成:IR生成(在 lib/IRGen 中实现)将 SIL 降到 LLVM IR,此时LLVM可以继续对其进行优化并生成机器码。

相关内容不详细讲解,可参考:https://blog.youkuaiyun.com/aas319/article/details/78606342

三、相关衍生的内容

1.Xcode 编译设置
1.1 Build Settings

这里是编译设置,针对编译流程中的各个过程进行参数和工具的配置:

  • Architectures:编译目标 CPU 架构,这里比较常见的是 Build Active Architectures Only(只编译为当前架构,是指你在 scheme 中选定的设备的 CPU 架构),debug 设置为 YES,Release 设置为 NO。

  • Assets:Assets.xcassets 资源组的配置。

  • Build Locations:查看 Build 日志可以看到在编译过程中的目标文件夹。

  • Build Options:这里是一些编译的选项设定,包含:

    • 是否总是嵌入 Swift 标准库,这个在静态库和动态库的第一篇文章中有讲,iOS 系统目前是不包含 Swift 标准库的,都是被打包在项目中。
    • c/c++/objective-c 编译器:Apple LLVM 9.0
    • 是否打开 Bitcode
    • …
  • Deployment:iOS 部署设置。说白了就是安装到手机的设置。

  • Headers:头文件?具体作用不详,知道的可以说一下。

  • Kernel Module:内核模块,作用不详。

  • Linking:链接设置,链接路径、链接标记、Mach-O 文件类型。

  • Packaging:打包设置,info.plist 的路径设置、Bundle ID 、App 显示名称的设置。

  • Search Paths:库的搜索路径、头文件的搜索路径。

  • Signing:签名设置,开发、生产的签名设置,这些都和你在开发者网站配置的证书相关。

  • Testing:测试设置,作用不详。

  • Text-Based API:基于文本的 API,字面翻译,作用不详。

  • Versioning:版本管理。

  • Apple LLVM 9.0 系列:LLVM 的配置,包含路径、编译器每一步的设置、语言设置。在这里 Apple LLVM 9.0 - Warnings 可以选择在编译的时候将哪些情况认定为错误(Error)和警告(Warning),可以开启困难模式,任何一个小的警告都会被认定为错误。

  • Asset Catalog Compiler - Options:Asset 文件的编译设置。

  • Interface Builder Storyboard Compiler - Options:Storyboard 的编译设置。

  • 以及一些静态分析和 Swift 编译器的设定。

1.2 Build Phases

编译阶段,编译的时候将根据顺序来进行编译。这里固定的有:

  • Compile Sources:编译源文件。
  • Link Binary With Libraries:相关的链接库。
  • Copy Bundle Resources:要拷贝的资源文件,有时候如果一个资源文件在开发过程中发现找不到,可以在这里找一下,看看是不是加进来了。

如果使用了 Cocoapods,那么将会被添加:

  • [CP] Check Pods Manifest.lock:检查 Podfile.lock 和 Manifest.lock 文件的一致性,这个会再后面的 Cocoapods 原理中详细解释。
  • [CP] Embed Pods Frameworks:将所有 cocoapods 打的 framework 拷贝到包中。
  • [CP] Copy Pods Resources:将所有 cocoapods 的资源文件拷贝到包中。
1.3 Build Rules

编译规则,这里设定了不同文件的处理方式,例如:

  • Copy Plist File:在编译打包的时候,将 info.plist 文件拷贝。
  • Compress PNG File:在编译打包的时候,将 PNG 文件压缩。
  • Swift Compiler:Swift 文件的编译方式,使用 Swift 编译器。
  • ….
2. Cocoapods 原理

使用了 Cocoapods 后,我们的编译流程会多出来一些,虽然每个 target 的编译流程都是一致的,但是 Cocoapods 是如何将这些库导入我们的项目、原项目和其他库之间的依赖又是如何实现的仍然是一个需要了解的知识点。下面这几篇文章从不同角度解释了 Cocoapods 是如何工作的:

这些文章大部分都是讲述了 Objective-C 语言下 Cocoapods 是如何使用静态库动态库并添加依赖的。特别说明一下 Swift 的实现:

Cocoapods 对于 Swift 和 Objective-C 的操作区别其实并不是很大,Swift 是必须使用动态库的,因此在 podfile 中我们必须添加代码 use_frameworks! 来指明 Cocoapods 所有管理的库都将被编译成 framework 动态库。这些库的依赖过程和 Objective-C 一致,我们的主项目依赖于 Pods-ProjectName.framework target,而这个 pods 的 target 则依赖于其他我们使用的库。

需要说明一个概念,就是 Swift 的命名空间和 Module。

  • Module:在 Swift 中,项目里的每个 target、framework 都是一个 Module。
  • 命名空间:每个 Module 都拥有独立的命名空间。

因此,我们在使用 Swift 库的时候,例如 Alamofire,在某个文件中 import Alamofire,其实就是在引入这个 Module,在代码提示中可以看到,这样其中的代码才可以被我们使用。

在同一个 Module 内我们的 Swift 文件是不需要被引用的,可以直接使用其中 public、internal 描述的类属性和方法。这也说明一个问题,在同一个 Module 中,命名空间也是同一个,重复的类都会在编译的时候报错。

总之

LLVM 的编译过程是相当复杂的,中间牵扯到的技术和语言要比我们做一个简单的移动开发要复杂的多,本文也只是非常浅显的描述了相关的编译过程,其主要目的是为了在 iOS 开发中更加得心应手。

研究编译原理、深究编译过程以及其相关内容,在开发过程中是非常重要的一个环节,任何开发者都应该做深入研究。如果你看不懂相关代码,也不要觉得困难,很多代码的命名就能很清楚的表明它到底是干什么的,英语差的话翻译一下就好了。只是相对于英语好,底层代码也略懂的情况来说,学习成本比较高,不过这不应该是打败你的理由。

原理是我们构建整个代码世界的基础,不懂原理(无论哪方面)都只能让我们做一个垒砖的,而不是整个工程的控制者。

浅谈Android和IOS系统的差异 1.    渲染机制不同 IOS的UI渲染采用实时优先级,而Android的UI渲染遵循传统电脑模式的主线程普通优先级 IOS它的响应顺序依次为Touch--Media--Service--Core架构   Android系统的优先级响应层级则是Application--Framework--Library--Kernal架构   2.    IOS基于GPU加速 iOS系统对图形的 阅读详情

相关推荐

ffmpeg浅谈 (一)编译(上)

职业开发ios一年,前期学过一些嵌入式对底层比较感兴趣 ,处于兴趣爱好,好奇心,工作之余略略的看看ffmeg,我把每天或者几天的收获写在博客中,作为学习的点滴记录.  我没法做到深入,因为我只是业余,是小白,仅作为日后的温故.  对于源码包得编译,其实就是三步: 1)./configure    2)make   3)make  install    不论是ffmeg源码包得安装还是其他

guanweidong42的专栏 709

基于clang插件的一种iOS包大小瘦身方案

转自:http://mp.weixin.qq.com/s?__biz=MzA3ODg4MDk0Ng==&mid=2651112856&idx=1&sn=b2c74c62a10b4c9a4e7538d1ad7eb739&chksm=844c6c45b33be553ee0ee89097b474c1360eb6ea9fe6a96bbc83dcd43484a272e3ad33f72bbd&scene=0#

demondev 3219

全球第一开源云ERP Odoo操作手册 模块安装和界面汉化指南

Odoo 的功能包含在不同的模块中, 刚创建的数据库除 Odoo 的核心模块外, 其它模块并没有被安装到系统中,你可以根据需要安装它们。本节将介绍安装模块和界面汉化的相关知识。

weixin_44793763的博客 2075

ObjC(Objective-C)中的Class(类类型),Selector(选择器SEL),函数指针(IMP)

ObjC(Objective-C)中的Class(类类型),Selector(选择器SEL),函数指针(IMP) https://www.cnblogs.com/TsengYuen/archive/2011/04/20/2022107.html ObjC(Objective-C)中的Class(类类型),Selector(选择器SEL),函数指针(IMP) 今天在园子里看到了一篇牛文“Objective-C 2.0 with Cocoa Foundation--- 5,Class类型,选择器S.

kuangben2000的博客 334

手撕代码系列:Conv2D

在实际的神经网络中,每个卷积核都会对应一个偏置值。这意味着如果网络有多个卷积核,每个卷积核的输出都可以加上对应的偏置。

qq_55794606的博客 910

苹果搜索广告ASA开户教程

阅读导航 一、创建账号 1、登录网站 2、填写账号主体信息 3、填写商家和发票信息 4、选择付款方式 二、上传审核材料 1、上传文件 2、请求审批 3、上传文件中的常见问题 一、创建账号 1、用Apple ID登录ASA网站 首先我们进入开户网址:https://app-ads.apple.com/ 如果已拥有Apple ID,直接登录即可,如果没有Apple ID,可以先按照步骤创建Apple ID,再返回登录。 注意,这里登录的Apple ID也有讲究: ① 如果是开发

testflight121的博客 2130

【iOS 应用瘦身】使用 Clang 插件扫描无用代码(Part2)

前言在上一篇文章中,我们了解了 AST 树的结构,并简单的实现了一个 RecursiveASTVisitor 子类,成功的访问了语法树上的各个节点。回头再看看一下前文的代码,结合官方文档,我们可以大致整理出如下调用逻辑:Clang 对 AST 树的解析是以单个文件为单位的,这点我们从 ast-dump 的命令也可以看出来,这也注定了使用 Clang AST 静态分析,是无法完整分析整个项目中的调用...

majiakun1的专栏 1147

IP路由原理、静态路由及动态路由区分

什么是路由 路由是指导IP报文发送的路径信息。 直连路由:目的地所在网络与路由器直接相连。 间接路由:目的地所在网络与路由器非直接相连。 静态路由是由管理员手工配置而生成的路由。 动态路由路由器使用路由协议从其他路由器那里获悉的路由。

weixin_42227328的博客 5470

LLVM学习笔记⑤

全局变量 ①全局变量定义在编译时而不是运行时分配的内存区域。 ②全局变量必须被初始化 ③全局变量也可以在其他编译单元中声明,这样的话就没有初始化程序了。 ④全局变量可以指定一个连接类型(前面有说到) 无论是全局变量定义还是声明,都可以放一个显式的section在里面,并且可以指定可选的显式对齐。如果变量声明的显式或推断部分信息与其定义之间不匹配,则产生的行为是未定义的。 一个变量可以被定义为一个全局constant,它表示变量的内容永远不会被修改(开启更好的优化,允许全局数据被放置在可执行文件的只读部分等)

m0_46220969的博客 723

数据分析—用户行为数据分析

一般价值用户(R高F高M低):用户处于购买活跃期,感兴趣的商品多,购买次数少 → 针对该部分客户,目的就是要让他们购买,所以可以和运营部门沟通,降低利润,给出有力折扣促进购买,进而转为重要价值用户。重要价值用户(R高F高M高)维护:用户处于购买活跃期,感兴趣的商品多,购买次数也多 → 针对兴趣产品,保持一定频率的触达,持续维护。

m0_74091312的博客 1420

Ceres Solver:从入门到使用

Ceres Solver是谷歌开源的C++非线性优化库,能够解决有约束或无约束条件下的非线性最小二乘问题。2010年之后大量的运用在谷歌的产品开发中,尤其在谷歌开源的cartographer中被大量的使用。 ceres可以在Linux,Windows,macOS,Andrioid,IOS系统进行安装使用,详情可查看下方的官网链接。 Ceres Solver 官网:http://ceres-solver.org/

非晚非晚的博客 1万+

浅谈iOS工程编译

编程语言一般可以分为两类:编译型语言和直译式语言。在iOS里面Objective-C、Swift都是编译型语言,也就是说需要通过编译器生产机器码,然后才能执行。也就需要依赖编译器去做编译,编译器一般又可以分为前端编译器和后端编译器。Objective-C是以clang作为前端编译器、而swift是以swift作为前端编译器,他们两者都是以LLVM作为后端编译器。 编译基础理论 编译大致可分为:预处理、词法分析、语法分析、语义分析、中间代码生成、代码优化、目标代码生成等步骤。词法分析、语法分析、语义分析,三个

Cone的博客 505

Open3D 三维重建-Delaunay Triangulation (德劳内三角剖分)

德劳内三角剖分(Delaunay Triangulation)是一种用于从一组点生成三角形网格的算法。它的特点是使得生成的三角形尽量接近等边三角形,即最大化最小角度,避免生成细长的三角形。

纵马踏花向自由 2502

浅谈IOS版本号

Version,通常说的版本号,是应用向用户宣传说明时候用到的标识。一般有2段式或者3段式,例如:2.1,8.1.2 Version一般由产品部门确定,完全迥异的更新需要改变主版本号,比如QQ4.0的变化非常大,主版本的变化会更加吸引用户的眼球,所以有的应用会频繁的更新主版本号,比如FireFox 20.0。两段式的副版本号既包含小功能更新也会包含Bug修复等,三段式副版本基本都是新功...

ansion2017的博客 314

全面解析智能座舱芯片参数:高通 (SA8295)vs 联发科,真实案例一行行讲给你听!

使用场景推荐芯片中低端车型 + 1屏导航语音SA8155P / MT8628(性价比高)中控+副驾+仪表+全景影像SA8255P / MT8678(性能强)高阶自动驾驶+域控融合SA8295P(顶级安全+算力)

weixin_43199439的博客 2079

华为的C语言编程规范.pdf

华为技术有限公司内部技术规范。本规范制定了编写C语言程序的基本原则、规则和建议。从代码的清晰、简洁、可测试、安全、程序效率、可移植各个方面对C语言编程作出了具体指导。

iOS Architectures 浅谈

iOS Architectures 浅谈   自己的项目,使用OSG 第三方库,编译的时候,设置成标准的框架即可, iOS项目打包,或者只是在项目里面调用第三方静态库抑或是自己新建一个静态库,就要无可避免的和Architectures打交道。Architectures在Targets面板的Build Settings下,如下图红框所示:       首先请注意上图蓝色

qq_30754211的博客 653

深度学习工具包 DeepLearningToolbox

深度学习工具包Deprecation notice.-----This toolbox is outdated and no longer maintained.There are much better tools available for deep learning than this toolbox, e.g. [Theano](http://deeplearning.net/software/theano/), [torch](http://torch.ch/) or [tensorflow](http://www.tensorflow.org/)I would suggest you use one of the tools mentioned above rather than use this toolbox.Best, Rasmus.DeepLearnToolbox================A Matlab toolbox for Deep Learning.Deep Learning is a new subfield of machine learning that focuses on learning deep hierarchical models of data.It is inspired by the human brain's apparent deep (layered, hierarchical) architecture.A good overview of the theory of Deep Learning theory is[Learning Deep Architectures for AI](http://www.iro.umontreal.ca/~bengioy/papers/ftml_book.pdf)For a more informal introduction, see the following videos by Geoffrey Hinton and Andrew Ng.* [The Next Generation of Neural Networks](http://www.youtube.com/watch?v=AyzOUbkUf3M) (Hinton, 2007)* [Recent Developments in Deep Learning](http://www.youtube.com/watch?v=VdIURAu1-aU) (Hinton, 2010)* [Unsupervised Feature Learning and Deep Learning](http://www.youtube.com/watch?v=ZmNOAtZIgIk) (Ng, 2011)If you use this toolbox in your research please cite [Prediction as a candidate for learning deep hierarchical models of data](http://www2.imm.dtu.dk/pubdb/views/publication_details.php?id=6284)```@MASTERSTHESIS\{IMM2012-06284, author = "R. B. Palm", title = "Prediction as a candidate for learning deep hierarchical models of data", year = "2012",}```Contact: rasmusbergpalm at gmail dot comDirectories included in the toolbox-----------------------------------`NN/` - A library for Feedforward Backpropagation Neural Networks`CNN/` - A library for Convolutional Neural Networks`DBN/` - A library for Deep Belief Networks`SAE/` - A library for Stacked Auto-Encoders`CAE/` - A library for Convolutional Auto-Encoders`util/` - Utility functions used by the libraries`data/` - Data used by the examples`tests/` - unit tests to verify toolbox is workingFor references on each library check REFS.mdSetup-----1. Download.2. addpath(genpath('DeepLearnToolbox'));Example: Deep Belief Network---------------------```matlabfunction test_example_DBNload mnist_uint8;train_x = double(train_x) / 255;test_x = double(test_x) / 255;train_y = double(train_y);test_y = double(test_y);%% ex1 train a 100 hidden unit RBM and visualize its weightsrand('state',0)dbn.sizes = [100];opts.numepochs = 1;opts.batchsize = 100;opts.momentum = 0;opts.alpha = 1;dbn = dbnsetup(dbn, train_x, opts);dbn = dbntrain(dbn, train_x, opts);figure; visualize(dbn.rbm{1}.W'); % Visualize the RBM weights%% ex2 train a 100-100 hidden unit DBN and use its weights to initialize a NNrand('state',0)%train dbndbn.sizes = [100 100];opts.numepochs = 1;opts.batchsize = 100;opts.momentum = 0;opts.alpha = 1;dbn = dbnsetup(dbn, train_x, opts);dbn = dbntrain(dbn, train_x, opts);%unfold dbn to nnnn = dbnunfoldtonn(dbn, 10);nn.activation_function = 'sigm';%train nnopts.numepochs = 1;opts.batchsize = 100;nn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.10, 'Too big error');```Example: Stacked Auto-Encoders---------------------```matlabfunction test_example_SAEload mnist_uint8;train_x = double(train_x)/255;test_x = double(test_x)/255;train_y = double(train_y);test_y = double(test_y);%% ex1 train a 100 hidden unit SDAE and use it to initialize a FFNN% Setup and train a stacked denoising autoencoder (SDAE)rand('state',0)sae = saesetup([784 100]);sae.ae{1}.activation_function = 'sigm';sae.ae{1}.learningRate = 1;sae.ae{1}.inputZeroMaskedFraction = 0.5;opts.numepochs = 1;opts.batchsize = 100;sae = saetrain(sae, train_x, opts);visualize(sae.ae{1}.W{1}(:,2:end)')% Use the SDAE to initialize a FFNNnn = nnsetup([784 100 10]);nn.activation_function = 'sigm';nn.learningRate = 1;nn.W{1} = sae.ae{1}.W{1};% Train the FFNNopts.numepochs = 1;opts.batchsize = 100;nn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.16, 'Too big error');```Example: Convolutional Neural Nets---------------------```matlabfunction test_example_CNNload mnist_uint8;train_x = double(reshape(train_x',28,28,60000))/255;test_x = double(reshape(test_x',28,28,10000))/255;train_y = double(train_y');test_y = double(test_y');%% ex1 Train a 6c-2s-12c-2s Convolutional neural network %will run 1 epoch in about 200 second and get around 11% error. %With 100 epochs you'll get around 1.2% errorrand('state',0)cnn.layers = { struct('type', 'i') %input layer struct('type', 'c', 'outputmaps', 6, 'kernelsize', 5) %convolution layer struct('type', 's', 'scale', 2) %sub sampling layer struct('type', 'c', 'outputmaps', 12, 'kernelsize', 5) %convolution layer struct('type', 's', 'scale', 2) %subsampling layer};cnn = cnnsetup(cnn, train_x, train_y);opts.alpha = 1;opts.batchsize = 50;opts.numepochs = 1;cnn = cnntrain(cnn, train_x, train_y, opts);[er, bad] = cnntest(cnn, test_x, test_y);%plot mean squared errorfigure; plot(cnn.rL);assert(er<0.12, 'Too big error');```Example: Neural Networks---------------------```matlabfunction test_example_NNload mnist_uint8;train_x = double(train_x) / 255;test_x = double(test_x) / 255;train_y = double(train_y);test_y = double(test_y);% normalize[train_x, mu, sigma] = zscore(train_x);test_x = normalize(test_x, mu, sigma);%% ex1 vanilla neural netrand('state',0)nn = nnsetup([784 100 10]);opts.numepochs = 1; % Number of full sweeps through dataopts.batchsize = 100; % Take a mean gradient step over this many samples[nn, L] = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.08, 'Too big error');%% ex2 neural net with L2 weight decayrand('state',0)nn = nnsetup([784 100 10]);nn.weightPenaltyL2 = 1e-4; % L2 weight decayopts.numepochs = 1; % Number of full sweeps through dataopts.batchsize = 100; % Take a mean gradient step over this many samplesnn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');%% ex3 neural net with dropoutrand('state',0)nn = nnsetup([784 100 10]);nn.dropoutFraction = 0.5; % Dropout fraction opts.numepochs = 1; % Number of full sweeps through dataopts.batchsize = 100; % Take a mean gradient step over this many samplesnn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');%% ex4 neural net with sigmoid activation functionrand('state',0)nn = nnsetup([784 100 10]);nn.activation_function = 'sigm'; % Sigmoid activation functionnn.learningRate = 1; % Sigm require a lower learning rateopts.numepochs = 1; % Number of full sweeps through dataopts.batchsize = 100; % Take a mean gradient step over this many samplesnn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');%% ex5 plotting functionalityrand('state',0)nn = nnsetup([784 20 10]);opts.numepochs = 5; % Number of full sweeps through datann.output = 'softmax'; % use softmax outputopts.batchsize = 1000; % Take a mean gradient step over this many samplesopts.plot = 1; % enable plottingnn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');%% ex6 neural net with sigmoid activation and plotting of validation and training error% split training data into training and validation datavx = train_x(1:10000,:);tx = train_x(10001:end,:);vy = train_y(1:10000,:);ty = train_y(10001:end,:);rand('state',0)nn = nnsetup([784 20 10]); nn.output = 'softmax'; % use softmax outputopts.numepochs = 5; % Number of full sweeps through dataopts.batchsize = 1000; % Take a mean gradient step over this many samplesopts.plot = 1; % enable plottingnn = nntrain(nn, tx, ty, opts, vx, vy); % nntrain takes validation set as last two arguments (optionally)[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');```[![Bitdeli Badge](https://d2weczhvl823v0.cloudfront.net/rasmusbergpalm/deeplearntoolbox/trend.png)](https://bitdeli.com/free "Bitdeli Badge")

浅谈ios上架的流程

最近app上线,大概了解了下上架的流程 1、创建App IDs 2、申请iOS发布证bai书 3、申请iOS发布描述文件 4、上传duios证书编译打包IPA 5、在iTunes Connect创建zhiApp 6、上传iTunes Connect 7、iTunes Connect填写APP信息dao并提交审核 首先需要在苹果官网缴费注册成为苹果开发者,从而按照上面的步骤进行操作。 补充: ios整体上架的成本比安卓高些,按我们正常流程的话需要配台mac pro才行,但是在网上也有对应的解决方案,可以借助于

BIlly Feng的博客 907

STM32F407中文手册

STM32f407中文手册 本参考手册面向应用开发人员,提供有关使用 STM32F405xx/07xx、STM32F415xx/17xx、 STM32F42xxx 和 STM32F43xxx 微控制器存储器与外设的完整信息。STM32F405xx/07xx、STM32F415xx/17xx、STM32F42xxx 和 STM32F43xxx 构成一个微 控制器系列,各产品具有不同的存储器大小、封装和外设。有关订购信息以及器件的机械与电气特性,请参见数据手册。有关 ARM Cortex™-M4F 内核的信息,请参见《Cortex™-M4F 技术参考手册》。

WINCC7.3破解文件

WINCC7.3硬件够破解文件,有需要的下载!WINCC7.3硬件够破解文件,有需要的下载!

iOS学习之协议浅谈

iOS学习之协议浅谈一、协议的定义协议是多种类共享的方法列表 协议定义了一系列的方法,其中这些方法又分为选择实现和必须实现。下面我们会详细说明。二、如何定义一个协议1.协议的定义定义一个协议很简单,在@protocol后加上所定义的协议的名称,然后就像接口部分一样,定义一些方法,但是要注意只列出方法有哪些,并不给出具体代码块,@end之前所有方法声明都是协议的一部分。 举个例子:下面是在标准的Fo...

shandamengcheng的博客 482

浅谈 iOS 版本号

FIR.im 一直在尽量兼容不同使用习惯的版本号形式, 但是在使用中我们发现好多开发者对怎么更好的用版本号来标示应用很陌生. 这是篇基础文章, 简单介绍 iOS 的版本号. 名词解释 Version, 通常说的版本号, 是应用向用户宣传说明时候用到的标识. 一般有2段或者3段式, 如:2.1,8.1.2 Version 一般由产品部门确定, 完全迥异的更新需要改变主版本号

FIRim的专栏 823

浅谈iOS之weak底层实现原理

前言 在iOS开发过程中,会经常使用到一个修饰词“weak”,使用场景大家都比较清晰,用于一些对象相互引用的时候,避免出现强强引用,对象不能被释放,出现内存泄露的问题。 weak 关键字的作用弱引用,所引用对象的计数器不会加一,并在引用对象被释放的时候自动被设置为 nil。 weak底层原理 1.weak编译解析 首先需要看一下weak编译之后具体出现什么样的变化,通过Clang...

Future_One的专栏 1万+

浅谈iOS KVC

1、KVC<Key-value-coding> 键值编码,单看这个名字可能不太好理解。其实翻译一下就很简单了,就是指iOS的开发中,可以允许开发者通过Key名直接访问对象的属性,或者给对象的属性赋值。而不需要调用明确的存取方法。这样就可以在运行时动态地访问和修改对象的属性。而不是在编译时确定,这也是iOS开发中的黑魔法之一,很多高级的iOS开发技巧都是基于KVC实现的。无论是Swift还是Objective-C,KVC的定义都是对NSObject的扩展来实现的(Objective-C中有个显式

shuzhi57的博客 296

浅谈iOS视频播放的N种解决方案

原文地址 :http://www.cocoachina.com/ios/20170507/19201.html MPMoviePlayerController自定义播放器并不是完美的: MPMoviePlayerController在iOS9被Apple废弃了; 当我们的需求需要深度定制播放器的时候,MPMoviePlayerController就显得力不从心了,比如

jyt婷的博客 1936

浅谈IOS ARC

浅谈IOS ARC碰到的问题ARCretain release dealloc 碰到的问题 今天ios产品出现重复加载多个图片引起程序崩溃,后发现是资源未释放引发的内存泄露。这让我对ios垃圾回收机制产生了兴趣 ARC 网上看了资料发现ios采用的居然是引用计数法进行垃圾回收。印象中,这个算法是非常古老且不好的方法,因为他无法解决循环引用的问题,我觉得现在还在用的垃圾回收机制怎么也得是标记清理吧。哈哈哈。简单介绍一下,说白了就是所有构造出的对象,有被引用就+1,取消掉一个就-1。那优点显而易见,他可以直接的

weixin_46045077的博客 221

ios学习之xcode编译器的浅谈

今天去逛了一下cocoachina 这个网站,了解到了xcode更多的知识  以下贴出 http://www.cocoachina.com/special/xcode/

不起眼的小学生 404

iOS 5的StoryBoard功能

苹果每一次推出新功能可以说都是一次比较大的变革,真的很难有人能够跟上它的节奏。iOS4.3 SDK刚开始有些理解了,iOS5就发布了,iOS的新功能刚熟悉,这不iOS6又要发布了。不管怎么样,还是先学习学习iOS5吧! 在iOS5中,一个比较特别的功能是StoryBoard,它的目的是代替历史悠久的NIB/XIB(其实StoryBoard还是基于NIB/XIB的,不过开发人员已经无需直接跟NIB

Kikim的地盘 2268

浅谈独立使用NDK编译库文件(Android)

原文地址: 阅读前准备 这是一篇相对入门的文章。文中会涉及到少许NDK的知识,但个人认为对初学者来说都相对比较实用,因为都是在平时项目中遇到的(目前自己也是初学者)。一些其他高深的技术不再本文探讨范围之内(因为我不懂)。文章中可能会存在一些啰里八嗦的地方,抱歉,目前的行文风格如此,考虑以后变得牛逼点儿再改改文风,毕竟现在的阶段还是自嘲的情感因素占上风。 你

xiaowu0124的专栏 819
上一篇: 解决Xcode Simulator 黑屏问题
下一篇: 浅谈iOS之UIWebView
Flame_Dream
博客等级 码龄12年 35粉丝 82原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值