iOS基础知识备忘

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基础知识备忘)