iOS相册名称展示崩溃问题解决

App开发相关产品技术讨论,包括OEM App、App SDK等话题


Post Reply
dengkeng
Posts: 2

一、背景
涂鸦公版App,相册选择模块,某些iOS系统版本,用户打开相册后会崩溃,Backtrace定位分析到是向PHAssetCollection.localizedTitle发送消息然后在 objc_msgSend +32 里 EXC_BAD_ACCESS 了。

Apple Developer Forums - PHCollection localizedTitle crash in iOS 14.2.1 有人说把 NSString Category 的 +initialize 换成 +load 就解决了,这样确实也解决了。但如若是第三方实现了 +initialize 而我们改不了呢?或者有没别的解决方法呢?

Crash 的原因是因为 NSString Category 实现了 +initialize 方法后,会导致 NSTaggedPointerString 不启用了,objc_msgSend 里处理 Tagged Pointer 时,访问了未允许的内存地址, 于是就 EXC_BAD_ACCESS 了。

二、重现
重现步骤较简单:

打开 iPhone/iPad Photos APP,创建几个名字为英文或数字的比较短(字符长度小于11)的相簿Album,比如这里创建一个名叫 'Abc' 的 Album。

写一个 NSString Category,实现 +initialize 方法 NSString+Category.h:

Code: Select all

@interface NSString (Category)

@end

NSString+Category.m:

Code: Select all

@implementation NSString (Category)

+ (void)initialize {
	// empty now ...
}
 
@end

写个测试代码,向 PHAssetCollection.localizedTitle发送消息:

Code: Select all

 for (PHAssetCollection *collection in userAlbums) {
     NSString *string = collection.localizedTitle;
     NSLog(@">>>>>>>>>>>> album name: %@", [string description]);
 }

发生崩溃:Crash with EXC_BAD_ACCESS

三、分析
iOS 官方 Photos 库没理由返回个野指针过来吧,这个 0x87a4c650b3180a9e 指针地址64位占满,且最高位是1,像 Tagged Pointer 了,用objc4的相关api来打印来验证一下,确认是否是Tagged Pointer ?

项目里引入 objc-internal.h (Link),此头文件可从苹果开发者网站获取 objc4 源码 复制出来,注释掉两个宏定义的错误,项目也就可以编译过去了

因为 objc-internal.h 里较多的inline函数,inline函数在编译时就embed了,编译后是没有这些函数symbol的,lldb里是不能直接调用的,所以我们在ObjcUtil.h/m时Wrap了一层常用的inline函数,方便在lldb里执行

当 Crash on EXC_BAD_ACCESS 触发时:

Code: Select all

(lldb) p isTaggedPointer(string)
(bool) $0 = true
	
(lldb) p [ObjcUtil objcIsTaggedPointer:string]		# call _objc_isTaggedPointer
(BOOL) $1 = YES

确实是 Tagged Pointer String 无疑了,事实上 objc_msgSend 里也是根据地址值小 0 来判断是否为 Tagged Pointer。

那么打印一下它的值是0x0000000006362413,对照ASCII表,十六进制63代表字母c,62代表字母b,41代表字母A,最后的3代表长度。'Abc' 这个也是之前创建Album时输入的短名字。

四、解决方式
NSString的分类中不实现 + (void)initialize, 如果要实现,将实现的逻辑放在 + (void)load方法中;

Code: Select all

@implementation NSString (Category)

+ (void)load
{
    //实现逻辑放这里
}

//不重写initialize
+ (void)initialize
{
    //
}

@end

Tags:
Post Reply