阿里字节:一套高效的iOS面试题(修正版)

Posted 码上work

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了阿里字节:一套高效的iOS面试题(修正版)相关的知识,希望对你有一定的参考价值。

& class_copyPropertyList区别

  • class_rw_tclass_ro_t 的区别

  • category如何被加载的,两个category的load方法的加载顺序,两个category的同名方法的加载顺序

  • category & extension区别,能给NSObject添加Extension吗,结果如何

  • 之前做了什么

  • IMPSELMethod的区别和使用场景

  • loadinitialize方法的区别什么?在继承关系中他们有什么区别

  • 方法有什么区别?

  • 修饰与否的区别?block的变量截获

  • block在修改NSMutableArray,需不需要添加__block

  • 的优劣

  • 视图&图像相关

    1. AutoLayout的原理,性能如何

    2. UIView & CALayer的区别

    3. 事件响应链

    4. drawrect & layoutsubviews调用时机

    5. UI的刷新原理

    6. 隐式动画 & 显示动画区别

    7. 什么是离屏渲染

    8. imageName &  imageWithContentsOfFile区别

    9. 多个相同的图片,会重复加载吗

    10. 图片是什么时候解码的,如何优化

    11. 图片渲染怎么优化

    12. 如果GPU的刷新率超过了ios屏幕60Hz刷新率是什么现象,怎么解决

    性能优化

    1. 如何做启动优化,如何监控

    2. 如何做卡顿优化,如何监控

    3. 如何做耗电优化,如何监控

    4. 如何做网络优化,如何监控

    开发证书

    1. 苹果使用证书的目的是什么

    2. AppStore安装app时的认证流程

    3. 开发者怎么在debug模式下把app安装到设备呢

    架构设计

    典型源码的学习

    只是列出一些iOS比较核心的开源库,这些库包含了很多高质量的思想,源码学习的时候一定要关注每个框架解决的核心问题是什么,还有它们的优缺点,这样才能算真正理解和吸收

    1. AFN

    2. SDWebImage

    3. JSPatch、Aspects(虽然一个不可用、另一个不维护,但是这两个库都很精炼巧妙,很适合学习)

    4. Weex/RN, 笔者认为这种前端和客户端紧密联系的库是必须要知道其原理的

    5. CTMediator、其他router库,这些都是常见的路由库,开发中基本上都会用到

    6. 圈友们在评论下面补充吧

    架构设计
    1. 手动埋点、自动化埋点、可视化埋点

    2. MVC、MVP、MVVM设计模式

    3. 常见的设计模式

    4. 单例的弊端

    5. 常见的路由方案,以及优缺点对比

    6. 如果保证项目的稳定性

    7. 设计一个图片缓存框架(LRU)

    8. 如何设计一个git diff

    9. 设计一个线程池?画出你的架构图

    10. 你的app架构是什么,有什么优缺点、为什么这么做、怎么改进

    其他问题

    1. PerformSelector & NSInvocation优劣对比

    2. oc怎么实现多继承?怎么面向切面(可以参考Aspects深度解析-iOS面向切面编程)

    3. 哪些bug会导致崩溃,如何防护崩溃

    4. 怎么监控崩溃

    5. app的启动过程(考察LLVM编译过程、静态链接、动态链接、runtime初始化)

    6. 沙盒目录的每个文件夹划分的作用

    7. 简述下match-o文件结构

    系统基础知识

    1. 进程和线程的区别

    2. HTTPS的握手过程

    3. 什么是中间人攻击?怎么预防

    4. TCP的握手过程?为什么进行三次握手,四次挥手

    5. 堆和栈区的区别?谁的占用内存空间大

    6. 加密算法:对称加密算法和非对称加密算法区别

    7. 常见的对称加密和非对称加密算法有哪些

    8. MD5、Sha1、Sha256区别

    9. charles抓包过程?不使用charles4G网络如何抓包

    数据结构与算法

    对于移动开发者来说,一般不会遇到非常难的算法,大多以数据结构为主,笔者列出一些必会的算法,当然有时间了可以去LeetCode上刷刷题

    LeetCode地址:https://leetcode.com/

    1. 八大排序算法

    2. 栈&队列

    3. 字符串处理

    4. 链表

    5. 二叉树相关操作

    6. 深搜广搜

    7. 基本的动态规划题、贪心算法、二分查找

    总结

    这些都是笔者收集的加上自身面试的一些经验总结,后期会持续收集补充,欢迎圈内的高手补充你的答案或者高质量问题

    准备面试是一方面,对于非面试的iOS开发者来说更适用于检验自己,发起进阶之路。另外知识点是琐碎的,但是真的能全部弄懂并把琐碎的知识点融会贯通,构建起自己的知识体系,你就进阶一层。

    最后欢迎关注笔者公众号:【码上work】,致力于浅显易懂的方式讲解移动/全栈开发、有趣算法、计算机基础知识等,帮你构建完整的知识体系,一起成为顶级开发者。公众号回复12345有精品学习资料(iOS、全栈、机器学习、区块链)


    阿里字节:高效 iOS 面试题之 Block

    👇👇关注后回复 “进群” ,拉你进程序员交流群👇👇

    来源;

    https://www.sunyazhou.com/2020/09/Block/

    block

    这一篇我们来研究一下objc的block并回答一下面试中的下列问题:

    1. block的内部实现,结构体是什么样的

    2. block是类吗,有哪些类型

    3. 一个int变量被 __block 修饰与否的区别?block的变量截获

    4. block在修改NSMutableArray,需不需要添加__block

    5. 怎么进行内存管理的

    6. block可以用strong修饰吗

    7. 解决循环引用时为什么要用__strong__weak修饰

    8. block发生copy时机

    9. Block访问对象类型的auto变量时,在ARCMRC下有什么区别

    在回答所有问题之前我们需要了解一些block背景相关的知识. 如下:

    • 如何查看Block的内部实现,也就是说转换成背后真正的c/c++代码的block是什么样的?以及转换格式或者原理等.

    • 关于变量的作用域

    Objective-C 转 C++的方法

    下面我写了个示例TestClass.m类其中block代码如下

    OC代码:

    @interface TestClass ()
    @end
    
    @implementation TestClass
    - (void)testMethods {
        void (^blockA)(int a) = ^(int a) {
            NSLog(@"%d",a);
        };
        if (blockA) {
            blockA(1990);
        }
    }
    @end

    经过上述转换操作我们在TestClass.cpp中最下面发现如下代码

    C++代码

    // @interface TestClass ()
    /* @end */
    
    
    // @implementation TestClass
    
    
    struct __TestClass__testMethods_block_impl_0 {
      struct __block_impl impl;
      struct __TestClass__testMethods_block_desc_0* Desc;
      __TestClass__testMethods_block_impl_0(void *fp, struct __TestClass__testMethods_block_desc_0 *desc, int flags=0) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
      }
    };
    
    static void __TestClass__testMethods_block_func_0(struct __TestClass__testMethods_block_impl_0 *__cself, int a) {
    
            NSLog((NSString *)&__NSConstantStringImpl__var_folders_wx_b8tcry0j24dbhr7zlzjq3v340000gn_T_TestClass_ee18d3_mi_0,a);
        }
    
    static struct __TestClass__testMethods_block_desc_0 {
      size_t reserved;
      size_t Block_size;
    } __TestClass__testMethods_block_desc_0_DATA = { 0, sizeof(struct __TestClass__testMethods_block_impl_0)};
    
    static void _I_TestClass_testMethods(TestClass * self, SEL _cmd) {
        void (*blockA)(int a) = ((void (*)(int))&__TestClass__testMethods_block_impl_0((void *)__TestClass__testMethods_block_func_0, &__TestClass__testMethods_block_desc_0_DATA));
        if (blockA) {
            ((void (*)(__block_impl *, int))((__block_impl *)blockA)->FuncPtr)((__block_impl *)blockA, 1990);
        }
    }

    上面的代码生成是通过如下操作:

    打开终端,cd到TestClass.m所在文件夹,使用如下命令

    clang -rewrite-objc TestClass.m

    就会在当前文件夹内自动生成对应的TestClass.cpp文件

    注意: 如果提示clang没有的话 需要安装, 输入如下

    brew install clang-format
       或者
       brew link clang-forma
       然后输入 下面命令测试是否好使
       clang-format --help

    通过上述代码我们发现Block的其实是一个结构体类型

    底层实现 会根据 __类名__方法名_block_impl_下标 (0代表这个方法或者这个类中第0个block 下面如果还有将会 第1个block 第2个…)

    struct __类名__方法名_block_impl_下标

    关于变量的作用域

    c语言的函数中可能使用的参数变量种类

    • 参数类型

    • 自动变量(局部变量)

    • 静态变量(静态局部变量)

    • 静态全局变量

    • 全局变量

    由于存储区域特殊,这其中有三种变量是可以在任何时候以任何状态调用的.

    • 静态变量

    • 静态全局变量

    • 全局变量

    而其他两种,则是有各自相应的作用域,超过作用域后,会被销毁.

    1.block的内部实现,结构体是什么样的

    看了上面的背景知识我们来回到一下这个问题

    block的内部实现如下:

    struct __TestClass__testMethods_block_impl_0 {
      struct __block_impl impl; //成员变量
      struct __TestClass__testMethods_block_desc_0* Desc; //desc 结构体声明
      // 构造函数
      // fp 函数指针
      // desc 静态全局变量初始化的 __main_block_desc_ 结构体实例指针
      // flags block 的负载信息(引用计数和类型信息),按位存储.
      __TestClass__testMethods_block_impl_0(void *fp, struct __TestClass__testMethods_block_desc_0 *desc, int flags=0) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
      }
    };
    //将来被调用的block内部的代码:block值被转换为C的函数代码
    //这里,*__cself 是指向Block的值的指针,也就相当于是Block的值它自己(相当于C++里的this,
    OC里的self)
    //__cself 是指向__TestClass__testMethods_block_impl_0结构体实现的指针
    //Block结构体就是__TestClass__testMethods_block_impl_0结构体.Block的值就是通过__TestClass__testMethods_block_impl_0构造出来的
    static void __TestClass__testMethods_block_func_0(struct __TestClass__testMethods_block_impl_0 *__cself, int a) {
     NSLog((NSString *)&__NSConstantStringImpl__var_folders_wx_b8tcry0j24dbhr7zlzjq3v340000gn_T_TestClass_9f58f7_mi_0,a);
    }
    
    static struct __TestClass__testMethods_block_desc_0 {
      size_t reserved;
      size_t Block_size;
    } __TestClass__testMethods_block_desc_0_DATA = { 0, sizeof(struct __TestClass__testMethods_block_impl_0)};
    
    static void _I_TestClass_testMethods(TestClass * self, SEL _cmd) {
        void (*blockA)(int a) = ((void (*)(int))&__TestClass__testMethods_block_impl_0((void *)__TestClass__testMethods_block_func_0, &__TestClass__testMethods_block_desc_0_DATA));
        if (blockA) {
            ((void (*)(__block_impl *, int))((__block_impl *)blockA)->FuncPtr)((__block_impl *)blockA, 1990);
        }
    }

    可以看得出来__TestClass__testMethods_block_impl_0有3个部分组成

    • impl 函数指针指向__TestClass__testMethods_block_impl_0

    struct __block_impl {
        void *isa;
        int Flags;
        int Reserved;  //今后版本升级所需的区域
        void *FuncPtr; //函数指针
      };
    • Desc 指向__TestClass__testMethods_block_impl_0的Desc指针,用于描述当前这个block的附加信息的,包括结构体的大小等等信息.

    static struct __TestClass__testMethods_block_desc_0 {
        size_t reserved; //今后升级版本所需区域
        size_t Block_size; //block的大小
      } __TestClass__testMethods_block_desc_0_DATA = { 0, sizeof(struct __TestClass__testMethods_block_impl_0)};
    • __TestClass__testMethods_block_impl_0()构造函数,也就是该block的具体实现

    __TestClass__testMethods_block_impl_0(void *fp, struct __TestClass__testMethods_block_desc_0 *desc, int flags=0) {  
      impl.isa = &_NSConcreteStackBlock;  
      impl.Flags = flags;  
      impl.FuncPtr = fp;  
      Desc = desc; 
      }

    此结构体中

    • isa指针保持这所属类的结构体的实例的指针.

    • struct __TestClass__testMethods_block_impl_0相当于Objective-C类对象的结构体

    • _NSConcreteStackBlock相当于Block的结构体实例,也就是说block其实就是Objective-C对于闭包的对象实现

    讲到这里block的内部实现你看懂了吗?结构体是什么样的你记住了吗? 其实看着繁琐 细心观察代码会发现还是比较简单的.

    2.block是类吗,有哪些类型?

    block也算是个类,因为它有isa指针,block.isa的类型包括

    • _NSConcreteGlobalBlock 跟全局变量一样,设置在程序的数据区域(.data)中

    • _NSConcreteStackBlock栈上(前面讲的都是栈上的 block)

    • _NSConcreteMallocBlock 堆上

    这个isa可以按位运算

    3.一个int变量被 __block 修饰与否的区别?block的变量截获

    __block 修饰与否的区别

    用一段示例代码来解答这个问题吧:

    __block int a = 10;
    int b = 20;   
    
    PrintTwoIntBlock block = ^(){  
        a -= 10;   
        printf("%d, %d\\n",a,b);
    };   
    
    block();//0 20    
    
    a += 20;
    b += 30;   
    
    printf("%d, %d\\n",a,b);//20 50   
    block();/10 20

    通过__block修饰int a,block体中对这个变量的引用是指针拷贝,它会作为block结构体构造参数传入到结构体中且复制这个变量的指针引用,从而达到可以修改变量的作用.

    int b没有被__block修饰,block内部对b是值copy.所以在block内部修改b不影响外部b的变化.

    block的变量截获

    通过如下代码我们来观察要一下变量的捕获

    blk_t blk;
    {    
        id array = [NSMutableArray new];   
        blk = [^(id object){       
            [array addObject:object];       
            NSLog(@"array count = %ld",[array count]);  
        } copy];
    }
    blk([NSObject new]);
    blk([NSObject new]);
    blk([NSObject new]);

    输出打印

    block_demo[28963:1629127] array count = 1
    block_demo[28963:1629127] array count = 2
    block_demo[28963:1629127] array count = 3

    我们把上面的代码翻译成C++看下

    struct __main_block_impl_0 {  
      struct __block_impl impl;  
      struct __main_block_desc_0* Desc;  
      id array;//截获的对象  
      __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, id _array, int flags=0) : array(_array) {   
        impl.isa = &_NSConcreteStackBlock;    
        impl.Flags = flags;    
        impl.FuncPtr = fp;    
        Desc = desc;
      }
    };

    在Objc中,C结构体里不能含有被__strong修饰的变量,因为编译器不知道应该何时初始化和废弃C结构体。但是Objc的运行时库能够准确把握Block从栈复制到堆,以及堆上的block被废弃的时机,在实现上是通过__TestClass__testMethods_block_copy_0函数和__TestClass__testMethods_block_dispose_0函数进行的

    static void __TestClass__testMethods_block_copy_0(struct __TestClass__testMethods_block_impl_0*dst, struct __TestClass__testMethods_block_impl_0*src) {   
        _Block_object_assign((void*)&dst->array, (void*)src->array, 3/*BLOCK_FIELD_IS_OBJECT*/);}  
    static void __TestClass__testMethods_block_dispose_0(struct __TestClass__testMethods_block_impl_0*src) {   
        _Block_object_dispose((void*)src->array, 3/*BLOCK_FIELD_IS_OBJECT*/);
    }
    • _Block_object_assign相当于retain操作,将对象赋值在对象类型的结构体成员变量中.

    • _Block_object_dispose相当于release操作.

    这两个函数调用的时机是在什么时候呢?

    函数被调用时机
    __TestClass__testMethods_block_copy_0从栈复制到堆时
    __TestClass__testMethods_block_dispose_0堆上的Block被废弃时
    什么时候栈上的Block会被复制到堆呢?
    • 调用block的copy函数时。

    • Block作为函数返回值返回时。

    • 将Block赋值给附有__strong修饰符id类型的类或者Block类型成员变量时。

    • 方法中含有usingBlock的Cocoa框架方法或者GCD的API中传递Block时。

    什么时候Block被废弃呢?
    • 堆上的Block被释放后,谁都不再持有Block时调用dispose函数。

    以上就是变量被block捕获的内容

    4.block在修改NSMutableArray,需不需要添加__block

    • 如修改NSMutableArray的存储内容的话,是不需要添加__block修饰的。

    • 如修改NSMutableArray对象的本身,那必须添加__block修饰。

    5.怎么进行内存管理的?

    在上面Block的构造函数__TestClass__testMethods_block_impl_0中的isa指针指向的是&_NSConcreteStackBlock,它表示当前的Block位于栈区中.

    block内存操作存储域/存储位置copy操作的影响
    _NSConcreteGlobalBlock程序的数据区域什么也不做
    _NSConcreteStackBlock从栈拷贝到堆
    _NSConcreteMallocBlock引用计数增加

    全局Block:_NSConcreteGlobalBlock的结构体实例设置在程序的数据存储区,所以可以在程序的任意位置通过指针来访问,它的产生条件:

    • 记述全局变量的地方有block语法时.

    • block不截获的自动变量.

    以上两个条件只要满足一个就可以产生全局Block.

    • 栈Block:_NSConcreteStackBlock在生成Block以后,如果这个Block不是全局Block,那它就是栈Block,生命周期在其所属的变量作用域内.(也就是说如果销毁取决于所属的变量作用域).如果Block变量和__block变量复制到了堆上以后,则不再会受到变量作用域结束的影响了,因为它变成了堆Block.

    • 堆Block:_NSConcreteMallocBlock将栈block复制到堆以后,block结构体的isa成员变量变成了_NSConcreteMallocBlock

    6.block可以用strong修饰吗?

    在ARC中可以,因为在ARC环境中的block只能在堆内存或全局内存中,因此不涉及到从栈拷贝到堆中的操作.

    在MRC中不行,因为要有拷贝过程.如果执行copy用strong的话会crash, strong是ARC中引入的关键字.如果使用retain相当于忽视了block的copy过程.

    7.解决循环引用时为什么要用__strong__weak修饰?

    首先因为block捕获变量的时候 结构体构造时传入了self,造成了默认的引用关系,所以一般在block外部对操作对象会加上__weak,在Block内部使用__strong修饰符的对象类型的自动变量,那么当Block从栈复制到堆的时候,该对象就会被Block所持有,但是持有的是我们上面加了__weak所以行程了比消此长的链条,刚好能解决block延迟销毁的时候对外部对象生命周期造成的影响.如果不这样做很容易造成循环引用.

    8.block发生copy时机?

    在ARC中,编译器将创建在栈中的block会自动拷贝到堆内存中,而block作为方法或函数的参数传递时,编译器不会做copy操作.

    • 调用block的copy函数时。

    • Block作为函数返回值返回时。

    • 将Block赋值给附有__strong修饰符id类型的类或者Block类型成员变量时。

    • 方法中含有usingBlock的Cocoa框架方法或者GCD的API中传递Block时。

    9.Block访问对象类型的auto变量时,在ARC和MRC下有什么区别?

    ARC下会对这个对象强引用,MRC下不会

    转自:掘金 iOS沐橙君

    https://juejin.cn/post/7026199465912434725

    -End-

    最近有一些小伙伴,让我帮忙找一些 面试题 资料,于是我翻遍了收藏的 5T 资料后,汇总整理出来,可以说是程序员面试必备!所有资料都整理到网盘了,欢迎下载!

    点击👆卡片,关注后回复【面试题】即可获取

    在看点这里好文分享给更多人↓↓

    以上是关于阿里字节:一套高效的iOS面试题(修正版)的主要内容,如果未能解决你的问题,请参考以下文章

    iOS知识点手写回顾记录

    iOS知识点手写回顾记录

    刷面试题阿里毕玄:来测试下你的Java编程能力 - 题解 16-21

    1353 道,阿里 + 腾讯 + 字节 + 滴滴 + 美团 内卷 面试题及答案

    字节跳动Andorid岗25k+的面试题,含BATJM大厂

    吃透了这一套2020阿里,美团,字节跳动Java 面试真题,你离 BAT还远吗?