iOS15线上问题总结
iOS15升级可以关注IT支架,获取最新的下载配置文件。以下下载链接从IT之家获取。
iOS/iPadOS升级文件:https://down.ruanmei.com/upload/iOS_iPadOS_15_Beta_Profile.mobileconfig
macOS升级文件:https://down.ruanmei.com/upload/macOSDeveloperBetaAccessUtility_121.dmg
watchOS 升级文件:https://down.ruanmei.com/upload/watchOS_8_Beta_Profile.mobileconfig
在浏览器中打开并下载,下载成功后系统会自动提示安装。
RestKit导致的崩溃问题解决
结论
UIImage和UIColor的RKObjectMapping占用内存太大。
分析过程
6.8号早上,就收到用户反馈
用户ID:262**21表示手机和平板更新了系统ios15,app一直打不开,APP也是刚下载的,最新版本 其他APP正常就是的不行 手机是iPhone xs max 平板是iPad air 3 辛苦核实
收到反馈之后,立即开始找iOS15系统的机子,下载Xcode13beta,一切就绪之后开始定位,分析崩溃日志,真机调试(后发现模拟器也能必现)。
崩溃日志是在组单接口的初始化上面
/usr/bin/atos -o **.app.dSYM/.app.dSYM/Contents/Resources/DWARF/** -arch arm64 -l 0x100df8000 0x0000000100e02bfc
[Y**rInitRequest responseDescriptor]
是内存暴涨导致的
模拟器调试崩溃堆栈,和接口定义也有关系
最后基本定位在,接口引起的内存暴涨,而且是在 [H****Request initialize]
中执行的代码有问题,通过对关键行前后对比内存占比的方式,来过滤下,哪些接口有问题。
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
@implementation UIDevice(Utils)
// 获取当前任务所占用的内存(单位:MB)
- (double)usedMemory{
struct task_vm_info info;
mach_msg_type_number_t count = TASK_VM_INFO_COUNT;
kern_return_t kernReturn = task_info(mach_task_self(),
TASK_VM_INFO,
(task_info_t)&info,
&count);
if (kernReturn == KERN_SUCCESS){
return info.phys_footprint/1024.0/1024.0;
}
else{
return NSNotFound;
}
}
@end
// 所有接口请求的基类
@implementation H****Request
+ (void)initialize {
RKObjectManager *manager = [RKObjectManager sharedManager];
NSAssert(nil != manager, @"manager必须在使用所有网络请求创建前创建完毕");
RKRequestDescriptor *requestDescriptor = [self requestDescriptor];
if (nil != requestDescriptor) {
[manager addRequestDescriptor:requestDescriptor];
}
double mem1 = [[UIDevice currentDevice] usedMemory];
// 初始化RestKit中的,数据模型和类字段的映射
NSArray *responseDesscriptors = [self responseDescriptors];
double mem2 = [[UIDevice currentDevice] usedMemory];
NSLog(@"%@ %f", mem2 - mem1);
for (RKResponseDescriptor *responseDescriptor in responseDesscriptors) {
[manager addResponseDescriptor:responseDescriptor];
}
}
@end
用iOS15和iOS14.5的模拟器启动对比日志,如下
分析发现,部分接口其实在iOS14.5上面也比其他接口的占内存要大的,只是在iOS15上被放大了,增加一个占内存大于1m的条件,然后输出全部的接口占内存情况
查找发现和不占内存的接口进行对比,使用排除法去除一些字段对比,无法发现差异处。
继续深入跟踪,盯住一个接口Y****yPolicyRequest 101.417969M
,[self responseDescriptors]
的执行逻辑,指向的是里面的,NSObject的分类方法ht_modelMapping
。
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
// 增加excludeModelList是为了避免递归过程中死锁的出现. 例如: Model A中有个成员变量类型仍然是Model A, 如果在取该成员变量的类型Model A的Mapping中,不添加参数excludeModelList, 就会无限递归.
+ (RKObjectMapping *)ht_modelMapping:(NSMutableArray *)excludeModelList blackPropertyList:(NSArray *)blackPropertyList hasCycle:(BOOL)hasCycle {
RKObjectMapping *mapping = [RKObjectMapping mappingForClass:[self class]];
NSArray *attributesArray = [self ht_mappingAttributesArrayWithBlackList:blackPropertyList];
if ([attributesArray count] > 0) {
[mapping addAttributeMappingsFromArray:attributesArray];
}
// 如果出现了循环,则限制最多层数为kMaxRelationshipMappingLevel.
if (hasCycle && [excludeModelList count] > kMaxRelationshipMappingLevel) {
return mapping;
}
NSDictionary *customTypePropertyDic = [self ht_customTypePropertyDic];
for (NSString *propertyName in customTypePropertyDic) {
if (0 == [propertyName length] || [blackPropertyList containsObject:propertyName]) {
// 属性名为空或者被显式排除,则不需要添加到Mapping中.
continue;
}
NSString *propertyType = [customTypePropertyDic objectForKey:propertyName];
Class modelClass = NSClassFromString(propertyType);
if (!hasCycle && [excludeModelList containsObject:propertyType]) {
// 如果属性已被排除,则表明出现了循环.
// 例如,ClassA含有类型为ClassB的属性,ClassB又含有类型为ClassA的属性,那么必须控制循环的曾经,否则会在添加RelationshipMapping时无限循环.
hasCycle = YES;
}
NSMutableArray *itemExcludeModeList = [NSMutableArray arrayWithObject:NSStringFromClass([self class])];
if ([excludeModelList count] > 0) {
[itemExcludeModeList addObjectsFromArray:excludeModelList];
}
// Note: 这里不需要传递blackPropertyList.
RKObjectMapping *relationMapping = [modelClass ht_modelMapping:itemExcludeModeList hasCycle:hasCycle];
if (nil == relationMapping) {
continue;
}
[mapping addRelationshipMappingWithSourceKeyPath:propertyName mapping:relationMapping];
}
return mapping;
}
断点执行,发现内存占用情况挺OK的,嵌套递归查询所有类的属性,过程太长,中间在NSDictionary *customTypePropertyDic = [self ht_customTypePropertyDic];
发现出现一些陌生的属性
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
{
CIColor = CIColor;
"PG_wantsVibrancyEffect" = "__NSCFBoolean";
"_accessibilityNameWithLuma" = NSString;
"_pkaxCachedApproximateColorDescription" = NSString;
accessibilityName = NSString;
alpha = NSNumber;
"asc_highlightedColor" = UIColor;
blue = NSNumber;
debugDescription = NSString;
description = NSString;
dynamic = "__NSCFBoolean";
green = NSNumber;
hash = NSNumber;
invert = UIColor;
isDynamicTintColor = "__NSCFBoolean";
pkaxApproximateColorDescription = NSString;
pkaxDescriptionWithLuma = NSString;
pkaxLuma = NSNumber;
red = NSNumber;
"safari_colorDataForSerialization" = NSData;
"safari_grayscaleComponent" = NSNumber;
"safari_luminance" = NSNumber;
"safari_meetsThresholdForDarkAppearance" = "__NSCFBoolean";
"safari_rgbColorComponents" = NSArray;
"sf_darkenedColor" = UIColor;
"sf_isDarkColor" = "__NSCFBoolean";
systemColorName = NSString;
"vk_colorWith20PercentOpacity" = UIColor;
"vk_colorWith40PercentOpacity" = UIColor;
"vk_colorWith60PercentOpacity" = UIColor;
"vk_colorWith80PercentOpacity" = UIColor;
"vk_colorWithMaxSaturation" = UIColor;
writableTypeIdentifiersForItemProvider = NSArray;
}
马上去iOS14上看了下,也有,定位跟踪,发现这些其实是UIColor中的系统字段,然后就大胆猜想,后端接口返回数据类型基本都是基础类型和NSString,所以如果接口Model中有系统类型,比如UIColor,会不会导致问题?,马上仔细查看Y****cyPolicyRequest
的返回数据,发现了有一个类的属性中有UIColor
马上进行改造,将属性改成get方法,执行,对了,占内存下来了,但是还有部分接口很占内存,最后使用回想加猜测的方式,发现另一个类中有UIImage
,改造成了objc_getAssociatedObject
和objc_setAssociatedObject
的方式,内存占用问题也解决了。
所以基本定位:是UIImage和UIColor导致的。
最后的解决方法是在上面的代码段方法ht_modelMapping:blackPropertyList:hasCycle:
中增加过滤条件,判断如果是UIImage和UIColor就不进行获取RKObjectMapping
。
具体为什么系统类型的RKObjectMapping,在iOS14和iOS15上表现差异这么大,还没有深究,后续会查看下
最近24小时的OOM崩溃系统分布
线上问题修复效果
数据提取的是6.1号至6.15号的数据(15号全天数据还未累计完整)
导航栏背景图透明问题
导航栏的底图设计如下
1
[self.navigationController.navigationBar setBackgroundImage:[UIImage imageWithColor:[UIColor whiteColor] size:CGSizeMake(1, 1)] forBarMetrics:UIBarMetricsDefault];
查看层级结构图,如下
发现导航栏的底图的alpha
是0,导致的看不见问题,而且用subviews的方式还遍历不到这个UIImageView,最后发现是_UIBarBackground
的_colorAndImageView1
,通过将alpha
设置回1来尝试解决问题
1
2
3
4
5
6
7
8
9
if (@available(iOS 15.0, *)) {
dispatch_async(dispatch_get_main_queue(), ^{
UIImageView *imageView = [self.navigationController.navigationBar.subviews.firstObject valueForKey:@"colorAndImageView1"];
UIView *testView = self.navigationController.navigationBar.subviews.firstObject;
[self addObserver:testView forKeyPath:@"colorAndImageView1" options:NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld context:nil];
[self addObserver:imageView forKeyPath:@"alpha" options:NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld context:nil];
imageView.alpha = 1;
});
}
但是进入下一个页面再后退回来,返现问题又出现了,通过hook,[UIImageView setAlpha:]
方法,来看看是哪里又给他设置回来了,发现是系统的转场动画里面
1
2
3
4
5
6
7
8
9
10
11
12
13
14
@implementation UIImageView (Test)
+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
[self nes_swizzleInstanceMethod:@selector(setAlpha:) to:@selector(nes_setAlpha:)];
});
}
- (void)nes_setAlpha:(CGFloat)alpha {
[self nes_setAlpha:alpha];// 条件断点:alpha == 0
}
@end
1
2
3
4
[_UIBarBackground updateBackground]
[UIView(Internal) _addSubview:positioned:relativeTo:]
[UIView(NEHeimdall) hmd_addSubview:]
[_UINavigationParallaxTransition animateTransition:]
对比执行iOS14系统的,并没有走到设置alpha = 0的逻辑里面来
使用runtime查看下_UIBarBackground
的方法列表
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
unsigned int outCount;
Method *methodList = class_copyMethodList(NSClassFromString(@"_UIBarBackground"), &outCount);
// 遍历所有属性列表
for (int i = 0; i<outCount; i++) {
SEL name = method_getName(methodList[i]);
NSLog(@"%@", NSStringFromSelector(name));
}
/**
iOS15、iOS14.5都存在updateBackground方法
encodeWithCoder:
initWithCoder:
.cxx_destruct
groupName
setGroupName:
layout
setLayout:
initWithFrame:
layoutSubviews
_shadowView
_encodableSubviews
_setupBackgroundValues
_updateBackgroundViewVisiblity
frameForYOrigin:height:
_orderSubviews
_setupShadowView:effect:image:shadowColor:shadowTint:alpha:
cleanupBackgroundViews
prepareBackgroundViews
updateBackground
setCustomBackgroundView:
transition:toLayout:
transitionBackgroundViews
_backgroundEffectView
set_backgroundEffectView:
set_shadowView:
setTopAligned:
customBackgroundView
*/
以此推断,iOS15的updateBackground
方法中,新增了alpha = 1
的逻辑导致的
尝试解决方案,在viewDidAppear:
中将UIImageView的alpha设置回1。