iOS面试题库将来跳槽用面试合集

iOS 经典面试题<记录篇>

2020-12-23  本文已影响0人  9岁就很6
1、APP的启动过程、main函数?

内核初始化空间创建进程-》加载解析执行文件 - 》载入动态链接器(加载依赖库)

程序启动分为两类:

一、有storyboard 情况下:
1.main函数
2.UIApplicationMain
创建UIApplication对象
创建UIApplication的delagate对象
3。根据info.plist获得Main.storyboard文件名,加载Main.storyboard
创建UIWindow
设置window 的 rootViewController
显示窗口
二、没有storyboard 情况下:
1.main函数
2.UIApplicationMain
创建UIApplication对象
创建UIApplication的delagate对象
3。delagate监听处理系统事件调用application:didFinishLaunchingWithOptions:方法
在application:didFinishLaunchingWithOptions:方法创建UIWindow
设置window 的 rootViewController
显示窗口

2、讲一下__block?

1.Block: 用copy 修饰,定义实在栈内存的,需要拷贝到堆内存来由程序员自己管理内存。
2.__block作为修饰被捕获的对象,使对象在block中可以被修改,在执行完业务代码之后手动把对象置为nil来打破循环引用
但是这样做有很大的限制:

  1. block必须执行!!!不执行的话就循环引用了!!!!!
  2. 需要保证被捕获的对象在block执行之后不再使用!!!因为block执行之后对象变为nil了。
3、如何理解iOS组件化路由?

参考文章:https://www.jianshu.com/p/f22051457789

4、如何理解iOS组件化?

当一个业务和产品越来越多,越来越复杂的时候,就要考虑组件化
我们肯定会想到CocoaPods工具,因为它基本都是组件化管理,面向对象就好,并没有互相通信,相互依赖
优点:避免冲突,项目维护,模块间耦合性低容易分离
缺点:引起组建之间版本维护等问题

如何实现(概念):
分离模块与模块之间的耦合关系,避免模块与模块直接直接联系

5、你是怎么理解RunLoop的?
  1. 字面意思就是“跑圈”,翻译的雅一点就是“运行的循环”;
  1. 内部就是do-while循环,在这个循环内部不断地处理各种任务。点击了屏幕(TouchEvent)、UI界面的刷新事件、定时器事件(Timer)、Selector、Observer等等
  1. 当我们将我们的程序退出到后台,注意只是退出到后台,并没有terminate,这时RunLoop不会处理任何事件,此时为了节省CPU的资源,RunLoop会自动进入休眠模式。

4.一个线程对应唯一一个RunLoop对象,这点很重要。但是往往,RunLoop并不能保证我们线程安全;

  1. 主线程中默认开启一个RunLoop,在main函数,只要程序不关闭,他就会一直运行循环。退出后台才会被休眠或者销毁;子线程的Runloop需要手动启动(调用run 方法)
    WeChat654c9c0e3ac10c7c0084018f5b1aa1ef.png

runloop的mode是什么?作用是什么?

1). 用来控制一些特殊操作只能在指定模式下运行,一般可以通过指定操作的运行。
mode 来控制执行时机,以提高用户体验
2). 系统默认注册了 5 个 Mode,其作用
1. kCFRunLoopDefaultMode:App 的默认 Mode,通常主线程是在这个 Mode下运行,对应 OC 中的:NSDefaultRunLoopMode
2. UITrackingRunLoopMode:界面跟踪 Mode,用于 ScrollView 追踪触摸滑动,保证界面滑动时不受其他 Mode 影响
3. kCFRunLoopCommonModes:这是一个标记 Mode,不是一种真正的 Mode,事件可 以 运 行 在 所 有 标 有 common modes 标 记 的 模 式 中 , 对 应 OC 中 的NSRunLoopCommonModes , 带 有 common modes 标 记 的 模 式 有 :UITrackingRunLoopMode 和 kCFRunLoopDefaultMode
4. UIInitializationRunLoopMode:在启动 App 时进入的第一个 Mode,启动完成后就不再使用
5. GSEventReceiveRunLoopMode:接受系统事件的内部 Mode,通常用不到

6. 说说 多线程 ?
优点:多线程同时(并发执行),提高程序的执行效率,提高资源利用率,包括CPU、内存等;
缺点:占用一定的内存,线程过多反而会降低性能,程序设计更加复杂,比如线程之间的通信或者数据共享。

在iOS中有3种多线程方案,分别为:NSThread、NSOperation、GCD

•   一个进程可有多个线程。
•   一个线程可有多个队列。
•   队列可分并发和串行队列。
NSThread

1.轻量级最低,相对简单,可直接操作线程对象;
2.需要手动管理线程活动(生命周期、睡眠、同步等);

NSOperation

1.自带线程管理的抽象类,操作上可更注重自己逻辑;
2.面向对象的抽象类,只能实现它或者使用它定义好的两个子类:NSInvocationOperationNSBlockOperation

GCD :Grand Central Dispatch是由苹果开发的一个多核编程的解决方案。iOS4.0+才能使用。

优点
1.最高效,避开并发陷阱。
2.官方推荐,更为简单。
3.性能更好 。
4。不需要编写任何线程管理代码。
缺点:基于C语言实

参考文章:https://www.jianshu.com/p/9a470a6dfcc9
7. 理解flutter

Flutter是由谷歌开发的一个使用 一套编程语言 实现 跨平台(iOS,Android)原生开发的“工具”。
目前还不是很成熟,很多公司都再试水,或者直接再原生代码嵌入Flutter混合开发。

参考官网:https://flutter.dev/
参考mac、Windows、等系统安装教程:https://www.jianshu.com/p/4ed03ee3516f

8. 理解swiftUI

写更少的代码,打造更出色的 app。
在 UI 开发领域,描述性语言是最佳方式

SwiftUI 是一种创新、简洁的编程方式,通过 Swift 的强大功能,在所有 Apple 平台上构建用户界面。借助它,您只需一套工具和 API,即可创建面向任何 Apple 设备的用户界面。SwiftUI 采用简单易懂、编写方式自然的声明式 Swift 语法,可无缝支持新的 Xcode 设计工具,让您的代码与设计保持高度同步。SwiftUI 原生支持“动态字体”、“深色模式”、本地化和辅助功能——第一行您写出的 SwiftUI 代码,就已经是您编写过的、功能最强大的 UI 代码。

9.给一个UIView获取所属的UIViewController?

通过响应链查找视图控制器,nextResponder获取下一个响应者

OC 版本

- (UIViewController *)getControllerFromView:(UIView *)view {
    // 遍历响应者链。返回第一个找到视图控制器
    UIResponder *responder = view;
    while ((responder = [responder nextResponder])){
        if ([responder isKindOfClass: [UIViewController class]]){
            return (UIViewController *)responder;
        }
    }
    // 如果没有找到则返回nil
    return nil;

或者

id responder = self.nextResponder;
while (![responder isKindOfClass: [UIViewController class]] && ![responder isKindOfClass: [UIWindow class]])
     {
        responder = [responder nextResponder];
    }
    if ([responder isKindOfClass: [UIViewController class]])
    {
        // responder就是view所在的控制器
        // do something
    }

Swift 版本

func viewController(aClass: AnyClass) -> UIViewController?{
       for(var next=self.superview;(next != nil);next=next?.superview){
           let nextResponder = next?.nextResponder()
           if((nextResponder?.isKindOfClass(aClass)) != nil){
               return nextResponder as? UIViewController
           }
       }
       return nil
   }

10、Get与Post的区别?

可以从提交数据模式、数据限制、安全性来细说。

提交的数据

GET提交的数据会放在URL之后,以?分割URL和传输数据,参数之间以&相连, 操作简单, 不过容易让外界看到, 安全性不高。

POST 请求, 将参数放到 body 里面, POST请求的操作相对复杂, 需要将参数和地址分开, 不过安全性高,参数放在body里面, 不容易被捕获抓包。

传输数据限制

GET提交的数据大小有限制(因为浏览器对URL的长度有限制)

POST方法提交的数据没有限制。

一般用GET来进行获取数据,因为有缓存,所以可以降低服务器的压力;

POST一般用来进行操作数据,如增删改。

GET方式需要使用Request.QueryString来取得变量的值,而POST方式通过Request.Form来获取变量的值。

安全性

POST的安全性要比GET的安全性高。比如:通过GET提交数据,用户名和密码将明文出现在URL上。

GET&&Post
11、swift 常用的第三方有哪些?
参考文章:https://www.jianshu.com/p/2638f79be33f
12、说说IM开发的过程,遇到的问题,以及你所了解的im知识?
12-1.场景

im社交软件的开发,如果是一般需求,项目里面只是陪衬(客服之类)的情况下,可以借助第三方IMSDK(环信、融云之类)接口来配合使用,但是如果业务需求比较大的社交软件,一般会是自己公司里面团队进行开发IM系统。

12-2.客户端连接过程
1.客户端调用 socket(...) 创建socket;
2.绑定IP地址、端口等信息到socket上,用函数bind()
3.客户端调用 connect(...) 向服务器发起连接请求以建立连接;
4.客户端与服务器建立连接之后,就可以通过send(...)/receive(...)向客户端发送或从客户端接收数据;
5.客户端调用 close 关闭 socket;
static NSString *Khost = @"127.0.0.1";
static uint16_t  Kport = 6969;
static NSInteger KPingPongOutTime  = 3;
static NSInteger KPingPongInterval = 5;
12-3.说明
CocoaAsyncSocket
Protobuf 的 优势:1)跨语言,跨平台 2)性能优越 3)兼容性好

使用基于Socket原生CocoaAsyncSocket配合Protobuf开发IM系统,Protobuf 是由 Google 开发的一种语言无关,平台无关,可扩展的序列化结构数据的方法,可用于通信和数据存储,可以理解为比Json还好的数据加密方式。

12-4. 建立长链接逻辑

socket与后台连接时需要完成:3次握手消息,成功握手后开启心跳包,届时就可以和后台相互收发。收发的内容受长度限制。取决于后台配置,也可能与移动端有关。长链接的scoket 理论上不会断开,因此还会涉及到粘包拆包解包的问题,目前接收到的那些通过Protobuf序列化后的数据需要通过base128编码的解码方式才可以解析,头部占用长度1-4个字节

12-5. 举例:竞技场-解包处理
enum{
   socketBouldIdByxt = 0,      //心跳包
   socketBouldIdByaq = 1,        //安全认证
   socketBouldIdByStop = 2,        //停止链接
   socketBouldIdBylist = 100,     //竞技场列表
   socketBouldIdByOutJJC = 1001,     //退出竞技场
   socketBouldIdByInJJC = 1002,     //加入竞技场
   socketBouldIdByInfoChange = 1003,     //用户状态变更
};
-(void) didReadData:(NSData *)data {
    //将接收到的数据保存到缓存数据中
    [self.cacheData appendData:data];;

    _receData = data.length + _receData;
    if (self.cacheData.length < 12) {//不完整的包
        [self.socket readDataWithTimeout:READ_TIME_OUT buffer:nil bufferOffset:0 maxLength:MAX_BUFFER tag:0];
        return;
    }
    //总的包都长度
    int totalLenght = [BytesUtils getIntFromData:self.cacheData withOffset:8] + 12;
    if (_cacheData.length >= totalLenght) {
        //截取完整数据包
        NSData *dataOne = [_cacheData subdataWithRange:NSMakeRange(0, totalLenght)];
         //接收到的每个包的头尾长度都是不固定的,是需要开发的时候协商的。data 取内容的时候,是需要去掉头尾的字节。再去转
        NSString *string = [BytesUtils getAppStrDatafromData:dataOne];
        int bould = [BytesUtils getId:dataOne];
        if (bould == socketBouldIdByaq) {
//            NSLog(@"安全验证通过");
        }else if (bould == socketBouldIdByxt){
//            NSLog(@"心跳验证通过");
        }else if (bould == socketBouldIdByStop){
            //            NSLog(@"心跳验证通过");
            self.socket.userData = SocketOfflineByStopConnect;
            [self.socket disconnect];
        }else{
            if (bould == socketBouldIdBylist) {//竞技场列表
                NSArray *arr = [ATGeneralFuncUtil JsonToDict:string];
                if ([self.delegate respondsToSelector:@selector(jjcListArr:)]) {
                    [self.delegate jjcListArr:arr];
                }
            }else if (bould == socketBouldIdByOutJJC || bould == socketBouldIdByInJJC) {//退出或者加入竞技场
                NSDictionary *dict = [ATGeneralFuncUtil JsonToDict:string];
                if ([self.delegate respondsToSelector:@selector(listDidChange:isIn:)]) {
                    [self.delegate listDidChange:dict isIn:bould == socketBouldIdByOutJJC?false:true];
                }
            }else if (bould == socketBouldIdByInfoChange) {//竞技场用户状态发生变更
                NSDictionary *dict = [ATGeneralFuncUtil JsonToDict:string];
                if ([self.delegate respondsToSelector:@selector(listInfoDidChange:)]) {
                    [self.delegate listInfoDidChange:dict];
                }
            }
        }
        self.count ++ ;
        [_cacheData replaceBytesInRange:NSMakeRange(0, totalLenght) withBytes:nil length:0];
        [self didReadData:nil];
    }else{
         [self.socket readDataWithTimeout:READ_TIME_OUT buffer:nil bufferOffset:0 maxLength:MAX_BUFFER tag:0];
    }
}

写在最后:实际上每个消息发过来的时候都有一定的大小 ,正常情况就是收到消息进行解包 , 就是一个消息体能够正常接受,但是再收到消息巨大的情况下 , 下个包的部分消息会拼到前一个包上,这是导致丢包的一个问题,所以要对收到的消息进行拼接再解包,这时候就是粘包 ,其他还要了解 心跳机制、PingPong机制、断线重连、离线消息等技术点。

IM的其他实现方式
(1)基于WebSocket最具代表性的一个第三方框架-SocketRocket

实现的思路和基于CocoaAsyncSocket框架类似,需要编写遵守webSocket协议的服务端,感兴趣的也可以参照实现一下。

(2)基于MQTT协议的框架-MQTTKit

MQTT是一个聊天协议,它比webSocket更上层,属于应用层,它的基本模式是简单的发布订阅,也就是说当一条消息发出去的时候,谁订阅了谁就会收到消息。其实它并不适合IM的场景,例如用来实现有些简单IM场景,却需要很大量的、复杂的处理。这个框架是c来写的,把一些方法公开在MQTTKit类中,对外用OC来调用,这个库有4年没有更新了。

(3)基于XMPP协议的框架-XMPPFramework

XMPP是较早的聊天协议(2000年发布第一个公开版本),当时主要是用来打通 ICQ、MSN 等 PC 端的聊天软件而设计的,技术比较成熟,它本身有很多优点,如开放、标准、可扩展,并且客户端和服务器端都有很多开源的实现,但是相对于移动端它也有很明显的缺点,譬如数据负载过重、不支持二进制,在交互中有50% 以上的流量是协议本身消耗的,需要做深度的二次开发。

Protobuf参考文章:https://www.jianshu.com/p/bb1c8adc62c7
CocoaAsyncSocket参考文章:https://www.jianshu.com/p/8800328f60bf
13、开发过程中遇到什么难题,如何解决的?

不回答一些比较基础的难题,比如控件不好写,或者数组越界之类的话题。
可以说说比较高大上的 内存、数据储存、缓存、图片处理、视频处理。

比如:我在开发我们项目的时候, 涉及到图像处理的问题,就比如说现在网络上比较火的用SDWebImage下载超清大图的时候出现的崩溃问题,因为decodeImageWithImage 这个方法用于对图片进行压缩并且缓存起来,以保证tableview/collectionview交互更加流畅,但是如果用此方法加载超清大图的时候, 会适得其反,有可能导致上G的内存消耗, 解决办法是对于高清图片,应该放在图片解压之后,禁止缓存解压后的数据。 代码如下”

- SD4.X的解决办法
[[SDImageCache sharedImageCache] setShouldDecompressImages:NO];
[[SDWebImageDownloader sharedDownloader] setShouldDecompressImages:NO];

- SD5.0 及以上的解决办法
SDWebImageAvoidDecodeImage添加了这个枚举,意思是在子线程程解压缩图片
[self.imageView sd_setImageWithURL:self.url placeholderImage:[UIImage imageNamed:@"logo"] options:SDWebImageAvoidDecodeImage];

再比如:我在开发大数据列表的时候,公司产品要求进入界面展示缓存数据,由于数据模式以及展示UI类型比较多,数据处理起来比较麻烦,于是想到使用的一个第三方FMDB封装工具来实现,实现过程中,出现有些数据未保存。造成UI显示混乱。
解决方案:输出检查缓存的数据是否正常,发现数组。字典等类型无法转换,考虑到这个需求是后期提出的,无法马上更换FMDB数据库的工具,只能在原基础上把数组的数据转换成一个字符串,用某一个符号,比如“&”不重复的符号来拼接储存数据,在进入界面的时候再把字符串转换成数组,即可。

再比如:当一个动态界面发布多种数据时候,比如1个视频或者多个视频+9张图片这种,由于公司需求需要一个点发布,自己马上就能看到但是不能操作的一条动态,在测试阶段,6以下的低内存机型会出现崩溃的问题。
解决方案:由于图片高清导致内存暴涨,部分机型会崩溃, 在点发布的时候,要对图片进行一个压缩处理后展示,这样可以避免一个崩溃问题,然后在提供给后台一个预发布的功能,图片异步上传,先传文本等字符串数据,传递后台预发布接口后返回一个id,在等图片上传完成后,请求发布成功接口,传入id,这样就可以做到先展示图片+视频数据,又能同时在默默的上传数据了。

14、RAC框架 你了解多少?

参考文章:https://www.jianshu.com/p/1837209e68d5

15、 讲一下 iOS 如何管理内存的 ?

可以稍微说下mrc+arc:

1.MRC 手动内存管理
2.ARC 自动引用计数,arc的判断准则,只要没有强指针指向对象,对象就会被释放。

然后最好回答工作中是如何处理内存管理的。

1.block的内存管理:
由于使用block很容易造成循环引用,因此一定要小心内存管理问题。最好在基类controller下重写以下dealloc,加一句打印日志,表示类已经得到释放。如果出现无法打印信息,说明这个类一直得不到释放,表明很有可能是使用block的地方出现循环引用了。对于block中需要引用外部controller的属性或者成员变量的时候,一定要使用弱引用,特别是成员变量像_textId这样的,导致内存得不到释放。
2.对于普通项目来说,因为现在都是ARC项目,记住ARC下的黄金内存管理原则:
只要还有人使用这个对象,那么这个对象就不会被回收
只要你想使用这个对象,那么就应该让这个对象的引用计数+1
当你不像使用这个对象的时,应该让对象引用计数-1
谁创建,就由谁来release
如果你通过alloc、new 、copy来创建一个对象时,当你不想用这个对象的时候就必须调用release 或者 autorelease 让引用计数-1
不是你创建的就不用负责 release

总结:有加就有减,曾让某个计数+1.就应该让其最后-1

拓展

内存管理研究的对象
  1. 野指针:指针变量没有进行初始化或者指向的空间已经被释放了。
    使用野指针调用调用对象方法,会报异常 ,程序崩溃。
    通常再调用完release方法后,把保存对象指针的地址清空,赋值为nil,所以【nil retain】 不会有异常。
  2. 内存泄漏。
    3.僵尸对象:堆中已经被释放的对象(retainCount = 0)
  3. 空指针:指针赋值为空,nil
16、 开发项目时你是怎么检查内存泄漏?
  1. 静态分析 analyze
  2. instruments工具里面有一个leak 可以动态分析
    如果再block中多次使用weakSelf的话,可以再block中先使用strongSelf,防止block执行时weakSelf被意外释放。
    ARC下,将weak改用为block 即可。
17、 常见的出现内存循环引用场景有哪些?

1.定时器(NSTimer):初始化要指定self为target,容易造成循环引用(self->timer->self);
2.block:成员变量再block内部使用,self.变量。互相引用;
3.delegate:使用assign(MRC),或者weak(ARC)

18、 KVC底层实现?

KVC:键值编码 使用字符串直接访问对象属性。

  • 当一个对象调用setValue方法时,方法内部会做以下操作:
    1.检查是否存在相应的keyset方法,如果存在,就调用set方法;
    2.如果set方法不存在,就会查找与key相同名称并且带下划线的成员属性,如果有,则直接给成员变量赋值。
    3.如果没有找到_key,就会查找相同名称的属性Key,如果有直接赋值。
    4.如果还没有找到,则调用valueForUndefinedKey:和setValue:forUndefinedKey:方法。
    这些方法的默认实现都是抛出异常,我们可以根据需要重写他们。
19、 KVO底层实现?

KVO :键值观察机制,它提供了观察某一属性的变化方法。

  • kvo 基于runtime 机制实现。当一个对象的属性值发生改变时,系统会自动生成一个类,继承NSKVONotifying_MYPerson
    isa=(Class)NSKVONotifying_MYPerson.
    最后 MYPerson 改变成NSKVONotifying_MYPerson,说的不清楚,建议代码输出断点测试下。
    使用完记得要移除观察者。
20、 谈谈你是怎么封装View的?

1.先添加子控件
2,接收模型数据根据模型数据来控制控件的位置
3.自己的事情自己做,把不需要暴露的都封装起来。

21、 什么情况下会死锁?如何避免死锁?

死锁:两个或两个以上的线程,互相等待彼此停止以获得某种资源,但是没有一方会提前退出的情况。

1、“同步”且在“当前队列【主线程】”中执行任务,必定死锁
2、“同步”且在“同一个串行队列”中执行任务,必定死锁
避免死锁
1.避免在串行队列中执行同步任务。
2.避免Operation相互依赖。

“同步”且在“当前队列【主线程】” “同步”且在“同一个串行队列”
23、了解哪些新技术点和方向?

1、iOS 14 轻应用(App Clips)开发调试: https://www.jianshu.com/p/21af85107a34

24、UITableView 卡顿的情况如何优化?

1). 正确的复用cell。
2). 设计统一规格的Cell
3). 提前计算并缓存好高度(布局),因为heightForRowAtIndexPath:是调用最频繁的方法;
4). 异步绘制,遇到复杂界面,遇到性能瓶颈时,可能就是突破口;
4). 滑动时按需加载,这个在大量图片展示,网络加载的时候很管用!
5). 减少子视图的层级关系
6). 尽量使所有的视图不透明化以及做切圆操作。
7). 不要动态的add 或者 remove 子控件。最好在初始化时就添加完,然后通过hidden来控制是否显示。
8). 使用调试工具分析问题。
9). cell的展示数据最好在展示之前处理,避免过多的逻辑处理
10).使用正确的数据结构。
11).减少动画效果。

25、 ios 内存几大区域?

1). 栈区(stack): 栈区主要存放局部变量和函数参数等相关变量,超出作用域之后自动
释放。由编译器自动分配和释放。
2). 堆区(heap): 堆区存放alloc,new等关键字生成的对象。由程序员分配和释放,若
不释放,则程序结束由操作系统回收。
3). 全局静态区(global): 全局静态区主要存放静态数据,全局数据和常量,程序运行之后一直存在。由编译器管理(分配和释放)。
4). 文字常量区: 由编译器管理(分配释放),程序结束由系统释放;存放常量字
符。
5). 程序代码区: 存放函数的二进制代码。
注:我们平时关注最多的部分都是堆区的内存。

26、 数据存储的优劣?

1). NSUserDefaults
优点:轻量级,易操作,保密性好
缺点:
1.不支持自定义对象的存储。
2.存储的数据都是不可变的,想将可变数据存入需要先转为不可变才可以存储。
3.定时把缓存中的数据写入磁盘的,而不是即时写入。
场景:用于存储用户的偏好设置和用户信息,如用户名,是否自动登录,字体大小等。
2). plist(属性列表文件)

  优点:轻量级,易上手
  缺点:
       1.不支持自定义对象的存储。
       2.对数据的操作比较繁琐。需要全部读写。
  场景:用于存储程序中经常用到且数据量小而不经常改动的数据。

3). 归档(属性列表文件)
优点:可以创建自己想要的数据模型,然后统一以模型方式存储
缺点:
1.速度相对较慢
2.保存和读取都需要一次性读取和写入。不支持检索。
3.需要对对象进行归档,结档操作,相对麻烦。
场景:需要对自定义模型进行保存。比如通讯录分类排序。保存对象
4). FMDB 优点:支持大批量的数据存储,支持检索,操作方便
缺点:
1.原生的支持类型不多。
2.需要有sql基础。
场景:即时聊天,社交。
5). CoreData
优点:支持对象存储。无需任何sql语句。支持可视化管理
缺点:
1.操作繁琐
场景:即时聊天,社交。

27、静态库和动态库的区别?

1). 静态库和动态库的区别
1. 系统创建的库为动态库,即在一次加载中,针对手机上的所有应用,该库都是通用的。
2. 开发者创建的库为静态库。该库的作用域仅在该app范围内。多次使用则多次加载。
2)..framework.a的区别
1. 两者都是静态库。
2. .a是一个纯二进制文件,而.framework里面除了二进制文件还包括资源文件
3. .a文件不能直接使用,需要.h文件配合使用,而.framework可独立使用。
4. .a无需暴露内部文件,而.framework 需要至少暴露一个头文件。

27、RunTime

1). runtime简介
1. runtime简称运行时,OC就是运行时机制,也就是在运行时候的一些机制,其中最主要的是消息机制。
2. 对于OC的函数,属于动态调用过程,在编译的时候不能决定真正调用哪个函数,只有在真正运行的时候才会根据函数的名称找到对应的函数来调用。
3. 在编译阶段:OC可以调用任何函数,即使这个函数并未实现,只要声明过就不会报错。而C语言调用未实现的函数就会报错。
2). runtime的五个功能
1. 发送消息
2. 交换方法
3. 动态添加方法
4. 给分类添加属性
5. 字典转模型
3). runtime的应用场景
1. 关联对象(Objective-C Associated Objects)给分类增加属性
2. 方法魔法(Method Swizzling)方法添加和替换和KVO实现
3. 消息转发(热更新)解决Bug(JSPatch)
4. 实现NSCoding的自动归档和自动解档
5. 实现字典和模型的自动转换(MJExtension)
4). 运行时阶段的消息发送的详细步骤
1. 检测selector 是不是需要忽略的。比如 Mac OS X 开发,有了垃圾回收就不理会retain,release 这些函数了。
2. 检测target 是不是nil 对象。ObjC 的特性是允许对一个 nil对象执行任何一个方法不会 Crash,因为会被忽略掉。
3. 如果上面两个都过了,那就开始查找这个类的 IMP,先从 cache 里面找,若可以找得到就跳到对应的函数去执行。
4. 如果在cache里找不到就找一下方法列表methodLists。
5. 如果methodLists找不到,就到超类的方法列表里寻找,一直找,直到找到NSObject类为止。
6. 如果还找不到,Runtime就提供了如下三种方法来处理:动态方法解析、消息接受者重定向、消息重定向。三者关系见消息转发流程图

image.png
28、谈谈你对MVC的理解

1). 数据管理者(M)、数据展示者(V)、数据加工者(C)
2). M 应该做的事:
1. 给 ViewController 提供数据
2. 给 ViewController 存储数据提供接口
3. 提供经过抽象的业务基本组件,供 Controller 调度
3). C 应该做的事:
1. 管理 View Container 的生命周期
2. 负责生成所有的 View 实例,并放入 View Container
3. 监听来自 View 与业务有关的事件,通过与 Model 的合作,来完成对应事件的业务。
2). V 应该做的事:
1. 响应与业务无关的事件,并因此引发动画效果,点击反馈(如果合适的话,尽量还是放在 View 去做)等。
2. 界面元素表达。

29、TCP 和 UDP 有什么区别?

1). TCP 是面向连接的,建立连接需要经历三次握手,保证数据正确性和数据顺序(上面IM面试题里面有细说
2). UDP 是非连接的协议,传送数据受生成速度,传输带宽等限制,可能造成丢包
3). UDP 一台服务端可以同时向多个客户端传输信息
4). TCP 报头体积更大,对系统资源要求更多

TCP 和 UDP
30、 常用的设计模式有哪些?

1). 单例模式
2). 观察者模式
3). 代理模式
4). 享元模式
3). 工厂方法模式
3). 抽象工厂模式

31、 沙盒目录结构是怎样的?各自用于那些场景?

1). Application:存放程序源文件,上架前经过数字签名,上架后不可修改
2).Documents:常用目录,iCloud 备份目录,存放数据
3).Library :
3- 1.Caches:存放体积大又不需要备份的数据
3-2. Preference:设置目录,iCloud 会备份设置信息
4). tmp:存放临时文件,不会被备份,而且这个文件下的数据有可能随时被清除的可能

32、 类方法和实例方法的区别?

1). 类方法
1. 类方法是属于类对象的
2. 类方法只能通过类对象调用
3. 类方法中的 self 是类对象
4. 类方法可以调用其他的类方法
5. 类方法中不能访问成员变量
6. 类方法中不能直接调用对象方法
7. 类方法是存储在元类对象的方法缓存中
2). 实例方法
1. 实例方法是属于实例对象的
2. 实例方法只能通过实例对象调用
3. 实例方法中的 self 是实例对象
4. 实例方法中可以访问成员变量
5. 实例方法中直接调用实例方法
6. 实例方法中可以调用类方法(通过类名)
7. 实例方法是存放在类对象的方法缓存中

33、 区分nil 、Nil、NULL、NSNUll?

1).nil: 一般是指把一个对象置空,既完全是一个空对象,完全从内存中释放。
2).Nil: 和nil基本没有任何区别,也可以说只要是可以使用nil的地方都可以使用Nil,
反之亦然。但是作为程序猿,我们应该更加严谨一些。nil和Nil的区别在于,
nil表示置空一个对象,而Nil表示置空一个类。
3). NULL: 大家都知道oc 是基于c的,并且oc是完全兼容c的,NULL源于c,表示一个空指针. 即:int *p = NULL

4). NSNull: 很有意思,大家一般都会觉得,NSNull也是空,但是看着这货又是“NS”开
头的很像一个对象,实质上NSNull的确是一个对象,它继承于NSObject。那它
和nil的区别在哪里呢?nil是把一对象完全释放,就是完全从内存中释放。但是
当我想把一个对象置空但是又想要一个容器的时候,我们就可以使用NSNull。
比如一瓶矿泉水,我们不想要里面的水,但是我们想保留瓶子一样。

34、 区分深拷贝与浅拷贝?

1). 浅拷贝指针拷贝,不增加新的内存。只是新增加一个指针指向原来的内存区域
2). 深拷贝内容拷贝,同时拷贝指针和指针指向的内存。新增指针指向新增的内存
3). 拷贝条件:
iOS中并非所有的对象都支持copy和mutableCopy,只有遵循了NSCopy协议或者
NSMutableCoy协议的类才行。如果遵循着两个协议就必须分别实现
copyWithZone和mutableCopyZone方法
4). 拷贝原则:
1. 非容器类:像NSString、NSNumber这样的不能包含其他对象的系统类
不可变对象调用copy是浅拷贝;而调用muablecopy是深拷贝并得到可变对象
可变对象调用copy和mutablecopy都是深拷贝, 区别在于copy返回不可变对
象,mutablecopy返回可变对象
2. 容器类:像NSArray、NSMutableArray等系统类
不可变对象调用copy是浅拷贝,而调用muablecopy是深拷贝并得到可变对象。
可变对象调用copy和mutablecopy都是深拷贝,区别在于copy返回不可变对象,
mutablecopy返回可变对象
3. 容器类与非容器类的拷贝原则相似,但需要注意的是:所有的容器类的拷贝,
拷贝后新容器里的元素始终是浅拷贝,其指针都指向原来对象。

34、 Category(类别)、 Extension(扩展)和继承的区别?

1). 分类有名字,类扩展没有分类名字,是一种特殊的分类。
2). 分类只能扩展方法(属性仅仅是声明,并没真正实现),类扩展可以扩展属性、成员变量和方法
3). 继承可以增加,修改或者删除方法,并且可以增加属性

35、ViewController生命周期
1). initWithCoder:通过nib文件初始化时触发。
2). awakeFromNib:nib文件被加载的时候,会发生一个awakeFromNib的消息到nib文件中的每个对象。
3). loadView:开始加载视图控制器自带的view。
4). viewDidLoad:视图控制器的view被加载完成。  
5). viewWillAppear:视图控制器的view将要显示在window上。
6). updateViewConstraints:视图控制器的view开始更新AutoLayout约束。
7). viewWillLayoutSubviews:视图控制器的view将要更新内容视图的位置。
8). viewDidLayoutSubviews:视图控制器的view已经更新视图的位置。
9). viewDidAppear:视图控制器的view已经展示到window上。 
10). viewWillDisappear:视图控制器的view将要从window上消失。
11). viewDidDisappear:视图控制器的view已经从window上消失。
36、如何保证线程安全?

1). 原子操作(Atomic Operation):是指不会被线程调度机制打断的操作;这种操作
一旦开始,就一直运行到结束,中间不会有任何的上下文切换(context switch)。
2). 内存屏障(Memory Barrier):确保内存操作以正确的顺序发生,不阻塞线程(锁
的底层都会使用内存屏障,会减少编译器执行优化的次数,谨慎使用)。
3).
1. 互斥锁(pthread_mutex):原理与信号量类似,但其并非使用忙等,而是阻塞
线程和休眠,需切换上下文。
2. 递归锁(NSRecursiveLock):本质是封装了互斥锁的
PTHREAD_MUTEX_RECURSIVE类型的锁,允许同一个线程在未释放其拥有的锁时反
复对该锁进行加锁操作。
3. 自旋锁(OSSpinLock):通过全局变量,来判断当前锁是否可用,不可用就忙
等。
4. @synchronized(self):系统提供的面向OC的API,通过把对象hash当做锁来
用。
5. NSLock:本质是封装了互斥锁的PTHREAD_MUTEX_ERRORCHECK类型的锁,
它会损失一定性能换来错误提示,因为面向对象的设计,其性能稍慢。
6. 条件变量(NSConditionLock):底层通过(condition variable)pthread_cond_t
来实现的,类似信号量具有阻塞线程与信号机制,当某个等待的数据就绪后唤醒线
程,比如常见的生产者-消费者模式。
7. 信号量(dispatch_semaphore)

37、 AFNetworking 断点续传原理是什么?

1.检查服务器文件信息
2.检查本地文件信息
3.如果比服务器文件小,开启断点续传,利用HTTP请求头的Range 来实现
4.如果比服务器文件大,重新下载;
4.如果和服务器文件一样大小,下载完成;

38、 iOS自带框架库有哪些?

分类:
音视频:CoreAudio、OpenAL、MediaLibrary、AVFoundation
数据管理: CoreData 、SQLite
图片和动画: CoreAnmiation、OpenGLES 、Quartz2D
网络:WebKit、Bonjour、BSDScokects
用户应用: AddressBook 、Cor Loaction 、MapKit 、 StoreKit、

39、im丢包?具体怎么拼接?用什么协议传输?

丢包实际场景中更多应该都是网络环境导致的。只能说怎么去验证包的完整性,用于判断包是否完整是否存在丢包。

tcp

40、你是如何内存优化的?性能?
41、你是怎么使用多线程,哪些场景?
42、如何使用一个多线程来请求多个接口,最后刷新UI?

异步并行队列,通过等待信号线程等待所有线程全部执行完毕再刷新

43、说一下runtime的交换方法,具体怎么实现?添加属性又要怎么做?

交换方法:https://www.jianshu.com/p/5f85a676326f,简单说,可以在系统启动后自动运行的load方法里面通过replace方法交换,交换后重写方法的实现。

添加属性:https://www.jianshu.com/p/3131b98b0420,简单说,一般会问分类里面能不能添加属性,正常你添加后没办法使用,但是我们可以使用runtime动态添加属性,在点m里面重写它的setter&&getter方法,例如下代码:

#import <objc/runtime.h>

@implementation People (P)

- (NSString *)address
{
    return objc_getAssociatedObject(self, _cmd);
}

- (void)setAddress:(NSString *)address
{
    objc_setAssociatedObject(self,
                             @selector(address),
                             address,
                             OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
44、 NSTimer定时器使用runloop,为何要使用?怎么管理内存泄漏?

当使用NSTimerscheduledTimerWithTimeInterval方法时。事实上此时Timer会被加入到当前线程的Run Loop中,且模式是默认的NSDefaultRunLoopMode。而如果当前线程就是主线程,也就是UI线程时,某些UI事件,比如UIScrollView的拖动操作,会将Run Loop切换成NSEventTrackingRunLoopMode模式,在这个过程中,默认的NSDefaultRunLoopMode模式中注册的事件是不会被执行的。也就是说,此时使用scheduledTimerWithTimeInterval添加到Run Loop中的Timer就不会执行。

所以为了设置一个不被UI干扰的Timer,我们需要手动创建一个Timer,然后使用NSRunLoop的addTimer:forMode:方法来把Timer按照指定模式加入到Run Loop中。这里使用的模式是:NSRunLoopCommonModes,这个模式等效于NSDefaultRunLoopMode和NSEventTrackingRunLoopMode的结合

无论是单次执行的NSTimer还是重复执行的NSTimer都不是准时的,这与当前NSTimer所处的线程有很大的关系,如果NSTimer当前所处的线程正在进行大数据处理(假设为一个大循环),NSTimer本次执行会等到这个大数据处理完毕之后才会继续执行

这期间有可能会错过很多次NSTimer的循环周期,但是NSTimer并不会将前面错过的执行次数在后面都执行一遍,而是继续执行后面的循环,也就是在一个循环周期内只会执行一次循环。

无论循环延迟的多离谱,循环间隔都不会发生变化,在进行完大数据处理之后,有可能会立即执行一次NSTimer循环,但是后面的循环间隔始终和第一次添加循环时的间隔相同。这个事件是怎么执行的?并且为什么有的时候会延迟?为什么子线程中创建的Timer并不执行?

首先,在进入循环开始以后,就要处理source0事件,处理后检测一下source1端口是否有消息,如果一个Timer的时间间隔刚好到了则此处有可能会得到一个消息,则runLoop直接跳转至端口激活处从而去处理Timer事件。

第二,为什么会延迟?我们知道,两次端口事件是在两个runLoop循环中分别执行的。比如Timer的时间间隔为1秒,在第一次Timer回调结束后,在很短时间内立即进入runLoop的下一次循环,这次并不是Timer回调并且是一个计算量非常大的任务,计算时间超过了1秒,那么runLoop的第二个循环就要执行很久,无法进入下一个循环等待有可能即将到来的Timer第二次回调的信号,所以Timer第二次回调就会推迟了。

第三,为什么在子线程中创建的Timer并且提交到当前runLoop中并不会运行?这还是要从runLoop的获取函数中看,当调用currentRunLoop的时候会取当前线程对应的runLoop,而首次是取不到的,则会创建一个新的runLoop。但是!这个runLoop并没有run。就是没有开启

- (void)applicationDidBecomeActive:(UIApplication*)application{

// NSThread 创建一个子线程   

 [NSThreaddetachNewThreadSelector:@selector(testTimerSheduleToRunloop1) toTarget:selfwithObject:nil];

}

// 测试把timer加到不运行的runloop上的情况

- (void)testTimerSheduleToRunloop1

{

        NSLog(@"Test timer shedult to a non-running runloop");

         SvTestObject *testObject4 = [[SvTestObject alloc] init];

        NSTimer*timer = [[NSTimeralloc] initWithFireDate:[NSDatedateWithTimeIntervalSinceNow:1] interval:1target:testObject4 selector:@selector(timerAction:) userInfo:nilrepeats:NO];

         [[NSRunLoop    currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];

        // 打开下面一行输出runloop的内容就可以看出,timer却是已经被添加进去//

        NSLog(@"the thread's runloop: %@", [NSRunLoop currentRunLoop]);

        // 下面一行, 该线程的runloop就会运行起来,timer才会起作用

        [[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:3]];

        NSLog(@"invoke release to testObject4");

}

- (void)applicationWillResignActive:(UIApplication*)application

{

    NSLog(@"SvTimerSample Will resign Avtive!");

}

45、.你平时怎么封装组件化?

移植性强,通用类型,满足多种需求,可以说下自己的库

46.怎么封装网络请求,具体怎么实现?

通用的GET+POST,还有下载,上传等等、

47、.GCD的信号量你是什么理解的?

A,B,C三个任务并发执行,但是C要等A,B执行完成之后再执行。

信号量:

// 创建一个信号,value:信号量
dispatch_semaphore_create(<#long value#>)
// 使某个信号的信号量+1
dispatch_semaphore_signal(<#dispatch_semaphore_t dsema#>)
// 某个信号进行等待或等待降低信号量 timeout:等待时间,永远等待为 DISPATCH_TIME_FOREVER
dispatch_semaphore_wait(<#dispatch_semaphore_t dsema#>, <#dispatch_time_t timeout#>)

正常的使用顺序是先降低然后再提高,这两个函数通常成对使用。

两种方法

-(void)dispatch_group_function1
{
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
    dispatch_group_t group = dispatch_group_create();
    dispatch_group_async(group, dispatch_queue_create("com.dispatch.test", DISPATCH_QUEUE_CONCURRENT), ^{
        NSURLRequest *request = [[NSURLRequest alloc] initWithURL:[NSURL URLWithString:@"https://www.baidu.com"]];
        NSURLSessionDownloadTask *task = [[NSURLSession sharedSession] downloadTaskWithRequest:request completionHandler:^(NSURL * _Nullable location, NSURLResponse * _Nullable response, NSError * _Nullable error) {
            // 请求完成,可以通知界面刷新界面等操作
            NSLog(@"第一步网络请求完成");
            // 使信号的信号量+1,这里的信号量本来为0,+1信号量为1(绿灯)
            dispatch_semaphore_signal(semaphore);
        }];
        [task resume];
        // 以下还要进行一些其他的耗时操作
        NSLog(@"耗时操作继续进行");
        dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
    });
    dispatch_group_async(group, dispatch_queue_create("com.dispatch.test", DISPATCH_QUEUE_CONCURRENT), ^{
        NSURLRequest *request = [[NSURLRequest alloc] initWithURL:[NSURL URLWithString:@"https://www.github.com"]];
        NSURLSessionDownloadTask *task = [[NSURLSession sharedSession] downloadTaskWithRequest:request completionHandler:^(NSURL * _Nullable location, NSURLResponse * _Nullable response, NSError * _Nullable error) {
            // 请求完成,可以通知界面刷新界面等操作
            NSLog(@"第二步网络请求完成");
            // 使信号的信号量+1,这里的信号量本来为0,+1信号量为1(绿灯)
            dispatch_semaphore_signal(semaphore);
        }];
        [task resume];
        // 以下还要进行一些其他的耗时操作
        NSLog(@"耗时操作继续进行");
        dispatch_semaphore_wait(semaphore,DISPATCH_TIME_FOREVER);
    });
    
    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        NSLog(@"刷新界面等在主线程的操作");
    });
}

2019-02-24 18:19:16.251067+0800 Semaphore[33094:12267734] 耗时操作继续进行
2019-02-24 18:19:16.251071+0800 Semaphore[33094:12267735] 耗时操作继续进行
2019-02-24 18:19:16.549563+0800 Semaphore[33094:12267748] 第一步网络请求完成
2019-02-24 18:19:18.091922+0800 Semaphore[33094:12267737] 第二步网络请求完成
2019-02-24 18:19:18.092222+0800 Semaphore[33094:12267662] 刷新界面等在主线程的操作

设定的信号值为2,先执行两个线程,等执行完一个,才会继续执行下一个,保证同一时间执行的线程数不超过2。

  dispatch_semaphore_t semaphore = dispatch_semaphore_create(2);
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    //任务1
    dispatch_async(queue, ^{
        dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
        NSLog(@"run task 1");
        sleep(1);
        NSLog(@"complete task 1");
        dispatch_semaphore_signal(semaphore);
    });
    //任务2
    dispatch_async(queue, ^{
        dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
        NSLog(@"run task 2");
        sleep(1);
        NSLog(@"complete task 2");
        dispatch_semaphore_signal(semaphore);
    });
    //任务3
    dispatch_async(queue, ^{
        dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
        NSLog(@"run task 3");
        sleep(1);
        NSLog(@"complete task 3");
        dispatch_semaphore_signal(semaphore);
    });
2019-02-24 18:31:02.447769+0800 Semaphore[33286:12284312] run task 1
2019-02-24 18:31:02.447767+0800 Semaphore[33286:12284310] run task 2
2019-02-24 18:31:03.450756+0800 Semaphore[33286:12284312] complete task 1
2019-02-24 18:31:03.450756+0800 Semaphore[33286:12284310] complete task 2
2019-02-24 18:31:03.450997+0800 Semaphore[33286:12284311] run task 3
2019-02-24 18:31:04.454259+0800 Semaphore[33286:12284311] complete task 3

参考文章:https://www.jianshu.com/p/a470d1f88f24

48、SDWebImage底层基本原理?

实现功能及思路:

  1. cell多图片展示:通过NSData网络下载小图片
  2. 重复下载问题:设置任务缓存,每次下载前先判断
  3. 内存暴涨问题:图片二级缓存机制。
  4. 卡住主线程:在子线程中下载,下载完毕后回主线程刷新UI
  5. 图片显示错误:通过reloadRowsAtIndexPaths定点刷新UI
49、 给出以下两个数组,block调用后分别输出结果是什么?
   NSArray *arr=  @[@"1"];
   NSMutableArray *mutableArray = [[NSMutableArray alloc]initWithObjects:@"123", nil];
   
    void(^block)(void) = ^{
        NSLog(@"array %@",arr);
        NSLog(@"mutableArray %@",mutableArray);
    };
    
    arr = @[@"2"];
 
    [mutableArray addObject:@"456"];
    
 
    block();

答案:1和123、456 ,为什么呢?我们通过测试可以知道,不可变数组在进行重新赋值后,系统会自动分配另一块内存给它,所以原来的值不会改变,可变数组的add方法是正常可以执行的,不会有内存地址改变的情况。具体看输出

 NSArray *arr=  @[@"1"];
    NSLog(@"不可变数组指针地址%p===内存地址%p",arr,arr);
    
    NSMutableArray *mutableArray = [[NSMutableArray alloc]initWithObjects:@"123", nil];
    NSLog(@"可变数组指针地址%p===内存地址%p",mutableArray,mutableArray);
       
    void(^block)(void) = ^{
        NSLog(@"array %@",arr);
        NSLog(@"mutableArray %@",mutableArray);
    };
    
    arr = @[@"2"];
   NSLog(@"不可变数组指针地址%p===内存地址%p",arr,arr);
    [mutableArray addObject:@"456"];
    
    NSLog(@"可变数组指针地址%p===内存地址%p",mutableArray,mutableArray);
       
    block();
2020-12-25 17:09:33.959913+0800 NativeFlutterDemo[66551:20771232] 指针地址0x600001dac1a0===内存地址0x600001dac1a0
2020-12-25 17:09:38.265963+0800 NativeFlutterDemo[66551:20771232] mutableArray指针地址0x6000011d8810===内存地址0x6000011d8810
2020-12-25 17:09:41.655014+0800 NativeFlutterDemo[66551:20771232] 指针地址0x600001da8350===内存地址0x600001da8350
2020-12-25 17:10:58.841020+0800 NativeFlutterDemo[66551:20771232] mutableArray指针地址0x6000011d8810===内存地址0x6000011d8810
50.TCP的三次握手是什么?

socket是对tcp的封装,tcp属于网络传输层,能保证数据的安全传输 ,那么tcp三次握手概念:
1.建立连接,客户端A向服务端B发送SYN包,等待服务端B接收;
2.服务端B收到客户端A的请求,并向客户端A发送一个SYN+ACK包,等待客户端A接收;
3.客户端A收到数据后确认包ACK = K+1,完成三次握手;

51.开启一个定时器NStimer,如何在离开界面的释放这个对象?
关于NSTimer释放和内存泄漏的问题。

@(NSTimer)[内存管理,NSTimer释放,循环引用]
出现有些时候无法释放timer,导致dealloc不调用。

问题分析:

原因是 Timer 添加到 Runloop 的时候,会被 Runloop 强引用,然后 Timer 又会有一个对 Target 的强引用(也就是 self )也就是说 NSTimer 强引用了 self ,导致 self 一直不能被释放掉,所以 self 的 dealloc 方法也一直未被执行.

NSTimer还有一个规则:(在哪个线程创建就要在哪个线程停止,否则会导致资源不能被正确的释放。)

问题关键:

问题的关键就在于 self 被 NSTimer 强引用了,如果能打破这个强引用,问题应该就能决了。

来自YYKit的解决方案,把self偷梁换柱:
+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)seconds block:(void (^)(NSTimer *timer))block repeats:(BOOL)repeats {
    return [NSTimer scheduledTimerWithTimeInterval:seconds target:self selector:@selector(_yy_ExecBlock:) userInfo:[block copy] repeats:repeats];
}

52.滚动的cell 中都有一个定时器是倒计时,(抢购等倒计时)怎么让他滑动时候依然可以正常倒计时?

Nstimer的创建可以给一个mode,kCFRunLoopCommonModes 、UITrackingRunLoopMode:界面跟踪 Mode,用于 ScrollView 追踪触摸滑动,保证界面滑动时不受其他 Mode 影响。

53.产品经理后期在项目已经完成的情况下插入一条需求,要求 监听按钮的点击,埋点统计。要怎么实现?

runtime来实现,在程序启动时候,+(void)load方法里面 替换按钮的系统方法,在【super 方法】之前拦截所点击的按钮tag 来进行标记,最终传给服务器

54.多张图片同时上传给用户好的体验怎么使用gcd来实现?
dispatch_get_global_queue 队列组
#pragma mark  //加载 图片
-(void)case10{
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    dispatch_async(queue, ^{
        UIImage * img1 = [self myImageWithUrl:@"https://ss0.bdstatic.com/94oJfD_bAAcT8t7mm9GUKT-xh_/timg?image&quality=100&size=b4000_4000&sec=1464070575&di=3a13253ffa34b1dc5a15210527e1c3a5&src=http://img3.3lian.com/2014/f1/2/d/11.jpg"];
        UIImage * image2=[self myImageWithUrl:@"http://img3.3lian.com/2014/f1/2/d/9.jpg"];
        
        dispatch_async(dispatch_get_main_queue(), ^{
            
            _image1.image =img1;
            _image2.image =image2;
        });
    });
} 
-(void)case11{
    
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    dispatch_async(queue, ^{
        
        //创建一个组
        dispatch_group_t group = dispatch_group_create();
        __block UIImage * image1 = nil;
        __block UIImage * image2 = nil;
        dispatch_group_async(group, queue, ^{
            
            NSLog(@"1111111>>>>>>%@",[NSThread currentThread]);
            image1 =[self myImageWithUrl:@"https://ss0.bdstatic.com/94oJfD_bAAcT8t7mm9GUKT-xh_/timg?image&quality=100&size=b4000_4000&sec=1464070575&di=3a13253ffa34b1dc5a15210527e1c3a5&src=http://img3.3lian.com/2014/f1/2/d/11.jpg"];
    
        });
        dispatch_group_async(group, queue, ^{
            
            NSLog(@"22222222>>>>>>%@",[NSThread currentThread]);
            image2 =[self myImageWithUrl:@"http://img3.3lian.com/2014/f1/2/d/9.jpg"];
            
        });
        //获取到图片后刷新ui
        
        dispatch_group_notify(group, queue, ^{
            _image1.image = image1;
            _image2.image =image2;
            
        });
    });
}
55.给出一个UIView,我要操作可点击事件,某一块区域不让点击怎么实现?
1.理解事件传递过程,用这个来实现扩大点击范围
2.使用Runtime机制扩大点击范围

传递过程

当用户点击屏幕后:

UIApplication 先响应事件。

然后传递给UIWindow。

如果window可以响应。就开始遍历window的subviews。遍历原则是:从离用户最近的那个子视图去遍历

遍历的过程中,如果第一个遍历的view1可以响应,那就遍历这个view1的subviews(依次这样不停地查找,直至查找到合适的响应事件view)。

如果view1不可以响应,那就开始对view2进行判断和子视图的遍历。依次类推view3,view4……

如果最后没有找到合适的响应view,这个消息就会被抛弃。这个就是iOS中的事件链。
 

如图


事件的传递

为了方便,我们将
- (UIView *)hitTest:(CGPoint)point withEvent:(nullableUIEvent *)event;称为方法A

- (BOOL)pointInside:(CGPoint)point withEvent:(nullableUIEvent *)event;称为方法B

对view进行重写这两个方法后,就会发现,点击屏幕后,首先响应的是方法A;
如果方法A中,我们没有调用父类的这个方法,那就根据这个方法A的返回view,作为响应事件的view。(当然返回nil,就是这个view不响应).
如果方法A中,我们调用了父类的这个方法,也就是

[super hitTest:point withEvent:event];那这个时候系统就要调用方法B;通过这个方法的返回值,来判断当前这个view能不能响应消息。

如果方法B返回的是NO,那就不用再去遍历它的子视图。方法A返回的view就是可以响应事件的view。

如果方法B返回的是YES,那就去遍历它的子视图。(就是上图我们描述的那样,找到合适的view返回,如果找不到,那就由方法A返回的view去响应这个事件。)

55.为什么只有主线程的runloop是开启的?

mian()函数中调用UIApplicationMain,这里会创建一个主线程,用于UI处理,为了让程序可以一直运行并接收事件,所以在主线程中开启一个runloop,让主线程常驻

56.PerformSelectorrunloop的关系?

当调用NSObect的 performSelector:相关的时候,内部会创建一个timer定时器添加到当前线程的runloop中,如果当前线程没有启动runloop,则该方法不会被调用.

开发中遇到最多的问题就是这个performSelector: 导致对象的延迟释放,这里开发过程中注意一下,可以用单次的NSTimer替代.

57.KVO的实现原理

通过runtime派生子类的方式 复写相关需要KVO监听的属性,在该属性setter之前和之后调用NSObject的监听方法,这样KVO就实现了属性变换前后的回调.

KVO派生的子类具体格式应该是:NSKVONotifying_+类名的类 eg: NSKVONotifying_Person

58.如何手动关闭KVO?

被观察的对象复写如下方法 返回NO即可关闭KVO

+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key {
    return NO;
}

如果关闭后还想触发 KVO的话 修改需要手动调用在变量setter的前后 主动调用 willChangeValueForKey:和didChangeValueForKey:

59.通过KVC修改属性会触发KVO么?

会,反之不会。

60.哪些情况下使用kvo会崩溃,怎么防护崩溃?

使用不当 会crash,比如:

添加和移出不是成对出现且存在多线程添加KVO的情况,经常遇到的crash是移出 - 内存 dealloc的时候 或者对象销毁前没有正确移出Observer

如何防护?

1.注意移出对象 匹配
2.内存野指针问题,一定要在对象销毁前移出观察者
3.可以使用第三方库BlockKit添加KVO,blockkit内部会自动移除Observer避免crash.

祝大家工作顺利~
上一篇下一篇

猜你喜欢

热点阅读