技术点

1、oc中 load 和initialize 方法的异同?

连接

+initialize方法会在类第一次接收到消息时调用
+initialize 消息发送机制(objc_msgSend)

调用顺序

先调用父类的+initialize,再调用子类的+initialize
(先初始化父类,再初始化子类,每个类只会初始化1次)

+load方法会在runtime加载类、分类时调用每个类、分类的+load,在程序运行过程中只调用一次
+(load)方法是根据方法地址直接调用,并不是讲过objc_msgSend函数调用
+load方法是在main函数之前调用的

调用顺序

先调用类的+load
按照编译先后顺序调用(先编译,先调用)
调用子类的+load之前会先调用父类的+load
再调用分类的+load
按照编译先后顺序调用(先编译,先调用)

load 方法:
  • 对于每个类(class)及分类(category)来说,都会调用此方法,且只会调用一次 。如果分类和其所属的类都调用了load方法,则先调用类里面的,再调用分类里的
  • 无论类又没有被使用到,只要重写load方法,load都会被调用。
  • 如果存在继承关系, 会先调用父类的initialize方法, 再调用子类的initialize方法
initialize方法:
  • 对每个类来说,该方法会在程序首次使用该类前调用,且只调用一次。它是由运行期系统来调用的,绝不应该通过代码直接调用。
  • initialize方法与其他消息一样,如果某个类未实现它,而其父类实现了,就会运行超类的实现代码。
  • 如果存在继承关系, 会先调用父类的initialize方法, 再调用子类的load方法
load和initialize之间的区别如下:
  • initialize是”惰性调用的”,即只有当用到了相关的类时,才会调用。如果某个类一直都没有使用,则其initialize方法就一直不会运行。这也就是说,应用程序无须把每个类的initialize都执行一遍
    这就与load不同,对于load来说,应用程序必须阻塞并等待所有类的load都执行完,才能继续。
  • 在运行期系统执行该方法时,是处于正常状态,因此从运行期系统完整度上来讲,此时可以安全使用并调用任意类中的任意方法。而且,运行期系统也能保证initialize方法一定会在“线程安全的环境(thread-safe environment)”中执行,这就是说,只有执行initialize的那个线程可以操作类或者类实例。其他线程都要先阻塞,等着initialize执行完。
    load方法的问题在于,执行该方法时,运行期系统处于“脆弱状态(fragile state)”。在执行子类的load方法之前,必定会执行所有超类的load方法。
    +load 方法会被默认执行,并且是在 main 函数之前执行的。

追问1、Category 中有 load 方法吗? load 方法是什么时候调用的? load 方法能继承吗?

  • Category 中是有 load 方法的
    在运行时时期, 会将 Category 中的实例方法列表, 协议列表, 属性列表添加到主类中, 并且不会对 load 方法做特殊处理, 故 load 方法跟其它方法一样, 被插到主类中.
    每个类都可以自己实现 load 方法的.

  • + load 方法是在运行时时期调用的
    在运行时时期, 将 Category 中的实例方法列表, 协议列表, 属性列表添加到主类中后, 会递归调用所有类的 load 方法, 递归调用确保以下几点:

    1、父类的 load 方法先调用
    2 、主类中的 load 方法先调用, 分类中的 load 方法后调用
    3、分类之间的 load 方法调用顺序, 看文件编译的顺序

  • + load 方法特殊之处
    在运行时时期, 循环调用所有类的 +load 方法. 直接使用函数内存地址的方式 (*load_method)(cls, SEL_load); 而不是使用发送消息 objc_msgSend 的方式.如果通过消息机制调用,分类方法就会覆盖主类方法,既然load方法分类和主类都调用了,就证明他不是消息机制来调用

总结

Category 是在运行时时期将方法, 协议, 属性插入到主类中, 对于load方法并没有做特殊操作, 所以 load方法跟其它方法一样, 会 "覆盖" 主类的方法;
同时在运行时时期, 又会递归调用 load方法, 而且是通过函数指针直接调用的, 没有走消息转发的机制, 这就导致只要类实现了load 方法就会被调用.

2、直接用UILabel和自己用DrawRect画UILabel,哪个性能好?为什么?哪个占用的内存少?为什么?

  直接使用UILabel性能更好。
  原因:重写了-drawRect:方法,-drawRect :方法就会自动调用,生成一张寄宿图,然后内容就会缓存起来,等待下次你调用-setNeedsDisplay时再进行更新。
 &emsp直接使用UILabel内存更占优。

3、项目采用64位,为什么要用64位?怎么修改成64位?i386是什么?他们有什么关系?

1、修改组织文件,增加arm64
2、i386是针对intel通用微处理器32位处理器
x86_64是针对x86架构的64位处理器
模拟器32位处理器测试需要i386架构,
模拟器64位处理器测试需要x86_64架构,
真机32位处理器需要armv7,或者armv7s架构,
真机64位处理器需要arm64架构。

4、iOS的应用程序有几种状态?追问,退到后台代码是否可以执行?双击home键,代码是否可以执行?

1、Not running 未运行:应用还没有启动,或者应用正在运行但是途中被系统停止。
2、Inactive 未激活:当前应用正在前台运行,但是并不接收事件。
3、Active 激活:当前应用正在前台运行,并且接收事件。
4、Suspended 挂起:应用处在后台,并且已停止执行代码。
5、Background 后台:应用处在后台,并且还在执行代码

5、TCP与UDP的区别

1、TCP面向连接(需要建立三次握手协议);UDP是无连接的,即发送数据之前不需要建立连接。
2、TCP提供可靠的服务,保证数据无差错,不丢失,不重复,且按序到达。UDP只管发,不管收不收的到
3、每一条TCP连接只能是点到点的;UDP支持一对一,一对多,多对一和多对多的交互通信.

6、NSString占用多少内存?

8个字节(通过sizeof() 函数计算)
OC 的对象类型 NSString、NSArray、NSInteger、CGFloat、NSDictionary 占用内存字节数为 8
基本数据类型 Int、Float 占用字节数 为 4

7、使用SDWebImage和YYImage下载高分辨率图,导致内存暴增的解决办法

http://www.cocoachina.com/ios/20160920/17602.html

8、谈谈对mrc和arc的理解?

https://www.jianshu.com/p/48665652e4e4

9、对于atomic、nonatomic、assign、retain、copy、strong、weak的简单理解?

1)atomic设置成员变量的@property属性时,atomic是默认值,提供多线程安全
2)nonatomic 若是禁止多线程,实现变量保护,提高性能,可设置成员变量的@property属性为nonatomic
3)assign 此标记说明设置器直接进行赋值,assign是默认值,针对基础数据类型(NSInteger, CGFloat)和C数据类型(int, float, double, char)等。 注意:MRC 下 assign 为属性的默认修饰符,无论是简单的数据类型,还是指向对象的指针
4)retain 定retain会在赋值时唤醒传入值的retain消息,对其他的NSObject和其子类对参数先进行release旧值,再retain新值。释放旧的对象,将旧对象的值赋予输入对象,再提高输入对象的引用计数为 1
5)copy 对NSString 它指出在赋值时使用传入值的一份拷贝。
6)strong 为 ARC 下属性的默认内存管理语义语义等同于 retain。变量的所有权修饰符为 __strong。被赋值时会持有对象,阻止对象被释放。
7)atomic:对于对象的默认属性,就是setter/getter生成的方法是一个原子操作。如果有多个线程同时调用setter的话,不会出现某一个线程执行setter全部语句之前,另一个线程开始执行setter的情况,相关于方法头尾加了锁一样。
8)nonatomic:不保证setter/getter的原子性,多线程情况下数据可能会有问题。效率高

strong与weak是由ARC新引入的对象变量属性
ARC引入了新的对象的新生命周期限定,即零弱引用。如果零弱引用指向的对象被deallocated的话,零弱引用的对象会被自动设置为nil

追问 1、

 NSObject *obj = [NSObject new];
    NSLog(@"1====%lu",(unsigned long)[obj retainCount]);
    
    NSObject *obj1 = obj;
    NSLog(@"2====%lu",(unsigned long)[obj retainCount]);

    NSObject *obj2 = [obj retain];
    NSLog(@"3====%lu",(unsigned long)[obj retainCount]);

打印结果:
2018-12-12 14:24:07.854338+0800 排序算法[3287:196458] 1====1
2018-12-12 14:24:07.854483+0800 排序算法[3287:196458] 2====2
2018-12-12 14:24:11.305737+0800 排序算法[3287:196458] 3====3

10、week 原理?

https://blog.csdn.net/jyt199011302/article/details/78368651

11、简述下block的实现?

https://www.aliyun.com/jiaocheng/352836.html

12、描述下IM系统如何保证消息不丢

https://blog.csdn.net/u014105540/article/details/80539507

13、数据源同步解决方案?

  • 并发访问,数据copy
  • 串行访问

14、UIView 和 CALayer 关系?

  • UIView为CALayer 提供内容,负责处理触摸等事件,参与相应者链
  • CALayer 负责显示内容 contents
    体现出六大设计原则中的单一原则

15、UI视图显示工作原理?

  • CPU:负责计算显示内容,比如视图的创建、布局计算、图片解码、文本绘制等,计算好显示内容会提交到 GPU
  • GPU: 负责进行变换、合成、渲染,完成后将渲染结果放入帧缓冲区

16、离屏渲染?

  • 当我们跟指定的UI视图的某些属性,标记为它在未预合成之前,不能用于当前屏幕直接显示,就会触发离屏渲染。
  • 离屏渲染的概念起源于GPU层面:指的是GPU在当前屏幕缓冲区以外,新开辟一个缓冲区,进行渲染操作。
    (1)、为何要避免离屏渲染?
    • 在触发离屏渲染的时候,会增加GPU的工作量,就会导致GPU和GPU工作耗时,超过16.6ms(毫秒),从而导致页面卡顿掉帧。离屏渲染会创建新的渲染缓冲区:就会有内存上的开销;上下文切换:因为有多通道渲染管线,最后需要把多通道渲染结果做最终合成,就会有上下文切换

追问1、tableview性能优化方案(拓展)

  • 重用cells
  • 尽量初始化一次性加载所需view,通过hidden或者fame来控制视图是否显示。
  • 属于模板预加载、预布局(高度在数模转换时就计算出来并缓存,显示时直接提取数据就可)。尽量少频繁更改布局。
  • 图形、文本异步绘制。
  • 避免离屏渲染(透明度、圆角都会导致离屏渲染)。
  • 使用轻量级组件代替重量级组件

追问2、相应者链

屏幕快照 2019-06-26 下午2.09.31.png

17、分类和扩展

  • Category 分类 作用:
    (1)、添加实力方法
    (2)、添加类方法
    (3)、添加协议
    (4)、添加属性

特点:
1、运行时决议(分类是在运行时才将分类中添加的内容真实的添加到宿主类中)
2、可为系统类添加方法。

  • Extension 分类 作用:
    (1)、声明私有属性
    (2)、声明私有成员变量
    (3)、声明私有方法(一般没啥作用)

特点:
1、编译时决议(编译的时候已经把我们扩展的内容添加到宿主类中了)
2、只以声明的形式存在,多数情况寄生于宿主类的.m文件中
3、不能为系统类添加扩展

追问 1、能否为分类增加成员变量?

可以通过关联对象的方式来达到分类可以添加成员变量的效果。

18、深copy 和浅copy

  • 深拷贝:开辟新的内容空间 保存原对象的值 。(不会改变原对象的引用计数)
  • 浅拷贝:浅拷贝是对object对象的指针拷贝,让指针指向同一块内存地址。(会触发原对象的引用计数 +1)
  • retain:引用计数器+1,指向同一地址。结果和浅拷贝一样,仅仅是使得对象的引用计数器自增1.
什么情况下是深拷贝?什么情况是浅拷贝?

可变对象:copy 或者 mutableCopy 操作都是深拷贝。
不可变对象:mutableCopy 操作是深拷贝。
不可变对象: copy 操作是浅拷贝。

copy操作拷贝的结果对象都是不可变对象;mutableCopy操作拷贝的结果对象都是可变对象。

面试题: @property (copy) NSMutableArray *array ; 这样声明一个成员属性会导致什么问题?

  • 如果赋值过来的对象是NSMutableArray,copy之后是NSArray;
  • 如果赋值过来的对象是NSArray,copy之后是NSArray;
    此时如果们拿array 进行 移除、添加对象操作,因为copy的结果是不可变对象,此时不可变对象去调用其子类(可变对象继承与不可变对象)方法,就会产生异常或者crash。

追问 1、 如何让自己写的对象具有拷贝功能?

  • 不可变对象:需要实现NSCopying协议。
    并实现如下方法
 - (id)copyWithZone:(NSZone *)zone;
  • 可变对象:需要实现NSMutableCopying协议。
    并实现如下方法
 - (id)mutableCopyWithZone:(NSZone *)zone;

19、请简述分类的实现原理。

分类:分类是有运行时决议的;不同分类中含有同名分类方法,谁最终生效,取决于谁最后参与编译;如果分类中添加的方法是宿主类中的方法,则分类方法会覆盖同名的宿主类方法。

20、KVO 的实现原理是怎样的?

KVO 是系统关于观察者模式的实现;kvo运用的isa混写技术来动态运行时去为某个类添加一个子类,然后重写setting 方法,setter 方法会负责在调用原 setter 方法之前和之后,通知所有观察对象属性值的更改情况。(同时将原有类的isa指针指向新创建的类上面)
set方法实现内部会顺序调用willChangeValueForKey方法、原来的setter方法实现、didChangeValueForKey方法,而didChangeValueForKey方法内部又会调用监听器的observeValueForKeyPath:ofObject:change:context:监听方法

  • 1.观察对象时,会动态创建一个新类:NSKVONotifying_A,并且A对象的isa指针指向新类。A对象就变成新类的对象了。
  • 2.重写了新类中观察的属性的setter方法。新的setter方法会负责在调用员setter方法之前和之后,通知所有观察对象属性值的更改情况。
  • 3.KVO的:KVO的键值观察通知依赖于 NSObject 的两个方法:willChangeValueForKey:和 didChangevlueForKey:,在存取数值的前后分别调用2个方法。

    21、assign和week关键字他们之间的区别有哪些?

assign:

  • 1、用来修饰基本数据类型,int、bool等。
  • 2、assign修饰对象类型时,不改变其引用计数。
  • 3、会产生悬垂指针。(assign所修饰的对象,再被释放之后,assign指针扔指向原对象内存地址,此时再通过assign指针访问原对象,会产生内存泄露)
    week:
  • 1、不改变所修饰对象的引用计数。
  • 2、所指对象在被释放之后会自动置nil。

    22、MRC下如何重写retain修饰对象的setter方法?

  • @property (nonatomic, retain)id obj;
- (void)setObj:(id)obj{
  if(_obj != obj){
    [_obj release];
    _obj = [obj retain];
   }
}

23、[obj foo] 和 objc_msgSend()函数之间有什么关系?

objc_msgSend()是[obj foo]的具体实现。
在runtime中,objc_msgSend()是一个c函数,[obj foo]会被翻译成这样的形式objc_msgSend(obj, foo)。

  • 去obj的对应的类中找方法
  • 先找缓存,找不到再去找方法列表,
  • 再找父类,如此向上传递。
  • 最后再找不到就要转发。

追问1、 _objc_msgForward函数是做什么的?直接调用它将会发生什么?

答案:_objc_msgForward是IMP类型,用于消息转发的:当向一个对象发送一条消息,但它并没有实现的时候,_objc_msgForward会尝试做消息转发
直接调用_objc_msgForward是非常危险
的事,这是把双刃刀,如果用不好会直接导致程序Crash,但是如果用得好,能做很多非常酷的事。

24、runtime 如何通过Selector 找到对应的IMP地址的?

  • 首先查找当前实例所对应类对象的缓存,是否有Selector对应缓存的IMP实现,如果缓存命中,我们就将命中的缓存函数返回给调用方;如果缓存没有命中,我们再根据当前类的方法列表,去查找Selector对应的IMP实现;如果当前类没有命中,再根据当前类的superClass指针,逐级查找父类的一些方法列表,查找Selector对应的IMP实现。

    追问1、isa指针是什么含义?

isa包括:指针型的isa(isa的值代表Class的地址)和非指针型的isa(isa的值的一部分代表Class的地址)

追问2、isa指针的指向?

  • 关于对象,isa指向类对象。
  • 关于类对象,其指向元类对象。
  • 我们调用一个实例的实例方法,实际是通过他的isa指针,到他的类对象中进行方法查找。
  • 如果我们调用的是类方法,是通过类对象的isa指针到他的元类里面查找

    追问3、类对象和元类对象之前有什么区别和联系?

  • 实例对象可以通过isa指针找到类对象(类对象中存储方法列表、协议列表、方法名等信息),类对象可以通过isa指针找到元类对象,从而可以访问类方法列表等相关信息。
  • 类对象和元类对象都是继承objc_class数据结构,objc_class数据结构又继承了objc_object,所以他们才有isa指针。

    追问4、元类对象的isa指针指向哪里?

无论subclass、supclass还是rootclass的meta元类对象isa指针都指向根元类对象。注意:根元类对象它的superclass指针指向根类对象

屏幕快照 2019-07-04 下午3.39.40.png

追问5、我们调用的一个类方法没有对应的实现,但是有同名的实例方法的实现,这时候会不会崩溃?会不会产生实际的调用?

由于根元类的superclass对象指向根类对象, 当我们在跟类对象的类方法列表没有查找到时,就会到根类对象的实例方法列表中查找,如果有同名方法,就会执行同名方法的实例方法调用。

25、能否向编译后的类中添加实例变量?

runtime 动态给类添加方法,是在编译之前。编译之后是没办法在给类动态添加方法的。

26、你都了解知道哪些设计原则,以及你对这些设计原则都有哪些理解?

  • 单一职责原则:一个类只负责一件事,职责要单一。
  • 依赖倒置原则:面向接口编程(抽象不应该依赖于具体实现,具体实现可以依赖于抽象。简单来说:就是面向接口编程,不深究接口内部的具体实现)
  • 开闭原则: 对修改关闭、对扩展开发。
  • 里氏替换原则:父类可以被子类无缝替换,切原有功能不受任何影响。(kvo就是一个简单的例子)
  • 接口隔离原则:接口设计要精简(使用多个专门的协议,而不是一个庞大臃肿的协议)
  • 迪米特法则:对象与对象之间应该尽量减少相关的联系。(最终结果:高内聚、低耦合)

27、多线程编程技术的优缺点比较

  • NSThread (抽象层次:低)
    优点:轻量级,简单易用,可以直接操作线程对象
    缺点: 需要自己管理线程的生命周期,线程同步。线程同步对数据的加锁会有一定的系统开销。
  • NSOperation (抽象层次:中)
    优点:不需要关心线程管理,数据同步的事情,可以把精力放在学要执行的操作上。基于GCD,是对GCD 的封装,比GCD更加面向对象(提供了一些在GCD中不容易实现的特性,如:创建队列,限制最大并发数量,操作之间的依赖关系。)
    缺点: NSOperation是个抽象类,使用它必须使用它的子类,可以实现它或者使用它定义好的两个子类NSInvocationOperation、NSBlockOperation.
  • GCD 全称Grand Center Dispatch (抽象层次:高)
    优点:是 Apple 开发的一个多核编程的解决方法,简单易用,效率高,速度快,基于C语言,更底层更高效,并且不是Cocoa框架的一部分,自动管理线程生命周期(创建线程、调度任务、销毁线程)。
    缺点: 使用GCD的场景如果很复杂,就有非常大的可能遇到死锁问题。

28、GCD中一些系统提供的常用dispatch方法?

  • dispatch_after延时添加到队列
  • dispatch_apply在给定的队列上多次执行某一任务,在主线程直接调用会阻塞主线程去执行block中的任务。
    dispatch_apply函数的功能:把一项任务提交到队列中多次执行,队列可以是串行也可以是并行,dispatch_apply不会立刻返回,在执行完block中的任务后才会返回,是同步执行的函数。
    dispatch_apply正确使用方法:为了不阻塞主线程,一般把dispatch_apply放在异步队列中调用,然后执行完成后通知主线程
  • dispatch_once保证在app运行期间,block中的代码只执行一次(经典使用场景---单例)
  • dispatch_barrier_async 栅栏的功能:是在并行队列中,等待在dispatch_barrier_async之前加入的队列全部执行完成之后(这些任务是并发执行的)再执行dispatch_barrier_async中的任务,dispatch_barrier_async中的任务执行完成之后,再去执行在dispatch_barrier_async之后加入到队列中的任务(这些任务是并发执行的)。(barrier块必须单独执行,不能与其他block并行)

追问1、如何在不使用GCD和NSOperation、NSThread的情况下,实现异步线程?

  • performSelectorInBackground 后台执行
  • [self performSelectorInBackground:@selector(test) withObject:nil];
    
  • performSelector:onThread:在指定线程执行
  • [self performSelector:@selector(test) onThread:[NSThread currentThread] withObject:nil waitUntilDone:YES];
    

这个方法有一个thread参数是指定执行的线程,但是很奇怪当我使用自己创建的线程 [[NSThread alloc] init];时,并不会执行test方法,只有当使用[NSThread currentThread]时才会执行:

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    [self performSelector:@selector(tests) onThread:[NSThread currentThread] withObject:nil waitUntilDone:NO];
});

追问2、performSelector如何进行多值传输?

  • 样例 1
    在OC中调用一个方法实际上就是发送消息objc_msgSend:
{
    NSNumber *age = [NSNumber numberWithInt:20];
    NSString *name = @"李周";
    NSString *gender = @"女";
    NSArray *friends = @[@"谢华华",@"亚呼呼"];

    SEL selector = NSSelectorFromString(@"getAge:name:gender:friends:");
    NSArray *array = @[age,name,gender,friends];

    ((void(*)(id,SEL,NSNumber*,NSString*,NSString*,NSArray*)) objc_msgSend)(self,selector,age,name,gender,friends);

}

- (void)getAge:(NSNumber *)age name:(NSString *)name gender:(NSString *)gender friends:(NSArray *)friends
{
    NSLog(@"%d----%@---%@---%@",[age intValue],name,gender,friends[0]);
}

  • 样例 2
    网上的第二种方法其实也是以NSArray的形式传值,然后创建NSInvocation的方式,将参数一一绑定。
-(id)performSelector:(SEL)aSelector withObject:(NSArray *)object
{
    //获得方法签名
    NSMethodSignature *signature = [[self class] instanceMethodSignatureForSelector:aSelector];
    
    if (signature == nil) {
        return nil;
    }
    
    //使用NSInvocation进行参数的封装
    NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];
    invocation.target = self;
    invocation.selector = aSelector;
    
    //减去 self _cmd
    NSInteger paramtersCount = signature.numberOfArguments - 2;
    paramtersCount = MIN(object.count, paramtersCount); 
    
    for (int i = 0; i < paramtersCount; i++) {
        id obj = object[i];
        
        if ([obj isKindOfClass:[NSNull class]]) continue;
        [invocation setArgument:&obj atIndex:i+2];
    }
    
    [invocation invoke];
    
    id returnValue = nil;
    if (signature.methodReturnLength > 0) { //如果有返回值的话,才需要去获得返回值
        [invocation getReturnValue:&returnValue];
    }
    
    return returnValue;
    
}

    NSNumber *age = [NSNumber numberWithInt:20];
    NSString *name = @"李周";
    NSString *gender = @"女";
    NSArray *friends = @[@"谢华华",@"亚呼呼"];
 SEL selector = NSSelectorFromString(@"getAge:name:gender:friends:");
    NSArray *array = @[age,name,gender,friends];
    
    [self performSelector:selector withObject:array];

28、iOS 中都有哪些锁?

  • @synchronized : 一般在创建单例对象的时候使用
  • atomic: 修饰属性的关键字,对被修饰的对象进行原子操作(不负责使用)
  • OSSpinLock:(自旋锁)循环等待访问,不释放当前资源。(一般用于轻量级数据访问,简单的int值 +1/-1操作)
  • NSRecursiveLock: 递归锁
  • NSLock
  • dispatch_semaphore_t:信号量

追问 1、@synthesize 和 @dynamic 分别有 么作 ?

1)@property有两个对应的词,一个是@synthesize,一个是@dynamic。如果@synthesize和@dynamic都没写,那么默认的就是@syntheszie var = _var;

2)@synthesize的语义是如果你没有手动实现setter方法和getter方法,那么编译器会自动为你加上这两个方法。

3)@dynamic告诉编译器:属性的setter与getter方法由用户自己实现,不自动生成。

28、什么是RunLoop,它是怎么做到有事做事,没事休息的?

  • RunLoop 是一个事件循环,用以处理我们的事件和消息,以及对它们的管理。在调用CFRunLoopRun相关的方法之后,会调用系统的函数machmessage,同时发生用户态向核心态的一个转换,然后当前线程处于一个休眠状态,所以做到有事做事,没事休息。

29、怎样实现一个常驻线程?

1.为当前线程开启一个runloop;
createRunloop方法会查找当前线程是否有runloop,如果没有则系统会为我们创建一个
2.向该runloop中添加一个port/source等维持runloop的事件循环
3.启动该runloop

追问1、RunLoop与线程是什么关系?

RunLoop与线程是一一对应的关系,一个线程默认是没有RunLoop的(除了主线程)。

追问2、NSRunLoopCommonModes的特殊性?

  • commonmode不是实际存在的model
  • 是同步source、timer、observer到多个mode的一种技术实现方案

追问3、当处于休眠状态的RunLoop我们可以通过哪些方式来唤醒他?

  • source1
  • timer事件
  • 外部手动唤醒

追问4、Runloop工作流程是怎样的?

1、在runloop启动之后,会发送一个通知,告知观察者Observer,runloop即将启动。
2、runloop会将处理Time/Sourece0事件的通知发送
3、处理source0事件的处理
4、如果接下来还有source1事件需要处理,通过go to语句进行代码逻辑的跳转,处理唤醒时接受到的消息
5、如果没有source1事件需要处理,此时线程要进入休眠,发送通知给observer,发生用户态向核心态的切换,线程正式进入休眠,等待唤醒。
6、杀死程序的时候,runloop就会退出停止,并且发送通知给observer即将退出runloop。
注释:当我们点击屏幕的时候 ,实际会产生一个machPort,基于machPort会转成一个source1,就会把主线程唤醒、运行、处理。

30、怎样保证子线程数据回来更新UI的时候,不打断用户的滑动操作?

当用户滑动的时候,当前的runloop运行在trackingMode模式下,我们可以把子线程抛会主线程更新UI这段逻辑封装到主线程的defaultMode下,这样抛回来的任务当用户滑动的时候,就不会执行打断用户滑动;当滑动结束后,主线程切回到defaultMode就可以执行更新ui数据。

31、怎样理解Block截获变量的特性?

  • 对于基本数据类型的局部变量是对其值进行截获。
  • 对于对象类型的局部变量是对其所有权修饰符共同进行截获
  • 对于静态局部变量对其指针进行截获
  • 对于全局变量或静态全局变量不对齐进行截获

30、iOS开发之 __block 与 __weak的区别理解

1.__block不管是ARC还是MRC模式下都可以使用,可以修饰对象,还可以修饰基本数据类型。
2.__weak只能在ARC模式下使用,也只能修饰对象(NSString),不能修饰基本数据类型(int)。
3.__block对象可以在block中被重新赋值,__weak不可以。
4.__block对象在ARC下可能会导致循环引用,非ARC下会避免循环引用,__weak只在ARC下使用,可以避免循环引用。

31、iOS 底层解析weak的实现原理

http://www.cocoachina.com/ios/20170328/18962.html
weak 的实现原理可以概括一下三步:
Runtime维护了一个weak表,用于存储指向某个对象的所有weak指针。
weak表其实是一个hash(哈希)表,Key是所指对象的地址,Value是weak指针的地址(这个地址的值是所指对象的地址)数组。
1、初始化时:runtime会调用objc_initWeak函数,初始化一个新的weak指针指向对象的地址。
2、添加引用时:objc_initWeak函数会调用 objc_storeWeak() 函数, objc_storeWeak() 的作用是更新指针指向,创建对应的弱引用表。
3、释放时,调用clearDeallocating函数。clearDeallocating函数首先根据对象地址获取所有weak指针地址的数组,然后遍历这个数组把其中的数据设为nil,最后把这个entry从weak表中删除,最后清理对象的记录。

追问 1、 iOS-实现weak后,为什么对象释放后会自动为nil?

runtime 对注册的类, 会进行布局,对于 weak 对象会放入一个 hash 表中。 用 weak 指向的对象内存地址作为 key,当此对象的引用计数为 0 的时候会 dealloc,假如 weak 指向的对象内存地址是 a ,那么就会以 a 为键, 在这个 weak 表中搜索,找到所有以 a 为键的 weak 对象,从而设置为 nil 。

追问 2、当weak引用指向的对象被释放时,又是如何去处理weak指针的呢?

1、调用objc_release

2、因为对象的引用计数为0,所以执行dealloc

3、在dealloc中,调用了_objc_rootDealloc函数

4、在_objc_rootDealloc中,调用了object_dispose函数

5、调用objc_destructInstance

6、最后调用objc_clear_deallocating

a. 从weak表中获取废弃对象的地址为键值的记录

b. 将包含在记录中的所有附有 weak修饰符变量的地址,赋值为 nil

c. 将weak表中该记录删除

d. 从引用计数表中删除废弃对象的地址为键值的记录

32、如何通过一个view查找它所在的viewController?

- (UIViewController *)findViewController:(UIView *)sourceView{ 
   id target=sourceView;   
 while (target) {     
   target = ((UIResponder *)target).nextResponder;    
    if ([target isKindOfClass:[UIViewController class]]) 
     {          
      break;    
     } 
   }   
 return target;
}

---------------------


33、iOS 如何扩大view的响应范围

- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event{
   return  YES;
}
// 在view中重写以下方法,其中self.button就是那个希望被触发点击事件的按钮
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
    UIView *view = [super hitTest:point withEvent:event];
    if (view == nil) {
        // 转换坐标系
        CGPoint newPoint = [self.button convertPoint:point fromView:self];
        // 判断触摸点是否在button上
        if (CGRectContainsPoint(self.button.bounds, newPoint)) {
            view = self.deleteButton;
        }
    }
    return view;
}


33、进程间的通信方式,并举例?

1、URLScheme
2、Keychain
3、UIPasteboard
4、UIDocumentInteractionController
5、local socket
6、 AirDrop
7、 UIActivityViewController
8、 App Groups

34、三种Block

根据isa指针,block一共有3种类型的block

_NSConcreteGlobalBlock 全局静态

_NSConcreteStackBlock 保存在栈中,出函数作用域就销毁

_NSConcreteMallocBlock 保存在堆中,retainCount == 0销毁

35、内存五大分区

  • 堆:就是那些由new分配的内存块,它们的释放编译器不管,由我们的应用程序控制,一般一个new就要对应一个delete。如果程序员没有释放掉,那么在程序结束后,操作系统会自动回收。
  • 栈:就是那些由编译器在需要的时候分配,不需要的时候自动清除的变量的存储区。里面的变量通常是局部变量、函数参数等
  • 代码区:就是那些由malloc等分配的内存块,和堆很相似,不过是用free来释放内存。
  • 全局区:全局变量和静态变量被分配到同一块内存中。
  • 常量区:这是一块比较特殊的存储区,里面存放的是常量,不允许修改。

36、get和post请求方式的区别?

安全:不引起server端的任何变化
幂等:同一个请求执行一次和执行多次的效果是完全相同的。
可缓存:请求是否可缓存

  • 1、get 一般用于获取资源,是安全的()、幂等的、可缓存的。
    post 一般用于处理资源,非安全、非幂等、非可缓存。
  • 2、get 请求有参数长度限制(2048字节),请求不安全。
    post 请求没有参数长度限制,请求相对比较安全。
  • 3、get 请求参数拼接在url里面。
    post 请求参数在body里面。

36、你都了解哪些状态码?

1xx、 2xx、 3xx、 4xx 、 5xx

  • 200 响应成功。
  • 301、302 发生了一些网络重定向。
  • 401、404 客户端发起的请求存在问题。403身份授权出现问题
  • 501、502 server端本身是有异常的。

37、tcp三次捂手四次挥手?

https://www.cnblogs.com/Andya/p/7272462.html
三次握手:

  • Client 发送链接请求报文。SYN同步报文
  • Server收到连接请求报文段后,如同意建立连接,则向Client发送确认报文
  • Client 收到Server的确认报文后,要向Server端发送确认报文。

39、怎样理解滑动窗口协议?

定义:传输的每个部分被分配唯一的连续序列号,接收方使用数字并以正确的顺序放置接收到的数据包,丢弃重复的数据包并识别丢失的数据。

  • 滑动窗口协议(Sliding Window Protocol),属于TCP协议的一种应用,用于网络数据传输时的流量控制,以避免拥塞的发生。该协议允许发送方在停止并等待确认前发送多个数据分组。由于发送方不必每发一个分组就停下来等待确认,因此该协议可以加速数据的传输,提高网络吞吐量。

40、请简单描述TCP的慢启动的特点?

  • 慢开始,拥塞避免
  • 快恢复,快重传

41、TCP的五大特点?

面向连接、可靠传输、面向字节流、流量控制、拥塞控制

42、加密算法?

  • 对称加密:对称加密(也叫私钥加密)指加密和解密使用相同密钥的加密算法
  • 非对称加密:公开密钥(publickey)和私有密钥(privatekey)。公开密钥与私有密钥是一对,如果用公开密钥对数据进行加密,只有用对应的私有密钥才能解密;如果用私有密钥对数据进行加密,那么只有用对应的公开密钥才能解密
  • 对称加密算法:DES、AES
    对称加密算法的优点是算法公开、计算量小、加密速度快、加密效率高
  • 非对称加密算法:RSA、DSA、ECC
    非对称加密的缺点是加密和解密花费时间长、速度慢,只适合对少量数据进行加密。但是安全等级高

43、NSOperation关于人物状态的控制都有哪些呢?

  • isReady 是否处于就绪状态
  • isExecuting 当前任务是否处于正在执行中
  • isFinished 当前任务是否以执行完成
  • isCancelled 当前任务是否已取消

43、NSOperation状态的控制?

  • 如果重写了main方法,底层控制变更任务执行完成状态,以及任务对退出
  • 如果重写了start方法,自动控制任务状态变更

44、怎样移除一个isFinished = Yes 的NSOperation的?

KVO

45、怎样移除一个isFinished = Yes 的NSOperation的?

46、Swift比Objective-C有什么优势?

1、Swift容易阅读,语法和文件结构简易化。
2、Swift更易于维护,文件分离后结构更清晰。
3、Swift更加安全,它是类型安全的语言。
4、Swift代码更少,简洁的语法,可以省去大量冗余代码
5、Swift速度更快,运算性能更高

47、为什么代理 要 weak?代理的delegate和dataSource有什么区别?block和代理的区别?

  • 通过weak打破循环引 。
  • delegate是一个类委托另一个类实现某个方法,协议里面的方法主要是与操作相关 的。
  • datasource 一个类通过datasource将数据发送给需要接受委托的类,协议里面的 方法主要是跟内容有关的。
  • 代理和block的区别:代理 和block的共同特性是回调机制。不同的是代理的方法比较多,block代码比较集中;代理的运行成本要低于block的运行成本,block的出站需要从栈内存拷贝到堆内存。公共接比较多时, 用代理解耦;简单回调和异步线程中使block。

48、id和NSObject*的区别?

id是 个 objc_object 结构体指针,定义是 typedef struct objc_object *id
id可以理解为指向对象的指针。所有oc的对象 id都可以指向,编译器不会做类型检 查,id调用任何存在的方法都不会在编译阶段报错,当然如果这个id指向的对象没有 这个方法,该崩溃还是会崩溃的。
NSObject *指向的必须是NSObject的 类,调用的也只能是NSObjec里面的方法否则就要做强制类型转换。
不是是所有的OC对象都是NSObject的子类,还有一些继承自NSProxy。NSObject * 可指向的类型是id的子
集。

49、iOS内存管理的原理是什么?

iOS中内存管理的方式主要有三种:1、taggedPointer。2、NONPOINTER_ISA。3、散列表。

taggedPointer:

那么为什么要使用taggedPointer这种内存管理方法呢?其如何达到节省内存的目的呢。举个例子,比如在OC中一个NSNumber这类小对象,在32位中的系统中占用4个字节的空间,但是迁移至64位系统中后,其占用空间达到了8字节,以此类推,所有在64位系统中占用空间会翻倍的对象,在迁移后会导致系统内存剧增,即时他们根本用不到这么多的空间,所以苹果对于一些小型数据,采用了taggedPointer这种方式管理内存。

什么是taggedPointer?

在64位的系统中,一个指针所占用的内存空间为8个字节,已足以存下一些小型的数据量了,当对象指针的空间中存满后,再对指针所指向的内存区域进行存储,这就是taggedPointer。

NONPOINTER_ISA :

在一个64位的指针内存中,第0位存储的是indexed标识符,它代表一个指针是否为NONPOINTER型,0代表不是,1代表是。第1位has_assoc,顾名思义,1代表其指向的实例变量含有关联对象,0则为否。第2位为has_cxx_dtor,表明该对象是否包含C++相关的内容或者该对象是否使用ARC来管理内存,如果含有C++相关内容或者使用了ARC来管理对象,这一块都表示为YES,第3-35位shiftcls存储的就是这个指针的地址。第42位为weakly_referenced,表明该指针对象是否有弱引用的指针指向。第43位为deallocing,表明该对象是否正在被回收。第44位为has_sidetable_rc,顾名思义,该指针是否引用了sidetable散列表。第45-63位extra_rc装的就是这个实例变量的引用计数,当对象被引用时,其引用计数+1,但少量的引用计数是不会直接存放在sideTables表中的,对象的引用计数会先存在NONPOINTER_ISA的指针中的45-63位,当其被存满后,才会相应存入sideTables散列表中。

散列表:

散列表是一个复杂的数据结构,其中包含了:自旋锁、引用计数表和弱引用表

散列表在系统中的提现是一个sideTables的哈希映射表,其中所有对象的引用计数(除上述存在NONPOINTER_ISA中的外)都存在这个sideTables散列表中,而一个散列表中又包含众多sideTable。每个SideTable中又包含了三个元素,spinlock_t自旋锁RefcountMap引用计数表weak_table_t弱引用表。所以既然SideTables是一个哈希映射的表,为什么不用SideTables直接包含自旋锁,引用技术表和弱引用表呢?因为在众多线程同时访问这个SideTables表的时候,为了保证数据安全,需要给其加上自旋锁,如果只有一张SideTable的表,那么所有数据访问都会出一个进一个,单线程进行,非常影响效率,而且会带来不好的用户体验,针对这种情况,将一张SideTables分为多张表的SideTable,再各自加锁保证数据的安全,这样就增加了并发量,提高了数据访问的效率,所以这就是一张SideTables表下涵盖众多SideTable表的原因。
当一个对象访问SideTables时,首先会取到对象的地址,将地址进行哈希运算,与SideTables的个数取余,最后得到的结果就是该对象所要访问的SideTable所在SideTables中的位置,随后在取到的SideTable中的RefcountMap表中再次进行一次哈希查找,找到该对象在引用计数表中所对应的位置,如果该位置存在对应的引用计数,则对其进行操作,如果没有对应的引用计数,则创建一个对应的size_t对象,其实就是一个uint类型的无符号整型

追问1、Objective-C 如何对内存管理的,说说你的看法和解决方法?

答:Objective-C的内存管理主要有三种方式ARC(自动内存计数)、手动内存计数、内存池。
1). 自动内存计数ARC:由Xcode自动在App编译阶段,在代码中添加内存管理代码。
2). 手动内存计数MRC:遵循内存谁申请、谁释放;谁添加,谁释放的原则。
3). 内存释放池Release Pool:把需要释放的内存统一放在一个池子中,当池子被抽干后(drain),池子中所有的内存空间也被自动释放掉。内存池的释放操作分为自动和手动。自动释放受runloop机制影响。

50.KVC的底层实现?

  • 一 kvc赋值底层实现
    当一个对象调用setValue方法时,方法内部会做以下操作:
    • 1、检查是否存在相应的key的set方法,如果存在,就调用set方法。
    • 2、如果set方法不存在。就要检查accessInstanceVariablesDirectly的返回结果;如果返回NO,直接调用setValue:forUndefinedKey:。
    • 3、如果accessInstanceVariablesDirectly返回YES,就会按照_, _is, , or is(下划线key成员变量、下划线isKey成员变量、key成员变量、isKey成员变量)进行查找,如果存在就进行赋值操作,然后结束。
    • 4、如果最终都没有进行赋值,则调用setValue:forUndefinedKey:和valueForUndefinedKey:方法。
  • 一 kvc valueForKey底层实现
    • 取值按照:get(该方法返回)、(该方法返回is)、is(该方法返回)、_(该方法返回is)等方法顺序来取值。

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

51. 什么是ARC?

自动引用计数:

  • ARC 实际上是由编译器自动为我们插入retain、release操作之外,还需要runtime功能进行支持,由编译器和runtime共同协作才能组成ARC的全部功能。
  • ARC 中是禁止手动调用reatin/release/retainCount/dealloc(可以重写dealloc方法,但是不能调用【super dealloc】方法)
  • ARC 中新增了strong、week属性关键字

手动引用计数:

  • MRC 实际是通过手动引用计数来进行对象的内存管理。
  • 主要几个方法 alloc、retain、release、retainCount、autorelease、delloc(注意:mar中dealloc需要调用【super dealloc】释放父类的成员变量)

53.请简单的介绍下APNS发送系统消息的机制

APNS优势:杜绝了类似安卓那种为了接受通知不停在后台唤醒程序保持长连接的行为,由iOS系统和APNS进行长连接替代。
APNS的原理:
1). 应用在通知中心注册,由iOS系统向APNS请求返回设备令牌(device Token)
2). 应用程序接收到设备令牌并发送给自己的后台服务器
3). 服务器把要推送的内容和设备发送给APNS
4). APNS根据设备令牌找到设备,再由iOS根据APPID把推送内容展示

54.Runtime实现的机制是什么,怎么用,一般用于干嘛?

1). 使用时需要导入的头文件
2). Runtime 运行时机制,它是一套C语言库。
3). 实际上我们编写的所有OC代码,最终都是转成了runtime库的东西。
比如:
类转成了 Runtime 库里面的结构体等数据类型,
方法转成了 Runtime 库里面的C语言函数,
平时调方法都是转成了 objc_msgSend 函数(所以说OC有个消息发送机制)
// OC是动态语言,每个方法在运行时会被动态转为消息发送,即:objc_msgSend(receiver, selector)。
// [stu show]; 在objc动态编译时,会被转意为:objc_msgSend(stu, @selector(show));
4). 因此,可以说 Runtime 是OC的底层实现,是OC的幕后执行者。

55.AFNetworking 为我们做了什么?

追问1、AFNetworking 架构图?

  • 会话模块:NSURLSession
  • 网络监听模块:监听网络模式的变化(Reachability)
  • 网络安全模块:Security
  • 请求序列化的封装:Serialization
  • 响应序列化的封装:Serialization
  • UIKit集成模块:对原生控件分类的添加(UIKit)

追问2、AFURLSessionManager 作为核心类的主要组成?

  • NSURLSession:会话模块
  • AFSecurityPolicy:(保证网络安全)网络证书校验或者公钥的验证。
  • AFNetworkReachabilityManager:对网络链接提供监听

追问3、AFURLSessionManager 作为核心类的主要负责哪些工作?

  • 创建和管理NSURLSession,以及调用系统API生成NSURLSessionTask。
  • 实现NSURLSessionDelegate等协议的代理方法,用来处理重定向,认证和响应数据的处理。
  • 引入AFSecurityPolicy保证请求安全
  • 引入AFNetworkReachabilityManager监控网络状态

追问4、NSURLConnection与NSURLSession(AFN 2.0 与 3.0区别)区别?

  • 下载优化
    • NSURLConnection下载文件时,先是将整个文件下载到内存,然后再写入到沙盒,如果文件比较大,就会出现内存暴涨的情况
    • NSURLSessionUploadTask下载文件时,会默认下载到沙盒中的tmp文件中,不会出现内存暴涨的情况 。下载完成后会把tmp中的临时文件删除,需要在初始化任务方法时,在completionHandler回调中增加保存文件的代码
  • 常驻线程
    • AFNetworking 2.0常驻线程,用来并发请求,和处理数据回调;避免多个网络请求的线程开销。(NSURLConnection的一大痛点就是:发起请求后,而需要一直处于等待回调的状态)
    • AFNetworking3.0不需要常驻线程。NSURLSession发起的请求,不再需要在当前线程进行回调,可以指定回调的delegateQueue,这样就不用为了等待代理回调方法而保活线程了。
  • 最大并发数
    • 1 、3.0需要设置最大并发数为1,
      self.operationQueue.maxConcurrentOperationCount = 1?

      串行:让并发的请求,串行的进行回调;

      锁:且为了防止多线程资源竞争加了锁(对 self.mutableTaskDelegatesKeyedByTaskIdentifier(多任务代理) 的访问进行了加锁),本来就需要等待,如果多线程并发反而造成资源浪费;

    • 2、而AF2.0的operationQueue是用来添加operation并进行并发请求的,所以不要设置为1。

55.SDWebImage 为我们做了什么?

追问1、SDWebImage 架构图?

  • UIImageView + WebCache
  • SDWebImageManager(核心工作类)
  • SDImageCache:处理图片的缓存,分为两部分,一个部分是磁盘缓存处理,一部分是内存缓存处理。
  • SDWebImageDownloader:具体图片下载

追问 2、.描述下SDWebImage里面给UIImageView加载图片的逻辑

加载图片的过程大致如下:
1.首先会在 SDWebImageCache 中寻找图片是否有对应的缓存, 它会以url 作为数据的索引先在内存中寻找是否有对应的缓存
2.如果缓存未找到就会利用通过MD5处理过的key来继续在磁盘中查询对应的数据, 如果找到了, 就会把磁盘中的数据加载到内存中,并将图片显示出来
3.如果在内存和磁盘缓存中都没有找到,就会向远程服务器发送请求,开始下载图片
4.下载后的图片会加入缓存中,并写入磁盘中
5.整个获取图片的过程都是在子线程中执行,获取到图片后回到主线程将图片显示出来

追问 3、sdwebimage 下载了图片后为什么要解码?

一般下载或者从磁盘获取的图片是PNG或者JPG,这是经过编码压缩后的图片数据,不是位图,要把它们渲染到屏幕前就需要进行解码转成位图数据,而这个解码操作比较耗时。你也可以这么理解,图片在远端存储一定都是编码后存储的,这样体积小,一个图像可以看做是一个图像文件,里面包含了文件头,文件体和文件尾,图像的数据就包含在文件体中,而我们的解码就是运用算法将文件体中的图像数据转化为位图数据,方便渲染和展示。

iOS默认是在主线程解码,所以SDWebImage将这个过程放到子线程了。

同时因为位图体积很大,所以磁盘缓存不会直接缓存位图数据,而是编码压缩后的PNG或JPG数据。

54、AsyncDiaplayKit 为我们做了什么?

主要处理问题:主要是通过减轻主线程的压力,把更多的事情能放到子线程去做,就放到子线程,主要分为三反面;

  • layout :文本宽度计算、视图布局计算
  • Rendering:文本渲染、图片解码、图形绘制
  • UIKit Objects:对象创建、对象调整、对象销毁

追问1、ASDK 的实现原理是怎样的?

主要封装了一个ASNode节点,我们对于视图的属性设置都转化成了ASNode的设置,这部分设置有恰好可以放到后台线程当中实现,然后我们在Runloop将要结束的时候,接受一个通知,提取全局容器当中的ADNode,一次性设置给UIView。

56、方法和选择器有何不同?

selector是一个方法的名字,method是一个组合体,包含了名字和实现。

57、iOS 中的几种锁?

屏幕快照 2019-10-15 上午10.52.20.png
  • @synchronized

  • NSLock 对象锁

  • NSRecursiveLock 递归锁

  • NSConditionLock 条件锁

  • pthread_mutex 互斥锁(C语言)

  • dispatch_semaphore 信号量实现加锁(GCD)

  • OSSpinLock 自旋锁 性能最佳(暂不建议使用,原因参见这里)
    1、@synchronized 关键字加锁 互斥锁,性能较差不推荐使用

@synchronized(这里添加一个OC对象,一般使用self) { 
        //这里写要加锁的代码
 }  

注意点   
1.加锁的代码尽量少    
2.添加的OC对象必须在多个线程中都是同一对象 
3.优点是不需要显式的创建锁对象,便可以实现锁的机制。 
4. @synchronized块会隐式的添加一个异常处理例程来保护代码,该处理例程会在异常抛出的时候自动的释放互斥锁。
所以如果不想让隐式的异常处理例程带来额外的开销,你可以考虑使用锁对象。

2、NSLock

  • NSLock实际上是在pthread_mutex(互斥锁)基础上进行了封装了,且pthread_mutex的类型为 PTHREAD_MUTEX_ERRORCHECK,会有错误提示,同时会损失一定性能。
  • NSLock使用注意点:同一线程重复获取同一非递归锁时,就会发生死锁
  • 原因如下:由于当前线程运行到第一个lock加锁,现在再次运行到lock同样的锁,需等待当前线程解锁,把当前线程挂起,不能解锁
  • 解决方案:我们可以用NSRecursiveLock或者@synchronized替代NSLock
    因为NSRecursiveLock或者@synchronized都是递归锁,
    递归锁:它允许同一线程多次加锁,而不会造成死锁。
  • NSRecursiveLock :内部封装了 pthread_mutex ,类型为 PTHREAD_MUTEX_RECURSIVE。
  • NSCondition :封装了一个互斥锁(pthread_mutex)和一个条件变量。
  • NSConditionLock: 在NSCondition的基础上来实现的,内部持有一个NSCondition对象和一个condition_value属性。
    参考1
    参考2

58、iOS 多读单写?

多个线程同时读取,多个线程有序写入。

// 通过宏定义 DISPATCH_QUEUE_CONCURRENT 创建一个并发队列
        concurrent_queue = dispatch_queue_create("read_write_queue", DISPATCH_QUEUE_CONCURRENT);

- (id)objectForKey:(NSString *)key
{
    __block id obj;
    // 同步读取指定数据
    dispatch_sync(concurrent_queue, ^{
        obj = [userCenterDic objectForKey:key];
    });
    
    return obj;
}

- (void)setObject:(id)obj forKey:(NSString *)key
{
    // 异步栅栏调用设置数据
    dispatch_barrier_async(concurrent_queue, ^{
        [userCenterDic setObject:obj forKey:key];
    });
}

你可能感兴趣的:(技术点)