iOS开发21年6月面试总结(未完待续~)
SDWebImageView
流程
diskResult
downloadImage
storImage
SDWebImageCache
内存磁盘双缓存
内存缓存SDMemoryCache
shouldUseWeakMemoryCache
存在NSCache,不可控,容易被清理
存在NSCache并存在MemoryCache,可以保证NSCache被清理之后,从MemoryCache中获取
磁盘缓存
创建缓存路径
内存缓存查找
磁盘缓存查找,存入内存缓存
内存泄露
产生原因
循环引用造成
检查方式
Product -> Analyze
Instruments -> Leaks
具体产生场景
Timer没有释放,在合适的时机,调用[timer invalidate]
blcok持有了对象,不能释放。使用weak属性
Network结束后,取消task
delegate的传递,尽量使用weak
消息转发机制
快速查找
是否支持tagged pointer对象
为空则直接返回Zero
获取
获取isa, 然后获取class isa & iSA_Mask
开启缓存查找
通过isa平移16获取到cache
通过cache&掩码获取到buckets
通过平移获取到mask(arm64为中为右移48位)
在通过mask & sel获取到方法下标index
在通过平移在buckets中获取到查找的buket,取到imp
比较通过index获取到的bucket中的sel与我们查找的sel是否为同一个
缓存命中,返回imp
如果不相等,则判断此bucket是否为第buckets的第一个元素
如果是第一个元素,则将bucket设置为buckets的最后一个元素,进行第二次递归查找
如果不是第一个元素,则从最后一个元素递归向前查找
如果一直没有找到则退出递归,进入慢速查找流程
慢速查找 (lookUpImpOrForward)
排除一些干扰因素,是否是已知类呀,是否完成初始化
确认继承链,因为这里会存在类方法和实例方法的区别,所以需要确定其继承链
然后进去死循环查找流程
当前查找的类是否是不断优化的类
查找其缓存
找到了就直接返回imp
没找到循环继续
通过二分查找,在方法列表中查找
找到了,写入cache,返回imp
查找其父类curClass = curClass -> superClass
现在父类缓存中找
找到了是否为forward_imp
结束循环,进入动态方法决议
存入查找类的cache,返回imp
一直没有找到,则imp为forward_imp,进入动态方法决议
imp = forward_imp 动态方法决议
动态方法决议
判断是否是元类
resolveInstanceMethod
+ (BOOL)resolveInstanceMethod:(SEL)sel{
if (sel == @selector(say666)) {
NSLog(@"%@ 来了", NSStringFromSelector(sel));
//获取sayMaster方法的imp
IMP imp = class_getMethodImplementation(self, @selector(sayMaster));
//获取sayMaster的实例方法
Method sayMethod = class_getInstanceMethod(self, @selector(sayMaster));
//获取sayMaster的签名
const char *type = method_getTypeEncoding(sayMethod);
//将sel的实现指向sayMaster
return class_addMethod(self, sel, imp, type);
}
return [super resolveInstanceMethod:sel];
}
resolveClassMethod
+ (BOOL)resolveClassMethod:(SEL)sel{
if (sel == @selector(sayNB)) {
NSLog(@"%@ 来了", NSStringFromSelector(sel));
IMP imp = class_getMethodImplementation(objc_getMetaClass("LGPerson"), @selector(lgClassMethod));
Method lgClassMethod = class_getInstanceMethod(objc_getMetaClass("LGPerson"), @selector(lgClassMethod));
const char *type = method_getTypeEncoding(lgClassMethod);
return class_addMethod(objc_getMetaClass("LGPerson"), sel, imp, type);
}
return [super resolveClassMethod:sel];
}
如果动态方法决议处理了,则返回IMP,如果没有处理则进入消息转发
消息转发
快速转发快速转发,可以转发给其它类或对象,其它的类会对象如果实现了查找方法的类方法或者对象方法,则不报错,如果没有则进入慢速转发
+ (id)forwardingTargetForSelector:(SEL)aSelector{
NSLog(@"%s- %@", __func__, NSStringFromSelector(aSelector));
return [Teacher alloc];
}
慢速转发这里在中返回方法签名,在forwardInvocation中可以处理,也可以不处理,处理方式这里也需要要在对应的类里面有对应的实现
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
NSLog(@"%s - %@",__func__,NSStringFromSelector(aSelector));
return [NSMethodSignature signatureWithObjCTypes:"v@:"];
}
- (void)forwardInvocation:(NSInvocation *)anInvocation{
NSLog(@"%s - %@",__func__,anInvocation);
}
/// 慢速转发
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
NSLog(@"%s- %@", __func__, NSStringFromSelector(aSelector));
return [NSMethodSignature signatureWithObjCTypes:"v@:"];
}
- (void)forwardInvocation:(NSInvocation *)anInvocation{
NSLog(@"%s- %@", __func__, anInvocation);
[anInvocation invokeWithTarget:[Student alloc]];
}
[anInvocation invokeWithTarget:[Student alloc]];
多线程的使用
NSThread的使用
通过alloc来启用,需要手动开启
NSThread *thread = [[NSThread alloc] initWithBlock:^{
NSLog(@"%@", [NSThread currentThread]);
}];
NSThread *thread2 = [[NSThread alloc] initWithTarget:self selector:@selector(threadDo) object:NULL];
[thread start];
[thread2 start];
通过detachNewThread直接开启
[NSThread detachNewThreadSelector:@selector(threadDo) toTarget:self withObject:@"ThreadName1"];
[NSThread detachNewThreadWithBlock:^{
NSLog(@"%@", [NSThread currentThread]);
}];
通过performSelector来开启
[self performSelectorInBackground:@selector(threadDo) withObject:@"ThreadName2"];
[self performSelectorOnMainThread:@selector(threadDo) withObject:@"ThreadName3" waitUntilDone:YES];
NSThread支持KVO,可以监听到threa的执行状态
是否正在执行
是否被取消
是否完成
是否是主线程
优先级
GCD
dispatch_after
dispatch_once
dispatch_apply
dispatch_group_t
dispatch_semaphore_t控制并发数
dispatch_source_t可以实现timer不依赖于runloop,精度比timer高
NSOperation
Block
block类型
:全局block,存储在全局区
此时的没有访问外界变量,无参也无返回值
void(^block)(void) = ^{
NSLog(@"hello world");
}
:堆区block
int a = 10;
void(^block)(void) = ^{
NSLog(@"hello world - %d", a);
}
NSlog(@"%@", block);
此时的block会访问外界变量,即底层拷贝a,所以是堆区block
:栈区block
int a = 10;
NSlog(@"%@", ^{
NSLog(@"hello world - %d", a);
});
在完成a的底层拷贝前,此时的block还是栈区block,拷贝完成之后,从上面的堆区block可以看出,就变成堆区block了
int a = 10;
void(^__weak block)(void) = ^{
NSLog(@"hello world - %d", a);
}
NSlog(@"%@", block);
可以通过__weak不进行强持有,block就还是栈区block
总结
block的循环引用
造成循环引用的原因
互相持有,导致释放不掉
解决循环引用的方法
weak-strong-dance
如果block未嵌套block,直接使用__weak修饰的self即可,否则,需要搭配__strong来使用
__weak typeof(self) weakSelf = self;
self.dkblock = ^{
NSLog(@"%@", weakSelf.name);
};
self.dkblock = ^{
__strong typeof(weakSelf) strongSelf = weakSelf;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"%@", weakSelf.name);
});
};
__block修饰对象(需要注意在block内部使用完成之后置为nil,block必须调用)
__block ViewController *vc = self;
self.dkblock = ^{
__strong typeof(weakSelf) strongSelf = weakSelf;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"%@", vc.name);
vc = nil; 记得手动释放
});
};
传递self作为block的参数,提供给block内部使用
self.myBlock = ^(ViewController *vc) {
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"%@", vc.name);
});
}
block为什么要用copy修饰,使用strong修饰会有什么问题吗?
block用strong和copy修饰都可以,对于block,编译器重写了strong底层逻辑,使其和copy是一样的原理,即把block从栈区复制到堆区
使用copy是因为block初始化时位于栈区,copy可以把栈区的对象复制到堆区,而栈上的block对象在作用域结束后释放
block的的底层结构
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__Block_byref_a_0 *a; // by ref
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_a_0 *_a, int flags=0) : a(_a->__forwarding) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
block的底层结构是一个结构体,也可以说block其实是一个对象、函数
block为什么需要调用
在底层block的类型是结构体,通过其同名构造函数创建,第一个传入的block的内部实现代码块,即,用fp来表示,然后赋值给impl的FuncPtr,然后再main中进行了调用,这也是block为什么需要调用的原因,如果不调用,内部实现的代码块将无法执行
函数生命,block的内部实现声明成了一个函数
通过block的指针,调用block执行
block是如何捕获外界变量的
对外界变量没有__weak修饰时,是进行了值拷贝
对外界变量实现__weak修饰时,是进行了指针拷贝
__weak原理
block使用使用__weak修饰的变量时,会生成结构体
结构体用来保存原始变量的指针和值
将变量生成的结构体对象的指针地址传递给block,然后再block内部就可以对外界变量进行操作了
block的三重拷贝
只有_block修饰的对象,block的copy才有三层
Extension类扩展
类扩展在编译器,会作为类的一部分,和类一起编译进来
类的扩展只是声明,依赖当前的主类,没有.m文件,可以理解为一个.h文件
声明属性和成员变量,也可以声明方法
在当前类.h中声明的属性和方法是共有的,在.m中声明的方法和属性是私有的
通过Extention新建的声明的属性与方法是私有的
Category分类
给类添加新的方法
不能给类添加成员属性,添加了成员属性,也无法取到
分类中使用@property定义的变量,只会生成Setter,Getter方法的声明,不会生成对应的实现
可以通过runtime给分类添加属性
关联对象
Objc_setAssociatedObject
创建一个AssociationsManager管理类
获取全局静态Hasmap
判断插入的关联值是否存在
创建一个空的ObjctAssociationMap去取查询的简直对
如果发现没有这个key就插入一个空的BucketT进去
标记关联对象
用当前的policy和value组成一个ObjcAssociation替代原来的BucketT中的空
标记一下ObjctAssociationMap的第一次为false
创建一个AssociationsManager管理类
获取全局静态Hasmap
根据DisguisedPtr找到AssociationsHashMap中的iterator迭代查询器
如果迭代查询器不是最后能获取
找到ObjctAssociationMap的迭代查询器获取一个经过policy修饰的value
返回value
AssociationsManager
AssociationsHashMap
ObjctAssociationMap
ObjcAssociation
KVO
kvo与NSNotificationCenter有什么区别
相同点
两者的实现都是观察者模式,都是用于监听
都能实现一对多的操作
不同点
KVO用于监听对象属性的变化,并且属性名都是通过NSString来查找,编译器不会检测对错与自动补全
NSNotification的发送监听的操作我们可以控制,KVO的由系统控制
KVO可以记录新旧值得变化
KVO对可变集合的监听
通过[arr addObject:object]这种方式向数组添加元素,是不会触发KVO
对可变集合需要调用对应的KVC方法,监听才能生效
KVO的实现原理
添加KVO之后,实例对象的isa指向了一个新的派生类
重写了原本类的观察属性的setter方法
新增了_isKVO来判断当前是否是KVO类
在观察移除之前对象的isa指向一直是派生类
移除观察之后对象的呃isa指向原有类
派生类一旦产生就会一直存在内存中,不会被销毁
KVC
API
valueForKey、setValueForKey
valueForKeyPath、setValue:ForKeyPath
KVC设值原理
如果能找到上面三个中的任意一个,则直接设值属性的value
如果能找到任意一个实例变量i,ii,iii,iv,则直接赋值,否则进入3
KVC取值原理
如果找到执行5
RunLoop
怎么保证子线程的数据回来更新UI操作不打断用户的滑动操作
将更新UI的事件,放到主线程的NSDefaultRunloopModel上执行,这样就会等用户不再滑动,主线程的RunLoop由UITrakingRunLoopModel切换到NSDefaultRunloopModel时再去更新UI
RunLoop有几种模式
面向对象的三大特性
封装
继承
多肽
网络
http与https有什么区别
Http协议 = Http协议 + SSL/TLS协议
SSL全程是,即安全套接层协议,是为网络通信提供安全及数据完整性的一种安全协议。TLS全称是,即安全传输层协议,即HTTPS是安全的HTTP
TCP
三次握手
发出链接请求
客户端的TCP首先向服务端的TCP发送一条特殊的SYN报文
发送SYN报文后,客户端进入SYN_SENT状态,等待服务端确认,并将SYN比特置为1的报文段
授予链接
收到SYN报文后,服务端会为该TCP链接分配TCP缓存和变量,服务端的TCP进入SYN_RCVD状态,等待客户端TCP发送确认报文
向客户端发送允许链接的SYNACK报文段
确认,并建立链接
收到SYNACK报文段后,客户端也要为TCP分配缓存和变量,客户端的TCP进入ESTABLISHED状态
向服务端TCP发送一个报文段,这最后一个报文段对服务端的允许连接的报文表示了确认(将 server_isn + 1 放到报文段首部的确认字段中)。因为连接已经建立了,所以该 SYN 比特被置为 0。 这个阶段,可以在报文段负载中携带应用层数据
收到客户端该报文段后,服务端TCP也会进入ESTABLISHED状态,可以发送和接收包含有效载荷数 据的报文段。
四次挥手
客户端发出终止FIN=1报文段
客户端向服务端发送FIN=1的报文段,并进入FIN_WAIT_1状态
服务端向客户端发送确认报文ACK=1
收到客户端发来的FIN=1的报文后,向客户端发送确认报文
服务端TCP进入CLOSE_WAIT状态
客户端送到确认报文后,进入FINAL_WAIT_2状态,等待服务端FIN=1的报文
服务端向客户端发送FIN=1报文段
服务端发送FIN=1的报文
服务端进入LAST_ACK状态
客户端向服务端发送ACK=1报文段
收到服务端的终止报文后,向服务端发送一个确认报文,并进入TIME_WAIT状态
如果ACK丢失,TIME_WAIT会使客户端TCP重传ACK报文。最后关闭,进入CLOSE状态,释放缓存和变量
服务端收到之后,TCP也会进入CLOSE状态,释放资源
为什么建立链接需要三次握手,而断开链接缺需要四次挥手
因为,在服务端发送ACK信号后,还有可能数据传输没有完成
数据传输完成才会发送FIN=1的信号
在四次握手中,客户端为什么在TIME_WAIT后必须等待2MSL时间呢
为了保证客户端发送的最后一个ACK报文段能够到达服务器
为什么要三次握手,而不是二次或者三次
两次握手会可能导致已失效的连接请求报文段突然又传送到了服务端产生错误,四次握手又太浪费资源
PUT和POST,POST与GET的区别
PUT VS POST
push和post都有更改指定URL的语义,但PUT被定义为idempotent的方法,post则不是。idempotent多个请求产生的效果是一样的
PUT请求,多个请求,后面的请求会覆盖掉前面的请求
POST请求,后一个请求不会覆盖掉前一个请求
GET VS POST
GET参数通过URL传递,POST放在Request body中
GET请求会被浏览器主动cache,而POST不会,除非手动设置
GET请求参数会被完整保留在浏览器历史记录里,而POST中的参数不会被保留
Get 请求中有非 ASCII 字符,会在请求之前进行转码,POST不用,因为POST在Request body中,通过 MIME,也就可以传输非 ASCII 字符
一般我们在浏览器输入一个网址访问网站都是GET请求
HTTP的底层是TCP/IP。HTTP只是个行为准则,而TCP才是GET和POST怎么实现的基本。GET/POST都是TCP链接。GET和POST能做的事情是一样一样的。但是请求的数据量太大对浏览器和服务器都是很大负担。所以业界有了不成文规定,(大多数)浏览器通常都会限制url长度在2K个字节,而(大多数)服务器最多处理64K大小的url
GET产生一个TCP数据包;POST产生两个TCP数据包。对于GET方式的请求,浏览器会把http header和data一并发送出去,服务器响应200(返回数据);而对于POST,浏览器先发送header,服务器响应100 continue,浏览器再发送data,服务器响应200 ok(返回数据)
在网络环境好的情况下,发一次包的时间和发两次包的时间差别基本可以无视。而在网络环境差的情况下,两次包的TCP在验证数据包完整性上,有非常大的优点。但并不是所有浏览器都会在POST中发送两次包,Firefox就只发送一次。
HTTP的请求方式有哪些
GET
POST
PUT
DELET
HEAD
OPTIONS
cookie和session的区别
存放位置不同
cookie存放在客户的浏览器
session存在在服务器上
安全程度不同
cookie不是很安全,其他人可以分析存放在本地的COOKIE并进行COOKIE欺骗,考虑到安全应当使用session
性能使用程度不同
session会在一定时间内保存在服务器上。当访问增多,会比较占用你服务器的性能,考虑到减轻服务器性能方面,应当使用cookie。
数据存储大小不同
单个cookie保存的数据不能超过4K,很多浏览器都限制一个站点最多保存20个cookie,而session则存储与服务端,浏览器对其没有限制
会话机制不同
session会话机制:session会话机制是一种服务器端机制,它使用类似于哈希表(可能还有哈希表)的结构来保存信息
cookies会话机制:cookie是服务器存储在本地计算机上的小块文本,并随每个请求发送到同一服务器。 Web服务器使用HTTP标头将cookie发送到客户端。在客户端终端,浏览器解析cookie并将其保存为本地文件,该文件自动将来自同一服务器的任何请求绑定到这些cookie。
Swift
swift类与结构体的区别
关键字
class定义的属性必须初始化, struct不用初始化
// class定义
class Person {
final var name:String = ""
}
// strcut定义
struct Person{
var name:String
}
定义函数
可以使用关键字修饰,只能使用修饰
扩展下标
class和struct都可以使用扩展下标
初始化
结构体有默认的初始化方法
结构体不能继承
类是引用类型,结构体是值类型
类有deinit方法,结构体没有
什么时候使用结构体
用于封装简单的数据结构类型
结构在传递的时候是被赋值,而不是被引用
不需要继承或者方法
swift怎么防止父类方法在子类被重写
class Person {
final var name:String = ""
final func personName() {
}
}
关键字
作者:丸疯链接:https://juejin.cn/post/6971808317282730015