[TOC]前端
##iOS面试题ios
本面试题为我的使用版本,如后续流传出去,请转发的朋友务必注释一下,答案正确性有待商榷,本人的答案不表明权威,仅仅是我的理解。 文章内部有写混乱,将就着看吧。另外大部分图片加载不出来,,MARKDown格式也不太统一(各平台不同),因为博主太懒不想改,不过不影响最终效果,程序员
###1.对象方法和类方法的区别?面试
__unsafe_unretained UIView *testObj = [[UIView alloc] init];
NSLog(@"testObj 指针指向的地址:%p 指针自己的地址:%p", testObj, &testObj);
[testObj setNeedsLayout];
// 能够看到NSlog打印不会闪退,调用[testObj setNeedsLayout];会闪退
复制代码
这是网友总结的,有兴趣的能够看下:www.jianshu.com/p/9fd4dc046… 本人,也就是看看乐呵,其原理啥的,见仁见智吧。开发行业太j8难了!数据库
一、找不到方法的实现unrecognized selector sent to instance 二、KVC形成的crash 三、EXC_BAD_ACCESS 四、KVO引发的崩溃 五、集合类相关崩溃 六、多线程中的崩溃 七、Socket长链接,进入后台没有关闭 八、Watch Dog超时形成的crash 九、后台返回NSNull致使的崩溃,多见于Java作后台服务器开发语言数组
大体实现方式以下。浏览器
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// Override point for customization after application launch.
NSSetUncaughtExceptionHandler(&my_uncaught_exception_handler);
return YES;
}
static void my_uncaught_exception_handler (NSException *exception) {
//这里能够取到 NSException 信息
NSLog(@"***********************************************");
NSLog(@"%@",exception);
NSLog(@"%@",exception.callStackReturnAddresses);
NSLog(@"%@",exception.callStackSymbols);
NSLog(@"***********************************************");
}
复制代码
实现方式如: blog.csdn.net/u013896628/…缓存
block 捕获的是当前在block内部执行的外部局部变量的瞬时值, 为何说瞬时值呢? 看一下C++源码中得知, 其内部代码在捕获的同时安全
其实block底层生成了一个和外部变量相同名称的属性值若是内部修改值,其实修改的是捕获以前的值,其捕获的内部的值因代码只作了一次捕获,并无作再一次的捕获,因此block里面不能够修改值。bash
若是当前捕获的为对象类型,其block内部能够认为从新建立了一个指向当前对象内存地址的指针(堆),操控内部操做的东西均为同一块内存地址,因此能够修改当前内部的对象里面的属性,可是不能直接修改当前的指针(没法直接修改栈中的内容)(即从新生成一个新的内存地址)。其原理和捕获基本数据类型一致。
说白了, block内部能够修改的是堆中的内容, 但不能直接修改栈中的任何东西。
分类的实现原理是将category中的方法,属性,协议数据放在category_t结构体中,而后将结构体内的方法列表拷贝到类对象的方法列表中。 Category能够添加属性,可是并不会自动生成成员变量及set/get方法。由于category_t结构体中并不存在成员变量。经过以前对对象的分析咱们知道成员变量是存放在实例对象中的,而且编译的那一刻就已经决定好了。而分类是在运行时才去加载的。那么咱们就没法再程序运行时将分类的成员变量中添加到实例对象的结构体中。所以分类中不能够添加成员变量。
在往深一点的回答就是 类在内存中的位置是编译时期决定的, 以后再修改代码也不会改变内存中的位置,class_ro_t 的属性在运行期间就不能再改变了, 再添加方法是会修改class_rw_t 的methods 而不是class_ro_t 中的 baseMethods
- (void)foo{
[类 invokeOriginalMethod:self selector:_cmd];
}
+ (void)invokeOriginalMethod:(id)target selector:(SEL)selector {
uint count;
Method *list = class_copyMethodList([target class], &count);
for ( int i = count - 1 ; i >= 0; i--) {
Method method = list[i];
SEL name = method_getName(method);
IMP imp = method_getImplementation(method);
if (name == selector) {
((void (*)(id, SEL))imp)(target, name);
break;
}
}
free(list);
}
复制代码
###9. 什么是线程安全?
###10. 你接触到的项目,哪些场景运用到了线程安全?
答: 举例说明,12306 同一列火车的车票, 同一时间段多人抢票! 如何解决 互斥锁使用格式
synchronized(锁对象) { // 须要锁定的代码 }
注意:锁定1份代码只用1把锁,用多把锁是无效的
Tips: 互斥锁的优缺点
优势:能有效防止因多线程抢夺资源形成的数据安全问题
缺点:须要消耗大量的CPU资源
互斥锁的使用前提:多条线程抢夺同一块资源
相关专业术语:线程同步,多条线程按顺序地执行任务
互斥锁,就是使用了线程同步技术
Objective-C中的原子和非原子属性
OC在定义属性时有nonatomic和atomic两种选择
atomic:原子属性,为setter/getter方法都加锁(默认就是atomic)
nonatomic:非原子属性,不加锁
atomic加锁原理:
property (assign, atomic) int age;
- (void)setAge:(int)age
{
@synchronized(self) {
_age = age;
}
}
- (int)age {
int age1 = 0;
@synchronized(self) {
age1 = _age;
}
}
原子和非原子属性的选择
nonatomic和atomic对比
atomic:线程安全,须要消耗大量的资源
nonatomic:非线程安全,适合内存小的移动设备
iOS开发的建议
全部属性都声明为nonatomic
尽可能避免多线程抢夺同一块资源
尽可能将加锁、资源抢夺的业务逻辑交给服务器端处理,减少移动客户端的压力
atomic就必定能保证线程安全么?
不能,还须要更深层的锁定机制才能够,由于一个线程在连续屡次读取某条属性值的时候,与此同时别的线程也在改写值,这样仍是会读取到不一样的属性值! 或者 一个线程在获取当前属性的值, 另一个线程把这个属性释放调了, 有可能形成崩溃
复制代码
###11. 你实现过单例模式么? 你能用几种实现方案?
1. 运用GCD:
import "Manager.h"
implementation Manager
+ (Manager *)sharedManager {
static dispatch_once_t onceToken;
static Manager * sharedManager;
dispatch_once(&onceToken, ^{
sharedManager=[[Manager alloc] init];
});
return sharedManager;
}
end
注明:dispatch_once这个函数,它能够保证整个应用程序生命周期中某段代码只被执行一次!
2. 不使用GCD的方式:
static Manager *manager;
implementation Manager
+ (Manager *)defaultManager {
if(!manager)
manager=[[self allocWithZone:NULL] init];
return manager;
}
end
3. 正常的完整版本
+(id)shareInstance{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
if(_instance == nil)
_instance = [MyClass alloc] init];
});
return _instance;
}
//重写allocWithZone,里面实现跟方法一,方法二一致就行.
+(id)allocWithZone:(struct _NSZone *)zone{
return [self shareInstance];
}
//保证copy时相同
-(id)copyWithZone:(NSZone *)zone{
return _instance;
}
// 方法3建立的目的的是 为了方式开发者在调用单例的时候并无用shareInstance方法来建立 而是用的alloc 或者copy的形式建立形成单例不一致的状况
//
复制代码
//必须把static dispatch_once_t onceToken; 这个拿到函数体外,成为全局的.
+ (void)attempDealloc {
onceToken = 0; // 只有置成0,GCD才会认为它从未执行过.它默认为0,这样才能保证下次再次调用shareInstance的时候,再次建立对象.
_sharedInstance = nil;
}
dispatch_once_t 的工做原理是,static修饰会默认将其初始化为0, 当且仅当其为0的时候dispatch_once(&onceToken, ^{})这个函数才能被调用, 若是执行了这个函数 这个dispatch_once_t 静态变成- 1了 就永远不会被调用
复制代码
1.第一种方式,重写+allocWithZone:方法;
+ (instancetype)allocWithZone:(struct _NSZone *)zone {
static id instance = nil;
@synchronized (self) { // 互斥锁
if (instance == nil) {
instance = [super allocWithZone:zone];
}
}
return instance;
}
2.第二种方式,不用重写+allocWithZone:方法,而是直接用@synchronized 来保证线程安全,其它与上面这个方法同样;
+ (instancetype)sharedSingleton {
static id instance = nil;
@synchronized (self) {
if (!instance) {
instance = [[self alloc] init];
}
}
return instance;
}
复制代码
###12. 项目开发中,你用单例都作了什么?
答 :整个程序公用一份资源的时候 例如 :
- 设置单例类访问应用的配置信息
- 用户的我的信息登陆后用的NSUserDefaults存储,对登陆类进一步采用单例封装方便全局访问
- 防止一个单例对 应用 多处 对赞成本地数据库存进行操做
- 第一阶段:应用程序的服务器端把要发送的消息、目的iPhone的标识打包,发给APNS。
- 第二阶段:APNS在自身的已注册Push服务的iPhone列表中,查找有相应标识的iPhone,并把消息发送到iPhone。
- 第三阶段:iPhone把发来的消息传递给相应的应用程序,而且按照设定弹出Push通知。
首先是注册
- Device(设备)链接APNs服务器并携带设备序列号(UUID)
- 链接成功,APNs通过打包和处理产生devicetoken并返回给注册的Device(设备)
- Device(设备)携带获取的devicetoken发送到咱们本身的应用服务器
- 完成须要被推送的Device(设备)在APNs服务器和咱们本身的应用服务器的注册
推送过程
- 一、首先手机装有当前的app,而且保持有网络的状况下,APNs服务器会验证devicetoken,成功那个以后会处于一个长链接。 (这里会有面试问? 若是app也注册成功了, 也下载了,也赞成了打开推送功能, 这个时候在把App删除了, 还能接受推送了么? )
- 二、当咱们推送消息的时候,咱们的服务器按照指定格式进行打包,结合devicetoken 一块儿发送给APNs服务器,
- 三、APNs 服务器将新消息推送到iOS 设备上,而后在设备屏幕上显示出推送的消息。
- 四、iOS设备收到推送消息后, 会通知给咱们的应用程序并给予提示
// 推送过程以下图
###14. RunLoop的基础知识
答 : iOS中有五种RunLoop模式
NSDefaultRunLoopMode (默认模式,有事件响应的时候,会阻塞旧事件)
NSRunLoopCommonModes (普通模式,不会影响任何事件)
UITrackingRunLoopMode (只能是有事件的时候才会响应的模式)
还有两种系统级别的模式
一个是app刚启动的时候会执行一次
另一个是系统检测app各类事件的模式
复制代码
答 : 本来系统就有一个runloop在检测App内部的行为或事件,当输入源(用户的直接或者间接的操做)有“执行操做”的时候, 系统的runloop会监听输入源的状态, 进而在系统内部作一些对应的相应操做。 处理完成后,会自动回到睡眠状态, 等待下一次被唤醒,
RunLoop和线程的关系
RunLoop的做用就是用来管理线程的, 当线程的RunLoop开启以后,线程就会在执行完成任务后,进入休眠状态,随时等待接收新的任务,而不是退出。
为何只有主线程的runloop
是开启的
程序开启以后,要一直运行,不会退出。 说白了就是为了让程序不死
// 先建立一个线程用于测试
NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(play) object:nil];
[thread start];
// 保证一个线程永远不死
[[NSRunLoop currentRunLoop] addPort:[NSPort port] -forMode:NSDefaultRunLoopMode];
[[NSRunLoop currentRunLoop] run];
// 在合适的地方处理线程的事件处理
[self performSelector:@selector(test) onThread:thread withObject:nil waitUntilDone:NO];
复制代码
###15. weak属性?
1. 说说你理解weak属性?
复制代码
1.实现weak后,为何对象释放后会自动为nil?
runtime 对注册的类, 会进行布局,
对于 weak 对象会放入一个 hash 表中。
用 weak 指向的对象内存地址做为 key,
Value是weak指针的地址数组。
当释放的时候,其内部会经过当前的key找到全部的weak指针指向的数组
而后遍历这个数组把其中的数据设置为nil。
稍微详细的说:在内部底层源码也同时和当前对象相关联得SideTable, 其内部有三个属性, 一个是一把自旋锁,一个是引用计数器相关,一个是维护weak生命得属性得表
**SideTable**这个结构体同样的东西,能够花半个小时看一眼。
复制代码
objc中向一个nil对象发送消息将会发生什么? 首先 在寻找对象化的isa指针时就是0地址返回了, 因此不会有任何错误, 也不会错误
objc在向一个对象发送消息时,发生了什么?
- 首先是经过obj 的isa指针找到对应的class
- 先去操做对象中的缓存方法列表中objc_cache中去寻找 当前方法,若是找到就直接实现对应IMP
- 若是在缓存中找不到,则在class中找到对用的Method list中对用foo
- 若是class中没有找到对应的foo, 就会去superClass中去找
- 若是找到了对应的foo, 就会实现foo对应的IMP
缓存方法列表, 就是每次执行这个方法的时候都会作如此繁琐的操做这样太过于消耗性能,因此出现了一个objc_cache,这个会把当前调用过的类中的方法作一个缓存, 当前method_name做为key, method_IMP做为Value,当再一次接收到消息的时候,直接经过objc_cache去找到对应的foo的IMP便可, 避免每一次都去遍历objc_method_list
若是一直没有找到方法, 就会专用消息转发机制,机制以下
// 动态方法解析和转发
上面的例子若是foo函数一直没有被找到,一般状况下,会出现报错,可是在报错以前,OC的运行时给了咱们三次补救的机会
- Method resolution
- Fast forwarding
- Normal forwarding
1. Runtime 会发送 +resolveInstanceMethod: 或者 +resolveClassMethod: 尝试去 resolve(重启) 这个消息;
2. 若是 resolve 方法返回 NO,Runtime 就发送 -forwardingTargetForSelector: 容许你把这个消息转发给另外一个对象;
3. 若是没有新的目标对象返回, Runtime 就会发送 -methodSignatureForSelector: 和 -forwardInvocation: 消息。你能够发送 -invokeWithTarget: 消息来手动转发消息或者发送 -doesNotRecognizeSelector: 抛出异常。
复制代码
- 二者最明显的区别是 View能够接受并处理事件,而 Layer 不能够;
- 每一个 UIView 内部都有一个 CALayer 在背后提供内容的绘制和显示,而且 UIView 的尺寸样式都由内部的 Layer 所提供。二者都有树状层级结构,layer 内部有 SubLayers,View 内部有 SubViews.可是 Layer 比 View 多了个AnchorPoint
- 在 View显示的时候,UIView 作为 Layer 的 CALayerDelegate,View 的显示内容由内部的 CALayer 的 display
CALayer 是默认修改属性支持隐式动画的,在给 UIView 的 Layer 作动画的时候,View 做为 Layer 的代理,Layer 经过 actionForLayer:forKey:向 View请求相应的 action(动画行为)
- layer 内部维护着三分 layer tree,分别是 presentLayer Tree(动画树),modeLayer Tree(模型树), Render Tree (渲染树),在作 iOS动画的时候,咱们修改动画的属性,在动画的实际上是 Layer 的 presentLayer的属性值,而最终展现在界面上的实际上是提供 View的modelLayer
复制代码
- @property有两个对应的词,一个是 @synthesize,一个是 @dynamic。若是 @synthesize和 @dynamic都没写,那么默认的就是@syntheszie var = _var;
- @synthesize 的语义是若是你没有手动实现 setter 方法和 getter 方法,那么编译器会自动为你加上这两个方法。
- @dynamic 告诉编译器:属性的 setter 与 getter 方法由用户本身实现,不自动生成。(固然对于 readonly 的属性只需提供 getter 便可)。假如一个属性被声明为 @dynamic var,而后你没有提供 @setter方法和 @getter 方法,编译的时候没问题,可是当程序运行到 instance.var = someVar,因为缺 setter 方法会致使程序崩溃;或者当运行到 someVar = var 时,因为缺 getter 方法一样会致使崩溃。编译时没问题,运行时才执行相应的方法,这就是所谓的动态绑定。
复制代码
static关键字能够修饰函数和变量,做用以下:
**隐藏**
经过static修饰的函数或者变量,在该文件中,全部位于这条语句以后的函数均可以访问,而其余文件中的方法和函数则不行
**静态变量**
类方法不能够访问实例变量(函数),经过static修饰的实例变量(函数),能够被类 方法访问;
**持久**
static修饰的变量,能且只能被初始化一次;
**默认初始化**
static修饰的变量,默认初始化为0;
复制代码
- objc_msgSend(recicver, selecter..)
复制代码
1. runloop与线程是一一对应的,一个runloop对应一个核心的线程,为何说是核心的,是由于runloop是能够嵌套的,可是核心的只能有一个,他们的关系保存在一个全局的字典里。
2. runloop是来管理线程的,当线程的runloop被开启后,线程会在执行完任务后进入休眠状态,有了任务就会被唤醒去执行任务。runloop在第一次获取时被建立,在线程结束时被销毁。
3. 对于主线程来讲,runloop在程序一启动就默认建立好了。
4. 对于子线程来讲, runloop是懒加载的,只有当咱们使用的时候才会建立,因此在子线程用定时器要注意:确保子线程的runloop被开启,否则定时器不会回调。
复制代码
键值观察通知依赖于 NSObject 的两个方法: willChangeValueForKey: 和 didChangevlueForKey: 。在一个被观察属性发生改变以前, willChangeValueForKey: 必定会被调用,这就 会记录旧的值。而当改变发生后, didChangeValueForKey: 会被调用,继而 observeValueForKey:ofObject:change:context: 也会被调用。若是能够手动实现这些调用,就能够实现“手动触发”了。
+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key {
if ([key isEqualToString:@"age"]) {
return NO;
}
return [super automaticallyNotifiesObserversForKey:key];
}
- (void)setAge:(NSInteger)age {
if (age >= 18) {
[self willChangeValueForKey:@"age"];
_age = age;
[self didChangeValueForKey:@"age"];
}else {
_age = age;
}
}
复制代码
(1)首先会按照顺序依次查找setKey:方法和_setKey:方法,只要找到这两个方法当中的任何一个就直接传递参数,调用方法;
(2)若是没有找到setKey:和_setKey:方法,那么这个时候会查看accessInstanceVariablesDirectly方法的返回值,若是返回的是NO(也就是不容许直接访问成员变量),那么会调用setValue:forUndefineKey:方法,并抛出异常“NSUnknownKeyException”;
(3)若是accessInstanceVariablesDirectly方法返回的是YES,也就是说能够访问其成员变量,那么就会按照顺序依次查找 _key、_isKey、key、isKey这四个成员变量,若是查找到了,就直接赋值;若是依然没有查到,那么会调用setValue:forUndefineKey:方法,并抛出异常“NSUnknownKeyException”。
(1)首先会按照顺序依次查找getKey:、key、isKey、_key:这四个方法,只要找到这四个方法当中的任何一个就直接调用该方法;
(2)若是没有找到,那么这个时候会查看accessInstanceVariablesDirectly方法的返回值,若是返回的是NO(也就是不容许直接访问成员变量),那么会调用valueforUndefineKey:方法,并抛出异常“NSUnknownKeyException”;
(3)若是accessInstanceVariablesDirectly方法返回的是YES,也就是说能够访问其成员变量,那么就会按照顺序依次查找 _key、_isKey、key、isKey这四个成员变量,若是找到了,就直接取值;若是依然没有找到成员变量,那么会调用valueforUndefineKey方法,并抛出异常“NSUnknownKeyException”。
按照执行顺序排列:
1. initWithCoder:经过nib文件初始化时触发。
2. awakeFromNib:nib文件被加载的时候,会发生一个awakeFromNib的消息到nib文件中的每一个对象。
3. loadView:开始加载视图控制器自带的view。
4. viewDidLoad:视图控制器的view被加载完成。
5. viewWillAppear:视图控制器的view将要显示在window上。
6. updateViewConstraints:视图控制器的view开始更新AutoLayout约束。
7. viewWillLayoutSubviews:视图控制器的view将要更新内容视图的位置。
8. viewDidLayoutSubviews:视图控制器的view已经更新视图的位置。
9. viewDidAppear:视图控制器的view已经展现到window上。
10. viewWillDisappear:视图控制器的view将要从window上消失。
11. viewDidDisappear:视图控制器的view已经从window上消失。
复制代码
###22.网络协议
三次握手
**1.**客户端向服务端发起请求连接,首先发送SYN报文,SYN=1,seq=x,而且客户端进入SYN_SENT状态 **2.**服务端收到请求连接,服务端向客户端进行回复,并发送响应报文,SYN=1,seq=y,ACK=1,ack=x+1,而且服务端进入到SYN_RCVD状态 **3.**客户端收到确认报文后,向服务端发送确认报文,ACK=1,ack=y+1,此时客户端进入到ESTABLISHED,服务端收到用户端发送过来的确认报文后,也进入到ESTABLISHED状态,此时连接建立成功
- 哎!
- 嗯
- 给你
复制代码
为何须要三次握手: 为了防止已失效的链接请求报文段忽然又传送到了服务端,于是产生错误。假设这是一个早已失效的报文段,但server收到此失效的链接请求报文段后,就误认为是client再次发出的一个新的链接请求。因而就向client发出确认报文段,赞成创建链接。假设不采用“三次握手”,那么只要server发出确认,新的链接就创建了。因为如今client并无发出创建链接的请求,所以不会理睬server的确认,也不会向server发送数据。但server却觉得新的运输链接已经创建,并一直等待client发来数据。这样,server的不少资源就白白浪费掉了。
四次挥手
**1.**客户端向服务端发起关闭连接,并中止发送数据 **2.**服务端收到关闭连接的请求时,向客户端发送回应,我知道了,而后中止接收数据 **3.**当服务端发送数据结束以后,向客户端发起关闭连接,并中止发送数据 **4.**客户端收到关闭连接的请求时,向服务端发送回应,我知道了,而后中止接收数据
- 哎!
- 嗯
- 关了
- 好的
复制代码
为何须要四次挥手: 由于TCP是全双工通讯的,在接收到客户端的关闭请求时,还可能在向客户端发送着数据,所以不能再回应关闭连接的请求时,同时发送关闭连接的请求
HTTP和HTTPS有什么区别?
HTTPS的加密方式?
Https采用对称加密和非对称加密结合的方式来进行通讯。
Https不是应用层的新协议,而是Http通讯接口用SSL和TLS来增强加密和认证机制。
HTTP
HTTPS
GET在特定的浏览器和服务器对URL的长度是有限制的。 可是理论上是没有限制的
POST不是经过URL进行传值,理论上不受限制。
GET会把请求参数拼接到URL后面, 不安全,
POST把参数放到请求体里面, 会比GET相对安全一点, 可是因为能够窥探数据, 因此也不安全, 想更安全用加密。
GET比POST的请求速度快。缘由:Post请求的过程, 会现将请求头发送给服务器确认,而后才真正的发送数据, 而Get请求 过程会在连接创建后会将请求头和数据一块儿发送给服务器。 中间少了一步。 因此get比post 快
post的请求过程
三次握手以后 第三次会把post请求头发送
服务器返回100 continue响应
浏览器开始发送数据
服务器返回200 ok响应
###23. 有没有使用过performSelector?
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
Person *p = [[Person alloc] init];
// 默认person,没有实现eat方法,能够经过performSelector调用,可是会报错。
// 动态添加方法就不会报错
[p performSelector:@selector(eat)];
}
@end
@implementation Person
// **这里真是奇葩, 实在想不到何时才有这种使用场景, 我再外面找不到方法, 我再当前类里面直接在写一个方法就好咯,干吗要在这里写这个玩意, 还要写一个C语言的东西, 既然面试想问, 那咱就要会!**
// void(*)()
// 默认方法都有两个隐式参数,
void eat(id self,SEL sel)
{
NSLog(@"%@ %@",self,NSStringFromSelector(sel));
}
// 当一个对象调用未实现的方法,会调用这个方法处理,而且会把对应的方法列表传过来.
// 恰好能够用来判断,未实现的方法是否是咱们想要动态添加的方法
+ (BOOL)resolveInstanceMethod:(SEL)sel
{
if (sel == @selector(eat)) {
// 动态添加eat方法
// 第一个参数:给哪一个类添加方法
// 第二个参数:添加方法的方法编号
// 第三个参数:添加方法的函数实现(函数地址)
// 第四个参数:函数的类型,(返回值+参数类型) v:void @:对象->self :表示SEL->_cmd
class_addMethod(self, @selector(eat), eat, "v@:");
}
return [super resolveInstanceMethod:sel];
}
@end
复制代码
// 延时操做 和GCD的after 一个效果
[p performSelector:@selector(eat) withObject:nil afterDelay:4];
复制代码
你觉得完了? 错了,大几率面试官会问你,*** 上面这段代码放在子线程中 是什么样子的?为何?**
—首先 上面这个方法其实就是内部建立了一个NSTimer定时器,而后这个定时器会添加在当前的RunLoop中因此上面代码放到子线程中不会有任何定时器相关方法被执行,若是想要执行,开启当前线程便可 即
[[NSRunLoop currentRunLoop] run];
复制代码
// 完整调用
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(queue, ^{
// [[NSRunLoop currentRunLoop] run]; 放在上面执行时不能够的,由于当前只是开启了runloop 里面没有任何事件(source,timer,observer)也是开启失败的
[self performSelector:@selector(test) withObject:nil afterDelay:2];
[[NSRunLoop currentRunLoop] run];
});
// 由此我自行又作了一个测试, 把
[self performSelector:@selector(test)];
在子线程调用,是没有任何问题的。
// 我又测试了一下,
[self performSelector:@selector(test) withObject:nil afterDelay:2];
这个方法在主线程执行 打印线程是1
在子线程中调用打印线程 非1
复制代码
// 在子线程中开启NStimer,或者更改当前Runloop的Mode 为NSRunLoopCommonModes
[[NSRunLoop mainRunLoop]addTimer:timer forMode:NSRunLoopCommonModes];
// 利用CADisplayLink (iOS设备的屏幕刷新频率是固定的,CADisplayLink在正常状况下会在每次刷新结束都被调用,精确度至关高)
CADisplayLink *displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(logInfo)];
[displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
// 利用GCD
NSTimeInterval interval = 1.0;
_timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0));
dispatch_source_set_timer(_timer, dispatch_walltime(NULL, 0), interval * NSEC_PER_SEC, 0);
dispatch_source_set_event_handler(_timer, ^{
NSLog(@"GCD timer test");
});
dispatch_resume(_timer);
复制代码
###24. 为何AFN3.0中须要设置self.operationQueue.maxConcurrentOperationCount = 1;而AF2.0却不须要?
在请求完成后咱们须要对数据进行一些序列化处理,或者错误处理。若是咱们在主线中处理这些事情很明显是不合理的。不只会致使UI的卡顿,甚至受到默认的RunLoopModel的影响,咱们在滑动tableview的时候,会致使时间的处理中止。
这里时候咱们就须要一个子线程来处理事件和网络请求的回调了。可是,子线程在处理完事件后就会自动结束生命周期,这个时候后面的一些网络请求得回调咱们就没法接收了。因此咱们就须要开启子线程的RunLoop来保存线程的常驻。
固然咱们能够每次发起一个请求就开启一条子线程,可是这个想一下就知道开销有多大了。因此这个时候保活一条线程来对请求得回调处理是比较好的一个方案。
在3.x的AFN版本中使用的是NSURLSession进行封装。对比于NSURLConnection,NSURLSession不须要在当前的线程等待网络回调,而是可让开发者本身设定须要回调的队列。
因此在3.x版本中AFN使用了NSOperationQueue对网络回调的管理,而且设置maxConcurrentOperationCount为1,保证了最大的并发数为1,也就是说让网络请求串行执行。避免了多线程环境下的资源抢夺问题。
###25. autoreleasePool 在什么时候被释放?
//来自Apple文档,见参考
NSArray *urls = <# An array of file URLs #>;
for (NSURL *url in urls) {
@autoreleasepool {
NSError *error;
NSString *fileContents = [NSString stringWithContentsOfURL:urlencoding:NSUTF8StringEncoding error:&error];
}
// 若是循环次数很是多,并且循环体里面的对象都是临时建立使用的,就能够用@autoreleasepool 包起来,让每次循环结束时,能够及时释放临时对象的内存
// for 和 for in 里面是没有自动包装@autoreleasepool着的,而下面的方法是由@autoreleasepool自动包围的
[array enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
// 这里被一个局部@autoreleasepool包围着
}];
复制代码
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
NSString* str = [[[NSString alloc] initWithString:@"666"] autorelease];
[pool drain];
// 其做用于为drain 和 init 之间
复制代码
###26. iOS界面渲染机制? [这是很大的一个模块,里面牵扯不少东西, 耐心看下去]
首先iOS渲染视图的核心是Core Animation,其渲染层次依次为:图层树->呈现树->渲染树
一共三个阶段
CPU阶段(进行Frame布局,准备视图和图层之间的层级关系)
OpenGL ES阶段(iOS8之后改为Metal), (渲染服务把上面提供的图层上色,生成各类帧)
GPU阶段 (把上面操做的东西进行一些列的操做,最终展现到屏幕上面)
稍微详细说明
首先一个视图由CPU进行Frame布局,准备视图和图层的层及关系。
CUP会将处理视图和图层的层级关系打包,经过IPC(进程间的通讯)通道提交给渲染服务(OpenGL和GPU)
渲染服务首先将图层交给OpenGL进行纹理生成和着色,生成先后帧缓存,再根据硬件的刷新帧率,通常以设备的VSync信号和CADisplayLink(相似一个刷新UI专用的定时器)为标准,进行先后帧缓存的切换
最后,将最终 要显示在画面上的后帧缓存交给GPU,进行采集图片和形状,运行变换, 应用纹理混合,最终显示在屏幕上。
这里会出现一个面试题!!! 题目以下:
在科普一下 1.Core Animation Core Animation 在 RunLoop 中注册了一个 Observer,监听了 BeforeWaiting 和 Exit 事件。这个 Observer 的优先级是 2000000,低于常见的其余 Observer。当一个触摸事件到来时,RunLoop 被唤醒,App 中的代码会执行一些操做,好比建立和调整视图层级、设置 UIView 的 frame、修改 CALayer 的透明度、为视图添加一个动画;这些操做最终都会被 CALayer 捕获,并经过 CATransaction 提交到一个中间状态去(CATransaction 的文档略有提到这些内容,但并不完整)。当上面全部操做结束后,RunLoop 即将进入休眠(或者退出)时,关注该事件的 Observer 都会获得通知。这时 CA 注册的那个 Observer 就会在回调中,把全部的中间状态合并提交到 GPU 去显示;若是此处有动画,CA 会经过 DisplayLink 等机制屡次触发相关流程。
2.CPU渲染职能
3.GPU渲染职能 GPU会根据生成的先后帧缓存数据,根据实际状况进行合成,其中形成GPU渲染负担的通常是:离屏渲染,图层混合,延迟加载。
这里又会出现一个面试题!!! 一个UIImageView添加到视图上之后,内部如何渲染到手机上的?
图片显示分为三个步骤: 加载、解码、渲染、 一般,咱们程序员的操做只是加载,至于解码和渲染是由UIKit内部进行的。 例如:UIImageView显示在屏幕上的时候须要UIImage对象进行数据源的赋值。而UIImage持有的数据是未解码的压缩数据,当赋值的时候,图像数据会被解码变成RGB颜色数据,最终渲染到屏幕上。
看完上面的又来问题了! 关于UITableView优化的问题?(真他妈子子孙孙无穷尽也~) 先说形成UITableView滚动时候卡顿的的缘由有哪些?
在说关于UITableView的优化问题!
基础的
**** 解释一下为何减小离屏渲染操做?****
须要建立新的缓冲区
整个过程须要屡次切换上下文环境, 显示从当前的屏幕切换到离屏,等待离屏渲染结束后,将离屏缓冲区的渲染结果 显示到屏幕有上, 又要将上下文环境从离屏切换到当前屏幕,
那些操做会触发离屏渲染?
光栅化 layer.shouldRasterize = YES
遮罩layer.mask
圆角layer.maskToBounds = Yes,Layer.cornerRadis 大于0
阴影layer.shadowXXX
进阶的
高阶的
至于上面的那些基础的,涉及到渲染级别的本身说的时候悠着点,面试官若是想搞你的话,考一考你最上面的那些,CUP和GUP,以及openGL相关, 在考一下你进程通讯IPC,以及VSync信号啥的, 这些东西太鸡儿高深了,没点匠心 这东西还真搞不了,要想研究能够看看YYKit的做者写的一篇关于页面流畅的文章:blog.ibireme.com/2015/11/12/…
既然都是图形绘制了,那就再研究一下事件响应链&原理
传统的问法来了:UIView和CALayer的区别? 一般咱们这样回答:UIView能够响应用户事件,而CALayer不能处理事件
回答这个以前, 先回顾一下另一个经典面试题:事件响应链和事件传递?
基本概念:
响应链: 是由连接在一块儿的响应者(UIResponse子类)组成的,通常为第一响应着到application对象以及中间全部响应者一块儿组成的。
事件传递: 获取响应链以后, 将事件由第一响应者网application的传递过程
事件的分发和传递
当程序中发生触摸事件以后,系统会将事件添加到UIApplication管理的一个队列当中
UIApplication将处于任务队列最前端的事件向下分发 即UIWindow
UIWindow将事件向下分发,即UIView或者UIViewController
UIView首先看本身可否处理这个事件,触摸点是否在本身身上,本身的透明度是否大于0,01,userInteractionEnabled 是不是YES, Hidden实际是NO,若是这些都知足,那么继续寻找其子视图
遍历子控件,重复上面步骤
若是没有找到,那么本身就是改事件的处理者
若是本身不能处理,那么就不作任何处理 即视为没有合适的View能接收处理当前事件,则改事件会被废弃。
*** 怎么寻找当前触摸的是哪个View?*** 下面中两个方法
// 此方法返回的View是本次点击事件须要的最佳View
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
// 判断一个点是否落在范围内
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event
复制代码
事件传递给控件以后, 就会调用hitTest:withEvent方法去寻找更合适的View,若是当前View存在子控件,则在子控件继续调用hitTest:withEvent方法判断是不是合适的View, 若是还不是就一直遍历寻找, 找不到的话直接废弃掉。
// 由于全部的视图类都是继承BaseView
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
// 1.判断当前控件可否接收事件
if (self.userInteractionEnabled == NO || self.hidden == YES || self.alpha <= 0.01) return nil;
// 2. 判断点在不在当前控件
if ([self pointInside:point withEvent:event] == NO) return nil;
// 3.从后往前遍历本身的子控件
NSInteger count = self.subviews.count;
for (NSInteger i = count - 1; i >= 0; i--) {
UIView *childView = self.subviews[I];
// 把当前控件上的坐标系转换成子控件上的坐标系
CGPoint childP = [self convertPoint:point toView:childView];
UIView *fitView = [childView hitTest:childP withEvent:event];
if (fitView) { // 寻找到最合适的view
return fitView;
}
}
// 循环结束,表示没有比本身更合适的view
return self;
}
复制代码
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event
复制代码
-(BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch
{
if([touch.view isKindOfClass:[XXXXcell class]])
{
return NO;
}
return YES;
}
复制代码