iOS基础知识备忘

作者: Lorne_coder | 来源:发表于2021-01-28 11:20 被阅读0次

    1、weak关键字的作用

    weak的作用是弱引用,它修饰的对象在释放时会置为nil,避免错误的内存访问。一般用于delegate、block、NSTimer中,避免循环引用造成的内存泄漏问题。

    weak修饰的对象释放时,weak指针自动置为nil的原理?

    runtime维护了一张weak表,存储了指向某个对象的weak指针地址。weak表其实是一个哈希表,key是指向某个对象的地址,value是指向某个对象的weak指针地址数组,当该对象被销毁时,会根据该对象的地址(key)获取指向该对象的weak指针数组,然后遍历该数组将weak指针依次置为nil,从weak表中删除该记录,最后从引用计数表中删除废弃对象的地址为键值的记录。

    系统如何知道哪些对象是被__weak修饰过的?

    1. 在arm64架构之前,isa就是一个普通的指针,存储着Class、Meta-Class对象的内存地址
    2. 从arm64架构开始,对isa进行了优化,变成了一个共用体(union)结构,还使用位域来存储更多的信息
    3. isa的结构中有一个weakly_referenced的成员变量,该成员变量记录了对象是否被弱引用指向过。
    //isa的底层数据结构
    union isa_t {
        Class cls;
        uintptr_t bits;
    
        struct {
          uintptr_t nonpointer        : 1;                                         \
          uintptr_t has_assoc         : 1;                                         \
          uintptr_t has_cxx_dtor      : 1;                                         \
          uintptr_t shiftcls          : 44; /*MACH_VM_MAX_ADDRESS 0x7fffffe00000*/ \
          uintptr_t magic             : 6;                                         \
          uintptr_t weakly_referenced : 1;                                         \
          uintptr_t deallocating      : 1;                                         \
          uintptr_t has_sidetable_rc  : 1;                                         \
          uintptr_t extra_rc          : 8
        };
    };
    
    isa位域.png

    2、引用循环

    当两个不同的对象各有一个强引用指向对方的时候,就会造成循环引用。

    NSTimer是如何造成循环引用的?

    在ViewController(简称VC)中使用timer属性时,VC强引用timer,timer的target又是VC,就造成了循环引用。当你在VC的dealloc方法中调用timer的invalidate方法来销毁timer时,会发现pop出当前VC时,并没有调用dealloc方法,VC在等timer释放后才走dealloc,而timer的释放在dealloc中,所以就造成了循环引用。

    如何解决NSTimer的循环引用?

    • 苹果新的api接口解决方案(iOS10.0以上可用)
    • 使用NSProxy方案
    • 对NSTimer进行封装

    参考资料

    3、OC对象的本质

    一个NSObject对象占用多少内存?

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

    对象的isa指针指向哪里?

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

    OC的类信息存放在哪里?

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

    4、TCP拥堵、TCP丢包问题

    参考资料

    5、https配置流程

    http与https的区别?
    1、https需要到ca申请证书,一般免费证书很少,需要交费;
    2、http是超文本传输协议,信息是明文传输,https是具有安全性的SSL加密传输协议;
    3、http和https的连接方式不同,用的端口也不同,前者是80,后者是443;
    4、http的连接很简单,是无状态的;https协议是由SSL+HTTP协议构建的可进行加密传输、身份认证的网络协议,比http协议安全;

    iOS配置流程?

    项目中网络交互基于AFN,要求AFN版本在3.0以上。代码部分:

    // 设置AFN请求管理者的时候,添加SSL认证:
    
    // 1.获得请求管理者
    AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
    // 2.加上这个函数,https ssl 验证。
    [manager setSecurityPolicy:[self customSecurityPolicy]];
    
    // https ssl 验证函数
    - (AFSecurityPolicy *)customSecurityPolicy
    {
        // 先导入证书
        NSString *cerPath = [[NSBundle mainBundle] pathForResource:@"xxx" ofType:@"cer"];//证书的路径
        NSData *cerData = [NSData dataWithContentsOfFile:cerPath];
    
        // AFSSLPinningModeCertificate 使用证书验证模式
        AFSecurityPolicy *securityPolicy = [AFSecurityPolicy policyWithPinningMode:AFSSLPinningModeCertificate];
        securityPolicy.pinnedCertificates = [NSSet setWithObject:cerData];
        
        // allowInvalidCertificates 是否允许无效证书(也就是自建的证书),默认为NO
        securityPolicy.allowInvalidCertificates = NO;
        //validatesDomainName 是否需要验证域名,默认为YES;
        securityPolicy.validatesDomainName = YES;
        
        return securityPolicy;
    }
    // 3.关于证书
    从沃通获取到HTTPS证书后,会得到一个有密码的压缩包文件,使用for other server里面的domain.crt的证书文件。
    

    6、atomic属性作用?

    atomic修饰的对象,setter和getter方法是线程安全的(因为在setter和getter赋值取值的时候添加了自旋锁),但不能保证整个对象的线程安全。

    为什么说atomic没办法保证整个对象的线程安全?

    1.对于NSArray类型 @property(atomic)NSArray *array我们用atomic修饰,数组的添加和删除并不是线程安全的,这是因为数组比较特殊,我们要分成两部分考虑,一部分是&array也就是这个数组本身,另一部分是他所指向的内存部分。atomic限制的只是&array部分,对于它指向的对象没有任何限制。
    atomic表示,我TM也很冤啊!!!!

    2.当线程A进行写操作,这时其他线程的读或者写操作会因为该操作而等待。当A线程的写操作结束后,B线程进行写操作,然后当A线程需要读操作时,却获得了在B线程中的值,这就破坏了线程安全,如果有线程C在A线程读操作前release了该属性,那么还会导致程序崩溃。所以仅仅使用atomic并不会使得线程安全,我们还要为线程添加lock来确保线程的安全。
    个人觉得这个就有点杠精的意味了,atomic还要管到你方法外面去了?????不过面试人家问你还要这么答,要严谨!!,

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

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

    如何手动触发KVO?

    • 手动调用willChangeValueForKey:didChangeValueForKey: 方法

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

    • 不会触发KVO

    8、通过KVC修改属性会触发KVO吗?

    • 会触发KVO

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

    赋值过程即[setValue: forKey:] 方法实现原理:

    • 按照 setKey:_setKey:顺序查找方法
    • a> 找到了方法,传递参数调用方法
    • b> 没找到方法,查看+(BOOL)accessInstanceVariablesDirectly方法的返回值
    • 返回值为YES,按照 _key、_isKey、key、isKey顺序来查找成员变量
      找到成员变量直接赋值
      找不到成员变量调用setValue: forUndefinedKey:方法,并抛出异常NSUnknownKeyException
    • 返回值为NO,调用setValue: forUndefinedKey:方法,并抛出异常NSUnknownKeyException
    • +(BOOL)accessInstanceVariablesDirectly方法的默认返回值是YES

    取值过程:

    • 首先按照getKey、key、isKey、_key顺序查找方法
    • a>找到了方法,调用方法取值
    • b>未找到方法,查看+(BOOL)accessInstanceVariablesDirectly方法的返回值
    • 返回值为YES,按照_key、_isKey、key、isKey顺序查找成员变量
      找到成员变量,就返回该成员变量的值
      未找到成员变量,调用valueForUndefinedKey:方法,并抛出异常NSUnknownKeyException
    • 返回值为NO,调用valueForUndefinedKey:方法,并抛出异常NSUnknownKeyException

    9、Category的实现原理?

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

    Category的加载处理过程?

    • 通过runtime加载某个类的所有Category数据
    • 把所有Category的方法、属性、协议数据,合并到一个大数组中
      后参与编译的Category数据,会在数组的前面
    • 将合并后的分类数据(方法、属性、协议),插入到类原来数据的前面

    Category和Class Extension的区别是什么?

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

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

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

    load、initialize方法的区别是什么?它们在Category中的调用顺序?以及出现继承时它们之间的调用过程?

    区别
    1. 调用方式
      1> load是根据函数地址直接调用
      2> initialize是通过objc_msgSend调用

    2. 调用时刻
      1> load是runtime加载类、分类的时候调用(只会调用一次)
      2> initialize是类第一次接收到消息的时候调用,每个类只会initialize一次(父类的initialize方法可能会被调用多次)

    调用顺序
    1. load
      1> 先调用类的load
      a) 先编译的类,优先调用
      b) 调用子类的load方法之前,会先调用父类的load方法

      2> 再调用分类的load
      a) 先编译的分类,优先调用

    2. initialize
      1> 先初始化父类
      2> 再初始化子类(可能最终调用的是父类的initialize方法)
      3> 如果子类没有实现initialize方法,会调用父类的initialize,所以父类的initialize可能会被调用多次
      4> 如果分类实现了initialize方法,就覆盖类本身的initialize调用

    Category能否添加成员变量?如果可以,如何添加?

    • 不能直接给Category添加成员变量,但可以通过runtime的API间接实现Category有成员变量的效果。
    • 添加关联对象:
      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)

    10、讲一下OC的消息机制?

    • OC的方法调用其实都会转成objc_msgSend函数的调用,给receiver(方法调用者)发送了一条消息(方法名)。
    • objc_msgSend函数底层有三大阶段:
      1>消息发送(当前类、父类中查找方法)
    消息发送阶段,查找方法的过程

    2>动态方法解析(消息发送阶段未找到方法实现,开发者可以在这个阶段实现+resolveInstanceMethod:+resolveClassMethod:方法来动态添加方法实现。动态解析过后,会重新走 消息发送 的流程,并且是直接“从receiverClass的cache中查找方法”这一步开始执行)

    动态方法解析过程

    3>消息转发(如果在 动态方法解析 过程没有做动态添加方法实现的处理,那么程序会进入消息转发过程)

    • 首先会调用forwardingTargetForSelector:方法A,返回值为nil,会继续调用methodSignatureForSelector:方法B,若返回值为nil,程序会报错,抛出找不到方法选择器的经典错误。
    • 若方法AforwardingTargetForSelector:的返回值不为nil,就代表转发成功,这条消息(方法实现)由返回值处理:objc_msgSend(返回值, SEL)
    • 若方法BmethodSignatureForSelector:的返回值不为nil,会继续调用forwardInvocation:方法C,开发者可以在该方法中自定义任何逻辑。
    • 注:以上消息转发过程涉及到的方法A/B/C,都有对象方法、类方法两个版本(可以是减号-方法,也可以是加号+方法)
    消息转发过程

    11、对runtime的理解?在项目中的应用场景有哪些?

    • 简单来说,OC是一门动态性比较强的编程语言,允许很多操作推迟到程序运行时再进行。
    • OC的动态性就是由runtime来支撑和实现的,runtime是一套C语言的API,封装了很多动态性相关的函数。
    • 平时编写的OC代码,底层都是转换成了runtimeAPI进行调用。
    应用场景
    • 利用关联对象(objc_setAssociatedObject)给分类添加属性。
    • 遍历类的所有成员变量(修改文本框占位文字颜色、字典转模型、自动归解档)
    • 交换方法实现(交换系统的方法)
    • 利用消息转发机制解决方法找不到的异常问题

    12、block的本质?

    • block本质也是一个OC对象,它内部也有一个isa指针。
    • block是封装了函数调用以及函数调用环境的OC对象。
    block的变量捕获机制
    • 为了保证block内部能正常访问外部的变量,block有个变量捕获机制:
    block变量捕获.png
    block的类型
    • block有三种类型,可通过class方法或isa指针查看具体类型,最终都是继承自NSBlock类型:
    1、没有访问auto变量:             __NSGlobalBlock__ (_NSConcreteGlobalBlock)    
    2、访问了auto变量:               __NSStackBlock__  (_NSConcreteStackBlock)     
    3、__NSStackBlock__调用了copy:  __NSMallocBlock__ (_NSConcreteMallocBlock)    
    
    各类型block在内存上的存储区域.png
    • 每一种类型的block调用了copy后的结果如下:
    各类型block调用copy的结果.png

    相关文章

      网友评论

        本文标题:iOS基础知识备忘

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