interview我爱编程iOS面试题

iOS面试题

2018-04-16  本文已影响285人  里克尔梅西
1、@autoreleasrPool 的释放时机?

自动释放池的原理是OC的一种内存自动回收机制,可以将一些临时变量通过自动释放池来回收统一释放。自动释放池本事销毁的时候,池子里面所有的对象都会做一次release操作,所以本质是对象的延迟释放。

而当一个运行循环结束前会释放自动释放池,还有池子满了也会销毁。

2、ARC应该遵循的原则
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使用场景如下:

6、__weak 属性修饰的变量,如何实现在变量没有强引用后自动置为 nil?

weak 对象会放入一个 hash 表中。 用 weak 指向的对象内存地址作为 key,当此对象的引用计数为0的时候会 dealloc,假如 weak 指向的对象内存地址是a,那么就会以a为键, 在这个 weak 表中搜索,找到所有以a为键的 weak 对象,从而设置为 nil。这个方法首先根据对象地址获取所以Weak指针地址的数组,然后遍历这个数组把其中的数据设为nil,最后把这个entry从Weak表中删除。

7、说一下什么是悬垂指针?什么是野指针?
8、内存管理默认的关键字是什么?
9、内存中的5大区分别是什么?

堆、栈、全局区/静态区(Data Segment)、常量区、代码区

10、说一下对 retain,copy,assign,weak,_Unsafe_Unretain 关键字的理解
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、为什么要采用三次握手,两次不行吗?
image
15、GET 和 POST 请求的区别?

按照我的理解

16、HTTP 请求报文 和 响应报文的结构?

1、请求行:
由3部分组成,分别为:请求方法、URL(见备注1)以及协议版本,之间由空格分隔

请求方法包括GET、HEAD、PUT、POST、TRACE、OPTIONS、DELETE以及扩展方法,当然并不是所有的服务器都实现了所有的方法,部分方法即便支持,处于安全性的考虑也是不可用的

协议版本的格式为:HTTP/主版本号.次版本号,常用的有HTTP/1.0和HTTP/1.1。

2、请求头部:
常见请求头如下:

image.png

3、请求正文:

HTTP响应报文主要由状态行、响应头部、响应正文3部分组成


image.png

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 五层模型的协议?
image.png
20、说一下 OSI 七层模型的协议?
image.png
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]);
}
- (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中的网络请求。那么它可以用来做什么呢?

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来把系统的方法交换为我们自己的方法,从而给系统方法添加一些我们想要的功能。

目前已更新实例汇总:

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 指针有哪两种类型?

每一个对象都有一个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 有几种类型?分别是什么?

三种。

如果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的区别,使用场景有哪些不同?

代理

block

使用场景:
代理

40、 block用什么属性修饰,为什么?

41、atomic和nonatomic的作用和区别?
上一篇 下一篇

猜你喜欢

热点阅读