(iOS)一个让我找了6小时的Bug

发表于:2017-5-18 11:47

字体: | 上一篇 | 下一篇 | 我要投稿

 作者:隳易    来源:简书

  0x0 bug描述
  bug是这样的,程序在debug模式下是正常的,然而打包成为ipa后安装到iPhone5s和iPhoneSE(iPhone5及其一下机型未尝试,不知道会不会。)手机上却出现了bug。如下图:
  如图所示,在点击了cell卡顿了很长的一段时间,页面才发生跳转。
  0x1 寻找bug
  bug是在打包成为ipa后才出现的,如果每次调试完毕只有通过打包再安装到手机上才能验证的话,那么在寻找bug的过程中将会非常繁琐和耗时。把调试状态从Debug调节到Release模式下。操作如下:
  步骤1
  
Debug切换到Release
  步骤2
  
Debug切换到Release
  设置完后直接run就是release模式下了。再次运行程序,发现没有卡顿。
  这就坑爹了,找了半天没找到问题错在。只能用其他方法了。
  打开神器Instruments选择Core Animation,选中工程运行程序,发现在进入该页面的时候cpu使用暴增,那应该是和UI界面有关,才会导致卡顿。
  只能使用注释+重复打包。经反复注释测试后发现,代码出现在UICollectionView上面,collectionView相关代码如下:
CGFloat cellWidth = ([UIScreen mainScreen].bounds.size.width - kLeftEdge - kRightEdge - 2*kCellItemSpacing)/3;
_cellSize = CGSizeMake(cellWidth, kCellHeight);
UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc]init];
layout.itemSize = _cellSize;
layout.sectionInset = UIEdgeInsetsMake(kTopEdge, kLeftEdge, kBottomEdge, kRightEdge);
layout.minimumInteritemSpacing = kCellItemSpacing;
layout.minimumLineSpacing = kCellRowSpacing;
_collectionView = [[UICollectionView alloc]initWithFrame:CGRectMake(0, 0, [UIScreen mainScreen].bounds.size.width,_collectionHeight) collectionViewLayout:layout];
_collectionView.showsHorizontalScrollIndicator = NO;
_collectionView.backgroundColor = [UIColor whiteColor];
_collectionView.dataSource = self;
_collectionView.delegate = self;
_collectionView.scrollEnabled = NO;
[_collectionView registerClass:[UICollectionViewCell class] forCellWithReuseIdentifier:kCollectionIdentifier];
[_collectionView registerClass:[UICollectionReusableView class] forSupplementaryViewOfKind:UICollectionElementKindSectionHeader withReuseIdentifier:kCollectionReusableViewHeader];
[_collectionView registerClass:[UICollectionReusableView class] forSupplementaryViewOfKind:UICollectionElementKindSectionFooter withReuseIdentifier:kCollectionReusableViewFooter];
[self.view addSubview:_collectionView];
#pragma mark - UICollectionViewDelegate,UICollectionViewDataSource
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath{
UICollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:kCollectionIdentifier forIndexPath:indexPath];
return cell;
}
- (UICollectionReusableView *)collectionView:(UICollectionView *)collectionView viewForSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath{
if ([kind isEqualToString:UICollectionElementKindSectionHeader]) {
UICollectionReusableView *view = [collectionView dequeueReusableSupplementaryViewOfKind:UICollectionElementKindSectionHeader withReuseIdentifier:kCollectionReusableViewHeader forIndexPath:indexPath];
for (UIView *subView in view.subviews) {
[subView removeFromSuperview];
}
//        view.backgroundColor = [ThemeManager colorForKeyPath:@"WhiteColor"];
view.size = CGSizeMake(LYScreen_Width, 30);
UILabel *titleLabel = [[UILabel alloc]initWithFrame:CGRectMake(12, 0, LYScreen_Width, 30)];
titleLabel.font = LYFont(12);
titleLabel.textColor = [UIColor LY_ColorWithHexString:@"999999"];
LYChargeDetailModel *model = _model.content[indexPath.section];
titleLabel.text = model.title;
[view addSubview:titleLabel];
return view;
}else{
UICollectionReusableView *view = [collectionView dequeueReusableSupplementaryViewOfKind:UICollectionElementKindSectionFooter withReuseIdentifier:kCollectionReusableViewFooter forIndexPath:indexPath];
//        view.backgroundColor = [ThemeManager colorForKeyPath:@"SpaLineColor"];
view.size = CGSizeMake(LYScreen_Width, 1.0f/[UIScreen mainScreen].scale);
return view;
}
}
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section{
LYChargeDetailModel *model = _model.content[section];
return model.items.count;
}
- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView{
return _model.content.count;
}
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout referenceSizeForHeaderInSection:(NSInteger)section{
return CGSizeMake(LYScreen_Width, 30);
}
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout referenceSizeForFooterInSection:(NSInteger)section{
return CGSizeMake(LYScreen_Width, 1.0f/[UIScreen mainScreen].scale);
}
  从代码看上去并没有什么不妥,接着逐行注释。发现当注释掉_collectionView.dataSource = self;的时候不卡了,但是UICollectionView也没内容了,再次检查UICollectionViewDataSource下的所有代码。还是没有发现异常。接下就麻烦了,开始逐行看代码(这代码不是我写的....)。终于在看到这样一段代码的时候,发现了异常。
- (void)setModel:(LYChargeModel *)model{
...
CGFloat height;
for (int i = 0; i<model.content.count; i++) {
LYChargeDetailModel *detailModel = model.content[i];
NSInteger row = detailModel.items.count/3;
row += (detailModel.items.count%3)?1:0;
height += kCellHeight * row + kCellRowSpacing*(row - 1) + kTopEdge + kBottomEdge + 30;
}
NSInteger address = (NSInteger)(&height);
self.title = [NSString stringWithFormat:@"v:%0.f a:%li",height,address];
_collectionHeight = height;
...
}
  release模式下运行程序,发现_collectionHeight的数值异常变得非常大。认真检查下height变量并没有赋初始值,给height加上初始值,再次打包发现好了,不卡了。那为什么数值变大的时候会导致主线程卡死呢?于是我做了个实验,给height赋值MAXFLOAT。再次打包安装到手机,发现在跳转的时候卡死了。那么可以猜测当collectionView太大在reload的时候会导致主线程卡死,bug完美修复。
  0x2 究其根源
  这个bug的根源是CGFloat变量没有设置初始值导致的,且只有在Release模式在下才会出现在Debug模式下是正常的,也就是说CGFloat的初始值在Debug模式和Release模式下的值是不同的。(在iPhone5s或iPhoneSE设备上)。
  于是做了个实验,发现在Debug和Release模式下CGFloat的默认值都是0,但是如果出现自加运算时就出现了数值异常。
  代码如下
CGFloat floatValue;
NSMutableString *text = @"\n".mutableCopy;
[text appendFormat:@"CGFloat Default:%f\n",floatValue];
floatValue+=100;
[text appendFormat:@"CGFloat +100:%f\n",floatValue];
//Debug打印
CGFloat Default:0.000000
CGFloat +100:100.000000
//Release打印
CGFloat Default:0.000000
CGFloat +200:100.000000
  显然在Release状态下数值出现了异常。稍微发散一下其他数值类型在Release模式下值是否也会有所不同?做了下实验。
  代码
NSInteger integer;
CGFloat floatValue;
int i;
float f;
double d;
NSMutableString *text = @"".mutableCopy;
[text appendFormat:@"NSInteger default:%li\n",integer];
[text appendFormat:@"CGFloat default:%f\n",floatValue];
[text appendFormat:@"int default:%i\n",i];
[text appendFormat:@"float default:%f\n",f];
[text appendFormat:@"double default:%f\n",d];
integer+=1;
floatValue+=1;
i+=1;
f+=1;
d+=1;
[text appendFormat:@"NSInteger +1:%li\n",integer];
[text appendFormat:@"CGFloat +1:%f\n",floatValue];
[text appendFormat:@"int +1:%i\n",i];
[text appendFormat:@"float +1:%f\n",f];
[text appendFormat:@"double +1:%f\n",d];
NSLog(@"%@",text);
iPhone SE
//Debug状态下
NSInteger default:2 //数值异常
CGFloat default:0.000000
int default:1//数值异常
float default:129753328198464616092303097856.000000//数值异常
double default:0.000000
NSInteger +100:102//数值异常
CGFloat +100:100.000000
int +100:101//数值异常
float +100:129753328198464616092303097856.000000//数值异常
double +100:100.000000
//Release 状态下
NSInteger default:5533341776//数值异常
CGFloat default:0.000000
int default:1238374480//数值异常
float default:0.000000
double default:0.000000
NSInteger +100:5533341776//数值异常
CGFloat +100:200.000000//数值异常
int +100:0//数值异常
float +100:200.000000//数值异常
double +100:200.000000//数值异常
iPhone 6
//Debug状态下
NSInteger default:2//数值异常
CGFloat default:0.000000
int default:1//数值异常
float default:137800089569547039707794243584.000000//数值异常
double default:0.000000
NSInteger +100:102//数值异常
CGFloat +100:100.000000
int +100:101//数值异常
float +100:137800089569547039707794243584.000000//数值异常
double +100:100.000000
//Release 状态下
NSInteger default:2//数值异常
CGFloat default:0.000000
int default:1//数值异常
float default:137800089569547039707794243584.000000//数值异常
double default:0.000000
NSInteger +100:102//数值异常
CGFloat +100:100.000000
int +100:101//数值异常
float +100:137800089569547039707794243584.000000//数值异常
double +100:100.000000
  发现大部分数值都会出现异常。值得注意的是CGFloat和double这两个类型总是同时出现异常,问题来了再iOS的底层CGFloat是不是用double实现的呢?于是查阅api发现CGFloat是这样定义的typedef CGFLOAT_TYPE CGFloat;。
  CGFLOAT_TYPE这个宏具体是什么,查阅资料也没找到,知道的大兄弟说下。感激不尽。
  如果说CGFloat Relaese 模式下在iPhone5s 和 iPhoneSE上不设置默认值进行加法运算会出现异常,那么同理推断CGRect、CGSize、CGPoint在不设置初始值的情况进行加法运算也是会出现问题的。
  代码
CGSize size;
CGRect rect;
CGPoint point;
NSMutableString *text = @"\n".mutableCopy;
[text appendFormat:@"CGSize default:%@\n",NSStringFromCGSize(size)];
[text appendFormat:@"CGRect default:%@\n",NSStringFromCGRect(rect)];
[text appendFormat:@"CGPoint default:%@\n",NSStringFromCGPoint(point)];
size.width+=100;
size.height+=100;
rect.size.width+=100;
rect.size.height+=100;
rect.origin.y+=100;
rect.origin.x+=100;
point.y+=100;
point.x+=100;
[text appendFormat:@"CGSize +100:%@\n",NSStringFromCGSize(size)];
[text appendFormat:@"CGRect +100:%@\n",NSStringFromCGRect(rect)];
[text appendFormat:@"CGPoint +100:%@\n",NSStringFromCGPoint(point)];
iPhone SE
//Debug
CGSize default:{4.9406564584124654e-324, 3.049280293648331e-314}
CGRect default:{{0, 0}, {2.6509482737098556e-314, 3.337809125940193e-314}}
CGPoint default:{0, 0}
CGSize +100:{100, 100}
CGRect +100:{{100, 100}, {100, 100}}
CGPoint +100:{100, 100}
//Release
CGSize default:{0, 0}
CGRect default:{{nan, 2.1219957904712067e-314}, {7.9499288951273625e-275, 0}}
CGPoint default:{nan, 2.1219957904712067e-314}
CGSize +100:{200, 200}
CGRect +100:{{200, 200}, {200, 200}}
CGPoint +100:{200, 200}
  根据结果表明,在Release模式下CGRect、CGSize、CGPoint不赋值进行加法运算会出现异常。
  0x3 结论
  数值类型在运算之前一定要赋初始值。
《2023软件测试行业现状调查报告》独家发布~

关注51Testing

联系我们

快捷面板 站点地图 联系我们 广告服务 关于我们 站长统计 发展历程

法律顾问:上海兰迪律师事务所 项棋律师
版权所有 上海博为峰软件技术股份有限公司 Copyright©51testing.com 2003-2024
投诉及意见反馈:webmaster@51testing.com; 业务联系:service@51testing.com 021-64471599-8017

沪ICP备05003035号

沪公网安备 31010102002173号