2024最新!!!iOS高级面试题,全!(二)
iOS应用是如何启动以及如何优化
pre-main阶段加载动态链接器dyld到App进程加载动态库(包括所依赖的所有动态库)Rebase 修正内部的指针指向Bind 修正外部指针指向初始化Objective C Runtime 包括oc的类、分类的注册,selector唯一性检查等初始化代码,调用Oc类和分类的+load方法,构造器函数的调用(用attribute((constructor))修饰的函数、创建C++静态全局变量等执行main函数后,调用didFinishLaunching方法完成初始化对于pre-main阶段的耗时测量方法,在 Xcode 中 Edit scheme -> Run -> Auguments 将环境变量DYLD_PRINT_STATISTICS 设为1 。之后控制台会输出耗时。DYLD_PRINT_STATISTICS_DETAILS建议应用的启动时间控制在400ms之下,并且在20s内启动,否则系统会kill app。一般是优化main函数阶段,大多数的耗时是在自己写的代码里优化方法:减少自定义的动态库数量,合并动态库,官方建议不超过6个。因为动态库的加载方式比静态库慢。如果必须依赖动态库,则把多个非系统的动态库合并成一个动态库。静态库是在编译时将库的代码打包到可执行程序中,动态库则是在运行时动态加载到程序中的。减少Objective-C类,分类和Selector的个数,类似功能尽量合并减少使用构造器函数减少使用oc的load减少c++静态全局变量压缩资源图片,,因为在启动加载时会加载资源图片进行IO操作。所以图片小加载速度也会显著提升。删除无用的方法和类,多用Swift Structs,因为Swfit Structs是静态分发的延迟初始化那些不必要的UIViewController。didFinishLaunching中,耗时操作的优化类和方法名不要太⻓。
Load和initialize的不同
调用顺序不同,以main函数为分界,+load方法在main函数之前执行,+initialize在main函数之后执行。子类中没有实现+load方法,子类不会调用父类的+load方法;而子类如果没有实现+initialize方法的话,也会自动调用父类的+initialize方法。+load方法是在类被装在进来的时候就会调用,+initialize在实例化对象的时候调用,并且只会调用一次,是懒加载模式,如果这个类一直没有使用,就不回调用到+initialize方法。
@property 的本质是什么?
@property的本质是实例变量(ivar)+ 存取方法,即 @property = ivar + getter + setter;属性” (property)作为 Objective-C 的特性,主要用于封装对象中的数据。 Objective-C 对象数据保存为实例变量。实例变量一般通过“存取方法”来访问。 getter读取,setter写入。
@property中有哪些属性关键字?/ @property 后面可以有哪些修饰符?
atomic、nonatmoic、readonly、readwrite、assign、weak、strong、copy
什么情况使用 weak 关键字,相比 assign 有什么不同?
什么情况使用 weak 关键字?循环引用的时候,比如: delegate 代理属性xib的IBOutlet 控件属性一般也使用 weak不同点:assigin 可以修饰非 OC 对象,比如简单数据类型 int bool,weak 必须用于 OC 对象,weak 是弱引用,被weak修饰的对象,引用计数不会加1, 不会产生野指针。weak修饰的对象释放后,指针会自动被置nil,是安全的。assign 修饰基本数据类型是安全的。如果修饰对象,会产生野指针;修饰的对象释放后,指针不会自动置空。所以一般都是用 assign 来修饰基本类型,weak 来修饰对象
__weak __block
通过 __weak 弱引用,来打破循环引用。__block:__block修饰的变量可以修改。在代码块中会被retain(ARC)__weak:__weak修饰的变量不可以修改。不会在block代码块中被retain__block不能修饰全局变量、静态变量(static)
block本质和说明
block闭包,本质上也是对象,是一段代码块,是带有自动变量的匿名函数,可以精简代码减少耦合block使用的时候,可以作为变量使用,可以作为方法参数,也可以做为属性或实例变量他有个特性叫变量捕获机制,(block内部会捕捉外部变量,auto局部变量直接捕获值,static修饰的变量捕获指针,全局变量不捕获)Auto局部变量:我们平时写的局部变量,默认就带着 auto (自动变量,离开作用域就销毁)在 Block 内部修改 外部变量 需要用 __block使用block时要特别注意循环引用,避免互相持有对方的强引用,导致它们无法被释放,从而造成内存泄漏。 __weak来打破循环引用。block内部如何修改变量的值这个问题一般问的是修改局部变量的值,因为全局变量直接可以访问到,不存在修改不了的情况如何修改局部变量的值:第一种:直接使用static修饰。第二种:局部变量改为全局变量。第三种:使用__block修饰变量注意:只要不对变量/对象 的值进行修改 就不要去使用__blockblock分类.引用全局变量、全局静态变量、局部静态变量:Block 在全局区,属于 GlobalBlockb.引用普通外部变量,用 copy,strong 修饰的 Block 就存放在堆区,属于 MallocBlock;用 weak 修饰的Block 存放在栈区,属于 StackBlock
RunLoop
事件驱动/事件循环 的一个对象。没有消息需要处理时,休眠;有消息需要处理时,唤醒main函数为什么可以一直运行而不退出?内部维护了一个主线程RunLoop1、RunLoop与NSTimer一个比较常见的问题:滑动tableView时,定时器不生效,因为在滑动时会切换RunLoop的mode,不在defaultmode下了,所以失效,可以放到commonmode下解决。commonMode是否使用过,怎样理解?相当于defaultmode和TrackingMode的集合怎样保证子线程数据回来更新UI的时候不打断用户的滑动操作?将更新UI事件放在主线程的NSDefaultRunLoopMode上执行,滑动是在UITrackingRunLoopMode上执行一个线程对应一个runloop,主线程自动运行runloop,但是子线程的runloop需要手动运行怎样实现一个常驻线程?每次开辟子线程都会消耗cpu,在需要频繁使用子线程的情况下,会消耗大量的cpu,而且创建线程都是任务执行完成之后也就释放了,不能再次利用向子线程runloop中添加空定时器保持唤醒
runtime
运行时动态属性 objc_setAssociatedObject objc_getAssociatedObject动态创建类 objc_allocateClassPair objc_registerClassPair交换方法 swizzlingInstanceMethodInClass例子:Scrollview的头部空白;解决方法是设置其contentInsetAdjustmentBehavior属性为UIScrollViewContentInsetAdjustmentNever。但对于现存的项目来说挨个修改工作量无疑是巨大的,也容易出问题。这时候就用到Runtime了,用runtime来交换其初始化方法来统一设置这个属性就可以得到解决。获取类的一些信息(包括属性列表,方法列表,成员变量列表,和遵循的协议列表)//1.获取成员变量列表class_copyIvarList//2.获取属性列表class_copyPropertyList//3.获取方法列表class_copyMethodList//4.获取协议列表class_copyProtocolList
设计模式
工厂模式 将创建对象的方法进行封装,调用即可创建对象单例模式 保证一个类仅有一个实例,并提供一个访问它的全局访问点。 UIApplication userdefaultdispatch_once private static let test = .init代理模式 UITableViewDatasource UITableViewdelegate uitexttfielddelegate等,适用于告知代理对象进行事件处理观察者模式KVO 添加监听,发生变化时触发监听方法,使用结束后移除监听。一般是通过notificationcenter addobserver,他是一个单例,一对多的消息广播模式kvc键值编码
架构模式
Mvc mvvmMVC 通过controller来协调model和view之间的交互,将代码变得模块化mvvm在mvc的基础上,衍生出了viewmodel来处理model的数据,复用性更高,ViewModel 处理 网络请求、数据逻辑和缓存读写等等微服务架构,特别适合大型的项目,可以将模块单独拆分成不同的project,然后嵌入到项目中,kite有微服务架构的思想
设计原则
单一职责原则强调一个类或模块只负责一项功能,例如CALayer负责动画和视图显示,而UIView则提供显示内容和事件处理。开闭原则要求对修改关闭,对扩展开放,例如通过使用category来实现功能的扩展。要考虑到后续的扩展性,而不是在原有的基础上来回修改依赖倒置原则指出抽象不应该依赖于具体实现,而实现应该依赖于抽象,这有助于提高代码的灵活性和可维护性。里氏替换原则表明子类必须能够替换其基类而不会引起程序行为的变化,这在面向对象编程中尤为重要。接口隔离原则使用多个专门的协议来做接口隔离、而不是一个庞大臃肿的协议UITableviewDelegateUITableViewDataSource迪米特法则,也称为最少知识原则,强调一个对象应当对其他对象尽可能少的依赖,以减少类与类之间的耦合。实现高聚合、低耦合
App的优化
启动过程耗时优化内存泄露,包括循环引用优化闪退或卡死优化过大图片、冗余文件控制ipa包体积页面流畅度
cocoapod
Swift Package Manager
Alamofire:http网络请求框架 要在Alamofire之后解析JSON,定义一个遵循Codable协议的Swift结构体或类,然后使用 JSONDecoder,可以设置 CodingKeys
11、SnapKit:autoLayout自动布局框架
12、Kingfisher:喵神王巍写的一款关于图片下载、缓存的框架。灵感取自OC里面的SDWebImage
4、RxSwift:函数响应式编程框架,是ReactiveX的swift版本,可以简化异步操作和事件/数据流
8、ObjectMapper:把json对象映射为model对象
不可变对象的copy是浅复制,mutablecopy是深复制
可变对象的copy与mutablecopy都是深复制