Objective-C 使用引用计数作为 iPhone 应用的内存管理方案,引用计数相比 GC 更适用于内存不太充裕的场景,只需要收集与对象关联的局部信息来决定是否回收对象,而 GC 为了明确可达性,需要全局的对象信息。引用计数固然有其优越性,但也正是因为缺乏对全局对象信息的把控,导致 Objective-C 无法自动销毁陷入循环引用的对象。虽然 Objective-C 通过引入弱引用技术,让开发者可以尽可能地规避这个问题,但在引用层级过深,引用路径不那么直观的情况下,即使是经验丰富的工程师,也无法百分百保证产出的代码不存在循环引用。
打造自己的内存泄漏检测工具
我们在编写日常业务代码时,或多或少都会引入一些导致内存泄漏的代码,而这种行为又很难被监控,这就导致应用内存泄漏的口子越开越大,直接影响到线上应用的稳定性。虽然 Xcode 的 Instrucment 提供了 Leaks 和 Allocations 工具让我们能精准地定位内存泄漏问题,但是这种方式相对比较繁琐,需要开发人员频繁地去操作应用界面,以触发泄漏场景,所以 Leaks 和 Allocations 更加适合定期组织的大排查,作为监测手段,则显得笨重。
计算 +load 方法的耗时
在 pre-main 时期,objc 会向 dyld 注册一个 init 回调,当 dyld 将要执行载入 image 的 initializers 流程时 (依赖的所有 image 已走完 initializers 流程时),init 回调被触发,在这个回调中,objc 会按照父类-子类-分类顺序调用 +load 方法。因为 +load 方法执行地足够早,并且只执行一次,所以我们通常会在这个方法中进行 method swizzling 或者自注册操作。也正是因为 +load 方法调用时间点的特殊性,导致此方法的耗时监测较为困难,而如何使监测代码先于 +load 方法执行成为解决此问题的关键点。
Ruby Singleton Class 与 Objective-C KVO
Ruby 是解释强类型动态语言,Objective-C 是编译弱类型(动态 & 静态)语言,两者看似没什么关联,但是实际上可以说是师出同门,它们很大程度上继承了 Smalltalk 的关键特性,所以很多设计理念是共通的,比如 Ruby 和 Objective-C 拥有相似的消息传递机制 (dynamic message dispatch)、对象模型 (object model —— object class metaclass),并且都提供极其强大的运行时特性以及支撑运行时特性所需的接口等。 Ruby 和 Objective-C 的异同其实有挺多可以说的,但是本文不会过多地去探讨,这里只是窥探下 singleton class 和 KVO 两个技术点间的联系。
Objective-C weak 弱引用实现
在编写代码时,弱引用一般以下面两种形式出现:
- 使用
weak
关键字修饰属性时 - 使用
__weak
关键字修饰变量时
这里我们可以统一把第一种形式看作使用 __weak
关键字修饰成员变量。
__weak
修饰的变量有两大特点:
- 不会增加指向对象的引用计数 (规避循环引用)
- 指向对象释放后,变量会自动置 nil (规避野指针访问错误)
下文会从源码的视角分析 runtime 是如何实现弱引用自动置 nil 的。
CocoaPods,GitLab CI 与组件自动化发布
在实施业务组件化后,大部分没有组件化工具链支撑的团队一般都会遇到组件发布效率问题,如果遇到多个特性一起上线时,发布的组件数量可能达到十几二十个,手动发布这些组件的话,费时费力,非常影响开发体验。同样的,这个问题也一直困扰着我们团队,虽然后期我们通过 CI 简化了单个组件的发布,组件负责人只需要根据 Podfile 中的版本提交相应 tag 即可触发发布动作,但是 CI 并没有解决多个关联组件发布的前后顺序问题————如果下层组件还未发布就发布上层组件,此组件的 CI 很可能会因为缺少下层组件的某些接口而执行失败。
基于 CocoaPods 的组件二进制化实践
火掌柜 iOS 客户端经过近两年的组件化推进,组件数量已经颇具规模,达到了近 100 个。随着组件数量和代码量越来越多,主工程的打包时间从最初的十几分钟,增加到了现在的四十分钟左右。依赖组件较多,改动相对频繁的上层业务组件,其发布时间也较为漫长。编译时长的困扰,已经明显影响了日常开发体验,同时也造成 CI pipeline 执行时间过长,在 runner 资源匮乏的情况下,不利于内部 CI 的推广。当前时间节点下,如何减少编译时长,已经成为开发团队较为迫切的需求。
CocoaPods 如何加载插件
CocoaPods 为开发者提供了插件注册功能,可以使用 pod plugins create NAME
命令创建插件,并在 Podfile 中通过 plugin 'NAME'
语句引入插件。虽然在一般情况下很少使用这个功能,但在某些场景下,利用插件能比较方便快捷地解决问题,比如清除 input
,output
文件、创建 Podfile DSL 等。
Git Commit 触发 Jenkins Job
火展柜团队目前使用 Jenkins 做持续集成,在提测 / 预发阶段,开发人员需要频繁地在 Jenkins 平台上进行如下操作以构建测试包:
- 在团队的 View 下创建 Job ,一般以某个已存在 Job 为模版,这个模版已经设置好打包仓库 url 和常用配置
- 更改需要打包的代码分支
- 更改钉钉 token,构建结束后,需要将结果发送至测试钉钉群组
- 点击构建项目
在创建 Job 后,后续构建则只需要以下两步:
- 更新 Podfile.lock 并提交至远程仓库
- 点击构建项目