自己能经常看看也是好的,查漏补缺
以下面试题只是简洁的回答,具体解析会有链接
一、AutoReleasePool,AutoRelease, AutoReleasePool与Runloop及GCD的关系
1.Autorelease对象什么时候释放
在没有手加Autorelease Pool的情况下,Autorelease对象是在当前的runloop迭代结束时释放的。
这里能引出Runloop与AutoReleasePool的关系
程序启动后,在主线程 RunLoop 里注册了两个 Observer:
第一个 Observer 监视的事件是 Entry(即将进入Loop),其回调内会调用_objc_autoreleasePoolPush() 创建自动释放池。其 order 是-2147483647,优先级最高,保证创建释放池发生在其他所有回调之前。
第二个 Observer 监视了两个事件: BeforeWaiting(准备进入睡眠) 和 Exit(即将退出Loop),
BeforeWaiting(准备进入睡眠)时调用_objc_autoreleasePoolPop() 和 _objc_autoreleasePoolPush() 释放旧的池并创建新池;
Exit(即将退出Loop) 时调用 _objc_autoreleasePoolPop() 来释放自动释放池。这个 Observer 的 order 是 2147483647,优先级最低,保证其释放池子发生在其他所有回调之后
2.Autorelease原理
@autoreleasepool{} 在使用时,编译器会改写成objc_autoreleasePoolPush()/Pop(),其实是对AutoreleasePoolPage的封装。
● AutoreleasePool并没有单独的结构,而是由若干个AutoreleasePoolPage以双向链表的形式组合而成(分别对应结构中的parent指针和child指针)
● AutoreleasePool是按线程一一对应的(结构中的thread指针指向当前线程)
● AutoreleasePoolPage每个对象会开辟4096字节内存(也就是虚拟内存一页的大小),除了上面的实例变量所占空间,剩下的空间全部用来储存autorelease对象的地址
● 上面的id *next指针作为游标指向栈顶最新add进来的autorelease对象的下一个位置
● 一个AutoreleasePoolPage的空间被占满时,会新建一个AutoreleasePoolPage对象,连接链表,后来的autorelease对象在新的page加入
每次push时,会加一个哨兵,这样pop时就会给新加入的每个对象发送release消息,直到哨兵位置。
注:使用容器的block版本的枚举器时,内部会自动添加一个AutoreleasePool:
[array enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) { // 这里被一个局部@autoreleasepool包围着}];
当然,在普通for循环和for in循环中没有,所以,新版的block版本枚举器更加方便。for循环中遍历产生大量autorelease变量时,就需要手加局部AutoreleasePool。
3.runloop与GCD的关系
在runloop中大量使用了GCD
1.RunLoop的超时时间使用的是GCD中的dispatch_source_t;
2.GCD MainQueue上的异步任务由runloop来执行。
黑幕背后的AutoRelease
二、内存泄漏的地方及场景
1.block
若有强强引用,记得__weak,修改局部变量时__block等修饰
2.代理
代理若用strong等强引用 ,会存在内存泄漏,一般用weak/assign修饰,weak相对于assign来说会将指针置为nil,防止野指针的存在。
3.timer
timer强引用target(self),一般self又会持有time作为属性,这样就造成了循环引用。
4.CF类型内存
注意以creat,copy作为关键的函数一定记得配对使用,释放
5.通知,观察者
成对存在,add和remove
三、copy是否会修改内存引用计数,与mutableCopy的区别,及与strong的区别
1.对不可变或者可变的对象进行mutableCopy操作,是一次深拷贝,返回的是可变的对象
2.对不可变的对象进行copy,是一次浅拷贝,返回一个不可变的对象
3.对可变对象进行copy,是一次深拷贝,产生了不可变的对象副本。
2.copy与strong主要区别就是深拷贝和浅拷贝,内存计数一样都会增加,只是针对mutableStr,copy是深拷贝,即不仅拷贝对象还拷贝指针,指的不是同一个对象,而strong浅拷贝,只是针对指针。
四、死锁的原因
产生死锁的4个必要条件
1.互斥条件:指进程对所分配到的资源进行排他性使用,即在一段时间内某资源只由一个进程使用
2.请求和保持条件:该进程已经保持至少一个资源,但还要求已被占领的其他资源
3.不剥夺条件:指进程已获得资源,在未使用完成时不能被剥夺,只能在使用完成时由自己释放
4.环路等待条件:指在发生死锁时,必然有进程等待下个被占有的资源,并形成环路
解决方案:打破其中一个条件皆可
了解ios中GCD经典死锁原理
五、OC的发送消息机制以及拦截调用,都做哪些修改判断,isa指针等内容
1.对象组成结构详解
结构详解
运行时,每个对象其实都是个结构体,会通过object的isa指针找到它的class
typedef struct objc_object {
Class isa;
} *id;
typedef struct objc_class *Class
struct object_class{
Class isa;
Class super_class; //父类
const char* name; //类名
long version; //版本信息
long info; //类信息
long instance_size; //实例大小
struct objc_ivar_list *ivars; //实例参数链表
struct objc_method_list *methodLists; //方法链表
struct objc_cache *cache; //方法缓存
struct objc_protocol_list *protocols; //协议链表}
typedef struct objc_method{
SEL method_name;
char *method_type;
IMP method _imp;
};
2.OC的发送消息机制
如图:1.调用一个对象方法时,实则是给对象发送了一条消息,以[object foo],在编译OC函数调用的语法时,会被编译成一个c的函数调用:objc_msgSend();
2.objc_msgSend()通过object的isa指针找到它的class,在class的缓存方法列表中找到foo,如果没找到,就去method_Lists中找,如果没找着,就去superclass和元组中找,找到就去实现(IMP),如果一直没找到动态方法决议与消息转发。
Dynamic Method Resolution(动态方法决议)
3.如果调用的方法是实例方法,OC的运行时会调用- (BOOL)resolveInstanceMethod:(SEL)sel,如果是类方法,则会调用+ (BOOL)resolveClassMethod:(SEL)sel 让我们可以在程序运行时动态的为一个selector提供实现,如果我们添加了函数的实现,并返回YES,运行时系统会重启一次消息的发送过程,调用动态添加的方法
4.如果返回NO,就会进入消息转发
2、Message Forwarding(消息转发)
消息转发分为两步:
- 首先运行时系统会调用- (id)forwardingTargetForSelector:(SEL)aSelector方法,如果这个方法中返回的不是nil或者self,运行时系统将把消息发送给返回的那个对象
- 如果- (id)forwardingTargetForSelector:(SEL)aSelector返回的是nil或者self,运行时系统首先会调用- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector方法来获得方法签名,方法签名记录了方法的参数和返回值的信息,如果-methodSignatureForSelector 返回的是nil, 运行时系统会抛出unrecognized selector exception,程序到这里就结束了。如果有返回,至forwardInvocation.
因此,可通过该体制能做很多事情,如多重代理转发,demo,提前防奔溃 ,JSPatch热更新也用到了该机制等等
六、内存的几大区域
1.栈区:由编译器自动分配并释放,存放函数的参数值,局部变量等。栈是系统数据结构,对应线程/进程是唯一的。优点是快速高效,缺点是有限制,数据不灵活(先进后出) 分为静态分配和动态分配两种
2.堆区:由程序员分配和释放,如果没释放,程序结束时,可能会由操作系统回收,比如ios中alloc都存放于堆中的。灵活方便,数据适应面更广泛,但效率有一定的降低。堆是函数库内部数据结构,不一定唯一。不同堆分配的内存无法互相操作。堆空间的分配总是动态的。
3.全局区(静态区static) 全局变量和静态变量的存储是放在一起的,初始化的全局变量和静态变量存放在一块区域,未初始化的全局变量和静态变量在相邻的另一块区域,程序结束后由系统释放
4.文字常量区 存放常量字符串,程序结束后由系统释放
5.代码区 存放函数的二进制代码
七、界面优化
1.页面间跳转的性能优化
页面间跳转性能优化
1.若是跳转页因加载数据太多,可将数据放到分线程中做
2.若是UI多,可将UI放到下个Runloop中去做,这样coreAnimation的动画并没有影响,而且结束了这个0.35秒时间
2.CPU和GPU综合考虑
ios保持界面流畅的技巧
在界面上市场上有各种各样的看法,可以多阅览,找适合自己项目的才是最好的,多实验
八、https原理,charles拦截原理
https原理
1.HTTP协议:超文本传输协议,定制传输数据的规范(客户端浏览器或其他程序与Web服务器之间的应用层通信协议),SOCKET实现,通过TCP经过三次握手连接。但很容易被冒充
HTTPS协议:可以理解为HTTP+SSL/TLS, 即 HTTP 下加入 SSL 层,HTTPS 的安全基础是 SSL,因此加密的详细内容就需要 SSL,用于安全的 HTTP 数据传输。
步骤:
如图:
1).客户端发送一次握手
2).服务端将发送一个ssl证书给客户端(证书包含了证书的发布机构、有效期、公钥、证书所有者、签名等)
3).客户端收到ssl证书时,会对证书的真伪进行校验(除了校验所有者、有效期,还会校验是否是可信任证书,用公钥解密,检验hash值及签名是否被更改)
4).证书通过后,就可以告诉服务端用对称密钥加密传输了
5).客户端与服务对内容通过对称密钥加密传输
HTTPS之所以较为安全,主要在2,3步,服务端难以被冒充HTTPS缺点
1) SSL 证书费用很高,以及其在服务器上的部署、更新维护非常繁琐。貌似12306一直用的就是免费证书
2)HTTPS 降低用户访问速度(多次握手)
3.)网站改用HTTPS 以后,由HTTP 跳转到 HTTPS 的方式增加了用户访问耗时(多数网站采用302跳转)
4) HTTPS 涉及到的安全算法会消耗 CPU 资源,需要增加大量机器(HTTPS访问过程需要加解密)
charles拦截原理
charles拦截
尝试下抓包就知道,中间需要自己手动设置代理,下载信任证书,打破了Https的2,3步,当然能拦截了,自找的哈
试了一下,内容果然一清二楚,吓的我赶紧把代理关掉,证书删掉,取消信任
篇幅较长,先整理到这里,会陆续整理出来,多多总结学习,找工作so easy
感谢各位贡献资料的大佬,学习永无止境