1,KVC的实现原理
当调用[persion setValue:@”lv” forKey:@”name”]的代码时,底层的执行机制如下:
- 程序优先调用setName方法,代码通过setter方法完成设置
- 如果没有找到setName方法,KVC机制会检查+ (BOOL)accessInstanceVariablesDirectly方法有没有返回YES,默认该方法会返回YES,如果你重写了该方法让其返回NO的话,那么在这一步KVC会执行setValue:forUNdefinedKey:方法,不过一般开发者不会这么做。所以KVC机制会搜索该类里面有没有名为name的成员变量,无论该变量是在类接口部分定义,还是在类实现部分定义,也无论用了什么样的访问修饰符,只在存在以命名的变量,KVC都可以对该成员变量赋值。
- 如果该类即没有set:方法,也没有_成员变量,KVC机制会搜索_is的成员变量
- 和上面一样,如果该类即没有set:方法,也没有_和_is成员变量,KVC机制再会继续搜索和is的成员变量。再给它们赋值
- 如果上面列出的方法或者成员变量都不存在,系统将会执行该对象的setValue:forUNdefinedKey:方法,默认是抛出异常(大部分情况下,我们没有重载这个方法,程序是会直接carsh的)
果想让这个类内部禁用KVC,那么重写+ (BOOL)accessInstanceVariablesDirectly方法让其返回NO即可,这样的话如果KVC没有找到set:属性名时,会直接用setValue:forUNdefinedKey:方法。如果返回了YES,且没有实现get方法,再以kvc方式取值会crash
KVC/KVO 进阶(一) 底层原理
2,KVO的实现原理
- KVO是基于runtime机制实现的
- 当某个类的属性对象第一次被观察时,系统就会在运行期动态地创建该类的一个派生类,在这个派生类中重写基类中任何被观察属性的setter 方法。派生类在被重写的setter方法内实现真正的通知机制
- 如果原类为Person,那么生成的派生类名为NSKVONotifying_Person
- 每个类对象中都有一个isa指针指向当前类,当一个类对象的第一次被观察,那么系统会偷偷将isa指针指向动态生成的派生类,从而在给被监控属性赋值时执行的是派生类的setter方法
- 键值观察通知依赖于NSObject 的两个方法: willChangeValueForKey: 和 didChangevlueForKey:;在一个被观察属性发生改变之前, willChangeValueForKey:一定会被调用,这就 会记录旧的值。而当改变发生后,didChangeValueForKey:会被调用,继而 observeValueForKey:ofObject:change:context: 也会被调用。
- 补充:KVO的这套实现机制中苹果还偷偷重写了class方法,让我们误认为还是使用的当前类,从而达到隐藏生成的派生类
IOS-详解KVO底层实现
3,NSRunloop
每个线程(NSThread)对象中内部都有一个run loop(NSRunLoop)对象用来循环处理输入事件,处理的事件包括两类,一是来自Input sources的异步事件,一是来自Timer sources的同步事件;
run Loop在处理输入事件时会产生通知,可以通过Core Foundation向线程中添加run-loop observers来监听特定事件,以在监听的事件发生时做附加的处理工作。
[图片上传失败...(image-4cda68-1532508928146)]
每个run loop可运行在不同的模式下,一个run loop mode是一个集合,其中包含其监听的若干输入事件源,定时器,以及在事件发生时需要通知的run loop observers。运行在一种mode下的run loop只会处理其run loop mode中包含的输入源事件,定时器事件,以及通知run loop mode中包含的observers。
- Default模式
定义:NSDefaultRunLoopMode (Cocoa) kCFRunLoopDefaultMode (Core Foundation)
描述:默认模式中几乎包含了所有输入源(NSConnection除外),一般情况下应使用此模式。 - Connection模式
定义:NSConnectionReplyMode(Cocoa)
描述:处理NSConnection对象相关事件,系统内部使用,用户基本不会使用。 - Modal模式
定义:NSModalPanelRunLoopMode(Cocoa)
描述:处理modal panels事件。 - Event tracking模式
定义:UITrackingRunLoopMode(iOS) NSEventTrackingRunLoopMode(cocoa)
描述:在拖动loop或其他user interface tracking loops时处于此种模式下,在此模式下会限制输入事件的处理。例如,当手指按住UITableView拖动时就会处于此模式。 - Common模式
定义:NSRunLoopCommonModes (Cocoa) kCFRunLoopCommonModes (Core Foundation)
描述:这是一个伪模式,其为一组run loop mode的集合,将输入源加入此模式意味着在Common Modes中包含的所有模式下都可以处理。在Cocoa应用程序中,默认情况下Common Modes包含default modes,modal modes,event Tracking modes.可使用CFRunLoopAddCommonMode方法想Common Modes中添加自定义modes。
iOS中NSRunLoop的模式
NSRunLoop原理详解——不再有盲点
4,多线程
NSThread,GCD,NSOperation
NSThread
阻塞线程的方法
[[NSRunLoop currentRunLoop] addPort:[NSMachPort port] forMode:NSRunLoopCommonModes];
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
GCD
同步任务,异步任务,同步队列,异步队列,分组
面试点:
GCD 控制线程数量
GCD 任务分组
主线程刷新UI
线程锁死
线程安全
信号量:
是一种可用来控制访问资源的数量的标识,设定了一个信号量,在线程访问之前,加上信号量的处理,则可告知系统按照我们指定的信号量数量来执行多个线程。
其实,这有点类似锁机制了,只不过信号量都是系统帮助我们处理了,我们只需要在执行线程之前,设定一个信号量值,并且在使用时,加上信号量处理方法就行了。
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);//创建
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);//等待降低信号量
dispatch_semaphore_signal(semaphore);//增加信号量
######dispatch_barrier_async
注意事项
NSOperation
NSOperation、NSOperationQueue 是苹果提供给我们的一套多线程解决方案。实际上 NSOperation、NSOperationQueue 是基于 GCD 更高一层的封装,完全面向对象。但是比 GCD 更简单易用、代码可读性也更高。
为什么要使用 NSOperation、NSOperationQueue?
可添加完成的代码块,在操作完成后执行。
添加操作之间的依赖关系,方便的控制执行顺序。
设定操作执行的优先级。
可以很方便的取消一个操作的执行。
使用 KVO 观察对操作执行状态的更改:isExecuteing、isFinished、isCancelled。
iOS多线程:『NSOperation、NSOperationQueue』详尽总结
iOS多线程全套:线程生命周期,多线程的四种解决方案,线程安全问题,GCD的使用,NSOperation的使用
5,锁
iOS 各种锁机制
- @synchronized关键字加锁
- dispatch_semaphore信号量锁
- NSLock
- NSRecursiveLock递归锁
- NSConditionLock条件锁
- NSCondition
总的来说:
OSSpinLock和dispatch_semaphore的效率远远高于其他。
@synchronized和NSConditionLock效率较差。
鉴于OSSpinLock的不安全,所以我们在开发中如果考虑性能的话,建议使用dispatch_semaphore。
如果不考虑性能,只是图个方便的话,那就使用@synchronized。
互斥锁属性PTHREAD_MUTEX_RECURSIVE
探讨iOS开发中各种锁
6. Block为什么用copy修饰
Block属性的声明,首先需要用copy修饰符,因为只有copy后的Block才会在堆中,栈中的Block的生命周期是和栈绑定的
<栈 :由系统维护的局部变量 是存在栈上的,其生命周期随函数的生命周期>
<堆 :由程序员申请空间地址,由程序员手动释放,生命周期受到程序员控制>
使用retain也可以,因为block的retain行为默认是用copy的行为实现的,block变量默认是声明为栈变量的,为了能够在block的声明域外使用,所以要把block拷贝(copy)到堆,所以说为了block属性声明和实际的操作一致,最好声明为copy。
block作为类的属性时用copy
7. 静态库和动态库
库是共享程序代码的方式,一般分为静态库和动态库;库实现了iOS程序的模块化,将某些特定的功能模块化为库的格式方便分享和使用!
2.静态库和动态库有什么特点?
异同点:
静态库:链接时完整地拷贝至可执行文件中,被多次使用就有多份冗余拷贝。
动态库:链接时不复制,程序运行时由系统动态加载到内存,供程序调用,系统只加载一次,多个程序可以共用,节省内存。
共同点:
静态库和动态库都是闭源库,只能拿来满足某个功能的使用,不会暴露内部具体的代码信息,而从github上下载的第三方库大多是开源库
3.这两种库都有哪些文件格式?
静态库:.a和.framework
动态库:.dylib和.framework(系统直接提供给我们的framework都是动态库!)
作者:公子墨香
链接:https://www.jianshu.com/p/c8366e4f9378
來源:
著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处。
8. RunTime
常用应用:
- 发送消息
- 交换方法
- 动态添加方法
- 给分类添加属性(类关联)
runtime机制和使用
从runtime开始: 理解面向对象的类到面向过程的结构体
从runtime开始: 深入理解OC消息转发机制
从runtime开始: 理解OC的属性property
从runtime开始: 实践Category添加属性与黑魔法method swizzling
从runtime开始: 深入weak实现机理
9. main()之前的过程有哪些?
APP启动时间由2部分组成
- main之前的系统dylib(动态链接库)和自身App可执行文件的加载的时间
- main之后执行didFinishLaunchingWithOptions:结束前的时间
- 系统先读取App的可执行文件(Mach-O文件),从里面获得dyld的路径,然后加载dyld,dyld去初始化运行环境。
- 开启缓存策略,加载程序相关依赖库(其中也包含我们的可执行文件),并对这些库进行链接,最后调用每个依赖库的初始化方法,在这一步,runtime被初始化。
- 当所有依赖库的初始化后,轮到最后一位(程序可执行文件)进行初始化,在这时runtime会对项目中所有类进行类机构初始化,然后调用所有的load方法。最后dyld返回main函数地址,main函数被调用,我们便来到程序入口main函数。
10. OC的反射机制
反射机制的概念:
对于任意一个类,都能够知道这个类的都有属性和方法
对于任意一个对象,都能够调用它的任意一个方法和属性
这种动态获取的信息以及动态调用对象的方法的 功能成为Java语言的反射机制。
oc反射机制有三个用途:
1.获得Class
2.检查继承关系
3.动态的调用方法
- 直接通过方法名来调用。[person show];
- 利用performSelector.
- NSInvocation来调用.
11. block的实质是什么?一共有几种block?都是什么情况下生成的?
block:本质就是一个object-c对象.
block:存储位置,可能分为3个地方:代码区,堆区、栈区(ARC情况下会自动拷贝到堆区,因此ARC下只能有两个地方:代码区、堆区)
代码区:不访问栈区的变量(如局部变量),且不访问堆区的变量(alloc创建的对象),此时block存放在代码区。
堆区:访问了处于栈区的变量,或者堆区的变量,此时block存放在堆区。–需要注意实际是放在栈区,在ARC情况下会自动拷贝到堆区,如果不是ARC则存放在栈区,所在函数执行完毕就回释放,想再外面调用需要用copy指向它,这样就拷贝到了堆区,strong属性不会拷贝、会造成野指针错区。
2.为什么在默认情况下无法修改被block捕获的变量? __block都做了什么?
默认情况下,block里面的变量,拷贝进去的是变量的值,而不是指向变量的内存的指针。
使用__block修饰后的变量,拷贝到block里面的就是指向变量的指针,所以我们就可以修改变量的值。
12. 造成内存泄漏的可能的原因?
第三方框架不正当使用。
block,delegate,NSTimer循环使用。
非oc对象的内存处理。
地图类处理。
大次数循环内存暴涨。
非oc对象的释放:
例如使用CGImageRelease(ref)方法释放内存;
CoreFoundation框架下的某些对象或者变量需要手动释放,c语言中malloc需要free;
地图类内存释放:
在使用完毕之后注意将地图及其相关代理释放,地图中大头针需正确复用,并使用完成之后清空标注;
13. 说一下线程之间的通信。
14. NSDictionary的实现原理是什么?
NSDictionary(字典)是使用 hash表来实现key和value之间的映射和存储的,
15. 你们的App是如何处理本地数据安全的(比如用户名的密码)?
不要再移动端存储密码,要存储token,存的话也要先加密再存。
键盘缓存输入补充。应用快照缓存。
16. 什么是指针常量和常量指针?
17. 不借用第三个变量,如何交换两个变量的值?要求手动写出交换过程。
18. 说一下runloop和线程的关系。
19. 说一下autoreleasePool的使用场景
20. 说一下简单工厂模式,工厂模式以及抽象工厂模式?
21. UITableViewCell的卡顿你是怎么优化的?
1.避免cell的重新布局
cell的布局填充等操作 比较耗时,一般创建时就布局好
如可以将cell单独放到一个自定义类,初始化时就布局好
2.提前计算并缓存cell的属性及内容
3.减少cell中控件的数量
尽量使cell得布局大致相同,不同风格的cell可以使用不用的重用标识符,初始化时添加控件,
- 加载网络数据,下载图片,使用异步加载,并缓存
- 使用不透明视图。
不透明的视图可以极大地提高渲染的速度。因此如非必要,可以将table cell及其子视图的opaque属性设为YES(默认值)。
22. 说一下静态库和动态库之间的区别
静态库和动态库是相对编译期和运行期的:静态库在程序编译时会被链接到目标代码中,程序运行时将不再需要改静态库;而动态库在程序编译时并不会被链接到目标代码中,只是在程序运行时才被载入,因为在程序运行期间还需要动态库的存在。
静态库:在链接时会被完整的复制到可执行文件中,被多次使用就会进行多次的拷贝,也就是说你有10个项目就要拷贝10次,静态库与 app 可执行文件 一起被加载到同一块代码区中。
动态库:链接的时候是不复制,动态库的代码是在可执行程序运行时才会被载入内存的,在编译过程中仅简单的引用,因此代码体积较小。系统只加载一次,多个程序会共用,在ios中也需要系统的支持。
静态库 好处:
模块化,分工合作,提高了代码的复用及核心技术的保密程度
避免少量改动经常导致大量的重复编译连接
也可以重用,注意不是共享使用
动态库 好处:
使用动态库,可以将最终可执行文件体积缩小,将整个应用程序分模块,团队合作,进行分工,影响比较小
使用动态库,多个应用程序共享内存中得同一份库文件,节省资源
使用动态库,可以不重新编译连接可执行程序的前提下,更新动态库文件达到更新应用程序的目的。
应用插件化
软件版本实时模块升级
23. load方法和initalize方法有什么区别和共同点
load和initialize都会在实例化对象之前调用,以main函数为分水岭,前者是在main函数之前,后者是在main函数之后。
load和initialize方法都不会显示的调用父类的方法而是自动调用,即使子类没有initialize方法也会调用父类的方法,load方法不会调用父类。
load和initialize方法内部使用了锁,因此他们是线程安全的,实现时要尽可能简单,避免线程阻塞,不要再次使用锁。
load方法常用来method swizzle,initialize常常用于初始化全局变量和静态变量.
24. NSNotificationCenter是在哪个线程发送的通知?
取决于发送通知时所在的线程,线程不安全
25. 为什么一定要在主线程里面更新UI?
为什么都要在主线程中更新UI
GUI为了性能(不知道GUI的可以自己查一下),故意让你只能在一个线程里面操作的,多线程操作一个UI,很容易导致,或者极其容易导致反向加锁和死锁问题。其实不光是GUI,同样的道理在几乎所有编程领域里都是这样的,这背后是线程同步的开销问题。
通俗的讲显然两个线程不能同时draw,否则屏幕会花;不能同时insert map,否则内存会花;不能同时write buffer,否则文件会花。需要互斥,比如锁。结果就是同一时刻只有一个线程可以做UI。那么当两个线程互斥几率较大时,或者保证互斥的代码复杂时,选择其中一个长期持有其他发消息就是典型的解决方案。所以普遍的要求UI只能单线程。
26. 如何让自己的类用 copy 修饰符?如何重写带 copy 关键字的 setter?
遵守NSCopying协议,实现- (id)copyWithZone:(NSZone *)zone
- (void)setName:(NSString *)name
{
if (_name != name) {
_name = [name copy];
}
}
27. property 的本质是什么?ivar、getter、setter 是如何生成并添加到这个类中的.
@property的本质 = ivar (实例变量) + getter (取方法) + setter (存方法)
“属性”(property)有两大概念:实例变量(ivar)、存取方法(getter + setter)
ivar、 getter 、setter 是如何生成并添加到这个类中的
这是编译器自动合成的,通过@synthesize 关键字指定,若不指定,默认为@synthesize propertyName = _propertyName;若手动实现了getter/setter 方法,则不会自动合成。
现在编译器已经默认为我们添加了@synthesize propertyName = propertyName;因此不再手动添加了,除非你真的要改变成员变量名字。
生成getter方法时,会判断当前属性名是否有“”,比如声明属性为@property(nonatomic,copy)NSString *_name;那么所生成的成员变量名就会变成“name”,如果我们要手动生成getter 方法,就要判断是否以“”开头了。
28. 单个viewController的生命周期
initWithCoder:(NSCoder *)aDecoder:(如果使用storyboard或者xib)
loadView:加载view
viewDidLoad:view加载完毕
viewWillAppear:控制器的view将要显示
viewWillLayoutSubviews:控制器的view将要布局子控件
viewDidLayoutSubviews:控制器的view布局子控件完成
这期间系统可能会多次调用viewWillLayoutSubviews 、 viewDidLayoutSubviews 俩个方法
viewDidAppear:控制器的view完全显示
viewWillDisappear:控制器的view即将消失的时候
这期间系统也会调用viewWillLayoutSubviews 、viewDidLayoutSubviews 两个方法
viewDidDisappear:控制器的view完全消失的时候
29. Cocopods
让自己项目支持coocopods:
- 创建配置podspec文件。
- 创建tag,并推送到github(podspec会解析为podspec.json)。
- 验证podspec文件。
安装cocopods:
1,sudo gem install cocoapods
2,pod setup (https://github.com/CocoaPods/Specs所有库信息download到本地)
使用cocopods:
pod search 从本地的库中搜索相关库信息
pod install
pod update
原理:
- 它是将所有的依赖库都放到另一个名为 Pods 项目中
- Pods 项目最终会编译成一个名为 libPods.a 的文件,主项目只需要依赖这个 .a 文件即可。这样,依赖库源码管理工作都从主项目移到了 Pods 项目中。
核心组件:
深入理解 CocoaPods
CocoaPods是用 Ruby 写的,并由若干个 Ruby 包 (gems) 构成的。在解析整合过程中,最重要的几个 gems 分别是: CocoaPods/CocoaPods, CocoaPods/Core, 和 CocoaPods/Xcodeproj (是的,CocoaPods 是一个依赖管理工具 -- 利用依赖管理进行构建的!)。
pod install步骤 :
- 读取 Podfile 文件
- 版本控制和冲突
- 加载源文件
- 生成 Pods.xcodeproj
- 安装第三方库
30. SDWebImage:
主要加载的类:
- SDWebImageManager
- SDImageCache:memCache,diskCachePath
- SDWebImageDownloader:
NSOperationQueue *downloadQueue,
NSMutableDictionary*URLOperations;