面试iOS面试准备iOS面试专题

2020iOS开发工程师面试题汇总(内含面试技巧)-看完BATJ

2020-07-27  本文已影响0人  iOS开发面试总结

iOS面试题

本面试题为个人使用版本,如后续流传出去,请转发的朋友务必注释一下,答案正确性有待商榷,本人的答案不代表权威,仅仅是个人理解。 文章内部有写混乱,将就着看吧。另外大部分图片加载不出来,,MARKDown格式也不太统一(各平台不一样),由于博主太懒不想改,不过不影响最终效果,

一、硬技术篇

1.对象方法和类方法的区别?

引伸1. 如果在类方法中调用self 会有什么问题?

引申2. 讲一下对象,类对象,元类,跟元类结构体的组成以及他们是如何相关联的?

引申3. 为什么对象方法中没有保存对象结构体里面,而是保存在类对象的结构体里面?

引申4. 类方法存在哪里? 为什么要有元类的存在?

引申5. 什么是野指针?

  __unsafe_unretained UIView *testObj = [[UIView alloc] init];
   NSLog(@"testObj 指针指向的地址:%p 指针本身的地址:%p", testObj, &testObj);
   [testObj setNeedsLayout];
   // 可以看到NSlog打印不会闪退,调用[testObj setNeedsLayout];会闪退

引申6. 如何检测野指针?

这是网友总结的,有兴趣的可以看下:www.jianshu.com/p/9fd4dc046… 本人,也就是看看乐呵,其原理啥的,见仁见智吧。开发行业太j8难了!

引申7. 导致Crash的原因有哪些?

1、找不到方法的实现unrecognized selector sent to instance 2、KVC造成的crash 3、EXC_BAD_ACCESS 4、KVO引起的崩溃 5、集合类相关崩溃 6、多线程中的崩溃 7、Socket长连接,进入后台没有关闭 8、Watch Dog超时造成的crash 9、后台返回NSNull导致的崩溃,多见于Java做后台服务器开发语言

引申8. 不使用第三方,如何知道已经上线的App崩溃问题, 具体到哪一个类的哪一个方法的?

大致实现方式如下。

- (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/…

iOS中内省的几个方法?

引申 2. ==、 isEqualToString、isEqual区别?

引申 3. class方法和object_getClass方法有什么区别?

3.深拷贝和浅拷贝

4.NSString类型为什么要用copy修饰 ?

5.iOS中block 捕获外部局部变量实际上发生了什么?__block 中又做了什么?


6.iOS Block为什么用copy修饰?

7. 为什么分类中不能创建属性Property(runtime除外)?

引伸:分类可以添加那些内容?
引伸:Category 的实现原理?
引申 使用runtime Associate方法关联的对象,需要在主对象dealloc的时候释放么?
引申 能否向编译后得到的类中增加实例变量, 能否向运行时创建的类中添加实力变量?
引申 主类执行了foo方法,分类也执行了foo方法,在执行的地方执行了foo方法,主类的foo会被覆盖么? 如果想只想执行主类的foo方法,如何去做?
- (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);
}

8. load 和 initilze 的调用情况,以及子类的调用顺序问题?


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的形式创建造成单例不一致的情况

//   

引申1. 单例是怎么销毁的?

//必须把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了  就永远不会被调用

引申2. 不使用dispatch_once 如何 实现单例


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存储,对登录类进一步采用单例封装方便全局访问
  • 防止一个单例对 应用 多处 对同意本地数据库存进行操作

13.APNS的基本原理

  • 第一阶段:应用程序的服务器端把要发送的消息、目的iPhone的标识打包,发给APNS。
  • 第二阶段:APNS在自身的已注册Push服务的iPhone列表中,查找有相应标识的iPhone,并把消息发送到iPhone。
  • 第三阶段:iPhone把发来的消息传递给相应的应用程序,并且按照设定弹出Push通知。

首先是注册

  • Device(设备)连接APNs服务器并携带设备序列号(UUID)
  • 连接成功,APNs经过打包和处理产生devicetoken并返回给注册的Device(设备)
  • Device(设备)携带获取的devicetoken发送到我们自己的应用服务器
  • 完成需要被推送的Device(设备)在APNs服务器和我们自己的应用服务器的注册

推送过程

  • 1、首先手机装有当前的app,并且保持有网络的情况下,APNs服务器会验证devicetoken,成功那个之后会处于一个长连接。 (这里会有面试问? 如果app也注册成功了, 也下载了,也同意了打开推送功能, 这个时候在把App删除了, 还能接受推送了么? )
  • 2、当我们推送消息的时候,我们的服务器按照指定格式进行打包,结合devicetoken 一起发送给APNs服务器,
  • 3、APNs 服务器将新消息推送到iOS 设备上,然后在设备屏幕上显示出推送的消息。
  • 4、iOS设备收到推送消息后, 会通知给我们的应用程序并给予提示

// 推送过程如下图

14. RunLoop的基础知识

答 : iOS中有五种RunLoop模式

NSDefaultRunLoopMode (默认模式,有事件响应的时候,会阻塞旧事件)
NSRunLoopCommonModes (普通模式,不会影响任何事件)
UITrackingRunLoopMode (只能是有事件的时候才会响应的模式)

还有两种系统级别的模式
一个是app刚启动的时候会执行一次
另外一个是系统检测app各种事件的模式

答 : 原本系统就有一个runloop在检测App内部的行为或事件,当输入源(用户的直接或者间接的操作)有“执行操作”的时候, 系统的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**这个结构体一样的东西,可以花半个小时看一眼。

延伸

  - 首先是通过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: 抛出异常。

16.UIView和CALayer是什么关系?

    - 两者最明显的区别是 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

16. @synthesize 和 @dynamic 分别有什么作用

- @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 方法同样会导致崩溃。编译时没问题,运行时才执行相应的方法,这就是所谓的动态绑定。

17. static有什么作用?

static关键字可以修饰函数和变量,作用如下:

**隐藏**

通过static修饰的函数或者变量,在该文件中,所有位于这条语句之后的函数都可以访问,而其他文件中的方法和函数则不行

**静态变量**

类方法不可以访问实例变量(函数),通过static修饰的实例变量(函数),可以被类   方法访问;

**持久**

static修饰的变量,能且只能被初始化一次;

**默认初始化**

static修饰的变量,默认初始化为0;

18. objc在向一个对象发送消息时,发生了什么?

- objc_msgSend(recicver, selecter..)

19. runloop是来做什么的?runloop和线程有什么关系?主线程默认开启了runloop么?子线程呢?

1\. runloop与线程是一一对应的,一个runloop对应一个核心的线程,为什么说是核心的,是因为runloop是可以嵌套的,但是核心的只能有一个,他们的关系保存在一个全局的字典里。
2\. runloop是来管理线程的,当线程的runloop被开启后,线程会在执行完任务后进入休眠状态,有了任务就会被唤醒去执行任务。runloop在第一次获取时被创建,在线程结束时被销毁。
3\. 对于主线程来说,runloop在程序一启动就默认创建好了。
4\. 对于子线程来说, runloop是懒加载的,只有当我们使用的时候才会创建,所以在子线程用定时器要注意:确保子线程的runloop被开启,不然定时器不会回调。  

20. 如何手动触发一个value的KVO

键值观察通知依赖于 NSObject 的两个方法: willChangeValueForKey: 和 didChangevlueForKey: 。在一个被观察属性发生改变之前, willChangeValueForKey: 一定会被调用,这就 会记录旧的值。而当改变发生后, didChangeValueForKey: 会被调用,继而 observeValueForKey:ofObject:change:context: 也会被调用。如果可以手动实现这些调用,就可以实现“手动触发”了。

引申 0 如何给系统KVO设置筛选条件?

+ (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.通过KVC修改属性会触发KVO么?直接修改成员变量呢 ?

引申 kvc的底层实现?

(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”。

21. ViewController生命周期

按照执行顺序排列:
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是全双工通信的,在接收到客户端的关闭请求时,还可能在向客户端发送着数据,因此不能再回应关闭链接的请求时,同时发送关闭链接的请求

引申

  1. HTTP和HTTPS有什么区别?

    • HTTP协议是一种使用明文数据传输的网络协议。
    • HTTPS协议可以理解为HTTP协议的升级,就是在HTTP的基础上增加了数据加密。在数据进行传输之前,对数据进行加密,然后再发送到服务器。这样,就算数据被第三者所截获,但是由于数据是加密的,所以你的个人信息让然是安全的。这就是HTTP和HTTPS的最大区别。
  2. HTTPS的加密方式?

    • Https采用对称加密和非对称加密结合的方式来进行通信。

    • Https不是应用层的新协议,而是Http通信接口用SSL和TLS来加强加密和认证机制。

      • 对称加密: 加密和解密都是同一个钥匙
      • 非对称加密:密钥承兑出现,分为公钥和私钥,公钥加密需要私钥解密,私钥加密需要公钥解密

HTTP和HTTPS的建立连接的过程?

HTTP

HTTPS

HTTP协议中GET和POST的区别


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];
[[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在子线程执行?

引申 为什么说NSTimer不准确?

// 在子线程中开启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却不需要?

AFNetworking 2.0 和3.0 的区别?

2.x版本常驻线程的分析

3.x版本不在常驻线程的分析?

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 之间

子线程中的autorelease变量什么时候释放?

autoreleasepool是如何实现的?

26. iOS界面渲染机制? [这是很大的一个模块,里面牵扯很多东西, 耐心看下去]

首先iOS渲染视图的核心是Core Animation,其渲染层次依次为:图层树->呈现树->渲染树

程序卡顿的原因?

这里会出现一个面试题!!! 题目如下:

在科普一下 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的优化问题!

基础的


进阶的

高阶的

至于上面的那些基础的,涉及到渲染级别的自己说的时候悠着点,面试官如果想搞你的话,考一考你最上面的那些,CUP和GUP,以及openGL相关, 在考一下你进程通信IPC,以及VSync信号啥的, 这些东西太鸡儿高深了,没点匠心 这东西还真搞不了,要想研究可以看看YYKit的作者写的一篇关于页面流畅的文章:blog.ibireme.com/2015/11/12/…

卡顿检测的方法


继续

既然都是图形绘制了,那就再研究一下事件响应链&原理

传统的问法来了:UIView和CALayer的区别? 通常我们这样回答:UIView可以响应用户事件,而CALayer不能处理事件


回答这个之前, 先回顾一下另外一个经典面试题:事件响应链和事件传递?

基本概念:

// 此方法返回的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;  
}

结交人脉

最后推荐个我的iOS交流群:789143298
'有一个共同的圈子很重要,结识人脉!里面都是iOS开发,全栈发展,欢迎入驻,共同进步!(群内会免费提供一些群主收藏的免费学习书籍资料以及整理好的几百道面试题和答案文档!)

上一篇下一篇

猜你喜欢

热点阅读