美文网首页工作生活
常见基础面试题总结

常见基础面试题总结

作者: osnail | 来源:发表于2019-07-01 10:58 被阅读0次
    一个NSObject对象占用多少内存?

    系统分配了16个字节给NSObject对象(通过malloc_size函数获得)
    但NSObject对象内部只使用了8个字节的空间(64bit环境下,32位环境下是4个字节,可以通过class_getInstanceSize函数获得)

    对象的isa指针指向哪里?

    instance对象的isa指向class对象
    class对象的isa指向meta-class对象
    meta-class对象的isa指向基类的meta-class对象

    OC的类信息存放在哪里?
    image.png
    image.png

    对象方法、属性、成员变量、协议信息,存放在class对象中
    类方法,存放在meta-class对象中
    成员变量的具体值,存放在instance对象

    iOS用什么方式实现对一个对象的KVO?(KVO的本质是什么?)

    利用RuntimeAPI动态生成一个子类,并且让instance对象的isa指向这个全新的子类
    当修改instance对象的属性时,会调用Foundation的_NSSetXXXValueAndNotify函数
    willChangeValueForKey:
    父类原来的setter
    didChangeValueForKey:
    内部会触发监听器(Oberser)的监听方法( observeValueForKeyPath:ofObject:change:context:)

    如何手动触发KVO?

    手动调用willChangeValueForKey:和didChangeValueForKey:

    直接修改成员变量会触发KVO么?

    不会触发KVO 重写了set 方法 如果需要需要手动调用willChangeValueForKey:和didChangeValueForKey:

    通过KVC修改属性会触发KVO么?

    会触发KVO
    KVC的全称是Key-Value Coding,俗称“键值编码”,可以通过一个key来访问某个属性
    常见的API有
    |- (void)setValue:(id)value forKeyPath:(NSString *)keyPath;
    |- (void)setValue:(id)value forKey:(NSString *)key;
    |- (id)valueForKeyPath:(NSString *)keyPath;
    |- (id)valueForKey:(NSString *)key;

    KVC的赋值和取值过程是怎样的?原理是什么?

    赋值


    image.png

    取值


    image.png
    Category的使用场合是什么?

    Category的实现原理
    Category编译之后的底层结构是struct category_t,里面存储着分类的对象方法、类方法、属性、协议信息
    在程序运行的时候,runtime会将Category的数据,合并到类信息中(类对象、元类对象中)

    Category和Class Extension的区别是什么?

    Class Extension在编译的时候,它的数据就已经包含在类信息中
    Category是在运行时,才会将数据合并到类信息中

    Category中有load方法吗?load方法是什么时候调用的?load 方法能继承吗?

    有load方法
    load方法在runtime加载类、分类的时候调用
    load方法可以继承,但是一般情况下不会主动去调用load方法,都是让系统自动调用

    +load方法
    image.png
    +initialize方法
    image.png
    load、initialize方法的区别什么?它们在category中的调用的顺序?以及出现继承时他们之间的调用过程?
    • 调用方式的区别
      load是根据函数地址直接调用
      initialize是通过objc_msgSend调用
      -调用时刻
      load是通过runtime加载类、分类的时候调用(只会调用1次)
      initialize是第一次接收消息的时候调用,每一个类只会initialize一次,但是父类的initialize可能会调用多次(因为继承时子类没有实现initialize方法 所以回去父类里面找)
    • load initialize 调用顺序
      load 是优先调用 类的load,在调用子类的load,后调用分类的load调用,分类的load是按照编译的顺序调用
      initialize 是先初始化父类 在初始化子类(在调用子类的时候由于子类没有实现就会调用父类)
    Category能否添加成员变量?如果可以,如何给Category添加成员变量?

    不能直接给Category添加成员变量,但是可以间接实现Category有成员变量的效果
    默认情况下,因为分类底层结构的限制,不能添加成员变量到分类中。但可以通过关联对象来间接实现

    关联对象提供了以下API
    添加关联对象
    void objc_setAssociatedObject(id object, const void * key,
    id value, objc_AssociationPolicy policy)
    获得关联对象
    id objc_getAssociatedObject(id object, const void * key)
    移除所有的关联对象
    void objc_removeAssociatedObjects(id object)


    image.png
    block的原理是怎样的?本质是什么?

    封装了函数调用以及调用环境的OC对象

    __block的作用是什么?有什么使用注意点?

    block的属性修饰词为什么是copy?使用block有哪些使用注意?
    block一旦没有进行copy操作,就不会在堆上
    使用注意:循环引用问题

    block在修改NSMutableArray,需不需要添加__block?

    需不需要加需要看 在block内部有没有该数组的指针 如果只是单纯的引用这个对象 是不需要加的,

    常用LLDB指令
    image.png
    Block的本质

    block本质上也是一个OC对象,它内部也有个isa指针
    block是封装了函数调用以及函数调用环境的OC对象
    block的底层结构如下图所示


    image.png
    block的类型

    NSGlobalBlock ( _NSConcreteGlobalBlock ) 全局block
    NSStackBlock ( _NSConcreteStackBlock ) 栈block
    NSMallocBlock ( _NSConcreteMallocBlock )堆block

    image.png
    block的变量捕获(capture)

    为了保证block内部能够正常访问外部的变量,block有个变量捕获机制


    image.png
    auto变量的捕获

    auto 关键字是C语言的关键字 意思是:自动变量,离开作用域自动销毁

    block的copy
    • 在ARC环境下
      编译器会根据情况自动将栈上的block复制到堆上,比如以下情况
      block作为函数返回值时
      将block赋值给__strong指针时
      block作为Cocoa API中方法名含有usingBlock的方法参数时
      block作为GCD API的方法参数时

    • MRC环境下block属性的建议写法
      @property (copy, nonatomic) void (^block)(void);

    • ARC下block属性的建议写法
      @property (strong, nonatomic) void (^block)(void);
      @property (copy, nonatomic) void (^block)(void);

    __block修饰符的作用

    __block可以用于解决block内部无法修改auto变量值的问题
    __block不能修饰全局变量、静态变量(static)
    编译器会将__block变量包装成一个对象

    __block的内存管理

    当block在栈上时,并不会对__block变量产生强引用

    当block被copy到堆时
    会调用block内部的copy函数
    copy函数内部会调用_Block_object_assign函数
    _Block_object_assign函数会对__block变量形成强引用(retain)
    当block从堆中移除时
    会调用block内部的dispose函数
    dispose函数内部会调用_Block_object_dispose函数
    _Block_object_dispose函数会自动释放引用的__block变量(release)

    对象类型的auto变量、__block变量

    当block在栈上时,对它们都不会产生强引用

    当block拷贝到堆上时,都会通过copy函数来处理它们
    __block变量(假设变量名叫做a)
    _Block_object_assign((void)&dst->a, (void)src->a, 8/BLOCK_FIELD_IS_BYREF/);

    对象类型的auto变量(假设变量名叫做p)
    _Block_object_assign((void)&dst->p, (void)src->p, 3/BLOCK_FIELD_IS_OBJECT/);

    当block从堆上移除时,都会通过dispose函数来释放它们
    __block变量(假设变量名叫做a)
    _Block_object_dispose((void)src->a, 8/BLOCK_FIELD_IS_BYREF*/);

    对象类型的auto变量(假设变量名叫做p)
    _Block_object_dispose((void)src->p, 3/BLOCK_FIELD_IS_OBJECT*/);

    Runtime

    什么是Runtime?平时项目中有用过么?

    OC是一门动态性比较强的编程语言,允许很多操作推迟到程序运行时再进行
    OC的动态性就是由Runtime来支撑和实现的,Runtime是一套C语言的API,封装了很多动态性相关的函数
    平时编写的OC代码,底层都是转换成了Runtime API进行调用

    具体应用

    利用关联对象(AssociatedObject)给分类添加属性
    遍历类的所有成员变量(修改textfield的占位文字颜色、字典转模型、自动归档解档)
    交换方法实现(交换系统的方法)
    利用消息转发机制解决方法找不到的异常问题

    讲一下 OC 的消息机制

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

    RunLoop

    什么是RunLoop

    顾名思义
    运行循环
    在程序运行过程中循环做一些事情
    程序并不会马上退出,而是保持运行状态
    RunLoop的基本作用
    保持程序的持续运行
    处理App中的各种事件(比如触摸事件、定时器事件等)
    节省CPU资源,提高程序性能:该做事时做事,该休息时休息
    应用范畴
    定时器(Timer)、PerformSelector
    GCD Async Main Queue
    事件响应、手势识别、界面刷新
    网络请求
    AutoreleasePool

    RunLoop对象

    iOS中有2套API来访问和使用RunLoop
    Foundation:NSRunLoop

    Core Foundation:CFRunLoopRef

    NSRunLoop和CFRunLoopRef都代表着RunLoop对象
    NSRunLoop是基于CFRunLoopRef的一层OC包装
    CFRunLoopRef是开源的
    https://opensource.apple.com/tarballs/CF/

    RunLoop与线程

    每条线程都有唯一的一个与之对应的RunLoop对象
    RunLoop保存在一个全局的Dictionary里,线程作为key,RunLoop作为value
    线程刚创建时并没有RunLoop对象,RunLoop会在第一次获取它时创建
    RunLoop会在线程结束时销毁
    主线程的RunLoop已经自动获取(创建),子线程默认没有开启RunLoop

    获取RunLoop对象

    Foundation
    [NSRunLoop currentRunLoop]; // 获得当前线程的RunLoop对象
    [NSRunLoop mainRunLoop]; // 获得主线程的RunLoop对象

    Core Foundation
    CFRunLoopGetCurrent(); // 获得当前线程的RunLoop对象
    CFRunLoopGetMain(); // 获得主线程的RunLoop对象

    RunLoop相关的类

    Core Foundation中关于RunLoop的5个类
    CFRunLoopRef
    CFRunLoopModeRef
    CFRunLoopSourceRef
    CFRunLoopTimerRef
    CFRunLoopObserverRef

    CFRunLoopModeRef

    CFRunLoopModeRef代表RunLoop的运行模式
    一个RunLoop包含若干个Mode,每个Mode又包含若干个Source0/Source1/Timer/Observer
    RunLoop启动时只能选择其中一个Mode,作为currentMode
    如果需要切换Mode,只能退出当前Loop,再重新选择一个Mode进入
    不同组的Source0/Source1/Timer/Observer能分隔开来,互不影响
    如果Mode里没有任何Source0/Source1/Timer/Observer,RunLoop会立马退出

    常见的2种Mode
    kCFRunLoopDefaultMode(NSDefaultRunLoopMode):App的默认Mode,通常主线程是在这个Mode下运行
    UITrackingRunLoopMode:界面跟踪 Mode,用于 ScrollView 追踪触摸滑动,保证界面滑动时不受其他 Mode 影响

    RunLoop的运行逻辑
    image.png
    image.png
    RunLoop休眠的实现原理
    image.png
    RunLoop在实际开中的应用

    控制线程生命周期(线程保活)

    解决NSTimer在滑动时停止工作的问题

    监控应用卡顿

    性能优化

    GCD

    GCD的队列
    GCD的队列可以分为2大类型
    并发队列(Concurrent Dispatch Queue)
    可以让多个任务并发(同时)执行(自动开启多个线程同时执行任务)
    并发功能只有在异步(dispatch_async)函数下才有效

    串行队列(Serial Dispatch Queue)
    让任务一个接着一个地执行(一个任务执行完毕后,再执行下一个任务)

    容易混淆的术语

    有4个术语比较容易混淆:同步、异步、并发、串行
    同步和异步主要影响:能不能开启新的线程
    同步:在当前线程中执行任务,不具备开启新线程的能力
    异步:在新的线程中执行任务,具备开启新线程的能力

    并发和串行主要影响:任务的执行方式
    并发:多个任务并发(同时)执行
    串行:一个任务执行完毕后,再执行下一个任务

    各种队列的执行效果
    image.png

    使用sync函数往当前串行队列中添加任务,会卡住当前的串行队列(产生死锁)

    自旋锁、互斥锁比较

    什么情况使用自旋锁比较划算?
    预计线程等待锁的时间很短
    加锁的代码(临界区)经常被调用,但竞争情况很少发生
    CPU资源不紧张
    多核处理器

    什么情况使用互斥锁比较划算?
    预计线程等待锁的时间较长
    单核处理器
    临界区有IO操作
    临界区代码复杂或者循环量大
    临界区竞争非常激烈

    copy和mutableCopy
    image.png

    相关文章

      网友评论

        本文标题:常见基础面试题总结

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