1.多线程在实际现实中有哪些应用?(网络操作和大量图片处理不算)
通常耗时的操作都会放在子线程里处理,然后再回到主线程来显示。下面举几个例子:
- 我们要从数据库提取数据还要将数据分组后显示,那么就会开个子线程来处理,处理完成后才去刷新UI显示。
- 拍照后,会在子线程处理图片,完成后才回到主线程来显示图片。拍照出来的图片太大了,因此要做处理。
- 音频、视频处理会在子线程来操作
- 文件较大时,文件操作会在子线程中处理
- 做客户端与服务端数据同步时,会在后台闲时自动同步
2、如果app比较大,怎么样减少app的大小?
参考答案:
- 将build setting中的Optimization Level设置为Fastest, Smallest [-Os],在发布模式下,默认就是这样设置的
- 将build setting 中的Strip Debug Symbols During Copy设置为YES,在发布模式下,默认就是这样设置的
- 资源文件查找出所有未使用的,去掉这些永远不会使用的资源文件
- 对嵌入App的音频进行压缩处理
3、你在迭代开发中是怎么处理版本兼容问题?
参考答案:
版本迭代一定要注意兼容老版本,比如新增了字段或者去掉了某些不再使用的字段,不能引起应用闪退。我们这里只谈程序代码兼容新老版本问题,不考虑业务。因为业务是要求后台来兼容的,通常接口会有版本号控制,用于兼容不同版本的客户端。
对于任何一个App,当可以升级的时候,不会是所有用户就立刻去升级,通常会有很大一部分的用户是不愿意立刻升级的。原因会有很多种,比如我这种的就不会频繁升级,因为对于我来说,这个App并不是天天用,没有必要升级。
那么,我们在iOS开发时,如何去兼容老版本的,保证新版本的增加或者删减不会影响到老版本呢?其实这个问题似乎并不是说有没有新、老版本问题,更重要的是程序的健壮性问题。
对于我们做前端的,永远不要相信后台一定会按照原先约定返回我们想要的数据结构以及所有字段。
假设接口返回来的数据是这样的,我们需要通过类型判断,确保不会因为接口变化返回无效数据而引起闪退。当然,当接口返回的数据结构与我们原先约定的不一样时,通常是因为后台出错了,因此为了程序更健壮,我们应该要容得下后台的错误:
AFHTTPRequestOperation *op = [selfPostRequestWithUrl:urlparams:paramscompletion:^(id responseObject) {
BOOL isSuccess = NO;
if ([responseObjectisKindOfClass:[NSDictionary class]]) {
NSDictionary *response = responseObject[@"response"];
if ([responseisKindOfClass:[NSDictionary class]]) {
NSArray *resultList = response[@"resultList"]; if ([resultListisKindOfClass:[NSArray class]]) { NSArray *listModels = [HYBCosmesisModelobjectArrayWithKeyValuesArray:resultList]; isSuccess = YES; completion(listModels); } } } if (!isSuccess) { // 表示出错 completion(nil); } }errorBlock:^(NSError *error) { errorBlock(error); }];
而我们在使用的时候,对于字符串、数组、字典都应该要做一下类型判断和空处理。比如:
if ([responseisKindOfClass:[NSDictionary class]]) {
NSString *value = response[@"blogName"];
if (!kIsEmptyString(value)) { // Do my job } NSArray *array = response[@"array"]; if ([arrayisKindOfClass:[NSArray class]]) { // Do my job } }
对于业务方面的话,由后台来做版本控制,通过在接口做添加公共参数,[email protected]
�要调用的是哪个版本的接口。
4、你和后端服务器是怎么进行交互的
参考答案:
这个问题非常简单,但是对于新手就不太清楚了。在很多小公司里,前端好像什么都不需要管,只等后台给你一个接口及参数说明就可以了,根本不清楚后端为什么要这么设计这个接口。
那笔者也来聊聊如何与后端服务器交互:
- 在需求确定,定下了开发的周期后,就需要准备开发了。
- iOS、安卓及后端各端主要负责人员在了解完需求后,开始分析本期需求需要哪些接口,是否需要新的接口,是否需要改动原有的接口等。在三端统一后,后端接口负责人确定哪天出接口文档及接口假数据。为什么要三端一起定接口呢?因为即使是后端接口负责人,也不一定对原有的业务和原有的接口全部都了解,任何一方的了解加起来才能确定是否可行。
- 如果App还没有开发过,首先开发一款新的App,那么iOS、安卓端的架构师,或者主要开发负责人,需要与后端接口负责人共同分析需求,然后初步写出第一版本接口文档,然后各方再各自好好看看、分析分析接口是否合理,参数是否合理,结构是否合理等。这数据的结构会决定着整个网络框架的搭建。
好了,就扯谈这些吧~
5、怎么用GCD加载多张图片之后,把图片放到融合到一张图片里?
参考答案:
使用Dispatch Group追加block到Global Group Queue,这些block如果全部执行完毕,就会执行Main Dispatch Queue中的结束处理的block。
当放到group中的所有请求都完成时,才会回调dispatch group
dispatch_queue_tqueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_group_tgroup = 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(), ^{ // 合并图片 });
6、使用GCD的时候,如何在一个group里添加几个任务的依赖关系(这几个任务放在一个组中)
参考答案:
这个不太清楚想问什么,笔者翻看了看GCD中dispatch group t里,也没有什么可以设置同一个组内的任务依赖关系的,就看到dispatch group
long
dispatch_group_wait(dispatch_group_tgroup, dispatch_time_ttimeout);
这个API是等待group中的所有任务都执行完毕才能继续往下执行其它任务。它是同步地等待任务执行完毕。比如,A、B、C、D四个任务,要求A、B执行完毕后,C、D才能开始执行,那么可以通过这样做:
dispatch_queue_tqueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_group_tgroup = dispatch_group_create();
dispatch_group_async(group, queue, ^{ /* 任务A */ });
dispatch_group_async(group, queue, ^{ /* 任务B */ });
dispatch_group_notify(group, dispatch_get_main_queue(), ^{ dispatch_group_async(group, queue, ^{ /* 任务C */ }); dispatch_group_async(group, queue, ^{ /* 任务D */ }); });
或者可以这样:
dispatch_queue_tqueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_group_tgroup = dispatch_group_create();
dispatch_group_async(group, queue, ^{ /* 任务A */ });
dispatch_group_async(group, queue, ^{ /* 任务B */ });
// 同步等待A、B执行 dispatch_group_wait(group, DISPATCH_TIME_FOREVER); dispatch_release(group); // 重新创建组 group = dispatch_group_create(); dispatch_group_async(group, queue, ^{ /* 任务C */ }); dispatch_group_async(group, queue, ^{ /* 任务D */ }); dispatch_group_notify(group, dispatch_get_main_queue(), ^{ // C、D执行完毕后,想干嘛就干嘛去吧 });
不知道笔者对题目的理解是否到位,上面的代码是随手写的,可能单词会写错~~~
7 iOS如何在用户修改头像后正常显示
方案一
笔者所能想到的方案:
笔者针对这个问题,第一想到的就是通过图片的摘要验证。服务端在接口中返回用户信息时,连同图片的摘要(md5)值也下发到客户端,然后每次App登录时,服务端都将最新的用户信息返回来,客户端将本地所缓存的用户头像取出,也生成摘要(md5)五,与服务端所返回来的md5值比较,若相同表示没有头像没有修改过;若不相同,表示头像已经修改过。
缺点:只要重新登录或者App在后台自动登录后,才能更新。也就是说,如果用户没有退出登录,或者没有过期而不会自动登录,头像也没有更新得了。
方案二
在微博上收集到大家的方案。给URL添加一个参数version,当图片修改之后,URL的version发生变化,那么就会重新下载图片来缓存。
缺点:与方案一类似,要求重新请求数据才能得到最新的URL。
方案三
在修改图片时,要求不能使用相同的名字,这样链接路径是一样的,但是因为名字不同而找不到。此时,就会重新获取图片而不是缓存的。可以在文件名中带上前缀进行上传,譬如avatar-3/v1.jpg,更新后改为avatar-3/v2.jpg。
方案四
客户端在请求头加上If-Modified-Since字段,表明请求此时间后最新的文件资源,服务端也会在响应头返回这个last-modified字段表示上次修改时间。
第二部分:
1 iOS基础
1.1 父类实现深拷贝时,子类如何实现深度拷贝。父类没有实现深拷贝时,子类如何实现深度拷贝。
- 深拷贝同浅拷贝的区别:浅拷贝是指针拷贝,对一个对象进行浅拷贝,相当于对指向对象的指针进行复制,产生一个新的指向这个对象的指针,那么就是有两个指针指向同一个对象,这个对象销毁后两个指针都应该置空。深拷贝是对一个对象进行拷贝,相当于对对象进行复制,产生一个新的对象,那么就有两个指针分别指向两个对象。当一个对象改变或者被销毁后拷贝出来的新的对象不受影响。
- 实现深拷贝需要实现NSCoying协议,实现- (id)copyWithZone:(NSZone *)zone 方法。当对一个property属性含有copy修饰符的时候,在进行赋值操作的时候实际上就是调用这个方法。
- 父类实现深拷贝之后,子类只要重写copyWithZone方法,在方法内部调用父类的copyWithZone方法,之后实现自己的属性的处理
- 父类没有实现深拷贝,子类除了需要对自己的属性进行处理,还要对父类的属性进行处理。
1.2 KVO,NSNotification,delegate及block区别
- KVO就是cocoa框架实现的观察者模式,一般同KVC搭配使用,通过KVO可以监测一个值的变化,比如View的高度变化。是一对多的关系,一个值的变化会通知所有的观察者。
- NSNotification是通知,也是一对多的使用场景。在某些情况下,KVO和NSNotification是一样的,都是状态变化之后告知对方。NSNotification的特点,就是需要被观察者先主动发出通知,然后观察者注册监听后再来进行响应,比KVO多了发送通知的一步,但是其优点是监听不局限于属性的变化,还可以对多种多样的状态变化进行监听,监听范围广,使用也更灵活。
- delegate 是代理,就是我不想做的事情交给别人做。比如狗需要吃饭,就通过delegate通知主人,主人就会给他做饭、盛饭、倒水,这些操作,这些狗都不需要关心,只需要调用delegate(代理人)就可以了,由其他类完成所需要的操作。所以delegate是一对一关系。
- block是delegate的另一种形式,是函数式编程的一种形式。使用场景跟delegate一样,相比delegate更灵活,而且代理的实现更直观。
- KVO一般的使用场景是数据,需求是数据变化,比如股票价格变化,我们一般使用KVO(观察者模式)。delegate一般的使用场景是行为,需求是需要别人帮我做一件事情,比如买卖股票,我们一般使用delegate。
Notification一般是进行全局通知,比如利好消息一出,通知大家去买入。delegate是强关联,就是委托和代理双方互相知道,你委托别人买股票你就需要知道经纪人,经纪人也不要知道自己的顾客。Notification是弱关联,利好消息发出,你不需要知道是谁发的也可以做出相应的反应,同理发消息的人也不需要知道接收的人也可以正常发出消息。
1.3 KVC如果实现,如何进行键值查找。KVO如何实现
1.4 将一个函数在主线程执行的4种方法
- GCD方法,通过向主线程队列发送一个block块,使block里的方法可以在主线程中执行。
dispatch_async(dispatch_get_main_queue(), ^{
//需要执行的方法
});
- NSOperation 方法
NSOperationQueue *mainQueue = [NSOperationQueue mainQueue]; //主队列
NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{ //需要执行的方法 }]; [mainQueue addOperation:operation];
- NSThread 方法
[self performSelector:@selector(method) onThread:[NSThread mainThread] withObject:nil waitUntilDone:YES modes:nil]; [self performSelectorOnMainThread:@selector(method) withObject:nil waitUntilDone:YES]; [[NSThread mainThread] performSelector:@selector(method) withObject:nil];
- RunLoop方法
[[NSRunLoop mainRunLoop] performSelector:@selector(method) withObject:nil];
1.5 如何让计时器调用一个类方法
- 计时器只能调用实例方法,但是可以在这个实例方法里面调用静态方法。
- 使用计时器需要注意,计时器一定要加入RunLoop中,并且选好model才能运行。scheduledTimerWithTimeInterval方法创建一个计时器并加入到RunLoop中所以可以直接使用。
- 如果计时器的repeats选择YES说明这个计时器会重复执行,一定要在合适的时机调用计时器的invalid。不能在dealloc中调用,因为一旦设置为repeats 为yes,计时器会强持有self,导致dealloc永远不会被调用,这个类就永远无法被释放。比如可以在viewDidDisappear中调用,这样当类需要被回收的时候就可以正常进入dealloc中了。
[NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(timerMethod) userInfo:nil repeats:YES]; -(void)timerMethod { //调用类方法 [[self class] staticMethod]; } -(void)invalid { [timer invalid]; timer = nil; }
1.6 如何重写类方法
- 1、在子类中实现一个同基类名字一样的静态方法
- 2、在调用的时候不要使用类名调用,而是使用[self class]的方式调用。原理,用类名调用是早绑定,在编译期绑定,用[self class]是晚绑定,在运行时决定调用哪个方法。
1.7 NSTimer创建后,会在哪个线程运行。
- 用scheduledTimerWithTimeInterval创建的,在哪个线程创建就会被加入哪个线程的RunLoop中就运行在哪个线程
- 自己创建的Timer,加入到哪个线程的RunLoop中就运行在哪个线程。
1.8 id和NSObject*的区别
- id是一个 objc_object 结构体指针,定义是
typedef struct objc_object *id
- id可以理解为指向对象的指针。所有oc的对象 id都可以指向,编译器不会做类型检查,id调用任何存在的方法都不会在编译阶段报错,当然如果这个id指向的对象没有这个方法,该崩溃还是会崩溃的。
- NSObject *指向的必须是NSObject的子类,调用的也只能是NSObjec里面的方法否则就要做强制类型转换。
- 不是所有的OC对象都是NSObject的子类,还有一些继承自NSProxy。NSObject *可指向的类型是id的子集。
我的理解如果有错漏请一定指出,非常感谢!
以下内容后续补充
iOS 核心框架
- CoreAnimation
- CoreGraphics
- CoreLocation
- AVFoundation
- Foundation
iOS核心机制
- UITableView 重用
- ObjC内存管理;自动释放池,ARC如何实现
- runloop
- runtime
- Block的定义、特性、内存区域、如何实现
- Responder Chain
- NSOperation
- GCD
数据结构
- 8大排序算法
- 二叉树实现
- 二分查找实现
面向对象编程
- 封装、继承、多态
- 设计模式6个原则
- 设计一个类的功能,如何划分粒度(单一职责)
- 接口隔离。
- 如果有一个鸟类,有飞的动作,一个鸵鸟继承它是合适的吗(里氏替换)
- 类之间的依赖如何依赖偶合度最小(依赖倒转)
高层依赖低层,低层不能依赖高层。依赖接口,不能依赖具体的类。 - 如果A要调用C函数,但C是B的成员类,应该如何设计?(迪米特)
- 如何设计类,能做到只增加代码,而不修改代码,有哪些经验(开放封闭)
通过设计模式解决。
计算机技术
- 计算机网络: TCP/IP、HTTPCDN、SPDY
- 计算机安全: RSA、AES、DES
- 操作系统:线程、进程、堆栈、死锁、调度算法
iOS新特性、新技术
- iOS7 UIDynamic、SpritKit、新布局、扁平化
- iOS8 应用程序扩展、HealthKit、SceneKit、CoreLocation、TouchID、PhotoKit
- iOS9
- Apple Watch
- 第三方库:SDWebImage、AFNetwork、JSONKit、wax
- swift