25. KVO的实现原理,如果手动实现KVO?
(1) KVO 是基于 runtime 机制实现的
(2) 当一个对象 (假设是person
对象,对应的类为 JLperson
) 的属性值age
发生改变时,系统会自动生成一个继承自JLperson
的类NSKVONotifying_JLPerson
,在这个类的 setAge
方法里面调用
[super setAge:age]
[self willChangeValueForKey:@"age"];
[self didChangeValueForKey:@"age"];
三个方法,而willChangeValueForKey:
和didChangeValueForKey:
两个方法内部会主动调用-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context
方法,在该方法中可以拿到属性改变前后的值。
(3) 最后把这个JLperson
对象的 isa
指针 ( isa
指针告诉 Runtime 系统这个对象的类是什么 ) 指向这个新创建的子类NSKVONotifying_JLPerson
,对象就神奇的变成了新创建的子类NSKVONotifying_JLPerson
的实例。
26.谈下iOS开发中知道的哪些锁?
相关文章:
- @synchronized
- NSLock 对象锁(个人理解是互斥锁)
- NSRecursiveLock 递归锁
- NSConditionLock 条件锁
- pthread_mutex 互斥锁(C语言)
- dispatch_semaphore 信号量实现加锁(GCD)
- OSSpinLock (暂不建议使用,原因参见这里)
- 哪个性能最差?
- SD和AFN使用的哪个?
@synchronized用的最多,其次就是信号量和NSLock,其余的都很少用。
- 一般开发中你最常用哪个? 哪个锁apple存在问题又是什么问题?
NSLock。
OSSpinLock。
- OSSpinLock为什么不安全了
如果一个低优先级的线程获得锁并访问共享资源,这时一个高优先级的线程也尝试获得这个锁,它会处于 spin lock 的忙等状态从而占用大量 CPU。此时低优先级线程无法与高优先级线程争夺 CPU 时间,从而导致任务迟迟完不成、无法释放 lock。这并不只是理论上的问题,libobjc 已经遇到了很多次这个问题了,于是苹果的工程师停用了 OSSpinLock。
- 什么情况下会有死锁?
- 在一个串行队列里做同步。如:
在一个递归里用互斥锁。使用锁最容易犯的一个错误就是在递归或循环中造成死锁
如下代码中,因为在线程1中的递归block中,锁会被多次的lock,所以自己也被阻塞了。
- 此处将NSLock换成NSRecursiveLock,便可解决问题。NSRecursiveLock类定义的锁可以在同一线程多次lock,而不会造成死锁。递归锁会跟踪它被多少次lock。每次成功的lock都必须平衡调用unlock操作。只有所有的锁住和解锁操作都平衡的时候,锁才真正被释放给其他线程获得。
- 互斥锁有哪些?互斥锁和自旋锁的区别是啥?
- 互斥锁:如果一个线程无法获取互斥量,该线程会被直接挂起,不再消耗CPU时间,当其他线程释放互斥量后,操作系统会激活被挂起的线程。互斥锁会使得线程阻塞,阻塞的过程又分两个阶段,第一阶段是会先空转,可以理解成跑一个 while 循环,不断地去申请加锁,在空转一定时间之后,线程会进入 waiting 状态,此时线程就不占用CPU资源了,等锁可用的时候,这个线程会立即被唤醒。
- 自旋锁: 如果一个线程需要获取自旋锁,该锁已经被其他线程占用,该线程不会被挂起,而是不断消耗CPU时间,一直试图获取自旋锁。
- 总结:最大的区别,互斥锁是挂起等待,等待时不占用CPU,自旋锁会不停地试图获取锁,会一直占用CPU。
26. 串行队列和同步锁两者在保护线程安全上的性能对比。
27. iOS下如何实现指定线程数目的线程池?
28. 数据库建表的时候索引有什么用?
在数据量大的时候,快速提高查找速度。
//在表上创建一个简单的索引。允许使用重复的值。
CREATE INDEX index_name ON table_name (column_name);
////在表上创建一个唯一的索引。唯一的索引意味着两个行不能拥有相同的索引值。
CREATE UNIQUE INDEX index_name ON table_name (column_name);
注意事项:
第一,对于那些在查询中很少使用或者参考的列不应该创建索引。这是因为,既然这些列很少使用到,因此有索引或者无索引,并不能提高查询速度。相反,由于增加了索引,反而降低了系统的维护速度和增大了空间需求。
第二,对于那些只有很少数据值的列也不应该增加索引。这是因为,由于这些列的取值很少,例如人事表的性别列,在查询的结果中,结果集的数据行占了表中数据行的很大比例,即需要在表中搜索的数据行的比例很大。增加索引,并不能明显加快检索速度。
第三,对于那些定义为text, image和bit数据类型的列不应该增加索引。这是因为,这些列的数据量要么相当大,要么取值很少。
第四,当修改性能远远大于检索性能时,不应该创建索引。这是因为,修改性能和检索性能是互相矛盾的。当增加索引时,会提高检索性能,但是会降低修改性能。当减少索引时,会提高修改性能,降低检索性能。因此,当修改性能远远大于检索性能时,不应该创建索引。
优点:
第一,通过创建唯一性索引,可以保证数据库表中每一行数据的唯一性。
第二,可以大大加快数据的检索速度,这也是创建索引的最主要的原因。
第三,可以加速表和表之间的连接,特别是在实现数据的参考完整性方面特别有意义。
第四,在使用分组和排序子句进行数据检索时,同样可以显著减少查询中分组和排序的时间。
第五,通过使用索引,可以在查询的过程中,使用优化隐藏器,提高系统的性能。缺点:虽然,索引有许多优点,但是,为表中的每一个列都增加索引,是非常不明智的。这是因为,增加索引也有许多不利的一个方面, 缺点:
第一,创建索引和维护索引要耗费时间,这种时间随着数据量的增加而增加。
第二,索引需要占物理空间,除了数据表占数据空间之外,每一个索引还要占一定的物理空间,如果要建立聚簇索引,那么需要的空间
就会更大。
第三,当对表中的数据进行增加、删除和修改的时候,索引也要动态的维护,这样就降低了数据的维护速度。
29. 介绍下iOS设备获取唯一设备号的历史变迁。
iOS5之后,udid被废弃。
iOS6之后,可以用idfa,不过idfa用户可以在设置—通用—隐私里还原。
iOS7之后,udid被彻底限制。有段时间可以用open udid替代,将其存在剪切板中。但是有缺点,如果用户完全删除了open udid sdk 的app,然后重启设备,就会重新生成新的open udid。
后来open udid停止更新了。
idfa或UUID保存在keychain里。(UUID每次生成都会变)。
30. 如何使用runtime hook一个class的某个方法,又如何hook某个instance的方法?
消息转发里有专门的方法。
31. UIView、CoreAnimation和CoreGraphics的关系。
CoreGraphics:核心图形库,平时使用最频繁的point,size,rect等这些图形,都定义在这个框架中,类名以CG开头的都属于CoreGraphics框架,它提供的都是C语言的函数接口,是可以在iOS和macOS通用的。
CoreAnimation:核心动画,其实就是QuartzCore框架。
CoreGraphics 和 CoreAnimation 的联系:它们都是跨 iOS 和 macOS 使用的,这点区别于UIKit,并且CoreAnimation中大量用到CoreGraphics中的类,原因是显然的,实现动画自然要用到图形库中的东西。
为什么 CAxxx 用的时候好多都要“.CGXXXX”呢?比如:
layer.backgroundColor = [UIColor redColor].CGColor;
首先,图层layer的类型是CALayer,它是CoreAnimation中的类。前面说CoreAnimation是跨平台的,为了跨平台的特性,它的backgroundColor属性就不能使用UIColor类型了,因为UIKit只能使用于iOS,而CoreGraphics框架是跨平台的,所以CALayer类的backgroundColor属性就使用了CGColor类型。所以使用时在赋值前要先进行转换,将UIKit中的东西转换为CoreGraphics中的类型。
32.通过[UIImage imageNamed:]生成的对象什么时候被释放?
用UIImage加载本地图像最常用的是下面三种:
- 用
imageNamed
方法
[UIImage imageNamed:ImageName];
- 用
imageWithContentsOfFile
方法
NSString *thumbnailFile = [NSString stringWithFormat:@"%@/%@.png", [[NSBundle mainBundle] resourcePath], fileName];
UIImage *thumbnail = [UIImage imageWithContentsOfFile:thumbnailFile];
- 用
initWithContentsFile
方法
UIImage *image = [[UIImage alloc] initWithContentsOfFile:filePath]
第一种方法为常见方法,利用它可以方便加载资源图片。用imageNamed的方式加载时,会把图像数据根据它的名字缓存在系统内存中,以提高imageNamed方法获得相同图片的image对象的性能。即使生成的对象被 autoReleasePool释放了,这份缓存也不释放。而且没有明确的释放方法。如果图像比较大,或者图像比较多,用这种方式会消耗很大的内存。优点是加载图片速度快,缺点是耗内存大,适用于小图片并且会被频繁读取的图片。
第二种方法加载的图片是不会缓存的。得到的对象时autoRelease的,当autoReleasePool释放时才释放。优点是图片数据不缓存,节省内存,缺点是加载图片速度相对慢,每次读取图片都要从路径下寻找并解析图片数据。适用于会用到的图片数据不大,且不经常用到的。
第三种方法要手动release掉。不系统缓存。release后立即释放,一般用在封面等图比较大的地方。适用于大图片,且不经常要用的图片。
-
优化方案
这两种方法都有各自的优缺点,那么有什么办法能够把其优点结合起来呢,答案是hook;
首先hook imageNamed
方法,然后在我们自己的方法中进行如下判断:
定义一个本地缓存图片的最大size
判断本地Bundle中是否存在该图片,不存在直接返回到原imageNamed
方法,存在则继续
从缓存中直接读取,如果存在该图片,直接返回,否则继续
判断该图片是否大于size,如果大于则调用原imageWithContentsOfFile
方法返回UIImage
如果该图片不大于size则返回,并存入缓存
接下来hookimageWithContentsOfFile
方法,步骤也是类似,就不多做阐述,直接看代码;
https://elliotsomething.github.io/2016/03/01/iOS-%E4%B9%8B-imageNamed%E5%92%8CimageWithContentsOfFile%E4%BC%98%E5%8C%96/
33. 如何终止正在运行的工作线程?
- NSOperationQueue:
[self.blockOperation cancel]; [self.invocationOperation cancel]; [self.queue cancelAllOperations];
注:设置优先级只针对同一队列中的操作,而且在必须start或者加入队列之前设置才有效果。操作的执行优先级还取决于其依赖关系和添加队列的顺序,如果其依赖的操作未执行完毕或者和其相同优先级的操作在其之前添加到队列,那该操作的执行顺序要延后。
- 暂停和继续: 可以通过以下方法暂停和继续操作队列。
[self.queue setSuspended:YES]; //暂停 [self.queue setSuspended:NO]; //继续
注:暂停一个操作队列不会导致正在执行的操作任务中途暂停,只是简单地阻止调度新操作任务执行。
- GCD:
- 法1:通过dispatch_block_cancel
不过这个方法只能iOS8以后才能用,而且也是只能停止还未开始执行的任务。- 法2:用于标记block是否需要取消,通过if判断是否需要return。
34. 分析下SDWebImage (q3:内部做Decoder的原因 (典型的空间换时间)).
35.你认为开发中那些导致crash?crash的收集和定位bug的方式谈下
- 数组越界。
- 野指针。
- 调用了没有的方法,判断错了类调用了错误的方法。
- 必须实现的代理方法未实现。
- 通知、观察者未移除。
36. Autorelease的原理?ARC的工作原理。
- Autorelease的原理:
- 是由
AutoreleasePoolPage
结构 以双向链表的方式实现的 - 当对象调用
autorelease
方法时,会将对象通过objc_autoreleasepoolPush
函数加入AutoreleasePoolPage
的栈中 - 当自动释放池释放时,调用
objc_autoreleasePoolPop
方法会向栈中的对象发送release
消息。
37. Runloop的原理,它是怎么休眠的?
就是一个do-while循环,很多地方底层都是依赖Runloop,如定时器,自动释放池等。
没有任务的超过一定时间,就会自动进入休眠。
38. 如果页面 A 跳转到 页面 B,A 的 viewDidDisappear 方法和 B 的 viewDidAppear 方法哪个先调用?
viewDidAppear
39. 怎么判断一个cell是否显示在屏幕上?
40.倒计时如何实现 ?
41. 熟悉 CocoaPods 么?能大概讲一下工作原理么?
42. 子线程里创建对象时是否需要创建自动释放池。
需要。
默认主线的运行循环(runloop)是开启的,子线程的运行循环(runloop)默认是不开启的,也就意味着子线程中不会创建autoreleasepool,所以需要我们自己在子线程中创建一个自动释放池。(子线程里面使用的类方法都是autorelease,就会没有池子可释放,也就意味着后面没有办法进行释放,造成内存泄漏。)----在主线程中如果产生事件那么runloop才回去创建autoreleasepool,通过这个道理我们就知道为什么子线程中不会创建自动释放池了,因为子线程的runloop默认是关闭的,所以他不会自动创建autoreleasepool,需要我们手动添加。
43.谈谈常用的设计模式。
单例模式
工厂模式
观察者模式
代理模式
44.KVC的实现原理。KVC是如何通过key找到value的?
setValue:forKey:的搜索方式
- 首先搜索
setKey:
方法。(key
指成员变量名,首字母大写)。- 若未找到
setter
方法,若类的accessInstanceVariablesDirectly
方法返回YES
(默认返回YES
),则按_key
、key
、_isKey
的顺序搜索成员名。- 如果没有找到成员变量,调用
setValue:forUnderfinedKey:
方法(然后应该是抛出异常)。
valueForKey:的搜索方式:
- 首先按
getKey
、key
、isKey
、_key
的顺序查找getter
方法,找到直接调用。如果是BOOL
、int
等内建值类型,会做NSNumber
的转换。- 上面的
getter
没找到,查找countOfKey
、objectInKeyAtIndex:
、keyAtIndexes
格式的方法。如果countOfKey
和另外两个方法中的其中一个找到,那么就会返回一个NSArray
对象。- 还没找到,查找
countOfKey:
、enumeratorOfKey:
、memberOfKey:
格式的方法。如果这三个方法都找到,那么就返回一个NSSet
对象。- 再没找到,调用
valueForUndefinedKey:
方法。
- 注:有的博客说
iskey
、_isKey
也会被调用,但经过我的代码尝试这俩并不行。
setValue:forKeyPath:
该方法能利用运算符一层一层往内部访问属性,如:
NSString *avg= [persons valueForKeyPath:@"Person.@avg.age"];
实现原理就是用Runtime动态的去做上面的事,比如setValue:forKey:
就是先动态的去找有没有setter方法,没有的话就动态的找有没有相关的成员变量,再没有的话就抛出异常。
-
KVC的keyPath中的集合运算符如何使用?
- 必须用在集合对象上或普通对象的集合属性上
- 简单集合运算符有@avg, @count , @max , @min ,@sum,
- 格式
@"@sum.age"
或@"集合属性[.@max.age](mailto:.@max.age)"
45. Block的本质是什么?
简单地说就是把函数当成一个参数进行传值,本质跟NSObject
一样,都是一些结构体定义的,其中也都有isa
指针。
46. 如何在异步下载时候, 取消下载, 保证流量不浪费
用NSOperation
,有cancel
方法。(答案可能不对)
- NSOperationQueue如何开启一个串行队列?
设置最大并发数为1即可。
NSOperation的相关操作链接:
https://www.jianshu.com/p/4b1d77054b35
47.id、NSObject *、id<NSObject>、instancetype的区别。
-
id
: 所有的OC对象都可以用它来表示,但是不一定是NSObject
类,如NSProxy
;另外id
修饰的变量不遵守<NSObject>协议。 -
id
可以调用任意selector
而编译器和IDE不会报错,而NSObject
必须调用NSObject
类声明的方法。 -
instancetype
可以返回和方法所在类相同类型的对象,id
只能返回未知类型的对象。初始化方法返回参数类型时,尽量用instancetype
。 -
instancetype
只能作为返回值,id可以作为参数。
48. Block和函数指针的区别。
49. 如何优化开机速度?
- main函数前优化:
- 删除没用到的framework、类、静态变量、图片等。
- 删减没有被调用到或者已经废弃的方法。
- 尽量不要在load方法里增加方法,可以放到initialize里。
- main函数后优化:
- 不使用xib、sb,直接视用代码加载首页视图。
- NSUserDefaults实际上是在Library文件夹下会生产一个plist文件,如果文件太大的话一次能读取到内存中可能很耗时,不要在这里保存图片。
- 每次用NSLog方式打印会隐式的创建一个Calendar,因此需要删减启动时各业务方打的log,或者仅仅针对内测版输出log。
- 首次加载的控制器的viewDidLoad以及viewWillAppear方法里尽量少做或延时加载。
- 压缩资源图片。
50. App内存你是如何分析的?
51. 怎么完成后期检测, 优化。
个人理解:
- 频繁打开和关闭SQLite,导致内存不断的增长。
SQLite的数据库本质上来讲就是一个磁盘上的文件,频繁打开和关闭是很耗时和浪费资源的,可以设置SQLite的长连接方式;避免频繁的打开和关闭数据库。 - 要大量创建局部变量的时候,可以创建内嵌的autorelease pool来及时释放内存。
52. @property 的本质是什么?ivar、getter、setter 是如何生成并添加到这个类中的。
本质就是:@property = ivar + getter + setter;
自动合成,通过@synthesize自动合成了成员变量和getter和setter方法。再往本质里说,类本身都是一些结构体,里面有成员变量列表、方法列表等一系列参数,自动合成的过程就是往成员列表里、和方法列表里增加相应的内容。