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 结论
数值类型在运算之前一定要赋初始值。