iOS面试题-开发实用篇

2018-03-16  本文已影响47人  洁简

简述APP生命周期

一张图即可反映APP生命周期:

APP
APP启动过程及简单优化
1. 解析Info.plist
加载相关信息,如闪屏
沙箱建立,权限检查
2. Mach-O加载
如果是胖二进制,寻找合适CUP类别的部分
加载所有依赖Mach-O文件
定位内部,外部指针引用,如字符串,函数
执行声明函数
加载扩展类
C++静态对象加载,调用Objc的+(load)函数
3. 程序执行
main函数
执行UIApplicationMain函数
UIApplicationDelegate对象开始处理监听事件

如果APP启动缓慢,可以想到的因素
main() 函数内有耗时操作;
动态库加载太多;
rootViewControlle以及childViewController的加载,view和subViews的加载耗时;
优化:
移除不需要用到的动态库
移除不需要用到的类
合并功能类似的类和扩展(Category)
压缩图片资源
优化applicationWillFinishLaunching
优化rootViewController加载

UIViewController 的生命周期

0.loadView:加载视图
1.loadViewIfNeeded(iOS9后):重新加载视图包括viewDidLoad
2.viewDidLoad:视图控制器中的视图加载完成,viewController自带的view加载完成
3.viewWillAppear:视图将要出现
4.viewWillLayoutSubviews:即将布局其 Subviews
5.viewDidLayoutSubviews:已经布局其 Subviews
6.viewDidAppear:视图已经出现
7.viewWillDisappear:视图将要消失
8.viewDidDisappear:视图已经消失
这个面试点在实际开发中还是比较重要的,毕竟写个视图都会有自己的生命周期,从而更好的优化视图加载.

一张老图:

生命周期
loadView什么作用
loadView在View为nil时调用,早于ViewDidLoad,通常用于代码实现控件,收到内存警告时会再次调用。
loadView默认做的事情是:如果此Viewcontroller存在一个对应的nib文件,那么就加载这个nib。否则,就创建一个UIView对象。
如果你用Interface Builder来创建界面,那么不应该重载这个方法。

如果你想自己创建View对象,那么可以重载这个方法,此时你需要自己给View属性赋值。
你自定义的方法不应该调用super。如果你需要对View做一些其他定制操作,在ViewDidload中去做

根据上面说明可以知道,有两种情况:

1、如果你用了nib文件,重载这个方法就没有太大意义。因为loadView的作用就是加载nib。
如果你重载了这个方法不调用super,那么nib文件就不会被加载。
如果调用了super,那么view已经加载完了,你需要做的其他事情在viewDidLoad里面做更合适。

2、如果你没有用nib,这个方法默认就是创建一个空的view对象。如果你想自己控制view对象的创建,
例如创建一个特殊尺寸的view,那么可以重载这个方法,自己创建一个UIView对象,然后指定 self.view = myView;
但这种情况也没有必要调用super,因为反正你也不需要在super方法里面创建的view对象。如果调用了super,那么就是浪费了一些资源而已

View中的加载顺序

1.initWithCoder(如果没有storyboard就会调用initWithFrame)
2.awakeFromNib:作为第一个方法的助手,方便处理一些额外的设置。
3.layoutSubviews:一般设置子控件的frame。
4.drawRect:UI控件都是画上去的,在这一步就是把所有的东西画上去。
layoutSubviews方便数据计算,drawRect方便视图重绘。
drawRect在以下情况下会被调用:
1、如果在UIView初始化时没有设置rect大小,将直接导致drawRect不被自动调用。drawRect 掉用是在Controller->loadView,Controller->viewDidLoad两方法之后掉用的.
  所以不用担心在 控制器中,这些View的drawRect就开始画了.这样可以在控制器中设置一些值给View(如果这些View?draw的时候需要用到某些变量 值).
2、该方法在调用sizeToFit后被调用,所以可以先调用sizeToFit计算出size。然后系统自动调用drawRect:方法。
3、通过设置contentMode属性值为UIViewContentModeRedraw。那么将在每次设置或更改frame的时候自动调用drawRect:。
4、直接调用setNeedsDisplay,或者setNeedsDisplayInRect:触发drawRect:,但是有个前提条件是rect不能为0。
在实际开发中layoutSubviews个人用的比较多,用来重新设置控件大小.
drawRect一般用来做绘图个人用的不多.

什么是KVC和KVO?

KVC也就是key-value-coding,即键值编码,通常是用来给某一个对象的属性进行赋值.
开发中我们可以对私有属性进行赋值的,修改一些控件的内部属性,还可以用于字典转模型.
KVO,即key-value-observing,利用一个key来找到某个属性并监听其值得改变。其实这也是一种典型的观察者模式。
大致用法:
1.添加观察者
2.在观察者中实现监听方法,observeValueForKeyPath: ofObject: change: context:
3.移除观察者
简单实现原理:
当一个类的属性被观察的时候,系统会通过runtime动态的创建一个该类的派生类,并且会在这个类中重写基类被观察的属性的setter方法,
而且系统将这个类的isa指针指向了派生类,从而实现了给监听的属性赋值时调用的是派生类的setter方法。
重写的setter方法会在调用原setter方法前后,通知观察对象值得改变。
KVC/KVO实现的根本是Objective-C的动态性和runtime

开发中,保存数据有哪几种方式?

所谓的持久化,就是将数据保存到磁盘中,使得在应用程序重启后可以继续访问之前保存的数据.
iOS本地数据保存有多种方式,比如NSUserDefaults、Plist文件保存、归档(NSKeyedArchiver)、SQLite、CoreData、KeyChain(钥匙串)等多种方式。
NSUserDefaults:
  NSUserDefaults 是一个单例对象,在整个应用程序的生命周期中都只有一个实例。
  NSUserDefaults保存的数据类型有:NSNumber, 基本数据类型(int,NSInter,float,double,CGFlat......),   NSString, NSData, NSArray, NSDictionary, NSURL。
  NSUserDefaults一般保存配置信息,比如用户名、密码、是否保存用户名和密码、是否离线下载等一些配置条件信息。
plist文件保存:
  plist文件是将某些特定的类,通过XML文件的方式保存在目录中。
  plist主要保存的数据类型为NSString、NSNumber、NSData、NSArray、NSDictionary。
可以看出NSUserDefaults和plist有一定局限性.
归档:在iOS中是另一种形式的序列化,只要遵循了NSCoding协议的对象都可以通过它实现序列化。
  需要注意的:必须遵循并实现NSCoding协议;保存文件的扩展名可以任意指定;继承时必须先调用父类的归档解档方法
上面的几个存储方法,都是覆盖存储。如果想要增加一条数据就必须把整个文件读出来,然后修改数据后再把整个内容覆盖写入文件。所以它们都不适合存储大量的内容。
因此保存大量数据可以优先考虑用数据库,sql语句对查询操作有优化作用,所以从查询速度或者插入效率都是很高的。
SQLite:在iOS中要使用SQLite3,需要添加库文件:libsqlite3.dylib并导入主头文件,这是一个C语言的库,所以直接使用SQLite3还是比较麻烦的。
  不过在一般开发过程中,使用的都是第三方开源库 FMDB,封装了这些基本的c语言方法,使得我们在使用时更加容易理解,提高开发效率。
CoreData:
  CoreData框架提供了对象-关系映射(ORM)的功能,即能够将OC对象转化成数据,保存在SQLite3数据库文件中,也能将保存在数据库中的数据还原成OC对象.在次数据操作期间,不需要编写任何SQL语句.
上面几种都是保存到沙盒中.
KeyChain:钥匙串是苹果公司Mac OS中的密码管理系统。一个钥匙串可以包含多种类型的数据:密码(包括网站,FTP服务器,SSH帐户,网络共享,无线网络,加密磁盘镜像等),私钥,电子证书和加密笔记等。
  当应用程序被删除后,保存到KeyChain里面的数据不会被删除,所以KeyChain是保存到沙盒范围以外的地方。安全性也比较高.
  KeyChain还有一个用途,就是替代UDID。UDID已经被废除了,所以只能用UUID代替,所以我们可以把UUID用KeyChain保存。

关键字const/static/extern、UIKIT_EXTERN区别和用法以及与宏的区别

const:
  1.const用来修饰右边的基本变量或指针变量
  2.被修饰的变量只读,不能被修改
  int  const  *p   //  *p只读 ;p变量
  int  *const  p  // *p变量 ; p只读
  const  int   *const p //p和*p都只读
  int  const  *const  p   //p和*p都只读
  开发者经常定义只读变量:
  const CGFloat kWidth = 10.0;
static:
  1.修饰局部变量,保证局部变量永远只初始化一次,在程序的运行过程中永远只有一份内存,  生命周期类似全局变量了,但是作用域不变。
  2.修饰全局变量使全局变量的作用域仅限于当前文件内部,即当前文件内部才能访问该全局变量。
  3.修饰函数时,被修饰的函数被称为静态函数,使得外部文件无法访问这个函数,仅本文件可以访问
   另外在开发中经常在单例中使用.
extern:它的作用是声明外部全局变量。这里需要特别注意extern只能声明,不能用于实现,而且定义和分配内存都在原来类中。
  UIKIT_EXTERN:可以解决重复定义的问题,可以参照苹果的做法,比如系统预置的通知:
  UIKIT_EXTERN NSString *const   UIKeyboardWillShowNotification;
  UIKIT_EXTERN NSString *const  UIKeyboardDidShowNotification;
宏:1.宏在编译开始之前就会被替换,而const只是变量进行修饰; 
  2.宏可以定义一些函数方法,const不能 
  3.宏编译时只替换不做检查不报错,也就是说有重复定义问题。而const会编译检查,会报错  
定义不对外公开的常量的时候,我们应该尽量先考虑使用 static 方式声名const来替代使用宏定义。const不能满足的情况再考虑使用宏定义。
例如:
  static const CGFloat kWidth = 10.0;
  可以代替 #define WIDTH 10.0

block的实质是什么?一共有几种block?都是什么情况下生成的?

block对象是一个c语言级别的语法和运行机制。它与标准c函数类似,不同之处在于,它除了有可执行的代码之外,还包含了与堆、栈内存绑定的变量。
作为一个回调,block特别的有用,因为block既包含了回调期间的代码,又包含了执行期间需要的数据。
iOS中有三种:
NSStackBlock    存储于栈区
NSGlobalBlock   存储于程序数据区
NSMallocBlock   存储于堆区
NSGlobalBlock:
  block 内部没有引用外部变量的 Block 类型都是 NSGlobalBlock 类型,存储于全局数据区,由系统管理其内存,retain、copy、release操作都无效。
NSStackBlock:
  block 内部引用外部变量,retain、release 操作无效,存储于栈区,变量作用域结束时,其被系统自动释放销毁.但在ARC下block 变量在赋值的时候系统自动将其拷贝到堆区了
NSMallocBlock:
  [block retain],[blockA copy]操作后变量类型变为 NSMallocBlock,支持retain、release.

代理、通知、block和单例使用场景

代理:这是很常用的方式,特点是一对一的形式,而且逻辑结构非常清晰。需要定义协议方法并且实现协议方法,会使代码结构变复杂.
通知:通知中心实际上是在程序内部提供了消息广播的一种机制。通知中心是基于观察者模式的,它允许注册、删除观察者。它是多对多关系.
block:是一段特殊的代码块。使用起来有点像函数.特点也是一对一的,代码结构更加紧凑,不需要额外定义方法.
这几个在开发中经常用于数据传递,如果只是单个参数往往使用block,如果参数内容较多则使用代理传值.而如果是跨界面传值往往使用通知传值.
当然传值的方式还有单例,数据存储来做传值.

frame和bounds有什么不同?

frame指的是:该view在父view坐标系统中的位置和大小。(参照点是父亲的坐标系统)
bounds指的是:该view在本身坐标系统中 的位置和大小。(参照点是本身坐标系统)
理解这两个概念在实际开发中还是比较重要的.

UIView、CALayer和UIWindow是什么关系?

UIView是iOS系统中界面元素的基础, 所有的界面元素都继承自它, UIView本身完全是由CoreAnimation来实现. 真正的绘图部分, 是由一个CALayer类来管理.
最大的区别是UIView继承自UIResponder, 能接收并响应事件, 负责显示内容的管理, 
而CALayer继承自NSObject, 不能响应事件, 负责显示内容的绘制.UIView是基于CALayer的高层封装。而CALayer不支持自动布局.
另外UIWindow是一种特殊的UIView,通常在一个程序中只会有一个UIWindow,但可以手动创建多个UIWindow,同时加到程序里面。UIWindow在程序中主要起到三个作用:
1、作为容器,包含app所要显示的所有视图
2、传递触摸消息到程序中view和其他对象
3、与UIViewController协同工作,方便完成设备方向旋转的支持

isMemberOfClass 、 isKindOfClass和 isSubclassOfClass 联系与区别

联系:都能检测一个对象是否是某个类的成员
区别:
isKindOfClass:确定一个对象是否是一个类的成员,或者是派生自该类的成员.
isSubclassOfClass和isKindOfClass的作用基本上是一致的,只不过一个是类方法,一个是对象方法。
isMemberOfClass:确定一个对象是否是当前类的成员.他的筛选条件更为苛刻,只有当类型完全匹配的时候才会返回YES。

将一个函数在主线程执行的几种方法

//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];
上一篇下一篇

猜你喜欢

热点阅读