1.关于copyWithZone
为了实现对象的深拷贝 需要遵循NSCopying
协议 并实现copyWithZone:
方法
这里的关键点在于 当父类未实现NSCopying
协议时 需要这样写:
- (id)copyWithZone:(NSZone *)zone {
Crime *newCrime = [[[self class] allocWithZone:zone] init];// 注意这里
if(newCrime) {
[newCrime setMonth:[self month]];
[newCrime setCategory:[self category]];
[newCrime setCoordinate:[self coordinate]];
[newCrime setLocationName:[self locationName]];
[newCrime setTitle:[self title]];
[newCrime setSubtitle:[self subtitle]];
}
return newCrime;
}
这样可以保证当子类继承调用[super copyWithZone:zone]
的时候能够获取到正确的实例对象:
- (id)copyWithZone:(NSZone *)zone {
Crime *newCrime = [super copyWithZone:zone];// 考虑下若父类直接使用[self allocWithZone:zone]的情况
[newCrime setMonth:[self month]];
[newCrime setCategory:[self category]];
[newCrime setCoordinate:[self coordinate]];
[newCrime setLocationName:[self locationName]];
[newCrime setTitle:[self title]];
[newCrime setSubtitle:[self subtitle]];
return newCrime;
}
2.copy和mutableCopy
对immutableObject 执行copy得到不可变对象 并且是浅拷贝
对immutableObject 执行mutableCopy得到可变对象 实现的是深拷贝
对mutableObject 执行copy得到不可变对象 并且是深拷贝
对mutableObject 执行mutableCopy得到可变对象 实现的是深拷贝
对于容器类来说,容器类对象本身遵循上述规律,但是其内部包含的所有对象都是浅拷贝,都指向原来的对象
3.@property常用修饰词及其含义
assign
:对属性赋值的时候直接赋值 不会引起引用计数变化 一般用于基本类型(BOOL
NSInteger
CGFloat
等)对像用assign修饰时由于不会将指针置nil所以会引起野指针问题strong
:对属性赋值的时候直接赋值并且对象的引用计数器 +1retain
:在MRC的时候使用较多 赋值的时候release旧对象( 旧对象计数器 -1 ) , retain新对象( 新对象计数器 +1 ) , 然后指向新对象 ARC中已被strong
代替weak
:赋值的时候不会引起引用计数变化 并且当对象销毁时让指针指向nil 一般用于delegate等容易引起循环引用的情况copy
:赋值时先copy对象(不管是深拷贝还是浅拷贝)然后将指针指向copy出来的对象 并且这个对象的引用计数+1nonatomic
:不对自动生成
的setter、getter方法加同步锁(@synchronized
) 线程不安全 但是性能好atomic
:对自动生成
的setter、getter方法加同步锁 保证同一时间只有一个线程可以调用setter、getter方法 但是他依然不能够保证线程安全 因为他不能保证getter、setter方法的执行顺序 如果线程 A 调了 getter,与此同时线程 B 、线程 C 都调了 setter——那最后线程 A get 到的值,3种都有可能:可能是 B、C set 之前原始的值,也可能是 B set 的值,也可能是 C set 的值@dynamic
表示不对属性生成setter和getter方法 也不生成其对应的成员变量@synthesize
表示自己声明生成的成员变量名称 用法为@synthesize name = _myname
一般来说 属性有默认的关键字,对于基本类型来说为atomic
、readwrite
、assign
,对于非基本类型来说为atomic
、readwrite
、strong
4.iOS下实现多线程的方式
有四种 包括pthread、NSThread、GCD和NSOperation
一般使用GCD作为多线程的解决方案
在遇到GCD无法解决的情况一般会使用NSOperation+NSOperationQueue 后者其实是基于前者实现的
GCD了解下async、sync的区别,dispatch_group的使用、dispatch_barrier_async/sync的使用
NSOperation注意下和GCD相比支持取消操作,而且可以方便的控制最大的并发数量,以及确定操作之间的依赖关系等。
5.为什么说OC是一门动态语言
可以从以下几个方面说一说:
动态类型 id 可以接受各种各样的类型 比如说发送通知传参数的时候
动态绑定 在运行的时候才决定要调用哪个方法 比如说method swizzle
动态载入 动态的加载代码运行
6.消息转发机制
所谓的消息转发是指对象无法直接处理消息时(比如说方法有定义,但是却没有实现),提供的补救方案,分为三步:动态方法解析
、备用接受者
、完整转发
动态方法解析
:对象在接收到未知的消息时,首先会调用resolveInstanceMethod
和resolveClassMethod
,在这里通过class_addMethod
函数动态添加C函数到类即可 返回值为BOOL 表示有没有成功的加入方法
备用接受者
:如果动态方法无法解析消息(上面的方法返回NO),则会走forwardingTargetForSelector
,这个方法返回你需要转发消息的对象,这个备用接受者只能是一个新的对象,不能是self本身,否则就会出现无限循环。返回值为备用的接收者对象
完整转发
:如果我们不实现forwardingTargetForSelector,系统就会调用两个方法methodSignatureForSelector
和forwardInvocation
。methodSignatureForSelector
用来生成方法签名,这个签名就是给forwardInvocation
中的参数NSInvocation
调用的。而forwardInvocation
方法则通过新建你需要执行NSInvocation
的对象,调用invokeWithTarget
来实现完整的消息转发。forwardInvocation
方法没有返回值
7.设计模式6大原则
单一职责原则
:有且仅有一个原因引起类的变更。单一职责原则提出了一个编写程序的标准,用“职责”或“变化原因”来衡量接口或类设计得是否优良
里氏替换原则
:在使用基类的的地方可以任意使用其子类,能保证子类完美替换基类。如果子类不能完整地实现父类的方法,或者父类的某些方法在子类中已经发生“畸变”,则建议断开父子继承关系 采用依赖、聚合、组合等关系代替继承
依赖倒置原则
:高层模块不应该依赖底层模块,二者都该依赖其抽象;抽象不应该依赖细节;细节应该依赖抽象。高层模块就是调用端,低层模块就是具体实现类。抽象就是指接口或抽象类。细节就是实现类
接口隔离原则
:类间的依赖关系应该建立在最小的接口上 建立单一接口,不要建立庞大臃肿的接口,尽量细化接口,接口中的方法尽量少,但是要有限度。对接口进行细化可以提高程序设计灵活性,但是如果过小,则会造成接口数量过多,使设计复杂化
迪米特法则
:类间解耦,对其他类知道的越少越好。
开放封闭原则
:尽量通过扩展软件实体来解决需求变化,而不是通过修改已有的代码来完成变化。一个软件产品在生命周期内,都会发生变化,既然变化是一个既定的事实,我们就应该在设计的时候尽量适应这些变化,以提高项目的稳定性和灵活性
8.关于Runloop
Runloop是一种让线程能随时处理事件但并不退出的机制,这种模型通常被称作 Event Loop。 Event Loop 在很多系统和框架里都有实现,比如 Node.js 的事件处理,比如 Windows 程序的消息循环,再比如 OSX/iOS 里的 RunLoop。实现这种模型的关键点在于:如何管理事件/消息,如何让线程在没有处理消息时休眠以避免资源占用、在有消息到来时立刻被唤醒。所以,RunLoop 实际上就是一个对象,这个对象管理了其需要处理的事件和消息,并提供了一个入口函数来执行上面 Event Loop 的逻辑。线程执行了这个函数后,就会一直处于这个函数内部 “接受消息->等待->处理” 的循环中,直到这个循环结束(比如传入 quit 的消息),函数返回。
一个 RunLoop 包含若干个 Mode,每个 Mode 又包含若干个 Source/Timer/Observer。每次调用 RunLoop 的主函数时,只能指定其中一个 Mode,这个Mode被称作 CurrentMode。如果需要切换 Mode,只能退出 Loop,再重新指定一个 Mode 进入。
9.HTTP响应码对应的含义
200 OK
:客户端请求成功
301 Moved Permanently
:请求永久重定向
302 Moved Temporarily
:请求临时重定向
304 Not Modified
:文件未修改,可以直接使用缓存的文件
400 Bad Request
:由于客户端请求有语法错误,不能被服务器所理解
401 Unauthorized
:请求未经授权。这个状态代码必须和WWW-Authenticate报头域一起使用
403 Forbidden
: 服务器收到请求,但是拒绝提供服务。服务器通常会在响应正文中给出不提供服务的原因
404 Not Found
: 请求的资源不存在,例如,输入了错误的URL
500 Internal Server Error
:服务器发生不可预期的错误,导致无法完成客户端的请求
503 Service Unavailable
:服务器当前不能够处理客户端的请求,在一段时间之后,服务器可能会恢复正常
10.OSI
11.三次握手
第一次:客户端
SYN:1
Seq:x
SYN_SEND状态
第二次:服务器
SYN:1
ACK:1
Seq:y
ACKNum:x+1
SYN_RCVD状态
第三次:客户端
ACK:1
ACKNum:y+1
ESTABLISH状态
为什么需要三次?
最主要是防止已过期的第二次握手报文因网络延迟到达客户端导致连接再次开启的问题
12.四次挥手
第一次:客户端
FIN=1
Seq=x
FIN_WAIT_1
第二次:服务器
ACK=1
ACKNum=x+1
CLOSE_WAIT
客户端进入FIN_WAIT_2
中途还可能继续传输一些数据
第三次:服务器
FIN=1
Seq=y
LAST_ACK
第四次: 客户端
ACK=1
ACKNum=y+1
TIME_WAIT
服务器在接收到客户端的第四次报文时就会关闭连接 客户端经过2MSL(一个报文发送加回复加起来所需最大的事件)之后如果没接收到服务器要求重传报文的请求 就进入CLOSE关闭连接
为什么需要四次?
因为虽然客户端不在传数据了 但是服务器可能要继续传输一些数据(全双工模式)
13.补充一些Git命令的使用
git reflog
:查看自己使用命令的历史
git diff commit1 commit2
:将某两次commit之间的变化作对比 commitid可以替换为HEAD HEAD~1等
git stash
:如果当前工作区文件有修改但是不想提交,又想切换到其他分支修改一些内容的时候可以使用这个命令,当切换回来的时候,使用git stash pop
即可恢复
14.+load
方法和+initialize
方法的区别
前者在程序开始运行,也就是类被载入的时候就调用
后者在用到这个类的时候才调用
前者如果自己不实现 不会继承父类的load方法
后者却会继承(如果本身没实现的话 会调用父类的initialize方法)
前者在方法体中使用其他类不安全(不能保证此时使用的类已经载入,使用Foundation中的类安全)
后者则安全
15.绘制性能相关问题
- 绘制过程简单来说就是CPU负责把绘制的操作放在缓存中,CPU则从缓存中取数据,再渲染到屏幕上面 如果这个过程每秒能完成60次 那就能保持60帧运行 否则就会造成卡顿 绘制性能的提升主要着眼点就是在CPU和GPU工作的合理分配和优化方面
- 对于GPU来说 把纹理渲染到屏幕容易造成瓶颈 主要包括几个方面:
-
合成(Compositing)
:合成是指将多个纹理拼到一起的过程,对应UIKit,是指处理多个view合到一起的情况(addSubview
)。如果view之间没有叠加,那么GPU只需要做普通渲染即可。 如果多个view之间有叠加部分,GPU需要做blending(图层或者图片不透明的情况下,计算某个像素点实际要显示的颜色)。 -
尺寸
:假如内存里有一张400x400的图片,要放到100x100的imageview里,如果不做任何处理,直接丢进去,问题就大了,这意味着,GPU需要对大图进行缩放到小的区域显示,需要做像素点的sampling,这种smapling的代价很高,又需要兼顾pixel alignment。计算量会飙升。 -
GPU的离屏渲染(Off-screen renderding)
:区别于当屏渲染(On-screen rendering)
,离屏渲染会在当前显示的缓冲区外,额外创建一款缓冲区,用来渲染。其中的代价包括两方面:一是创建额外缓冲区的开销;二是切换上下文的开销(从当屏渲染缓冲区切换到离屏渲染缓冲区,再切换回来)。
-
- CPU主要执行计算方面的操作,包括布局计算,图片解压等,尤其要注意的是当实现
drawRect方法
,使用Core Graphics绘制
,甚至实现一个drawRect
空方法时,会触发CPU的离屏渲染
。同样这会造成巨大的性能开销,需要注意。 - 造成图像性能问题的主要原因一般都是离屏渲染,触发离屏渲染(包括CPU和GPU)的方式有:
- 为图层设置蒙版
layer.mask
- 将图层的
layer.masksToBounds / view.clipsToBounds
属性设置为true
- 将图层
layer.allowsGroupOpacity
属性设置为YES
和layer.opacity
小于1.0
- 为图层设置阴影
layer.shadowxxx
- 为图层设置
layer.shouldRasterize(光栅化)=true
- 具有
layer.cornerRadius
,layer.edgeAntialiasingMask
,layer.allowsEdgeAntialiasing
的图层 - 实现
drawRect
方法
- 为图层设置蒙版
上面列出的触发离屏渲染的方式除了drawRect
都是GPU上的离屏渲染。
16.一些小Tips:
- switch枚举的时候可以不需要default 这样方便编译器提供警告信息
- iOS里图片占用的大小只和图片的分辨率有关 和图片文件的大小无关(1像素占用4KB)。当然,相对来说,图片的分辨率越大,文件大小就会越大,但是同一分辨率的图片,不同格式大小可能不一样,但是在iOS的内存里其实是一样大的。
17.理解类对象
每个对象结构体内部都有一个isa
指针指向其对应的类对象,类对象结构体除了有对象的方法类表(实例方法),变量列表等信息外,有一个super_class
指针,指向自己的超类的类对象,还有一个isa
指针,指向自己的元类
。元类有类对象的方法列表(类方法)等信息,他的super_class
指针指向其父元类,而isa
指针都统一指向根元类。根元类是继承体系中处于顶端的类的元类,也就是说NSObject继承体系下的所有类对象的元类都指向同一个NSObject类对象的元类。
18.SDWebImage 使用ImageView下载图片的大致流程是怎么样的?
草草看了下代码,综合相关文章,只说说重点:
- 新版的
SDWebImage
是使用NSURLSession
来实现图片下载的 - 入口方法当然是常用的
sd_setImageWithURL
这一类的方法 - 下载任务由
SDWebImageManager
负责创建,这个对象是一个单例,他包含了SDImageCache
和SDWebImageDownloader
这两个对象,前者负责缓存图片,后者负责下载。如果你不手动提供,那这两个对象都是用的库预定义的单例对象。 -
sd_setImageWithURL
这一类的方法内部调用了SDWebImageManager
中的downloadImageWithURL:options:progress:completed
方法 - 上述方法首先会调用
SDImageCache
的queryDiskCacheForKey:done:
方法来查询缓存,会先查询内存,再查询磁盘(默认禁用NSURLCache防止浪费资源)。由于磁盘IO会阻塞当前线程,所以该操作通过dispatch_async
在一个自定义的IO串行队列里进行。 - 如果缓存未找到,则会通过
SDWebImageDownloader
的downloadImageWithURL:options:progress:completed
方法来创建一个SDWebImage
自定义的NSOperation
的子类对象SDWebImageDownloaderOperation
。创建完之后通过往downloadQueue
里面添加这个operation来调用其start
方法启动下载任务。 - Operation对象的start方法里通过各种参数创建了对应的NSURLSessionDataTask对象,并调用resume方法开始下载任务。这里如果你传入的options允许后台下载的话,会通过调用系统的
beginBackgroundTaskWithExpirationHandler
来进行后台下载。 - 使用NSOperation类的好处是取消很方便
- 例如,对于UITableView复用UIImageView的情况,新版的
sd_setImageWithURL
这一类的方法执行前都有一个cancel所有operation的操作,保证了复用过程中不会出现图片错位的问题 - 总体来看,由于iOS不用像Android那样手动管理线程池,所以管理下载任务方便很多
19.GCD拾遗
- block具有块级作用域,分配在栈空间上,比如在if-else里的块,出了if-else可能就会出错。可以通过手动调用copy方法将块拷贝到堆空间上,这样就可以正常使用了。
- block的定义语法其实和函数指针很像,
void(*func)(id, SEL)
和void(^MyBlock)(BOOL flag, int value)
。利用typedef语法可以简化block变量的使用:typedef int(^SomeBlock)(BOOL flag)
-
dispatch_barrier_async/dispatch_barrier_sync
方法和并行队列
配合使用(串行队列本来就是顺序执行的),用来实现下图任务0执行顺序功能(任务0以外的任务都用dispatch_async/dispatch_sync
加入):
dispatch_barrier.png -
dispatch_group
的重点:dispatch_group_create
创建group;dispatch_group_async/dispatch_group_sync
分发任务;dispatch_group_wait
阻塞当前线程等待group中的任务完成;dispatch_group_notify
不会阻塞当前线程,等待group任务完成,并在指定的队列里执行后续操作。 -
dispatch_group
一定程度上可以实现dispatch_barrier
的功能
20.loadView方法 viewDidLoad方法
loadView
方法会在UIViewController
想使用self.view
,但是view
为空的时候调用,其默认实现为创建一个空白View
(没有xib的时候),如果想自己创建self.view
,那么就可以重写这个方法,并且不调用[super loadView]
避免创建空白view
,然后创建属于自己的view即可。
viewDidLoad
则会在self.view
被创建出来的时候调用,我们一般可以在这里方法里面往view里面添加子view,然后进行一些其他数据的初始化操作。
21.UIViewController生命周期方法的调用顺序
-
initWithCoder/initWithNibName:bundle:
:完成UIViewController的创建工作后会调用 -
loadView
:参见上面 -
viewDidLoad
:参见上面 -
viewWillAppear
:通常我们会在这个方法对即将显示的视图做进一步的设置。比如,设置设备不同方向时该如何显示;设置状态栏方向、设置视图显示样式等 -
viewWiiLayoutSubviews
:view将要调整其subViews,可以把调整代码写这里 -
viewDidLayoutSubviews
:view已经完成调整 -
viewDidAppear
:视图已经可见 viewWillDisappear
viewDidDisappear
22.产生死锁的必要条件和解决死锁的策略
-
必要条件:
- 互斥条件:资源只能被一个进程所占有
- 请求和保持:自己已经占有一个资源不放,又去请求一个新的资源
- 不可抢占:进程已经占有的资源不可让给其他进程占用
- 循环等待:必定存在一个循环链
-
解决死锁
- 预防死锁:破坏四个必要条件中的一个(比如资源一次性分配,允许剥夺资源)
- 避免死锁:资源在分配的时候如果可能进入不安全状态(死锁),则暂时不分配(银行家算法)
- 检测并解除死锁:比如剥夺资源等
23.常见的设计模式
单例、Builder、工厂模式
工厂模式分为普通的工厂模式和抽象工厂,两者区别在于后者的工厂接口定义了生产多种抽象产品的方法,而普通工厂只生产一种抽象产品
24.关于iOS中的事件传递和响应机制
-
UIResponser
是所有支持触摸事件类的基类,包括UIView
、UIViewController
和UIApplication
,如果你想自己处理触摸事件,就需要重写UIResponser
中定义的touchesBegan/touchesMoved/touchesEnd/touchedCancelled
等方法 - 点击一个
UIView
产生一个触摸事件A
,这个触摸事件A
会被添加到由UIApplication
管理的事件队列中 -
UIApplication
会从事件对列中取出最前面的事件(此处假设为触摸事件A
),把事件A
传递给应用程序的主窗口(keyWindow
) - 窗口会在视图层次结构中找到一个最合适的视图来处理触摸事件。(至此,第一步已完成)
- 触摸事件的传递是从父控件传递到子控件,如果父控件不能接受触摸事件,那么子控件也无法接收到触摸事件
- 通过设置
userInteractionEnabled=NO
、隐藏控件以及设置透明度为透明(alpha=0.0~0.01
)可以使控件无法接受到触摸事件. - 寻找最合适的控件的方法是调用
hitTest:withEvent
方法。这个方法内部通过调用子控件的pointInside:withEvent
方法来查询自己的子控件能不能接收到触摸事件(即触摸的点在不在这个视图的范围内)。如果不能接受,这个控件的层级视图就会被忽略。如果能接受到触摸事件,还得判断上面提到的userInteractionEnabled
等来看看是否能够接受触摸事件。方法最后返回其子控件中最合适的控件。通过重写这个方法,可以实现让特定的子控件来响应触摸事件。 - 如果某个控件是最合适的控件,那么他的
touches
方法就会被调用,这个方法的默认实现是[super touchesxxx]
,也就是将事件向上抛,所以触摸事件响应的顺序和传递的顺序刚好相反的,也不难理解。 - 如果想一次触摸,多个控件都有相应的处理,则可以重写
touches
方法。
25.iOS中有哪些设计模式的相关体现?
观察者:KVO
单例:经常用到
代理:delegate
26.HTTP和HTTPS的区别
HTTPS更安全(基于SSL 非明文 非对称加密)
端口号(HTTPS443 HTTP80)
非对称加密:用秘钥加密的数据只有公钥才能解开 反之亦然(不会导致私钥的泄露)
27.iOS如何实现线程安全?
- OSSpinLock 不安全
- dispatch_semaphore_t 信号量 在等待情况比较少的时候性能高
- @synchronized
- NSLock
- NSConditionLock
- NSRecursiveLock 可重入
- pthread_mutex
28.iOS内存分布
栈:局部变量(指针) 函数的参数
堆:各种对象
全局区:全局对象 常量(字符串常量之类) 静态对象(static修饰的)
29.TCP和UDP的区别
TCP有连接(三次握手 四次挥手) UDP无连接
TCP可靠传输 UDP不可靠
TCP有拥塞控制 UDP没有 网络不好也不会导致传输效率降低
TCP是一对一的 但是UDP支持一对多和多对一
30.TCP的拥塞控制
发送方维持一个叫做拥塞窗口的东西:conwind
阈值threshold:决定conwind超过这个值的时候采用什么方式增加窗口大小
conwind < threshold:慢启动 指数级增加窗口大小 由于刚刚开始传输的时候threshold很大 所以一开始肯定是慢启动
conwind > threshold: 拥塞避免 线性增加窗口大小
两种可能的超时情况:3个冗余ACK(说明有报文丢失了) 和 直接超时
3个冗余ACK:说明网络情况还是能传数据的 将conwind和threshold都设置为conwind的一半 进入拥塞避免状态
超时:网络情况很差了 将threshold减半 但是conwind从1MSS(最大报文段长度) 从慢启动开始
31.在浏览器中输入一个域名会执行哪些操作
1.根据域名 查找IP 涉及到DNS解析域名
2.根据IP 向服务器发送HTTP请求 建立TCP连接涉及到三次握手
3.服务器回传HTML资源 客户端浏览器根据资源进行页面的渲染
4.如果没有后续的连接操作 通过四次挥手 断开TCP连接
32.为什么选择使用代码布局而不是用stroyBoard或者xib
主要是为了多人开发的时候方便代码的codereview,而且重用方便,虽然会麻烦一些,而且不是很直观
33.讲解一下RunLoop
①为什么要有RunLoop?因为我们希望某个线程不退出一直能够处理事件 细化到iOS里面 就是保持程序运行 处理各种触摸事件等。而作为这样的一个工具,要保证可以良好的处理好事件,并在空闲的时候休眠节省资源,需要的时候可以快速唤醒。
②如何获取RunLoop?除开主线程,线程默认是不启动RunLoop的,它的第一次创建是你在第一次获取它的时候:[NSRunLoop currentRunLoop]
。RunLoop无法被直接创建。
③RunLoop的内部构成是怎么样的?一个RunLoop包含若干个Mode,其中一个Mode又包含若干个STO(Source/Timer/Observer)。每次调用RunLoop主函数的时候只能指定一个Mode,这是为了分割开不同组的STO防止相互影响。这个被指定的Mode称为CurrentMode。如果想要切换Mode,只能先退出当前RunLoop再进入。
④如何理解Mode?RunLoop总是运行在某种Mode下,Mode必须包含STO,否则RunLoop就会退出。App在刚启动的时候会在一个UIInitilizationRunLoopMode下,然后就会变为CFRunLoopDefaultMode,这是RunLoop的默认Mode。除了这两种Mode,还有UITrackingRunLoopMode(用来追踪ScrollView的滚动)和GSEventReceiverRunLoopMode(处理系统事件),以及一个通用的CFRunLoopCommonMode(一个占位用的Mode)
⑤如何理解Source?Source代表事件源,和Timer一样用于唤醒RunLoop处理消息。Source有两种类型,一种是不基于端口的,叫做Source0,一般用来处理系统事件,UITouch等。还有一种是基于端口的Source叫做Source1,用它可以实现进程间互相发送消息,它可以主动的唤醒RunLoop。
⑥如何理解Timer?STO里面的Timer基于CoreFoundation其实和NSTimer可以互相转换,它其实也是一种事件源,一旦时间点到了,就唤醒RunLoop,执行相应的回调。所以我们初始化一个NSTimer的时候需要指定一个RunLoop(通过runLoop的addTimer:forMode
)(scheduled系列的方法其实默认指定使用当前线程的RunLoop)。performSelector:withObject:afterDelay方法其实是基于NSTimer实现的。
⑦如何理解Obeserver?RunLoop拥有各种各样的运行状态,比如进入/退出RunLoop,将要开始处理Source/Timer或者将要唤醒/休眠。利用Observer就可以监听RunLoop的各种状态。
⑧RunLoop有哪些应用场景?1.防止UITableView卡顿,使用performSelector:withObject:afterDelay:inMode中指定为DefaultMode,防止TableView在滚动的时候设置上图片。2.系统有用Obeserver监听主线程RunLoop的休眠状态来提交各种UI的更新。
34.iOS证书非对称加密的机制和原理?
①数字签名?对原始文件的摘要用私钥加密,得到的叫做数字签名。将数字签名和文件一起发送给用户,用户通过公钥对签名解密,得到摘要,然后再用同一种摘要算法计算传过来文件的摘要,对比两个摘要就知道文件有没有被篡改
②苹果如何加密?两对秘钥:开发者MAC生成的公钥L 私钥L 苹果的公钥A 私钥A。首先开发者将自己的公钥L上传给苹果 苹果用私钥A对公钥L加密 得到签名。开发者将app用自己的私钥L加密,和证书一起装到iOS设备里,iOS设备用苹果提供的公钥A解密签名,得到可靠的公钥L,再用这个公钥L解密app,确保app的可靠性。
35.有哪些定时器?
NSTimer/GCD/CADisplayLink
35.谈谈MVC和MVVM
MVC:model 代表数据,view 代表 UI ,而 controller 则负责协调它们两者之间的关系
C在这里占据核心位置,负责更新View和model的状态,View则将用户的交互通知给C,Model增加数据的更新传递给C。controller 由于承载了过多的逻辑,往往会变得臃肿不堪
MVVM:将controller中的展示逻辑抽离出来,放在viewModel里,得到的模型如下:
View由 MVC 中的 view 和 controller 组成,负责 UI 的展示,绑定 viewModel 中的属性,触发 viewModel 中的命令
ViewModel负责从 model 中获取 view 所需的数据,转换成 view 可以展示的数据,并暴露公开的属性和命令供 view 进行绑定
Model与 MVC 中的 model 一致,包括数据模型、访问数据库的操作和网络请求等;
36.谈谈消息发送机制(不是转发哦)?
在oc里面使用[object func]调用某个方法其实在运行时是通过objc_msgSend(object_object, SEL)这个方法来实现的,也就是所谓的消息发送。其内部原理实际上是通过对象结构体的isa指针来查询类对象结构体。类对象结构体里面包含了方法列表和方法列表的缓存。首先从缓存里面找,如果没有找到,则从方法列表里面直接找(如果找到会将方法加入缓存),找到的方法其实是一个IMP,他是一个函数指针,通过这个指针,可以调用对应的函数。如果方法没有找到,那么就会进行方法的转发流程。
37.什么是多态?
一个接口有多种实现方式就是多态
38.Unicode和UTF-8 UTF-16 UTF-32有什么联系?
Unicode是一种字符和码点值对应的规范,就是一张表。比如说16进制下的码点值U+0021
对应的字符就是感叹号!
。
UTF-8 UTF-16和UTF-32则是将码值转换为计算机中的01串的规范,他对应的是计算机内的储存形式。