常见的iOS面试题

2021-10-13  本文已影响0人  小哥_xiaoge

1. Cocoa有私有方法吗?

Cocoa没有任何真正的私有方法。只要知道对象支持的某个方法的名称,即使该对象所在的类的接口中没有该方法的声明,你也可以调用该方法。不过这么做编译器会报错,但是只要新建一个该类的 类别(category),在类别.h文件中写上原始类该方法的声明,类别.m文件中什么也不写,就可以正常调用私有方法了。这就是传说中的 私有方法前向引用。 所以说Cocoa没有真正的私有方法。

2. 什么时候会报unrecognized selector的异常,怎么解决?

简单来说:当调用该对象上某个方法,而该对象上没有实现这个方法的时候, 可以通过“消息转发”进行解决。

objc在向一个对象发送消息时,runtime库会根据对象的isa指针找到该对象实际所属的类,然后在该类中的方法列表以及其父类方法列表中寻找方法运行,如果,在最顶层的父类中依然找不到相应的方法时,程序在运行时会挂掉并抛出异常 unrecognized selector sent to XXX 。但是在这之前,objc的运行时会给出三次拯救程序崩溃的机会:

消息转发流程简图

注意: 不少初学runtime的同学都被这张图(侵删)有点误导了,认为resolveInstanceMethod返回NO才接着走后面的转发流程,而返回YES就停止转发了,其实如果重写的resolveInstanceMethod什么也不做,只是返回YES也会接着走后面的转发流程。
这个返回值对于消息转发流程没有任何意义,从runtime的源码来看这个返回值只和debug的信息相关。
原文链接:https://blog.csdn.net/yinyignfenlei/article/details/105366819

3. App 编译过程有了解吗?

可以参考这篇文章

一般会执行如下几个步骤:

4. iOS程序的启动原理(过程)

一、解析Info.plist
二、Mach-O加载
三、程序执行

5. 影响 iOS程序启动性能的因素

一、main()函数之前耗时的影响因素
二、main()函数之后耗时的影响因素

6. iOS程序启动性能的优化

iOS程序启动性能的优化

7. 单例优缺点

优点:

缺点:

8. load 和 Initialize 的区别

9. 什么是 离屏渲染?什么情况下会触发?该如何应对?

离屏渲染就是在当前屏幕缓冲区以外,新开辟一个缓冲区进行操作。

离屏渲染出发的场景有以下:

为什么要避免离屏渲染?

CPU GPU 在绘制渲染视图时做了大量的工作。离屏渲染发生在 GPU 层面上,会创建新的渲染缓冲区,会触发 OpenGL 的多通道渲染管线,图形上下文的切换会造成额外的开销,增加 GPU 工作量。如果 CPU GPU 累计耗时 16.67 毫秒还没有完成,就会造成卡顿掉帧。

圆角属性蒙层遮罩 都会触发离屏渲染。指定了以上属性,标记了它在新的图形上下文中,在未愈合之前,不可以用于显示的时候就出发了离屏渲染。

10. 说一下对 APNS 的认识?

我写过三篇关于推送框架的总结:

以前的通知基于 Socket ,但是不稳定。新的通知框架基于 Http

设备的唯一识别码,程序的BundleID,通过长连接发给 APNS 服务器,返回一个 DeviceToken。把这个 DeviceToken 发给服务器,以后服务器把 DeviceToken 和 通知的内容发给 APNS 就可以了。

11. Designated Initializer 的规则

1.推荐一篇写的很好的文章
2.这篇文章写得更简单易懂

12. Autoreleasepool原理及释放时机

AutoreleasePool是在RunLoop即将进入RunLoop和准备进入休眠这两种状态的时候被创建和销毁的。
所以AutoreleasePool的释放有如下两种情况:

13.delegate、Block和Notification的区别

14.iOS为什么很少用try catch?

准确的说是OC很少用try catch,因为OC的异常类型多,不统一,而try catch不能捕获所有类型,使用价值不高。同时try catch对异常的处理是stack unwind机制?从itanium C++ ABI中我们知道,stack unwind是采用了压缩的栈帧信息表,遇到异常的时候通过栈回溯的方式一层一层地处理每一层调用栈在异常处理时的相关逻辑,会有一定的性能损失。

swift 在 ABI 上给 error 单独分配了一个寄存器,可以快速地将 error 透传到外面去。

15. AFNetworking为何需要常驻子线程?3.0后为什么又不需要开启了?

一、2.x为何要开启常驻子线程?

先来看看 NSURLConnection 发送请求时的线程情况,NSURLConnection 是被设计成异步发送的,调用了start方法后,NSURLConnection 会新建一些线程用底层的 CFSocket 去发送和接收请求,在发送和接收的一些事件发生后通知原来线程的Runloop去回调事件。

AFN(2.x基于NSURLConnection,此类现已废弃) 的做法是把网络请求的发起和解析都放在同一个子线程中进行,但由于子线程默认不开启 runloop,它会向一个 C语言程序那样在运行完所有代码后退出线程。而网络请求是异步的,这会导致获取到请求数据时,线程已经退出,代理方法没有机会执行。因此,AFN 的做法是使用一个 runloop 来保证线程不死。然而频繁的创建线程并启动runloop肯定会造成内存泄露(runloop 无法停止.线程无法退出),所以AFN就创建了一个单例线程,并且保证线程不退出~

二、2.x怎么开启常驻子线程?
+ (void)networkRequestThreadEntryPoint:(id)__unused object {
    @autoreleasepool {
        [[NSThread currentThread] setName:@"AFNetworking"];

        NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
        [runLoop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];
        [runLoop run];
    }
}

+ (NSThread *)networkRequestThread {
    static NSThread *_networkRequestThread = nil;
    static dispatch_once_t oncePredicate;
    dispatch_once(&oncePredicate, ^{
        _networkRequestThread = [[NSThread alloc] initWithTarget:self
selector:@selector(networkRequestThreadEntryPoint:) object:nil];
        [_networkRequestThread start];
    });

    return _networkRequestThread;
}
三、3.0后为什么又不需要开启了?

NSURLConnection的一大痛点就是:发起请求后,这条线程并不能随风而去,而需要一直处于等待回调的状态。从iOS9.0开始 deprecated 了NSURLConnection,替代方案就是NSURLSession

self.operationQueue = [[NSOperationQueue alloc] init];
self.operationQueue.maxConcurrentOperationCount = 1;
self.session = [NSURLSession sessionWithConfiguration:self.sessionConfiguration 
delegate:self delegateQueue:self.operationQueue];

从上面的代码可以看出,NSURLSession发起的请求,不再需要在当前线程进行代理方法的回调!可以指定回调的delegateQueue,这样我们就不用为了等待代理回调方法而苦苦保活线程了。

同时还要注意一下,指定的用于接收回调的Queue的maxConcurrentOperationCount设为了1,这里目的是想要让并发的请求串行的进行回调。

- (AFURLSessionManagerTaskDelegate *)delegateForTask:(NSURLSessionTask *)task {
    NSParameterAssert(task);
    AFURLSessionManagerTaskDelegate *delegate = nil;
    [self.lock lock];
    //给所要访问的资源加锁,防止造成数据混乱
    delegate = self.mutableTaskDelegatesKeyedByTaskIdentifier[@(task.taskIdentifier)];
    [self.lock unlock];
    return delegate;
}

上面的代码对 self.mutableTaskDelegatesKeyedByTaskIdentifier 的访问进行了加锁,目的是保证多线程环境下的数据安全。既然加了锁,就算maxConcurrentOperationCount不设为1,当某个请求正在回调时,下一个请求还是得等待一直到上个请求获取完所要的资源后解锁,所以这边并发回调也是没有意义的。相反多task回调导致的多线程并发,还会导致性能的浪费。

/*
AF3.x会给每个 NSURLSessionTask 绑定一个 
AFURLSessionManagerTaskDelegate ,这个TaskDelegate相当于把
NSURLSessionDelegate进行了一层过滤,最终只保留类似
didCompleteWithError这样对上层调用者输出的回调。
*/
- (void)URLSession:(__unused NSURLSession *)session 
task:(NSURLSessionTask *)task 
didCompleteWithError:(NSError *)error
{
//此处代码进行了大量删减,只是为了让大家清楚的看到这个方法做的最重要的事
dispatch_group_async(manager.completionGroup ?: url_session_manager_completion_group(), 
manager.completionQueue ?: dispatch_get_main_queue(), ^{
            if (self.completionHandler) {
                self.completionHandler(task.response, responseObject, error);
            }
    }
}
四、为什么AF3.0中需要设置self.operationQueue.maxConcurrentOperationCount = 1;,而AF2.x却不需要?

功能不一样:AF3.0的operationQueue是用来接收NSURLSessionDelegate回调的,鉴于一些多线程数据访问的安全性考虑,设置了maxConcurrentOperationCount = 1来达到串行回调的效果。而AF2.0的operationQueue是用来添加operation并进行并发请求的,所以不要设置为1。

- (AFHTTPRequestOperation *)POST:(NSString *)URLString
                      parameters:(id)parameters
                         success:(void (^)(AFHTTPRequestOperation *operation, id responseObject))success
                         failure:(void (^)(AFHTTPRequestOperation *operation, NSError *error))failure
{
    AFHTTPRequestOperation *operation = [self HTTPRequestOperationWithHTTPMethod:@"POST" URLString:URLString parameters:parameters success:success failure:failure];
    [self.operationQueue addOperation:operation];
    return operation;
}

更详细的文章: https://www.likecs.com/show-203542885.html

16. SDWebImage是如何做到Url不变的情况下,更新图片内容

通过查阅HTTP协议相关的资料得知,与服务器返回的Last-Modified相对应的request header里可以加一个名为If-Modified-Since的key,value即是服务器回传的服务端图片最后被修改的时间,第一次图片请求时If-Modified-Since的值为空,第二次及以后的客户端请求会把服务器回传的Last-Modified值作为If-Modified-Since的值传给服务器,这样服务器每次接收到图片请求时就将If-Modified-Since与Last-Modified进行比较,如果客户端图片已陈旧那么返回状态码200、Last-Modified、图片内容,客户端存储Last-Modified和图片;如果客户端图片是最新的那么返回304 Not Modified、不会返回Last-Modified、图片内容。

Apache比较时是看If-Modified-Since之后有没有更新图片,Nginx比较时是看If-Modified-Since与Last-Modified是否相等,所以对于Apache服务器环境客户端每次都要严格的存储服务器回传的Last-Modified以便下次请求时作为If-Modified-Since的值传给服务器,对于Nginx服务器环境客户端不必存储服务器回传的Last-Modified,每次请求时只需将图片自身的fileModificationDate作为If-Modified-Since的值传服务器即可。在实际开发中,如果遇到明明传了If-Modified-Since、服务器图片也变更了、但是客户端却请求不到最新的图片的情况时,那么就需要查看一下服务器对这两个时间戳的比较逻辑。

通过查看SDWebImageDownloader的源码得知,它开放了一个headersFilter的block,意在让开发者可以对所有图片请求追加一些额外的header,这正合我意。那么我们就可以在诸如AppDelegate didFinishLaunching的地方追加如下代码:

SDWebImageDownloader *imgDownloader = SDWebImageManager.sharedManager.imageDownloader;
imgDownloader.headersFilter  = ^NSDictionary *(NSURL *url, NSDictionary *headers) {
 
    NSFileManager *fm = [[NSFileManager alloc] init];
    NSString *imgKey = [SDWebImageManager.sharedManager 
cacheKeyForURL:url];
    NSString *imgPath = [SDWebImageManager.sharedManager.imageCache 
defaultCachePathForKey:imgKey];
    NSDictionary *fileAttr = [fm attributesOfItemAtPath:imgPath error:nil];
 
    NSMutableDictionary *mutableHeaders = [headers mutableCopy];
    NSDate *lastModifiedDate = nil;
 
    if (fileAttr.count > 0) {
        if (fileAttr.count > 0) {
            lastModifiedDate = (NSDate *)fileAttr[NSFileModificationDate];
        }
    }
    NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
    formatter.timeZone = [NSTimeZone timeZoneWithAbbreviation:@"GMT"];
    formatter.locale = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US"];
    formatter.dateFormat = @"EEE, dd MMM yyyy HH:mm:ss z";
 
    NSString *lastModifiedStr = [formatter stringFromDate:lastModifiedDate];
    lastModifiedStr = lastModifiedStr.length > 0 ? lastModifiedStr : @"";
    [mutableHeaders setValue:lastModifiedStr forKey:@"If-Modified-Since"];
 
    return mutableHeaders;
};

然后,加载图片的地方以前怎么写还是怎么写,但别忘了Option是SDWebImageRefreshCached

NSURL *imgURL = [NSURL URLWithString:@"http://handy-img-storage.b0.upaiyun.com/3.jpg"];
[[self imageView] sd_setImageWithURL:imgURL
                    placeholderImage:nil
                             options:SDWebImageRefreshCached];

更多参考:https://blog.csdn.net/lizhilin_vip/article/details/53185423

17.MVC、MVVM

传统的MVC
image.png
Cocoa 版本MVC
image.png
MVVM
7A12F6A8-D0F4-40BB-B0BB-AFDAE484911C.png

更多参考:https://www.jianshu.com/p/70f1902c0f54

18.tableview列表中的cell复用有倒计时的情况

19.超大图显示解决方案

占用内存过大的图片用UIImageView直接设置image展示不了,因为内存占用很大,可能导致崩溃问题。

方案一:使用UIScrollView

步骤:
1、通过循环体把大长图裁剪成多个等份,等份划分可以用长图的高度对屏幕高度进行相除,份数为除数+1,这样每一个等份就是当前屏幕大小的一张小图。

2、渲染出每一个等份为一张小图,将其放置在scrollView当前屏幕显示的部分中。

3、需要考虑两件事情,当前正显示在屏幕中的小图的上一张图,需要将其内存释放掉,而对于其下一张图,需要预先获取将其渲染出来,防止滑动不流畅的效果。

缺点:各种计算尺寸及滑动显示处理很麻烦。

方案二:使用UITableView

针对方案一中,需要考虑到当前正显示在屏幕中的小图的上一张图将其内存释放掉,对于其下一张图预先获取将其渲染,这个步骤就是我们的cell的重用机制。

步骤:
1、通过循环体把大长图裁剪成多个等份,小图个数 = 长图高度 / Row行高 + 1
2、分割图片保存到沙盒
3、从沙盒中获取到缓存后的图片进行展示

优点:
1、和重用机制的共同点是都只初始化当前可见部分的对象的内存,而其余的对象始终重用这部分内存,只是绘制的内容改变了,这样就解决了我们长图造成的内存峰值崩溃问题。
2、通过UITableView,系统帮我们自动实现了重用机制,不用我们再去考虑上一张图的内存释放掉,下一张图的预先渲染问题,大大简化了问题的复杂度。
缺点:
1、因为该方案是加载多个小图片段,那么便不能实现类似缩略图那种放大缩小的功能了。
2、存在布局计算、裁剪、缓存等操作,耗时。

方案三:相对较完美WKWebview

跳出IOS编程的惯有思维,从IOS原生到借助H5来实现,复杂的问题丢给它解决,既解决了问题又简化了代码,就像数学题,简便的方法很难想到,容易想到的方法实现起来却很复杂。

其基本思路是将image包装为HTML代码,再使用WKWebView加载HTML代码。

方案四:CATiledLayer

利用CATiledLayer绘制的内存峰值最低,绘制后内存增量最小,也充分利用了CPU的能力。

更多可以参考:https://www.jianshu.com/p/89b2c892e161

20.Swift 关键字weakunowned

weakunowned 类似,不同点是 unowned 是永远有值的。weak可以声明可选型,很多时候我们不想声明一个可选型,可选型代表着风险,此时就可将属性声明成 unowned。因为unowned设置以后即使它原来引用的内容已经被释放了,它仍然会保持对被已经释放了的对象的一个 "无效的" 引用,它不能是 Optional 值,也不会被指向 nil 。如果你尝试调用这个引用的方法或者访问成员属性的话,程序就会崩溃。而 weak 则友好一些,在引用的内容被释放后,标记为 weak 的成员将会自动地变成 nil (因此被标记为 @ weak 的变量一定需要是 Optional 值)。

class Example {
    class Example {
    var num = 10
    lazy var method:(Int) -> Int = {
        [unowned self] (i:Int) in
            return self.num + i
    }
}
class Example {
    var delegate:UITableViewDelegate?
    lazy var method:(Int) -> Void = {
        [weak delegate = self.delegate] (i: Int) in
        //操作delegate
    }
}

21.const、static区别

9999. NSNotification底层实现

9999.delegate底层实现

9999. Block底层实现

. runtime 如何实现 weak 属性(weak底层实现原理)

. KVO的底层实现

9999. NSCache,NSDictionary,NSArray的区别

. 谈谈iOS事件响应链

iOS App签名的原理

怎么防止反编译

哈希原理

9999. TCP和UDP的区别于联系

999.TCP、socket、websocket区别

999.socket消息帧粘包,拆包及处理方法

9999. TCP连接的三次握手

第一次握手:客户端发送 syn 包(syn=j)到服务器,并进入 SYN_SEND 状态,等待服务器确认;

第二次握手:服务器收到 syn 包,必须确认客户的 SYN(ack=j+1),同时自己也发送一个 SYN 包(syn=k),即 SYN+ACK 包,此时服务器进入 SYN_RECV 状态;

第三次握手:客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK(ack=k+1),此包发送完毕,客户端和服务器进入 ESTABLISHED 状态,完成三次握手。

握手过程中传送的包里不包含数据,三次握手完毕后,客户端与服务器才正式开始传送数据。理想状态下,TCP 连接一旦建立,在通信双方中的任何一方主动关闭连接之前,TCP 连接都将被一直保持下去。断开连接时服务器和客户端均可以主动发起断开 TCP 连接的请求,断开过程需要经过“四次握手”(过程就不细写了,就是服务器和客户端交互,最终确定断开)

9999. Scoket连接和HTTP连接的区别

9999. HTTP协议的特点及HTTP请求GET和POST的区别

HTTP协议的特点:

GET提交、请求的数据会附在URL之后,即把数据放置在HTTP协议头<requestline>中。
以分割URL和传输数据,多个参数用&连接。如果数据是英文字母或数字,原样发送,
如果是空格,转换为+,如果是中文/其他字符,则直接把字符串用BASE64加密。

9999. HTTP请求GET和POST的区别:

一、传输数据的大小:

GET提交时,传输数据就会受到URL长度限制,POST由于不是通过URL传值,理论上书不受限。

二、安全性:

9999. HTTP post的body体使用form-urlencoded和multipart/form-data的区别

一、application/x-www-form-urlencoded:

窗体数据被编码为名称/值对,这是标准且默认的编码格式。当action为get时候,客户端把form数据转换成一个字串append到url后面,用?分割。当action为post时候,浏览器把form数据封装到http body中,然后发送到server。

二、multipart/form-data:

multipart表示的意思是单个消息头包含多个消息体的解决方案。multipart媒体类型对发送非文本的各媒体类型是有用的。一般多用于文件上传。
multipart/form-data只是multipart的一种。目前常用的有以下这些类型(注:任何一种执行时无法识别的multipart子类型都被视为子类型"mixed")

9999. 谈一谈网络中的 session 和 cookie

因为 Http 无状态的特性,如果需要判断是哪个用户,这时就需要 Cookie 和 Session。

Cookie 存储在客户端,用来记录用户状态,区分用户。一般都是服务端把生成的 Cookie 通过响应返回给客户端,客户端保存。

Session 存储在网络端,需要依赖 Cookie 机制。服务端生成了 Session 后,返回给客户端,客户端 setCookie:sessionID,所以下次请求的时候,客户端把 Cookie 发送给服务端,服务端解析出 SessionID,在服务端根据 SessionID 判断当前的用户。

一、如何修改 Cookie?
二、如何删除 Cookie?
三、如何保证 Cookie 的安全?
上一篇 下一篇

猜你喜欢

热点阅读