Problem
公司的某台测试机启动时间变的非常的慢,甚至达到10秒左右。但是其他大部分手机都没有问题。所以对这个手机进行了一番调查,发现是openUDID这个常用的第三方库拖慢了启动时间,于是做了一些优化,并做个总结。
History
ios5之前,开发者可以直接访问 UDID(unique device identifier)。比较准确
ios5系统,苹果禁止开发者获取UDID。各家公司开始自研相应的替代方案。所以出现了很几种方案。
初衷:Appsfire
为了统一碎片化的状况,研发openUDID。鼓励大家都集成SGOpenUDID,是得不同产品之间可以共享同一个唯一标示符。
通过setPersistent来持久
1 | [slotPB setPersistent:YES]; |
ios10之后,setPersistent
被废弃
pasteboardWithName
的说明:
App pasteboards returned by this method are not persistent, existing only until the app quits. Starting in iOS 10, persistent named pasteboards are deprecated. Instead use a shared container, as described in the overview for the UIPasteboard class.
Research
使用Instrument,对启动慢的设备进行检测,发现几个慢的方法中,都卡在了与openUDID的方法调用中。
主要是pasteboardWithName
和dataForPasteboardType
两个方法中。这个库使用比较普遍,且启动时的模块是在主线程进行,所以减慢了启动速度。
在代码中对两个方法进行swizzling
,之后在系统方法前后统计时长以及调用次数。得出类似以下输出
1 | UIPasteboard pasteboardWithName duration 1.709938 name:org.TTTrackerOpenUDID.slot.84 |
1 | UIPasteboard dataForPasteboardType duration 15.424090 name:org.OpenUDID.slot.42 type:org.OpenUDID |
duration单位ms. dataForPasteboardType的耗时更长。而统计调用次数可以发现,在启动缓慢的设备上该方法调用达到**500多次**
, 而启动正常的总调用次数不到100次。
调用次数非常多,查看openUDID的代码,发现有循环操作:
1 | for (int n=0; n<kOpenUDIDRedundancySlots; n++) { |
其中kOpenUDIDRedundancySlots
的数值是100
设计思路:
- 先从UserDefault尝试读取出UDID, AppID。
- 每个app安装后,创建自己的AppID, 通过固定命名规则,并寻找一个没有被占用的slot,叫做
availableSlotPBid
- 如果slot已经被占用,读取该slot粘贴板上存储的AppID,以及存储的UDID
- 如果有相同AppID的slot,将这个slot叫做
myRedundancySlotPBid
。如果有哪个slot上没有存储uuid,这个slotye 可以复用赋值给availableSlotPBid
。 - 统计已有的每个UDID被使用个数,使用个数最多的,叫做
mostReliableUDID
- 遍历结束后,如果UserDefault里取出的UDID是空的,就使用
mostReliableUDID
。这个也没有就创建新的。之后存到UserDefault里。 - 如果有可用的
availableSlotPBid
, 且不存在相同AppID的slot,则将选的uuid数据也存储到availableSlotPBid
粘贴板上
通过Demo验证,最新的iOS12系统上,
- 通过
pasteboardWithName
创建的粘贴板, app退出重新启动后,创建的粘贴板还在,并且同一Team里的粘贴板也可以共享。 但是不同Team之间不能共享。 - 调用
setPersistent
对结果无影响,即使设置页不能再不同Team间共享。 removePasteboardWithName
是有效的,可以删除Team内的粘贴板。team外的没有影响
从Api的官方描述看,这一变化是从iOS10开是的。
Why
- 随着测试的不断重装,每次卸载重装都会占用一个slot,并保存数据。导致每次启动后需要读取数据的pasteboard越来 越多,越来越慢
- 项目App中的openUDID, 只改了类名,slotKey还是原来库里的
OpenUDID_slot
。后果是如果有其他app也用了openUDID,而且也没有修改slotKey。其他app的安装也会增加已用粘贴板的数量。装的app阅读,启动越慢。
Case
- 自己App重新启动100次会怎样。
占满了100个slot之后,会复用具有相同AppID的slot - 自己App重装100次后会怎么处理?
UserDefault被删除,所以每次启动新建一个AppID。然后获取mostReliableUDID
,保存到userDefault. 100次之后后去不到可用slot,所以每次只会保存到UserDefault - 自己的App新装时,发现100个slot都被其他公司app占用了怎么处理?
这种情况和自己重装是表现是一样的。
Solution
目标:
- 保留UserDefault中的UDID
- 是众多粘贴板中只有一个跟内存中数据一致,其他的释放掉。
不会影响现有的UDID获取。也不影响mostAlibableUDID
的获取。缺陷是其他某App打开后,不论升级还是新装,都有availableSlot
可以用来保存
步骤:
修改slotKey.在升级不会改变原 openUDID获取值的情况下,尝试修改slotKey。保证只有自己的app重装才会自我影响,降低几率- 获取粘贴板数据时,记录每个不同的UDID所在的第一个粘贴板slotName
- 增加一个阈值判断,当前使用的UDID,所使用的slot占用一定数目之后进行进行清理。
- 开始清理时,从Userdefault中读取使用的UDID, 之后查找其所在的第一个slotName. 找到后保留该slot,删除其他同UDID的粘贴板。
- 搜集项目中openUDID的不同SlotKey组,针对每组做一次冗余清理。
目前发现的OpenUDID
,TTTrackerOpenUDID
,KATrackerOpenUDID
,BUOpenUDID
对不同的UDID暂不处理,可能会影响其他同Team的App,虽然这种情况很少,代码中叫做isCompromised
Test
- 升级优化后,app自身的udid不会变
- 其他sdk的udid也不会变
Risk
- 粘贴板的名称是Team空间的。如果公司其他App也使用的是未改过名的openUDID, 那其他app所用到的粘贴板也会被清除。如果这个app只修改的关键常量,没有改逻辑,那么对该App其实也是有加速启动的效果。但如果该app修改了逻辑实现,那影响结果就不可预期了
- 同理,如果第三方sdk只修改了关键常量,那么是有加速效果,但如果第三方sdk中修改了实现逻辑,那结果也是不可预期。
Refer
https://techcrunch.com/2011/09/01/appsfire-announces-open-source-udid-replacement-for-ios-openudid/