金氧

iOS开发 - 制作同时支持armv7,armv7s,arm64,i386,x86_64的静态库.a

一、概要

平时项目开发中,可能使用第三方提供的静态库.a,如果.a提供方技术不成熟,使用的时候就会出现问题,例如:

在真机上编译报错:No architectures to compile for (ONLY_ACTIVE_ARCH=YES, active arch=x86_64, VALID_ARCHS=i386).

在模拟器上编译报错:No architectures to compile for (ONLY_ACTIVE_ARCH=YES, active arch=armv7s, VALID_ARCHS=armv7 armv6).

要解决以上问题,就要了解一下Apple移动设备处理器指令集相关的一些细节知识。

二、几个重要概念

1、ARM

ARM处理器,特点是体积小、低功耗、低成本、高性能,所以几乎所有手机处理器都基于ARM,在嵌入式系统中应用广泛。

2、ARM处理器指令集

armv6|armv7|armv7s|arm64都是ARM处理器的指令集,这些指令集都是向下兼容的,例如armv7指令集兼容armv6,只是使用armv6的时候无法发挥出其性能,无法使用armv7的新特性,从而会导致程序执行效率没那么高。

还有两个我们也很熟悉的指令集:i386|x86_64 是Mac处理器的指令集,i386是针对intel通用微处理器32架构的。x86_64是针对x86架构的64位处理器。所以当使用iOS模拟器的时候会遇到i386|x86_64,ios模拟器没有arm指令集。

3、目前iOS移动设备指令集

arm64:iPhone5S| iPad Air| iPad mini2(iPad mini with Retina Display)

armv7s:iPhone5|iPhone5C|iPad4(iPad with Retina Display)

armv7:iPhone3GS|iPhone4|iPhone4S|iPad|iPad2|iPad3(The New iPad)|iPad mini|iPod Touch 3G|iPod Touch4

armv6 设备: iPhone, iPhone2, iPhone3G, 第一代、第二代 iPod Touch(一般不需要去支持)

4、Xcode中指令集相关选项(Build Setting中)

(1)Architectures

Space-separated list of identifiers. Specifies the architectures (ABIs, processor models) to which the binary is targeted. When this build setting specifies more than one architecture, the generated binary may contain object code for each of the specified architectures.

指定工程被编译成可支持哪些指令集类型,而支持的指令集越多,就会编译出包含多个指令集代码的数据包,对应生成二进制包就越大,也就是ipa包会变大。

(2)Valid Architectures

Space-separated list of identifiers. Specifies the architectures for which the binary may be built. During the build, this list is intersected with the value of ARCHS build setting; the resulting list specifies the architectures the binary can run on. If the resulting architecture list is empty, the target generates no binary.

限制可能被支持的指令集的范围,也就是Xcode编译出来的二进制包类型最终从这些类型产生,而编译出哪种指令集的包,将由Architectures与Valid Architectures(因此这个不能为空)的交集来确定,例如:

比如,你的Valid Architectures设置的支持arm指令集版本有:armv7/armv7s/arm64,对应的Architectures设置的支持arm指令集版本有:armv7s,这时Xcode只会生成一个armv7s指令集的二进制包。

再比如:将Architectures支持arm指令集设置为:armv7,armv7s,对应的Valid Architectures的支持的指令集设置为:armv7s,arm64,那么此时,XCode生成二进制包所支持的指令集只有armv7s

在Xcode6.1.1里的 Valid Architectures 设置里, 默认为 Standard architectures(armv7,arm64),如果你想改的话,自己在other中更改。

原因解释如下:

使用 standard architectures (including 64-bit)(armv7,arm64) 参数,则打的包里面有32位、64位两份代码,在iPhone5s( iPhone5s的cpu是64位的 )下,会首选运行64位代码包, 其余的iPhone( 其余iPhone都是32位的,iPhone5c也是32位 ),只能运行32位包,但是包含两种架构的代码包,只有运行在ios6,ios7系统上。

这也就是说,这种打包方式,对手机几乎没要求,但是对系统有要求,即ios6以上。

而使用 standard architectures (armv7,armv7s) 参数, 则打的包里只有32位代码, iPhone5s的cpu是64位,但是可以兼容32位代码,即可以运行32位代码。但是这会降低iPhone5s的性能。 其余的iPhone对32位代码包更没问题, 而32位代码包,对系统也几乎也没什么限制。

所以总结如下:

要发挥iPhone5s的64位性能,就要包含64位包,那么系统最低要求为ios6。 如果要兼容ios5以及更低的系统,只能打32位的包,系统都能通用,但是会丧失iPhone5s的性能。

(3)Build Active Architecture Only

指定是否只对当前连接设备所支持的指令集编译

当其值设置为YES,这个属性设置为yes,是为了debug的时候编译速度更快,它只编译当前的architecture版本,而设置为no时,会编译所有的版本。 编译出的版本是向下兼容的,连接的设备的指令集匹配是由高到低(arm64 > armv7s > armv7)依次匹配的。比如你设置此值为yes,用iphone4编译出来的是armv7版本的,iphone5也可以运行,但是armv6的设备就不能运行。 所以,一般debug的时候可以选择设置为yes,release的时候要改为no,以适应不同设备。

1)

Architectures: armv7, armv7s, arm64

ValidArchitectures: armv6, armv7s, arm64

生成二进制包支持的指令集: arm64

2)

Architectures: armv6, armv7, armv7s

Valid Architectures: armv6, armv7s, arm64

生成二进制包支持的指令集: armv7s

3)

Architectures: armv7, armv7s, arm64

Valid Architectures: armv7,armv7s

这种情况是报错的,因为允许使用指令集中没有arm64。

注:如果你对ipa安装包大小有要求,可以减少安装包的指令集的数量,这样就可以尽可能的减少包的大小。当然这样做会使部分设备出现性能损失,当然在普通应用中这点体现几乎感觉不到,至少不会威胁到用户体检。

三、制作静态库.a是指令集选择

现在回归到正题,如何制作一个“没有问题”的.a静态库,通过以上信息了解到,当我们做App的时候,为了追求高效率,并且减小包的大小,Build Active Architecture Only设置成YES,Architectures按Xcode默认配置就可以,因为arm64向前兼容。但制作.a静态库就不同了,因为要保证兼容性,包括不同iOS设备以及模拟器运行不出错,所以结合当前行业情况,要做到最大的兼容性。

ValidArchitectures设置为:armv7|armv7s|arm64|i386|x86_64

Architectures设置不变(或根据你需要): armv7|arm64

然后分别选择iOS设备和模拟器进行编译,最后找到相关的.a进行合包,使用lipo -create 真机库.a的路径 模拟器库.a的的路径 -output 合成库的名字.a(详情可以参考http://blog.csdn.NET/lizhongfu2013/article/details/12648633)

这样就制作了一个通用的静态库.a

.a 与 FrameWork 合并技巧

查看.a 和 FrameWork 的适配机型

lipo -create Debug-iphoneos/libMJRefresh.a Debug-iphonesimulator/libMJRefresh.a -output libMJRefresh.a

lipo -create /Users/harvey/Library/Developer/Xcode/DerivedData/FMDB-ctegiztcjikewoeprxxtmryzetfa/Build/Products/Release-iphoneos/libFMDB.a /Users/harvey/Library/Developer/Xcode/DerivedData/FMDB-ctegiztcjikewoeprxxtmryzetfa/Build/Products/Release-iphonesimulator/libFMDB.a -output /Users/harvey/Desktop/libFMDB.a

lipo -create /Users/harvey/Library/Developer/Xcode/DerivedData/FMDB-clvayfrjgytqrbdkyqrtcjkxfeuz/Build/Products/Release-iphonesimulator/FMDB.framework/FMDB /Users/harvey/Library/Developer/Xcode/DerivedData/FMDB-clvayfrjgytqrbdkyqrtcjkxfeuz/Build/Products/Release-iphoneos/Release-iphoneos.framework/FMDB -output /Users/harvey/Desktop/FMDB

Architectures in the fat file: /Users/kunkkaqi/Desktop/TestFrameWork.framework/TestFrameWork.lipo are: i386 armv7 armv7s x86_64 arm64 

不越狱iOS自动抢红包

春节临近,能用红包解决的,咱尽量不用语言表达。但是只恨手速太慢啊!那么问题来了,如果可以自动抢就完美了。辣么这肯定难不倒程序猿。如果你的手机已经越狱了,那么直接在Cydia市场搜索Tweak就可以了。如果没有越狱,又非常不想越狱或者根本就不会越狱该肿么办?办法也是有的,下面记录一下过程,虽然复杂了一些,但是亲测好用!

注意:本篇文章只是记录过程,并不深入原理,iOS 逆向比较复杂,这里只是初探,大家可以一起讨论。

Instagram 是如何做持续部署的

他们一天部署后台代码30到50次,大部分情况不用人为干预。直接部署master branch的代码。文章描述了他们从非常手动的快糙猛的部署代码的流程,进化到几乎是全自动的持续部署的过程。

如果哪个工程师成为了老鼠屎,commit了有bug的代码,导致CI的测试挂掉挂掉、或者canary机器上有太多错误,他们oncall的工程师还是得手动地revert这个commit并把这件事及时通知团队其他成员。软件工程真是团体运动,每个人都要力争成为一个好队友。

有效的、全面的、快速的测试,是实践持续部署的基础。

2016 总结 - 工作十小时,休息五分钟

工作与项目

今年的工作内容主要是负责移动端,iOS、Android、H5等,甚至微信服务号方面的工作,会实际参与iOS的打码(项目实际使用的objective-c)。

今年的项目主要是数字4S店一期,一期项目主要做D端的APP应用(iOS和Android),主要功能是方便处理来自4S店微信服务号的业务。下面来段专业的介绍:是一款装载于智能手机上的移动化应用产品,将基于互联网思维,在互联网、大数据、云计 算等科技不断发展的背景下,为经销商售前售后人员,整合日常工作中所使用到SGM业务系统的主要功能。该产品的诞生,将解决业务人员依赖台式电脑的低效工作方式,取而代之是在App上随时获取客户信息、便捷与客 户进行联系、快速记录保存有效内容;并改变客户信息 收集的模式(经销商输入 -> 客户输入/自动采集)。

这是公司接触通用汽车的第一个项目,项目组对于客户的沟通和工作方式前期不是很适应,也有一方面是人员配置和其他因素的问题,所以前期非常吃力。这也是我参与的第一个移动端的项目,所以我非常不希望这个项目失败,在前期看到项目进度不容乐观的情况下,主动参与了不是我负责的内容,比如原型制作、需求确认的工作。总之,根本不管是啥方面的活,只要有力就出力,冲着问题,抓紧解决,继续前行。项目期间有时会很心急,估计也得罪了一些人,但是自己其实只是对事不对人。也没办法奢望得到原谅,只能在以后尽量改掉,少说。功夫不负有心人,终于项目在几个通宵后,顺利上线。这个项目整整持续了大半年,由于前期浪费了很多时间,所以工期很紧,基本是天天加班,简直是工作十小时,休息五分钟。经历完这个项目后,对我正面的成长基本没有,但是有很多反面的收获,下面会分成类一一记录下来。

Chrome插件:网易云音乐一键全赞

最近帮朋友做了一个Chrome插件,方便做一些批量的动作。对于Chrome插件有兴趣的同学可以看一下。

需求

网易云音乐音乐详情页底部的评论区,一键自动全赞,然后翻页继续全赞直至最后一页。

分析

那么我们以薛之谦的“演员”这首歌页面为例仔细分析一下。首先看页面的URL http://music.163.com/#/song?id=32507038,页面用的锚点,主url music.163.com/ 一直没变过。那么再看页面底部的评论区域,评论区域分为两部分,第一部分是精彩评论,第二部分是最新评论。最新评论下面有翻页按钮,点击第二页后,可以翻页,第二页以后的页面上部没有精彩评论。

实现

由于代码中有注释而且非常短,就不一一解释了,直接上代码吧。
关于Chrome插件的制作可以看着里:如何从零开始写一个 Chrome 扩展?

manifest.json

{
  "name": "云音乐",
  "description": "云音乐",
  "version": "1.0",
  "permissions": [
    "tabs", "http://music.163.com/*"
  ],
  "content_scripts": [
    {
      "matches": ["http://music.163.com/*"],
      "js": ["jquery.min.js", "myscript.js"]
    }
  ],
  "manifest_version": 2
}

myscript.js

window.onhashchange = handleHashchange;
window.onerror = handleError;

var time = new Date().getTime();
var array = new Array();

$(window.frames["contentFrame"].document).ready(function(){
  addButton();
});

function handleHashchange() {
  //console.log("onhashchange");
  location.reload();
}

function handleError() {
  console.log("onerror");
}

function addButton() {
  array = new Array();
  var iptarea = $(window.frames["contentFrame"].document).find(".iptarea");
  //var button = $('<input id="like" class="btn u-btn u-btn-1" type="button" value="全部点赞" />');
  var button = $('<a href="javascript:void(0)" class="btn u-btn u-btn-1 j-flag" >全赞</a>');

  button.click( function () {
    //绑定事件监听评论区域的数据改变
    var cmmts = $(window.frames["contentFrame"].document).find(".cmmts");
    cmmts.bind('DOMNodeInserted', function(e) {
      //console.log('element now contains: ' + $(e.target).html());
      //console.log('element now length:%d contains:%s ', $(e.target).parent().children().length, $(e.target).html());
      //console.log('element now length: ' + $(e.target).parent().children().length);
      //判断是否满一页,然后全部点一页
      //if ($(e.target).parent().children().length == 20) {
      //  like();
      //}
      item($(e.target));
    });

    //当前页一键全赞
    like();
  });
  var input = $('<div>频率<input id="interval" class="" type="text" value="0" />s</div>');
  iptarea.after(input);
  iptarea.after(button);
}

function like() {
  var cmmts = $(window.frames["contentFrame"].document).find(".cmmts").children(".itm");
  //console.log("cmmts.length : %d", cmmts.length);
  //console.log("cmmts.html : %s", cmmts.html());
  for (var i=0;i<cmmts.length;i++){
    var itm = cmmts.eq(i);
    item(itm);
  }
  next();
}

function item(itm) {
    time = new Date().getTime();
    var rp = itm.find(".cntwrap .rp a");
    //console.log(rp.html());
    //console.log(rp.attr("data-type"));
    if(rp.attr("data-type") == "like"){
      //console.log(rp.html());
      var zan = rp.children();
      //console.log(zan.html());
      zan.click();
    }
}

function next() {
  var upage = $(window.frames["contentFrame"].document).find(".u-page").children();
  //console.log("upage.length : %d", upage.length);
  if (upage.length > 0) {
    var znxt = upage.eq(upage.length - 1);
    if(!znxt.hasClass('js-disabled')){
      znxt.html("<span>下一页</span>");
      //console.log("znxt.html : %s", znxt.html());
      znxt.children().click();
    }
  }
  var interval = $(window.frames["contentFrame"].document).find("#interval");
  interval = interval.val() - 0;
  //console.log('interval: %d', interval);
  setTimeout("next()", interval*1000);
}

张小龙首次公开解读小程序:1月9号上线

只能说偏执的张小龙真的很厉害。

小程序定义:

  1. 无需下载,用完即走
  2. 没有入口,没有排序,没有推荐
  3. 不能订阅
  4. 不能通知
  5. 不能分享到朋友圈,可以分享到聊天。协作式任务,小程序页,活的页面
  6. 不能做游戏
  7. 能被搜索到,但是是有限的搜索能力
  8. 小程序和公众号没有关系,相互独立,但有个关联,就是可以互相跳转
  9. 有周边推荐和提示,有哪些小程序可以用。

精简一点的定义:这也不行,那也不行 =。=|

微服务的好处和陷阱

微服务架构设计代表了一种架构设计思想,配合现在的容器技术(如 Docker),可在软件开发流程、部署、服务维护等各方面产生效率提升。

但不一定所有的业务场景都适合微服务,有时候非常简单的业务场景下,微服务反而会降低效率。什么是微服务,其特性,好处及陷阱,是本文要讨论的内容。

一、什么是微服务

微服务是一种软件架构风格,它是以专注于单一责任与功能的小型功能区块为基础,利用模组化的方式组合出复杂的大型应用程序,各功能区块使用与语言无关的 API(例如 REST)集相互通讯,且每个服务可以被单独部署,它具备以下三个核心特点:

  1. 微服务为大型系统而生。随着业务的快速增长,会带来系统流量压力和复杂度的上升,系统的可维护性和可扩展性成为架构设计的主要考虑因素,微服务架构设计理念通过小而美的业务拆分,通过分而自治来实现复杂系统的优雅设计实现。
  2. 微服务架构是面向结果的。 微服务架构设计风格的产生并非是出于学术或为标准而标准的设计,而是在软件架构设计领域不断演进过程中,面对实际工业界所遇到问题,而出现的面向解决实际问题的架构设计风格。
  3. 专注于服务的可替代性来设计。 微服务架构设计风格核心要解决的问题之一便是如何便利地在大型系统中进行系统组件的维护和替换,且不影响整体系统稳定性。

Rexxar:豆瓣对混合开发的思考

前段时间,豆瓣将自己的混合开发框架Rexxar开源了。豆瓣可以说是在国内对HTML5实践最早的一批公司,早在2013年的时候他们就应用了当时还显得超前的Web Component概念开发了CardKit移动UI框架。在移动开发上,豆瓣也采用了混合开发的模式,Rexxar就是他们实践和思考的结晶。我采访了Rexxar的主要开发者之一郭麟,看看他们对混合开发的思考。

豆瓣使用混合开发的原因,是因为他们需要同时提供iOS、Android、移动Web版本的页面,相对于同时开发三个版本,使用混合开发显然可以在代码重用、开发成本和效率方面有很大的优势,在权衡性能体验的前提下,使用混合开发是非常现实的选择。

Rexxar是什么

Rexxar是一个针对移动端的混合开发框架。支持Android、iOS和移动Web。

Rexxar主要由三部分组成:

Rexxar-web:前端代码库。包括一套打包、调试、发布工具,以及公共前端组件,和对Rexxar Container实现的Widget的调用。

Rexxar-Router:路由表,将每个页面分配一个服务器端链接,以及一个本地URI,通过路由表来访问页面。

Rexxar-container:增强版WebView,封装了一些Native API支持,包括OAuth授权、图片缓存等。

Rexxar目前已经开源,并且分为3个项目,你可以只使用其中某个项目来开发对应平台的代码:

https://github.com/douban/rexxar-web
https://github.com/douban/rexxar-android
https://github.com/douban/rexxar-ios

CLOSE_WAIT 问题分析

问题场景

服务器出现大量 TCP 连接状态为 CLOSE_WAIT,将系统资源耗尽,导致业务处理失败。

处理方式

重启导致问题的进程,释放 TCP 连接后服务恢复正常。

调整服务器网络参数:

  • net.ipv4.tcp_keepalive_time=600,tcp 连接空闲 10 分钟后发送探测包,默认是 7200 秒(两小时)
  • net.ipv4.tcp_keepalive_intvl=15,每次探测包间隔 15 秒,默认是 75 秒
  • net.ipv4.tcp_keepalive_probes=5,一共发送 5 次探测包,默认是 9 次

让内核尽快检测出空闲连接并释放。

分析

服务器环境是 Java、Tomcat。服务端在收到客户端 FIN 包后进入 CLOSE_WAIT,但服务端没有发送 FIN 包,具体原因没有查明。猜测的原因是

  • 服务端的业务处理阻塞,导致发送不了 FIN 包
  • 网络问题导致依赖的基础库 bug

附:TCP 连接状态图

8b538e707e964084a2161844efb023ea-tcp.jpg

Swift 3:sizeof移进MemoryLayout

sizeof这个方法名直接取自C语言,但是实际上LLVM中并没有一个函数叫sizeof。而且sizeof的使用范围很窄,不像map,filter这种经常会全局用到。所以重新定义了一个结构体 MemoryLayout来实现原来sizeof的功能,使用上也有变化。

主要有两种方式,一种是直接通过泛型参数从静态变量获取:

let stringSize = MemoryLayout<String>.size

也可以通过调用静态方法获取

let stringValue = "A"
let varSize = MemoryLayout.size(ofValue: stringValue)

两者都会得到正确的结果。

除了sizeof, MemoryLayout还可以获取stride, alignment。

Xcode 8:在Active Compilation Conditions中自定义环境变量

在Xcode 7我们在 OTHER_SWIFT_FLAGS中配置环境变量。但是有一个不爽的地方就是需要在自定义的变量前增加“-D”后才能使用。

#if MYFLAG
// 逻辑判断
#endif

现在在Xcode 8中新增了一个SWIFT_ACTIVE_COMPILATION_CONDITIONS选项,现在直接在里面添加就可以啦!在代码中的使用逻辑和之前一样。

兼容iOS 10:配置获取隐私数据权限声明

iOS 10的一大变化是更强的隐私数据保护。在文档中是这么描述的:

You must statically declare your app’s intended use of protected data classes by including the appropriate purpose string keys in your Info.plist file.

简单的说访问用户数据都需要现在Info.plist中声明,否则会crash。
这些用户数据包括:

Contacts, Calendar, Reminders, Photos, Bluetooth Sharing, Microphone, Camera, Location, Health, HomeKit, Media Library, Motion, CallKit, Speech Recognition, SiriKit, TV Provider.

10之前只需要获取位置时配置,现在更严格了,比如需要调用相册访问权限,也需要在Info.plist中配置privacy。
好在这些key的名字在Xcode 8中已经有了自动补全。添加一个属性,输入Privacy后就会出现自动提示,后面填的string会在弹出用户允许时展示在描述里。描述一定要填写,如果描述空着提交AppStore时会拒绝。

Swift中的可选类型(Optional Type)

Optional Type总览

什么是optional? Swift中声明的一个变量时, 默认情况下它是non-optional的, 即必须赋予这个变量一个非空的值. 如果给non-optional类型的变量赋值nil, 编译器就会报错。

var string1: String = "This is string1" // OK
string1 = nil  // Nil cannot be assigned to type 'String'
- Swift中, 当声明一个类的属性时, 属性默认也是non-optional的
swift
class MyClass {
    var name: String = "Lv"
    var age: String  // Class 'MyClass' has no initializers
}

对于之前使用Objective-C的同学来说, 这样的错误可能会让你们有些惊讶, 因为在Objective-C中, 当把nil赋值给一个变量或者声明一个没有初始值的属性时, 编译器都不会报错.

NSString *string1 = @"This is string1";
string1 = nil;
class MyClass {
    NSString *name = @"Lv"
    NSString *age;
}

然而并不意味着在Swift中不能声明一个没有初始值的属性. Swift中引入了可选类型(optional type)来解决这一问题. 它的定义是通过在类型生命后加加一个?操作符完成的.

class MyClass {
    var name: String?  // OK
    var age: String?  // OK
}

Xcode真机调试报错:The Application Could Not Be Verified.

今天真机调试的时候遇到这个错误:

The application could not be verified.

这还是第一次遇到,应该是手机上的app的证书跟现在的证书不一致导致。

解决方法有两个

xcode中切换证书:

你手机上的app用的是哪个证书,你现在还用那个证书运行。

删除手机上的app

直接删除手机上的app,再运行就可以啦!

Swift中被忽略的@noescape

这里需要先介绍一下escape的概念。当一个闭包当做一个参数传进函数里,这个闭包是在这个函数执行完后执行的,这个时候我们就说这个闭包从函数逃出来了(escape)。这种场景很常见,比如我们进行一个异步的请求,请求时会传入一个handler,比如当请求成功后执行达到回调的目的。

众所周知swift的内存管理是引用计数。闭包里用到的数据都需要捕捉到闭包里,保证闭包执行时这些数据不会被释放还在内存里。Xcode为了让我们意识到闭包里用到的对象其实已经被retain了,就要求我们访问当前属性时显示声明self。
这个时候如果新手就很容易犯引用循环的错误。闭包retain了self,self如果又持有retain了闭包。最后就谁都释放不了,内存就泄露了。

这是swift中默认闭包的使用场景。
但是这里是有另外一种可能,假设有一个闭包是传入用于sort用的,或者比如作为map参数的闭包。当这行代码执行完成时,这个闭包也就使用完了,之后不会再被执行。这个情况下,闭包就不必再持有里面用到的对象。
这就是非escape闭包。

swift里针对非escape用@noescape表示。
比如map函数就使用了:

func map<T>(@noescape transform: (Self.Generator.Element) throws -> T) rethrows -> [T]

这样标记之后能看到的好处就是这个闭包里如果再使用self的属性不需要加self.了。对于编译器而言,在知道是noescape闭包后可以进行一些内存的优化。