美文网首页
iOS面试题

iOS面试题

作者: AbaryLiu | 来源:发表于2023-07-30 15:30 被阅读0次
    1. 一个NSObject对象占用多少内存?

      • 系统分配了16个字节给NSObject对象(通过malloc_size函数获得)
      • 但NSObject对象内部只使用了8个字节的空间(64bit环境下,可以通过class_getInstanceSize函数获得),NSObject对象只有一个isa指针,这8个字节就是用来存放isa这个成员变量
    2. 对象的isa指针指向哪里?

      • instance对象的isa指向class对象
      • class对象的isa指向meta-class对象
      • meta-class对象的isa指向基类的meta-class对象,基类的meta-class对象的isa指向自身
    3. OC的类信息存放在哪里?

      • 实例方法、属性信息、成员变量信息、协议信息都存放在class对象中
      • 类方法存放在meta-class对象中
      • 成员变量的具体值存放在instance对象中
    4. iOS用什么方式实现对一个对象的KVO?(KVO的本质是什么)

      • 使用RuntimeAPI动态生成一个子类(NSKVONotifying_XXX),并且让instance对象的isa指向这个全新的子类
      • 当修改instance对象的这个属性时(调用这个属性的setter方法),会调用Foundation的_NSSetXXXValueAndNotify函数
        • -willChangeValueForKey:
          • 记录旧值,触发之后的didChangeValueForKey
        • 原本父类的setter方法
          • 赋值
        • -didChangeValueForKey:
          • 内部触发监听器Observer的监听方法(-observeValueForKeyPath:ofObject:change:context:)
      • PS:直接监听成员变量不起效,除非有成员变量的setXXX:方法,因为生成的子类要重写这个方法来进行监听 // 直接修改不会触发KVO
      • self.per1->_height += 1;
      • // 这样才会触发KVO,说明NSKVONotifying_XXX内部重写的是setXXX:方法
      • [self.per1 setHeight:10];
    5. 如何手动触发KVO

      • 手动调用-willChangeValueForKey:方法和-didChangeValueForKey:方法
      • 必须先-willChangeValueForKey:后-didChangeValueForKey:,且缺一不可
    6. 直接修改成员变量会触发KVO吗?

      • 不会触发KVO,调用setter方法才会触发KVO,因为KVO生成的子类内部重写的是这个属性的setter方法
    7. Category的实现原理。

      • Category编译之后的底层结构是struct category_t,里面存储着分类的实例方法、类方法、属性、协议信息
      • 在程序运行的时候,Runtime会将Category的数据,合并附加到类信息中(class对象、meta-class对象)
      • PS1:Runtime将分类的方法放在原来的方法的前面,因此分类的方法优先级更高,所以如果有重名的方法,会优先调用分类的方法
      • PS2:如果多个分类有重名的方法,【后】编译的分类的方法优先级更高,因为Runtime里面的方法列表附加操作的顺序是下标从大到小进行的,越往后越靠前 · PS3:在 Tatgets -> Build Phases -> Compile Sources 中可以控制编译顺序,编译是从上到下的顺序,想调用的优先级高的就往下放
    8. Category和Class Extension的区别是什么?

      • Class Extension在编译的时候,它的数据就已经包含在类信息中
      • Category是在运行时,才会将数据合并到类信息中
    9. Category中有load方法吗?load方法是什么时候调用的?load方法能继承吗?

      • 有。
      • load方法在Runtime加载类、分类的时候调用
      • load方法可以继承,在子类没有重写load方法,主动去调用load方法(消息发送机制)时会调用父类的load方法,说明是有继承关系的,但是一般情况下不会主动去调动load方法,都是让系统自动调用(在Runtime加载时是直接拿到load方法的地址去调用,之后手动调用时其实就是利用消息发送机制:子类没有load方法就会去父类的方法列表里面找)
    10. load、initialize方法的区别是什么?它们在Category中的调用的顺序?以及出现继承时它们之间的调用过程?

      • 区别:
        • 调用方式
          • load是根据函数地址直接调用
          • initialize是通过objc_msgSend调用
        • 调用时刻
          • load是Runtime加载(所有参与编译的)类、分类的时候调用,只会调用1次
          • initialize是类第一次接收到消息的时候调用,每一个类只会调用initialize一次,但父类的initialize方法可能会被调用多次(例如:子类和子类的分类都没有实现initialize方法时就会去调用父类的initialize方法)
      • 调用顺序
        • load:
          • 先调用类的load
            • 先编译的类,优先调用load
            • 调用子类的load之前,会先调用父类的load
          • 再调用分类的load
            • 先编译的分类,优先调用load
        • initialize:
          • 调用父类的initialize(初始化)
          • 再调用子类的initialize(初始化)
        • 可能最终调用的是父类的initialize(子类没有实现initialize)
    11. Category能否添加成员变量?如果可以,如何给Category添加成员变量?

      • 不能直接给Category添加成员变量,但是可以间接实现Category有成员变量的效果(关联对象)。
    12. 通过KVC修改属性会触发KVO么?

      • 会触发
        • 通过-_isKVOA方法判定是否有监听器(_isKVOA为KVO生成的NSKVONotifying_XXX的方法)
        • 内部实现:
          • [per willChangeValueForKey:@"age"]; // 保存旧值,标识等会调用didChangeValueForKey
          • 执行setter方法,没有则直接对成员变量赋值:per->_age = 20;
          • [per didChangeValueForKey:@"age"]; // 通知监听器,XX属性值发送了改变
    13. KVC的赋值和取值过程是怎样的?原理是什么?

      • 赋值 -setValue:forKey: 的过程:
        • 按照优先级为 -setKey: 、-_setKey: 的顺序查找方法
          • 找到:传递参数,调用方法
        • 找不到,查看 +accessInstanceVariablesDirectly 方法是否为YES
          • NO,不允许:抛出NSUnknownKeyException异常
        • YES,允许,按照优先级为 _key、_isKey、key、isKey 的顺序查找成员变量
          • 找到:直接赋值
          • 找不到:抛出NSUnknownKeyException异常
      • 取值 -valueForKey: 的过程:
        • 按照优先级为 -getKey 、-key、-isKey、-_key 的顺序查找方法
          • 找到:调用方法,取值
        • 找不到,查看 +accessInstanceVariablesDirectly 方法是否为YES
          • NO,不允许:抛出NSUnknownKeyException异常
        • YES,允许,按照优先级为 _key、_isKey、key、isKey 的顺序查找成员变量
          • 找到:直接取值
          • 找不到:抛出NSUnknownKeyException异常
      • PS:+accessInstanceVariablesDirectly:是否允许访问成员变量,默认为YES
    14. block的原理是怎样的?本质是什么?

      • 本质就是一个封装了函数调用(impl.FuncPtr)以及函数调用环境(函数需要的参数)的OC对象(impl.isa)
    15. __block的作用是什么?有什么使用注意点?

      • 被__block修饰的变量会被包装成另一种对象,可以用于解决block内部无法修改auto变量值的问题,先通过这个对象再通过__forwarding指针就可以访问到那个变量进行修改
      • 注意内存管理的问题,如果修饰的是对象,block拷贝到堆上时,在ARC环境下会对对象做retain操作,而MRC环境下则不会。
    16. block的属性修饰词为什么是copy?使用block有哪些使用注意?

      • block一旦没有进行copy操作就不会在堆上,拷贝在堆上是为了控制block的生命周期,进行内存管理
      • 使用注意:循环引用
    17. block在修改NSMutableArray,需不需要添加__block?

      • 不需要,NSMutableArray有相应的api来修改数组内容,这是直接使用这个变量,而不是修改这个变量,所以不需要__block修饰,
      • __block能不加就尽量不加,加了数据结构就变得复杂,会生成一个额外的结构体
    18. 讲一下OC的消息机制

      • OC中的方法调用其实都是转成了objc_msgSend函数的调用,给receiver(方法调用者)发送一条消息(selector方法名)
      • objc_msgSend底层有3大阶段:
        • 消息发送(当前类、父类中查找)
        • 动态方法解析(resolveInstanceMethod、resolveClassMethod)
        • 消息转发
    19. 消息转发机制流程

      • 调用forwardingTargetForSelector:方法,返回转发对象
        • 返回值不为nil,将消息转发给别人
          • objc_msgSend(转发对象, SEL);
        • 返回值为nil
          • 调用methodSignatureForSelector:方法,返回方法签名(返回值和参数信息)
            • 返回值不为nil
              • 调用forwardInvocation:方法 --> 根据方法签名包装成一个NSInvocation给到方法中让我们使用,自定义任何逻辑
            • 返回值为nil
              • 调用doseNotRecognizeSelector:方法 --> 报错
    20. 什么是Runtime?平时项目中有用过么?

      • Runtime:
        • OC是一门动态性比较强的编程语言,允许很多操作推迟到程序运行时再进行
        • OC的动态性就是由Runtime来支撑和实现,Runtime是一套C语言的API,封装了很多动态性相关的函数。
        • 平时编写的OC代码,底层都是转换成了Runtime API进行调用
      • 平时项目中用到的地方:
        • 利用关联对象(AssociatedObject)给分类添加属性
        • 遍历类的所有成员变量(可以访问私有成员变量):修改私有成员变量、字典转模型、自动归档解档
        • 交换方法实现:交换系统的方法
        • 利用消息转发机制解决方法找不到的异常问题
        • 可以在程序运行的过程中动态生成新的类(KVO)
    21. 你理解的多线程?

      • 多条线程同时工作,充分利用设备的多核,提高运行效率
    22. iOS的多线程方案有哪几种?你更倾向于哪一种

      • pthread
      • NSThread
      • GCD(倾向)
      • NSOperation(倾向)
    23. 你在项目中用过GCD吗?

      • 有,用于开启多线程处理复杂任务、线程同步、读写安全、延时任务、一次性任务、快速迭代等
    24. 说一下NSOperationQueue和GCD的区别,以及各自的优势

      • NSOperationQueue
        • 是OC对象,使用起来更加面向对象,底层是用GCD实现,其实就是对GCD的封装
        • 加入队列的任务可以随时取消执行(已经开始的任务无法停止)
        • 即使是在并发队列当中,同个队列里的不同任务之间可以设置依赖关系
        • 可以简单设置任务的优先级,使得在并发队列中的任务也能区分先后地执行
        • 可以监听任务的执行状态
        • 可继承,定制自由度高
      • GCD
        • 底层的C语言构成的API,轻量级的数据结构,性能比NSOperationQueue高
        • 加入队列的任务可以无法停止执行
        • 同个队列的不同的任务之间不可以设置依赖关系
        • 只能区分队列的优先级,区分任务的优先级需要复杂代码
        • 只能手动写代码监听执行状态
        • 无法定制别的功能
    25. 你对 iOS 内存管理的理解

      • 使用引用计数来管理OC对象的内存
      • 一个新创建的OC对象引用计数默认是1,当引用计数减为0,OC对象就会销毁,释放其占用的内存空间
      • 调用retain会让OC对象的引用计数+1,调用release会让OC对象的引用计数-1
      • 内存管理的经验总结:
        • 当调用alloc、new、copy、mutableCopy方法返回了一个对象,在不需要这个对象时,要调用release或者autorelease来释放它
        • 想拥有某个对象,就让它的引用计数+1;不想再拥有某个对象,就让它的引用计数-1
    26. ARC 都帮我们做了什么? · ARC是【LLVM编译器】和【Runtime系统】相互协助的一个结果 - LLVM:例如在某个作用域的{}即将结束的时候,自动对里面的对象调用release方法 - Runtime:例如weak指针的实现,在程序运行的过程中,监控到对象要销毁时会去清空对象的弱引用

    27. autorelease对象在什么时机会被调用release

      • 如果是自定义的autoreleasepool,会在自动释放池的{}结束前一刻调用release
      • 如果不是在自定义的autoreleasepool,而是在main函数的autoreleasepool,是由RunLoop控制的,可能是在某次RunLoop循环中,在RunLoop休眠之前调用了release(kCFRunLoopBeforeWaiting)
    28. 方法里有局部对象, 出了方法后会立即释放吗

      • 【MRC】环境下不会,会等RunLoop那次循环的休眠之前才释放
      • 【ARC】环境下会,LLVM会在方法的{}即将结束的时候,自动对里面的对象调用release方法
    29. 你在项目中是怎么优化内存的?

      • 减少类的创建,多复用
      • 尽量用轻量级的对象
      • 图片不能过大,尽可能少用图片
      • 少用定时器
      • 控制线程的最大并发数量
      • 减少、压缩网络数据
    30. 列表卡顿的原因可能有哪些?你平时是怎么优化的?

      • 使用MVVM模式进行开发,ViewModel在子线程获取Model的同时也将视图的UI属性都提前计算好,再回到主线程刷新,尽可能减少在滚动的时候的计算操作
      • 图片圆角不使用layer.masksToBounds = YES、layer.cornerRadius大于0的方式设置,通过CoreGraphics绘制裁剪圆角,或者叫美工提供圆角图片
      • 要使用阴影就必须也设置layer.shadowPath
    31. 遇到tableView卡顿嘛?会造成卡顿的原因大致有哪些?

      • 高度并没有提前算好、离屏渲染、视图结构过于复杂、视图的UI属性修改频繁、透明的视图过多、图片过大
    32. 讲讲 MVC、MVVM、MVP,以及你在项目里具体是怎么写的?

      • MVC:将View内部的细节封装起来了,View依赖Model,展示的内容由Model决定,Controller负责控制View展示哪个Model的内容
      • MVVM:View和Model互不依赖,都经由ViewModel进行联动,ViewModel负责Model的加载和View需要的其他数据的处理,并将其内容展示到View上
      • MVP:跟MVC类似,只是把Controller的工作交付给Presenter,并且View不依赖Model,由Presenter控制Model的内容数据展示在哪个View上

    相关文章

      网友评论

          本文标题:iOS面试题

          本文链接:https://www.haomeiwen.com/subject/qpsspdtx.html