面了20多家总结出来的部分iOS面试题(五)

2020-08-11  本文已影响0人  JoeyM

27. SDWebImage是怎么做缓存的?

二、如何获取图片的?
1、在内存缓存中找
2、如果内存中找不到, 会去默认磁盘目录中寻找, 如果找不到,在去自定义磁盘目录中寻找
3、如果磁盘也找不到就会下载图片
4、获取图片数据之后, 将图片数据从NSData转化UIImage。其中转化根据图片的类型进行转化
5、默认对图片进行解压缩,生成位图图片
6、将位图图片返回

三、图片是如何被解压缩的?
1、判断图片是否是动态图片,如果是,不能解压缩
2、判断图片是否透明,如果是,不能解压缩
3、判断图片的颜色空间是不是RGB如果不是、不能解压缩
4、根据图片的大小创建一个上下文
5、将图片绘制在上下文中
6、从上下文中读取一个不透明的位图图像,该图像就是解压缩后的图像
7、将位图图像返回

接上说 NSCache

//名称
@property (copy) NSString *name;

//NSCacheDelegate代理
@property (nullable, assign) id<NSCacheDelegate> delegate;

//通过key获取value,类似于字典中通过key取value的操作
- (nullable ObjectType)objectForKey:(KeyType)key;

//设置key、value
- (void)setObject:(ObjectType)obj forKey:(KeyType)key; // 0 cost

/*
设置key、value
cost表示obj这个value对象的占用的消耗?可以自行设置每个需要添加进缓存的对象的cost值
这个值与后面的totalCostLimit对应,如果添加进缓存的cost总值大于totalCostLimit就会自动进行删除
感觉在实际开发中直接使用setObject:forKey:方法就可以解决问题了
*/
- (void)setObject:(ObjectType)obj forKey:(KeyType)key cost:(NSUInteger)g;

//根据key删除value对象
- (void)removeObjectForKey:(KeyType)key;

//删除保存的所有的key-value
- (void)removeAllObjects;

/*
当NSCache缓存的对象的总cost值大于这个值则会自动释放一部分对象直到占用小于该值
非严格限制意味着如果保存的对象超出这个大小也不一定会被删除
这个值就是与前面setObject:forKey:cost:方法对应
*/
@property NSUInteger totalCostLimit;    // limits are imprecise/not strict

/*
缓存能够保存的key-value个数的最大数量
当保存的数量大于该值就会被自动释放
非严格限制意味着如果超出了这个数量也不一定会被删除
*/
@property NSUInteger countLimit;    // limits are imprecise/not strict
/*
这个值与NSDiscardableContent协议有关,默认为YES
当一个类实现了该协议,并且这个类的对象不再被使用时意味着可以被释放
*/
@property BOOL evictsObjectsWithDiscardedContent;

@end

//NSCacheDelegate协议
@protocol NSCacheDelegate <NSObject>
@optional
//上述协议只有这一个方法,缓存中的一个对象即将被删除时被回调
- (void)cache:(NSCache *)cache willEvictObject:(id)obj;
@end**

countLimit注意一下这个属性, 这个属性就是设置最大缓存数量,啥意思呢? 这玩意就和栈差不多, 先进先出(叫什么FIFO?)原则。比如你countLimit设置为5 那么当你缓存第6个对象的时候, 原本第一个就被移除了。 所以这便就有有一个风险,也可能会是面试点,为什么,通过key去取值的时候,一定要判断一个获取的对象是否为nil?答:就因为很有可能某些对象被释放(顶)掉了。

又又又可能出现的面试题!NSCache里面缓存的对象,在什么场景下会被释放?

28.SDWebImage实现原理是什么? 它是如何解决tableView的复用时出现图片错乱问题的呢

29. 为什么刷新UI要在主线程操作

30. RunTime

类的结构体:

//Class也表示一个结构体指针的类型
typedef struct objc_class *Class;

struct objc_class {
    Class _Nonnull isa  OBJC_ISA_AVAILABILITY;

#if !__OBJC2__
    Class _Nullable super_class                              OBJC2_UNAVAILABLE;
    const char * _Nonnull name                               OBJC2_UNAVAILABLE;
    long version                                             OBJC2_UNAVAILABLE;
    long info                                                OBJC2_UNAVAILABLE;
    long instance_size                                       OBJC2_UNAVAILABLE;
    struct objc_ivar_list * _Nullable ivars                  OBJC2_UNAVAILABLE;
    struct objc_method_list * _Nullable * _Nullable methodLists                    OBJC2_UNAVAILABLE;
    struct objc_cache * _Nonnull cache                       OBJC2_UNAVAILABLE;
    struct objc_protocol_list * _Nullable protocols          OBJC2_UNAVAILABLE;
#endif

} OBJC2_UNAVAILABLE;

分类结构体

struct category_t {
    const char *name;
    classref_t cls;
    struct method_list_t *instanceMethods; // 对象方法
    struct method_list_t *classMethods; // 类方法
    struct protocol_list_t *protocols; // 协议
    struct property_list_t *instanceProperties; // 属性
    // Fields below this point are not always present on disk.
    struct property_list_t *_classProperties;

    method_list_t *methodsForMeta(bool isMeta) {
        if (isMeta) return classMethods;
        else return instanceMethods;
    }

    property_list_t *propertiesForMeta(bool isMeta, struct header_info *hi);
};

引申1. class_copyIvarList与class_copyPropertyList的区别?

引申2. class_ro_t和class_rw_t的区别?

31. NSNotification

// 比如
   dispatch_async(dispatch_get_main_queue(), ^{
       // 刷新UI
   });

32. 什么情况使用 weak 关键字,相比 assign 有什么不同?(轮回系列)

引申 1.关键字copy 的用法?

引申 2.@property 的本质是什么?ivar、getter、setter 是如何生成并添加到这个类中的。

33. 说说内存管理?

杂项

互斥锁:如果共享数据已经有了其他线程加锁了,线程会进行休眠状态等待锁,一旦被访问的资源被解锁,则等待资源的线程会被唤醒。信号量dispatch_semaphore 为互斥锁   @synchronized是NSLock的封装 属于互斥锁  互斥锁一般用于等待时间较长的情况
**适用于**:线程等待锁的时间较长

自旋锁:如果共享数据已经有其他线程加锁了,线程会以死循环的方式等待锁,一旦被访问的资源被解锁,则等待资源的线程会立即执行。OSSpinLock 属于自旋锁   自旋锁一般用于时间较短的情况,OSSpinLock
**适用于**:线程等待锁的时间较端

drawRect调用时机

  • 一般是第一次想要为项目添加pod的时候使用的,当然也可以在添加和移除库使用
  • 每次pod install的时候,pod install 回为每一个安装的pod库在Podfile.lock文件中写入其版本号,并且锁定当前版本号。
  • 如果pod install的时候,不会更新其版本库,而是去下载新的或者移除当前版本
    pod update
  • 当执行了pod update的时候,cocoaPods不会考虑Podfile.lock中的版本。直接去更新当前所有的库到最新,然后Podfile.lock会更新这一次的版本号。
  • 有的相对布局最终都会转换成Frame绝对布局 中间多了一层转换的操作

iOS9

从HTTP升级到HTTPS
App瘦身 下面有讲 这里不赘述( App瘦身 )
新增UIStackView

iOS10

新增通知推送相关的操作。自定义通知弹窗,自定义通知类型(地理位置,时间间隔,日历等)

iOS11

无线 调试
齐刘海儿,导航条,安全距离等

iOS12

启动速度优化
应用启动速度提升40%
键盘响应速度提升50%
相机启动速度提升70%

iOS13

黑暗模式 详情请查阅 https://www.jianshu.com/p/0da3b107f06c


二、App包以及启动过程

App瘦身

1、App如何瘦身?

Slicing: 这个过程是iOS9出来之后 不需要程序员干预的一个瘦身的过程,简单来说就是我们再上传IPA包到iTunes Connect,然后AppStore会对app进行切片,切成特定的机型想要的数据,比如@3x给max用,@2x就自动剔除了。 是一个自动的过程
Bitcode:是一种中间码,如果配置了Bitcode(Xcode7以后默认开启)的程序会在App Store Connect上被重新编译等一系列操作,进而苹果内部会对可执行文件进行优化,也就是说不需要我们干预什么东西,也操作不了, 如果后面苹果有更牛逼的优化操作,也是苹果的事情, 跟我们个人开发者一毛钱关系没有。
On Demand Resources 按需加载, 是程序员自己手动操作,说白了就是在用的时候去下载某些资源, 但是我们自己在配置的时候都需要配置,要额外写一些代码啥的,等我们提交到市场的时候, 苹果内部会把我们按需加载的资源从包里面做了一些抽离操作啥的, 让我们的包在下载的时候更小,举个例子,就是吃鸡里面沙漠地图如果玩家不自己下载, 就玩不了沙漠。

on-demond resource(ODR)具体请查看原理版本:https://www.jianshu.com/p/bacedd8a3ad8
或者详细使用版本:http://www.cocoachina.com/articles/12155

关于 slicing, bitcode, on-demond resource(ODR)的参考资源https://blog.csdn.net/zhuod/article/details/70051514?utm_source=blogxgwz6


2、app启动时候都经历了什么?

启动分为两种。 一种是之前启动过,按了一下home键,然后再点启动,这个启动叫热启动,另外就是第一次启动app,或者启动杀死之后的app 叫做冷启动

根据info.plist里面的设置加载,建立沙箱,权限检查等
加载可执行文件
加载动态库
objc运行时的初始化处理(类的注册,category注册,selector唯一性检查等等)
初始化,包括+load方法
执行main函数
Application 初始化,到 applicationDidFinishLaunchingWithOptions 执行完
渲染屏幕,到viewDidAppear 执行完毕,展现给用户

根据info.plist里面的设置加载,建立沙箱,权限检查等
加载可执行文件
加载动态库
objc运行时的初始化处理(类的注册,category注册,selector唯一性检查等等)
初始化,包括+load方法


3、优化启动时间

注意:启动时间在小于400ms是最佳的,因为从点击图标到显示Launch Screen,到Launch Screen消失这段时间是400ms。启动时间不可以大于20s,否则会被系统杀掉。

mian函数之前的启动优化


mian函数之后的启动优化
首先分析一下从main函数开始执行,到第一个页面显示, 这段时间做了哪些事情

  1. 执行didFinishLaunchingWithOptions方法
  2. 初始化Window,初始化基础ViewContreoller(一般是UINavigationController+UITabViewController)
  3. 获取数据(本地和远程)
  4. 最后展示给用户
  5. 减少创建线程(高性能iOS开发一书中提到,线程不仅仅有创建时的时间开销,还会消耗内核的内存,即应用的内存空间。 每个线程大约消耗 1KB 的内核内存空间线程创建的耗时(不包含启动时间),其区间范围在 4000~5000 微秒,即 4~5 毫秒。创建线程后启动线程的耗时区间为 5~100 毫秒,平均大约在 29 毫秒。这是很大的时间开销,若在应用启动时开启多个线程,则尤为明显。线程的启动时间之所以如此之长,是因为多次的上下文切换所带来的开销。所以线程在开发过程中也避免滥用)
  6. 合并或者删减不必要的类(或者分类)和函数objc的类越多,函数越多启动越慢
  7. 在设计师可接受的范文尽量使用小的图片
didFinishLaunchingWithOptions
applicationDidBecomeActive

优化的核心思想就是,能延时的延时, 不能延时的尽量放到后台去优化。

- 日志、统计等必须在 APP 一启动就最先配置的事件。仍然把它留在 didFinishLaunchingWithOptions 里启动。
- 项目配置、环境配置、用户信息的初始化 、推送、IM等事件,这些功能在用户进入 APP 主体的之前是必须要加载完的,把他放到广告页面的viewDidAppear启动。
- 其他 SDK 和配置事件,由于启动时间不是必须的,所以我们可以放在第一个界面的 viewDidAppear 方法里,这里完全不会影响到启动时间。
- 每次用NSLog方式打印会隐式的创建一个Calendar,因此需要删减启动时各业务方打的log,或者仅仅针对内测版输出log
- 尽量不要在didFinishLaunchingWithOptions 里面创建和开启多线程

参考文献https://www.jianshu.com/p/f40fdd8799b8
其文章内部作者谈到了美团关于启动优化的相关分析,看似似曾相似,没记错的画《高性能iOS应用开发》这本书就是美团这几个哥们儿翻译的吧,实现方式和书中颇为相似。


3、App电量消耗

定位优化

1.尽量不要实时更新
2.定位精度尽量不要太高

网络优化

1.减少、压缩网络数据
2.能使用缓存就使用缓存,减少网络请求
3.断点续传
4.批量传输
5.设置适合的超时时间,用户可以取消耗时的网络请求
6.网络不可用时就不要再执行网络请求了

CPU/GPU优化

相关离屏渲染操作尽量避免
内存管理处理好
使用懒加载
使用绘制
图片与imageView相同大小避免多余运算
Timer的时间间隔不宜太短,满足需求即可
线程适量,不宜过多,不要阻塞主线程
适当使用多线程
减少视图刷新:确保必要的时候才刷新,能刷新1行cell最好只刷新一行;

为了优化耗电我们还可以做:
1.尽量不要使用定时器
2.优化I/O操作(文件的读写操作)
2.1最好不要频繁读写小数据,最好批量读写
2.2数据量比较大的时候可以考虑使用数据库
2.3读写大量重要数据时,考虑用dispatch_io,其提供了基于GCD的异步操作文件I/O的API。用dispatch_io系统会优化磁盘访问

高性能iOS应用开发中提到一下几点

  • 数据处理(例如文本格式优化)
  • 待处理的数据大小----更大的显示屏允许软件在单个视图中展示更多的信息,但这也意味着要处理更多的数据
  • 处理数据的算法和数据结构
  • 执行更新的次数,尤其是在数据更新之后,触发应用的状态或者UI进行更新(比如刷新单行cell)
  • 服务器中的数据尽量不要在客户端上处理(例如服务器字符串,在客户端进行拆分操作)
  • 按需加载(例如tableViewcell 不需要一下子全部渲染,快速滑动的时候 过程中的留白处理。)
  • 在进行网络请求之前,先检查是否有网络连接。(没网络的时候,不要请求网络)
  • 避免没有连接WiFi的情况下进行高带宽的消耗操作(因为3G、4G等手机网络耗电量远大于WIFi信号),例如视频流在4G或者非Wifi情况下应该给出响应的提示。
  • 尽量不要实时更新
  • 定位精度尽量不要太高
上一篇 下一篇

猜你喜欢

热点阅读