数据结构、算法和网络结构
这两个类别的面试题请参考算法 、网络 程序员
面向对象的基础题
面向对象的几个设计原则?面试
SOLID原则(更新:据朋友提醒应为七个)
Single Responsibility Principle(单一原则)
Open Close Principle(开闭原则)
Liskov Substitution Principle(里氏替换原则)
Interface Segregation Principle(接口分离原则)
Dependency Inversion Principle(依赖倒置原则)
Law of Demeter(Leaset Knowledge Principle)迪米特法则(最少知道原则)
Composite Reuse Principle(合成复用原则)
Hash表的实现?算法
经过把关键码值(key)映射到表中的一个位置来访问记录,Hash实现的关键是散列函数和冲突解决(链地址法和开放定址法)。
什么是进程和线程?有什么区别?数据库
进程:是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位。
线程:操做系统可以进行运算调度的最小单位
区别:线程被包含在进程之中,是进程中的实际运做单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中能够并发多个线程,每条线程并行执行不一样的任务。
内存的几大区域?各自的职能?编程
栈区:由编译器自动分配和释放,通常存放函数的参数值,局部变量等。
堆区:由程序员分配和释放,若不释放,则程序结束由操做系统回收。
全局区(static):由编译器管理(分配和释放),程序结束由系统释放。全局变量和静态变量(相邻两块区,初始化和未初始化)。
文字常量区:由编译器管理(分配释放),程序结束由系统释放;存放常量字符。
程序代码区:存放函数的二进制代码。
架构、框架和设计模式的区别?swift
架构:一种顶层归纳性的设计概念,像是蓝图,将不一样的需求抽象为具体组件并使组件互相通讯。
框架:软件框架是提取特定领域软件的共性部分造成的体系结构,不一样领域的软件项目有不一样的框架类型。
设计模式:一套被反复使用、多数人知晓、通过分类编目的总结,代码设计的一个成果,能够用在不一样软件框架中一种实际问题的解决方案。(在软件开发中总结出的一种适合解决某种问题的方法)
MVC、MVVM和MVP架构的不一样?设计模式
MVC
【优势】简单易上手、没有多层调用
【缺点】耦合性强;不利于单元测试,Controller担负了太多的事件处理,处理用户操做,管理View的声明周期,API接口调用,处理错误,处理回调,监听通知,处理视图方向等,容易形成臃肿,维护不方便
MVP
【优势】利于单元测试、明确责任分配
【缺点】要写一些额外的代码(如绑定),若业务逻辑复杂,也会形成Presenter的臃肿
MVVM
【优势】责任划分明确;逻辑清晰;可测性高;双向绑定,配合第三方开源库使用方便
【缺点】(1)数据绑定使得Bug很难被定位;(2)对于过大的项目,数据绑定须要花费更多的内存;依赖于开源库实现双向绑定,学习起来比较复杂;
iOS基础面试题
UI
UIView和CALayer的区别?
UIView 和 CALayer 都是 UI 操做的对象。二者都是 NSObject 的子类,发生在 UIView 上的操做本质上也发生在对应的 CALayer 上。
UIView 是 CALayer 用于交互的抽象。UIView 是 UIResponder 的子类( UIResponder 是 NSObject 的子类),提供了不少 CALayer 所没有的交互上的接口,主要负责处理用户触发的种种操做。
CALayer 在图像和动画渲染上性能更好。这是由于 UIView 有冗余的交互接口,并且相比 CALayer 还有层级之分。CALayer 在无需处理交互时进行渲染能够节省大量时间。
CALayer的动画要经过逻辑树、动画树和显示树来实现
loadView是干吗用的?
loadView用来自定义view,只要实现了这个方法,其余经过xib或storyboard建立的view都不会被加载 。
layoutIfNeeded、layoutSubviews和setNeedsLayout的区别?
layoutIfNeeded:方法调用后,在主线程对当前视图及其全部子视图当即强制更新布局。
layoutSubviews:方法只能重写,咱们不能主动调用,在屏幕旋转、滑动或触摸界面、子视图修改时被系统自动调用,用来调整自定义视图的布局。
setNeedsLayout:方法与layoutIfNeeded类似,不一样的是方法被调用后不会当即强制更新布局,而是在下一个布局周期进行更新。
iOS的响应链?什么状况会影响响应链?
事件UIResponder:继承该类的对象才能响应
事件处理:touchesBegain、touchesMoved、touchesEnded、touchesCancelled;
事件响应过程:
UIApplication(及delegate)接收事件传递给 keyWindow
keyWindow遍历subViews的hitTest:withEvent:方法和pointInside方法找到点击区域内的视图并处理事件
UIView的子视图也会遍历其hitTest:withEvent:方法,以此类推,直到找到点击区域内最上层的视图,将视图逐步返回给UIApplication
最后找到的视图成为第一响应者,若没法响应,调用其nextResponder方法,一直找到响应链中能处理该事件的对象(据朋友提醒:UIControl子类不适用)。
最后到Application依然没有能处理该事件的对象的话,就废弃该事件;
关键是hitTest:withEvent:方法和pointInside方法
⚠️ 如下几种状况会忽略,hidden为YES,alpha小于等于0.01,userInteractionEnabled为NO,
UIControl的子类和UIGestureRecognizer优先级较高,会打断响应链;
说几种给UIImageView添加圆角的方式?
cornerRadius(iOS9以后不会致使离屏渲染)
CoreGraphic绘制
UIBezierPath(本质同上)
iOS有哪些实现动画的方式?
UIView Animation:系统提供的基于CALayer Animation的封装,能够实现平移、缩放、旋转等功能。
CALayer Animation:底层CALayer配合CAAnimation的子类,能够实现更复杂的动画
UIViewPropertyAnimator:iOS10后引入的用于处理交互的动画,能够实现UIView Animation的全部效果。
使用drawRect有什么影响?
处理touch事件时会调用setNeedsDisplay进行强制重绘,带来额外的CPU和内存开销。
OC基础
iOS的内存管理机制?
引用计数,从MRC(Manuel Reference Count)到ARC(Automatic Reference Count)。(最好能说明对象的生命周期)
@property后的相关修饰词有哪些?
原子性:
nonatomic:非原子性,编译器不会对其进行加锁(同步锁的开销较大)
atomic:原子性(默认),编译器合成的方法会经过锁定机制确保原子性(非线程安全)
读写权限:
readwrite:可读写(默认),若不用@dynamic修饰编译器会自动为其生成setter和getter方法,不然编译器不生成由用户本身实现
readonly:只读,若不用dynamic修饰仅生成getter方法,不然编译器不生成getter方法 有用户来实现
内存管理语义:
strong:修饰引用类型(表示持有当前实例),保留新值释放旧值,若修饰IMMutable不可变类型建议用copy
copy:指修饰不可变集合类型、AttributeString、Block(ARC下strong和copy同样,把栈中的Block拷贝到堆中,copy表示不须要copy操做了)(设置方法不保留新值而是将其copy,不可变类型的可变类型子类)
weak:修饰引用类型(非持有关系),不保留新值,不释放旧值(属性所指对象被摧毁时,属性值也会清空置nil)
assign:修饰基本数据类型(也能够修饰引用类型,但可能会产生野指针),执行简单赋值操做
unsafe_unretained:修饰引用类型(也能够修饰基本类型),所指对象被摧毁时,属性值不会被清空(unsafe)
方法名:
getter=指定方法名
setter(不经常使用)
dynamic和synthesis的区别?
dynamic:告诉编译器不要帮我自动合成setter和getter方法,本身来实现,若没有实现,当方法被调用时会致使消息转发。
synthesis:指定实例变量的名字,子类重载父类属性也须要synthesis(从新set和get、使用dynamic,在Protocol定义属性、在category定义属性,默认不会自动合成)。
array为什么用copy修饰?mutableArray为什么用strong修饰?
array:若用strong修饰,在其被赋值可变的子类后,内容可能会在不知不觉中修改,用copy防止被修改。
mutableArray若用copy修饰会返回一个NSArray类型,若调用可变类型的添加、删除、修改方法时会由于找不到对应的方法而crash。
深拷贝和浅拷贝(注意NSString类型)?
NSString:strong和copy修饰的属性是同等的,指向同一个内存地址,mutableCopy才是内存拷贝;
NSMutableString:strong和copy不一样,strong指向同一个内存地址,copy则会进行内存拷贝,mutableCopy也会进行内存拷贝;
NSArray:对于字符类型和自定义对象结果是不一样的,strong和copy都指向相同内存地址,mutableCopy也仅仅是指针拷贝;可是mutableCopy能够把Array变成MutableArray
PS:copy产生一个不可变类型,mutableCopy产生一个可变类型;对于字符类型mutableCopy会拷贝字符,copy对于NSArray是不拷贝的,对于NSMutableArray 是拷贝的;对于对象类型,仅仅是指针拷贝;(建议手动试试)
Block的几种类型?
_NSConcreteGlobalBlock:全局Block也是默认的block类型,一种优化操做,私有和公开,保持私有可防止外部循环引用,存储在数据区,当捕获外部变量时会被copy到堆上
_NSConcreteStackBlock:栈Block,存储在栈区,待其做用域结束,由系统自动回收
_NSConcreteMallocBlock:堆Block,计数器为0 的时候销毁
isEqual和“==”的区别?
==:比较两个指针自己,而不是其所指向的对象。
isEqual:当且仅当指针值也就是内存地址相等;若重写该方法则要保证isEqual相等则hash相等,hash相等isEqual不必定相等;若指针值相等则相等,值不相等就判断对象所属的类,不属于同一个类则不相等,同属一个类时再判断每一个属性是否相等。
id和NSObject的区别?
id:指向对象的指针,编译时不作类型检查,运行时向其发送消息才会对其检查。
NSObject:NSObject类及其子类,编译时作类型检查,向其发送的消息没法处理时就会执行消息转发。
id<NSObject >:编译器不对其作类型检查,对象所属的类默认实现名为NSObject的Protocol,既能响应方法又不对其作类型检查.
通知、代理、KVO和Block的不一样(结合应用场景回答)?
通知: 适用于毫无关联的页面之间或者系统消息的传递,属于一对多的信息传递关系。例如接收系统音量、系统状态、键盘等,应用模式的设置和改变,都比较适合用通知去传递信息。
代理: 一对一的信息传递方式,适用于相互关联的页面之间的信息传递,例如push和present出来的页面和原页面之间的信息传递。
block: 一对一的信息传递方式,效率会比代理要高(毕竟是直接取IMP指针的操做方式)。适用的场景和代理差很少,都是相互关联页面之间的页面传值。
KVO:属性监听,监听对象的某一属性值的变化情况,当须要监听对象属性改变的时候使用。例如在UIScrollView中,监听contentOffset,既能够用KVO,也能够用代理。可是其余一些状况,好比说UIWebView的加载进度,AVPlayer的播放进度,就只能用KVO来监听了,不然获取不到对应的属性值。
什么是循环引用?__weak、__strong和__block的区别?
循环引用:两个对象互相持有对方,一个释放须要另一个先释放,delegate的weak声明就是为了防止循环引用。(通常指双向强引用,单向强引用不须要考虑,如:UIView动画Block)
__weak:Block会捕获在Block中访问Block做用域外的实例,这样会有内存泄漏的风险,用__weak修饰表示在Block不会致使该实例引用计数器加1,也能够在Block执行结束后强制将Block置nil,这样Block捕获的实例也会跟着释放,若是捕获的仅是基本数据类型,Block只会对其值进行拷贝一份,此时值再怎么变化也不会影响Block内部的操做。
__strong:在Block中使用__weak修饰的实例很容易被释放,因此须要加锁判断是否释放,未释放则对其进行强引用持有,保证向该实例发送消息的时候不会致使崩溃
__block:Block默认不容许修改外部变量的值,以int类型为例,若在Block中访问变量就把该变量进行Copy一份保存到Block函数内,而后变量在Block外部不管怎么改变都不会影响Block中使用的变量的值,若在Block改变外部变量的值,变量必需要用__block修饰,Block是把该变量的栈内存地址拷贝到堆中,因此能够直接把改变的新值写入内存。(把栈中变量的指针地址拷贝到堆中)
内存泄漏、野指针和僵尸对象的区别?
内存泄露:在堆中申请的再也不使用的内存没有释放,程序结束释放(Analyzer,Leaks)
野指针:指向的内存已经被释放,或被系统标记为可回收(用Malloc Scribble调试)。
僵尸对象:已经被释放的对象,指针指向的内存块认为你无权访问或它没法执行该消息。(EXC_Bad_Access,开启NSZombieEnabled检测)
nil、Nil、NULL、NSNull的区别?
nil:空实例对象(给对象赋空值)
Nil:空类对象(Class class = Nil)
NULL:指向C类型的空指针
NSNull:类,用于空对象的占位符(用于替代集合中的空对象,还有判断对象是否为空对象)
static和const的区别?
const:声明全局只读变量,(若前面没有static修饰,在另一个文件中声明同名的常量会报错)
static:修饰变量的做用域(本文件内),被修饰的变量只会分配一分内存,在上一次修改的基础上进行修改。
通常二者配合使用,如:static const NSTimeInterval kAnimationDuration = 1.0;
不会建立外部符合,编译时预处理指令会把变量替换成常值。
iOS中有哪些设计模式?
【单例】保证应用程序的生命周期内仅有一个该类的实力对象,易于外界访问。如:UIApplication、NSBundle、NSNotificationCenter、NSFileManager、NSUserDefault、NSURLCache等;
【观察者】定义了一种一对多的依赖关系,可让多个观察者同时监听一个主题对象,当主题对象状态或值发生改变,会通知全部的观察者;KVO当对象属性变化时,通知观察此属性的对象。案例表明:通知和KVO
【类簇】(隐藏抽象基类背后的实现细节)如:UIButton、NSNumber、NSData、NSArray、NSDictionary、NSSting。用isMemberOfClass和isKindOfClass来判断。
【命令模式】(运行时能够调用任意类的方法),表明:NSInvocation,封装一个请求或行为做为对象,包含选择器、方法名等。
【委托模式】“我想知道列表中被选中的内容在第几行”?能够,接受个人委托就能够知道;只是接受个人委托就要帮我完成这几件事情,有必需要完成的,有没必要要完成的,至于你怎么完成我就不关心了。
【装饰器模式】:装饰器模式在不修改原来代码的状况下动态的给对象增长新的行为和职责,它经过一个对象包装被装饰对象的方法来修改类的行为,这种方法能够作为子类化的一种替代方法。 案例表明:Category和Delegation
静态库和动态库的区别?
静态库:.a(.h配合)和.framework(资源文件.bundle)文件,编译好的二进制代码,使用时link,编译时会拷贝一份到target程序中,容易增长target体积。
动态库:.tbd和.dylib,程序运行时动态加载到内存,可共享使用,动态载入有性能损失且有依赖性(系统直接提供给的framework都是动态库!)
iOS中内省的几个方法?
内省是指对象在运行时将其自身细节泄露为对象的能力。 这些细节包括对象在继承树中的位置,它是否符合特定的协议,以及它是否响应某个特定的消息。以及它是否响应某一消息。
class和superclass方法
isKindOfClass:和isMemberOfClass:
respondsToSelector:
conformsToProtocol:
isEqual:
OC进阶
Foundation和CoreFoundation的转换?数组
__bridge:负责传递指针,在OC和CF之间转换,不转移管理权,若把OC桥接给CF,则OC释放后CF也没法使用。
__bridge_retained:将OC换成CF对象,并转移对象全部权,同时剥夺ARC的管理权,以后须要使用CFRelease释放。
__bridge_transfer:将CF转换成OC,并转移对象全部权,由ARC接管,因此不须要使用CFRelease释放。
array和set的区别?查找速度和遍历速度谁更快?缓存
array:分配的是一片连续的存储单元,是有序的,查找时须要遍历整个数组查找,查找速度不如Hash。
set:不是连续的存储单元,且数据是无序的,经过Hash映射存储的位置,直接对数据hash便可判断对应的位置是否存在,查找速度较快。
遍历速度:array的数据结构是一片连续的内存单元,读取速度较快,set是不连续的非线性的,读取速度较慢;
什么是内联函数?为何须要它?安全
用inline修饰的函数,不必定是内联函数,而内联函数必定是inline修饰的。
普通函数:编译会生成call指令(入栈、出栈),调用时将call指令地址入栈,并将子程序的起始地址送入,执行完毕后再返回原来函数执行的地址,因此会有必定的时间开销。
#define宏定义和inline修饰的函数代码被放入符号表中,使用时直接替换,没有调用的开销;#define宏定义的函数:使用时从符号表替换,有格式要求,不会对参数做有效性检查且返回值不能强转;inline修饰的内联函数:不须要预编译,使用时直接替换且做类型检查,是一段直接在函数中展开的代码(宏是直接文本替换)
PS:inline定义的内联函数代码被放入符号表中,在使用时直接替换(把代码嵌入调用代码),不像普通函数须要在函数表中查找,省去压栈和出栈,没有了调用的开销,提升效率;
PS:inline修饰的内联函数只是向编译器申请,最后不必定按照内联函数的方式执行,可能申请会失败,由于函数内不容许使用循环或开关语句,太大会被编译器还原成普通函数;inline关键词必定要与函数定义一块儿使用,声明的话不算内联函数;
图片显示的过程?
假设咱们使用 +imageWithContentsOfFile: 方法从磁盘中加载一张图片,这个时候的图片并无解压缩;
而后将生成的 UIImage 赋值给 UIImageView ;
接着一个隐式的 CATransaction 捕获到了 UIImageView 图层树的变化;
在主线程的下一个 run loop 到来时,Core Animation 提交了这个隐式的 transaction ,这个过程可能会对图片进行 copy 操做,而受图片是否字节对齐等因素的影响,这个 copy 操做可能会涉及如下部分或所有步骤:
分配内存缓冲区用于管理文件 IO 和解压缩操做;
将文件数据从磁盘读到内存中;
将压缩的图片数据解码成未压缩的位图形式,这是一个很是耗时的 CPU 操做;
最后 Core Animation 使用未压缩的位图数据渲染 UIImageView 的图层。(必需要了解位图相关的知识点)
dispatch_once如何只保证只执行一次?
多线程中,如有一个线程在访问其初始化操做,另一个线程进来后会延迟空待,有内存屏障来保证程序指令的顺序执行
void dispatch_once_f(dispatch_once_t *val, void *ctxt, void (*func)(void *)){
volatile long *vval = val;
if (dispatch_atomic_cmpxchg(val, 0l, 1l)) {
func(ctxt); // block真正执行
dispatch_atomic_barrier();
*val = ~0l;
}
else
{
do
{
_dispatch_hardware_pause();
} while (*vval != ~0l);
dispatch_atomic_barrier();
}
}
复制代码
NSThread、NSRunLoop和NSAutoreleasePool三者之间的关系?
根据官方文档中对 NSRunLoop 的描述,咱们能够知道每个线程,包括主线程,都会拥有一个专属的 NSRunLoop 对象,而且会在有须要的时候自动建立。
根据官方文档中对NSAutoreleasePool的描述,咱们可知,在主线程的 NSRunLoop 对象(在系统级别的其余线程中应该也是如此,好比经过 dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)
获取到的线程)的每一个 event loop 开始前,系统会自动建立一个 autoreleasepool ,并在 event loop 结束时 drain 。
须要手动添加autoreleasepool的状况:
若是你编写的循环中建立了大量的临时对象;
若是你建立了一个辅助线程
分类可扩展的区别?(可从内存布局、加载顺序、分类方法和原类方法的执行顺序来回答)
extension:必须在.m中添加,编译时做为类的一部分参与内存布局,生命周期与类同样,添加私有实现,隐藏类的私有信息。
category:独立的.h和.m文件,独立编译动态加载dyld,为已有类扩展功能。
分类局限:没法添加实例变量,能够添加属性但不会为其生成实例变量(在运行时,对象的内存布局已肯定,若添加实例变量就会破坏类的内存布局,这对编译型语言来讲是灾难性的)
分类的加载:把实例方法、属性和协议添加到类上,如有协议和类方法则添加到元类, 运行时分类的方法列表被动态添加到类的方法列表中,且在类的原有方法前面;
方法顺序:【load方法】先调用父类的load方法再调用分类的,【重写的方法】因为分类方法在原类方法的前面,因此优先调用分类中的方法。
OC对象释放的流程?runloop的循环周期会检查引用计数,释放流程:release->dealloc->dispose
release:引用计数器减一,直到为0时开始释放
dealloc:对象销毁的入口
dispose:销毁对象和释放内存
objc_destructInstance:调用C++的清理方法和移除关联引用
clearDeallocating:把weak置nil,销毁当前对象的表结构,经过如下两个方法执行(二选一)
sidetable_clearDeallocating:清理有指针isa的对象
clearDeallocating_slow:清理非指针isa的对象
free:释放内存
CDDisplayLink和NSTimer的区别?
CADisplayLink:以和屏幕刷新率相同的频率将内容画到屏幕上的定时器,需手动添加runloop
NSTimer:可自动添加到当前线程的runloop,默认defualt模式,也能够选择添加的loopMode;
用runtime实现方法交换有什么风险?
风险1:若不在load中交换是非原子性的,在initial方法中不安全
风险2:重写父类方法时,大部分都是须要调用super方法的,swizzling交换方法,若不调用,可能会出现一些问题,如:命名冲突、改变参数、调用顺序、难以预测、难以调试。
runtime源码相关
知道AutoreleasePoolPage吗?它是怎么工做的?
AutoreleasePool由AuthReleasePoolPage实现,对应AutoreleasePoolPage 的具体实现就是往AutoreleasePoolPage中的next位置插入一个POOL_SENTINEL,而且返回插入的POOL_SENTINEL的内存地址。这个地址也就是咱们前面提到的 pool token,在执行pop操做的时候做为函数的入参。
经过调用 autoreleaseFast函数来执行具体的插入操做;autoreleaseFast函数在执行一个具体的插入操做时,分别对三种状况进行了不一样的处理:
当前 page存在且没有满时,直接将对象添加到当前page中,即next指向的位置;
当前page存在且已满时,建立一个新的page,并将对象添加到新建立的 page中,而后关联child page。
当前page不存在时,即尚未page时,建立第一个page,并将对象添加到新建立的page中。
KVO的底层实现?(看过RAC源码的应该知道,RAC监听方法也是基于此原理,只是稍微有些不一样)
当一个object有观察者时,动态建立这个object的类的子类
对每一个被观察的property,重写其setter方法
在重写的setter方法中调用willChangeValueForKey:和didChangeValueForKey通知观察者
当一个property没有观察者时,重写方法不会被删除,直到移除全部的观察者才会删除且删除动态建立的子类。
// 1)监听前的准备
[human addObserver:self for KeyPath:@"name" options:NSKeyValueObservingOptionNew context:nil];
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context { /* 监听后的处理 */ }
// 2)关闭系统观察者的自动通知
+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key {
if ([key isEqualToString:@"name" ]) {
return NO;
}
return [super automaticallyNotifiesObserversForKey:key];
}
// 3)调用这两个方法
[self willChangeValueForKey:@"name" ];
/* 在此对值进行修改 */
[self didChangeValueForKey:@"name”]; 复制代码
被weak修饰的对象是如何被置nil的?知道SideTable吗?
Runtime对注册的类会进行内存布局,有个SideTable结构体是负责管理类的引用计数表和weak表,weak修饰的对象地址做为key存放到全局的weak引用表中,value是全部指向这个weak指针的地址集合,调用release会致使引用计数器减一,当引用计数器为0时调用dealloc,在执行dealloc时将全部指向该对象的weak指针的值设为nil,避免悬空指针。
什么是关联对象?能够用来干吗?系统如何管理管理对象?支持KVO吗?
关联对象:在运行时动态为指定对象关联一个有生命周期的变量(经过objc_setAssociatedObject和objc_getAssociatedObject),用于实现category中属性保存数据的能力和传值。(支持KVO,让关联的对象做为观察者)
管理关联对象:系统经过管理一个全局哈希表,经过对象指针地址和传递的固定参数地址来获取关联对象。根据setter传入的参数策略,来管理对象的生命周期。经过一个全局的hash表管理对象的关联,经过对象指针地址获取对象关联表,再根据自定义key查找对应的值(外表:key(对象指针)-value(hash表),内表:key(自定义name)-value(管理的值))
isa、对象、类对象、元类和父类之间的关系?
类:对象是类的一个实例,类也是另外一个类的实例,这个类就是元类 (metaclass)。元类保存了类的类方法。当一个类方法被调用时,元类会首先查找它自己是否有该类方法的实现,若是没有,则该元类会向它的父类查找该方法,一直找到继承链的根部,找不到就转发。
元类:元类 (metaclass) 也是一个实例,那么元类的 isa 指针又指向哪里呢?为了设计上的完整,全部的元类的 isa 指针都会指向一个根元类 (root metaclass)。根元类 (root metaclass) 自己的 isa 指针指向本身,这样就行成了一个闭环。上面提到,一个对象可以接收的消息列表是保存在它所对应的类中的。在实际编程中,咱们几乎不会遇到向元类发消息的状况,那它的 isa 指针在实际上不多用到。不过这么设计保证了面向对象的干净,即全部事物都是对象,都有 isa 指针。
继承:咱们再来看看继承关系,因为类方法的定义是保存在元类 (metaclass) 中,而方法调用的规则是,若是该类没有一个方法的实现,则向它的父类继续查找。因此,为了保证父类的类方法能够在子类中能够被调用,因此子类的元类会继承父类的元类,换而言之,类对象和元类对象有着一样的继承关系。
知道建立类的方法objc_allocateClassPair?方法里面具体作了什么事情?
// objc-class-old.mm
Class objc_allocateClassPair(Class supercls, const char *name,
size_t extraBytes)
{
Class cls, meta;
if (objc_getClass(name)) return nil;
// fixme reserve class name against simultaneous allocation
if (supercls && (supercls->info & CLS_CONSTRUCTING)) {
// Can't make subclass of an in-construction class return nil; } // Allocate new classes. if (supercls) { // 若父类存在,父类的内存空间+额外的空间 = 新类的内存大小 cls = _calloc_class(supercls->ISA()->alignedInstanceSize() + extraBytes); meta = _calloc_class(supercls->ISA()->ISA()->alignedInstanceSize() + extraBytes); } else { // 若父类不存在,基类的内存空间+额外的空间 = 新类的内存大小(objc_class是objc_object的子类) cls = _calloc_class(sizeof(objc_class) + extraBytes); meta = _calloc_class(sizeof(objc_class) + extraBytes); } // 初始化 objc_initializeClassPair(supercls, name, cls, meta); return cls; } // objc-runtime-new.mm Class objc_allocateClassPair(Class superclass, const char *name, size_t extraBytes) { Class cls, meta; rwlock_writer_t lock(runtimeLock); // Fail if the class name is in use. // Fail if the superclass isn' t kosher.
if (getClass(name) || !verifySuperclass(superclass, true /*rootOK*/)) {
return nil;
}
// Allocate new classes.
cls = alloc_class_for_subclass(superclass, extraBytes);
meta = alloc_class_for_subclass(superclass, extraBytes);
// fixme mangle the name if it looks swift-y?
objc_initializeClassPair_internal(superclass, name, cls, meta);
return cls;
}
复制代码
class_ro_t 和 class_rw_t 的区别?
class_ro_t是readonly,class_rw_t是readwrite;
在编译以后,class_ro_t的baseMethodList就已经肯定。当镜像加载的时候,methodizeClass方法会将baseMethodList添加到class_rw_t的methods列表中,以后会遍历category_list,并将category的方法也添加到methods列表中。这里的category指的是分类,基于此,category能扩充一个类的方法。这是开发时常常须要使用到。
class_ro_t在内存中是不可变的。在运行期间,动态给类添加方法,实质上是更新class_rw_t的methods列表。
除了objc_msgSend,还知不知作别的消息发送函数?
objc_msgSend_stret:当CPU的寄存器可以容纳下消息返回类型时,该函数才能处理此消息,若超过CPU寄存器则由另外一个函数执行派发,原函数会经过分配在栈上的变量来处理消息所返回的结构体。
objc_msgSendSuper_stret:向父类实例发送一个返回结构体的消息
objc_msgSendSuper:向超类发送消息([super msg])
objc_msgSend_fpret:消息返回浮点数时,在某些结构的CPU中调用函数时须要对“浮点数寄存器”作特殊处理,不一样的架构下浮点数表示范围不同(寄存器是CPU的一部分,用于存储指令、数据和地址)
什么是方法交换?怎么用的?
SEL方法地址和IMP函数指针是经过DispatchTable表来映射,能够经过runtime动态修改SEL,因此能够实现方法的交换(使用经验根据项目来谈便可)。
数据持久化
plist:XML文件,读写都是整个覆盖,需读取整个文件,适用于较少的数据存储,通常用于存储App设置相关信息。
NSUserDefault:经过UserDefault对plist文件进行读写操做,做用和应用场景同上。
NSKeyedArchiver:被序列化的对象必须支持NSCoding协议,能够指定任意数据存储位置和文件名。整个文件复写,读写大数据性能低。
CoreData:是官方推出的大规模数据持久化的方案,它的基本逻辑相似于 SQL 数据库,每一个表为 Entity,而后咱们能够添加、读取、修改、删除对象实例。它能够像 SQL 同样提供模糊搜索、过滤搜索、表关联等各类复杂操做。尽管功能强大,它的缺点是学习曲线高,操做复杂。
SQLite(FMDB、Realm)
多线程
串行队列和并发队列的区别?同步和异步的区别?
串行队列(Serial Queue):指队列中同一时间只能执行一个任务,当前任务执行完后才能执行下一个任务,在串行队列中只有一个线程。
并发队列(Concurrent Queue):容许多个任务在同一个时间同时进行,在并发队列中有多个线程。串行队列的任务必定是按开始的顺序结束,而并发队列的任务并不必定会按照开始的顺序而结束。
同步(Sync):会把当前的任务加入到队列中,除非等到任务执行完成,线程才会返回继续运行,也就是说同步会阻塞线程。
异步(Async):也会把当前的任务加入到队列中,但它会马上返回,无需等任务执行完成,也就是说异步不会阻塞线程。
PS:不管是串行仍是并发队列均可以执行执行同步或异步操做。注意在串行队列上执行同步操做容易形成死锁,在并发队列上则不用担忧。异步操做不管是在串行队列仍是并发队列上均可能出现线程安全的问题。
GCD和NSOperation的区别?
语法:面向对象(重量级) - 面向C(轻量级)
相比GCD的优势:
取消某个操做(未启动的任务)、
指定操做间的依赖关系(根据需求指定依赖关系)
经过键值观察机制监控NSOperation对象的属性(KVO)
指定操做的优先级
重用NSOperation对象
提供可选的完成block
线程安全
如何保证线程安全?
原子操做(Atomic Operation):是指不会被线程调度机制打断的操做;这种操做一旦开始,就一直运行到结束,中间不会有任何的上下文切换(context switch)。
内存屏障(Memory Barrier):确保内存操做以正确的顺序发生,不阻塞线程(锁的底层都会使用内存屏障,会减小编译器执行优化的次数,谨慎使用)。
锁
互斥锁(pthread_mutex):原理与信号量相似,但其并不是使用忙等,而是阻塞线程和休眠,需切换上下文。
递归锁(NSRecursiveLock):本质是封装了互斥锁的PTHREAD_MUTEX_RECURSIVE类型的锁,容许同一个线程在未释放其拥有的锁时反复对该锁进行加锁操做。
自旋锁(OSSpinLock):经过全局变量,来判断当前锁是否可用,不可用就忙等。
@synchronized(self):系统提供的面向OC的API,经过把对象hash当作锁来用。
NSLock:本质是封装了互斥锁的PTHREAD_MUTEX_ERRORCHECK类型的锁,它会损失必定性能换来错误提示,由于面向对象的设计,其性能稍慢。
条件变量(NSConditionLock):底层经过(condition variable)pthread_cond_t来实现的,相似信号量具备阻塞线程与信号机制,当某个等待的数据就绪后唤醒线程,好比常见的生产者-消费者模式。
信号量(dispatch_semaphore)// 传入值需>=0,若传0则阻塞线程并等待timeout
dispatch_semaphore_create(1):
// lock 资源已被锁,会使得signal值-1
dispatch_semaphore_wait(signal, overTime):
// unlock,会使得signal值+1
dispatch_semaphore_signal(signal):
复制代码
什么是死锁?如何避免死锁?
死锁:两个或两个以上的线程,互相等待彼此中止以得到某种资源,可是没有一方会提早退出的状况。
避免在串行队列中执行同步任务;避免Operation相互依赖;
什么是优先倒置?
低优先级任务会先于高优先级任务执行,假设:任务优先级A>B>C,A等待访问被C正在使用的临界资源,同时B也要访问该资源,B的优先级高于C,同时,C优先级任务被B次高优先级的任务所抢先,从而没法及时地释放该临界资源。这种状况下,B次高优先级任务得到执行权,而高优先级任务A只能被阻塞。 能够设置相同的任务优先级避免优先倒置。
项目经验相关题
开源库
这部分主要跟简历中提到的相关库有关,建议对简历中提到的开源库,必定要有所准备。
SDWebImage
SDWebImage几乎是每一个iOS开发者都用过的开源库,也是在简历中曝光度比较高的开源库之一,同时也几乎是面试都会问到的,因此要准备充分再去。
从调用到显示的过程?
根据请求的文件URL路径判断当前是否存在还没结束的操做,如有的话就取消并移除与URL映射的操做。
设置placeholder占位图,无论URL是否为空都会设置,且在当前队列同步执行。
判断URL是否为空,为空则执行completedBlock回调,并结束。
URL非空,如果首次则开始相关类的初始化,如:加载进度及其相关的Block、SDWebImageManager(SDWebImageCache管理缓存、SDWebImageDownloader管理),若非首次仍是使用最初初始化的实例,由于SDWebImageManager是以单例的形式存在。
开始进入SDWebImageManager的范围,URL容错处理、建立负责加载图片和取消加载图片的类,判断当前的URL是否在黑名单里,若存在则执行回调,返回当前的操做类,再也不处理下载失败过的URL,并结束。
若URL不在黑名单里,则开始在内存缓存中查找,找到后执行doneBlock回调,在该回调方法中执行判断当前操做是否已取消,未取消则回调并异步设置图片,在回调的Block中,每次都会检查当前操做是否已取消,若取消则不处理,并结束。
内存缓存没找到,则在磁盘缓存中查找,通常是在串行队列异步执行,根据URL路径找到存储的数据并取出,而后缩放解压缩返回,执行回调设置图片,并结束。
磁盘没找到则下载图片,下载完成后缓存,设置图片;SDWebImage对图片做了哪些优化:子线程强制解压缩,从硬盘获取的图片和下载的图片都进行解压缩操做,提升渲染效率,节省主线程的工做量。
ReactiveCocoa
该库比较复杂,可问的问题也很是多,如下仅供参考,建议本身找答案(本身理解后才能从容面对)
冷热信号的区别?
RAC如何监听方法?
bind方法作了什么?
RAC中的RACObserver和KVO有什么区别?
RAC的map和flattenMap的区别?
工具
不免会遇到一些有关经常使用工具的问题,提供几个仅供参考
Git、SVN?
问题可深可浅,浅:基本用法或相关命令,深:Git的工做原理
CocoaPods
pod update和pod install的区别
CI(持续集成、持续部署)