19、iOS面试题·自整理·Three
1.请简述你对工厂方法的理解?
工厂Mycontrol,设计控件是用到工厂设计模式。类簇类似于工厂设计模式;工厂模式就是定义创建对象的接口,让子类决定实例化哪一个类。这样,类的实例化就推迟到了子类
2.UITableView有哪些优化方式?
UITableView的优化主要从三个方面入手:
• 提前计算并缓存好高度(布局),因为heightForRowAtIndexPath:是调用最频繁的方法;
• 异步绘制,遇到复杂界面,遇到性能瓶颈时,可能就是突破口;
• 滑动时按需加载,这个在大量图片展示,网络加载的时候很管用!(SDWebImage已经实现异步加载,配合这条性能杠杠的)。
3.你如何理解block,block有什么用途?
• 我们可以把Block当做Objective-C的匿名函数。Block允许开发者在两个对象之间将任意的语句当做数据进行传递,往往这要比引用定义在别处的函数直观。另外,block的实现具有封闭性(closure),而又能够很容易获取上下文的相关状态信息。
• block是代码块,其本质和变量类似。不同的是代码块存储的数据是一个函数体。使用Block,就可以像其他标准函数一样,传入参数,并得到返回值。
作为OC对象的属性,实现对象之间的传值 • Block可以看做是一个变量,因此可以作为OC对象的属性
4.请问怎样能够保证定位更省电?
1.开启开始定位之后,不关闭,让其持续定位
2.设置距离筛选器:坐标移动到指定距离才会调用代理方法
3.设置精准度:通过降低计算的过程(GPS),来达到省电的目的
5.请简述NSUserDefaults的使用场景和 使用注意事项?
SUserDefaults适合存储轻量级的数据,他不仅可以存储基本数据类型,还可以存储NSNumber(Integer、Float、Double),NSString,NSDate,NSArray,NSDictionary,BOOL类型。
但是NSUserDefaults不能存储自定义的类对象,若把一个对象存储到NSUserDefaults会报错。聪明的人会把对象放进数组,再把数组存入NSUserDefaults,不过这样做事错误的,因为数组中包含了自定义对象。
若要在NSUserDefaults中存入自定义对象,则对象需要遵循NSCoding协议,并实现encodeWithCoder方法和initWithCoder方法。具体参考http://my.oschina.NET/u/1245365/blog/294449
值得一提的是,我发现类型为NSNull的空数据也是无法存入NSUserDefaults的。若数据中有NSNull类型空数据,把它置nil即可存入NSUserDefaults。
总之,NSUserDefaults是一种操作简单的数据库。
6.iOS中数据库使用什么技术实现的 ?
使用Sqlite和CoreData实现的
7.iOS中如何实现数据模型的存储?
归档也是iOS提供给开发者的一种数据存储的方式,事实上,几乎所有的数据类型都可以通过归档来进行存取。其存储与读取的过程,主要封装在两个类中:NSKeyedArchiver和NSKeyedUnarchiver
8.为什么说Objective-C是一门动态的语言?
Objective-C是动态语言,它并非通过调用类的方法来执行功能,而是给对象发送消息,
对象在接收到消息之后会去找匹配的方法来运行。
9.讲一下MVC和MVVM,MVP?
1.MVC作为老牌架构, 优点在于将业务场景按展示数据类型划分出多个模块, 每个模块中的C层负责业务逻辑和业务展示, 而M和V应该是互相隔离的以做重用, 另外每个模块处理得当也可以作为重用单元. 拆分在于解耦, 顺便做了减负, 隔离在于重用, 提升开发效率. 缺点是没有区分业务逻辑和业务展示, 对单元测试不友好.
2.MVP作为MVC的进阶版, 提出区分业务逻辑和业务展示, 将所有的业务逻辑转移到P层, V层接受P层的数据更新通知进行页面展示. 优点在于良好的分层带来了友好的单元测试, 缺点在于分层会让代码逻辑优点绕, 同时也带来了大量的代码工作, 对程序员不够友好.
3.MVVM作为集大成者, 通过数据绑定做数据更新, 减少了大量的代码工作, 同时优化了代码逻辑, 只是学习成本有点高, 对新手不够友好.
4.MVP和MVVM因为分层所以会建立MVC两倍以上的文件类, 需要良好的代码管理方式.
5.在MVP和MVVM中, V和P或者VM之间理论上是多对多的关系, 不同的布局在相同的逻辑下只需要替换V层, 而相同的布局不同的逻辑只需要替换P或者VM层. 但实际开发中P或者VM往往因为耦合了V层的展示逻辑退化成了一对一关系(比如SceneA中需要显示"xxx+Name", VM就将Name格式化为"xxx + Name". 某一天SceneB也用到这个模块, 所有的点击事件和页面展示都一样, 只是Name展示为"yyy + Name", 此时的VM因为耦合SceneA的展示逻辑, 就显得比较尴尬), 针对此类情况, 通常有两种办法, 一种是在VM层加状态进而判断输出状态, 一种是在VM层外再加一层FormatHelper. 前者可能因为状态过多显得代码难看, 后者虽然比较优雅且拓展性高, 但是过多的分层在数据还原时就略显笨拙, 大家应该按需选择.
这里随便瞎扯一句, 有些文章上来就说MVVM是为了解决C层臃肿, MVC难以测试的问题, 其实并不是这样的. 按照架构演进顺序来看, C层臃肿大部分是没有拆分好MVC模块, 好好拆分就行了, 用不着MVVM. 而MVC难以测试也可以用MVP来解决, 只是MVP也并非完美, 在VP之间的数据交互太繁琐, 所以才引出了MVVM. 当MVVM这个完全体出现以后, 我们从结果看起源, 发现它做了好多事情, 其实并不是, 它的前辈们付出的努力也并不少!
10.为什么在默认情况下无法修改被block捕获的变量? __block都做了什么?
如果是用block(用static也可以)修饰的局部变量,在block内部访问的话,而是把这个局部变量的地址传递过去了,所以会跟踪这个局部变量的变化,并且可以修改,
如果block内部引用的变量是全局变量的话,那么在block内部访问,他也是把这个变量的地址传递过去了.。
11.模拟一下循环引用的一个情况?block实现界面反向传值如何实现?
ClassA和ClassB分属两个不同的线程,ClassB通常由ClassA发起请求创建,并由ClassA使用,ClassB则会在必要时通知ClassA一些事件。两者中各保留了对方的一个引用计数指针RefPtr。
如果在析构时释放成员变量的话,就会发生循环引用的问题,导致两个对象释放失败。
block的回调的使用步骤
1.声明 : 在谁那里调用就在谁那里声明
实现代码
typedef void(^MyBlock)(NSString *name);//block的重命名
@property (nonatomic,copy) MyBlock block;//block的声明
2.实现 : 谁要装值就在谁那里实现
实现代码
SecondViewController *secondVC = [[SecondViewController alloc] init];
[self presentViewController:secondVC animated:YES completion:nil];//在这里没用导航控制器,用presentViewController来进入下一个视图
//block实现
secondVC.block = ^void(NSString *name)
{
_label.text = name;
};//block的位置摆放很作用的,因为它是一个函数,不过一定不能放在使用它的对象的外面和前面就好了
3.调用 : 谁要传值就在谁那里调用
self.block(@"呵呵");//block的调用
总结一句话:block用在不同视图控制器之间的值回传,回传还有代理、单例,在回传中最简单的就是用block了
*/
12.objc在向一个对象发送消息时,发生了什么?
SomeClass * someObject;
someObject = nil;
[someObject doSomething];
就像这样,向nil发送了doSomething;OC中nil是被当做0定义的。也就是说runtime要去获取这个nil的信息,会去读取内存中0的位置,这肯定是不允许的,会返回nil,0,0.0等数据,根据返回值类型。
比较让你混淆的是,僵尸对象。僵尸对象并不是nil,僵尸对象是你的object被销毁或者用于其他地方了,但是指向它的指针还在。会发生向一个object发送一个它没有的方法。
13.什么时候会报unrecognized selector错误?iOS有哪些机制来避免走到这一步?
对象未实现该方法。
对象已经被释放。
使用[id respondsToSelector:]进行判断。
Method resolution
objc运行时会调用+resolveInstanceMethod:或者 +resolveClassMethod:,让你有机会提供一个函数实现。如果你添加了函数,那运行时系统就会重新启动一次消息发送的过程,否则 ,运行时就会移到下一步,消息转发(Message Forwarding)。
返回Nil和self,去调用第三步methodSignatureForSelector和forwarInvocation;返回receiver,如果receiver有响应就直接处理,如果没有就去对应的对象内去调用第三步;调用子类的函数,子类没有进行这几个方法的重载,在父类处理时返回子类,会死循环。
Fast forwarding
如果目标对象实现了-forwardingTargetForSelector:,Runtime 这时就会调用这个方法,给你把这个消息转发给其他对象的机会。 只要这个方法返回的不是nil和self,整个消息发送的过程就会被重启,当然发送的对象会变成你返回的那个对象。否则,就会继续Normal Fowarding。 这里叫Fast,只是为了区别下一步的转发机制。因为这一步不会创建任何新的对象,但下一步转发会创建一个NSInvocation对象,所以相对更快点。
Normal forwarding
这一步是Runtime最后一次给你挽救的机会。首先它会发送-methodSignatureForSelector:消息获得函数的参数和返回值类型。如果-methodSignatureForSelector:返回nil,Runtime则会发出-doesNotRecognizeSelector:消息,程序这时也就挂掉了。如果返回了一个函数签名,Runtime就会创建一个NSInvocation对象并发送-forwardInvocation:消息给目标对象。
14.能否向编译后得到的类中增加实例变量?能否向运行时创建的类中添加实例变量?为什么?
- 不能向编译后得到的类增加实例变量
- 能向运行时创建的类中添加实例变量
解释:
- 编译后的类已经注册在runtime中,类结构体中的objc_ivar_list实例变量的链表和instance_size实例变量的内存大小已经确定,runtime会调用class_setvarlayout或class_setWeaklvarLayout来处理strong weak引用.所以不能向存在的类中添加实例变量
2.运行时创建的类是可以添加实例变量,调用class_addIvar函数.但是的在调用objc_allocateClassPair之后,objc_registerClassPair之前,原因同上.
15.runtime如何实现weak变量的自动置nil?
runtime 对注册的类, 会进行布局,对于 weak 对象会放入一个 hash 表中。 用 weak 指向的对象内存地址作为 key,当此对象的引用计数为0的时候会 dealloc,假如 weak 指向的对象内存地址是a,那么就会以a为键, 在这个 weak 表中搜索,找到所有以a为键的 weak 对象,从而设置为 nil。
16.给类添加一个属性后,在类结构体里哪些元素会发生变化?
定义结构体会
17.runloop是来做什么的?runloop和线程有什么关系?主线程默认开启了runloop么?子线程呢?
实际上,run loop和线程是紧密相连的,可以这样说run loop是为了线程而生,没有线程,它就没有存在的必要。
Run loops是线程的基础架构部分,Cocoa和CoreFundation都提供了run loop对象方便配置和管理线程的run loop。
每个线程,包括程序的主线程(main thread)都有与之相应的run loop对象。
用来控制一些特殊操作只能在指定模式下运行,一般可以通过指定操作的运行mode 来控制执行时机,以提高用户体验
系统默认注册了 5 个 Mode
kCFRunLoopDefaultMode:App 的默认 Mode,通常主线程是在这个 Mode
下运行,对应 OC 中的:NSDefaultRunLoopMode
UITrackingRunLoopMode:界面跟踪 Mode,用于 ScrollView 追踪触摸滑
动,保证界面滑动时不受其他 Mode 影响
kCFRunLoopCommonModes:这是一个标记 Mode,不是一种真正的 Mode,事件
可以运行在所有标有 common modes 标记的模式中,对应 OC 中的
NSRunLoopCommonModes , 带 有 common modes 标 记 的 模 式 有 :UITrackingRunLoopMode 和 kCFRunLoopDefaultMode
UIInitializationRunLoopMode:在启动 App 时进入的第一个 Mode,启动完成后
就不再使用
GSEventReceiveRunLoopMode:接受系统事件的内部 Mode,通常用不到
RunLoop只能运行在一种mode下,如果要换mode,当前的loop也需要停下重启成新的。利用这个机制,ScrollView滚动过程中NSDefaultRunLoopMode(kCFRunLoopDefaultMode)的mode会切换到UITrackingRunLoopMode来保证ScrollView的流畅滑动:只能在NSDefaultRunLoopMode模式下处理的事件会影响scrllView的滑动。
如果我们把一个NSTimer对象以NSDefaultRunLoopMode(kCFRunLoopDefaultMode)添加到主运行循环中的时候, ScrollView滚动过程中会因为mode的切换,而导致NSTimer将不再被调度。
同时因为mode还是可定制的,所以:
Timer计时会被scrollView的滑动影响的问题可以通过将timer添加到NSRunLoopCommonModes(kCFRunLoopCommonModes)来解决。
20.苹果是如何实现Autorelease Pool的?每一个线程的 autoreleasepool 其实就是一个指针的堆栈;
每一个指针代表一个需要 release 的对象或者 POOL_SENTINEL(哨兵对象,代表一个 autoreleasepool 的边界);
一个 pool token 就是这个 pool 所对应的 POOL_SENTINEL 的内存地址。当这个 pool 被 pop 的时候,所有内存地址在 pool token 之后的对象都会被 release ;
这个堆栈被划分成了一个以 page 为结点的双向链表。pages 会在必要的时候动态地增加或删除;
Thread-local storage(线程局部存储)指向 hot page ,即最新添加的 autoreleased 对象所在的那个 page 。
21.isa指针?(对象的isa,类对象的isa,元类的isa都要说)
一、类的基本概念:
1、类其实也是一个对象, 这个对象会在这个类第一次被使用的时候创建
2、只要有了类对象, 将来就可以通过类对象来创建实例对象
3、实例对象中有一个isa指针, 指向创建自己的类对象
4、类对象中保存了当前对象所有的对象方法
5、当给一个实例对象发送消息的时候, 会根据实例对象中的isa指针去对应的类对象中查找
6、所有类对象的继承关系就是元类对象的继承关系
二、isa指针
1.每一个对象都包含一个isa指针.这个指针指向当前对象所属的类。
2.[d bark];表示给d所指向的对象发送一条bark消息,调用对象的bark方法,此时对象会顺着内部的isa指针找到存储于类中的方法并执行。
3.isa是对象中的隐藏指针,指向创建这个对象的类。
4.通过isa指针我们可以在运行的时候知道当前对象是属于那个类。
三、元类
1、元类的定义:元类是类对象的类,每个类都有自己独一无二的元类,即
(1)当你给对象发送消息时,消息是在寻找这个对象的类的方法列表。
(2)当你给类发消息时,消息是在寻找这个类的元类的方法列表。
元类是必不可少的,因为它存储了类的类方法。每个类都必须有独一无二的元类,因为每个类都有独一无二的类方法。
2、元类的类:
(1)元类,就像类一样,它也是一个对象,也可以调用它的方法。这就意味着他必须也有一个类。
(2)所有的元类都使用根元类(继承体系中处于顶端的类的元类)作为他们的类。即所有NSObject的子类的元类都会以NSObject的元类作为他们的类。
(3)所有的元类使用根元类作为他们的类,根元类的元类则就是它自己。也就是说基类的元类的isa指针指向他自己。
22.介绍一下分类,能用分类做什么?内部是如何实现的?它为什么会覆盖掉原来的方法?
因为分类方法加入类中这一操作是在运行期系统加载分类时完成的,运行期系统会把分类中所实现的每一个方法都加入类的方法列表中,具体步骤如下:
-
category的实例方法、协议以及属性添加到类上
-
category的类方法和协议添加到类的metaclass上
category的方法被放到了新方法列表的前面,而原来类的方法被放到了新方法列表的后面,所以category的方法会“覆盖”掉原来类的同名方法。
23.运行时能增加成员变量么?能增加属性么?如果能,如何增加?如果不能,为什么?
很多人在面试的时候都会被问到Category,既然允许用Category给类增加方法和属性,那为什么不允许增加成员变量?
在Objective-C提供的runtime函数中,确实有一个class_addIvar()函数用于给类添加成员变量,但是阅读过苹果的官方文档的人应该会看到:
This function may only be called after objc_allocateClassPair and before objc_registerClassPair. Adding an instance variable to an existing class is not supported.
大概的意思说,这个函数只能在“构建一个类的过程中”调用。一旦完成类定义,就不能再添加成员变量了。经过编译的类在程序启动后就被runtime加载,没有机会调用addIvar。程序在运行时动态构建的类需要在调用objc_registerClassPair之后才可以被使用,同样没有机会再添加成员变量。
在 Objective-C 中向 nil 发送消息是完全有效的——只是在运行时不会有任何作用:
- 如果一个方法返回值是一个对象,那么发送给nil的消息将返回0(nil)。例如:
如果 spouse 对象为 nil,那么发送给 nil 的消息 mother 也将返回 nil。Person * motherInlaw = [[aPerson spouse] mother];
- 如果方法返回值为指针类型,其指针大小为小于或者等于sizeof(void*),float,double,long double 或者 long long 的整型标量,发送给 nil 的消息将返回0。
- 如果方法返回值为结构体,发送给 nil 的消息将返回0。结构体中各个字段的值将都是0。
- 如果方法的返回值不是上述提到的几种情况,那么发送给 nil 的消息的返回值将是未定义的。