面试题

作者: 凯歌948 | 来源:发表于2020-11-10 00:03 被阅读0次

    队列和线程的关系

    1.往主队列提交Block,无论是sync,还是async,都是在主线程中执行。
    2.往非主队列中提交,如果是sync,会在当前提交Block的线程中执行。如果是async,则会在分线程中执行。

    GCD的死锁

    sync的阻塞机制:

    1. sync提交Block,首先是阻塞的当前提交Block的线程(简单理解下就是阻塞sync之后的代码)。
    2. 而在队列中,轮到sync提交的Block,仅仅阻塞串行queue,而不会阻塞并行queue。(dispatch_barrier_(a)sync除外,我们后面会讲到。)

    我们了解了sync的阻塞机制,再结合发生死锁的根本原因来自于互相等待,我们用下面一句话来总结一下,会引起GCD死锁的行为:
    如果同步(sync)提交一个Block到一个串行队列,而提交Block这个动作所处的线程,也是在当前队列,就会引起死锁。

    dispatch_barrier_async, 这个想必大家也知道是干嘛用的,如果不知道,我也大概讲讲:
    它的作用可以用一个词概括--承上启下,它保证此前的任务都先于自己执行,此后的任务也迟于自己执行。当然它的作用导致它只有在并行队列中有意义。

    dispatch_barrier_sync这个方法和dispatch_barrier_async作用几乎一样,都可以在并行queue中当做栅栏。
    唯一的区别就是:dispatch_barrier_sync有GCD的sync共有特性,会阻塞提交Block的当前线程,而dispatch_barrier_async是异步提交,不会阻塞。

    dispatch_sync,我们来讲讲它和dispatch_barrier_sync的区别。二者因为是sync提交,所以都是阻塞当前提交Block线程。
    而它俩唯一的区别是:dispatch_sync并不能阻塞并行队列。
    而dispatch_barrier_sync可以阻塞并行队列(栅栏作用的体现)

    load 和 initialize 的区别?

    +load:
    1、只要程序启动就会将所有类的代码加载到内存中(在main函数执行之前), 放到代码区(无论该类有没有被使用到都会被调用)
    2、+load方法会在当前类被加载到内存的时候调用, 有且仅会调用一次
    3、当父类和子类都实现+load方法时, 会先调用父类的+load方法, 再调用子类的+load方法
    4、先加载原始类,再加载分类的+load方法
    5、子类实现或不实现 load ,父类都会调用load
    6、多个类都实现+load方法,+load方法的调用顺序,与Compile Sources中出现的顺序一致

    +initialize:
    1、当类第一次被使用的时候就会调用(创建类对象的时候)
    2、initialize方法在整个程序的运行过程中只会被调用一次, 无论你使用多少次这个类都只会调用一次
    3、initialize用于对某一个类进行一次性的初始化
    4、先调用父类的initialize再调用子类的initialize
    5、当子类未实现initialize方法时,会把父类的实现继承过来调用一遍,再次之前父类的initialize方法会被优先调用一次
    6、当有多个Category都实现了initialize方法,会覆盖类中的方法,只执行一个(会执行Compile Sources 列表中最后一个Category 的initialize方法)

    _objc_msgForward 函数是做什么的?直接调用会发生什么问题?

    当对象没有实现某个方法 ,会调用这个函数进行方法转发。
    (某方法对应的IMP没找到,会返回这个函数的IMP去执行)

    1.调用resolveInstanceMethod:方法,允许用户在此时为该Class动态添加实现。如果有实现了,则调用并返回。如果仍没实现,继续下面的动作。
    2.调用forwardingTargetForSelector:方法,尝试找到一个能响应该消息的对象。如果获取到,则直接转发给它。如果返回了nil,继续下面的动作。
    3.调用methodSignatureForSelector:方法,尝试获得一个方法签名。如果获取不到,则直接调用doesNotRecognizeSelector抛出异常。
    4.调用forwardInvocation:方法,将地3步获取到的方法签名包装成Invocation传入,如何处理就在这里面了。

    如果直接调用这个方法,就算实现了想调用的方法,也不会被调用,会直接走消息转发步骤。

    简述下 Objective-C 中调用方法的过程:

    1. Objective-C是动态语言,每个方法在运行时会被动态转为消息发送,即:objc_msgSend(receiver, selector),整个过程介绍如下:
    2. objc在向一个对象发送消息时,runtime库会根据对象的isa指针找到该对象实际所属的类
    3. 然后在该类中的方法列表以及其父类方法列表中寻找方法运行
    4. 如果,在最顶层的父类(一般也就NSObject)中依然找不到相应的方法时,程序在运行时会挂掉并抛出异常 unrecognized selector sent to XXX
      但是在这之前,objc的运行时会给出三次拯救程序崩溃的机会。

    如何实现 dispatch_once?

    + (instancetype)sharedInstance
    {
        /*定义相应类实例的静态变量;
        意义:函数内定义静态变量,无论该函数被调用多少次,
             在内存中只初始化一次,并且能保存最后一次赋的值
        */
        static ClassName *instance = nil;
        /*定义一个dispatch_once_t(其实也就是整型)静态变量,
        意义:作为标识下面dispatch_once的block是否已执行过。
             static修饰会默认将其初始化为0,当值为0时才会执行block。
             当block执行完成,底层会将onceToken设置为1,这也就是为什
             么要传onceToken的地址(static修饰的变量可以通过地址修改
             onceToken的值),同时底层会加锁来保证这个方法是线程安全的
        */
        static dispatch_once_t onceToken;
        /*只要当onceToken == 0时才会执行block,否则直接返回静态变量instance*/
        dispatch_once(&onceToken, ^{
            instance = [[ClassName alloc] init];
            //...
        });
        return instance;
    }
    

    输入一棵二叉树的根结点,求该树的深度?

    如果一棵树只有一个结点,它的深度为1。 如果根结点只有左子树而没有右子树, 那么树的深度应该是其左子树的深度加1,同样如果根结点只有右子树而没有左子树,那么树的深度应该是其右子树的深度加1. 如果既有右子树又有左子树, 那该树的深度就是其左、右子树深度的较大值再加1。

    public static int treeDepth(BinaryTreeNode root) {
        if (root == null) {
            return 0;
        }
        int left = treeDepth(root.left);
        int right = treeDepth(root.right);
        return left > right ? (left + 1) : (right + 1);
    }
    

    GCD容易让人迷惑的几个小问题
    给iOS中高级求职者的一份面试题解答

    相关文章

      网友评论

          本文标题:面试题

          本文链接:https://www.haomeiwen.com/subject/ehqdbktx.html