24

iOS开发笔记— 数据库、Crash、内存问题分析

 5 years ago
source link: http://www.cocoachina.com/ios/20180910/24844.html?amp%3Butm_medium=referral
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

前言

分享iOS开发中遇到的问题,和相关的一些思考,本次内容包括:UIKit的iOS11问题、数据库问题定位、线上Crash处理、内存问题分析。

正文

1、iOS 11的UITabbar的高度异常

问题描述:iOS 11+iPhone,在横竖屏切换的场景下,UITabbarViewController的底部栏UITabbar会出现高度异常。

问题定位:经过调试发现,从竖屏到横屏的时候,系统会改变UITabbar的高度;而我们的底部栏高度是自定义的值,故而会导致系统修改后的高度与自定义值不相同的情况。

解决方案,KVO:

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary
<nskeyvaluechangekey id="">
  *)change context:(void *)context {
        if (self.tabBar.height != KSTabBarHeight) {
            self.tabBar.height = KSTabBarHeight;
            self.tabBar.bottom = SCREEN_HEIGHT;
        }
</nskeyvaluechangekey>

Stackoverflow的类似情况

2、CoreData数据库升级时间长

问题描述:App在升级的时候会对CoreData数据库进行一次迁移,而某些用户反馈升级时间长达数分钟。

问题定位:CoreData数据库迁移使用的是系统提供的自动迁移,经过本地测试,确实存在数据库较大的情况下,升级时间较长的问题。

那么如何确定数据库是哪些表是瓶颈?

用户的数据库比较大,不可能进行整个数据库上传操作;而CoreData并不支持获取某个表的大小。

可以采取一种方案:用户上报数据库每张表的行数,本地通过工具求出每张表的平均值,用以估算每张表的大小。

找到可以导出沙盒本地沙盒的App活跃使用者(比如说运营、产品),用sqlite3_analyzer对数据库进行分析,得到每张表大小,再除以行数,得到每张表每行的平均值。

(不能通过行数直接判断数据库大小,因为表的列数不确定;也不能通过列大小*行数得到表体积,因为某些字段为空)

修复方案:

  • 对瓶颈的表进行行数和体积双重控制;

  • 对某些行数较多但表体积小的表建索引;

引用:

sqlite数据库分析

sqlite3_analyzer安装

Appropriate Uses For SQLite

sqlite索引

Customizing the Migration Process

3、objc_msgSend的Crash分析

问题描述:objc_msgSend是常见的一种Crash,这次的堆栈如下

IfI3Qra.png!web

这类由UIKit引起的Crash通常是在回调业务层时,对应的target已经被释放,于是在objc_msgSend的时候就会发生Crash。

QnaAriv.png!web 寄存器和模块加载地址

问题定位:在本例中,查看上图知道,lr寄存器的地址是在第一个模块的加载区间内,以此作为线索。

用以下指定,进行手工符号化:

atos -o XXX arm64 0x000000010134d36c -l 0x1000fc000(XXX是二进制名字)

最终定位到问题,具体的代码类似:

[self.delegete remove];
self.data = ...

在这种情况下,self.delegate在remove掉之后self之后,self已经被释放,下面的self.data再进行赋值操作,就会出现异常情况。

解决方案:把  [self.delegete remove]; 放到最后一行。

后记:

该问题只出现在iOS 8。在iOS 11的机型上,通过调试我们可以获取到self.data=...这一行在执行时,关于self的内存引用情况:

ieiuAzf.png!webautoreleasepool和thread都会持有self,保证self在本次执行过程中不释放。故此猜测该问题苹果已经发现,并且在iOS 8后续的版本已经修复。

4、内存相关问题

实际场景涉及到业务,所以抽象成代码来进行分析。

场景1

下面这段代码是否能够正常运行?

如果可以,结果是什么?

如果不可以,是为什么?

- (void)viewDidLoad {
    [super viewDidLoad];
    SInt16* buffer = NULL;
    SInt16* p = &buffer[15];
    NSLog(@"tmp %d", p);
}

场景2

下面这段代码是否能够正常运行?

如果可以,结果是什么?

如果不可以,是为什么?

- (void)viewDidLoad {
    [super viewDidLoad];
    
    char *pBuf = malloc(5);
    memcpy(pBuf, "aaabbbcccddddeeefff", 10);
   
    puts(pBuf);
}

分析:

场景1:

此处有两个trick:

1、p是否能够正常赋值,以及赋何值;

2、指针类型是SInt16*, 计算地址要注意;

[] 是下标运算符,根据操作数和偏移量,获取指定地址的值;

在此题之中,buffer[15]等于*(buffer + 15);

&buffer[15] 等于&(*(buffer + 15));

& 是取址运算符,返回操作数的内存地址;

&buffer[15] = &(*(buffer + 15)) = buffer + 15

所以p = &buffer[15] = buffer+15 = 0+15*sizeof(SInt16) = 30

故而答案是能正常运行,结果是"tmp 30"。

场景2:

申请了一块较大的内存,在memcpy的时候,偶然情况下会出现越界的情况。但是因为堆内存空间到栈内存空间的距离不固定,不一定会出现crash的情况。

上面的题目本质是堆内存访问越界。

故而上述代码大多数情况下输出aaabbbcccd,少数情况下不可预知。

总结

2018年的忙碌情况超过我想象,长时间不更新iOS开发笔记让我都忘了还有这个专题所在。

我有个习惯,开发中遇到问题,超过十分钟还没解决的时候,就会记录下来,这样是开发笔记专题的雏形。

而在加入新公司的第二个年头,我慢慢已经在iOS上的收获越来越少。

从笔记的新增情况来看,就可以发现:每天大多数是重复性劳动!

尝试看过一些iOS相关的书籍,但总感觉收获不大。

今年我选择把更多的业余学习时间分配给Metal,详见 Metal入门教程总结

作者:落影loyinglin

链接:https://www.jianshu.com/p/ee2606c6725c


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK