2021年3月iOS面试题

一、基础

1.如何令自己所写的对象具有拷贝功能?

  • 让对象遵守NSCopying 协议,实现copyWithZoon方法。
  • 如果对象有可变版本,则要遵守NSMutableCopying协议,实现MutableCopyWithZoon这个方法。

2.是如何理解weak属性的?

  • Runtime会对weak属性进行内存布局,构建hash表:以weak属性对象内存地址为key,weak属性值(weak自身地址)为value。
  • 当对象引用计数为0 dealloc时,会将weak属性值自动置nil
  • weak 属性只能用于修饰的对象,在设置新值时,既不保留新值,也不释放旧值。
  • 常用于非拥有关系,比如delegate。

3.swift 的mutating关键字的使用?

  • Swift中的enum 和 structure 是值类型,不能直接修改里面的值,如果要修改,则可以使用mutating来修饰。

4.UIView 和UILayer的关系

  • UIView是属于UIKit框架下的,UILayer是属于Quartz Core 框架
  • UIView 继承自UIRseponder, UILayer直接继承NSObject
  • 所以,UIView是可以响应触摸事件的,而UILayer主要负责绘制UI
  • UIView更像是CALayer的管理器,访问UIView的frame、bounds 实际就是在访问CALayer。

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

  • @synthesize 语义是如果没有实现set、get方法,那么就编译器会自动帮你生成set、get方法。
  • @dynamic 语义是需要自己实现set、get方法,如果没有实现,程序编译的时候没有问题,在调用点语法的时候就会奔溃。

6.动态绑定

程序在运行时,对象被确定后,这个对象的类型就被确定了,这个对象的属性和响应方法也会被确定。

7.Category、Extension、继承的区别

Category:
  • 给类添加方法的时候,如果跟类存在同名的方法,会覆盖类的原始方法,优先级为分类->本类->父类;
  • 分类是给原有类添加方法的,分类的结构体中没有属性列表,只有方法列表,不能添加属性,实际可以通过runtime 添加set、get方法实现添加属性
  • 多个分类中都有和原有类同名的方法时,那么调用的方法时执行哪一个方法由编译器决定,编译器会执行最后一个参与编译的分类中的方法
Extension:
  • 类扩展可以增加实例变量和方法,但是实例变量是私有的,方法只能由本类来完成实现。
  • 类扩展中申明的方法没有被实现,则编译器会报错,因为类扩展是在编译阶段被添加到类中的。
继承:
  • 继承者拥有父类全部的属性和方法。
  • 调用父类同名的方法,可以选择性的覆盖掉,当扩展的方法和原 有类同名,且需要调用父类的方法时,可以选择继承。

8.为什么代理要用weak?delegate 和dataSource有什么区别?block和代理的区别?

  • weak 可以避免循环引用,指明不持有delegate这个对象,销毁由外部控制。
  • delegate 响应的主要是用户交互事件,dataSource主要是数据操作的代理方法。
  • Block相对于代理,更轻量级一些,能够直接访问上下文,代码通常也是写在一块,便于阅读,缺点是容易导致循环引用。
  • 代理的方式,需要遵守协议和实现协议方法,每个代理方法都是分开的,不便于阅读,使用繁琐一点,好处是weak申明的情况下不用担心造成循环引用。

9.id 和NSObject *的区别

  • NSObject 包含了一些方法,需要实现协议,可以用id来表示,但是不能用id来表示NSObject。
  • id在编译时不会被检查,而NSObject在编译时编译器会检查是否有错误。
  • id可以是任何对象,包括不是NSObject的对象,如:NSProxy类
  • id可以不用带“*” ,NSObject需要带“*”

10.使用系统的block API(如UIView 的动画时),是否要考虑循环引用?

  • 不需要
  • UIView的block会立即执行,动画的延时不能决定block的执行时机,始终是瞬间执行,UIView层的block只是提供了 data 数据层的变化,动画执行是将“原有状态”和“block状态”做一个差值。

11.@property 申明的NSString、NSArray、NSDictionary 经常使用copy关键字,为什么?改用strong关键字,可能会造成什么问题?

  • 这些不可变类型的类,有可变类型的子类赋值的情况,如果使用copy,赋值一个可变数组,可变数组即使修改后,原有数组也不会可变;
  • 如果是strong修饰,会随着赋值的可变数组改变而变化。

12. static有什么作用

  • 修饰变量:

    • 全局静态变量,作用域为申明的文件内部使用,其他文件用Extern也无法使用,除非在本文件申明时使用 Extern;
    • 局部静态变量,作用域为函数内部使用,由于static修饰的变量存放在静态区,所以函数运行结束,这个静态局部变量也不会被销毁,下次使用还是上一次的值。
  • 是修饰函数:

    • 静态函数,指的不是存储方式,而是指作用域仅限于本文件,不用担心自己定义的函数与其他文件的函数同名,OC中不用担心这一点,只要不公开就行。

13.Block的循环引用、内部修改外部变量、三种block?

  • self强引用block,block强引用self
  • 内部修改外部变量:
    • block不允许修改外部变量的值,外部变量指的是栈中指针内存的地址。
    • _ _Block的作用是只要观察到变量被block使用,就把外部变量在栈中的内存地址放到堆中。
  • 三种block:NSStackBlock(栈block)、NSGlobalBlock(全局block)、NSMollocBlock(堆block)

14.对于Objective-C,你认为它最大的优点和最大的不足是什么?对于不足之处,现在有没有可用的方法绕过这些不足来实现需求。如果可以的话,你有没有考虑或者实践过重新实现OC的一些功能,如果有,具体会如何做?

  • 最大的优点:OC的运行时特性。
  • 不足:没有命名空间,对于命名冲突,可以用长命名法或特殊前缀解决。
  • 不支持多继承。

15. 你实现过一个框架或者库以供别人使用么?如果有,请谈一谈构建框架或者库时候的经验;如果没有,请设想和设计框架的public的API,并指出大概需要如何做、需要注意一些什么方面,来使别人容易地使用你的框架。

  • 做法:抽象和封装,方便调用者使用,尽量根据实际情况设计API的颗粒度,从调用者的角度看,只需要关注结果。
  • 注意事项:
    • 程序应该在适当的时候通知调用者,如处理完成、出错。
    • 在框架内部去构建对象的关系,通过抽象让其更加健壮和易于修改。
    • API的说明文档要清晰易懂。

16. block在ARC中和传统的MRC中的行为和用法有没有什么区别,需要注意些什么?如何避免循环引用?

  • 使用block是要注意循环引用
  • MRC下:__block修饰的变量,并不改变引用计数,但是block内部对引入的外部对象,会更改引用计数。所以要及时对block进行release.
  • ARC下 :__block修饰的引用计数会增加,同时block内部持有的对象引用计数会增加,会导致循环引用,所以要用weak弱引用

17. property 默认参数是那些?

值类型:atomic assign readwrite
引用类型:atomic strong readwrite

18.atomic ?atomic和nonatomic的区别?

  • atomic:原子属性(线程安全),保证同一时间只有一个线程能够写入(但是同一个时间多个线程都可以取值),atomic 本身就有一把锁(自旋锁),但是只保证了setter和getter的原子性,并非真正的线程安全,线程安全需要自己写代码保证。

  • 如:同时有两个线程在执行,一个是读写线程另一个是在执行别的操作,这样还是保证不了线程的安全。

  • 如:不是调用setter/getter方法,而是直接对对象内部属性作修改、字符串拼接、数组增删都不能保证线程安全。

  • atomic和nonatomic区别:用来决定编译器生成的getter和setter是否为原子操作。atomic提供多线程安全,是描述该变量是否支持多线程的同步访问,如果选择了atomic 那么就是说,系统会自动的创建lock锁,锁定变量。nonatomic禁止多线程,变量保护,提高性能。

19.Block变量的截获?

  • block如果要访问block以外定义的变量,对基本数据类型的局部变量、静态变量、全局变量、全局静态变量和对象变量的接受方式是不一样的。
  • 局部变量是值截获,在block里如果修改变量,也是无效的
    NSInteger num = 3;
    
    NSInteger(^block)(NSInteger) = ^NSInteger(NSInteger n){
        
        return n*num;
    };
    
    num = 1;
    
    NSLog(@"%zd",block(2)); // 这里的输出是6而不是2
  • 全局变量,静态全局变量截获:对于全局变量和静态全局变量,block直接访问其值,而不进行截获。也就是说,block执行的时候,该全局变量或静态全局变量是什么值,就用什么值
  • 一般情况下,如果我们要对block截获的局部变量进行赋值操作需添加__block
    修饰符,而对全局变量,静态变量是不需要添加__block修饰符的。
    另外,block里访问self或成员变量都会去截获self。

20.NSDictionary 的key有要求吗?

遵守以下协议的类型才可以作为 NSDictionary 的key

  • NSCopying
  • NSMutableCopying,
  • NSSecureCoding
  • NSFastEnumeration

21.new 的作用是什么?

  • 向计算机(堆区)申请内存空间
  • 初始化实例变量
  • 返回申请空间的首地址

22.ARC为什么会有内存泄漏?

  • 循环引用:block 、NSTimer、delegate
  • 非OC对象申请到内存没有释放
  • 大次数循环内存暴涨

23.数据结构有哪些?

常用的数据结构:数组、链表、堆、栈、树、图、散列表、队列

24.iOS设计的原则有哪些?

  • 单一职责:一个类应该只有一种职责,一个方法只能做一件事
  • 开闭原则:不应该改动原有类来实现新的需求
  • 依赖倒置原则:依赖抽象,而不应该依赖实现细节
  • 接口分离原则:一个接口里面不能包含同类职责的方法,接口责任划分明确,符合高内聚低耦合的思想。
  • 迪米特法则:一个类应该只和它的成员变量,方法的输入,返回参数中的类作交流,而不应该引入其他的类(间接交流)
  • 里氏替换原则:不能重写父类的非抽象方法

25.iOS面向对象的特性,重写和重载的区别?

  • 重写:子类不想继承使用父类的方法,通过重写覆盖掉
  • 重载:方法一样但是参数不一样,可以发生在同类和子类中,但是OC不能重载,swift可以。

26.iOS设计模式有哪几种?

  • 单例模式
  • 工厂模式
  • 代理模式
  • 观察者模式
  • MVP
  • MVC
  • MVVM

27.iOS内存中有多少区,每一区的含义和用法?

  • 栈区:由编译器自动分配释放,存放函数的参数值,局部变量。
  • 堆区:由程序员分配和释放,如果程序员不释放,那么会在程序结束的时候,可能会由系统回收
  • 全局区:存放全局变量和静态变量,程序结束后由系统释放
  • 常量区:存放字符串常量,程序结束后由系统释放
  • 代码区:存放二进制代码,程序结束后由系统释放

28.OC类的结构体指针了解?里面有哪些属性?类方法存在哪里,实例方法在哪里?

  • 有一个isa指针
  • property 属性列表
  • methodList 方法列表
  • protocol 协议列表
  • cache 快速查找方法
  • 发送消息时,runtime会根据这个对象的isa指针找到所属的类,通过selector遍历方法列表,找到对应的method,接着cache缓存该方法,以便下次调用该方法能快速响应。

29.+load和+initialize区别?

1、调用时机不同
+load是在类编译时调用,+initialize是在类第一次发送消息时调用
2、方法调用方式不同
+load是根据方法地址直接调用,+initialize是通过objc_msgSend消息机制调用
3、调用次数不同
+load方法只会调用一次,而父类的+initialize有可能会调用多次
4、调用顺序不同
+load的调用顺序跟类的编译顺序有关,+initialize是跟类的第一次发送有关
5、分类的影响
如果分类实现了+initialize,会覆盖类本身的+initialize调用,而对+load没有影响

30.一个NSObject对象分配多少内存?

  • 系统分配了16个字节给NSObject对象(通过malloc_size函数获得)
  • 但NSObject对象内部只使用了8个字节的空间(64bit环境下,可以通过class_getInstanceSize函数获得)

31. NSTimer 如何解决循环引用?

image.png

解决办法: 由于循环引用的起因是target,则可以包装一个target,让target是另一个对象,而不是ViewController即可.
1.创建一个集成NSObject的分类TimerWeakTarget,创建类方法---开启定时器的方法
2.TimerWeakTarget.m文件中
3.在下面我们封装的类的方法中,我们将开启定时器的方法 [NSTimer scheduledTimerWithTimeInterval:interval target:timer selector:@selector(fire:) userInfo:userInfo repeats:repeats];中的target换掉了,换成了 本类的对象,timer.在OneViewController中开启定时器的时候直接调用这个类方法,就不会造成循环引用.


image.png

32. setNeedsLayout 刷新UI的时机

  • setNeedsLayout
    标记为需要重新布局,异步调用layoutIfNeeded刷新布局,不立即刷新,在下一轮runloop结束前刷新,对于这一轮runloop之内的所有布局和UI上的更新只会刷新一次,layoutSubviews一定会被调用。
  • layoutIfNeeded
    如果有需要刷新的标记,立即调用layoutSubviews进行布局(如果没有标记,不会调用layoutSubviews)。

关键点

  • layoutIfNeeded不一定会调用layoutSubviews方法。
  • setNeedsLayout一定会调用layoutSubviews方法(有延迟,在下一轮runloop结束前)。
  • 如果想在当前runloop中立即刷新,调用顺序应该是
[self setNeedsLayout];
[self layoutIfNeeded];

反之可能会出现布局错误的问题。

二、底层

1.main()之前的过程有哪些?

  • 加载可执行文件(.o 的文件集合)
  • 加载动态链接库,rebase指针调整(解决动态库的虚拟内存地址冲突)和bind符号绑定
  • Objc运行时初始处理,包括Objc相关类的注册、Category注册、Selector的唯一性检查
  • 执行+load方法、attribute(constructor)修饰的函数调用、创建C++的静态全局变量

2.KVO的基本原理?手动触发KVO?swift如何实现KVO?

  • 监听某个类的属性对象第一次被观察时,系统会在运行期动态的创建一个继承该对象的一个派生类,派生类会重写基类的setter方法,重写的setter方法会负责在调用原有类的setter方法前后通知所有观察对象值的更改,最后会把isa指针指向这个创建的子类,对象就变成子类的实例。
  • 键值观察通知依赖于两个方法:willChangeValueForKey: 和 didChangevlueForKey:,实现这两个方法即可手动触发KVO
  • 直接willSet/didSet实现

3.Swift下如何使用KVC

  • 属性前加@objc关键字修饰
  • 先调用构造函数再使用KVC方法
  • 属性不能使用private
  • 基本数据类型一定要赋初始值

4.swift有哪些模式匹配?

  • 通配符模式
  • 标识符模式
  • 值绑定模式
  • 元组模式
  • 枚举case模式
  • 可选模式
  • 类型转换模式
  • 表达式模式

5.OC在向一个对象发送消息时,发生了什么?在向一个nil对象发送消息时,会发生什么?

  • Runtime会根据对象的isa指针找到实际的所属类,遍历该类方法列表和父类的方法列表,如果一直到根类都找不到实现方法,就走消息转发机制一旦找到,就执行实现方法。
  • 如果像一个nil发送消息时,在寻找对象的地址时已经返回0了,所以程序不会奔溃,具体的返回值:数据类型 返回 0,结构体返回值全为0,对象返回nil。

6.静态库的原理是什么?有没有自己实现过静态编译库,遇到了哪些问题?

  • 原理:
    • 静态库实际是一些目标文件(可执行文件.o)的集合,一般以.a结尾。
    • 在创建可执行程序的时候,静态库被链接到程序代码,被主程序调用的函数目标文件连同主程序组合成单一的可执行程序。
    • 静态库只在程序链接时起作用,最终的执行程序脱离静态库运行。
  • 问题:
    • 适配多个ARM指令集问题,需要不同指令集下编译的.a文件进行合并。
    • 编译环境的配置问题,需要告诉使用者依赖的系统库。
  • 静态库和动态库区别
    • 所谓静态和动态是相对编译期和运行期的
    • 静态库:链接时完整地拷贝至可执行文件中,被多次使用就有多份冗余拷贝。
    • 动态库:链接时不复制,程序运行时由系统动态加载到内存,供程序调用,系统只加载一次,多个程序共用,节省内存。

7.runloop是用来做什么的?runloop和线程有什么关系?主线程默认开启了runloop 了吗?子线程呢?runloop有哪几种工作模式?

  • Runloop 是协调和调度事件的。
  • Runloop和线程是一一对应的关系,是用来管理线程的,每个线程都有一个对应的runloop,当线程的runloop创建后,线程会在执行完任务后进入休眠状态,等待下一个任务来唤醒其去执行,runloop也会消失。
  • 主线程默认是程序一启动就开启了runloop,子线程的runloop是懒加载的,使用时才会去创建。

总共是有五种CFRunLoopMode:

kCFRunLoopDefaultMode:默认模式,主线程是在这个运行模式下运行

UITrackingRunLoopMode:跟踪用户交互事件(用于 ScrollView 追踪触摸滑动,保证界面滑动时不受其他Mode影响)

UIInitializationRunLoopMode:在刚启动App时第进入的第一个 Mode,启动完成后就不再使用

GSEventReceiveRunLoopMode:接受系统内部事件,通常用不到

kCFRunLoopCommonModes:伪模式,不是一种真正的运行模式,是同步Source/Timer/Observer到多个Mode中的一种解决方案
NSTimer 工作的模式为:DefaultMode、TrackingRunLoopMode、CommonModes下,CommonModes是一个集合,包含DefaultMode、TrackingRunLoopMode,如果timer既要在静态页面和滑动页面使用,那么就把timer运行在CommonModes模式。

8.不手动指定autorelease的情况下,一个autorelease对象会在什么情况下释放,比如在VC的viewDidLoad里面创建?

  • autorelease对象是在当前的runloop迭代结束时释放的。
  • 它能够释放的原因是系统在每个runloop迭代中都加入了自动释放池push和pop。

9.OC的消息转发机制完整的代码实现

20200407160626381.png
  • 所属类动态方法解析 resolveInstanceMethod
    resolveClassMethod。
  • 备援接收者
    当对象所属类不能动态添加方法后,runtime就会询问当前的接受者是否有其他对象可以处理这个未知的selector
    forwardingTargetForSelector
  • 消息重定向
    当没有备援接收者时,就只剩下最后一次机会,那就是消息重定向
    返回一个方法签名 methodSignatureForSelector
    封装为NSInvocation对象
  • (void)forwardInvocation: (NSInvocation*)invocation;。
  • 如果以上都没有找到实现方法,则执行doseNotRecognize ,消息转发失败。

10.以+ scheduledTimerWithTimeInterval...的方式触发的定时器,在滑动页面时,定时器为什么会暂停回调?如何解决?

  • 在滑动界面的时候,当前的runloop mode 由default变成了Traucking模式,定时器本来是工作在default模式,所以会暂停了。
  • 解决办法其一是:把定时器放在commonModes模式;

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

分别前后手动调用valueWillChange 和valueDidChange两个方法

12.如何定位和分析项目中影响性能的地方?如何进行性能优化?

  • 通过instruments 的Time Profile工具查看哪部分程序最耗时,通过Leaks查找内存是否泄漏。
    -优化:
    • 使用ARC管理内存,避免忘记内存释放而引起的内存泄漏
    • 正确的使用reuseIdentifie,复用UITabview、UICollectionView的cell ,避免在显示时,创建新的cell ,iOS6 以后 footer、header也需要复用,
    • 尽量把views设置为不透明,系统会优化一些渲染过程和提高性能,设置为透明时会导致blending(混合),会引入更多的计算,影响APP的性能。
    • 避免过于庞大的XIB,加载的时候就会放在内存中,如果有些view没有使用到,就会浪费资源
    • 不要阻塞主线程,需要消耗很大资源或者耗时时,可以使用异步处理。
    • 图片的大小应该要和UIImageView的大小保持一致,图片的缩放也会造成资源损耗
    • 合理选择容器的使用
      • NSArray:有序的一组值,使用index 查找很快,使用value查找很慢,插入/删除很慢
      • Dictionary:存储键值对,用键来查找很快
        NSset:无序的一组值,值来查找很快,插入、删除很快
    • 服务端和APP中打开gzip压缩,在网络交互的时候提高效率

13.autoreleasePool 释放的时机

  • 第一种情况:

    • 每个runloop中都创建一个Autorelease Pool,并在runloop的末尾进行释放,所以,一般情况下,每个接受autorelease消息的对象,都会在下个runloop开始前被释放。
    • 也就是说,在一段同步的代码中执行过程中,生成的对象接受autorelease消息后,一般是不会在代码段执行完成前释放的。
  • 第二种情况:

    • 让autorelease提前生效:@autoreleasepool {},生成的对象出了大括号以后,就会释放。

14.自旋锁和互斥锁的区别?

  • 相同点:都能保证同一时间只有一个线程访问共享资源。都能保证线程安全。
  • 不同点:
    • 互斥锁:互斥锁会休眠,如果共享数据已经有其他线程加锁了,线程会进入休眠状态等待锁。一旦被访问的资源被解锁,则等待资源的线程会被唤醒。
    • 自旋锁:自旋锁会忙等,如果共享数据已经有其他线程加锁了,线程会以死循环的方式等待锁,一旦被访问的资源被解锁,则等待资源的线程会立即执行。

15.单例优缺点?

  • 主要优点:
    • 提供了对唯一实例的受控访问。
    • 由于在系统内存中只存在一个对象,因此可以节约系统资源,对于一些需要频繁创建和销毁的对象单例模式无疑可以提高系统的性能。
    • 允许可变数目的实例。
  • 主要缺点:
    • 由于单利模式中没有抽象层,因此单例类的扩展有很大的困难。
    • 单例类的职责过重,在一定程度上违背了“单一职责原则”。
    • 滥用单例将带来一些负面问题,如为了节省资源将数据库连接池对象设计为的单例类,可能会导致共享连接池对象的程序过多而出现连接池溢出。
    • 如果实例化的对象长时间不被利用,系统会认为是垃圾而被回收,这将导致对象状态的丢失。

16.Command+B 和Cammand+R中间发生了什么?

  • 编译过程
    • 预处理: 宏替换、头文件导入、注释处理、条件编译
    • 词法分析:将代码转化为符合特定语言的词法单元
    • 语法检查:
    • 生成汇编代码
    • 汇编器:将汇编代码转化机器代码,输出.O目标文件
    • 链接link :将.O文件和 dyld、.a、tbd文件链接起来生成Mach-O文件
  • 运行过程
    • 加载Mach-O文件
    • 加载动态链接库
    • rebase指针调整,解决虚拟内存地址冲突,bind(符号绑定)
    • runtime初始化,ObjC类注册、初始化类对象
    • 执行类和分类的+load方法,c++静态初始化contribute 检查

17.内存管理方案TaggedPointer

比较下面两段代码的输出结果
代码1

  
  for (int i = 0; i<10000; i++) {
      dispatch_async(self.queue, ^{
          self.nameStr = [NSString stringWithFormat:@"a"];  // alloc 堆 iOS优化 - taggedpointer
           NSLog(@"%@",self.nameStr);
      });
  }

代码2

    self.queue = dispatch_queue_create("com.aaa.cn", DISPATCH_QUEUE_CONCURRENT);
    for (int i = 0; i<10000; i++) {
        dispatch_async(self.queue, ^{
            self.nameStr = [NSString stringWithFormat:@"内存管理,TaggedPointer,和谐学习不急不躁"];
            NSLog(@"%@",self.nameStr);
        });
    }

结果:
代码1正常运行
代码2发生崩溃

  • 代码2为什么会崩溃?
    iOS使用引用计数管理对象,由于多线程的影响,会在某个瞬间多条线程对同一对象释放,从而导致过度释放。
  • 代码1为什么正常运行?
    nameStr 是一个NSTaggedPointerString 类型 ,代码2的属性nameStr 是__NSCFString 类型,相同的代码只是在字符串的内容不一样就导致了崩溃,主要是因为对NSString类型做了优化,称为小对象。

18.[self class] 与 [super class]

下面的代码输出什么?

@implementation Son : Father
   - (id)init
   {
       self = [super init];
       if (self) {
           NSLog(@"%@", NSStringFromClass([self class]));
           NSLog(@"%@", NSStringFromClass([super class]));
       }
       return self;
   }
   @end

NSStringFromClass([self class]) = Son
NSStringFromClass([super class]) = Son

  • 在调用[super class]的时候,runtime会去调用objc_msgSendSuper方法,而不是objc_msgSend;
  • objc_msgSendSuper的工作原理应该是这样的:
    从objc_super结构体指向的superClass父类的方法列表开始查找selector,找到后以objc->receiver去调用父类的这个selector。注意,最后的调用者是objc->receiver,而不是super_class!

那么objc_msgSendSuper最后就转变成:

// 注意这里是从父类开始msgSend,而不是从本类开始
objc_msgSend(objc_super->receiver, @selector(class))
 
/// Specifies an instance of a class.  这是类的一个实例
    __unsafe_unretained id receiver;   
 
 
// 由于是实例调用,所以是减号方法
- (Class)class {
    return object_getClass(self);
}
  • 由于找到了父类NSObject里面的class方法的IMP,又因为传入的入参objc_super->receiver = self。self就是son,调用class,所以父类的方法class执行IMP之后,输出还是son,最后输出两个都一样,都是输出son。

三、线程和网络

1.串行和并行,并行和并发,异步和同步的区别?

  • 串行和并行:
    • 串行指的是多个任务顺序执行,只有当一个任务完成时,才能进行下一个任务。
    • 并行是指多个任务同一时刻执行,异步是多个任务并行的前提条件。
  • 并行和并发:
    • 并发:一个CPU,只能把CPU运行时间,划分成若干个时间段,再把若干时间段分配给各个线程,一个时间段的线程正在运行时,其他线程只能挂起。
    • 并行:系统有一个以上的CPU时,一个CPU执行一个线程时,另一个CPU可以执行另一个线程,两个线程互不抢占资源,可以同时进行。
    • 区别:并行是指两个或者多个事件在同一时刻发生,而并发是指两个或多个事件在同一时间间隔内发生
  • 异步和同步:是指能否开启新的线程,同步不能开启新的线程,异步可以开启新的线程。

2.线程是什么?进程是什么?两者有什么区别和联系?

  • 线程和进程是操作系统不同的资源管理方式
  • 进程有独立的内存管理空间,一个进程奔溃了不会影响其他的进程,而线程只是进程中不同的执行路径。
  • 线程有自己的堆栈和局部变量,但是没有独立的内存空间,一个线程死掉,就等于整个进程死掉。
  • 进程=火车 ,线程=车厢。

3.物理内存和虚拟内存间的关系?

  • 物理内存指的是内存条上的内存,以前一个进程的数据是全部加载在物理内存上的,CPU直接通过物理内存地址访问进程数据
    缺点:内存不够用;内存占用浪费;内存数据的安全问题。
  • 虚拟内存是处于进程和物理内存间的一个中间层,有系统生成,本质上,虚拟内存是一张关联物理内存各项数据的虚拟内存地址和物理内存地址的映射表

4.有没有考虑过用户通过分享,启动app时,push到app的二级页面的场景优化?

  • 正常做法是遍历得到当前的VC,然后再通过id push到detail界面
  • 可以选择使用路由的方式,可以跳转到任意界面,不用担心做过多的逻辑判断,也可实现各模块之间的解耦。

5.在异步线程发送通知,在主线程接收通知。会不会有什么问题?

  • 接受通知的方法所在的线程是由发送通知的线程决定的。
  • 所以接受异步线程方法,如果要更新UI需要切换到主线程中执行。

6 .内存管理的几条原则是什么?按照默认法则,哪些关键字生成的对象需要手动释放?使用property的时候,怎样避免内存泄露?

  • 谁创建谁释放,谁引用谁管理。
  • strong、copy关键字生成的对象需要手动释放。
  • 双向引用的对象,使用weak关键字,可以避免内存泄漏。

7.dispatch_barrier_async的作用是什么?

  • 栅栏的作用是,等待所有位于barrier之前的执行都完成,并且Barrie函数执行完后,才会执行barrier后面的操作。
  • 实现高效率的数据库访问和文件访问
  • 避免数据竞争

8.如何用GCD同步若干个异步调用?(如根据若干个URL异步加载图片,在图片都下载完成后合成一张图片)

  • 创建多个队列任务
  • 每个任务的执行方式为异步,添加到队列组中
  • 在队列组收到任务完成的通知后合成照片

9.http与https 的区别?

  • http超文本传输协议,信息是明文传输,https则是具有安全性的SSL加密传输协议
  • http和https的连接方式不同,端口也不一样,前者是80端口,后者是443端口
  • https需要到ca申请证书
  • https:主要作用第一种是建立一个传输的安全通道,第二种是确认网站的真实性。
  • HTTPS: 采用 对称加密 和 非对称加密 结合的方式来保证通信安全。
  • 对称加密算法加密数据+非对称加密算法交换密钥+数字证书验证身份=安全

10.对称加密和非对称加密的概念以及有哪些使用?

  • 对称加密:
    • 特点是文件加密和解密使用相同的密钥加密。
    • 常见 算法有:DES、3DES、Blowfish、IDEA、RC4、RC5、RC6和AES
  • 非对称加密:
    • 非对称加密算法需要两个密钥:公开密钥(publickey)和私有密钥(privatekey)。
    • 常见的非对称加密算法有:RSA、ECC(移动设备用)、Diffie-Hellman、El Gamal、DSA(数字签名用)。

11.服务器能否知道APNs推送后有没有到达客户端?

  • APNs支持状态码和返回error信息,返回状态码200是推送成功
  • APNs最大的推送长度为4096个字节(4kb)
  • 可以通过 HTTP/2 PING心跳包功能检测当前的APNs连接是否可用

12.你用过NSOperationQueue么?如果用过或者了解的话,你为什么要使用NSOperationQueue,实现了什么?请描述它和GCD的区别和类似的地方(提示:可以从两者的实现机制和适用范围来描述)

  • 使用NSOperationQueue 管理子类化的NSOperation对象,控制其线程并发数目
  • GCD和NSOperationQueue都可以实现对线程的管理,区别是NSOperationQueue和NSOperation是多线程面向对象的抽象。
  • 使用NSOperationQueue的优点是:对线程的高度抽象、具有对象封装、复用的优点、可以对操作进行状态监听、取消、设置优先级、和依赖
  • GCD 简单易用,不复杂的多线程操作,会节省代码量,而Block参数的使用,会是代码更为易读,尽量在简单项目中使用。

13.慢启动?

是传输控制协议(TCP)使用的一种阻塞控制机制。

  • 慢启动也叫做指数增长期。
  • 慢启动是指每次TCP接收窗口收到确认时都会增长,增加的大小就是已确认段的数目,这种情况一直保持到要么没有收到一些段,要么窗口大小到达预先定义的阈值。
  • 如果发生丢失事件,TCP就认为这是网络阻塞,就会采取措施减轻网络拥挤。
  • 一旦发生丢失事件或者到达阈值,TCP就会进入线性增长阶段。这时,每经过一个RTT窗口增长一个段。

14.TCP如何建立连接?

  • TCP连接过程:


    image.png

(1) 服务端通过socket,bind和listen准备好接受外来的连接,此时服务端状态为Listen
(2)客户端通过调用connect来发起主动连接,导致客户端TCP发送一个SYN(同步)字节,告诉服务器客户将在(待建立的)连接中发送的数据的初始序列号,客户端状态为SYN_SENT。
(3)服务器确认(ACK)客户的SYN,并自己也发送一个SYN,它包含服务器将在同一连接中发送数据的初始序列号。
(4)客户端确认服务的ACK和SYN,向服务器发送ACK,客户端状态ESTABLISHED
(5)服务器接收ACK,服务器状态ESABLISHED。

  • TCP关闭过程(四次握手):


    image.png

    TCP连接中止过程:
    (1)某端首先调用close,成为主动关闭端,向另一端发送FIN分节,表示数据发送完毕,此时主动关闭端状态FIN_WAIT_1;
    (2)接收到FIN的是被动关闭端,FIN由TCP确认,先向主动关闭端发送ACK,作为一个文件结束符传递给接收端应用进程(放在已排队等候该应用进程接收到的任何其他数据之后),因为FIN的接收意味着接收端应用进程在相应连接无额外数据可接收,接收端状态CLOSE_WAIT;主动关闭端接收到ACK状态变为FIN_WAIT_2;
    (3)一段时间后,接收端接收到这个文件结束符的应用进程调用close关闭套接字,向主动关闭端发送FIN,接收端状态为LAST_ACK;
    (4)主动关闭端确认FIN,状态变为TIME_WAIT,并向接收端发送ACK,接收端接收到ACK关闭TCP,而主动关闭端一段时间后也关闭TCP;

15.为什么建立连接是三次握手,而关闭连接却是四次挥手呢?

  • 这是因为服务端在LISTEN状态下,收到建立连接请求的SYN报文后,把ACK和SYN放在一个报文里发送给客户端。
  • 而关闭连接时,当收到对方的FIN报文时,仅仅表示对方不再发送数据了但是还能接收数据,己方也未必全部数据都发送给对方了,所以己方可以立即close,也可以发送一些数据给对方后,再发送FIN报文给对方来表示同意现在关闭连接。
  • 因此,己方ACK和FIN一般都会分开发送。

16.TCP 如何确定数据的可靠性?

能够保证TCP协议可靠的算法有

  • 检验和
  • 连接管理机制
  • ACK应答机制
  • 快速重传和超时重传机制
  • 滑动窗口机制
  • 拥塞控制机制
  • 这些机制共同保证TCP协议的可靠性。

详解
https://www.jianshu.com/p/c4e53e5f2c58

17.TCP和UDP的区别?

  • 连接方面:TCP 面向连接(如打电话要先建立连接),而UDP是无连接的,发送消息之前可以不用建立连接
  • 安全方面:TCP提供可靠的服务,通过TCP传输的数据,无差错、不丢失、不重复、且有序到达;UDP尽最大的努力交付,即不可靠服务。
  • 传输效率方面:TCP的传输效率相对较低,UDP的传输效率高,适用于高速传输和实时性较高的通信或广播通信
  • 连接对象的数量方面:TCP 只能是点到点、一对一的;UDP可以一对一,一对多,多对一,多对多的通信。

18.http的请求方式有几种,每种的概念?

  • GET 请求指定页面的信息,返回实体主体
  • POST 向指定资源提交数据进行处理请求
  • HEAD 类似get,但不返回具体内容,获取报头信息
  • PUT 完整替换更新指定数据资源,没有就新增
  • DELETE 删除指定资源数据
  • OPTIONS 查看HTTP支持的请求方法
  • CONNECT 预留给能将连接改为管道的代理服务器
  • TRCE 追踪服务器收到的请求,用于测试或诊断

19.GET与POST的区别?

  • get参数放在地址栏中,post参数放在请求主体中;
  • get请求只发送一次TCP数据包,post要发送两次TCP数据包(是浏览器或框架的行为,与POST没有必然联系)
  • get请求只支持URL编码,但post支持多种编码
  • 对参数的数据类型,GET只接受ASCII字符,而POST没有限制

20.ARP是否了解?

地址解析协议,即ARP(Address Resolution Protocol),是根据IP地址获取物理地址的一个[TCP/IP协议]

21.一次完整的HTTP请求是怎么样的?请求由哪几部分组成构成?响应由哪几部分构成?

  • 域名解析->TCP握手连接->发起HTTP请求->发送请求头信息->HTTP服务器应答->服务器回应头信息->服务器发送数据->断开TCP链接
  • 请求部分包括:请求行、请求头、请求数据、空行
  • 响应部分包括:状态行、消息报头、响应正文

22.HTTP2.0与HTTP 1.1相比,主要区别包括

HTTP/2采用二进制格式而非文本格式
HTTP/2是完全多路复用的,而非有序并阻塞的——只需一个连接即可实现并行
使用报头压缩,HTTP/2降低了开销
HTTP/2让服务器可以将响应主动“推送”到客户端缓存中

22.粘包处理

给每个数据包添加头部,头部中包含数据包的长度,这样接收到数据后,通过读取头部的长度字段,便知道每一个数据包的实际长度了,再根据长度去读取指定长度的数据便能获取到正确的数据了


image.png

完整的数据包 = 服务号 + 数据包长度 + 数据
数据包头 = Id(4B) + length(4B) 共占用8字节
数据包 = length(假设占100个字节)
所以这条消息的长度就是108字节可以看到,要想知道一条完整数据的边界,关键就是数据包头中的length字段

四、APP

1.什么方式可以看到上架App的头文件?

  • 下载ipa包:

    • 方式一:下载低版本的iTunes通过iTunes下载APP后拿到ipa包
    • 方式二:使用Apple Configurator连接手机下载app后导出ipa
  • 使用class-dump工具进行反编译:

    • 使用class-dump工具来dump出目标文件的类信息
    • 利用OC的runtime特性,将存储在Mach-O文件的@interface 和 @protocol信息提取出来,生成对应的.h文件

2.阅读过哪些框架的源码?能说说它的架构方式吗

3.iOS iAP内购审核可能失败的问题?

  • 内购的金额超过60美金,需要给出合理的解释,避免apple认为是欺诈用户的行为
  • APP内只能使用虚拟币不能使用人名币标签,在创建内购价格和描述时也不能显示人名币标签
  • 消费类型区分为永久性的项目和有时效性的订阅项目,根据自己的需求合理分类和收费

4.IAP内购中虚拟货币导致审核无法通过的问题?

  • Android手机上购买的道具在iOS设备上不能使用,会影响apple的收益
  • 审核期间不能有在App Store上可以购买的商品的兑换码,也会影响apple收益
  • IAP支付,避免出现其他的支付方式,如支付宝 、H5,即使上线后期也还会审查

五.算法

1.假设有一个字符串aabcad,请写一段程序,去掉字符串中不相邻的重复字符串,即上述字符串处理之后的输出结果为:aabcd

- 比较前后相邻的字符串,重复且相邻的字符放入容器A
- 不重复的字符遍历容器A,如果不存在则放入容器A
- 最后得到容器A就是满足条件的字符串集合

- (NSString *)deleteRepeatString:(NSString *)str{
    NSMutableArray *components = [[NSMutableArray alloc]init];//存放需要保留的容器
    [components addObject:[str substringWithRange:NSMakeRange(0, 1)]];//第一个不需要删除
    for (int i = 1; i < str.length-1; i ++) {//第一个不需要判断,最后一个在末尾判断
        NSString *curStr = [str substringWithRange:NSMakeRange(i, 1)];
        NSString *preStr = [str substringWithRange:NSMakeRange(i-1, 1)];
        NSString *nextStr = [str substringWithRange:NSMakeRange(i+1, 1)];
        // 当前的字符与前后比较,相同的保留
        if ([curStr isEqualToString:preStr] || [curStr isEqualToString:nextStr]) {
            [components addObject:curStr];
            continue;
        }
       
        BOOL isRepeat = NO;
        for (int j = 1; j 

2.两个数m、n,如果m=2,n=5,用递归实现2 3 4 5相加= 14

- (int )sum:(int )m :(int )n{
    if (m == n){
        return m    
    }   
    if (m > n){
        int tmp = m
        n = m
        m = n
    }
    return sum(m,n-1)+n
}

3.自定义宏 #define MIN(A,B) A
float a = 1;
float b = MIN(a++,1.5);
问 a= ? b = ?
答案: a = 3; b = 2
a++ 会后执行, a++在表达式出现了2次,得3,  a++<1.5,返回a++,得2

// 扩展
float a = 1;
float b = [self getMax:a++ b:1.5];
- (CGFloat)getMax:(CGFloat ) a b:(CGFloat)b{
   return a>b?a:b;
}
运行 a = 2; b =1.5;

4.找出字符串中重复最多的字串?

- 第一步:取出第一个字符
- 第二步:遍历相同的字符
- 第三步:判断是否拼接
- 第四步:拼接的字符串超过输出字串就赋值

//@"asdfghjklwertyuivbhnhghfhvvddssssssssbvfvvccccccccccc";
- (NSString *)getSubString:(NSString *)input{
    NSString *outString;
    int i = 0;
    NSString *tmpStr = [input substringWithRange:NSMakeRange(0, 1)];
    while (i < input.length-1) {
        NSString *curStr = [input substringWithRange:NSMakeRange(i, 1)];
        if ([tmpStr rangeOfString:curStr].location == NSNotFound ) {
            tmpStr = curStr;
        }else{
            tmpStr = [tmpStr stringByAppendingString:curStr];
        }
        
        if (tmpStr.length > outString.length) {
            outString = tmpStr ;
        }
        I++;
    }
    NSLog(@"outString == %@",outString);//outString == cccccccccc
    return outString;
}

你可能感兴趣的:(2021年3月iOS面试题)