写点什么

iOS 开发 21 年 6 月面试总结(未完待续~)

用户头像
iOSer
关注
发布于: 2021 年 06 月 16 日

SDWebImageView

  • 流程

  • sd_setImageWithUrl

  • sd_internalSetImageWithUrl

  • loadImageWithUrl

  • quryCacheForKey

  • 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

  • 获取 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 的最后一个元素,进行第二次递归查找

  • 如果不是第一个元素,则从最后一个元素递归向前查找

  • 如果一直没有找到则退出递归,进入__objc_msgSend_uncached慢速查找流程

慢速查找 (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];}
复制代码


  • 慢速转发这里在methodSignatureForSelector中返回方法签名,在 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 的执行状态

  • isExecuting是否正在执行

  • isCancelled是否被取消

  • isFinished是否完成

  • isMainThread是否是主线程

  • threadPriority优先级

GCD

  • dispatch_after

  • dispatch_once

  • dispatch_apply

  • dispatch_group_t

  • dispatch_semaphore_t 控制并发数

  • dispatch_source_t 可以实现 timer 不依赖于 runloop,精度比 timer 高

NSOperation

Block

block 类型

  • __NSGlobalBlock__:全局 block,存储在全局区


此时的block没有访问外界变量,无参也无返回值


void(^block)(void) = ^{  NSLog(@"hello world");}
复制代码


  • __NSMallocBlock__:堆区 block


int a = 10;void(^block)(void) = ^{  NSLog(@"hello world - %d", a);}NSlog(@"%@", block);
复制代码


此时的 block 会访问外界变量,即底层拷贝 a,所以是堆区 block


  • __NSStackBlock__:栈区 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


总结


  1. block 直接存储在全局区

  2. 如果 block 访问外界变量,并进行 block 相应拷贝

  3. 如果此时的 block 是强引用,则 block 存储在堆区

  4. 如果此时的 block 通过__weak变成了弱引用,则 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 的类型是__main_block_impl_0结构体,通过其同名构造函数创建,第一个传入的 block 的内部实现代码块,即_main_block_func_0,用 fp 来表示,然后赋值给 impl 的 FuncPtr,然后再 main 中进行了调用,这也是 block 为什么需要调用的原因,如果不调用,内部实现的代码块将无法执行


  • 函数生命,block 的内部实现声明成了一个函数__main_block_impl_0

  • 通过 block 的FuncPtr指针,调用 block 执行

block 是如何捕获外界变量的

  • 对外界变量没有__weak 修饰时,是进行了值拷贝

  • 对外界变量实现__weak 修饰时,是进行了指针拷贝

__weak 原理

  • block 使用使用__weak 修饰的变量时,会生成__Block_byref_x_0结构体

  • 结构体用来保存原始变量的指针和值

  • 将变量生成的结构体对象的指针地址传递给 block,然后再 block 内部就可以对外界变量进行操作了

block 的三重拷贝

  1. 通过__block_copy实现对象的自身拷贝,从栈区到堆区

  2. 通过__block_byref_copy方法,将对象拷贝为block_byref结构体类型

  3. 调用_block_object_assign方法,对_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

  • Objec_getAssociatedObject

  • 创建一个 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 方法,监听才能生效

  • [[self.person mutableArrayValueForkey:@"dataArray"] addObject:@"1"]

KVO 的实现原理

  • 添加 KVO 之后,实例对象的 isa 指向了一个新的派生类NSKVONotifying_Class

  • 重写了原本类的观察属性的 setter 方法

  • 新增了_isKVO 来判断当前是否是 KVO 类

  • 在观察移除之前对象的 isa 指向一直是派生类

  • 移除观察之后对象的呃 isa 指向原有类

  • 派生类一旦产生就会一直存在内存中,不会被销毁

KVC

API

  • valueForKey、setValueForKey

  • valueForKeyPath、setValue:ForKeyPath

KVC 设值原理

  1. 查找是否有这三种setter方法

  2. set<Key>

  3. _set<Key>

  4. setIs<Key>


如果能找到上面三个中的任意一个,则直接设值属性的 value


  1. 查找accessInstanceVariablesDirectly是否返回 YES

  2. 如果返回 YES,则查找间接访问的实例变量进行赋值,查找顺序为

  3. _<Key>

  4. _is<Key>

  5. <key>

  6. is<Key>

  7. 如果返回 NO,则进入 3


如果能找到任意一个实例变量 i,ii,iii,iv,则直接赋值,否则进入 3


  1. 如果 setter 方法或者实例变量都没有找到,系统会执行该对象的setValue:forUndefinedKey:方法,默认抛出NSUndefinedKeyException类型异常

KVC 取值原理

  1. 首先查找 getter 方法,顺序为

  2. get<Key>

  3. <Key>

  4. is<Key>

  5. _<Key>


如果找到执行 5


  1. 继续查找countOf<Key>objectIn<Key>AtInde<Key>AtIndex

  2. 如果找到countOf<Key>和其他两个中的一个,则会创建一个响应所有NSArray方法的集合代理对象,并返回该对象,即NSKeyValueArray,是NSArray的子类。代理对象随后将接收到的所有NSArray消息转换为countOf<Key>objectIn<Key>AtIndex:<key>AtIndexes:消息的某种组合,用来创建键值编码对象。如果原始对象还实现了一个名为 get:range:之类的可选方法,则代理对象也将在适当时使用该方法(注意:方法名的命名规则要符合 KVC 的标准命名方法,包括方法签名。)

  3. 查找countOf <Key>enumeratorOf<Key>memberOf<Key>这三个方法

  4. 如果这三个方法都找到,则会创建一个响应所有NSSet方法的集合代理对象,并返回该对象,此代理对象随后将其收到的所有NSSet消息转换为countOf<Key>enumeratorOf<Key>memberOf<Key>:消息的某种组合,用于创建它的对象

  5. 检查类方法InstanceVariablesDirectly是否YES,依次搜索

  6. _<key>

  7. _is<Key>

  8. <key>

  9. is<Key>

  10. 根据属性的类型值,返回不同的结果

  11. 对象指针,直接返回结果

  12. 如果是 NSNumber 支持标量的类型,则将其存储在 NSNumber 实例中并返回

  13. 如果是 NSNumber 不支持的标量类型,请转换为 NSValue 对象

  14. 如果一直没有找到,系统会执行该对象的valueForUndefinedKey:方法,默认抛出NSUndefinedException异常

RunLoop

怎么保证子线程的数据回来更新 UI 操作不打断用户的滑动操作

将更新 UI 的事件,放到主线程的 NSDefaultRunloopModel 上执行,这样就会等用户不再滑动,主线程的 RunLoop 由 UITrakingRunLoopModel 切换到 NSDefaultRunloopModel 时再去更新 UI

RunLoop 有几种模式

  1. kCFRunloopDefultModel:默认模式,主线程也是在此 model 下执行

  2. UITrakingRunLoopModel:跟踪用户事件

  3. UIInitializetionRunLoopModel:在刚启动 APP 时第一个 Model,启动完成后就不再使用

  4. GSEventReceiveRunloopModel:接受内部事件,一般用不到

  5. kCFRunloopCommonModels:伪 model,是同步 source/timer/observer 到多个 Model 的一种解决方案

面向对象的三大特性

  • 封装

  • 继承

  • 多肽

网络

http 与 https 有什么区别

  • Http 协议 = Http 协议 + SSL/TLS 协议


SSL 全程是Secure Sockets Layer,即安全套接层协议,是为网络通信提供安全及数据完整性的一种安全协议。TLS 全称是Transport Layer Security,即安全传输层协议,即 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 定义的属性必须初始化, struct 不用初始化


// class定义class Person {    final var name:String = ""  }
// strcut定义struct Person{ var name:String}
复制代码


  • 定义函数


class可以使用关键字static修饰,struct只能使用static修饰


  • 扩展下标


class 和 struct 都可以使用扩展下标


  • 初始化


结构体有默认的初始化方法


  • 结构体不能继承

  • 类是引用类型,结构体是值类型

  • 类有 deinit 方法,结构体没有

什么时候使用结构体

  • 用于封装简单的数据结构类型

  • 结构在传递的时候是被赋值,而不是被引用

  • 不需要继承或者方法

swift 怎么防止父类方法在子类被重写

class Person {    final var name:String = ""    final func personName() {
} }
复制代码


final关键字


作者:丸疯链接:https://juejin.cn/post/6971808317282730015

文末推荐:iOS 热门文集

用户头像

iOSer

关注

微信搜索添加微信 mayday1739 进微信群哦 2020.09.12 加入

更多大厂面试资料进企鹅群931542608

评论

发布
暂无评论
iOS开发21年6月面试总结(未完待续~)