iOS 头条面试
今天参加了头条的高级 iOS 岗位面试,1.5 h,一共两道题。一道问答,另外一道是算法。算法要求必须在纸上写出完整的代码,否则就算 Fail 。对于 iOS 开发者来说,除非处于研究目的,链表使用的场景真的不多。只能依据仅存在记忆中的大学 C 语言知识,边回忆变应用于眼前的算法题。最后结果完全在预料之中--「落榜」。
利用闲余时间,详细整理了一下问题和答案,供后来人准备。
问题1:iOS 消息调用过程;
问题2 :然后就拿出了一个链表的题目,要求手写算法:
两个链表 L1、 L2, 存储的是 10 以内的整数,求两个链表各个元素相加的结果。比如 L1 = 1 -> 2 -> 3 -> 5->9; L2 = 2 -> 1 -> 4 -> 7->1; 然后结果 res = 3->3->8->4->0.
答案 1 :iOS 消息调用属于基本知识,苹果官方有一个详细的介绍图:
image.png
iOS 工程中,调用对象的方法,就是向对象发送消息。我们知道,iOS 中的方法分为实例方法和对象方法。iOS 所有的对象都是继承至 NSObject, 编译完成后,在对象的定义中,存在一个实例方法链表、一个缓存方法链表。当对实例 son 发送消息后,会在 son 缓存方法链表中寻找;缓存中没有时,向实例方法链表寻找;再找不到,会向父类的实例方法缓存链表 -> 父类的实例方法链表寻找,直至 NSObject。在 NSObject 中会经历以下两个步骤:
1 - (BOOL)resolveInstanceMethod:(SEL)sel ;
2 - (id)forwardingTargetForSelector:(SEL)aSelector ;
如果在步骤 2 中范围 nil, 就会触发 iOS 的崩溃。
当向 Son 发送类方法时,会首先向 Son 的元类 metaClass 中的类缓存方法链表中寻找,然后类方法链表,然后父类 metaClass 的缓存方法链表 -> 类方法链表,直至 NSObject . 在 NSObject 中会经历如下两个步骤:
实例的 methodList 链表中寻找方法,找不到时会寻找 Son 的类方法,仍然找不到时,会寻找父类的方法链表,直到 NSObject 。
其中不同对象间的切换,通过 isa 指针完成,实例 son 的 isa 指向类 Son, 类 Son 的 isa 指向元类,元类的 isa 指向父类的元类, 父类的元类向上传递,直至 NSObject .
NSObject 的指针 isa 指向其本身,在想 NSObject 发送消息时,会经历如下步骤:
1 + (BOOL)resolveClassMethod:(SEL)sel ;
2 - (void)doesNotRecognizeSelector:(SEL)aSelector ;
当调用方法 2 时,会触发 iOS 的崩溃。利用以上机制,可以对resolveInstanceMethod 和 resolveClassMethod 两个方法进行方法交换,拦截可能出现的 iOS 崩溃,然后自定义处理。
如果理解了以上机制,就能明白这个题目。
答案 2 首先讲解一下实现思路:计算链表和,需要从后往前计算,进位需要保存,用于下一个链表和。最后一个链表计算后,如果还存在进位,需要新建节点保存结果。
题目中链表属于单向,只能从前往后遍历,所以首先需要将链表反向,然后对结果采用头插法的方式进行保存。下面直接上代码:
- (void)jiSuanHe{
ListNode *l1_1 = malloc(sizeof(ListNode));
l1_1->value = 9;
ListNode *l1_2 = malloc(sizeof(ListNode));
l1_2->value = 8;
l1_1->next = l1_2;
ListNode *l1_3 = malloc(sizeof(ListNode));
l1_3->value = 7;
l1_2->next = l1_3;
ListNode *l1_4 = malloc(sizeof(ListNode));
l1_4->value = 5;
l1_3->next = l1_4;
ListNode *l1_5 = malloc(sizeof(ListNode));
l1_5->value = 3;
l1_5->next = NULL;
l1_4->next = l1_5;
ListNode *l2_1 = malloc(sizeof(ListNode));
l2_1->value = 9;
ListNode *l2_2 = malloc(sizeof(ListNode));
l2_2->value = 8;
l2_1->next = l1_2;
ListNode *l2_3 = malloc(sizeof(ListNode));
l2_3->value = 7;
l2_2->next = l1_3;
ListNode *l2_4 = malloc(sizeof(ListNode));
l2_4->value = 5;
l2_3->next = l1_4;
// ListNode *l2_5 = malloc(sizeof(ListNode));
// l2_5->value = 3;
// l2_5->next = NULL;
// l2_4->next = l1_5;
ListNode *l1 = l1_1;
ListNode *l2 = l2_1;
printf("\n****\n");
ListNode *l1_reverse = [self reverseList:l1];
ListNode *l2_reverse = [self reverseList:l2];
printf("\n--l1---\n");
[self printList:l1_reverse];
printf("\n--l2---\n");
[self printList:l2_reverse];
ListNode *temp1 = l1_reverse;
ListNode *temp2 = l2_reverse;
int jinWei = 0;
int i = 0;
ListNode *res;
while (temp1 != NULL || temp2 != NULL) {
int value1 = 0, value2 = 0;
if (temp1 != NULL) {
value1 = temp1 -> value;
}
if (temp2 != NULL) {
value2 = temp2 -> value;
}
int yu = (value1 + value1 + jinWei) % 10;
jinWei = (value1 + value1 + jinWei) / 10;
ListNode *tempRes = malloc(sizeof(ListNode));
tempRes -> value = yu;
if (i == 0) {
tempRes -> next = NULL;
}else{
tempRes -> next = res;
}
res = tempRes;
temp1 = temp1 -> next;
temp2 = temp2 -> next;
i++;
}
if (jinWei != 0) {
ListNode *tempRes = malloc(sizeof(ListNode));
tempRes -> value = jinWei;
tempRes -> next = res;
res = tempRes;
}
printf("\n***结果\n");
[self printList:res];
printf("\n");
}
- (ListNode *)reverseList:(ListNode *)listNode{
ListNode *resL;
ListNode *l = listNode;
int i = 0;
while (l != NULL) {
ListNode *temp = malloc(sizeof(ListNode));
temp -> value = l -> value;
if (i==0) {
temp->next = NULL;
}else{
temp->next = resL;
}
resL = temp;
i++;
l = l -> next;
}
return resL;
}
- (void)printList:(ListNode *)listNode{
ListNode *node = listNode;
while (node != NULL) {
printf("value = %d ", node->value);
node = node->next;
}
}
算法题目并不算难,但是想要在面试期间的短时间内顺利解决,则需要平常不断的积累。算法意识是一个程序员进阶必须经过的磨炼,从这个角度,进入头条的人都具备了成长的潜力。当然,作为雇主的头条大概率上也会越来越好。虽然这次失败了,但是对头条的印象却好起来了,希望以后还有尝试的机会,加油~