iOS 面试关键点

518 阅读5分钟

一、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、读写锁:读写锁实际是一种特殊的自旋锁。将对共享资源的访问分成读者写者读者只对共享资源进行读访问写者则需要对共享资源进行写操作。这种锁相对于自旋锁而言,能提高并发性