iOS底层面试题(下篇)

7月,iOS求职跳槽的相对较少,能在这个时间段求职的,不是被迫,就是对自己的技术很自信;
针对7月,特别总结了第三份iOS常见大厂面试题(下);

iOS面试题分为 上、中、下三部分,方便大家观看;

请先自己答一答

话不多说;直接上题本文收录:公众号【iOS进阶宝典《iOS底层面试题(下篇)》】

13. 如何用Charles抓HTTPS的包?其中原理和流程是什么?

流程:

  • 首先在手机上安装Charles证书

  • 在代理设置中开启Enable SSL Proxying

  • 之后添加需要抓取服务端的地址

原理:

Charles作为中间人,对客户端伪装成服务端,对服务端伪装成客户端。简单来说:

  • 截获客户端的HTTPS请求,伪装成中间人客户端去向服务端发送HTTPS请求
  • 接受服务端返回,用自己的证书伪装成中间人服务端向客户端发送数据内容。

具体流程如下图:扯一扯HTTPS单向认证、双向认证、抓包原理、反抓包策略

14. 什么是中间人攻击?如何避免?

中间人攻击就是截获到客户端的请求以及服务器的响应,比如Charles抓取HTTPS的包就属于中间人攻击。

避免的方式:客户端可以预埋证书在本地,然后进行证书的比较是否是匹配的

15. 了解编译的过程么?分为哪几个步骤?

1:预编译:主要处理以“#”开始的预编译指令。

2:编译:

  • 词法分析:将字符序列分割成一系列的记号。

  • 语法分析:根据产生的记号进行语法分析生成语法树。

  • 语义分析:分析语法树的语义,进行类型的匹配、转换、标识等。

  • 中间代码生成:源码级优化器将语法树转换成中间代码,然后进行源码级优化,比如把 1+2 优化为 3。中间代码使得编译器被分为前端和后端,不同的平台可以利用不同的编译器后端将中间代码转换为机器代码,实现跨平台。

  • 目标代码生成:此后的过程属于编译器后端,代码生成器将中间代码转换成目标代码(汇编代码),其后目标代码优化器对目标代码进行优化,比如调整寻址方式、使用位移代替乘法、删除多余指令、调整指令顺序等。

3:汇编:汇编器将汇编代码转变成机器指令。

  • 静态链接:链接器将各个已经编译成机器指令的目标文件链接起来,经过重定位过后输出一个可执行文件。

  • 装载:装载可执行文件、装载其依赖的共享对象。

  • 动态链接:动态链接器将可执行文件和共享对象中需要重定位的位置进行修正。

最后,进程的控制权转交给程序入口,程序终于运行起来了。

16. 静态链接了解么?静态库和动态库的区别?

静态链接是指将多个目标文件合并为一个可执行文件,直观感觉就是将所有目标文件的段合并。需要注意的是可执行文件与目标文件的结构基本一致,不同的是是否“可执行”。

  • 静态库:链接时完整地拷贝至可执行文件中,被多次使用就有多份冗余拷贝。

  • 动态库:链接时不复制,程序运行时由系统动态加载到内存,供程序调用,系统只加载一次,多个程序共用,节省内存。

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

  • 优化DNS解析和缓存

  • 对传输的数据进行压缩,减少传输的数据

  • 使用缓存手段减少请求的发起次数

  • 使用策略来减少请求的发起次数,比如在上一个请求未着地之前,不进行新的请求

  • 避免网络抖动,提供重发机制

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

self和super的区别:

  • self 是类的一个隐藏参数,每个方法的实现的第一个参数即为self
  • super并不是隐藏参数,它实际上只是一个”编译器标示符”,它负责告诉编译器,当调用方法时,去调用父类的方法,而不是本类中的方法。

在调用[super class]的时候,runtime会去调用objc_msgSendSuper方法,而不是objc_msgSend

OBJC_EXPORT void objc_msgSendSuper(void /* struct objc_super *super, SEL op, ... */ )
/// Specifies the superclass of an instance.
struct objc_super {
/// Specifies an instance of a class.
__unsafe_unretained id receiver;
/// Specifies the particular superclass of the instance to message. 
# if !defined(__cplusplus) &&  !__OBJC2__
/* For compatibility with old objc-runtime.h header */
__unsafe_unretained Class class;
# else
__unsafe_unretained Class super_class;
# endif
/* super_class is the first class to search */
}

objc_msgSendSuper方法中,第一个参数是一个objc_super的结构体,这个结构体里面有两个变量,一个是接收消息的receiver,一个是当前类的父类super_class

objc_super结构体指向的superClass父类的方法列表开始查找selector,父类找到了,父类就执行这个方法。

class 方法的内部实现:

- (Class)class {
return object_getClass(self);
}

在class 方法内,默认传入的是self, 无论调用者是谁。

所以这个道题的答案就出来了: 两个打印的都是当前的类。

18.isKindOfClass 与 isMemberOfClass

下面代码输出什么?

@interface Sark : NSObject
@end

@implementation Sark
@end

int main(int argc, const char * argv[]) {
@autoreleasepool {
BOOL res1 = [(id)[NSObject class] isKindOfClass:[NSObject class]];
BOOL res2 = [(id)[NSObject class] isMemberOfClass:[NSObject class]];
BOOL res3 = [(id)[Sark class] isKindOfClass:[Sark class]];
BOOL res4 = [(id)[Sark class] isMemberOfClass:[Sark class]];

NSLog(@"%d %d %d %d", res1, res2, res3, res4);
}
return 0;
}

先来分析一下源码这两个函数的对象实现

+ (Class)class {
return self;
}

(Class)class {
    return object_getClass(self);
    }

Class object_getClass(id obj)
{
if (obj) return obj->getIsa();
else return Nil;
}

inline Class
objc_object::getIsa()
{
if (isTaggedPointer()) {
uintptr_t slot = ((uintptr_t)this >> TAG_SLOT_SHIFT) & TAG_SLOT_MASK;
return objc_tag_classes[slot];
}
return ISA();
}

inline Class
objc_object::ISA()
{
assert(!isTaggedPointer());
return (Class)(isa.bits & ISA_MASK);
}
(BOOL)isKindOfClass:(Class)cls {
    for (Class tcls = object_getClass((id)self); tcls; tcls = tcls->superclass) {
    if (tcls == cls) return YES;
    }
    return NO;
    }

 (BOOL)isKindOfClass:(Class)cls {
    for (Class tcls = [self class]; tcls; tcls = tcls->superclass) {
    if (tcls == cls) return YES;
    }
    return NO;
    }

(BOOL)isMemberOfClass:(Class)cls {
    return object_getClass((id)self) == cls;
    }

(BOOL)isMemberOfClass:(Class)cls {
    return [self class] == cls;
    }

首先题目中NSObject 和 Sark分别调用了class方法。

  • + (BOOL)isKindOfClass:(Class)cls方法内部,会先去获得object_getClass的类,而object_getClass的源码实现是去调用当前类的obj->getIsa(),最后在ISA()方法中获得meta class的指针。
  • 接着在isKindOfClass中有一个循环,先判断class是否等于meta class,不等就继续循环判断是否等于super class,不等再继续取super class,如此循环下去。
  • [NSObject class]执行完之后调用isKindOfClass,第一次判断先判断NSObjectNSObjectmeta class是否相等,之前讲到meta class的时候放了一张很详细的图,从图上我们也可以看出,NSObjectmeta class与本身不等。

  • 接着第二次循环判断NSObjectmeta classsuperclass是否相等。还是从那张图上面我们可以看到:Root class(meta)superclass 就是 Root class(class),也就是NSObject本身。所以第二次循环相等,于是第一行res1输出应该为YES

  • 同理,[Sark class]执行完之后调用isKindOfClass,第一次for循环,SarkMeta Class[Sark class]不等,第二次for循环Sark Meta Class的super class 指向的是 NSObject Meta Class, 和 Sark Class不相等。

  • 第三次for循环,NSObject Meta Classsuper class指向的是NSObject Class,和 Sark Class 不相等。第四次循环,NSObject Classsuper class 指向 nil, 和 Sark Class不相等。第四次循环之后,退出循环,所以第三行的res3输出为NO

  • 如果把这里的Sark改成它的实例对象,[sark isKindOfClass:[Sark class],那么此时就应该输出YES了。因为在isKindOfClass函数中,判断sark的meta class是自己的元类Sark,第一次for循环就能输出YES了。

  • isMemberOfClass的源码实现是拿到自己的isa指针和自己比较,是否相等。

  • 第二行isa 指向 NSObjectMeta Class,所以和 NSObject Class不相等。第四行,isa指向SarkMeta Class,和Sark Class也不等,所以第二行res2和第四行res4都输出NO。

19.Class与内存地址

下面的代码会?**Compile Error / Runtime Crash / NSLog…?**

@interface Sark : NSObject
@property (nonatomic, copy) NSString *name;

(void)speak;
    @end
    @implementation Sark
(void)speak {
    NSLog(@"my name's %@", [self.name](http://self.name/));
    }
    @end
    @implementation ViewController
(void)viewDidLoad {
    [super viewDidLoad];
    id cls = [Sark class];
    void *obj = &cls;
    [(__bridge id)obj speak];
    }
    @end

这道题有两个难点。

  • 难点一:obj调用speak方法,到底会不会崩溃。
  • 难点二:如果speak方法不崩溃,应该输出什么?

首先需要谈谈隐藏参数self和_cmd的问题。[receiver message]调用方法时,系统会在运行时偷偷地动态传入两个隐藏参数self_cmd,之所以称它们为隐藏参数,是因为在源代码中没有声明和定义这两个参数。self在已经明白了,接下来就来说说_cmd_cmd表示当前调用方法,其实它就是一个方法选择器SEL

难点一,能不能调用speak方法?

id cls = [Sark class];
void *obj = &cls;

答案是可以的。obj被转换成了一个指向Sark Class的指针,然后使用id转换成了objc_object类型。obj现在已经是一个Sark类型的实例对象了。当然接下来可以调用speak的方法。

难点二,如果能调用speak,会输出什么呢?

很多人可能会认为会输出sark相关的信息。这样答案就错误了。

正确的答案会输出

my name is <ViewController: 0x7ff6d9f31c50>

内存地址每次运行都不同,但是前面一定是ViewController。why?

我们把代码改变一下,打印更多的信息出来。

- (void)viewDidLoad {
[super viewDidLoad];
NSLog(@"ViewController = %@ , 地址 = %p", self, &self);
id cls = [Sark class];
NSLog(@"Sark class = %@ 地址 = %p", cls, &cls);
void *obj = &cls;
NSLog(@"Void *obj = %@ 地址 = %p", obj,&obj);
[(__bridge id)obj speak];
Sark *sark = [[Sark alloc]init];
NSLog(@"Sark instance = %@ 地址 = %p",sark,&sark);
[sark speak];
}

我们把对象的指针地址都打印出来。输出结果:

ViewController = <ViewController: 0x7fb570e2ad00> , 地址 = 0x7fff543f5aa8
Sark class = Sark 地址 = 0x7fff543f5a88
Void *obj = <Sark: 0x7fff543f5a88> 地址 = 0x7fff543f5a80

my name is <ViewController: 0x7fb570e2ad00>

Sark instance = <Sark: 0x7fb570d20b10> 地址 = 0x7fff543f5a78
my name is (null)

objc_msgSendSuper2 解读

// objc_msgSendSuper2() takes the current search class, not its superclass.
OBJC_EXPORT id objc_msgSendSuper2(struct objc_super *super, SEL op, ...)
__OSX_AVAILABLE_STARTING(__MAC_10_6, __IPHONE_2_0);

objc_msgSendSuper2方法入参是一个objc_super *super。

/// Specifies the superclass of an instance.
struct objc_super {
/// Specifies an instance of a class.
__unsafe_unretained id receiver;
/// Specifies the particular superclass of the instance to message. 
# if !defined(__cplusplus) && !**OBJC2**
/* For compatibility with old objc-runtime.h header */
__unsafe_unretained Class class;
# else
__unsafe_unretained Class super_class;
# endi
/* super_class is the first class to search */
};
# endif

所以按viewDidLoad执行时各个变量入栈顺序从高到底为self, _cmd, self.class, self, obj

  • 第一个self和第二个_cmd是隐藏参数。

  • 第三个self.class和第四个self[super viewDidLoad]方法执行时候的参数。

  • 在调用self.name的时候,本质上是self指针在内存向高位地址偏移一个指针。在32位下面,一个指针是4字节=4*8bit=32bit(64位不一样但是思路是一样的)

  • 从打印结果我们可以看到,obj就是cls的地址。在obj向上偏移32bit就到了0x7fff543f5aa8,这正好是ViewController的地址。

所以输出为my name is **<ViewController: 0x7fb570e2ad00>**

至此,Objc中的对象到底是什么呢?

实质:Objc中的对象是一个指向ClassObject地址的变量,即 id obj = &ClassObject , 而对象的实例变量 void *ivar = &obj + offset(N)

加深一下对上面这句话的理解,下面这段代码会输出什么?

- (void)viewDidLoad {
[super viewDidLoad];


NSLog(@"ViewController = %@ , 地址 = %p", self, &self);

NSString *myName = @"halfrost";

id cls = [Sark class];
NSLog(@"Sark class = %@ 地址 = %p", cls, &cls);

void *obj = &cls;
NSLog(@"Void *obj = %@ 地址 = %p", obj,&obj);

[(__bridge id)obj speak];

Sark *sark = [[Sark alloc]init];
NSLog(@"Sark instance = %@ 地址 = %p",sark,&sark);

[sark speak];

}
ViewController = <ViewController: 0x7fff44404ab0> , 地址 = 0x7fff56a48a78
Sark class = Sark 地址 = 0x7fff56a48a50
Void *obj = <Sark: 0x7fff56a48a50> 地址 = 0x7fff56a48a48

my name is halfrost

Sark instance = <Sark: 0x6080000233e0> 地址 = 0x7fff56a48a40
my name is (null)

由于加了一个字符串,结果输出就完全变了,[(__bridge id)obj speak];这句话会输出“my name is halfrost”

原因还是和上面的类似。按viewDidLoad执行时各个变量入栈顺序从高到底为self_cmdself.classselfmyNameobjobj往上偏移32位,就是myName字符串,所以输出变成了输出myName了。

20. 排序题:冒泡排序,选择排序,插入排序,快速排序(二路,三路)能写出那些?

这里简单的说下几种快速排序的不同之处,随机快排,是为了解决在近似有序的情况下,时间复杂度会退化为O(n^2),双路快排是为了解决快速排序在大量数据重复的情况下,时间复杂度会退化为O(n^2),三路快排是在大量数据重复的情况下,对双路快排的一种优化。

  • 冒泡排序
extension Array where Element : Comparable{
public mutating func bubbleSort() {
let count = self.count
for i in 0..<count {
for j in 0..<(count - 1 - i) {
if self[j] > self[j + 1] {
(self[j], self[j + 1]) = (self[j + 1], self[j])
}
}
}
}
}
  • 选择排序
extension Array where Element : Comparable{
public mutating func selectionSort() {
let count = self.count
for i in 0..<count {
var minIndex = i
for j in (i+1)..<count {
if self[j] < self[minIndex] {
minIndex = j
}
}
(self[i], self[minIndex]) = (self[minIndex], self[i])
}
}
}
  • 插入排序
extension Array where Element : Comparable{
public mutating func insertionSort() {
let count = self.count
guard count > 1 else { return }
for i in 1..<count {
var preIndex = i - 1
let currentValue = self[i]
while preIndex >= 0 && currentValue < self[preIndex] {
self[preIndex + 1] = self[preIndex]
preIndex -= 1
}
self[preIndex + 1] = currentValue
}
}
}
  • 快速排序
extension Array where Element : Comparable{
public mutating func quickSort() {
func quickSort(left:Int, right:Int) {
guard left < right else { return }
var i = left + 1,j = left
let key = self[left]
while i <= right {
if self[i] < key {
j += 1
(self[i], self[j]) = (self[j], self[i])
}
i += 1
}
(self[left], self[j]) = (self[j], self[left])
quickSort(left: j + 1, right: right)
quickSort(left: left, right: j - 1)
}
quickSort(left: 0, right: self.count - 1)
}
}
  • 随机快排
extension Array where Element : Comparable{
public mutating func quickSort1() {
func quickSort(left:Int, right:Int) {
guard left < right else { return }
let randomIndex = Int.random(in: left...right)
(self[left], self[randomIndex]) = (self[randomIndex], self[left])
var i = left + 1,j = left
let key = self[left]
while i <= right {
if self[i] < key {
j += 1
(self[i], self[j]) = (self[j], self[i])
}
i += 1
}
(self[left], self[j]) = (self[j], self[left])
quickSort(left: j + 1, right: right)
quickSort(left: left, right: j - 1)
}
quickSort(left: 0, right: self.count - 1)
}
}
  • 双路快排
extension Array where Element : Comparable{
public mutating func quickSort2() {
func quickSort(left:Int, right:Int) {
guard left < right else { return }
let randomIndex = Int.random(in: left...right)
(self[left], self[randomIndex]) = (self[randomIndex], self[left])
var l = left + 1, r = right
let key = self[left]
while true {
while l <= r && self[l] < key {
l += 1
}
while l < r && key < self[r]{
r -= 1
}
if l > r { break }
(self[l], self[r]) = (self[r], self[l])
l += 1
r -= 1
}
(self[r], self[left]) = (self[left], self[r])
quickSort(left: r + 1, right: right)
quickSort(left: left, right: r - 1)
}
quickSort(left: 0, right: self.count - 1)
}
}
  • 三路快排
// 三路快排
extension Array where Element : Comparable{
    public mutating func quickSort3() {
        func quickSort(left:Int, right:Int) {
            guard left < right else { return }
            let randomIndex = Int.random(in: left...right)
            (self[left], self[randomIndex]) = (self[randomIndex], self[left])
            var lt = left, gt = right
            var i = left + 1
            let key = self[left]
            while i <= gt {
                if self[i] == key {
                    i += 1
                }else if self[i] < key{
                    (self[i], self[lt + 1]) = (self[lt + 1], self[i])
                    lt += 1
                    i += 1
                }else {
                    (self[i], self[gt]) = (self[gt], self[i])
                    gt -= 1
                }

            }
            (self[left], self[lt]) = (self[lt], self[left])
            quickSort(left: gt + 1, right: right)
            quickSort(left: left, right: lt - 1)
        }
        quickSort(left: 0, right: self.count - 1)
    }
}
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,271评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,275评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,151评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,550评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,553评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,559评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,924评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,580评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,826评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,578评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,661评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,363评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,940评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,926评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,156评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,872评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,391评论 2 342

推荐阅读更多精彩内容

  • 前面两篇文章分别记录了自己学习 Runtime 的一些知识点以及常见的一些应用。之前立下 flag 说准备写三篇关...
    smooth_lgh阅读 448评论 0 2
  • 上篇文章:Runtime在工作中的运用 1.objc在向一个对象发送消息时,发生了什么? objc在向一个对象发送...
    minjing_lin阅读 1,073评论 4 7
  • 1.一个objc对象的isa的指针指向什么?有什么作用? 指向他的类对象,从而可以找到对象上的方法 详解:下图很好...
    長茳阅读 376评论 0 8
  • 喜欢就关注我呗! 1.设计模式是什么? 你知道哪些设计模式,并简要叙述? 设计模式是一种编码经验,就是用比较成熟的...
    iOS白水阅读 1,092评论 0 2
  • 表情是什么,我认为表情就是表现出来的情绪。表情可以传达很多信息。高兴了当然就笑了,难过就哭了。两者是相互影响密不可...
    Persistenc_6aea阅读 123,969评论 2 7