序言
又是一年金三银四时,相信很多人会选择这个时机考虑跳槽,但又对轮轮面试望而生畏。其实面试只是对个人技术及应变能力的一次考验,通常面试第一轮为技术面试,二面一般是跟HR交流,进一步了解你(严格的大公司还会有三面、四面)。第一轮技术面试中遇到的问题通常都是一些基础性的知识,平时可能忙于夜以继日的工作而疏于整理和总结,匆忙去面试可能被面试官层层紧逼的提问问的晕头转向,或者笔试题答的一塌糊涂。技术过关后忠厚老实的你在二面时察觉不到HR问题的意图,答非所问最终败下阵来或者被HR硬生生的往下压了千元预期薪资。
跳槽不是简单的找工作或换一家公司,是一个双向选择的机会。如何将自己完美的呈现出来从而获得一个自己满意的offer?整理一些自己经历的、看到的问题,与君共勉。
技术基础篇
1、为什么说Objective-C是一门动态的语言?
静态、动态是相对的,这里引用一下网上的定义:
①动态类型语言:动态类型语言是指在运行期间才去做数据类型检查的语言,也就是说,在用动态类型的语言编程时,永远也不用给任何变量指定数据类型,该语言会在你第一次赋值给变量时,在内部将数据类型记录下来。
②静态类型语言:静态类型语言与动态类型语言刚好相反,它的数据类型是在编译其间检查的,也就是说在写程序时要声明所有变量的数据类型,C/C++是静态类型语言的典型代表
Objective-C是C的超集,在C语言的基础上添加了面向对象的特性,可以通过Runtime运行时机制在运行时动态的添加变量、方法、类等,所以说OC是一门动态语言。
延伸
编译时与运行时
编译时:即编译器对语言的编译阶段,编译时只是对语言进行最基本的检查报错,包括词法分析、语法分析等。将程序代码翻译成计算机能够识别的语言,编译通过并不意味程序就可以运行成功。
运行时:即程序通过了编译这一关之后编译好的代码被装载到内存中跑起来的阶段。这个时候会具体对类型进行检查,而不是仅仅对代码进行简单的扫描分析,此时如果出错的话程序就会崩溃。
可以说编译时是一个静态的阶段,类型错误很明显的可以直接检查出来,可读性好;而运行时则是动态的阶段,开始具体与运行环境结合起来。
Objective-C语言的动态性
OC语言的动态性主要体现在三个方面:动态类型(Dynamic typing)、动态绑定(Dynamic binding)、动态加载(Dynamic loading)。
<一>、动态类型
动态类型指的是对象指针类型的动态性。具体是指使用id任意类型将对象的类型确定推迟到运行时,由赋给它的对象类型来决定对象指针类型。另外类型确定推迟到运行时之后,可以通过NSObject的isKindOfClass方法动态的判断对象最后的类型(动态类型识别)。也就是说id任意类型修饰的对象为动态类型对象,其他在编译器指明类型的对象为静态对象。
示例:
对于语句 NSString* testObject = [[NSData alloc] init]; testObject在编译时和运行时分别是什么类型的对象?
首先,testObject是一个指向某个对象的指针,不论何时指针的空间大小都是固定的
编译时:指针的类型为NSString,即编译时会被当成一个NSString实例来处理,编译器在类型检查时如果发现类型不匹配就会给出黄色警告。该语句给指针赋值用的是一个NSData对象,则编译时编译器会给出类型不匹配警告。但是编译时如果testObject调用NSString的方法,编译器会认为是正确的,既不会警告也不会报错
运行时:运行时指针指向的实际是一个NSData对象,因此如果指针调用了NSString的方法,虽然编译时通过额,但运行时也会崩溃,因为NSData对象没有该方法。另外虽然运行时指针实际指向的是NSData,但编译时编译器并不知道,因此如果试图用这个指针调用NSData的方法会直接编译不通过,给出红色报错程序也运行不起来。
下面给出测试示例:
// 1.编译时编译器认为testObject是一个NSString对象,这里赋给它一个NSData对象编译器给出黄色类型错误警告,但运行时却是指向一个NSData对象
NSString* testObject = [[NSData alloc] init];
// 2.编译器认为testObject是NSString对象,所以允许其调用NSString的方法,这里编译通过无警告和错误
[testObject stringByAppendingString:@"string"];
// 3.但不允许其调用NSData的方法,下面这里编译不通过给出红色报错
[testObject base64EncodedDataWithOptions:NSDataBase64Encoding64CharacterLineLength];
将上面第三句编译不通过的注释掉,然后在第二句打断点,编译后跑程序到断点后会看到testObject指针类型是_NSZeroData对象。继续运行程序会崩溃,因为NSData对象没有NSString的stringByAppendingString这个方法。
如果我们假设testObject是id类型会怎么样呢?
// 1.id任意类型,编译器就不会把testObject在当成NSString对象了
id testObject = [[NSData alloc] init];
// 2.调用NSData的方法编译通过
[testObject base64EncodedDataWithOptions:NSDataBase64Encoding64CharacterLineLength];
// 3.调用NSString的方法编译也通过
[testObject stringByAppendingString:@"string"];
结果是编译完全通过,编译时编译器把testObject指针当做任意类型,运行时才确定testObject为NSData对象,因此执行NSData的方法正常。但是执行NSString的方法时还是崩溃了。通过这个例子我们就可以很清楚的知道id类型的作用了,将类型的确定推迟到运行时,这就提现了OC的一种动态性:动态类型。
延伸
动态类型的识别方法(面向对象语言的内省Introspeciton特性)
1.Class类型:
- Class class = [NSObject class];//通过类名得到对应的Class动态类型
- Class class = [obj class];//通过实例对象得到对应的Class动态类型
- if([obj1 class] == [obj2 class])//判断是不是相同类型的实例
2.Class动态类型和类名字符串的相互转换:
- NSClassFromString(@"NSObject");//由类名字符串得到Class动态类型
- NSStringFromClass([NSObject class]);//由类名的动态类型得到类名字符串
- NSStringFromClass([obj class]);//由对象的动态类型得到类名字符串
3.判断对象是否属于某种动态类型:
- -(BOOL)isKindOfClass:class//判断某个对象是否是动态类型class的实例或其子类的实例
- -(BOOL)isMemberOfClass:Class//只判断某个对象是否是class类型的实例,不考虑其子类
4.判断类中是否有对应的方法:
- -(BOOL)respondsToSelector:(SEL)selector//类中是否有这个类方法
- -(BOOL)instancesRespondToSelector:(SEL)selector // 类中是否有这个实例方法
5.方法名字符串和SEL类型的转换
在编译时,编译器会根据方法的名字和参数序列生成唯一标识方法的ID,这个ID为SEL类型。到了运行时编译器通过SEL类型的ID来查找对应的方法,放法的名字和参数序列相同,那么它们的ID就都是相同的。另外可以通过@select()指示符获得方法的ID。常用方法如下:
SEL funcID = @select(func);// 这个注册事件回调时常用,将方法转成SEL类型
SEL funcID = NSSelectorFromString(@"func"); // 根据方法名得到方法标识
NSString *funcName = NSStringFromSelector(funcID); // 根据SEL类型得到方法名字符串
<二>、动态绑定
动态绑定指的是方法确定的动态性。具体指的是利用OC的消息传递机制将要执行的方法的确定推迟到运行时,可以动态添加方法。也就是说一个OC对象是否调用某个方法不是由编译器决定的,而是由运行时决定的。另外,关于动态绑定的关键一点是基于消息传递机制的消息转发机制,主要处理应对一些接受者无法处理的消息,此时有机会将消息转发给其他接收者处理。
动态绑定是基于动态类型的,在运行时对象的类型确定后,那么对象的属性和方法也就确定了(包括类中原来的属性和方法以及运行时动态新加入的属性和方法),这也就是所谓的动态绑定了。动态绑定的核心就是在运行时动态的为类添加属性和方法,以及方法的最后处理和转发,主要用法C语言语法,因为涉及到运行时,因此要引入运行时头文件#include <objc/runtime.h>
。
延伸
消息传递机制
在OC中,方法的调用不再理解为对象调用起方法,而是要理解成对象接受消息,消息的发送采用动态绑定机制,具体会调用哪个方法直到运行时才能确定。确定之后才回去执行绑定的代码。方法的调用实际就是告诉对象要干什么,给对象传送一个消息,对象为接收者(receiver),调用的方法及其参数即消息(message),给一个对象传消息表达为:[receiver message];接收者的类型可以通过动态类型识别,于运行时确定。
在消息传递机制中,当开发者编写[receiver message];语句发送消息后,编译器都会将其转换成对应的一条objc_msgSend C语言消息发送原语,具体格式为:void objc_msgSend (id self, SEL cmd, ...)
这个原语函数参数可变,第一个参数是消息的接收者,第二个参数是消息的选择子,后面跟着可选的消息的参数。有了这些参数,objc_msgSend就可以通过接收者的isa指针,到其类对象中的方法列表中以选择子的名称为键寻找对应的方法,找到则转发哦其实现代码执行,找不到则继续根据继承关系从父类中寻找。如果找到了跟类还是无法找到对应的方法,说明该接收者对象无法响应该消息,则会出发消息转发机制,给开发者最后一次挽救程序的机会
<三>、动态加载
动态加载主要包括两个方面,一个是动态加载资源,一个是一些可执行代码模块的加载。这些资源在运行时根据需要动态的选择性的加入到程序中,是一种代码和资源的“懒加载“模式,可以降低内存需求,提高整个程序的性能。