面向对象
1、Hash表实现
- 概念:通过把关键码值(key)映射到表中的一个位置来访问记录,Hash实现的关键是散列函数和冲突解决(链地址法和开放定址法)。
H i ( key ) = ( H ( key )+ d i ) mod m ( i = 1,2,…… , k ( k ≤ m – 1))
其中: H ( key ) 为关键字 key 的直接哈希地址, m 为哈希表的长度, di 为每次再探测时的地址增量。
采用这种方法时,首先计算出元素的直接哈希地址 H ( key ) ,如果该存储单元已被其他元素占用,则继续查看地址为 H ( key ) + d 2 的存储单元,如此重复直至找到某个存储单元为空时,将关键字为 key 的数据元素存放到该单元。
增量 d 可以有不同的取法,并根据其取法有不同的称呼:
( 1 ) d i = 1 , 2 , 3 , …… 线性探测再散列;
( 2 ) d i = 1^2 ,- 1^2 , 2^2 ,- 2^2 , k^2, -k^2…… 二次探测再散列;
( 3 ) d i = 伪随机序列 伪随机再散列;
2、什么是进程和线程?有什么区别?
- 进程:是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位。
- 线程:操作系统能够进行运算调度的最小单位。
- 区别:线程被包含在进程中,是进程中实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每个线程并行执行不同的任务。
3、内存几大区域?各自职责?
- 栈区:由编译器自动分配和释放,一般存放函数的形参、局部变量
- 堆区:由程序员手动分配和释放,若不释放,则程序结束由操作系统回收
- 全局区:由编译器自动分配和释放,程序结束由系统释放。全局变量和静态变量
- 常量区:由编译器分配和释放,程序结束由系统释放,存放常量字符
- 代码区:存放函数的二进制代码
4、架构、框架和设计模式的区别?
- 架构:一种顶层概括性的设计概念,像是蓝图,将不同的需求抽象为具体组件并使组件互相通信
- 框架:软件框架是提取特定领域软件的共性部分形成的体系结构,不同领域的软件项目有不同的框架类型
- 设计模式:一套被反复使用、多数人知晓、经过分类编目的总结,代码设计的一个成果,可以用在不同软件框架中一种实际问题的解决方案。(在软件开发中总结出的一种适合解决某种问题的方法
5、MVC、MVVM、MVP架构的不同?
-
MVC:
- 优点:简单易上手、没有多层调用
- 缺点:耦合性强;不利于单元测试,Controller担负了太多的事件处理,处理用户操作,管理View的声明周期,API接口调用,处理错误,处理回调,监听通知,处理视图方向等,容易造成臃肿,维护不方便
-
MVP:
- 优点:利于单元测试、明确责任分配
- 缺点:要写一些额外的代码(如绑定),若业务逻辑复杂,也会造成Presenter的臃肿
-
MVVM:
- 优点:责任划分明确;逻辑清晰;可测性高;双向绑定,配合第三方开源库使用方便
- 缺点:1、数据绑定使得Bug很难被定位;2、对于过大的项目,数据绑定需要花费更多的内存;依赖于开源库实现双向绑定,学习起来比较复杂
UI
1、UIView和CALayer的区别?
- UIView 和 CALayer 都是 UI 操作的对象。两者都是 NSObject 的子类,发生在 UIView 上的操作本质上也发生在对应的 CALayer 上。
- UIView 是 CALayer 用于交互的抽象。UIView 是 UIResponder 的子类( UIResponder 是 NSObject 的子类),提供了很多 CALayer 所没有的交互上的接口,主要负责处理用户触发的种种操作。
- CALayer 在图像和动画渲染上性能更好。这是因为 UIView 有冗余的交互接口,而且相比 CALayer 还有层级之分。CALayer 在无需处理交互时进行渲染可以节省大量时间。
- CALayer的动画要通过逻辑树、动画树和显示树来实现
2、loadView的作用?
- loadView用来自定义view,只要实现了这个方法,其他通过xib或storyboard创建的view都不会被加载
3、layoutIfNeeded、layoutSubviews和setNeedsLayout的区别?
- layoutIfNeeded:方法调用后,在主线程对当前视图及其所有子视图立即强制更新布局。
- layoutSubviews:方法只能重写,我们不能主动调用,在屏幕旋转、滑动或触摸界面、子视图修改时被系统自动调用,用来调整自定义视图的布局。
- setNeedsLayout:方法与layoutIfNeeded相似,不同的是方法被调用后不会立即强制更新布局,而是在下一个布局周期进行更新。
4、iOS的响应链?什么情况会影响响应链?
- 事件UIResponder:继承该类的对象才能响应。
- 事件处理:touchesBegain、touchesMoved、touchesEnded、touchesCancelled;
- 事件响应过程:
- 1、UIApplication(及delegate)接收事件传递给 keyWindow
- 2、keyWindow遍历subViews的hitTest:withEvent:方法和pointInside方法找到点击区域内的视图并处理事件
- 3、UIView的子视图也会遍历其hitTest:withEvent:方法,以此类推,直到找到点击区域内最上层的视图,将视图逐步返回给UIApplication
- 4、最后找到的视图成为第一响应者,若无法响应,调用其nextResponder方法,一直找到响应链中能处理该事件的对象。
- 5、最后到Application依然没有能处理该事件的对象的话,就废弃该事件。
- ⚠️ 以下几种情况会忽略,hidden为YES,alpha小于等于0.01,userInteractionEnabled为NO
- UIControl的子类和UIGestureRecognizer优先级较高,会打断响应链;
5、UIImageView添加圆角的方式
- cornerRadius(iOS9之后不会导致离屏渲染)
- CoreGraphic绘制
- UIBezierPath
6、iOS有哪些实现动画的方式
- UIView Animation:系统提供的基于CALayer Animation的封装,可以实现平移、缩放、旋转等功能。
- CALayer Animation:底层CALayer配合CAAnimation的子类,可以实现更复杂的动画
- UIViewPropertyAnimator:iOS10后引入的用于处理交互的动画,可以实现UIView Animation的所有效果
7、使用drawRect有什么影响?
- 处理touch事件时会调用setNeedsDisplay进行强制重绘,带来额外的CPU和内存开销
OC
1、iOS的内存管理机制
- 通过引用计数机制实现的,当使用new、alloc、copy、retain、strong操作时,会使该对象的引用计数+1,需要对应release、autorelease操作,会使对象的引用计数-1。当引用计数为0时,对象会被系统销毁
2、@property的相关修饰词
- 原子性:
- nonatomic:非原子性,在使用该属性时编译器不会对其进行加锁(同步锁的开销较大)
- atomic:原子性(默认),编译器合成的方法会通过锁定机制确保原子性(非线程安全)
- 读写权限:
- readwrite:可读写(默认),若不用@dynamic修饰,编译器会自动为其生成setter和getter方法的声明和实现,否则编译器仅仅会生成方法的声明,没有实现
- readonly:只读,若不用@dynamic修饰仅生成getter方法,否则编译器仅会生成方法的声明,没有实现
3、内存管理语义
strong:修饰对象引用类型(表示持有当前实例),保留新值释放旧值,若修饰IMMutable不可变类型建议用copy
copy:指修饰不可变集合类型、AttributeString、Block(ARC下strong和copy一样,把栈中的Block拷贝到堆中)(设置方法不保留新值而是将其copy,不可变类型的可变类型子类)
weak:修饰对象弱引用类型(非持有关系),不保留新值,不释放旧值(属性所指对象被摧毁时,属性值也会清空置nil)
assign:修饰基本数据类型(也可以修饰引用类型,但可能会产生野指针),执行简单赋值操作
unsafe_unretained:修饰引用类型(也可以修饰基本类型),所指对象被摧毁时,属性值不会被清空(unsafe)
4、方法名
- getter=指定方法名
- setter=指定方法名
5、dynamic和synthesis的区别
- dynamic:告诉编译器不要帮我自动合成setter和getter方法,自己来实现,若没有实现,当方法被调用时会报方法找不到。
- synthesis:指定实例变量的名字,子类重载父类属性也需要synthesis(重新set和get、使用dynamic,在Protocol定义属性、在category定义属性,默认不会自动合成)。
6、array为何用copy修饰?mutableArray为何用strong修饰?
- array:若用strong修饰,在其被赋值可变的子类后,内容可能会在不知不觉中修改,用copy防止被修改。
- mutableArray若用copy修饰会返回一个NSArray类型,若调用可变类型的添加、删除、修改方法时会因为找不到对应的方法而crash。
7、深拷贝和浅拷贝(注意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 是拷贝的;对于对象类型,仅仅是指针拷贝
8、Block的类型
-
_NSConcreteGlobalBlock
:全局Block也是默认的block类型,一种优化操作,私有和公开,保持私有可防止外部循环引用,存储在数据区,当捕获外部变量时会被copy到堆上 -
_NSConcreteStackBlock
:栈Block,存储在栈区,待其作用域结束,由系统自动回收 -
_NSConcreteMallocBlock
:堆Block,计数器为0 的时候销毁
OC进阶
1、Foundation和CoreFoundation的转换
__bridge
:负责传递指针,在OC和CF之间转换,不转移管理权,若把OC桥接给CF,则OC释放后CF也无法使用。__bridge_retained
:将OC换成CF对象,并转移对象所有权,同时剥夺ARC的管理权,之后需要使用CFRelease释放。__bridge_transfer
:将CF转换成OC,并转移对象所有权,由ARC接管,所以不需要使用CFRelease释放。
2、array和set的区别?查找速度和遍历速度谁更快
- array:分配的是一片连续的存储单元,是有序的,查找时需要遍历整个数组查找,查找速度不如Hash。
- set:不是连续的存储单元,且数据是无序的,通过Hash映射存储的位置,直接对数据hash即可判断对应的位置是否存在,查找速度较快。
- 遍历速度:array的数据结构是一片连续的内存单元,读取速度较快,set是不连续的非线性的,读取速度较慢。
3、图片显示的过程
imageNamed:方法适用于经常使用,并且图片小、数量少的场合,方便快速加载;
imageWithContentsOfFile:方法适用于图片比较大,并且图片数量非常多的场合,此时需要考虑程序的性能
1、当从磁盘中加载一张图片时,这个时候图片是没有被解压缩;
2、将生成的 UIImage 赋值给 UIImageView;
3、接着一个隐式的 CATransaction 捕获到了 UIImageView 图层树的变化
-
4、在主线程的下一个 run loop 到来时,Core Animation 提交了这个隐式的 transaction ,这个过程可能会对图片进行 copy 操作,而受图片是否字节对齐等因素的影响,这个 copy 操作可能会涉及以下部分或全部步骤:
- 分配内存缓冲区用于管理文件 IO 和解压缩操作;
- 将文件数据从磁盘读到内存中;
- 将压缩的图片数据解码成未压缩的位图形式,这是一个非常耗时的 CPU 操作;
- 最后 Core Animation 使用未压缩的位图数据渲染 UIImageView 的图层。
4、dispatch_once如何只保证只执行一次?
- 多线程中,若有一个线程在访问其初始化操作,另外一个线程进来后会延迟等待,有内存屏障来保证程序指令的顺序执行
5、NSThread、NSRunLoop、NSAutoreleasePool三者之间的关系?
根据官方文档中对 NSRunLoop 的描述,我们可以知道每一个线程,包括主线程,都会拥有一个专属的 NSRunLoop 对象,并且会在有需要的时候(使用懒加载的方式,只有用到才会创建)自动创建。
根据官方文档中对NSAutoreleasePool的描述,我们可知,在主线程的 NSRunLoop 对象(在系统级别的其他线程中应该也是如此,比如通过 dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0) 获取到的线程)的每个 event loop 开始前,系统会自动创建一个 autoreleasepool ,并在 event loop 结束时 drain。
-
需要手动添加autoreleasepool的情况:
- 如果你编写的循环中创建了大量的临时对象;
- 如果你创建了一个辅助线程
6、分类可扩展的区别?(可从内存布局、加载顺序、分类方法和原类方法的执行顺序来回答)
7、OC对象释放的流程?
- release:引用计数器减一,直到为0时开始释放
- dealloc:对象销毁的入口
- dealloc中会调用_objc_rootDealloc函数,又会调用rootDealloc函数,又会调用object_dispose函数,又会调用objc_destructInstance函数,通过object_cxxDestruct移除成员变量,通过_object_remove_assocations移除当前对象的关联对象,又调用clearDeallocating函数,调用clearDeallocating_slow函数,通过调用weak_clear_no_lock清理weak表,以及将weak指针置位nil
- sidetable_clearDeallocating:清理有指针isa的对象
- clearDeallocating_slow:清理非指针isa的对象,清理weak表、将weak指针置位nil,清空引用计数
8、CADisplayLink和NSTimer的区别?
- CADisplayLink:以和屏幕刷新率相同的频率将内容画到屏幕上的定时器,需手动添加runloop
- NSTimer:可自动添加到当前线程的runloop,默认defualt模式,也可以手动添加的loopMode;
9、用runtime实现方法交换有什么风险
- 风险1:若不在load中交换是非原子性的,在initial方法中不安全
- 风险2:重写父类方法时,大部分都是需要调用super方法的,swizzling交换方法,若不调用,可能会出现一些问题,如:命名冲突、改变参数、调用顺序、难以预测、难以调试。