题照
1、对数组中的元素去重复
例如:
1 2 3 |
NSArray *array |
参考答案:
- 第一种方法:开辟新的内存空间,然后判断是否存在,若不存在则添加到数组中,得到最终结果的顺序不发生变化。效率分析:时间复杂度为O ( n2 ):
1 2 3 4 5 6 7 8 9 10 11 12 |
NSMutableArray *resultArray // 外层一个循环 for (NSString // 调用-containsObject:本质也是要循环去判断,因此本质上是双层遍历 // 时间复杂度为O ( n^2 )而不是O (n) if [resultArray addObject:item]; } } NSLog(@"resultArray: %@", |
补充:原来集合操作可以通过valueForKeyPath来实现的,去重可以一行代码实现:
1 2 3 4 |
array = NSLog(@"%@", |
但是返回的结果是无序的,与原来的顺序不同。
- 第二种方法:利用NSDictionary去重,字典在设置key-value时,若已存在则更新值,若不存在则插入值,然后获取allValues。若不要求有序,则可以采用此种方法。若要求有序,还得进行排序。效率分析:只需要一个循环就可以完成放入字典,若不要求有序,时间复杂度为O(n)。若要求排序,则效率与排序算法有关:
1 2 3 4 5 6 7 8 |
NSMutableDictionary *resultDict for [resultDict setObject:item forKey:item]; } NSArray *resultArray NSLog(@"%@", |
如果需要按照原来的升序排序,可以这样:
1 2 3 4 5 6 7 8 |
resultArray = NSString NSString return }]; NSLog(@"%@", |
- 第三种方法:利用集合NSSet的特性(确定性、无序性、互异性),放入集合就自动去重了。但是它与字典拥有同样的无序性,所得结果顺序不再与原来一样。如果不要求有序,使用此方法与字典的效率应该是差不多的。效率分析:时间复杂度为O (n):
1 2 3 4 5 |
NSSet *set NSArray NSLog(@"%@", |
如果要求有序,那就得排序,比如这里要升序排序:
1 2 3 4 5 6 7 8 |
resultArray = NSString NSString return }]; NSLog(@"%@", |
补充:
一直没有使用过有序集合,网友们反馈到可以直接使用有序集合,感谢大家:
1 2 3 4 |
NSOrderedSet *set NSLog(@"%@", |
以上三种方法是笔者所能想到的办法。如果大家有更好的办法,欢迎在评论中指出。
2、说说以下元素的特性和作用
1 2 3 |
NSArray、NSSet、NSDictionary与NSMutableArray、NSMutableSet、NSMutableDictionary |
参考答案:
特性:
- NSArray表示不可变数组,是有序元素集,只能存储对象类型,可通过索引直接访问元素,而且元素类型可以不一样,但是不能进行增、删、改操作;NSMutableArray是可变数组,能进行增、删、改操作。通过索引查询值很快,但是插入、删除等效率很低。
- NSSet表示不可变集合,具有确定性、互异性、无序性的特点,只能访问而不能修改集合;NSMutableSet表示可变集合,可以对集合进行增、删、改操作。集合通过值查询很快,插入、删除操作极快。
- NSDictionary表示不可变字典,具有无序性的特点,每个key对应的值是唯一的,可通过key直接获取值;NSMutableDictionary表示可变字典,能对字典进行增、删、改操作。通过key查询值、插入、删除值都很快。
作用:
- 数组用于处理一组有序的数据集,比如常用的列表的dataSource要求有序,可通过索引直接访问,效率高。
- 集合要求具有确定性、互异性、无序性,在iOS开发中是比较少使用到的,笔者也不清楚如何说明其作用
- 字典是键值对数据集,操作字典效率极高,时间复杂度为常量,但是值是无序的。在ios中,常见的JSON转字典,字典转模型就是其中一种应用。
3、简单描述一下XIB与Storyboards,说一下他们的优缺点。
参考答案:
笔者倾向于纯代码开发,所以所提供的参考答案可能具有一定的个人感情,不过还是给大家说说笔者的想法。
优点:
- XIB:在编译前就提供了可视化界面,可以直接拖控件,也可以直接给控件添加约束,更直观一些,而且类文件中就少了创建控件的代码,确实简化不少,通常每个XIB对应一个类。
- Storyboard:在编译前提供了可视化界面,可拖控件,可加约束,在开发时比较直观,而且一个storyboard可以有很多的界面,每个界面对应一个类文件,通过storybard,可以直观地看出整个App的结构。
缺点:
- XIB:需求变动时,需要修改XIB很大,有时候甚至需要重新添加约束,导致开发周期变长。XIB载入相比纯代码自然要慢一些。对于比较复杂逻辑控制不同状态下显示不同内容时,使用XIB是比较困难的。当多人团队或者多团队开发时,如果XIB文件被发动,极易导致冲突,而且解决冲突相对要困难很多。
- Storyboard:需求变动时,需要修改storyboard上对应的界面的约束,与XIB一样可能要重新添加约束,或者添加约束会造成大量的冲突,尤其是多团队开发。对于复杂逻辑控制不同显示内容时,比较困难。当多人团队或者多团队开发时,大家会同时修改一个storyboard,导致大量冲突,解决起来相当困难。
4、请把字符串2015-04-10
格式化日期转为NSDate类型
参考答案:
1 2 3 4 5 6 7 8 9 |
NSString *timeStr NSDateFormatter formatter.dateFormat formatter.timeZone NSDate *date // 2015-04-09 16:00:00 +0000 NSLog(@"%@", |
5、在App中混合HTML5开发App如何实现的。在App中使用HTML5的优缺点是什么?
参考答案:
在iOS中,通常是通常UIWebView来实现,当然在iOS8以后可以使用WKWebView来实现.有以下几种实现方法:
- 通过实现UIWebView的代理方法来拦截,判断scheme是否是约定好的,然后iOS调用本地相关API来实现:
1 2 3 |
- (BOOL)webView:(UIWebView |
- 在iOS7以后,可以直接通过JavaScripteCore这个库来实现,通过往JS DOM注入对象,而这个对象对应于我们iOS的某个类的实例。更详细请阅读:
- 可以通过WebViewJavascriptBridge来实现。具体如何使用,请大家去其它博客搜索吧!
优缺点:
- iOS加入H5响应比原生要慢很多,体验不太好,这是缺点。
- iOS加入H5可以实现嵌入别的功能入口,可随时更改,不用更新版本就可以上线,这是最大的优点
6、请描述一下同步和异步,说说它们之间的区别。
参考答案:
首先,我们要明确一点,同步和异步都是在线程中使用的。在iOS开发中,比如网络请求数据时,若使用同步请求,则只有请求成功或者请求失败得到响应返回后,才能继续往下走,也就是才能访问其它资源(会阻塞了线程)。网络请求数据异步请求时,不会阻塞线程,在调用请求后,可以继续往下执行,而不用等请求有结果才能继续。
区别:
- 线程同步:是多个线程访问同一资源时,只有当前正在访问的线程访问结束之后,其他线程才能开始访问(被阻塞)。
- 线程异步:是多个线程在访问竞争资源时,可以在空闲等待时去访问其它资源(不被阻塞)。
7、请简单描述一下队列和多线程的使用原理。
参考答案:
在iOS中队列分为以下几种:
- 串行队列:队列中的任务只会顺序执行
1 2 3 |
dispatch_queue_t |
- 并行队列: 队列中的任务通常会并发执行
1 2 3 |
dispatch_queue_t |
- 全局队列:是系统的,直接拿过来(GET)用就可以;与并行队列类似
1 2 3 |
dispatch_queue_t |
- 主队列:每一个应用程序对应唯一一个主队列,直接GET即可;在多线程开发中,使用主队列更新UI
1 2 3 |
dispatch_queue_t |
上面这四种是针对GCD来讲的,串行队列中的任务只能一个个地执行,在前一个没有执行完毕之前,下一个只能等待。并行队列可以并发地执行任务,因此多个任务之间执行的顺序不能确定,当添加一个新的任务时,交由GCD来判断是否要创建新的新的线程。
大家可以阅读图片多线程,也许更明了:
8、描述一下iOS的内存管理,在开发中对于内存的使用和优化包含哪些方面。我们在开发中应该注意哪些问题。
参考答案:
内存管理准则:谁强引用过,谁就在不再使用时使引用计数减一。
对于内存的使用和优化常见的有以下方面:
- 重用问题:如UITableViewCells、UICollectionViewCells、UITableViewHeaderFooterViews设置正确的reuseIdentifier,充分重用。
- 尽量把views设置为不透明:当opque为NO的时候,图层的半透明取决于图片和其本身合成的图层为结果,可提高性能。
- 不要使用太复杂的XIB/Storyboard:载入时就会将XIB/storyboard需要的所有资源,包括图片全部载入内存,即使未来很久才会使用。那些相比纯代码写的延迟加载,性能及内存就差了很多。
- 选择正确的数据结构:学会选择对业务场景最合适的数组结构是写出高效代码的基础。比如,数组: 有序的一组值。使用索引来查询很快,使用值查询很慢,插入/删除很慢。字典: 存储键值对,用键来查找比较快。集合: 无序的一组值,用值来查找很快,插入/删除很快。
- gzip/zip压缩:当从服务端下载相关附件时,可以通过gzip/zip压缩后再下载,使得内存更小,下载速度也更快。
- 延迟加载:对于不应该使用的数据,使用延迟加载方式。对于不需要马上显示的视图,使用延迟加载方式。比如,网络请求失败时显示的提示界面,可能一直都不会使用到,因此应该使用延迟加载。
- 数据缓存:对于cell的行高要缓存起来,使得reload数据时,效率也极高。而对于那些网络数据,不需要每次都请求的,应该缓存起来,可以写入数据库,也可以通过plist文件存储。
- 处理内存警告:一般在基类统一处理内存警告,将相关不用资源立即释放掉
- 重用大开销对象:一些objects的初始化很慢,比如NSDateFormatter和NSCalendar,但又不可避免地需要使用它们。通常是作为属性存储起来,防止反复创建。
- 避免反复处理数据:许多应用需要从服务器加载功能所需的常为JSON或者XML格式的数据。在服务器端和客户端使用相同的数据结构很重要。
- 使用Autorelease Pool:在某些循环创建临时变量处理数据时,自动释放池以保证能及时释放内存。
- 正确选择图片加载方式:详情阅读细读UIImage加载方式
9、plist文件是用来做什么的。一般用它来处理一些什么方面的问题。
参考答案:
plist是iOS系统中特有的文件格式。我们常用的NSUserDefaults偏好设置实质上就是plist文件操作。plist文件是用来持久化存储数据的。
我们通常使用它来存储偏好设置,以及那些少量的、数组结构比较复杂的不适合存储数据库的数据。比如,我们要存储全国城市名称和id,那么我们要优先选择plist直接持久化存储,因为更简单。
10、iOS中缓存一定量的数据以便下次可以快速执行,那么数据会存储在什么地方,有多少种存储方式?
参考答案:
- 偏好设置(NSUserDefaults)
- plist文件存储
- 归档
- SQLite3
- Core Data
11、请简单写出增、删、改、查的SQL语句。
参考答案:
数据库的简单操作,还是会的,大学可没白学。
增:
1 2 3 |
insert into tb_blogs(name, |
删:
1 2 3 |
delete from tb_blogs |
改:
1 2 3 |
update tb_blogs set |
查:
1 2 3 |
select name, |
12、在提交苹果审核时,遇到哪些问题被拒绝,对于被拒绝的问题是如何处理的。
参考答案:
对于笔者而言,所提交过的app还没有被拒绝过。不过在笔者所维护的几个群里经常有朋友们问到相关被拒绝的解决办法。幸好还懂一点点英文,还能帮助他们翻译翻译苹果反馈的被拒原因及所担的建议。
这里只列出几种最常见的被拒原因:
- 最常见到的就是app中有虚拟物品交易,但是不是走内购导致被拒。
- 音频类App或者使用到音频相关的app,因为版权问题而被拒
- App出现必闪退而被拒
最后
感谢标哥整理的试题~
题目照
1、编程规范问题
这题看不清楚,不过可以看得出来是编程规范问题。所以呢,笔者也就没有办法说明哪些不合理了。不过笔者曾经为公司的出过一个编程规范文档,后来整理成文章GIF图分享给大家。
2、请写出UIViewController的完整生命周期
参考答案:
下面是笔者通过打印,先出现ViewController,然后在点击ViewController上的按钮时,模态弹出了一个纯代码HYBViewController,其打印如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
-[ViewController -[ViewController -[ViewController -[ViewController -[ViewController // present HYBViewController -[HYBViewController -[HYBViewController -[HYBViewController -[HYBViewController -[ViewController -[HYBViewController -[HYBViewController -[ViewController |
生命周期如下:
1 2 3 4 5 6 7 8 9 |
* xib/storyboard:-initWithCoder:,而非xib/storyboard的是-initWithNibName:bundle:然后-init * * -viewDidLoad * * -viewDidAppear: * * -viewDidDisappear: |
注意,当从ViewController进入到HYBViewController控制器时,注意出现顺序如下:
1 2 3 4 5 6 |
* -[ViewController * * -[HYBViewController * |
在HYBViewController完全出现后,才会调用前一个控制器的完全消失。像这种要不同控制器之间导航条隐藏与显示控制问题,就需要特别注意其生命周期的顺序。
注意:有朋友说这里是错的,不过笔者打印出来验证发现就是这样的顺序!用模态呈现测试!截图如下:
请大家看看哪里出错!
请写出有多少有方法给UIImageView添加圆角?
参考答案:
- 最直接的方法就是使用如下属性设置:
1 2 3 4 5 |
imgView.layer.cornerRadius // 这一行代码是很消耗性能的 imgView.clipsToBounds |
好处是使用简单,操作方便。坏处是离屏渲染(off-screen-rendering)需要消耗性能。对于图片比较多的视图上,不建议使用这种方法来设置圆角。通常来说,计算机系统中CPU、GPU、显示器是协同工作的。CPU计算好显示内容提交到GPU,GPU渲染完成后将渲染结果放入帧缓冲区。
简单来说,离屏渲染,导致本该GPU干的活,结果交给了CPU来干,而CPU又不擅长GPU干的活,于是拖慢了UI层的FPS(数据帧率),并且离屏需要创建新的缓冲区和上下文切换,因此消耗较大的性能。
- 给UIImage添加生成圆角图片的扩展API:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
- (UIImage CGRect UIGraphicsBeginImageContextWithOptions(self.size, CGContextAddPath(UIGraphicsGetCurrentContext(), [UIBezierPath bezierPathWithRoundedRect:rect cornerRadius:radius].CGPath); CGContextClip(UIGraphicsGetCurrentContext()); [self drawInRect:rect]; UIImage UIGraphicsEndImageContext(); return } |
然后调用时就直接传一个圆角来处理:
1 2 3 |
imgView.image |
这么做就是on-screen-rendering了,通过模拟器->debug->Color Off-screen-rendering看到没有离屏渲染了!(黄色的小圆角没有显示了,说明这个不是离屏渲染了)
- 在画之前先通过UIBezierPath添加裁剪,但是这种不实用:
1 2 3 4 5 6 7 8 |
- (void)drawRect:(CGRect)rect CGRect [[UIBezierPath bezierPathWithRoundedRect:rect cornerRadius:8.0] [self.image drawInRect:bounds]; } |
- 通过mask遮罩实现
这里不细说了,个人感觉不如第二种好用、通用
若有更多,可以在评论中指出~
4、请描述事件响应者链的工作原理
参考答案:
iOS使用hit-testing寻找触摸的view。 Hit-Testing通过检查触摸点是否在关联的view边界内,如果在,则递归地检查该view的所有子view。在层级上处于lowest(就是离用户最近的view)且边界范围包含触摸点的view成为hit-test view。确定hit-test view后,它传递触摸事件给该view。
官方小例子事件响应者链如下图所示:
- 触摸点在view A中,所以要先检查子view B和C。
- 触摸点不在view B中,但在C中,所以检查C的子view D和E。
- 触摸点不在D中,但在E中。View E是这个层级上处于lowest的view的边界范围包含触摸点,所以它成为了hit-test view。
Hit-test view是处理触摸事件的第一选择,如果hit-test view不能处理事件,该事件将从事件响应链中寻找响应器,直到系统找到一个处理事件的对象。若不能处理,则就有事件传递链了,继续看下面的事件传递链。
事件传递链如下图所示:
左半图:
- initial view若不能处理事件,则传到其父视图view
- view若不能处理,则传到其父视图,因为它还不是最上层视图
- 这里view的父视图是view controller的view,因为这个view也不能处理事件,因此传给view controller
- 若view controller也不能处理此事件,则传到window
- 若window也不能处理此事件,则传到app单例对象Application
- 若UIApplication单例对象也不能处理,则表示无效事件
右半图:
- initial view一直传递直到最上层view(原话:A view passes an event up its view controller’s view hierarchy until it reaches the topmost view.)
- topmost view传递事件到它所在的控制器(原话:The topmost view passes the event to its view controller.)
- view controller传递事件到topmost view的父视图,重复前三步,走到到达root controller(原话:passes the event to its topmost view’s superview. Steps 1-3 repeat until the event reaches the root view controller.)
- 由root控制器传递事件到window(原话:The root view controller passes the event to the window object.)
- 若window也不能处理此事件,则传到app单例对象Application
- 若UIApplication单例对象也不能处理,则表示无效事件
为了解答这个小题目,翻阅了官方文档,由于内容较多,这里不说那么多,若要了解更多,参考官方文档吧:Event
Handling Guide for iOS
5、如何避免使用block时发生循环引用
参考答案:
关于block循环引用问题是非常常见的,但是很多人没有深入研究过,xcode没有提示警告就以为没有形成循环引用了。笔者也见过很多高级iOS开发工程师的同事,使用block并不会分析是否形成循环引用。
6、请比较GCD与NSOperation的异同
参考答案:
- 相同点:GCD和NSOperation都是苹果提供的多线程实现方案。
- 不同点:GCD是轻量级的纯C写的多线程实现方案,使用起来非常方便,在开发中大量使用,但是对于取消和暂停线程就比较麻烦些。而NSOperation是面向对象的,兼容KVO,对于取消和暂停任务是非常容易的。
7、请写出NSTimer使用时的注意事项(两项即可)
说到NSTimer这个定时器类,要使用好它,还得了解Run Loop,因为在不同的run loop mode下,定时器不都会回调的。
mode主要是用来指定事件在运行循环中的优先级的,分为:
- NSDefaultRunLoopMode(kCFRunLoopDefaultMode):默认,空闲状态
- UITrackingRunLoopMode:ScrollView滑动时会切换到该Mode
- UIInitializationRunLoopMode:run loop启动时,会切换到该mode
- NSRunLoopCommonModes(kCFRunLoopCommonModes):Mode集合
苹果公开提供的Mode有两个:
- NSDefaultRunLoopMode(kCFRunLoopDefaultMode)
- NSRunLoopCommonModes(kCFRunLoopCommonModes)
如果我们把一个NSTimer对象以NSDefaultRunLoopMode(kCFRunLoopDefaultMode)添加到主运行循环中的时候, ScrollView滚动过程中会因为mode的切换,而导致NSTimer将不再被调度。当我们滚动的时候,也希望不调度,那就应该使用默认模式。但是,如果希望在滚动时,定时器也要回调,那就应该使用common mode。
如果想要销毁timer,则必须先将timer置为失效,否则timer就一直占用内存而不会释放。造成逻辑上的内存泄漏。该泄漏不能用xcode及instruments测出来。 另外对于要求必须销毁timer的逻辑处理,未将timer置为失效,若每次都创建一次,则之前的不能得到释放,则会同时存在多个timer的实例在内存中。
参考答案:
- 注意timer添加到runloop时应该设置为什么mode
- 注意timer在不需要时,一定要调用invalidate方法使定时器失效,否则得不到释放
8、说说Core Animation是如何开始和结束动画的
笔者不是很清楚题目的真正要求,是想知道核心动画的哪些知识点。如何开始和结束动画,这核心动画有很多种,每种动画还有很大的区别。
参考答案:
动画的开始和结束都可以通过CAMediaTiming协议来处理,核心动画的基类是遵守了CAMediaTiming协议的,可以指定动画开始时间、动画时长、动画播放速度、动画在完成时的行为(停留在结束处、动画回到开始处、动画完成时移除动画)。