1、weak关键字的作用
weak的作用是弱引用,它修饰的对象在释放时会置为nil,避免错误的内存访问。一般用于delegate、block、NSTimer中,避免循环引用造成的内存泄漏问题。
weak修饰的对象释放时,weak指针自动置为nil的原理?
runtime维护了一张weak表,存储了指向某个对象的weak指针地址。weak表其实是一个哈希表,key是指向某个对象的地址,value是指向某个对象的weak指针地址数组,当该对象被销毁时,会根据该对象的地址(key)获取指向该对象的weak指针数组,然后遍历该数组将weak指针依次置为nil,从weak表中删除该记录,最后从引用计数表中删除废弃对象的地址为键值的记录。
系统如何知道哪些对象是被__weak修饰过的?
- 在arm64架构之前,isa就是一个普通的指针,存储着Class、Meta-Class对象的内存地址
- 从arm64架构开始,对isa进行了优化,变成了一个共用体(union)结构,还使用位域来存储更多的信息
- 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> load是根据函数地址直接调用
2> initialize是通过objc_msgSend调用 -
调用时刻
1> load是runtime加载类、分类的时候调用(只会调用一次)
2> initialize是类第一次接收到消息的时候调用,每个类只会initialize一次(父类的initialize方法可能会被调用多次)
调用顺序
-
load
1> 先调用类的load
a) 先编译的类,优先调用
b) 调用子类的load方法之前,会先调用父类的load方法2> 再调用分类的load
a) 先编译的分类,优先调用 -
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,程序会报错,抛出找不到方法选择器的经典错误。 - 若方法A
forwardingTargetForSelector:
的返回值不为nil,就代表转发成功,这条消息(方法实现)由返回值处理:objc_msgSend(返回值, SEL)
- 若方法B
methodSignatureForSelector:
的返回值不为nil,会继续调用forwardInvocation:
方法C,开发者可以在该方法中自定义任何逻辑。 - 注:以上消息转发过程涉及到的方法A/B/C,都有对象方法、类方法两个版本(可以是减号-方法,也可以是加号+方法)
11、对runtime的理解?在项目中的应用场景有哪些?
- 简单来说,OC是一门动态性比较强的编程语言,允许很多操作推迟到程序运行时再进行。
- OC的动态性就是由
runtime
来支撑和实现的,runtime
是一套C语言的API,封装了很多动态性相关的函数。 - 平时编写的OC代码,底层都是转换成了
runtime
API进行调用。
应用场景
- 利用关联对象(
objc_setAssociatedObject
)给分类添加属性。 - 遍历类的所有成员变量(修改文本框占位文字颜色、字典转模型、自动归解档)
- 交换方法实现(交换系统的方法)
- 利用消息转发机制解决方法找不到的异常问题
12、block的本质?
-
block
本质也是一个OC对象,它内部也有一个isa指针。 -
block
是封装了函数调用以及函数调用环境的OC对象。
block的变量捕获机制
- 为了保证block内部能正常访问外部的变量,block有个变量捕获机制:
block的类型
-
block
有三种类型,可通过class方法或isa指针查看具体类型,最终都是继承自NSBlock类型:
1、没有访问auto变量: __NSGlobalBlock__ (_NSConcreteGlobalBlock)
2、访问了auto变量: __NSStackBlock__ (_NSConcreteStackBlock)
3、__NSStackBlock__调用了copy: __NSMallocBlock__ (_NSConcreteMallocBlock)
各类型block在内存上的存储区域.png
- 每一种类型的
block
调用了copy
后的结果如下:
网友评论