2025年IOS程序员面试笔试真题库附答案_第1页
2025年IOS程序员面试笔试真题库附答案_第2页
2025年IOS程序员面试笔试真题库附答案_第3页
2025年IOS程序员面试笔试真题库附答案_第4页
2025年IOS程序员面试笔试真题库附答案_第5页
已阅读5页,还剩18页未读 继续免费阅读

下载本文档

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

文档简介

2025年IOS程序员面试笔试练习题库附答案1.简述Swift中结构体(struct)和类(class)的核心区别,至少列出5点,并说明在实际开发中如何选择使用。结构体是值类型,类是引用类型。具体区别:(1)内存管理:结构体通过复制传递(栈或堆上的值拷贝),类通过引用计数(堆上对象,ARC管理);(2)继承支持:类支持继承,结构体不支持;(3)默认构造器:结构体自动提供成员逐一构造器,类需手动实现;(4)可变性:结构体实例的属性修改需显式声明为var(即使属性是let修饰的引用类型,也无法修改指向的对象),类实例的属性修改受属性自身修饰符限制;(5)相等性判断:结构体默认支持值相等(==),类默认判断引用相等(===)。实际开发中,若需数据封装且强调值语义(如坐标点、用户信息),选结构体;若需继承、多态或需要共享状态(如视图控制器、网络管理器),选类。2.什么是循环引用?在Objective-C和Swift中分别如何检测和解决?循环引用指两个或以上对象相互强引用,导致引用计数无法降为0,内存无法释放。检测方法:-Objective-C:通过Instruments的Leaks工具检测,或在dealloc中打印日志确认是否调用;-Swift:使用Xcode的DebugMemoryGraph工具,查看对象间引用关系是否存在闭环。解决方式:-Objective-C:使用__weak修饰符打破强引用链(如代理属性用weak);block中使用__weaktypeof(self)weakSelf=self;避免捕获self;-Swift:weak修饰变量(用于可选类型),unowned修饰(用于非可选且确保对象存活时,如闭包捕获非可选的self);闭包中使用[weakself]或[unownedself]捕获列表。3.描述GCD中dispatch_sync和dispatch_async的区别,并举出一个可能导致死锁的场景及解决方法。dispatch_sync:同步提交任务,当前线程会等待任务执行完成后再继续执行后续代码;dispatch_async:异步提交任务,当前线程立即返回,任务在目标队列中异步执行。死锁场景示例:主线程中使用dispatch_sync向主队列提交任务。代码示例:```objcdispatch_queue_tmainQueue=dispatch_get_main_queue();dispatch_sync(mainQueue,^{NSLog(@"任务");//主线程等待sync任务执行,但该任务需等待主线程空闲,导致死锁});```解决方法:将sync改为async,或切换目标队列为其他串行/并发队列(如dispatch_get_global_queue)。4.解释HTTP的POST和PUT请求的语义区别,iOS中使用URLSession发送PUT请求时需注意哪些配置?语义区别:POST用于创建资源(多次提交可能产生多个资源),PUT用于替换/更新指定资源(多次提交结果一致)。URLSession使用PUT的注意点:(1)设置请求方法:request.httpMethod="PUT";(2)若需上传数据,需设置httpBody属性(如上传文件时读取Data赋值);(3)处理服务器返回的状态码(如200表示成功,404表示资源不存在);(4)若涉及大文件上传,建议使用backgroundSessionConfiguration,支持后台上传并避免进程被挂起;(5)注意请求头配置(如Content-Type需与上传数据类型匹配,如application/json或application/octet-stream)。5.如何分析iOS应用的启动耗时?请列出至少3种工具或方法,并说明优化策略。分析方法:(1)Xcode启动日志:通过设置环境变量DYLD_PRINT_STATISTICS=1,查看dyld加载时间、dylib加载、类注册等阶段耗时;(2)Instruments的TimeProfiler:记录启动过程中各函数调用时间,定位耗时函数;(3)自定义打点:在main函数开始、AppDelegate的application:didFinishLaunchingWithOptions:关键节点插入CFAbsoluteTimeGetCurrent(),计算阶段耗时。优化策略:-减少dyld阶段耗时:合并或移除不必要的动态库,使用静态库;-优化类加载:减少+load方法数量,将非必要逻辑移至+initialize或懒加载;-延迟初始化:将非首屏必要的服务(如推送注册、统计SDK)延迟到首屏渲染完成后;-减少主线程I/O:避免在启动阶段执行文件读写、数据库查询,改用异步或缓存;-优化首屏渲染:减少视图层级,避免离屏渲染,使用懒加载或异步绘制。6.Swift的可选类型(Optional)是如何实现类型安全的?简述其底层原理,并说明guardlet和iflet的使用场景差异。可选类型通过枚举实现(enumOptional<Wrapped>{casenone;casesome(Wrapped)}),编译器强制对可选值解包前进行判空检查,避免隐式解包导致的崩溃,从而保证类型安全。底层原理:可选值本质是枚举实例,存储nil(.none)或具体值(.some(value))。编译器通过SIL(SwiftIntermediateLanguage)层面的检查,确保未处理nil的可选值无法直接使用。guardlet与iflet差异:-guardlet:用于提前退出无效状态,要求必须在else分支中使用return、break、throw等终止当前作用域,确保后续代码处理的是已解包的非可选值;-iflet:用于在局部作用域内临时解包,适合处理单次条件判断。示例:```swift//guardlet适合校验前置条件funcprocessUser(_user:User?){guardletuser=userelse{print("用户不存在")return}//后续代码直接使用user(非可选)print()}//iflet适合局部解包ifletuser=currentUser{print(user.age)}else{print("无用户")}```7.什么是RunLoop?它与线程的关系是什么?在实际开发中如何利用RunLoop优化性能?RunLoop是事件循环机制,负责接收输入源(如触摸事件、网络请求)和定时源(NSTimer),协调线程处理事件,保持线程存活。关系:每个线程(包括主线程)对应一个RunLoop,子线程的RunLoop默认不启动,需手动调用run()方法。主线程的RunLoop在应用启动时自动创建并运行。性能优化应用:(1)延迟加载:将非紧急任务(如首屏外的图片加载)添加到RunLoop的空闲阶段(NSRunLoopCommonModes),利用两次事件处理的间隙执行;(2)监控卡顿:通过监听RunLoop的状态变化(kCFRunLoopBeforeSources、kCFRunLoopAfterWaiting),计算状态切换耗时,超过阈值则记录堆栈;(3)优化NSTimer:将timer添加到NSRunLoopCommonModes,避免滚动时(主RunLoop切换到UITrackingRunLoopMode)timer暂停;(4)异步绘制:在子线程绘制完成后,通过CFRunLoopPerformBlock将结果提交到主线程RunLoop,确保绘制操作在正确的RunLoop周期执行。8.简述MVVM架构与MVC的核心差异,说明MVVM中ViewModel的职责,并举例说明如何避免ViewModel过重。核心差异:MVC中View和Controller强耦合,业务逻辑混杂在Controller;MVVM通过ViewModel(VM)解耦View和Model,VM负责处理业务逻辑、数据转换,View/Controller仅负责界面展示。ViewModel职责:-处理用户输入(如按钮点击)并触发业务逻辑;-转换Model数据为View需要的展示格式(如时间戳转字符串);-管理网络请求、数据持久化等异步操作;-暴露可观察的属性(如使用Combine或KVO)供View绑定。避免ViewModel过重的方法:(1)拆分功能模块:将网络请求逻辑抽离为单独的Service层,数据转换抽离为Converter类;(2)使用协议抽象:定义ViewModel协议,约束其职责范围,避免添加与业务无关的方法;(3)复用ViewModel:通过组合模式,让复杂页面的ViewModel由多个子ViewModel组成(如订单详情页VM包含地址VM、商品列表VM);(4)限制状态管理:仅保留与界面相关的状态,将全局状态(如用户登录状态)移至单例或Redux-like存储层。9.解释iOS中的内存泄漏和内存溢出的区别,如何使用Instruments检测内存泄漏?列举3种常见的泄漏场景。内存泄漏:对象无法被释放(引用计数无法降为0),导致内存持续增长;内存溢出(OOM):应用占用内存超过系统限制,被强制终止。Instruments检测泄漏步骤:(1)启动Instruments,选择Leaks模板;(2)运行应用,触发可能泄漏的操作(如反复进入/退出页面);(3)点击Leaks导航栏的“CheckforLeaks”按钮,工具会标记泄漏的内存块;(4)查看CallTree,定位泄漏对象的创建位置及引用链。常见泄漏场景:(1)非静态NSTimer:timer对target强引用,若未在dealloc中invalidate,target无法释放;(2)闭包捕获self:未使用[weakself]导致闭包持有self,形成循环引用;(3)代理模式:代理属性错误使用strong修饰(应使用weak),导致双方互相强引用;(4)单例持有对象:单例生命周期与应用一致,若错误持有ViewController实例,会导致其无法释放。10.Swift5.5引入的async/await如何解决传统回调地狱问题?对比GCD和Combine,说明其优势。async/await通过结构化并发将异步代码写成同步风格,避免嵌套回调。传统GCD需多层dispatch_async嵌套,Combine需链式调用多个操作符,而async/await使代码线性执行,可读性更高。优势:(1)语法简洁:异步任务通过await关键字调用,无需闭包嵌套;(2)错误处理统一:使用do-try-catch捕获错误,替代NSError回调或Combine的failure处理;(3)自动任务管理:async函数的调用栈隐式管理任务生命周期,避免手动取消(如Task的取消机制);(4)与现有API兼容:通过@asyncHandler桥接传统回调API(如URLSession的data(from:delegate:)),支持逐步迁移。示例(获取用户信息):```swift//传统回调fetchUserInfo{user,errorinifletuser=user{self.fetchUserPosts(user.id){posts,errorin//嵌套回调}}}//async/awaitdo{letuser=tryawaitfetchUserInfo()letposts=tryawaitfetchUserPosts(user.id)//线性执行}catch{print(error)}```11.如何优化UITableView的滑动流畅度?至少列出5种方法,并说明原理。(1)缓存行高:实现tableView(_:heightForRowAt:)时使用字典缓存计算过的行高,避免重复计算(尤其动态内容场景);(2)异步绘制:在子线程计算绘制内容(如文字布局、图片处理),主线程仅负责渲染(使用draw(_:)或CALayer的delegate);(3)减少离屏渲染:避免使用cornerRadius+clipsToBounds、mask、shadow等触发离屏渲染的属性,改用预渲染图片或设置layer.shouldRasterize(需权衡光栅化内存开销);(4)重用单元格:正确使用dequeueReusableCell(withIdentifier:for:),避免重复创建单元格;(5)限制同时加载的图片:滑动时暂停图片加载(通过UIScrollView的delegate方法检测滚动状态),减速时恢复加载;(6)优化数据模型:确保cellForRowAtIndexPath中仅处理必要逻辑,将复杂数据转换提前到数据加载阶段。12.描述iOS中应用沙盒(Sandbox)的目录结构及各目录的作用,如何限制第三方键盘访问沙盒?沙盒目录结构(以iOS17为例):-/AppName.app:应用程序包,包含可执行文件、资源文件(只读,更新应用时替换);-Documents:存储用户数据(如用户提供的文件),iCloud会备份此目录;-Library/Caches:存储缓存数据(如网络请求缓存),iCloud不备份,系统可能清理;-Library/Preferences:存储NSUserDefaults数据(由系统管理,自动同步);-tmp:临时文件(系统可能随时清理,应用退出后建议主动删除)。限制第三方键盘访问沙盒:通过应用扩展的权限控制,在Info.plist中设置NSExtensionAttributes的RequestsOpenAccess为YES(需用户手动启用“完全访问”),未启用时键盘仅能访问自身沙盒的特定目录(如Documents),启用后可访问更多系统资源(但仍受沙盒限制)。13.什么是方法交换(MethodSwizzling)?在Objective-C中如何实现?需注意哪些问题?方法交换是运行时替换方法实现的技术,通过交换两个方法的IMP(函数指针),达到修改原有方法行为的目的。实现步骤:(1)获取原方法和替换方法的SEL(如原方法viewDidLoad,替换方法swizzled_viewDidLoad);(2)使用class_getInstanceMethod或class_getClassMethod获取Method;(3)使用method_exchangeImplementations交换两个Method的IMP。注意事项:(1)线程安全:需在+load方法中执行交换(保证类加载时只执行一次),避免多线程同时交换导致IMP混乱;(2)父类方法覆盖:若子类未实现原方法,交换会影响父类(需先调用class_addMethod尝试添加原方法,再交换);(3)保留原功能:替换方法中需调用原方法(通过交换后的SEL调用,避免递归);(4)版本兼容:不同iOS版本可能方法实现不同,需做版本判断(如使用respondsToSelector:)。14.简述SwiftUI中@State、@Binding和@ObservableObject的作用及使用场景。@State:用于管理视图的私有状态(值类型),状态变化时触发视图重绘。适用于视图内部的临时状态(如开关按钮的isOn状态)。@Binding:用于双向绑定,将父视图的@State传递给子视图,子视图修改绑定值会同步到父视图。适用于子视图需要修改父视图状态的场景(如输入框的文本绑定)。@ObservableObject:用于管理可观察的对象(引用类型),配合@Published属性包装器,当属性变化时通知订阅者(视图)。适用于复杂业务逻辑(如网络请求结果、数据模型),通常作为环境对象(@EnvironmentObject)或直接注入视图。示例:```swiftstructParentView:View{@Stateprivatevarusername=""varbody:someView{ChildView(username:$username)//传递@Binding}}structChildView:View{@Bindingvarusername:String//双向绑定varbody:someView{TextField("输入用户名",text:$username)}}classUserStore:ObservableObject{@Publishedvaruser:User?//属性变化触发视图更新funcfetchUser(){//网络请求后赋值user}}structUserView:View{@EnvironmentObjectvarstore:UserStore//注入ObservableObjectvarbody:someView{Text(store.user?.name??"未登录")}}```15.如何实现一个线程安全的计数器?使用GCD和OSSpinLock(或os_unfair_lock)分别写出示例代码,并说明选择锁的依据。线程安全的核心是保证增/减操作的原子性。GCD实现(使用串行队列):```objc//Objective-C@interfaceThreadSafeCounter:NSObject-(void)increment;-(NSInteger)currentValue;@end@implementationThreadSafeCounter{dispatch_queue_t_queue;NSInteger_value;}-(instancetype)init{if(self=[superinit]){_queue=dispatch_queue_create("com.example.counter.queue",D

温馨提示

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

评论

0/150

提交评论