关于ios:iOS底层面试题下篇

31次阅读

共计 10914 个字符,预计需要花费 28 分钟才能阅读完成。

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,第一次判断先判断NSObject 和 NSObjectmeta 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 Class 的 super class 指向 nil,和 Sark Class不相等。第四次循环之后,退出循环,所以第三行的res3 输入为 NO
  • 如果把这里的 Sark 改成它的实例对象,[sark isKindOfClass:[Sark class],那么此时就应该输入 YES 了。因为在 isKindOfClass 函数中,判断 sark 的 meta class 是本人的元类 Sark,第一次 for 循环就能输入YES 了。
  • isMemberOfClass的源码实现是拿到本人的 isa 指针 和本人比拟,是否相等。
  • 第二行 isa 指向 NSObject 的 Meta 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_cmdself.classselfobj

  • 第一个 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)
    }
}

文末举荐:iOS 热门文集 & 视频解析

① Swift

② iOS 底层技术

③ iOS 逆向防护

④ iOS 面试合集

⑤ 大厂面试题 + 底层技术 + 逆向安防 +Swift

喜爱的小伙伴记得点赞喔~

珍藏等于白嫖,点赞才是真情ღ(´・ᴗ・`)ღ

正文完
 0