金氧

不越狱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闭包后可以进行一些内存的优化。

Swift 3:inout 关键字位置的变化

inout 关键字可以用于将参数修饰为可修改,并且将修改会回传。这次 Swift 3 中对这个关键字也做了一些修改,咱们一起来kan'k吧。

在 Swift 中,如果我们希望一个函数可以修改它的参数变量的值,我们可以使用 inout 关键词。这个相当于编程语言概念中所谓的传址调用。 具体代码中就是这样的例子:

func foo(inout x:  Int) {

    x = 2

}

var x = 5
foo(&x)
print(x) // 2

在这个例子中, foo 函数的参数 x, 使用了 inout 修饰,这样我们在后面的调用中,将变量 x 传递给这个函数后,这个变量中的值也会被这个函数内部改变。 这个就是 inout 的基本作用了。

在 Swift 3.0 中,有一个 SE-0031 的提案,就是改变 inout 这个关键词的位置的。

当前的语法中,inout 的位置是在参数标签的位置上,也就是这个例子中的:

func foo(inout x:  Int)

inout 关键字位于参数标签 x 的前面。 而 Swift 3.0 以后,这个关键词的位置就会在参数类型前面了:

func foo(x: inout Int)

这个提案中也解释了这样做的好处。首先这样做之后参数标签就不和修饰关键字发生混淆,比如避免了这样的情况:

func foo(inOut x:  Int)
func foo(inout x:  Int)

第一行的 inout 的大小写错误,所以它不是关键字。但这时编译器不会报错,因为 inOut 这时候作为了外部参数标签(Swift 的参数标签分为外部标签和内部标签,这个例子中外部标签是 inOut,所以我们在外部调用这个函数的时候就是这个语法 foo(inOut:),而函数内部还是用内部标签 x 来引用这个参数)。

除了避免混淆之外,inout 的位置移动后还有一点需要大家注意的。按照提案中的意思,就是以前版本的遗留代码中的 inout,很可能会变成参数标签,也就是这样:

func foo(inout x:  Int)

比如之前我们的 foo 函数这样定义,但 Swift 3.0 之后, 这里的 inout 并不是关键字,而变成这个参数的外部标签了。所以这一点还是很值得注意的。

最后提案中还说了一句,这样修改后能够更好的和 Rust 语言的模式相匹配。并且说在后续的版本中会更多的引入到 Swift 语言中。

总体来说呢,这个改动不算大,但多多少少会对我们已有的代码造成一些影响。 inout 这个关键字大家应该多少都会用到过。所以在 Swift 3.0 更新后,也需要注意下这个问题。