Blacktea's Life

A programmer who love music and football


  • 首页

  • 分类

  • 关于

  • 归档

  • 标签

  • 公益404

iOS性能最佳实践系列之--加快页面加载的检查清单

发表于 2017-09-03 | 分类于 iOS | 阅读次数

如果一个页面加载慢,会给用户带来最直接的伤害。下面整理了一些tips,来保证页面的加载速度。

跳转事件触发后不要做阻塞操作

过去review代码时发现过一个问题。某个按钮点击后需要跳转到另外一个页面,但这个页面需要一些权限才能访问。于是代码里点击事件触发后,发了一个网络请求去校验权限,校验成功后再跳转。从用户的角度看,相当于卡了一下再跳新的页面,体验很糟糕。
建议有两种做法:
1 提前做好权限校验
2 直接进入新页面后再做权限校验,没有权限显示无权限的背景图

UIViewController的生命周期要避免阻塞
UIViewController的生命周期方法都在主线程,不要因为做消耗资源的事情(除了初始化视图)导致主线程被阻塞。

有消耗资源的操作放到子线程里,需要做UI渲染再回到主线程

下面是检查清单:

1
2
3
4
5
6
7
8
init
initWithNibName:bundle
viewWillAppear
viewDidAppear
viewDidLoad
viewWillDisappear
viewDidDisappear
...

如果接口请求慢,先加载缓存,再刷最新数据

如果页面的网络请求很慢,会出现白屏转菊花很久的情况,改善的策略如下。
1 先加载缓存数据
2 同步请求网络数据,网络数据请求完成后和本地数据校对,得出需要变化的数据。
3 将新增的数据加载到页面里,并提示“有数据更新”(类似新浪微博)

UITableView的DataSource的代理实现要避免阻塞

UITableView的DataSource回调函数在主线程,要避免耗时计算。

下面是检查清单:

1
2
3
tableView:cellForRowAtIndexPath
tableView:heightForRowAtIndexPath
...

减少UITableView高度计算次数

  • 有多种Cell或高度是动态的
    • 设置estimatedRowHeight(设置后会开启self—sizing,自动计算高度)
  • 只有一种Cell并且高度固定
    • 设置rowHeight
    • 不要重载”tableView:heightForRowAtIndexPath”

UICollectionView的DataSource的代理实现要避免阻塞

UICollectionView生命周期的回调函数在主线程,要避免耗时计算。
下面是检查清单:

1
2
3
collectionView:cellForItemAtIndexPath
collectionView:layout:sizeForItemAtIndexPath
collectionView:didSelectItemAtIndexPath

如何建设App的页面性能基线

发表于 2017-08-30 | 分类于 iOS | 阅读次数

为什么我们需要页面性能基线?

开发人员的差异性
在客户端的团队里,每个人对性能的了解程度是不一样的。不同的开发人员开发出来的页面性能的水位也不一样。除了技术上的差异,意识上的差异也不能忽视。对性能的敬畏之心不一样,也会影响开发的性能。

业务迭代的变化
随着业务的迭代,同一个页面因为需求的持续变更而会不断改造。这会让页面的性能指标有波动的出现。如果改造不注意,很容易会出现性能突然变差的情况。因为页面性能是波动性的,如果有基线数据做为参考线,则可以对存量页面的性能进行自动化分析。

页面性能的两个特性

不一致性

一个页面的性能指标怎么定主要受2个因素的影响,一个是UI元素的复杂度,一个是接口数据的复杂度。UI布局越复杂,页面渲染就会越慢。数据获取越复杂,页面就需要等待越长的时间进行渲染。因此我们不能一视同仁,所有页面都设置同样的基线。

合理波动性

鲜肉页面(新开发的页面)进入市场后,随着版本迭代,性能数据一定会有所波动。在一定范围内波动,我们认为是合理波动。

基线建设的3个目标

流程化
建立完整、合理的基线产出流程。

工具统一化
建立统一的性能分析方法论、分析工具。

标准化
建立标准化的基线算法,通过该算法计算出各页面的基线标准

3条性能基准线

性能红线

性能指标只要差到一定的程度,用户就会感受到明显的不顺畅、慢、卡。这时候,无论页面多复杂、接口多慢,都不是理由(再有价值的产品,如果体验太差对用户来说就是垃圾)。因此,我们需要设立一个性能红线,无论如何不能超过红线。

App综合性能指数

正如证券市场的道琼斯指数一样,取App所有页面性能数据的平均值作为页面综合性能指数。鲜肉页面没有历史版本的数据,没有参考的标准。由于页面复杂度存在不一致性,不能随便找找一个页面来做参考,这个时候取App综合性能指数做参考是最合适的。

页面性能基线(3个历史版本数据的均值)

鲜肉页面通过过滤流水线后,我们就将通过后页面的线上平均数据做为该页面的性能基线。

2条流水线

鲜肉过滤流水线

新开发的页面(鲜肉页面),由于复杂度不一致,我们不能准确判断开发出来的页面性能是否合理。但我们可以根据两条基准线做为参考值来估算页面性能是否合理。第一条是红线,高压线不能越。第二条是“APP综合性能指数”,如果低于这条线则合格,如果高于这条线则进入鉴别池。进入鉴别池的页面,将使用测试工具进行专项性能测试和分析。若发现问题,开发需要进行优化。

熟肉自动流水线

鲜肉页面经过过滤后,性能基本可以得到保障。但我们还需要保证,随着版本迭代这些页面性不能超过合理波动的范围。每期的版本迭代前,我们通过自动化测试拿到线下性能数据,然后与该页面的性能基线进行自动对比,计算迭代后的数据是否超过了基线的合理波动范围。若超过了,则进入鉴别池,使用测试工具进行专项性能测试和分析。若发现问题,开发需要进行优化。

两条流水线都可以建立自动化过滤系统,根据基准线进行自动化数据分析,解放测试同学的同时,保证全面地覆盖。

落地设计

根据实际的项目流程,可以分为线上实时监控和线下自动化数据分析。

线上实时监控

线上建立可视化的性能数据图表,方便进行实时监控和分析。
图片显示如下几条线

  • 波动线(页面性能基线+合理波动值)
  • 性能红线
  • 当前版本均值

线下自动化数据分析

线下建立自动化过滤系统,根据基准线进行自动化数据分析。

鲜肉页面过滤条件:
测试数据 > APP综合指数(剔除一定范围0-500,2000+)
熟肉页面过滤条件:
测试数据 > 页面基线值(页面性能基线+合理波动值)

整体流程图

整体落地

基线的流程和方法的建设,需要开发和测试人员一起协作完成。
开发人员负责:数据埋点、问题优化分析、积累沉淀
测试人员负责:测试件、数据平台建设、测试流程建设

落地是重点也是难点。其中有2个要点。
1进行方案评审和宣导,获取团队人员的反馈,并进行改善,形成团队内的统一认知。
2合理安排落地节奏。

《未来简史》————虚构比真实更重要

发表于 2017-08-16 | 分类于 history | 阅读次数

赫拉利提出了一个了解历史的理由。了解历史可以让我们了解历史给我带上的枷锁,让我们不再被历史的枷锁困住。

美国中产阶级们喜欢在自己家的小别墅前养一片绿油油、整齐的草坪。他们还会买除草机等各种设备,并且定期去修剪、浇灌、维护这块草坪。事实上这很浪费时间,也没有太多实际的价值,但为什么他们还要维持这样的习惯呢?

在中世纪欧洲,维护这样的草坪是很费人力、物力的,那个时候没有先进的设备,只有贵族家才能养得起,这是尊贵的表现。现在只要愿意花时间,中产阶级们都能养得起。那么这恰恰就失去了养殖草坪的尊贵性。就好像LV、Gucci等奢侈品如果大家都买得起,就失去了显示身份的价值。而了解了这段历史,我们可以知道自己为什么会有维护草坪这种自己都无法解释的习惯。不用再为这段偶然产生的历史习惯,而买单。

人和自然是我们常常思考的问题。人真的是第一可宝贵的吗?

回顾历史,在采集狩猎时代,人类相信万物有灵,相信泛灵论。人类每天需要猜测动物的想法、避免被动物偷袭、还要想办法捕获动物。采集狩猎时代人类只是大自然食物链上普通的一环,没有太大的优势。

农业社会,人驯养了很多动物,动物成了人类的私有物品。人对动物可以为所欲为,不再需要考虑对方的感受,人和动物不再平等。这时候泛灵论就不满足时代的需要了,于是诞生了各种有神论。基督教宣布,上帝给人类永久的灵魂,而其他动物是没有灵魂的。人类成为第二可宝贵的。

现代社会,进化论逐渐被社会认可,基督教那套灵魂的说法就站不住脚了。那人又如何比别的动物更宝贵呢?有人给出了答案,人有意识。这个理由还是很有市场的,它很难被证伪。科学家人认为人根本不需要意识。旧的记忆和新的外部刺激,对大脑来说都是电信号和化学反应。我们看到危险信号靠近后,视觉信号传递给大脑,大脑把神经信号传递给腿,腿受到命令就可以开始跑了。恐惧、害怕、惊慌等主观体验根本没有什么用。而且,现在科学家已经有足够证据证明哺乳动物和鸟类、鱼类都是有感情的。讨论意识不能说明人比动物宝贵。

那人到底为什么可宝贵呢?
因为,突然有一天,人类基因突变,成为一个会幻想、懂虚构的年轻人。
人能虚构东西,并且能相信虚构的东西。虚构出来各种新的概念,上帝、国家、金钱、公司、价值观,能让人群实现大规模、跨越时间和空间的合作。

国家也是虚构的,没有哪片土地天生属于某个国家。过去有一个国家,几千年来都维系着的文化和传统。突然有一天,在这个国家的土地上发生了一场战争。战争打完,这个国家被拆成了两片区域。这两片区域分别被植入了不同的想象共同体。最后出现了2个互相仇视的国家。

这是现在我们认为人类为什么比别的动物更可贵的原因。这些虚构的概念甚至比真实存在的都显得重要。比如宗教的出现维系着大尺度的社会秩序。

宗教是什么?
赫拉利总结了宗教的3个特点。
1 它有一套号称不是人发明的,而且不能被人改变的道德规范,要求人们必须遵守。
2 它给人们一个许诺,只要你遵从这套法规,就会有什么好处。
3 它的目的是为了巩固自己设想的社会秩序

我想起我一个大学同学,一个信仰基督教的女生。她是一个善良、乐于助人,有所为有所不为。她相信以她的标准区为人处事,一定会收获幸福。她有一个特点,内心没有恐惧。她不惧怕现在、不惧怕未来,不惧怕自己的出生低微,不惧怕别人有更好的机会,不惧怕天灾、不惧怕人祸。

这样的人,我们认为是有强大内心的。这也是基督教、佛教等主流宗教给社会带来的正面意义。每个宗教都有自己一套道德体系,只要这套道德体系无限趋近于社会共同的认知,那么我们可以只要大家去遵守,社会会变得更和谐。即便它给于教民的许诺是没有能力实现的。

这么说来,各种主义、有价值观和远景的公司文化,其实也符合宗教的特点。这也是为什么管理学会认为,最高级的管理是价值观、理念的管理。大家都遵守同样的规定、并且愿意相信未来的收获和意义,管理起来成本是相当的低啊。所有人一颗心、指哪打哪,不论战略和战术如何调整和部署,都能朝同一个方向去发力,效果肯定是杠杠的6啊。

看到这里,你估计也已经认为虚构的影响力确实很6,那你有没有想过你可以怎么使用虚构的思维呢?
虚构影响力可以这么大,是因为我们可以赋予虚构概念价值和意义,从而聚集更多的人。
我们做一个产品,要思考这个产品是否有改变世界的意义。做某一个项目,也需要思考它是否有影响深远的价值。
有新的想法,要尝试给想法定义一个新的概念。虚构的新概念,至少是需要经过思考和抽象的过程。大家对新概念的好奇,也能引发思考和传播。

阿里巴巴国际无线技术部 - 在这里遇见最好的自己

发表于 2017-08-16 | 分类于 Invite | 阅读次数

不要压抑你的天性了!打开邮箱把你的iOS/Android简历发到blackteachinese@gmail.com吧!

我们做什么

阿里巴巴国际无线技术部背靠阿里巴巴集团和阿里巴巴国际站,阿里巴巴国际站是全球领先的跨境B2B贸易服务平台,服务全世界数以千万计的采购商和供应商,阿里巴巴国际站(www.alibaba.com)帮助中小企业拓展国际贸易的出口营销推广服务,专注服务于全球中小微企业,在平台上,通过向海外买家展示、推广供应商的企业和产品,进而获得贸易商机和订单,买卖双方可以在线更高效地找到适合的彼此,并更快更安心地达成交易,是出口企业拓展国际贸易的首选跨境贸易平台。

阿里巴巴国际站技术致力于打造一站式eWTP全球国际B类贸易服务平台解决方案,基于大数据在搜索、推荐、营销、流量、金融等场景中提供全方位一站式的服务,赋能卖家。

近年来,随着智能手机的普及无线的趋势已经锐不可当。阿里巴巴国际无线技术部不仅是站在阿里巴巴集团和阿里巴巴国际站两个巨人的肩上,更是站在无线时代的浪潮之上。来吧少年,一起为全球中小企业服务!

我们在哪里

杭州 - 一座来了就不想走的城市
杭州有“欲把西湖比西子,淡妆浓抹总相宜”的西湖,

杭州有“钱塘岸上春如织,淼淼寒潮带晴色”的钱塘江(潮),

杭州有“最爱湖东行不足,绿杨阴里白沙堤”的白堤,

。。。。。。

从古至今无数的文人墨客为杭州驻足,给杭州添色。正所谓“你在杭州看风景,看风景的人在历史中看你”。古有一本《西湖梦寻》让无数人慕名杭州,今有一家“阿里巴巴”让追梦人逐梦杭州。来吧少年,“江南无所有,聊赠一聘书”。

为什么是我们

阿里巴巴早已经是一家庞大的集团公司,有大家熟知的淘宝、天猫等等。然而我却要说请记住它们,但请来阿里巴巴国际无线技术部:

内部环境:国际化 - 集团三大战略之一

早在2015年阿里巴巴集团提出:农村化、国际化、大数据和云计算是阿里巴巴集团未来的三大战略,2016年马老师又创造性的提出了eWTP,2016年9月马老师在二十国集团工商峰会(B20)发表演讲,系统性的阐述eWTP的理念、愿景和行动计划。并且在一年中几乎飞遍全球,向各国政府、国际组织描述和倡议eWTP。

2017年6月阿里巴巴在美国底特律举办大规模的论坛,马老师做了“中国的贸易机会”的主题演讲,更是承诺未来的5年内在美国创造100万的工作机。

无论是国际化、eWTP和中美贸易阿里巴巴国际无线技术部都是最重要的战场。

外部环境:一带一路 - 国家级顶层战略

2013年,中国首次超越美国跃居世界第一大贸易国。持续三年世界第一之后,世贸组织(WTO)的最新数据显示,2016年中国的进出口贸易额被美国反超,丧失了“世界第一外贸国家”的称号。

2015国家发展改革委、外交部、商务部联合发布了《推动共建丝绸之路经济带和21世纪海上丝绸之路的愿景与行动》,即是大家熟知的“一带一路”。它将充分依靠中国与有关国家既有的双多边机制,借助既有的、行之有效的区域合作平台,一带一路旨在借用古代丝绸之路的历史符号,高举和平发展的旗帜,积极发展与沿线国家的经济合作伙伴关系,共同打造政治互信、经济融合、文化包容的利益共同体、命运共同体和责任共同体。可以说“一带一路”和eWTP有异曲同工之秒。

2017年3月16号《中国新外贸模式研究报告》正式发布,重点分析了以阿里巴巴为代表的新外贸模式。结论是:新外贸模式极大降低了中国外贸的交易成本,同时交易平台提供的信息、数据和生态价值,会随时间增加为企业带来更大价值。阿里巴巴提倡的新外贸模式是外贸行业的新拐点。

无论是内部环境还是外部环境,作为阿里巴巴发家的阿里巴巴国际站正站在新的历史起点欲展翅翱翔,来吧少年,一起鹰击长空。

我们的技术

在新技术上无论是Andriod还是iOS,我们积极跟进业内新技术,试验落地。比如:在iWatch产品发布之初便做了适配、遵循Andriod的MD规范、DeepLink、Firebase等;在国际化上自研多语言的实时更新引擎、RTL适配,做到覆盖全球所有主流语种、货币单位等。

阿里巴巴国际无线技术部出品的Alibaba.com Andriod APP 多次获得Google Play官方推荐,以2017年3月10日为例:我们获得Google Play 104个国家的主页和购物分类的黄金展位。这是我们不断追求卓越,不断提升用户体验,不断创新的结果。由于我们在技术上的精益求精,我们始终和Google保持着非常良好的合作关系。

更多的荣誉:

来吧少年,一起码出未来!

不要压抑你的天性了!打开邮箱把你的iOS/Android简历发到blackteachinese@gmail.com吧!

我们的团队

阿里巴巴国际无线技术部现聚集40多名江湖三大名门正派(Andriod, iOS,JAVA)的顶级高手,由江湖人称“小马哥”的带头大哥领导,在这里每一个人都身怀绝技,一起来走进这些可爱的小伙伴吧:

如果你是终生学习者,如果你对认知科学、商业本质感兴趣,来吧少年!

“小马哥”之所以称为“小马哥”,不仅仅因为他姓马,也不仅仅他的英文名叫“Jack”,更重要的是有马老师的风采。如果你常年关注“得到APP”,你一定对傅盛、刘润、罗胖、李笑来等等如数家珍,那么来吧少年,在这里近距离感受“小马哥”价值2W的《认知升级》。在这里每一天都是成长!

如果你是IOS的开发者,又常见混迹开源社区,来吧少年!

如果你怀有一颗不安分的心,来吧少年!

我们一起去探索这个世界:

如果你精通八大菜系,来吧少年!

我们一起去吃遍全球:

如果以上这些都还不够打动你,那么下面一张一定可以

不要压抑你的天性了!打开邮箱把你的iOS/Android简历发到blackteachinese@gmail.com吧!

10分钟入门arm64汇编

发表于 2017-07-12 | 分类于 default | 阅读次数

什么是栈?

堆栈严格来说应该叫做栈,栈(Stack)是限定仅在一端进行插入或删除操作的线性表。因此,对栈来说,可以进行插入或删除操作的一端端称为栈顶(top),相应地,另一端称为栈底(bottom)。由于堆栈只允许在一端进行操作,因而按照后进先出(LIFO-Last In First Out)的原理运作。

会变化的栈顶

从栈顶的定义来看,栈顶的位置是可变的。空栈时,栈顶和栈底重合;满栈时,栈顶离栈底最远。ARM为堆栈提供了硬件支持,它使用一个专门的寄存器(堆栈指针)指向堆栈的栈顶。

两种存储器堆栈

递增堆栈:向上生长:向高地址方向生长
递减堆栈:向下生长:向低地址方向生长

ARM堆栈的生长方向

虽然ARM处理器核对于两种生长方式的堆栈均支持,但ADS的C语言编译器仅支持一种方式,即从上往下长,并且必须是满递减堆栈。所以STMFD等指令用的最多。

寄存器

寄存器是用来存储CPU在计算过程中临时数据的, arm64有32个通用寄存器(这里不对浮点数/向量寄存器等做说明):
x0-x31, 这些寄存器可以直接在汇编代码里面使用, 也是最经常被使用到的寄存器.

  • SP寄存器, Stack Pointer, 指向栈低的指针. 它是一个隐含的寄存器, 可以在内存操作指令中通过x31寄存器来访问.
  • PC寄存器, Program Counter, 记录当前执行的代码的地址. 它是一个隐含的寄存器, 无法被直接访问, 只能被特定的指令隐含访问.
  • LR寄存器, Link Register, 指向返回地址, 即return时回到的地址. 它就是x30, 可以随意访问读写, 意味着程序可以随意改变方法的返回地址.
  • FP寄存器, Frame Pointer, 指向上一次方法调用的frame的最高位地址, frame位于栈上. 它就是x29, 可以随意访问读写.

FP是指向frame的最高位地址, frame位于栈上, 那frame是什么呢?
frame其实就是一个按照方法调用顺序, 从栈的高地址向低地址依次存放的一组数据, 用于存放上一次方法调用的关键信息. 数据可以参考下图:

寻址方式

1
2
3
4
5
6
7
8
9
10
11
12
add x0,x0,#1 ;x0 <==x0+1 ,把x0的内容加1。
add x0,x0,#0x30 ;x0 <==x0+0x30,把x0的内容加 0x30。
add x0,x1,x3 ;x0 <==x1+x3, 把x1的内容加上x3的内容放入x0
add x0,x1,x3,lsl #3 ;x0 <==x0+x3*8 ,x3的值左移3位就是乘以8,结果与x1的值相, 放入x0.
add x0,x1,[x2] ;x0 <==x1+[x2], 把x1的内容加上x2的内容作为地址取内存内容放入x0
ldr x0,[x1] ;x0 <==[x1], 把x1的内容作为地址取内存内容放入x0
str x0,[x1] ;[x1] <== x0, 把x0的内容放入x1的内容作为地址的内存中
ldr x0,[x1,#4] ;x0 <==[x1+4], 把x1的内容加上4, 作为内存地址, 取其内容放入x0
ldr x0,[x1,#4]! ;x0 <==[x1+4]、 x1<==x1+4, 把x1的内容加上4, 作为内存地址, 取其内容放入x0, 然后把x1的内容加上4放入x1
ldr x0,[x1],#4 ;x0 <==[x1] 、x1 <==x1+4, 把x1的内容作为内存地址取内存内容放入x0, 并把x1的内容加上4放入x1
ldr x0,[x1,x2] ;x0 <==[x1+x2], 把x1和x2的内容相加, 作为内存地址取内存内容放入x0

常用指令

b 跳转到地址(无返回), 不会改变LR寄存器的值
bl 跳转到地址(有返回), 会改变LR寄存器的值为返回地址
ldr/ldur 地址对应的内容加载到寄存器
str/stur 寄存器内容存储到内存地址
ldp/stp 取/存一对数据(2个)
cbz/cbnz 为零跳转到地址/不为零跳转到
add 加法运算
mov 寄存器之间内容移动
ldp/stp 从栈取/存数据
adrp, 用来定位数据段中的数据用, 因为aslr会导致代码及数据的地址随机化, 用adrp来根据pc做辅助定位

案例解析

main函数汇编解析

  • 设置Debug->Debug Workflow->Always Show Disassembly
  • 打断点到callMe函数。PS:callMe调用别的方法才会被保存frame
1
2
3
4
5
6
7
8
9
10
11
12
13
void callYou() {
}
void callMe(int a, int b) {
callYou();
}
int main(int argc, char * argv[]) {
int a = 4;
int b = 10;
callMe(a, b);
}

这段代码编译结果如下

1
2
3
4
libdyld.dylib`start:
0x18aad55b4 <+0>: nop
0x18aad55b8 <+4>: bl 0x18ab05378 ; exit
0x18aad55bc <+8>: brk #0x3
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Arm64`main:
0x100026c24 <+0>: sub sp, sp, #0x30 ; =0x30 ;编译器计算到此次方法调用要48bit的栈,把栈底减48bit留出空位存临时变量
0x100026c28 <+4>: stp x29, x30, [sp, #0x20] ;将fp和lr存入栈的sp+32的地址
0x100026c2c <+8>: add x29, sp, #0x20 ; =0x20 ;sp+32后存入x29,fp此时的位置就是sp+32
0x100026c30 <+12>: mov w8, #0xa ;将10存到寄存器w8
0x100026c34 <+16>: orr w9, wzr, #0x4 ;将4与wzr进行或运算,存到寄存器W9
0x100026c38 <+20>: stur w0, [x29, #-0x4] ;把w0(main方法的第一个参数, argc),存入x29减4的位置
0x100026c3c <+24>: str x1, [sp, #0x10] ;将x1(main方法的第二个参数*argv[]),存入sp-16的位置
0x100026c40 <+28>: str w9, [sp, #0xc] ;将w9的值,存入到sp+12的位置
0x100026c44 <+32>: str w8, [sp, #0x8] ;将w8的值,存入到sp+8的位置
0x100026c48 <+36>: ldr w0, [sp, #0xc] ;将sp+12地址对应的内容加载到寄存器w0
0x100026c4c <+40>: ldr w1, [sp, #0x8] ;将sp+8地址对应的内容加载到寄存器w1
0x100026c50 <+44>: bl 0x100026c00 ; callMe at main.m:16 ;调用callMe方法,将w0、w1作为参数
-> 0x100026c54 <+48>: mov w8, #0x0 ;将0存到w8
0x100026c58 <+52>: mov x0, x8 ;将0存到x0
0x100026c5c <+56>: ldp x29, x30, [sp, #0x20] ;从栈里把最开始保存的fp和lr还原回来
0x100026c60 <+60>: add sp, sp, #0x30 ; =0x30 ;sp+48后,存到sp(跳到下一个frame的位置)
0x100026c64 <+64>: ret ;返回
1
2
3
4
5
6
7
8
9
10
Arm64`callMe:
0x100026c00 <+0>: sub sp, sp, #0x20 ; =0x20 ;编译器计算到此次方法调用要用到32bit的栈,把栈底减32bit留出空位存临时变量
0x100026c04 <+4>: stp x29, x30, [sp, #0x10] ;将fp和lr寄存器存入到sp+16的位置
0x100026c08 <+8>: add x29, sp, #0x10 ; =0x10 ; 将sp+16后,存入到x29
0x100026c0c <+12>: stur w0, [x29, #-0x4] ; 将w0(第一个参数)存入到x29 -4的位置
0x100026c10 <+16>: str w1, [sp, #0x8] ;将w1(第二个参数) 存入到sp+8的位置
-> 0x100026c14 <+20>: bl 0x100026bfc ; callYou at main.m:14 ; 调用callYou方法
0x100026c18 <+24>: ldp x29, x30, [sp, #0x10] ;从栈里把最开始保存的fp和lr还原回来
0x100026c1c <+28>: add sp, sp, #0x20 ; =0x20 ;sp+32后,存入到sp(跳到下一个frame的位置)
0x100026c20 <+32>: ret

从start->main->callme->callYou有3个frame

sp的正负偏移

sp存取的时候有的偏移量是正数, 有的是负数, 这有什么区别呢?
在stack里面, sp指针之下(负数偏移量)的数据是不保证安全的, 可能被覆盖, 而sp指针之上(正数偏移量)的数据是安全的. 放到负数偏移量一般都是临时存一下数据, 需要被整个方法用到的数据一般放到sp的正数偏移位置.

git地址

demo传送门

参考

ARM Reference Manual: https://www.element14.com/community/servlet/JiveServlet/previewBody/41836-102-1-229511/ARM.Reference_Manual.pdf

ARM64 Function Calling Conventions: https://developer.apple.com/library/content/documentation/Xcode/Conceptual/iPhoneOSABIReference/Articles/ARM64FunctionCallingConventions.html

FP寄存器及frame pointer介绍: http://blog.chinaunix.net/uid-25871104-id-2938389.html
RealView 编译工具 《汇编器指南》
Hopper Disassembler

如何面试一个iOS工程师

发表于 2017-07-12 | 分类于 管理 | 阅读次数

0x1 我们的考察重点是什么?

对于一个客户端开发工程师我们需要考察的关键能力可以抽象为5个方面。基础能力、专项能力、架构能力、系统分析能力、价值产出能力。

对于一个客户开发工程师,基础能力都有什么呢?
我的定义是,能够支撑这个工程师完成90%开发工作所需要具备的能力。

什么是专项能力呢?
音视频处理、性能优化、网络通信、工程效能、疑难问题排查这些都算。
一个工程师是否有专线领域的积累,往往可以看出这个人是否有技术追求、是否有技术深度积累的意识。另外,一个团队也需要具备各种专项能力的人,去解决不同的难题。

架构能力不是面试架构师才要考察的能力吗?
当然不是。处理很复杂的系统,需要架构能力。设计一个简单的模块也需要架构的能力。例如设计一个监控模块、智能图片加载模块、UI组件库。一个大公司的部门设计、组织架构如何安排相当于复杂系统的架构设计。一个项目组如何安排每一个角色的工作职责、协作方式、监督机制、激励与惩罚机制就相当于是一个小模块的架构设计。

只有具备了系统分析能力,才能对系统内各功能模块进行合理的设计和安排。
在实际工作里,你不仅需要考虑技术层面的问题,你还需要考虑业务发展对架构的要求、有多少资源可以用,项目成本和价值产出比的情况等综合因素。而且你还需要考虑这些因素会不断的变化。系统性、综合性的分析能力这个时候就显得十分重要。
另外,我认为这也是人类和人工智能最大的区别。人类的大脑实际上在处理复杂问题、综合性问题的能力是远远强与现在的人工智能的。

最后是价值产出能力。对于一个工程师,价值的产出有许多维度和层次,下面我介绍最常见的3种。
第一层,完成项目组布置的日常工作。
第二层,通过分析,找到目前项目组存在的问题,主动产出解决这些问题的方案,并推动这些方案的执行。
第三层,可以根据业务的现状和发展方,能规划出技术团队各阶段需要储备的基础建设、架构的发展、人员的培养方向,并针对这些规划制定高效、可执行的方案。

不同公司在不同的阶段,对于一个客户端研发工程师的要求都是不一样的。我们需要针对项目组当前的实际需要和未来的发展趋势、着重对其中几项关键点进行考察。

0x2 面试的方法论

面试时我们需要更真实的反馈,我们需要使用STAR(行为面试方法),多问情景问题、开发性问题,挖掘候选者实际情景处理问题的细节,来考察他的能力。
STAR的方法有4个关键词,situation(情景)、task(任务)、action(行动)、result(结果)。
根据这4个关键词逐层深入问问题。

1做这个项目前有什么困难?
2你是怎么做的?
3为什么这样做?
4结果怎么样?
5再做一次,有什么地方可以改进?

0x3 基础能力考察

c语言、oc、swift
开发环境、工具
UI
网络层
多线程
设计模式
….

这些都是一个iOS开发工程师需要具备的基础能力。基础能力是首要考察点,如果这部分能力有严重的欠缺,说明候选者是不够专业的。
对于基础能力的考察,要尽量考察得全面。准备好各项考察点的问题,其中同一个考察点,要准备好3个层次的问题,这样可以更好地定位候选者每个知识点的掌握的深度。

  • tableView的delegate、datasource各自复杂哪些事情?
  • 有什么办法让tableView变得更加流畅?
  • 如果要自己写一个tableView,介绍一下思路。有哪些关键点要注意的?

附上一个iOS技术树

0x4 进阶能力考察

进阶能力包括前面提到的专线能力、架构能力、系统分析能力、表达能力。这部分能力的考察首先应该结合候选者过往开发的商业项目、开源项目。挖掘对方过往项目里的亮点,使用STAR方法去评估对方各项进阶能力。

  • 有没有别的方法?
  • 遇到什么困难,怎么解决?
  • 怎么保障性能/安全性?
  • 实际效果怎么样?怎么体现?

0x5 程序员通用技能

  • sql
  • 正则
  • 数据结构
  • 基础算法
  • 网络编程
  • …

程序员一些通用的技能也是需要考察的。通过这部分内容的考察,可以看出候选者的计算机基础水平掌握的程度。

一些非科班出生的候选者,往往这部分知识会掌握得较差一些。但他们是否有意识地去补充这部分知识,是很容易问出来。

0x6 潜力

什么程序员有潜力?

我的定义是,在未来可以遇见的3到5年内,进步的空间比同级别的人要大,进步的速度相对要更快。

很多人可以觉得年轻就有潜力,其实未必。我们应该细化到候选人身上的一些基能力、性格、思维、习惯。下面我列举一些,有潜力的考察点。

  • 英语水平
  • 学习习惯
  • 工作习惯
  • 耐操性
  • 总结能力
  • 说服力
  • 表现能力
  • 管理意识
  • 共情能力

成为值得被爱的人

发表于 2017-07-10 | 分类于 心灵 | 阅读次数

人不但天生希望被爱,而且希望自己成为值得被爱的人。
我们天生都是自利的。也许有人不同意这个观点。是的,你会不求回报地对某个人好,这很棒。但是这也是自利的表现。
我们对家人好,可能是我们喜欢得到家人的温暖的感觉。我们不希望家人远离我们,于是我们对他们额外的好。因为,我们不是活在只有一个人的世界里。我们的自利,会驱动我们,希望被别人爱。
被一个人爱还不够,我们还希望被更多人爱、持续地被爱。因此,我们便需要称为值得被爱的人。
是的,这也许便是我们一生为之奋斗的目标。即便你是一个放荡不羁的自由主义者,当你看到美得无法用言语表达的巴塞罗那夜景,你也不可能只希望独自欣赏。你至少会希望,有一个和你一样的人、你爱的人,能够和你一起分享,你内心的喜悦。

我们如何成为一个值得被爱的人?
很多人一生在不断追求名利,希望通过名利的获取,让自己指得被爱。这确实是一种方法,但是这并不一定是最好的方法。因为如果你即便很有钱、很有名气,也不一定就特别值得被爱。你可以只是值得被拥有。
追求智慧和美德是更好的方法。
培养自己的美德,比如正义之气、比如仁慈的内心、比如不以物喜、不以己悲的价值观。
寻求改变世界的智慧。这并不是说要你做声么大事,恰恰是“不做大事的智慧”。我们要做的是,从我做起、从小事做起。见到别人做好事,就鼓掌,见到坏事就站出来说话,这样无数的你一起,就能通过“涌现”的办法,改变世界

webrtc实现音视频通话

发表于 2017-05-09 | 分类于 voip | 阅读次数

webrtc是什么?

webrtc(Web Real-Time Communication)是跨平台的音视频实时通话协议。
使用webrtc开发一个移动端的实时音视频通讯协议功能需要具备以下几项能力,NAT穿越、SignalChannel信令通道、webRTC会话建立、音视频数据获取、音视频数据传输。

NAT穿越技术

ICE

由以下几个技术和协议组成的:STUN、NAT、TURN、SDP,这些协议技术,帮助ICE共同实现了NAT/防火墙穿越。

STUN

1 概念

STUN(Session Traversal Utilities for NAT,NAT会话传输应用程序)是一种网络协议,它允许位于NAT(或多重NAT)后的客户端找出自己的公网地址,查出自己位于哪种类型的NAT之后以及NAT为某一个本地端口所绑定的Internet端端口。

2 作用

STUN服务器部署2个IP以及2个port。A客户端通过向STUN服务器不同IP发UDP包。再根据是否能收到STUN服务器的response等等来判断。1是否当前在公网2NAT的类型

3 NAT探测过程

STEP1:

B向C的IP1的pot1端口发送一个UDP 包。C收到这个包后,会把它收到包的源IP和port写到UDP包中,然后把此包通过IP1和port1发还给B。这个IP和port也就是NAT的外网 IP和port,也就是说你在STEP1中就得到了NAT的外网IP。当B收到此UDP后,把此UDP中的IP和自己的IP做比较,如果是一样的,就说明自己是在公网。如果不一样,说明有NAT的存在,系统进行STEP2的操作。

STEP2:

B向C的IP1发送一个UDP包,请求C通过另外一个IP2和PORT(不同与SETP1的IP1)向B返回一个UDP数据包。我们来分析一下,如果B收到了这个数据包,那说明什么?说明NAT来着不拒,不对数据包进行任何过滤,这也就是STUN标准中的full cone NAT。如果没收到,那么系统进行STEP3的操作。

STEP3:

B向C的IP2的port2发送一个数据包,C收到数据包后,把它收到包的源IP和port写到UDP包中,然后通过自己的IP2和port2把此包发还给B。如果这个port和step1中的port一样,那么可以肯定这个NAT是个CONE NAT,否则是对称NAT。

STEP4:

B向C的IP2的一个端口PD发送一个数据请求包,要求C用IP2和不同于PD的port返回一个数据包给B。

我们来分析结果:如果B收到了,那也就意味着只要IP相同,即使port不同,NAT也允许UDP包通过。显然这是restrict cone NAT。如果没收到,没别的好说,port restrict NAT.

4 获取客户端公网地址

客户端A向STUN服务器发送一个UDP包1。STUN服务器接收到UDP包1后,将该UDP包的源IP和源Port写到UDP包2发送给A。A收到UDP包2后,打开一看,源IP和源Port就是自己的公网地址

TURN(数据中转)

1 概念

TURN(Traversal Using Relay NAT),是一种资料传输协议(data-transfer protocol)。通过中继服务器,穿透 NAT 或防火墙使两个 TCP 或 UPD 客户端建立连接。

2 作用

如果使用STUN打洞失败,只能使用TURN服务器做中转数据

信令通道(SignalChannel)

Signalling 来发现各个 Peer,通过 Signalling 来控制各个 Peer 之间连接的建立和断开。(通常用WebSocket/MQTT/Socket原生/XMPP)

webRTC协议

WebRTC会话建立

SDP(客户端配置信息)

会话描述协议Session Description Protocol (SDP) 是一个描述多媒体连接内容的协议,例如分辨率,格式,编码,加密算法等

JSEP(会话建立协议JavaScript Session Establishment Protocol)

• 呼叫方发送 offer;
• 被呼叫方接受这个 offer;
• 被呼叫方发送 answer;
• 呼叫方接受 answer

必要信息

1 本地客户端的配置信息;
2 远程客户端的配置信息;
3 远程参与建立P2P连接的信息:主要是 IP 和端口。

P2P连接的过程

1 A和B连接上服务端,建立一个TCP长连接(任意协议都可以,WebSocket/MQTT/Socket原生/XMPP),我们这里为了省事,直接采用WebSocket,这样一个信令通道就有了。

2 A从ice server(STUN Server)获取ice candidate并发送给Socket服务端,并生成包含session description(SDP)的offer,发送给Socket服务端。

3 Socket服务端把A的offer和ice candidate转发给B,B会保存下A这些信息。

4 然后B发送包含自己session description的answer(因为它收到的是offer,所以返回的是answer,但是内容都是SDP)和ice candidate给Socket服务端

5 Socket服务端把B的answer和ice candidate给A,A保存下B的这些信息。至此A与B建立起了一个P2P连接。

获取音视频数据

MediaStream:
媒体流,一个媒体流包含 0 个到多个的媒体数据源,媒体流里面的数据源在呈现(render)必须同步

MediaStreamTrack:
媒体数据源,一个媒体数据源构成一个 MediaStreamTrack,比如音频数据源和视频数据源,多个相互之间有关联的媒体数据源(比如有同步关系的音频视频媒体数据源)构成一个媒体流(MediaStream)。

传输音频和视频数据

PeerConnection(RTCPeerConnection (C++)):

表示一个 WebRTC 通讯连接对象,它维护与这个通讯连接相关的 MediaStream,处理通讯双方信令事件,完成通讯数据的传输。

传输任意二进制数据

DataChannel(RTCDataChannel)

数据通道,两个 WebRTC 终端的连接建立后,它们可以通过这个数据通道传输任意类型的数据。

安全性

客户端

  • 对通话对象的安全身份认证
  • 对通话内容的加密

服务端

  • 对客户端的合法性认证,对自身的保护
  • 对合法客户端的保护

编译原理入门

发表于 2017-04-25 | 分类于 compile | 阅读次数

编译器结构

编译器由两个部分组成:分析 (analysis)部分和综合 (synthesis)部分。

  • 分析部分也称前端 (front end),它把源程序分解成为多个组成要素,并在这些要素之上加上语法结构,然后使用这个结构来创建该源程序的一个中间表示。
  • 分析部分还会收集有关源程序的信息,放在一个称为符号表 (symbol table)的数据结构中。符号表将和中间表示形式一起传送给综合部分。
  • 综合部分也称后端 (back end),根据中间表示和符号表中的信息来构造用户期待的目标程序。

编译步骤

  • 词法分析
    • 词法分析器读入组成源程序的字符流,将他们切分组织成有意义的词法单元 (token)的序列作为输出。
  • 语法分析
    • 语法分析器根据各个词法单元,创建树形中间表示,常用语法树 (syntax tree)。语法分析只判断源程序在结构上是否正确。
  • 语义分析
    • 使用语法树和符号表中的信息来检查源程序是否和语言定义的语义一致。语义分析是判断结构正确的源程序所表达的意义是否正确,其中一个重要部分是类型检查 (type checking)。
  • 中间代码生成
    • 编译器根据语法树和符号表,生成一个明确的低级的或类机器语言的中间表示 (intermediate representation)。三地址代码 (three-address code)是一种常见的中间表示形式。
  • 代码优化(机器无关)
    • 机器无关的代码优化步骤试图改进中间代码,以便生成更好的目标代码。
  • 代码生成器
    • 代码生成器以源程序的中间表示形式作为输入,并把它映射到目标语言。如果目标语言是机器代码,就必须为程序使用的每个变量选择寄存器或内存位置。然后,中间指令被翻译成为能够完成相同任务的机器指令序列
  • 代码优化(机器相关)
    • 优化目标机器语言

查看编译步骤

创建文件 compileText.c

1
2
3
4
5
#include <stdio.h>
int main (int argc, char *argv[]) {
printf("Hello workd");
return 0;
}

使用clnag/llvm,输入下面命令查看编译步骤:

1
clang -ccc-print-phases compileTest.c

输出得到编译步骤:

1
2
3
4
5
6
7
0: input, "compileTest.c", c
1: preprocessor, {0}, cpp-output
2: compiler, {1}, ir
3: backend, {2}, assembler
4: assembler, {3}, object
5: linker, {4}, image
6: bind-arch, "x86_64", {5}, image

符号表

符号表记录源程序中使用的变量的名字,并收集和每个名字的各种属性有关的信息。如一个名字的存储分配、类型、作用域、参数数量和类型、返回类型等。
符号表为每个变量名字创建一个记录条目。编译器可以向记录中快速存放和获取数据。

Example

词法分析

创建文件 compileText.c

1
2
3
4
5
#include <stdio.h>
int main (int argc, char *argv[]) {
printf("Hello workd");
return 0;
}

输入以下命令:

1
clang -fmodules -fsyntax-only -Xclang -dump-tokens compileTest.c

输出token序列:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
annot_module_include '#include <s' Loc=<compileTest.c:1:1>
int 'int' Loc=<compileTest.c:2:1>
identifier 'main' [LeadingSpace] Loc=<compileTest.c:2:5>
l_paren '(' [LeadingSpace] Loc=<compileTest.c:2:10>
int 'int' Loc=<compileTest.c:2:11>
identifier 'argc' [LeadingSpace] Loc=<compileTest.c:2:15>
comma ',' Loc=<compileTest.c:2:19>
char 'char' [LeadingSpace] Loc=<compileTest.c:2:21>
star '*' [LeadingSpace] Loc=<compileTest.c:2:26>
identifier 'argv' Loc=<compileTest.c:2:27>
l_square '[' Loc=<compileTest.c:2:31>
r_square ']' Loc=<compileTest.c:2:32>
r_paren ')' Loc=<compileTest.c:2:33>
l_brace '{' [LeadingSpace] Loc=<compileTest.c:2:35>
identifier 'printf' [StartOfLine] [LeadingSpace] Loc=<compileTest.c:3:5>
l_paren '(' Loc=<compileTest.c:3:11>
string_literal '"Hello workd"' Loc=<compileTest.c:3:12>
r_paren ')' Loc=<compileTest.c:3:25>
semi ';' Loc=<compileTest.c:3:26>
return 'return' [StartOfLine] [LeadingSpace] Loc=<compileTest.c:4:5>
numeric_constant '0' [LeadingSpace] Loc=<compileTest.c:4:12>
semi ';' Loc=<compileTest.c:4:13>
r_brace '}' [StartOfLine] Loc=<compileTest.c:5:1>
eof '' Loc=<compileTest.c:5:2>

语法分析

输入以下命令:

1
clang -fmodules -fsyntax-only -Xclang -ast-dump compileTest.c

输出如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
TranslationUnitDecl 0x7f8e4d81fad0 <<invalid sloc>> <invalid sloc>
|-TypedefDecl 0x7f8e4d820018 <<invalid sloc>> <invalid sloc> implicit __int128_t '__int128'
| `-BuiltinType 0x7f8e4d81fd40 '__int128'
|-TypedefDecl 0x7f8e4d820078 <<invalid sloc>> <invalid sloc> implicit __uint128_t 'unsigned __int128'
| `-BuiltinType 0x7f8e4d81fd60 'unsigned __int128'
|-TypedefDecl 0x7f8e4d820368 <<invalid sloc>> <invalid sloc> implicit __NSConstantString 'struct __NSConstantString_tag'
| `-RecordType 0x7f8e4d820170 'struct __NSConstantString_tag'
| `-Record 0x7f8e4d8200c8 '__NSConstantString_tag'
|-TypedefDecl 0x7f8e4d8203f8 <<invalid sloc>> <invalid sloc> implicit __builtin_ms_va_list 'char *'
| `-PointerType 0x7f8e4d8203c0 'char *'
| `-BuiltinType 0x7f8e4d81fb60 'char'
|-TypedefDecl 0x7f8e4d8206d8 <<invalid sloc>> <invalid sloc> implicit __builtin_va_list 'struct __va_list_tag [1]'
| `-ConstantArrayType 0x7f8e4d820680 'struct __va_list_tag [1]' 1
| `-RecordType 0x7f8e4d8204f0 'struct __va_list_tag'
| `-Record 0x7f8e4d820448 '__va_list_tag'
|-ImportDecl 0x7f8e4d98ada0 <compileTest.c:1:1> col:1 implicit Darwin.C.stdio
|-FunctionDecl 0x7f8e4d98b040 <line:2:1, line:5:1> line:2:5 main 'int (int, char **)'
| |-ParmVarDecl 0x7f8e4d98ade8 <col:11, col:15> col:15 argc 'int'
| |-ParmVarDecl 0x7f8e4d98af00 <col:21, col:32> col:27 argv 'char **':'char **'
| `-CompoundStmt 0x7f8e4d9e9ab0 <col:35, line:5:1>
| |-CallExpr 0x7f8e4d9e9a18 <line:3:5, col:25> 'int'
| | |-ImplicitCastExpr 0x7f8e4d9e9a00 <col:5> 'int (*)(const char *, ...)' <FunctionToPointerDecay>
| | | `-DeclRefExpr 0x7f8e4d98b538 <col:5> 'int (const char *, ...)' Function 0x7f8e4d98b158 'printf' 'int (const char *, ...)'
| | `-ImplicitCastExpr 0x7f8e4d9e9a60 <col:12> 'const char *' <BitCast>
| | `-ImplicitCastExpr 0x7f8e4d9e9a48 <col:12> 'char *' <ArrayToPointerDecay>
| | `-StringLiteral 0x7f8e4d98b598 <col:12> 'char [12]' lvalue "Hello workd"
| `-ReturnStmt 0x7f8e4d9e9a98 <line:4:5, col:12>
| `-IntegerLiteral 0x7f8e4d9e9a78 <col:12> 'int' 0
`-<undeserialized declarations>

中间代码生成

输入命令:

1
clang -S -emit-llvm compileTest.c -o compileTest.ll

得到compileTest.ll文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
; ModuleID = 'compileTest.c'
source_filename = "compileTest.c"
target datalayout = "e-m:o-i64:64-f80:128-n8:16:32:64-S128"
target triple = "x86_64-apple-macosx10.12.0"
@.str = private unnamed_addr constant [12 x i8] c"Hello workd\00", align 1
; Function Attrs: nounwind ssp uwtable
define i32 @main(i32, i8**) #0 {
%3 = alloca i32, align 4
%4 = alloca i32, align 4
%5 = alloca i8**, align 8
store i32 0, i32* %3, align 4
store i32 %0, i32* %4, align 4
store i8** %1, i8*** %5, align 8
%6 = call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([12 x i8], [12 x i8]* @.str, i32 0, i32 0))
ret i32 0
}
declare i32 @printf(i8*, ...) #1
attributes #0 = { nounwind 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" "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 = { "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" "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}
!llvm.ident = !{!1}
!0 = !{i32 1, !"PIC Level", i32 2}
!1 = !{!"Apple LLVM version 8.1.0 (clang-802.0.41)"}

生成汇编

输入命令:

1
clang -S compileTest.c -o compileTest.s

得到compileTest.s:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
.section __TEXT,__text,regular,pure_instructions
.macosx_version_min 10, 12
.globl _main
.p2align 4, 0x90
_main: ## @main
.cfi_startproc
## BB#0:
pushq %rbp
Ltmp0:
.cfi_def_cfa_offset 16
Ltmp1:
.cfi_offset %rbp, -16
movq %rsp, %rbp
Ltmp2:
.cfi_def_cfa_register %rbp
subq $32, %rsp
leaq L_.str(%rip), %rax
movl $0, -4(%rbp)
movl %edi, -8(%rbp)
movq %rsi, -16(%rbp)
movq %rax, %rdi
movb $0, %al
callq _printf
xorl %ecx, %ecx
movl %eax, -20(%rbp) ## 4-byte Spill
movl %ecx, %eax
addq $32, %rsp
popq %rbp
retq
.cfi_endproc
.section __TEXT,__cstring,cstring_literals
L_.str: ## @.str
.asciz "Hello workd"
.subsections_via_symbols

生成目标文件

输入命令:

1
clang -fmodules -c compileTest.c -o compileTest.o

输出文件compileTest.o:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
cffa edfe 0700 0001 0300 0000 0100 0000
0400 0000 0002 0000 0020 0000 0000 0000
1900 0000 8801 0000 0000 0000 0000 0000
0000 0000 0000 0000 0000 0000 0000 0000
a000 0000 0000 0000 2002 0000 0000 0000
a000 0000 0000 0000 0700 0000 0700 0000
0400 0000 0000 0000 5f5f 7465 7874 0000
0000 0000 0000 0000 5f5f 5445 5854 0000
0000 0000 0000 0000 0000 0000 0000 0000
3400 0000 0000 0000 2002 0000 0400 0000
c002 0000 0200 0000 0004 0080 0000 0000
0000 0000 0000 0000 5f5f 6373 7472 696e
6700 0000 0000 0000 5f5f 5445 5854 0000
0000 0000 0000 0000 3400 0000 0000 0000
0c00 0000 0000 0000 5402 0000 0000 0000
0000 0000 0000 0000 0200 0000 0000 0000
0000 0000 0000 0000 5f5f 636f 6d70 6163
745f 756e 7769 6e64 5f5f 4c44 0000 0000
0000 0000 0000 0000 4000 0000 0000 0000
2000 0000 0000 0000 6002 0000 0300 0000
d002 0000 0100 0000 0000 0002 0000 0000
0000 0000 0000 0000 5f5f 6568 5f66 7261
6d65 0000 0000 0000 5f5f 5445 5854 0000
0000 0000 0000 0000 6000 0000 0000 0000
4000 0000 0000 0000 8002 0000 0300 0000
0000 0000 0000 0000 0b00 0068 0000 0000
0000 0000 0000 0000 2400 0000 1000 0000
000c 0a00 0000 0000 0200 0000 1800 0000
d802 0000 0200 0000 f802 0000 1000 0000
0b00 0000 5000 0000 0000 0000 0000 0000
0000 0000 0100 0000 0100 0000 0100 0000
0000 0000 0000 0000 0000 0000 0000 0000
0000 0000 0000 0000 0000 0000 0000 0000
0000 0000 0000 0000 0000 0000 0000 0000
5548 89e5 4883 ec20 488d 0525 0000 00c7
45fc 0000 0000 897d f848 8975 f048 89c7
b000 e800 0000 0031 c989 45ec 89c8 4883
c420 5dc3 4865 6c6c 6f20 776f 726b 6400
0000 0000 0000 0000 3400 0000 0000 0001
0000 0000 0000 0000 0000 0000 0000 0000
1400 0000 0000 0000 017a 5200 0178 1001
100c 0708 9001 0000 2400 0000 1c00 0000
80ff ffff ffff ffff 3400 0000 0000 0000
0041 0e10 8602 430d 0600 0000 0000 0000
2300 0000 0100 002d 0b00 0000 0200 0015
0000 0000 0100 0006 0100 0000 0f01 0000
0000 0000 0000 0000 0700 0000 0100 0000
0000 0000 0000 0000 005f 6d61 696e 005f
7072 696e 7466 0000

生成可执行文件

输入命令:

1
clang compileTest.o -o compileTest

输出可执行文件compileTest,执行如下:

1
./compileTest
1
2
输出
Hello world

Example传送门

https://github.com/blackteachinese/compile_principle_introduce/tree/master

编译器 (compiler)

编译器是一个程序,可以阅读以某一种语言(源语言)编写的程序,并把该程序翻译成为一个等价的、用另一种语言(目标语言)编写的程序。

解释器 (interpreter)

解释器不通过翻译的方式生成目标程序,直接利用用户提供的输入执行源程序中指定的操作。

编译器和解析器的区别

编译型语言在编译过程中生成目标平台的指令,解释型语言在运行过程中才生成目标平台的指令。
虚拟机的任务是在运行过程中将中间代码翻译成目标平台的指令。

语言处理流程

预处理器 (preprocessor)
编译器 (compiler)
汇编器 (assembler)
链接器 (linker)/加载器 (loader)

相关名词

  • 词法单元 (token)
  • 抽象语法树 (AST - abstract syntax tree)
  • 类型检查 (type checking)
  • 三地址代码 (three-address code)
  • 中间表示形式 (IR - intermediate representation)
    • 中间语言 (IL - intermediate language)
    • 字节码 (bytecode)

WebRTC源码如何编译iOS使用的Framework

发表于 2017-04-18 | 分类于 webrtc | 阅读次数

webRTC简介

编译工具准备

  • depot_tools
    • depot_tools是Chromium 和 Chromium OS管理代码拉取的脚本包。里面包含了gclient, gcl, git-cl, repo
  • 创建一个build工作目录
    • /webrtc_build
  • 在webrtc_build目录下,使用git下载Chromium的depot_tools工具
1
git clone https://chromium.googlesource.com/chromium/tools/depot_tools.git
  • 添加depot_tools到你OS的环境变量
1
export PATH=`pwd`/depot_tools:"$PATH"

获取源码

在webrtc_build目录下,使用命令行拉取webRTC的源码

1
2
fetch --nohooks webrtc_ios
gclient sync

得到源码:src/…

开一个新的分支

1
git new-branch <branch name>

使用GN生成Ninja工程文件

1
2
3
4
# debug build for 64-bit iOS
gn gen out/ios_64 --args='target_os="ios" target_cpu="arm64"'
# debug build for simulator
gn gen out/ios_sim --args='target_os="ios" target_cpu="x64"'

命令行编译Ninja工程

1
ninja -C out/ios_64 AppRTCMobile

AppRTCMobile是其中一个Target,可以切换别的

生成Xcode工程

1
2
gn gen out/ios --args='target_os="ios" target_cpu="arm64"' --ide=xcode
open -a Xcode.app out/ios/all.xcworkspace

编译webRTC.framwork

  • 查看编译选项
1
./src/tools-webrtc/ios/build_ios_libs.py --help
  • 默认会编译出全部架构的 [‘arm64’, ‘arm’, ‘x64’, ‘x86’] 的 framework.
1
./src/tools-webrtc/ios/build_ios_libs.py
  • 只编译ARM架构
1
./src/tools-webrtc/ios/build_ios_libs.py --arch {'arm64','arm'}

  • xcode8.3的编译错误

如果你的XCode版本是8.3,执行build_ios_libs.py会出现下面的错误

1
error: taking address of packed member 'time_entered' of class or structure 'sctp_state_cookie' may result in an unaligned pointer value [-Werror,-Waddress-of-packed-member]

原因是有两个文件有错需要改正:
third_party/usrsctp/usrsctplib/usrsctplib/netinet/sctp_input.c
third_party/usrsctp/usrsctplib/usrsctplib/netinet/sctp_output.c
具体改正点下面可以查看
sctp_input.c改正
sctp_output.c改正

  • 编译配置文件错误
    你把编译好的WebRTC.framework放到Demo工程跃跃欲试,却可能发现编译不过。
    日了🐶了。。。
    “Could not build module ‘WebRTC’”
    “‘WebRTC/RTCVideoCapturer.h’ file not found”

    太可怕了。。看来编译的脚本又有问题,欲哭无泪。。。
    一通找终于找到生成WebRTC.framework头文件的脚本。

    缺了下面几个头文件链接

1
2
3
"objc/Framework/Headers/WebRTC/RTCCameraVideoCapturer.h",
"objc/Framework/Headers/WebRTC/RTCNSGLVideoView.h",
"objc/Framework/Headers/WebRTC/RTCVideoCapturer.h",

修改BUILD.gn,再使用build_ios_libs.py编译,就可以得到完美的WebRTC.framework。

  • 把编译好的WebRTC.framework导入demo工程,编译demo工程,发现动态库找不到。
1
dyld: Library not loaded: @rpath/WebRTC.framework/WebRTC

在Embedded Binaries +WebRTC.framework即可

最后附上修改后的文件BUILD.gn、sctp_input.c、sctp_output.c
以及编译成功后的webRTC.Framework(arm和All)

git地址:https://github.com/blackteachinese/WebRTCCompileFix

1234
Blacktea

Blacktea

iOS music footbood barcelona 歌手、程序员、爵士乐、巴萨罗那、自由主义者、学生

38 日志
14 分类
35 标签
RSS
GitHub 微博 豆瓣 知乎
© 2017 - 2019 Blacktea
由 Hexo 强力驱动
主题 - NexT.Pisces