iOS开发备战金三银四·头条三面记录

iOS开发备战金三银四·头条三面记录,第1张

一面 一、自我介绍

简单介绍一下你自己吧

解析:简单介绍下自己的名字,教育背景,现在的工作,做过的项目

二、自我介绍衍生的口头问题

讲讲下你在你项目中做过的优化或者技术难点

解析:介绍了自己封装的一个集picker,文本域的灵活展开的表视图。这个视图的数据源是json,怎么转成模型数组的?这个cell有哪些类型?展示的怎么区分这些cell?这里面有用过复用机制吗?这些cell有实现过多重继承吗?

题外话:这种问题最好各人自己找问题讲讲,不多,提前准备一个你项目中非常擅长并熟悉的点,即可。

1. 你平时怎么解决网络请求的依赖关系:当一个接口的请求需要依赖于另一个网络请求的结果

解析:

办法1: *** 作:NSOperation *** 作依赖和优先级(不适用,异步网络请求并不是立刻返回,无法保证回调时再开启下一个网络请求)
[operationB addDependency:operationA]; //  *** 作B依赖于 *** 作

办法2:逻辑:在上一个网络请求的响应回调中进行下一网络请求的激活(不适用,可能拿不到回调)

办法3:线程同步 – 组队列(dispatch_group)

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, queue, ^{ /*加载图片1 */ });
dispatch_group_async(group, queue, ^{ /*加载图片2 */ });
dispatch_group_async(group, queue, ^{ /*加载图片3 */ }); 
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        // 合并图片… …
});
办法4:线程同步 --阻塞任务(dispatch_barrier):
/* 创建并发队列 */
dispatch_queue_t concurrentQueue = dispatch_queue_create("test.concurrent.queue", DISPATCH_QUEUE_CONCURRENT);
/* 添加两个并发 *** 作A和B,即A和B会并发执行 */
dispatch_async(concurrentQueue, ^(){
    NSLog(@"OperationA");
});
dispatch_async(concurrentQueue, ^(){
    NSLog(@"OperationB");
});
/* 添加barrier障碍 *** 作,会等待前面的并发 *** 作结束,并暂时阻塞后面的并发 *** 作直到其完成 */
dispatch_barrier_async(concurrentQueue, ^(){
    NSLog(@"OperationBarrier!");
});
/* 继续添加并发 *** 作C和D,要等待barrier障碍 *** 作结束才能开始 */
dispatch_async(concurrentQueue, ^(){
    NSLog(@"OperationC");
});
dispatch_async(concurrentQueue, ^(){
    NSLog(@"OperationD");
});
办法5:线程同步 – 信号量机制(dispatch_semaphore):
/* 创建一个信号量 */
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);

/* 任务1 */
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    /* 耗时任务1 */
    NSLog(@"任务1开始");
    [NSThread sleepForTimeInterval:3];
    NSLog(@"任务1结束");
    /* 任务1结束,发送信号告诉任务2可以开始了 */
    dispatch_semaphore_signal(semaphore);
});

/* 任务2 */
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    /* 等待任务1结束获得信号量, 无限等待 */
    dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
    /* 如果获得信号量则开始任务2 */
    NSLog(@"任务2开始");
    [NSThread sleepForTimeInterval:3];
    NSLog(@"任务2结束");
});
[NSThread sleepForTimeInterval:10];
2. 关于RAC你有怎样运用到解决不同API依赖关系 信号的依赖

使用场景是当信号A执行完才会执行信号B,和请求的依赖很类似,例如请求A请求完毕才执行请求B,我们需要注意信号A必须要执行发送完成信号,否则信号B无法执行

//这相当于网络请求中的依赖,必须先执行完信号A才会执行信号B
  //经常用作一个请求执行完毕后,才会执行另一个请求
  //注意信号A必须要执行发送完成信号,否则信号B无法执行
  RACSignal * concatSignal = [self.signalA concat:self.signalB];

  //这里我们是对这个拼接信号进行订阅
  [concatSignal subscribeNext:^(id x) {

      NSLog(@"%@",x);

  }];
3. 编译链接你有了解多少

解析:这个涉及到简单知识,可参考http://www.360doc.com/content/17/0111/22/32626470_621879084.shtml

4. 简单介绍下KVO的用法

解析:首先,简单介绍下KVO的用法,先添加观察者,然后怎样实现监听的代理。关于原理,可以利用runtime的知识进行实现KVO的原理,笔者曾经实现KVO的block和delegate两种形式

简单概述下 KVO 的实现原理:

当你观察一个对象时,一个新的类会动态被创建。这个类继承自该对象的原本的类,并重写了被观察属性的 setter 方法。自然,重写的 setter 方法会负责在调用原 setter方法之前和之后,通知所有观察对象值的更改。最后把这个对象的 isa 指针 ( isa 指针告诉 Runtime 系统这个对象的类是什么 ) 指向这个新创建的子类,对象就神奇的变成了新创建的子类的实例。

原来,这个中间类,继承自原本的那个类。不仅如此,Apple 还重写了 -class 方法,企图欺骗我们这个类没有变,就是原本那个类。更具体的信息,去跑一下 Mike Ash 的那篇文章里的代码就能明白,这里就不再重复。

5. 介绍下Block的理解

本质是个匿名函数,可以用clang编译一下block块,可以看到它的内部。再讲讲平时怎样避免block里面可能产生的引用循环。再讲讲block怎样捕获变量的,最后讲讲block的有哪些类型,有什么区别(栈block,堆block,全局block)

6. 平时怎么解决多线程安全的问题

解析1:NSLock(),还可以围绕YYModel的线程安全方案mutex讲讲。

解析2:区分比较各自的优劣:atomic,sychronized,dispatch_semaphore_signal,重写setter方法里面串行队列

(1) nonatomic atomic:使用atomic多线程原子性控制,atomic的原理给setter加上锁,getter不会加锁。OC在定义属性时有nonatomic和atomic两种选择

atomic:原子属性,为setter方法加锁(默认就是atomic)
nonatomic:非原子属性,不会为setter方法加锁

(2) 使用GCD实现atomic *** 作:给某字段的setter和getter方法加上同步队列:

- (void)setCount:(NSInteger)newcount{
    dispatch_sync(_synQueue, ^{ count = newcount; });
}

- (NSInteger)count
{
   __block NSInteger localCount;
   dispatch_sync(_synQueue, ^{
        localCount = count;
   });
   return localCount;
}
7. static与extern的区别

解析:static修饰的是静态变量,保存在全局静态区。extern主要是跨文件访问用的。

8. Block里面怎么避免变量被回收,不是改变外部变量的问题

解析:主要避免栈变量的回收,栈变量的生命周期和堆block的生命不一致可能导致调用crash。

9. Masory的Block体里面怎样避免的循环引用

解析:这个block体里面放心使用self等等,不用weakSelf。并不是所有block里面要用weakSelf,关键看有没有形成引用循环。Masory的block虽然应用了self,但self并没拥有block。所以,不用。

二面 1. 编程题:RLE算法,编写一个函数,实现统计字符次数的功能:例如输入为aaabbccc,输出为a3b2c3。不限语言。

解析:比较简单,可以参考这
http://blog.51cto.com/lanchaohuan/1563103

2. 编程题:请实现一个函数,用来判断一颗二叉树是不是对称的。注意,如果一个二叉树同此二叉树的镜像是同样的,定义其为对称的.

解析:思路,递归,从根节点开始,判断左右子节点是否对称,若对称,递归,若不对称,则返回NO。

3. 问答题:你认为自动布局怎么实现的

解析:先提到系统提供的NSLayoutConstraint,再介绍Masonry怎样基于它的封装?

然而面试官继续问AutoLayout原理是?它的原理就是一个线性公式!比如,创建约束,iOS6中新加入了一个类:NSLayoutConstraint。它的约束满足这个公式:

item1.attribute = multiplier ⨉ item2.attribute + constant

基于这个原理的代码API是:

//redView(红色)top 距离self.view的top
NSLayoutConstraint *redViewTopToSuperViewTop = [NSLayoutConstraint constraintWithItem:redView attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeTop multiplier:1 constant:30];

这个具体代码对应的约束是: view_1的顶部(y)= self.view的顶部(y)*1 + 30。

4. 问答题:runtime的知识你有哪些实际应用?(根据简历问的) 三面 编程题:实现以下功能
编写一个自定义类:Person,父类为NSObject
解析:头文件这样写 @interface Person:NSObject
该类有两个属性,外部只读的属性name,还有一个属性age
解析:name的修饰符nonatomicstrongreadonlyage的修饰符nonatomiccopy
为该类编写一个初始化方法 initWithName:(NSString *)nameStr,并依据该方法参数初始化name属性。
解析:头文件声明该方法,实现文件实现该方法
如果两个Person类的name相等,则认为两个Person相等
解析:重写isEqual,这里面涉及到了哈希函数在iOS中的应用。 由编程题衍生的口头题目 1

题目: 怎样实现外部只读的属性,让它不被外部篡改

解析:

头文件用readonly修饰并声明该属性。正常情况下,属性默认是readwrite,可读写,如果我们设置了只读属性,就表明不能使用setter方法。在.m文件中不能使用self.ivar = @"aa"; 只能使用实例变量_ivar = @"aa";,而外界想要修改只读属性的值,需要用到kvc赋值[object setValue:@"mm" forKey:@"ivar"];

实现文件里面声明私有属性,并在头文件在protocol里面规定该属性就可以了,外部通过protocol获取,这样还可以达到隐藏成员的效果。

2

题目: nonatomic是非原子 *** 作符,为什么要这样,atomic为什么不行?有人说能atomic耗内存,你觉得呢?保读写安全吗,能保证线程安全吗?有的人说atomic并不能保证线程安全,你觉得他们的出发点是什么,你认同这个说法吗?

关于为什么用nonatomic

如果该对象无需考虑多线程的情况,请加入这个属性修饰,这样会让编译器少生成一些互斥加锁代码,可以提高效率。

而atomic这个属性是为了保证程序在多线程情况下,编译器会自动生成一些互斥加锁代码,避免该变量的读写不同步问题。

atomic 和 nonatomic 的区别在于,系统自动生成的 getter/setter 方法不一样。如果你自己写 getter/setter,那 atomic/nonatomic/retain/assign/copy 这些关键字只起提示作用,写不写都一样。

关于atomic语nonatomic的实现

苹果的官方文档有解释,下面我们举例子解释一下背后的原理。

至于 nonatomic 的实现
//@property(nonatomic, retain) UITextField *userName;
//系统生成的代码如下:

- (UITextField *) userName {
    return userName;
}

- (void) setUserName:(UITextField *)userName_ {
    [userName_ retain];
    [userName release];
    userName = userName_;
}
而 atomic 版本的要复杂一些:
//@property(retain) UITextField *userName;
//系统生成的代码如下:

- (UITextField *) userName {
    UITextField *retval = nil;
    @synchronized(self) {
        retval = [[userName retain] autorelease];
    }
    return retval;
}

- (void) setUserName:(UITextField *)userName_ {
    @synchronized(self) {
      [userName release];
      userName = [userName_ retain];
    }
}

简单来说,就是 atomic 会加一个锁来保障多线程的读写安全,并且引用计数会 +1,来向调用者保证这个对象会一直存在。假如不这样做,如有另一个线程调 setter,可能会出现线程竞态,导致引用计数降到0,原来那个对象就释放掉了。

关于atomic和线程安全

atomic修饰的属性只能说是读/写安全的,但并不是线程安全的,因为别的线程还能进行读写之外的其他 *** 作。线程安全需要开发者自己来保证。

关于修饰符失效

因为atomic修饰的属性靠编译器自动生成的get和set方法实现原子 *** 作,如果重写了任意一个,atomic关键字的特性将失效

3

题目: 你在初始化的方法中为什么将参数赋给_name,为什么这样写就能访问到属性声明的示例变量?

xcode4 之后,编辑器添加了自动同步补全功能,只需要在 h 文件中定义 property,在编译期m文件会自动补全出 @synthesize name = _name 的代码,不再需要手写,避免了“体力代码”的手动编码 4

题目: 初始化方法中的_name是在什么时候生成的?分配内存的时候吗?还是初始化的时候?

成员变量存储在堆中(当前对象对应的堆得存储空间中) ,不会被系统自动释放,只能有程序员手动释放。

编译的时候自动的为name属性生成一个实例变量_name

如果m中什么都不写,xcode会默认在编译期为 market 属性,补全成 @synthesize market = _market,实例变量名为 _market;

如果m中指定了 @synthesize market,xcode会认为你手动指定了实例变量名为 market ,编译期补全成:@synthesize market = market,实例变量名为 market。

5

题目: 作为return的self是在上面时候生成的?

是在alloc时候分配内存,在init初始化的。

一种典型带成员变量初始化参数的代码为:

- (instancetype)initWithDistance:(float)distance maskAlpha:(float)alpha scaleY:(float)scaleY direction:(CWDrawerTransitionDirection)direction backImage:(UIImage *)backImage {
    if (self = [super init]) {
        _distance = distance;
        _maskAlpha = alpha;
        _direction = direction;
        _backImage = backImage;
        _scaleY = scaleY;
    }
    return self;
}
6

题目: 为什么用copy,哪些情况下用copy,为什么用copy?

可变的类,例如NSArray、NSDictionary、NSString最好用copy来修饰,它们都有对应的Mutable类型。copy修饰属性的本质是为了专门设置属性的setter方法,例如,setName:传进一个nameStr参数,那么有了copy修饰词后,传给对应的成员变量_name的其实是[nameStr copy];。为什么要这样?如果不用copy会有什么问题?例如,strong修饰的NSString类型的name属性,传一个NSMutableString:
NSMutableString *mutableString = [NSMutableString stringWithFormat:@"111"];
self.myString = mutableString;

strong修饰下,把可变字符串mutableString赋值给myString后,改变mutableString的值导致了myString值的改变。而copy修饰下,却不会有这种变化。

strong修饰下,可变字符串赋值给myString后,两个对象都指向了相同的地址。而copy修饰下,myString和mutableString指向了不同地址。这也是为什么strong修饰下,修改mutableString引起myString变化,而copy修饰下则不会。

总之,当修饰可变类型的属性时,如NSMutableArray、NSMutableDictionary、NSMutableString,用strong。当修饰不可变类型的属性时,如NSArray、NSDictionary、NSString,用copy。 7

题目: 分类中添加实例变量和属性分别会发生什么,编译时就报错吗,还是什么时候会发生问题?为什么

编译的时候,不能添加实例变量,否则报错。

编译的时候可以添加属性,但是一旦在创建对象后为属性赋值或者使用这个属性的时候,程序就崩溃了,奔溃的原因也很简单,就是找不到属性的set/get方法。

那我们就按照这个流程来,在类别中为属性添加set/get方法,在set方法里面赋值的时候找不到赋值的对象,也就是说系统没有为我们生成带下划线的成员变量,没生成我们就自己加。但是通过传统实例变量的方式,一加就报错。看来这才是类别不能扩展属性的根本原因。

那么怎么办?通过runtime的关联对象。 另外聊到的实际开发问题

你平时有做过优化内存的哪些工作?怎样避免内存消耗的大户?

你怎样实现线程安全的?这些线程安全的办法和atomic有什么不一样?atomic的实现机制是怎样,如果不加atomic会怎么样呢?

同时我也整理了一些面试题,有需要的朋友可以加QQ群:1012951431 获取

① BAT等各个大厂iOS面试真题+答案大全

② iOS中高级开发必看的热门书籍(经典必看)

③ iOS开发高级面试"简历制作"指导

④ iOS面试流程到基础知识大全

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/web/994416.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-05-21
下一篇 2022-05-21

发表评论

登录后才能评论

评论列表(0条)

保存