7月,iOS求职跳槽的相对较少,能在这个时间段求职的,不是被迫,就是对本身的技术很自信;
针对7月,特别总结了第三份iOS常见大厂面试题(下);前端iOS面试题分为 上、中、下三部分,方便你们观看;git
请先本身
答一答
!github
话很少说;直接上题
本文收录:公众号【iOS进阶宝典《iOS底层面试题(下篇)》】面试13. 如何用Charles抓HTTPS的包?其中原理和流程是什么?
流程:后端
首先在手机上安装Charles证书缓存
在代理设置中开启Enable SSL Proxying服务器
原理:markdown
Charles
做为中间人,对客户端假装成服务端,对服务端假装成客户端。简单来讲:网络
具体流程以下图:扯一扯HTTPS单向认证、双向认证、抓包原理、反抓包策略dom
中间人就是截获到客户端的请求以及服务器的响应,好比Charles
抓取HTTPS的包就属于中间人。
避免的方式:客户端能够预埋证书在本地,而后进行证书的比较是不是匹配的
1:预编译:主要处理以“#”开始的预编译指令。
2:编译:
词法分析:将字符序列分割成一系列的记号。
语法分析:根据产生的记号进行语法分析生成语法树。
语义分析:分析语法树的语义,进行类型的匹配、转换、标识等。
中间代码生成:源码级优化器将语法树转换成中间代码,而后进行源码级优化,好比把 1+2 优化为 3。中间代码使得编译器被分为前端和后端,不一样的平台能够利用不一样的编译器后端将中间代码转换为机器代码,实现跨平台。
- 目标代码生成:此后的过程属于编译器后端,代码生成器将中间代码转换成目标代码(汇编代码),其后目标代码优化器对目标代码进行优化,好比调整寻址方式、使用位移代替乘法、删除多余指令、调整指令顺序等。
3:汇编:汇编器将汇编代码转变成机器指令。
静态连接:连接器将各个已经编译成机器指令的目标文件连接起来,通过重定位事后输出一个可执行文件。
装载:装载可执行文件、装载其依赖的共享对象。
- 动态连接:动态连接器将可执行文件和共享对象中须要重定位的位置进行修正。
最后,进程的控制权转交给程序入口,程序终于运行起来了。
静态连接是指将多个目标文件合并为一个可执行文件,直观感受就是将全部目标文件的段合并。须要注意的是可执行文件与目标文件的结构基本一致,不一样的是是否“可执行”。
静态库:连接时完整地拷贝至可执行文件中,被屡次使用就有多份冗余拷贝。
- 动态库:连接时不复制,程序运行时由系统动态加载到内存,供程序调用,系统只加载一次,多个程序共用,节省内存。
优化DNS解析和缓存
对传输的数据进行压缩,减小传输的数据
使用缓存手段减小请求的发起次数
使用策略来减小请求的发起次数,好比在上一个请求未着地以前,不进行新的请求
- 避免网络抖动,提供重发机制
@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, 不管调用者是谁。
因此这个道题的答案就出来了: 两个打印的都是当前的类。
下面代码输出什么?
@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
和 NSObject
的meta class
是否相等,以前讲到meta class
的时候放了一张很详细的图,从图上咱们也能够看出,NSObject
的meta class
与自己不等。
接着第二次循环判断NSObject
与meta class
的superclass
是否相等。仍是从那张图上面咱们能够看到:Root class(meta)
的superclass
就是 Root class(class)
,也就是NSObject
自己。因此第二次循环相等,因而第一行res1
输出应该为YES
。
同理,[Sark class]
执行完以后调用isKindOfClass
,第一次for
循环,Sark
的Meta Class
与[Sark class]
不等,第二次for循环
,Sark
Meta Class的super class
指向的是 NSObject Meta Class
, 和 Sark Class
不相等。
第三次for循环,NSObject Meta Class
的super 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
指向Sark
的Meta Class
,和Sark Class
也不等,因此第二行res2
和第四行res4
都输出NO。下面的代码会?**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
,_cmd
,self.class
,self
,myName
,obj
。obj
往上偏移32位,就是myName
字符串,因此输出变成了输出myName
了。
这里简单的说下几种快速排序的不一样之处,随机快排,是为了解决在近似有序的状况下,时间复杂度会退化为
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) } }
喜欢的小伙伴记得点赞喔~
收藏等于白嫖,点赞才是真情ღ( ´・ᴗ・` )ღ