1、@autoreleasrPool
的释放时机?
自动释放池的原理是OC的一种内存自动回收机制,可以将一些临时变量通过自动释放池来回收统一释放。自动释放池本事销毁的时候,池子里面所有的对象都会做一次release操作,所以本质是对象的延迟释放。
而当一个运行循环结束前会释放自动释放池,还有池子满了也会销毁。
2、ARC应该遵循的原则
谁创建谁释放
alloc/retain/copy的时候,自动引用计数retaincount + 1
当retainCount为0的时候,对象销毁
不能使用retain/release/retainCount/autorelease
不能使用NSAllocateObject/NSDeallocateObject
须遵循内存管理的方法命名规则
不要显式调用dealloc(即不能手动调用的dealloc方法, 可以重载)
使用@autoreleasepool block代替NSAutoreleasePool
不能使用区域(NSZone)
对象型变量不能作为C语言结构体(struct/union)的成员
显式转换”id”和”void *”
3、访问 __weak 修饰的变量,是否已经被注册在了 @autoreleasePool
中?为什么?
在访问附有__weak修饰符的变量时,实际上必定要访问注册到autoreleasepool的对象。因为__weak只是持有对象的弱引用,在访问引用对象的过程中,该对象可能被废弃销毁,所以把对象放到@autoreleasePool
中,可以保证在block结束前,该对象都存在。
4、__weak 和 __unsafe_unretained 的区别
对于__weak,当释放指针指向的对象时,该对象的指针将转换为nil,这是比较安全的行为。而__unsafe_unretain,正如其名称隐藏的含义,尽管释放指针指向的对象时,该指针将继续指向原来的内存,变量所指的对象已经被抛弃了,地址还还存在,但内存中对象已经没有了。如果还是访问该对象,将引起「BAD_ACCESS」错误,这将会导致应用crash,所以是unsafe。
为什么我们仍要使用__unsafe_unretain呢?这是因为__weak直到iOS5.0以及lion之后才出现。
5、为什么已经有了 ARC ,但还是需要 @autoreleasePool
的存在?
正常情况下,一个被标记为“autorelease”的对象,在retain count为0的时候,要等到当前runloop结束的时候,才会被释放。而在当前runloop结束之前,可能会出现无数个等待被释放而没有被释放的对象,这时候内存占用率就会比较高。恰当的使用@autoreleasepool可以及时释放这些对象,降低内存的使用率。
根据Apple的文档,@autoreleasepool使用场景如下:
- 写基于命令行的的程序时,就是没有UI框架,如AppKit等Cocoa框架时;
- 写循环,循环里面包含了大量临时创建的对象;
- 创建了新的线程。(非Cocoa程序创建线程时才需要);
- 长时间在后台运行的任务。
6、__weak 属性修饰的变量,如何实现在变量没有强引用后自动置为 nil?
weak 对象会放入一个 hash 表中。 用 weak 指向的对象内存地址作为 key,当此对象的引用计数为0的时候会 dealloc,假如 weak 指向的对象内存地址是a,那么就会以a为键, 在这个 weak 表中搜索,找到所有以a为键的 weak 对象,从而设置为 nil。这个方法首先根据对象地址获取所以Weak指针地址的数组,然后遍历这个数组把其中的数据设为nil,最后把这个entry从Weak表中删除。
7、说一下什么是悬垂指针?什么是野指针?
- 悬垂指针:
指向曾经存放对象的内存,但是该对象已经不存在了;delete操作完成后的指针就是悬垂指针,此时需要将指针置为0变为零值指针; - 野指针:
野指针指的是还没有初始化的指针。
8、内存管理默认的关键字是什么?
- MRC
为retain/assign/ 循环引用为retain/assign - ARC
为strong/assign 循环引用为__strong/__weak
9、内存中的5大区分别是什么?
堆、栈、全局区/静态区(Data Segment)、常量区、代码区
10、说一下对 retain,copy,assign,weak,_Unsafe_Unretain 关键字的理解
- retain:MRC时候用的,是指针复制(浅复制),引用计数加1,而不会导致内容被复制;
- copy:复制内容(深复制)如果调用copy的是数组,则为指针复制(浅复制),仅仅复制子元素的指针;
- assign:对基础数据类型(NSInteger,CGFloat)和C数据类型(int,float,double,char等);
- strong:strong相当于retain,strong在ARC环境下为默认属性类型;
- weak:取代之前的assign,对象销毁之后会自动置为nil,防止野指针;
11、是否了解 深拷贝 和 浅拷贝 的概念,集合类深拷贝如何实现?
12、Http 和 Https 的区别?为什么更加安全?Https 的加密过程?
HTTPS (基于安全套接字层的超文本传输协议 或者是 HTTP over SSL) 是一个 Netscape 开发的 Web 协议。
你也可以说:HTTPS = HTTP + SSL/TLS
HTTPS 在 HTTP 应用层的基础上使用安全套接字层作为子层。
HTTP 和 HTTPS 的不同之处
https协议需要到CA申请证书。
http是超文本传输协议,信息是明文传输;https 则是具有安全性的ssl加密传输协议。
http和https使用的是完全不同的连接方式,用的端口也不一样,前者是80,后者是443。
http的连接很简单,是无状态的;HTTPS协议是由SSL+HTTP协议构建的可进行加密传输、身份认证的网络协议,比http协议安全。
https://blog.csdn.net/renwotao2009/article/details/51068676
https://blog.csdn.net/wzgxyy1234/article/details/51750007
13、解释一下 三次握手 和 四次挥手?也可以说网络连接的过程
三次握手:
第一次:客户端向服务器发送syn包,此时进入[SYN_SENT]状态,等待服务器返回信息;这里客户端什么都不能确认,而服务端确认了客户端发送是正常的。
第二次:服务器收到syn包,需要确认客户端的syn包,同时自己发出一个syn+ask包给客户端;此时客户端知道了自己发送、接收正常,服务器发送接收也正常,而服务器只知道自己接受正常,对方发送正常。
第三次:[客户端]收到服务器的syn+ask包后,向服务器发送确认包ask,此包发送完毕,客户端和服务器进入[ESTABLISHED](TCP连接成功)状态,完成三次握手。此时双方都可以确定自己和对方发送以及接收都正常。
14、为什么要采用三次握手,两次不行吗?
15、GET 和 POST 请求的区别?
按照我的理解
- get请求的参数会被放在url中,而post的参数会被放在body中。
- 因为上面的缘故,所以说get比post略显不安全,因为比如登录名密码用get的方式的话,都是明文显示在url中的,会被浏览器缓存。
- 一般用get来进行获取数据,因为有缓存,所以可以降低服务器的压力;而post一般用来进行操作数据,如增删改。
- get由于url或者浏览器长度限制,一般参数不能超过1k,而post没有长度限制,所以进行大文件上传的话用post。
16、HTTP 请求报文 和 响应报文的结构?
-
HTTP请求报文格式:
1、请求行:
由3部分组成,分别为:请求方法、URL(见备注1)以及协议版本,之间由空格分隔
请求方法包括GET、HEAD、PUT、POST、TRACE、OPTIONS、DELETE以及扩展方法,当然并不是所有的服务器都实现了所有的方法,部分方法即便支持,处于安全性的考虑也是不可用的
协议版本的格式为:HTTP/主版本号.次版本号,常用的有HTTP/1.0和HTTP/1.1。
2、请求头部:
常见请求头如下:
3、请求正文:
- HTTP响应报文格式:
HTTP响应报文主要由状态行、响应头部、响应正文3部分组成
1、状态行:
由3部分组成,分别为:协议版本,状态码,状态码描述,之间由空格分隔
状态代码为3位数字,200-299的状态码表示成功,300-399的状态码指资源重定向,400-499的状态码指客户端请求出错,500-599的状态码指服务端出错(HTTP/1.1向协议中引入了信息性状态码,范围为100-199)
2,响应头部:
与请求头部类似,为响应报文添加了一些附加信息
17、什么是Mime Type?
首先,我们要了解浏览器是如何处理内容的。在浏览器中显示的内容有 HTML、有 XML、有 GIF、还有 Flash ……那么,浏览器是如何区分它们,决定什么内容用什么形式来显示呢?答案是 MIME Type,也就是该资源的媒体类型。
常见的MIME类型:
超文本标记语言文本 .html,.html text/html
普通文本 .txt text/plain
RTF文本 .rtf application/rtf
GIF图形 .gif image/gif
JPEG图形 .ipeg,.jpg image/jpeg
au声音文件 .au audio/basic
MIDI音乐文件 mid,.midi audio/midi,audio/x-midi
RealAudio音乐文件 .ra, .ram audio/x-pn-realaudio
MPEG文件 .mpg,.mpeg video/mpeg
AVI文件 .avi video/x-msvideo
GZIP文件 .gz application/x-gzip
TAR文件 .tar application/x-tar
18、数据传输的加密过程?
一、对称加密
加密数据与解密数据使用相同的密钥,这种加密方法称为对称加密
特点
加密与解密使用相同密钥,加密解密速度快
将原始数据进行切块,逐个进行加密。
缺点
每一个通信的对象都有一把密钥,如果通信对象过多导致密钥过多。
密钥分发问题,如何保证密钥不被窃取
二、非对称加密
加密数据与解密数据使用一堆不相同的密钥,公钥公开给所有人,私钥自己保存。使用公钥加密的数据只有自己的私钥可以解开。
特点
用公钥加密数据,只能使用配对的私钥进行解密。
用私钥加密的数据,只能使用配对的私钥进行解密
缺点
加密解密速度慢、时间长,不适用于对大数据进行加密解密。
以上三种如果单独使用任何一种对数据进行数据加密的话都是不安全的,那么现在在互联网中的数据时如何传输的呢?其实数据在互联网中并不会使用单一加密技术,往往都是各类技术混合使用,互补优缺点使数据的传输更加安全。
首先通过TCP三次握手进行连接,然后客户端发送hello包到服务端,服务端回应一个hello包,如果客户端需要再次发送数字证书, 则发送数字证书到客户端。
客户端得到服务器的证书后通过CA服务验证真伪、验证证书的主体与访问的主体是否一致,验证证书是否在吊销证书列表中。如果全部通过验证则与服务器端进行加密算法的协商。
然后是用证书中服务器的公钥加密【对称秘钥】发送给服务器端,【对称秘钥】只能用服务器的私钥进行解密,当服务器通过私钥解密【对称秘钥】后。使用对称秘钥将客户端请求的数据发送到客户端,客户端在用对称秘钥进行解密,从而得到想要的数据。
19、说一下 TCP/IP 五层模型的协议?
20、说一下 OSI 七层模型的协议?
21、大文件下载的功能有什么注意点?断点续传的实现?
首先NSUrlConnection(ios9废弃)和NSUrlSession解决的方法是不一样的。
1、NSUrlConnection
- 大文件下载
/**
* 2.当接收到服务器返回的实体数据时调用(具体内容,这个方法可能会被调用多次)
*
* @param data 这次返回的数据
*/
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
}
这个方法会被频繁的调用,每次都会传回来一部分data,这时候我们通常想到的方法是定义一个全局的NSMutableData,接受到响应的时候初始化这个MutableData,在didReceiveData方法里面去拼接
[self.totalData appendData:data];
最后在完成下载的方法里面把整个MutableData写入沙盒。
但是!!!
这样会造成内存的暴涨,为了解决这一问题,就必须在获取一部分data后,就将其写入沙盒,然后释放掉内存中的data。
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
// 移动到文件的最后面
[self.writeHandle seekToEndOfFile];
// 将数据写入沙盒
[self.writeHandle writeData:data];
// 累计写入文件的长度
self.currentLength += data.length;
// 下载进度
self.myPregress.progress = (double)self.currentLength / self.totalLength;
}
- 断点下载
不难看出,通过设置请求头的Range我们可以指定下载的位置、大小。
那么我们这样设置bytes=500- 从500字节以后的所有字节,
只需要在didReceiveData中记录已经写入沙盒中文件的大小(self.currentLength),
把这个大小设置到请求头中,因为第一次下载肯定是没有执行过didReceive方法,self.currentLength也就为0,也就是从头开始下。代码如下:
// 2.请求
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
// 设置请求头
NSString *range = [NSString stringWithFormat:@"bytes=%lld-", self.currentLength];
[request setValue:range forHTTPHeaderField:@"Range"];
// 3.下载
self.connection = [NSURLConnection connectionWithRequest:request delegate:self];
2、NSUrlSession
- 大文件下载
//1.创建NSULRSession,设置代理
self.session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration] delegate:self delegateQueue:[NSOperationQueue mainQueue]];
//2.创建task
NSURL *url = [NSURL URLWithString:@"http://120.25.226.186:32812/resources/videos/minion_01.mp4"];
self.downloadTask = [self.session downloadTaskWithURL:url];
//3.执行task
[self.downloadTask resume];
(1)downloadTaskWithURL内部默认已经实现了变下载边写入操作,所以不用开发人员担心内存问题;
(2)文件下载后默认保存在tmp文件目录,需要开发人员手动的剪切到合适的沙盒目录。
#pragma mark - delegate
//1.下载完毕会调用
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)location {
NSLog(@"下载完毕");
// 创建存储文件路径
NSString *caches = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES)lastObject];
// response.suggestedFilename:建议使用的文件名,一般跟服务器端的文件名一致
NSString *file = [caches stringByAppendingPathComponent:downloadTask.response.suggestedFilename];
/**将临时文件剪切或者复制到Caches文件夹
AtPath :剪切前的文件路径
toPath :剪切后的文件路径
*/
NSFileManager *mgr = [NSFileManager defaultManager];
[mgr moveItemAtPath:location.path toPath:file error:nil];}
//2.执行下载任务时有数据写入,在这里面监听下载进度
- (void)URLSession:(NSURLSession *)session
downloadTask:(NSURLSessionDownloadTask *)downloadTask
didWriteData:(int64_t)bytesWritten
totalBytesWritten:(int64_t)totalBytesWritten
totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite {
NSLog(@"%@", [NSString stringWithFormat:@"下载进度:%f",(double)totalBytesWritten/totalBytesExpectedToWrite]);
}
- 断点续传
设置一个@property (nonatomic,strong) NSData *resumeData; //包含了继续下载的开始位置和下载的url
具体方法如下:
- (IBAction)start:(id)sender {
self.session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration] delegate:self delegateQueue:[NSOperationQueue mainQueue]];
NSURL *url = [NSURL URLWithString:@"http://120.25.226.186:32812/resources/videos/minion_01.mp4"];
self.downloadTask = [self.session downloadTaskWithURL:url];
[self.downloadTask resume];
}
- (IBAction)stop:(id)sender {
__weak typeof(self) vc = self;
[self.downloadTask cancelByProducingResumeData:^(NSData * _Nullable resumeData) {
//resumeData : 包含了继续下载的开始位置和下载的url
vc.resumeData = resumeData;
vc.downloadTask = nil;
}];
}
- (IBAction)continued:(id)sender {
// 传入上次暂停下载返回的数据,就可以回复下载
self.downloadTask = [self.session downloadTaskWithResumeData:self.resumeData];
// 开始任务
[self.downloadTask resume];
// 清空
self.resumeData = nil;
}
参考:https://www.cnblogs.com/jierism/p/6284304.html
22、NSUrlProtocol用过吗?用在什么地方了?
听说过,NSUrlProtocol是苹果URL Loading System中的一个抽象类。通过实现其子类并注册到APP中,我们可以拦截APP中的网络请求。那么它可以用来做什么呢?
- app内部设置了测试服、正式服的切换开关,需要一键切换所有网络请求的server地址;
- 限制app内webview的跳转行为,例如禁止向某域名下的跳转或者对其进行重定向操作,当然这些用webview的代理也可以做到;
- 对于app内所有的网络请求,需要添加公共的header;
- 需要统计某个api的调用次数;
- 需要统计网络请求的失败率。
23、实例对象的数据结构?
id obj = self
这就是一个OC对象。
/// A pointer to an instance of a class.
/// 指向一个实例的指针
typedef struct objc_object *id;
/// Represents an instance of a class.
/// 表示类的实例。
struct objc_object {
Class _Nonnull isa OBJC_ISA_AVAILABILITY;
};
整个实例对象的结构体内部,只有一个isa指针,指向了其所属的class。在调用实例方法时,系统会根据isa指针去类的方法列表或者父类的方法列表中,去寻找与消息对应的selector指向的方法。
24、类对象的数据结构?
Class class = [self class];
这就是一个类。点进去:
#import <objc/objc.h>
/// An opaque type that represents an Objective-C class.
typedef struct objc_class *Class;
可以看到class的本质是一个objc_class的结构体。
struct objc_class {
Class isa ; // 指向所属类的指针(_Nonnull)
Class super_class; // 父类(_Nullable)
const char * name; // 类名(_Nonnull)
long version; // 类的版本信息(默认为0)
long info; // 类信息(供运行期使用的一些位标识)
long instance_size; // 该类的实例变量大小
struct objc_ivar_list * ivars; // 该类的成员变量链表(_Nullable)
struct objc_method_list ** methodLists ; // 方法定义的链表(_Nullable)
struct objc_cache * cache // 方法缓存(_Nonnull)
struct objc_protocol_list * protocols; // 协议链表(_Nullable)
} ;
这里就先不详细展开了,如果详细了解可以看这篇文章:https://www.jianshu.com/p/f82fe7ead6ce
25、元类对象的数据结构?
Meta Class。上面我们看到了。在objc_class中也有一个isa指针、这说明Class类本身也是一个对象。为了处理类和对象的关系、Runtime 库创建了一种叫做Meta Class(元类) 的东西、类对象所属的类就叫做元类。Meta Class表述了类对象本身所具备的元数据。
元类用来储存类方法。
元类也是一个对象、所以才能调用他的方法。
元类的元类、为根元类。
根元类的元类、是其本身。
26、Category 的实现原理?
Category 在编译时,新添加的方法,都被以倒序插入到原有方法列表的最前面,所以不同的Category,添加了同一个方法,执行的实际上是最后一个。而原类中,如果有相同的方法,则排在后面。并没有被覆盖,其实可以理解为存在两个MethodA方法。所以这也是不建议用Category复写原类方法的原因,因为不能调用到原类的方法了,而且会造成不可预估的问题。
27、Category 有哪些用途?
- 给现有的类添加方法;
- 将一个类的实现拆分成多个独立的源文件;
- 声明私有的方法。
28、如何给 Category
添加属性?关联对象以什么形式进行存储?
@interface NSObject (Property)
// @property分类:只会生成get,set方法声明,不会生成实现,也不会生成下划线成员属性
@property NSString *name;
@property NSString *height;
@end
@implementation NSObject (Property)
- (void)setName:(NSString *)name {
// objc_setAssociatedObject(将某个值跟某个对象关联起来,将某个值存储到某个对象中)
// object:给哪个对象添加属性
// key:属性名称
// value:属性值
// policy:保存策略
objc_setAssociatedObject(self, @"name", name, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (NSString *)name {
return objc_getAssociatedObject(self, @"name");
}
// 调用
NSObject *objc = [[NSObject alloc] init];
objc.name = @"123";
NSLog(@"runtime动态添加属性name==%@",objc.name);
// 打印输出
2016-02-17 19:37:10.530 runtime[12761:543574] runtime动态添加属性--name == 123
其实,给属性赋值的本质,就是让属性与一个对象产生关联,所以要给NSObject的分类的name属性赋值就是让name和NSObject产生关联,而runtime可以做到这一点。
我们可以看到所有的关联对象都由AssociationsManager管理,AssociationsManager里面是由一个静态AssociationsHashMap(哈希表)来存储所有的关联对象的。这相当于把所有对象的关联对象都存在一个全局单例里面。
28、Category 和 Extension 有什么区别
1.分类的加载在 运行时,类拓展的加载在 编译时。
2.类扩展中添加的新方法,一定要实现。Category中没有这种限制。
3.类拓展只以声明的形式存在,一般存在 .m 文件中。
29、说一下 Method Swizzling? 说一下在实际开发中你在什么场景下使用过?
文章:https://www.jianshu.com/p/f6dad8e1b848
简单来说我们主要是使用Method Swizzling来把系统的方法交换为我们自己的方法,从而给系统方法添加一些我们想要的功能。
目前已更新实例汇总:
- 实例一:替换ViewController生命周期方法
- 实例二:解决获取索引、添加、删除元素越界崩溃问题
- 实例三:防止按钮重复暴力点击
- 实例四:全局更换控件初始效果
- 实例五:App热修复
- 实例六:App异常加载占位图通用类封装
- 实例七:全局修改导航栏后退(返回)按钮
30、如何实现动态添加方法和属性?
OC 中我们很习惯的会用懒加载,当用到的时候才去加载它,但是实际上只要一个类实现了某个方法,就会被加载进内存。当我们不想加载这么多方法的时候,就会使用到 runtime 动态的添加方法。
- (void)viewDidLoad {
[super viewDidLoad];
Person *p = [[Person alloc] init];
// 默认person,没有实现run:方法,可以通过performSelector调用,但是会报错。
// 动态添加方法就不会报错
[p performSelector:@selector(run:) withObject:@10];
}
@implementation Person
// 没有返回值,1个参数
// void,(id,SEL)
void aaa(id self, SEL _cmd, NSNumber *meter) {
NSLog(@"跑了%@米", meter);
}
// 任何方法默认都有两个隐式参数,self,_cmd(当前方法的方法编号)
// 什么时候调用:只要一个对象调用了一个未实现的方法就会调用这个方法,进行处理
// 作用:动态添加方法,处理未实现
+ (BOOL)resolveInstanceMethod:(SEL)sel
{
// [NSStringFromSelector(sel) isEqualToString:@"run"];
if (sel == NSSelectorFromString(@"run:")) {
// 动态添加run方法
// class: 给哪个类添加方法
// SEL: 添加哪个方法,即添加方法的方法编号
// IMP: 方法实现 => 函数 => 函数入口 => 函数名(添加方法的函数实现(函数地址))
// type: 方法类型,(返回值+参数类型) v:void @:对象->self :表示SEL->_cmd
class_addMethod(self, sel, (IMP)aaa, "v@:@");
return YES;
}
return [super resolveInstanceMethod:sel];
}
@end
// 打印输出
2016-02-17 19:05:03.917 runtime[12761:543574] runtime动态添加方法--跑了10米
31、说一下对 isa 指针的理解, 对象的isa 指针指向哪里?isa 指针有哪两种类型?
是一个Class类型的指针,每一个实例对象都有一个isa指针,指向该对象的类。例如NSSting *str的isa指针指向str的类,即NSSring。而类Class里面也有个isa指针,指向元类(meteClass)。元类中保存了类方法的列表。当类方法被调用时,会先从本身查找类方法的实现,如果没有,元类回向父类查找该方法。元类(meteClass)也是类,他也有isa指针,他最终指向一个根元类(root meteClass)。根元类的isa指针指向本身,这样形成了一个封闭的内循环。
- 实例对象 isa 指向类对象
- 类对象指 isa 向元类对象
- 元类对象的 isa 指向元类的基类
isa 有两种类型
纯指针,指向内存地址
NON_POINTER_ISA,除了内存地址,还存有一些其他信息
32、如何运用 Runtime 字典转模型?
利用运行时,遍历模型中所有属性,根据模型的属性名,去字典中查找key,取出对应的值,给模型的属性赋值(从提醒:字典中取值,不一定要全部取出来)。Runtime 遍历 ivar_list,结合 KVC 赋值。
33、说一下对 runtime 的理解。(主要讲一下消息机制,是对上述的总结)
消息机制原理:对象根据方法编号SEL去映射表查找对应的方法实现。
面试:消息机制方法调用流程
怎么去调用eat方法,对象方法:(保存到类对象的方法列表) ,类方法:(保存到元类(Meta Class)中方法列表)。
1.OC 在向一个对象发送消息时,runtime 库会根据对象的 isa指针找到该对象对应的类或其父类中查找方法。。
2.注册方法编号(这里用方法编号的好处,可以快速查找)。
3.根据方法编号去查找对应方法。
4.找到只是最终函数实现地址,根据地址去方法区调用对应函数。
runtime文章:https://www.jianshu.com/p/f82fe7ead6ce
https://www.jianshu.com/p/f82fe7ead6ce
34、Block 有几种类型?分别是什么?
三种。
- _NSGlobeBlock 全局的block,相当于全局变量, 系统会自动释放
- _NSStackBlock 栈内存的block,相当于局部变量, 系统会自动释放
- _NSMallockBlock 堆内存的block,需要手动释放内存
如果block实现中没有访问任何"外部"变量(包括局部和全局), 该block为GlobalBlock
如果block实现中访问了任何"外部"变量(包括局部和全局), 该block为StackBlock
对StackBlock进行拷贝(copy/Block_copy), 该block为MallocBlock
注意点:
block默认都是在栈上创建的, 当block超过作用域, 就会被销毁, 如果要在作用域外使用block, 应copy该block到堆上, 此时会创建一个新的MallocBlock到堆上
声明block对象, 应该使用copy修饰, 将其保存到堆上, 不然在回调时block已经销毁, 无法访问
35、__block 的解释以及在 ARC 和 MRC 下有什么不同?
__block是一个结构体,函数指针。
相同点:
*使用__block都可以解决在block中修改外部变量的问题。
不同点:
在ARC下将会强引用这个对象一次。这也保证了原对象不被销毁,但与此同时,也会导致循环引用问题。
在MRC下将不会被retain,因而可以避免循环引用。
即ARC下解决循环引用用__weak,MRC下用__block。
文章:https://blog.csdn.net/abc649395594/article/details/47086751
36、Block 的内存管理
37、Block 自动截取变量
分两种情况。
第一种,外部变量没有__block的修饰。此时block截获的是block定义处对该变量的一份瞬时拷贝,此后block外的这个变量与block内截获的拷贝相互独立,再没有半毛钱关系。变量值改变不会影响拷贝的值,也就是说block执行时使用的是当时拷贝的值。如果试图在block内对该拷贝进行赋值,编译器会报错。
第二种情况,该变量有__block修饰。此时block截获的是该变量的真身。block执行时使用的是执行时该变量的值,而不是block定义时该变量的值。可以在block内改变该变量的值,而且是实实在在地改变了该变量的值。
文章:https://www.jianshu.com/p/43f1058432e8
38、Block 处理循环引用。
即ARC下解决循环引用用__weak,MRC下用__block。
39、delegate跟block的区别,使用场景有哪些不同?
代理
- 代理是一对一的, 需要设置接口
- 可能引起循环引用,导致内存泄漏
- 通过指定delegate对象为weak,可以避免循环引用
block
- block的不需要设置接口, 使用更为轻型
- 更容易造成循环引用
- 通过指定block内的对象为__weak,可以避免循环引用
使用场景:
代理
- 当要实现的方法大于3个
- 为了防止循环引用时
- 写一个库给别人使用,又不清楚使用者的能力,建议使用delegate
block - 当要实现的方法小于3个
- 需要请求数据回调
40、 block用什么属性修饰,为什么?
- 在MRC中, 定义Block属性时, 应该用copy修饰
- 在ARC中, 定义Block属性时, 系统会自动将其copy, 即复制到堆上. 但习惯上还是会用copy修饰
- 用copy修饰的原因
- block创建时默认是创建在栈上的, 超过作用域后就会被销毁, 只有使用copy才会生成一个堆block, 在作用域外被访问
41、atomic和nonatomic的作用和区别?
- atomic是线程安全的,会自动为setter方法加锁,需要消耗大小资源
- nonatomic是非线程安全的,适合内存小的移动设备
42、什么是 离屏渲染?什么情况下会触发?该如何应对?
https://blog.csdn.net/qq_29846663/article/details/68960512
43、http请求的方式
①GET: 直接访问URI识别的资源,也就是说根据URI来获取资源。
②POST: 用来传输实体的主体。
③PUT: 用来传输文件。
④HEAD: 用来获取报文首部,和GET方法差不多,只是响应部分不会返回主体内容。
⑤DELETE: 删除文件,和PUT恰恰相反。按照请求的URI来删除指定位置的资源。
44、分类,子类,父类中普通方法、+load方法、+initialize方法的调用优先级
1.普通方法的优先级: 分类> 子类 > 父类, 优先级高的同名方法覆盖优先级低的
2.+load方法的优先级: 父类> 子类> 分类
3.+load方法是在main() 函数之前调用,所有的类文件都会加载,包括分类
4.+load方法不会被覆盖
5.同一主类的不同分类中的普通同名方法调用, 取决于编译的顺序, 后编译的文件中的同名方法会覆盖前面所有的,包括主类. +load方法的顺序也取决于编译顺序, 但是不会覆盖
6.分类中的方法名和主类方法名一样会报警告, 不会报错
7.声明和实现可以写在不同的分类中, 依然能找到实现
8.当第一次用到类的时候, 如果重写了+ initialize方法,会去调用
9.当调用子类的+ initialize方法时候, 先调用父类的,如果父类有分类, 那么分类的+ initialize会覆盖掉父类的, 和普通方法差不多
10.父类的+ initialize不一定会调用, 因为有可能父类的分类重写了它
连接:https://blog.csdn.net/appleLg/article/details/79931742
苹果的官方文档很清楚地说明了+initialize和+load的区别在于:
不同点在于:
+load是只要类所在文件被引用就会被调用,而+initialize是在类或者其子类的第一个方法被调用前调用。所以如果类没有被引用到项目中,就不会有+load调用;但即使类文件被引用进来,但是没有使用,那么+initialize也不会被调用。
+load方法通常用来进行Method Swizzle,+initialize方法一般用于初始化全局变量或静态变量。
相同点在于:
方法只会被调用一次。
+load和+initialize方法内部使用了锁,因此它们是线程安全的。