iOS 进阶面试题及详细答案(中高级)-1_第1页
iOS 进阶面试题及详细答案(中高级)-1_第2页
iOS 进阶面试题及详细答案(中高级)-1_第3页
iOS 进阶面试题及详细答案(中高级)-1_第4页
iOS 进阶面试题及详细答案(中高级)-1_第5页
已阅读5页,还剩7页未读, 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

iOS进阶面试题及详细答案(中高级)一、Objective-C底层原理1.详细讲解OC的消息发送机制,以及动态决议、慢速转发、快速转发的完整流程答案:OC是动态语言,方法调用本质是向对象发送消息objc_msgSend,全程分为快速查找、动态决议、消息转发三个阶段。1)快速发送阶段(核心流程)当调用[objmethod],底层执行:1.校验receiver是否为nil,nil直接返回,不崩溃;2.获取对象的isa指针,找到当前类的方法缓存表(buckets);3.缓存命中则直接执行方法实现,流程结束;4.缓存未命中,遍历当前类的方法列表(methodList);5.当前类未找到,顺着父类继承链逐级向上查找;6.找到方法后,将方法缓存到当前类的缓存表,下次调用直接命中;7.父类链遍历完毕仍未命中,进入消息转发流程。2)动态决议(第一次补救)系统会优先调用两个类方法,动态添加方法实现:-实例方法未实现:+resolveInstanceMethod:-类方法未实现:+resolveClassMethod:开发者可以在该方法中,通过class_addMethod动态为SEL绑定IMP。添加成功后,系统会重新走一次消息发送流程,正常执行方法。若未处理,进入下一步。3)快速转发(二次补救)调用-forwardingTargetForSelector:,可以返回一个能够处理该方法的对象。优势:开销极低,只是单纯转发对象,不会生成消息签名、调用栈,适合简单的方法转发场景。若返回nil,进入慢速转发。4)慢速转发(最终补救)1.调用-methodSignatureForSelector:,生成方法签名(参数、返回值类型);2.签名生成成功后,调用-forwardInvocation:;3.在forwardInvocation中可以自定义处理逻辑:转发给多个对象、修改参数、拦截方法等;4.若所有流程都未处理,最终触发doesNotRecognizeSelector:,程序崩溃。总结:消息转发是OC动态特性的核心,日常用到的热修复、方法拦截、组件解耦,底层均基于此实现。2.说说isa指针的结构,以及arm64架构下的优化答案:isa是对象的核心指针,本质是一个联合体,存储对象的类信息、内存标记、引用计数等核心数据。1)arm64之前(32位架构)isa直接指向类对象地址,结构简单,但是内存利用率极低,浪费大量存储空间。2)arm64架构优化(当前设备通用)苹果将isa改为压缩isa(TaggedPointer思想延伸),用位域拆分64位内存,每一位存储不同信息,极大节省内存。核心位域含义:-isa指针(33位):存储类对象地址(足够覆盖所有内存地址)-weaklyreferenced(1位):是否被弱引用指向-deallocating(1位):对象是否正在释放-has_sidetable_rc(1位):引用计数是否存在辅助表中-extra_rc(19位):存储部分引用计数(优先存在isa中,超出后存入散列表)-magic(6位):校验标记,用于判断对象是否初始化完成-nonpointer(1位):标记是否为优化后的isa,1代表有效优化核心亮点:日常对象的引用计数不需要额外开辟内存,直接存在isa位域中,只有引用计数过大、或对象是字符串、TaggedPointer类型时,才会用到SideTable辅助表。3.TaggedPointer原理、作用及使用场景答案:TaggedPointer是苹果针对小内存对象的优化方案,主要用于NSNumber、NSString、NSDate等轻量对象。1)核心原理普通OC对象需要堆内存开辟空间、指针引用、内存管理,开销大。TaggedPointer直接将对象数据存储在指针地址本身,不分配堆内存,不是真正的堆对象。指针的高位标记类型,低位存储真实数据,整个对象存在栈中,读写速度极快。2)核心特点-无堆内存分配,无需malloc/free-无retain、release操作,内存管理零开销-访问速度比普通对象快数倍-线程绝对安全,不会出现野指针、重复释放问题3)生效场景-短字符串(长度极短的NSString)-小数值NSNumber(int、double小数值)-常规NSDate对象4)失效场景数据超出存储容量、字符串过长、数值过大时,会自动降级为普通堆对象。面试延伸:TaggedPointer对象不能进行自定义retain、release操作,强行操作会直接崩溃。4.引用计数底层实现、SideTable结构、内存泄漏常见场景答案:1)引用计数存储逻辑arm64架构下,对象的引用计数优先存在isa的extra_rc位域中,可存储的数值有限。当引用计数超出阈值、或对象不支持isa存储时,会存入SideTable散列表。2)SideTable结构SideTable是一个全局哈希表,系统会开辟多张SideTable分担压力,避免单表锁竞争。每张表包含三个核心结构:-spinlock_t锁:自旋锁,保证多线程下引用计数操作安全-refcounts:存储对象引用计数的哈希表-weak_table:存储所有弱引用指针的哈希表3)retain/release底层逻辑-retain:优先累加isa中的extra_rc,满了则去SideTable累加计数-release:优先递减extra_rc,减为0后,清理SideTable记录、清空弱引用指针、调用dealloc4)常见内存泄漏场景1.循环引用:Block内部引用外部self、Delegate未用weak、父子对象互相强引用;2.定时器未销毁:NSTimer、CADisplayLink强引用target,页面退出未invalidate;3.常驻线程:创建的子线程runloop不退出,线程对象无法释放;4.全局静态变量持有临时对象;5.视图层循环嵌套、手势、KVO未移除监听。二、RunLoop核心进阶1.RunLoop运行机制、Mode作用、常驻线程实现原理答案:RunLoop是事件循环机制,本质是持续接收事件、休眠、唤醒的循环,保证线程不退出、随时处理任务。主线程RunLoop默认开启,子线程默认关闭。1)核心作用-保活线程,避免子线程执行完任务立即销毁-监听事件:触摸、滚动、定时器、网络回调、端口消息、Block任务-合理休眠,节省CPU资源,无事则休眠,有任务则唤醒2)RunLoopMode原理Mode是RunLoop的运行模式,同一时间RunLoop只会处于一种Mode,不同Mode隔离任务,互不干扰。常用Mode:-kCFRunLoopDefaultMode:默认模式,日常业务、定时器、普通任务运行在此模式,滑动屏幕时会退出此模式,导致NSTimer暂停-UITrackingRunLoopMode:界面滚动模式,屏幕滑动时切换至此模式,保证滑动流畅-kCFRunLoopCommonModes:占位模式,等价于默认+滚动模式,定时器添加到此模式可解决滑动暂停问题3)常驻线程实现原理1.子线程创建后,手动获取RunLoop;2.向RunLoop添加Source/Timer/Port资源(无资源会直接退出循环);3.调用run方法开启循环;4.线程持续休眠等待任务,实现常驻;5.需手动标记退出条件,避免线程永久常驻造成内存泄漏。2.NSTimer滑动卡顿、失效问题底层原因及解决方案答案:1)失效/暂停原因默认创建的NSTimer会添加到kCFRunLoopDefaultMode,当列表、页面滑动时,RunLoop切换到UITrackingRunLoopMode,默认模式下的定时器任务无法执行,表现为暂停、卡顿。2)三种解决方案1.将Timer添加到kCFRunLoopCommonModes,适配所有常用模式,滑动不暂停;2.使用GCD定时器(dispatch_source_timer),不依赖RunLoop,子线程即可运行,不受页面滑动影响,精度更高;3.监听滚动状态,手动切换定时器启停(兜底方案)。进阶区别:NSTimer依赖RunLoop调度,有RunLoop卡顿、任务堆积误差;GCD定时器基于内核调度,精度高、无依赖、性能更好,项目中优先使用。三、多线程与GCD进阶1.GCD队列类型、死锁场景全面解析答案:1)GCD核心队列类型-主队列(mainqueue):串行队列,所有UI操作必须在主队列执行,线程为主线程-全局并发队列(globalqueue):系统提供,分高、中、低、后台优先级,可并发执行多个任务-自定义串行队列:一次只执行一个任务,任务有序执行,线程安全,适合数据读写、数据库操作-自定义并发队列:可同时执行多个任务,适合批量耗时操作2)常见死锁场景(面试高频)场景1:主线程同步执行主队列任务(必死锁)主线程中调用dispatch_sync(dispatch_get_main_queue(),^{})。当前线程等待任务执行完成,任务需要等待当前线程空闲,互相等待导致死锁。场景2:同一串行队列嵌套同步任务(必死锁)自定义串行队列,外层同步任务中嵌套内层同步任务,队列串行先进先出,内层任务无法执行,外层持续等待,形成死锁。场景3:多队列互相同步等待队列A同步等待队列B任务,队列B同步等待队列A任务,循环等待死锁。核心结论:串行队列同步嵌套、主线程同步主队列任务,是100%死锁;并发队列嵌套不会死锁。2.GCD栅栏函数、信号量、组队列的实战使用场景答案:1)栅栏函数dispatch_barrier只对自定义并发队列生效,全局队列无效。作用:阻塞当前队列,等待队列中所有已有任务执行完毕,再执行栅栏任务,栅栏任务完成后,后续任务继续并发执行。实战场景:多读单写,多个读任务并发执行,写任务通过栅栏隔离,保证读写安全,数据不混乱。2)信号量dispatch_semaphore核心作用:控制最大并发数、线程加锁、异步任务同步化。实战场景:-限制网络请求最大并发数,避免同时发起大量请求-异步接口转同步,等待多个异步任务完成后再执行后续逻辑-简易线程锁,替代@synchronized,性能更高3)GCD组dispatch_group作用:监听一组异步任务全部完成,统一回调。实战场景:-多个并行网络请求,全部成功后刷新页面-批量文件读写、批量图片处理,统一收尾3.@synchronized底层原理及优缺点,对比GCD信号量锁答案:1)@synchronized底层原理底层基于递归锁实现,系统会根据锁定的对象,在全局锁表中匹配对应的锁对象,实现加锁解锁。支持递归调用,同一线程可多次加锁,不会死锁。2)优点-语法简单,无需手动管理锁生命周期-自带异常保护,代码崩溃会自动解锁,不会永久死锁-支持递归锁场景3)缺点-性能差,底层需要查表、哈希匹配、容错处理,开销远大于信号量-锁粒度不可控,只能以代码块为单位加锁4)对比GCD信号量锁-信号量性能更高,纯内核调度,无多余开销-信号量不支持递归,递归场景会死锁-信号量需要手动控制加锁解锁,代码严谨性要求更高业务选型:简单快速加锁、容错要求高用@synchronized;高频读写、性能敏感场景用信号量。四、内存管理与性能优化1.野指针、空指针区别,EXC_BAD_ACCESS崩溃底层原因答案:1)空指针(nil)指针指向空地址,没有指向任何对象。OC中向nil发送方法、赋值不会崩溃,系统直接忽略,返回空值,是安全的。2)野指针指针指向已经销毁、释放的堆内存,指针地址合法,但内存已被回收、可能被其他数据覆盖。访问野指针属于非法内存访问,直接崩溃。3)EXC_BAD_ACCESS崩溃核心原因程序访问了不被允许的内存地址,90%场景为野指针,其余为内存越界、重复释放、栈溢出。野指针解决方案:对象释放后手动置空、用weak修饰临时对象、开启僵尸对象调试模式排查。2.页面卡顿、FPS低的常见原因及全套优化方案答案:UI渲染依托主线程RunLoop,主线程阻塞、渲染耗时、图层过多都会导致卡顿,正常流畅FPS为60。1)卡顿核心原因-主线程执行耗时操作:数据解析、文件读写、网络回调处理-视图层级过多、重复渲染、离屏渲染大量触发-图片过大、解码耗时、未做尺寸压缩-RunLoop任务堆积,定时器、监听、Block任务过多-列表复用不当,cell内部创建大量视图、重复计算布局2)全套优化方案1.所有耗时操作(网络、解析、读写、计算)全部丢子线程,主线程只做UI刷新;2.优化视图层级,减少多余嵌套,尽量扁平化布局;3.规避离屏渲染:圆角、阴影、边框尽量用裁剪、预渲染,避免动态实时渲染;4.图片预处理:下载后压缩尺寸、解码,避免实时解码卡顿;5.列表优化:高度缓存、cell复用、懒加载视图、减少实时计算;6.优化RunLoop任务,批量处理刷新、减少频繁UI刷新;7.使用Instruments工具定位CPU、GPU、内存、卡顿耗时点。3.离屏渲染触发场景及优化手段答案:1)什么是离屏渲染正常UI渲染是在屏幕帧缓冲区直接渲染,离屏渲染是GPU需要开辟额外的离线缓冲区先渲染图层,再合并到屏幕,开销极大,极易造成卡顿、掉帧。2)常见触发场景-视图设置cornerRadius+masksToBounds同时开启-设置阴影shadowColor/shadowOffset未指定shadowPath-图层遮罩、渐变、模糊效果-自定义绘制、drawRect大量绘制逻辑3)优化手段1.圆角优化:使用贝塞尔曲线裁剪、UIView扩展预渲染圆角,避免同时开启裁剪和圆角;2.阴影优化:手动设置shadowPath,固定阴影路径,让GPU无需实时计算;3.减少模糊、渐变等特效的动态渲染,静态视图提前渲染缓存;4.尽量避免在滚动列表cell中使用会触发离屏渲染的属性。五、Swift进阶1.Swift内存管理、ARC机制、闭包循环引用解决方案答案:Swift沿用OC的ARC内存管理机制,基于引用计数管理堆对象内存,栈对象值类型无需管理。1)值类型与引用类型内存区别-值类型(struct、enum、基本数据类型):存储在栈中,赋值即拷贝,无引用计数,无内存泄漏-引用类型(class):存储在堆中,栈中存指针,依靠ARC计数管理内存2)闭包循环引用核心原因实例对象强引用闭包,闭包内部捕获实例对象,双方互相强引用,引用计数无法归零,造成内存泄漏。3)三种解决方案-weak:弱引用,对象释放后指针置空,适合对象可能提前释放的场景-unowned:无主引用,不计数、不置空,适合对象生命周期和闭包一致的场景,性能更高,野指针风险更高-手动置空闭包:任务完成后清空闭包,打破引用环2.Swift懒加载、static、lazy、final关键字作用答案:1)lazy懒加载属性第一次访问时才初始化,延迟开辟内存,节省启动开销。特点:支持可变、可重复赋值、非线程安全、常用于UI控件、大型对象初始化。2)static静态修饰修饰类属性、类方法,属于类本身,不属于实例对象,全局唯一,内存只初始化一份,常用于单例、全局配置。3)final关键字修饰类、方法、属性,禁止被继承、重写。核心优势:编译器可静态优化,去掉动态派发,直接地址调用,提升执行性能。六、组件化与架构、编译进阶1.组件化实现方案、优缺点,如何解决组件间通信耦合答案:1)组件化核心目的解决项目臃肿、耦合严重、编译慢、多人开发冲突问题,实现功能拆分、独立编译、按需集成。2)主流实现方案-基于路由组件化(最常用):通过URL路由跳转,组件间无直接依赖,通过路由中间层通信-基于协议组件化:通过Protocol定义接口,组件依赖协议不依赖具体实现,解耦性更强-CocoaPods私有库组件化:将各功能模块拆分为独立pod库,支持单独编译、版本管理3)组件通信解耦方案1.路由跳转+参数传递,无直接类依赖;2.基于通知、闭包、代理回调跨组件通信;3.全局中间件统一管理组件服务注册、调用;4.协议注册匹配,动态获取组件服务。4)优缺点优点:编译速度提升、业务解耦、分工清晰、支持组件复用、便于迭代维护;缺点:初期搭建成本高、需要统一规范、路由参数校验繁琐。2.Xcode编译流程、Clang与LLVM作用答案:完整编译流程:预处理>词法语法分析>生成AST>静态分析>生成IR中间代码>汇编生成机器码>链接>打包。1)Clang作用基于LLVM的前端编译器,负责OC、C、C++代码的编译。核心工作:预处理(宏替换、头文件展开)、语法校验、生成抽象语法树AST、生成IR中间代码。日常的语法报错、警告、静态检查均由Clang完成。2)LLVM作用编译器后端,负责代码优化、IR代码编译、生成

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论