iOS 面试总结

1.arc什么时候起作用?

iOS 5 推出的ARC(自动引用计数),更加方便的管理对象的释放问题,对象自动添加了retain/release 不必要自行管理。提升开发效率。

2. weak的底层实现,从对象alloc开始,不是我们平时讲讲hash表,key,value是什么就好了。要求答得很细节,估计得debug过源码才行

1.初始化时:runtime会调用objc_initWeak函数,初始化一个新的weak指针指向对象的地址。
2.添加引用时:objc_initWeak函数会调用 storeWeak() 函数, storeWeak() 的作用是更新指针指向,创建对应的弱引用表。Key是所指对象的地址,Value是weak指针的地址数组。 加自旋锁操作,防止多线程中竞争冲突,先更新指针指向,创建新旧两个散列表,
3.释放时,调用clearDeallocating函数。clearDeallocating函数首先根据对象地址获取所有weak指针地址的数组,然后遍历这个数组把其中的数据设为nil,最后把这个entry从weak表中删除,最后清理对象的记录。

struct SideTable {
spinLock lock //锁
refCountMap count //引用计数表
weak_table_t  weak_table //弱引用表
}
struct weak_table_t {
    // 保存了所有指向指定对象的 weak 指针
    weak_entry_t *weak_entries;
    // 存储空间
    size_t    num_entries;
    // 参与判断引用计数辅助量
    uintptr_t mask;
    // hash key 最大偏移值
    uintptr_t max_hash_displacement;
};

3. @property (copy)nsmutablearr *arr; 这样的arr调用了addobject方法会有什么问题?

  • copy对于不可变对象是浅拷贝(内存地址指向同一处,引用计数加1(常量区数据固定为-1),指向的是同一个对象)
  • copy对于可变对象是深拷贝(开辟新内存,已经不是指向同一个内存空间同一个对象了)
  • addobject这里是会报错的,提示这个对象没有这个方法,因为对可变数组进行了深复制,_arr = [[NSMutableArray array] copy],但是copy方法返回的始终是不可变的对象,所以没有addobject这个方法

4. 讲一下OC的消息机制

  • OC中的方法调用其实都是转成了objc_msgSend函数的调用,给receiver(方法调用者)发送一条消息selector(方法名),objc_msgSend主要分3大步骤
  • (1)消息发送:receiver不为空,从receiver的cache缓存列表中寻找,再从receiver的class_rw_t(方法列表)中寻找,再从父类的cache缓存列表找,再从父类的class_rw_t(方法列表)中寻找,直至到根对象,进入第二个流程动态方法解析。
  • (2)动态方法解析:系统将调用
    + (BOOL)resolveInstanceMethod:(SEL)sel,
    此方法有返回调用者,则进行消息发送,回到第一步,进行消息发送。如果没有调用者,将进行第三个步骤 消息转发
  • (3)消息转发:进入消息转发会调用
    - (id)forwardingTargetForSelector:(SEL)aSelector; 如果没有调用者,继续调用
    - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector进行方法签名,此处如果有返回方法签名,则会调用
    - (void)forwardInvocation:(NSInvocation *)anInvocation 进行方法的实现,进行调用,如果没有返回方法签名,则会抛出异常,调用结束。

5. super关键字

// JWPerson JWStudent 继承关系
- (instancetype)init {
    if (self = [super init]) {
        NSLog(@"%@",[self class]); //JWStudent
        NSLog(@"%@",[self superclass]); //JWPersion
        
        NSLog(@"%@",[super class]); //JWStudent
        NSLog(@"%@",[super superclass]); //JWPersion
    }
    return self;
}

[super Class]返回的还是JWStudent,xcrun -sdk -iphoneos clang -arm64 -rewrite-objc JWStudent.m 编译出来的C++代码简略成

objc_msgSendSuper)(__rw_objc_super){(id)self, (id)class_getSuperclass(objc_getClass("JWStudent"))}, @selector(test);

可以看出__rw_objc_super结构体的第一个参数是当前类self,第二个参数是super父类。
根据源码得知,第一个参数就是消息调用者,第二个参数是用来查询方法的时候从父类开始查找
因为 [object class]的实现是在NSObject中实现的返回当前类。所以当调用[super class]的时候,会转换成,self调用者查询class的方法实现,并且是从父类开始寻找。因为 [object class]返回的是调用者,所以当调用[super class]的时候回返回自身类,并且class方法从父类中开始寻找,并不是直接调用父类的class方法。

6. super关键字面试题,下面这段代码可以执行成功么?

#import <Foundation/Foundation.h>
@interface JWPerson : NSObject 
@property (nonatomic, copy) NSString *  name;
- (void)print;
@end
#import "JWPerson.h"
@implementation JWPerson
-(void)print {
    NSLog(@"my name is %@",self.name);
}
@end
/*-------------------------------------------------------*/
- (void)viewDidLoad {
    [super viewDidLoad];
    //2.NSString * s = @"123";
    id cls = [JWPerson class];
    void * obj = &cls;
    [(__bridge id)obj print];
}
答案:my name is <ViewController: 0x7ff9ddd24ba0>
答案2.:my name is 123;

解释:(1).当一个[instance method]调用一个方法的本质就是instance通过指向对象的isa指针,找到当前类的class对象,进行方法调用。
而上述代码,cls指向类对象,obj指向cls,跟正常发送消息的机制正好吻合。这里面cls就相当于正常类的isa指针,obj就相当于实例对象,所以会调用成功。

结构对比图

(2). 当触发NSLog(@"my name is %@",self.name); 的时候self内部查找name变量进行打印,因为在栈空间中内存是连续的,isa后面接着就是_name变量,所以instance跳过isa8个字节找到name进行打印。回到原题我们可以看出,cls此时就是充当的实例对象,obj就是充当的指向该实例对象的指针,所以此时调用self.name相当于在cls内部跳过8个字节来找到进行输出,因为栈控件在内存中连续并且是从高地址开始分配内存,所以obj跳过8个字节就找到了NSString * s,并且输入,所以输出的是my name is 123;
实例图

(3).下面解释为什么会输出my name is <ViewController: 0x7ff9ddd24ba0>
由于[super viewDidLoad],进行消息转发的时候,底层的实现就是调用

struct superInsatce = {
self,
[UIViewController class]
};
objc_msgSendSuper(superInsatce,sel_registerName("viewDidLoad"));
super传递的两个参数一个是当前类,另外一个是为了表示,方法查找的时候从super开始寻找,而不是当前类。

所以self在最高位上,当obj跳过8个字节找到的内存地址就是self的内存地址,所以输出的就是当前self。

7. 什么是Runtime,在项目中有用过么?

OC是一门动态性比较强的编程语言,允许很多操作推迟到程序运行时再进行。OC的动态性是由Runtime来支撑和实现的,Runtime是一套C语言的API,封装了很多动态性相关的函数,平时写的OC代码,底层都是转换成了Runtime API进行调用
(1).利用关联对象,给分类添加属性
(2).便利类的所有成员变量,可以访问私有成员变量,比如修改textfield的占位文字颜色、自动归档解档、字典转模型
(3).交换方法实现,交换系统的方法
(4).利用消息转发机制,解决方法找不到的问题。

8. 讲讲HTTPS、HTTP连接

  1. HTTP属于应用层的协议,超文本传输协议,主要是规定了客户端/服务端的通信格式,本身默认的是80端口,无状态协议,HTTP请求包含的字段:Cache、Content-Type、Host、User-Agent、Content-Length等。
  2. 3次连接4次挥手:

1.客户端发送连接请求(SYN seq=x),进入SYN_SEND状态。
2.服务端返回确认连接请求(SYN seq = y,ACK=x+1)进入SYN_RECV状态。
3.客户端确认连接请求(ACK = y+1)进入ESTABLISHED状态

1.客户端发起断开连接请求(FIN seq = x+2,ACK = y+1) FIN_WAITE_1
2.服务端同意断开请求 FIN_WAITE_2
2.服务端发起确认断开请求 LAST_ACK
2.客户端接受断开请求,断开连接。 TIME_WAIT

由于HTTP有可能被窃听、被篡改、被冒充的危险。出现了HTTPS,就是在hTTP的基础上又加上了一层验证通道,在HTTP的基础上又曾加了TLS/SSL协议,默认断就号是:443,原理就是采用非对称加密(主流的是RSA加密)方式,验证两端身份。达到安全通信的目的。
HTTPS加密流程:

  1. 客户端产生随机数A,并且按照约定的加密方式,请求服务端
  2. 服务端收到请求确认加密方式,产生随机数B,并且将公钥发送给客户端
  3. 客户端收到公钥,产生随机数C,客户端用公钥将C加密,发送给服务端
  4. 服务端用私钥解密得到C,匹配成功。
  5. 客户端按照约定的加密方式,加密ABC,生成加密密钥,以后都使用这个加密密钥来进行通信。

HTTP、HTTPS的区别:

1.默认端口不一样,一个是80一个是443
2.http是超文本传输协议,采用明文传输,https有SSL安全协议。
3.http是无连接的,https采用http+SSL安全通道加密,验明双方身份,更加安全
4.http传输速度快,https要在3次握手4次挥手的基础上,进行ssl加密握手。

9. 常用的设计模式,以及架构

  • 常用的架构:MVC、MVVM、MVP、
    MVC:最常用的架构,C连接model跟view的桥梁,控制两端之间的通信。缺点是C中容易代码冗余,可读性差。
    MVP:在MVC的基础上引申出来Present类处理C中的相关逻辑,使用协议绑定VIew跟Model之间的关系,传给C,减少了C中的代码,缺点是:需要写一大堆协议代理。
    MVVM:是在MVC的基础上引申出来一个ViewModel,来实现减少C的职责,
    双向绑定:model-->view : VM中使用一个回调,处理将model数据返回给Controller。
    view --> model:在view中使用KVO观察这模式,监听model的改变,处理给VM改变数据源。
    kvo中也可以使用常见的框架ReactiveCocoa替代使用,mvvm达到最优。
  • 设计模式:
    创建型模式: 单例模式、工厂模式
    结构型模式:适配器模式(继承类,重写方法)、代理模式
    行为型模式:观察者模式、模板模式(一些固定方法,子类重写方法)、命令模式(请求封装一个类,不同客户处理不同类)

10. 讲讲你常用的性能优化方法,以及检测方法

图像的显示原理,使用垂直同步机制(VSync),当VSync到来之后,系统图形服务会通过CADisplayLink机制通知App,APP主线程开始在CPU上计算显示内容、视图的创建、布局的计算、文本的绘制、图片的解码然后交给GPU进行顶点变换、片源着色器纹理、渲染、合成,随后GPU将结果递交给帧缓冲区,等待下一次的VSync的到来,iOS系统使用双帧缓冲区,GPU渲染完成之后递交给后缓冲区,屏幕显示从全缓冲区获取,渲染之后切换前后缓冲区。由于垂直同步信号的机制,如果在信号到来之际,CPU或者GPU没有完成渲染操作。会保留上一次的缓存进行来读取,所以屏幕上还是显示的是之前的图像造成卡顿。
1.冷启动启动时间的优化:

  • 在main函数之前动态链接库dyld连接framework,所以这里多个链接库合并成1个动态库,优化连接时间。
  • load方法调用,runtime机制会调用所有类的load方法,所以删除无用的类。
  • didFinishLauchOpation到来之后,初始化sdk的操作,尽可能的将一些非必须的初始化放进子线程去做,或者延时操作。
  1. 优化方案
  • tableview cell的复用
  • 高度缓存
  • 减少视图层级,不需要点击事件使用CALayer替代
  • 圆角、阴影等造成离凭渲染的情况使用shawdow path来绘制
  • 图片应该使用png图片,少使用其他格式的图片,图片大小跟控件一致,可以考虑将多张图片合成一张图片来显示。
  • 减少透明视图
  • 减少离屏渲染
  • 合理使用光栅化(从GPU的操作转移到CPU,生成位图缓存起来,直接复用)
  • 采用异步渲染
  1. 耗电优化
  • 合理使用地图定位,视频播放、网络加载,优化I/O操作

11. NSCache优于NSDictionary的几点?

用法:他们两个的基本用法都是相同的
不同点:

  1. NSCache是线程安全的,而Dict不是。
  2. NSCache当内存不足的时候会自动释放内存。
  3. NSCache可以指定缓存个数

12. AFN为什么添加一条常驻线程?

如果没有常住线程的话,就会每次请求网络就去开辟线程,完成之后销毁开辟线程,这样就造成资源的浪费,而开辟一条常驻线程,就可以避免这种浪费,我们可以在每次的网络请求都添加到这条线程。

13. 关联对象有什么应用,系统如何管理关联对象?其被释放的时候需要手动将其指针置空么。

在不影响class的前提下动态给类添加属性,因为class在编译时期内存 已固定,assocation关联对象维护了全局的一张hash表,key是对象地址,value是另外一张hashmap,存储这对象的kv对。
delloc的时候会调用objc_dispose函数,会检查是否存在关联对象,有的话调用关联对象的移除方法。不需要手动置nil。

14.class_ro_t和class_rw_t的区别?

ObjC 类中的属性、方法还有遵循的协议等信息都保存在 class_rw_t 中,其中还有一个指向常量的指针 ro,其中存储了当前类在编译期就已经确定的属性、方法以及遵循的协议

15.iOS 反射跟内省

iOS是一门动态化语言,内省是在运行时对自身状态的检测
反射是在运行时对自身的改变。
内省:

  • isKindofClass
  • isMemberOfClass
  • responseForSelector
    反射:
  • NSStringFormForClass
  • NSClassFormForString
  • NSStringFromSelector
  • NSStringFromProtocol

16. 你知道有哪些情况会导致app崩溃,分别可以用什么方法拦截并化解?

  • 方法找不到
  • 数组、字符串越界,KVC,setObject:nil
  • 死循环、死锁
  • 已经释放的对象发送消息
  • 自定义对象调用copy方法
  • 调用[setObject:nil For:@"123"]

17. App 网络层有哪些优化策略?

  1. DNS优化解析:优化DNS解析跟缓存,网络不好的时候选择Server IP列表中最优的一个IP进行解析,当DNS解析成功之后,返回IP,插入到Server IP列表中去
  2. 网络质量检测:当网络不好的时候,将请求变成串行返回数据。
  3. 优化网络优先级以及依赖:图片》数据返回
  4. 提供网络服务重发机制:提供网络请求失败时进行重发机制
  5. 减少数据传输量:尽可能的减少服务端返回数据内容以及参数。
  6. 设置动态超时时间。
  7. 使用HTTP缓存。

18. iOS clang编译过程

原代码-->clang编译器前端生成-->生成中间代码(LLVM IR)-->编译器后端生成不同架构的机器语言

  1. 载入文件
  2. 替换全局变量、语法分析等
  3. 生成中间代码
  4. 编译器后端生成机器指令
  5. 连接关联模块、架构
  6. 绑定机器架构,生成对用机型码

19. 面向对象的几个设计原则了解么?最好可以结合场景来说。

1.单一原则: 每个类有单独的任务

  1. 开闭原则:可以扩展一个类,但是不能修改。我能穿衣服,但是衣服不能改变我的身体
  2. 里式替换原则:继承的子类应该都可以使用父类的方法。(鸵鸟/鸽子,都继承鸟类的飞行方法是不符合规则的)
  3. 接口隔离原则: 无用的方法无需暴露出来。
  4. 依赖倒置原则:高等级的模块不应该依赖低等级的模块。(汽车轮胎换了,不应该整台车都给换掉)

20:iOS SDK 里面有哪些设计模式的实践?

  • 创建型模式
    单例:UIApplication;
  • 结构性模式
    代理模式:delegate
    类簇:NSNumber;
    装饰者模式:分类;
    享元模式:UITableviewCell(UITableview的重用)
  • 行为性模式
    观察者模式:KVO;
    命令模式:NSInvocation;

21: 全局变量和单例模式的区别

全局变量是对一个对象的静态引用,全局变量确实可以提供单例模式实现的全局访问功能,但是它并不能保证应用程序只有一个实例;编码规范也明确的指出应该要少使用全局变量,因为过多的使用全局变量会造成代码难读;全局变量并不能实现继承。
单例模式虽然在继承上不能很好的处理,但是还是可以实现继承的;单例模式在类中保存了它的唯实例这个类,可以保证只能创建一个实例,同时它还提供了一个访问该唯一实例的全局访问点。

22:Tagged Pointer

通过关联的方法添加的一个属性

.h
@property (nonatomic, assign) CGFloat sameProperty;
.m
- (void)setSameProperty:(CGFloat)sameProperty {
    objc_setAssociatedObject(self, @selector(sameProperty), @(sameProperty), OBJC_ASSOCIATION_ASSIGN);
}
- (CGFloat)sameProperty {
    return [objc_getAssociatedObject(self, @selector(sameProperty)) floatValue];
}
self.sameProperty = 100;
self.sameProperty = 100.1;
当调用会发生什么情况?

结论就是设置为100的时候没有问题,设置为100.1的时候程序会发生奔溃,因为设置为100的时候系统会将该属性封装成Tagged Pointer指针来存储,他并不是一个真正意义上的指针,存储方式就是:对象的标记 + 对象的数据形式来存储,所以取值的时候是没有问题的,但是当设置成100.1的时候,系统无法用Tagged Pointer优化来管理该数据,只能申请一个地址空间,然后一个指针指向它,所以是一个真正意义上的指针,但是因为是用ASSIGN来管理的一个指针,就会出现引用计数的问题,会发生野指针的奔溃问题。
优化方案就是
1.将OBJC_ASSOCIATION_ASSIGN替换OBJC_ASSOCIATION_RETAIN_NONATOMIC来使用
或者2. 将CGFloat替换成NSNumber来管理使用。

23.Bitmap 内部结构

位图是由存储图片像素点的组成。

  1. 头信息:记录着位图信息,文件大小、保留段等
  2. 颜色表:记录RGBA颜色点的范围值
  3. 数据段:记录图片每个像素点的值

24.

国际化 换肤 cocospod原理 http 富文本优化 内存泄漏