一、APP 瘦身方案
1、代码层面(重复代码整合)
- 整合相同功能的组件库,比如SDWebImage和Kingfisher,只选一个。
- iOS组件化之后,不同的组件库使用了相同功能的类,导致重复。
2、资源文件优化
无用资源清理,使用LSUnusedResource检测。 使用tinypng进行图片压缩。 大图使用更小体积的图片格式HEIC,或者使用url的方式加载。 把图标放到Asset Catalog中,苹果有瘦身机制。尽量不要放到文件夹或者bundle文件中。
3、动态库改成静态库
动态库是在APP启动dyld的时候链接的库,静态库是编译期连接的库,动态库引用其他库的符号会占用额外空间。
4、在podfile中设置编译优化
主要有三点:1、swift编译优化级别 2、GCC编译优化级别 3、打包后裁剪不必要的符号
# pod安装时会执行的hook函数
post_install do |installer|
installer.pods_project.targets.each do |target|
target.build_configurations.each do |config|
if config.name == 'Release'
# swift编译优化级别
config.build_settings['SWIFT_OPTIMIZATION_LEVEL'] = '-Osize'
config.build_settings['SWIFT_COMPILATION_MODE'] = 'wholemodule'
# GCC编译优化级别
config.build_settings['GCC_OPTIMIZATION_LEVEL'] = 'z'
config.build_settings['LLVM_LTO'] = 'YES_THIN'
# 打包后裁剪不必要的符号
config.build_settings['STRIP_INSTALLED_PRODUCT'] = 'YES'
config.build_settings['DEBUG_INFORMATION_FORMAT'] = 'dwarf-with-dsym'
end
end
end
end
二、动、静态库的加载方式和优劣
| 对比项 | 静态库 | 动态库 |
|---|---|---|
| 文件格式 | .a .framework .xcframework | .framework .dylib .xcframework .tbd |
| 编译链接 | 编译连接时,合并到可执行文件 | 编译连接不合并,会独立生成一个动态库类型Mach-O文件 |
| 启动影响 | 内容跟随主二进制加载到内存中,对启动影响较小 | 启动时dyld会加载其Mach-O并进行符号解析,比静态库更加耗时 |
| 包体积 | 相对动态库较小 | 比静态库大一点 |
注意点: OC静态库中有分类,需要在Other link flags 中添加-OjbC,否则会崩溃。如果是通过podfile加载的静态库,系统会自动添加。
三、KVO崩溃
1、在dealloc中没有移除观察者,或者重复移除观察者
2、多次添加相同的观察者,产生多次通知导致崩溃
3、被观察的对象已释放,比如观察者所在VC已经释放
[object addObserver:self forKeyPath:@"property" options:NSKeyValueObservingOptionNew context:nil];
[object release]; // 被观察对象被释放
4、观察者对象已释放
[object addObserver:self forKeyPath:@"property" options:NSKeyValueObservingOptionNew context:nil];
// 在 KVO 通知回调过程中,self 被释放,建议使用strongSelf
5、在不同的线程中添加移除观察者也会崩溃
四、KVO原理
1、当person类的属性被观察时,会生成一个派生类,NSKVOObserver_Person,同时将person的isa指针指向派生类,重写观察属性的setter方法。
2、在setter方法中,键值观察通知依赖NSObject的两个方法。willchangevalueforkey和didchangevalueforkey。在一个被观察属性发生改变之前willchangevalueforkey会被调用。属性改变后didchangevalueforkey一定会被调用。然后observervalueforkey:ofObject:change:context
五、Block的类型和原理
Block分:堆、栈、全局三种Block
堆:Block内部引用了非全局变量
栈:在ARC下会自动copy到堆上
全局:引用全局变量或者没有引用任何变量的block
Block的原理: Block本身也是个结构体,包含isa指针、flags标识符、invoke指针函数(存储block内部变量信息)等等
// CJL注释:Block 结构体
struct Block_layout {
//指向表明block类型的类
void *isa;//8字节
//用来作标识符的,类似于isa中的位域,按bit位表示一些block的附加信息
volatile int32_t flags; // contains ref count 4字节
//保留信息,可以理解预留位置,用于存储block内部变量信息
int32_t reserved;//4字节
//函数指针,指向具体的block实现的调用地址
BlockInvokeFunction invoke;
//block的附加信息
struct Block_descriptor_1 *descriptor;
// imported variables
};
六、锁
1、自旋锁:在自旋锁中,线程会反复检查变量是否可用。由于线程这个过程中一致保持执行,所以是一种忙等待。一旦获取了自旋锁,线程就会一直保持该锁,直到显式释放自旋锁。自旋锁避免了进程上下文的调度开销,因此对于线程只会阻塞很短时间的场合是有效的。对于iOS属性的修饰符atomic,自带一把自旋锁
2、互斥锁:互斥锁是一种用于多线程编程中,防止两条线程同时对同一公共资源(例如全局变量)进行读写的机制,该目的是通过将代码切成一个个临界区而达成。典型: NSLock、 @synchronized
3、条件锁:条件锁就是条件变量,当进程的某些资源要求不满足时就进入休眠,即锁住了,当资源被分配到了,条件锁打开了,进程继续运行。典型:NSCondition、NSConditionLock
4、递归锁:递归锁就是同一个线程可以加锁N次而不会引发死锁。递归锁是特殊的互斥锁,即是带有递归性质的互斥锁。典型:NSRecursiveLock
5、信号量:信号量是一种更高级的同步机制,互斥锁可以说是semaphore在仅取值0/1时的特例,信号量可以有更多的取值空间,用来实现更加复杂的同步,而不单单是线程间互斥。典型:dispatch_semaphore
6、读写锁:读写锁实际是一种特殊的自旋锁。将对共享资源的访问分成读者和写者,读者只对共享资源进行读访问,写者则需要对共享资源进行写操作。这种锁相对于自旋锁而言,能提高并发性。