19、iOS面试题·自整理·Three

1.请简述你对工厂方法的理解?

工厂Mycontrol,设计控件是用到工厂设计模式。类簇类似于工厂设计模式;工厂模式就是定义创建对象的接口,让子类决定实例化哪一个类。这样,类的实例化就推迟到了子类

 

2.UITableView有哪些优化方式?

    UITableView的优化主要从三个方面入手:

提前计算并缓存好高度(布局),因为heightForRowAtIndexPath:是调用最频繁的方法;

异步绘制,遇到复杂界面,遇到性能瓶颈时,可能就是突破口;

滑动时按需加载,这个在大量图片展示,网络加载的时候很管用!(SDWebImage已经实现异步加载,配合这条性能杠杠的)。

 

3.你如何理解blockblock有什么用途?

  我们可以把Block当做Objective-C的匿名函数。Block允许开发者在两个对象之间将任意的语句当做数据进行传递,往往这要比引用定义在别处的函数直观。另外,block的实现具有封闭性(closure),而又能够很容易获取上下文的相关状态信息。

block是代码块,其本质和变量类似。不同的是代码块存储的数据是一个函数体。使用Block,就可以像其他标准函数一样,传入参数,并得到返回值。

作为OC对象的属性,实现对象之间的传值    • Block可以看做是一个变量,因此可以作为OC对象的属性

4.请问怎样能够保证定位更省电?

1.开启开始定位之后,不关闭,让其持续定位

  2.设置距离筛选器:坐标移动到指定距离才会调用代理方法

  3.设置精准度:通过降低计算的过程(GPS),来达到省电的目的

5.请简述NSUserDefaults的使用场景和 使用注意事项?

SUserDefaults适合存储轻量级的数据,他不仅可以存储基本数据类型,还可以存储NSNumberIntegerFloatDouble),NSStringNSDateNSArrayNSDictionaryBOOL类型。

但是NSUserDefaults不能存储自定义的类对象,若把一个对象存储到NSUserDefaults会报错。聪明的人会把对象放进数组,再把数组存入NSUserDefaults,不过这样做事错误的,因为数组中包含了自定义对象。

若要在NSUserDefaults中存入自定义对象,则对象需要遵循NSCoding协议,并实现encodeWithCoder方法和initWithCoder方法。具体参考http://my.oschina.NET/u/1245365/blog/294449

值得一提的是,我发现类型为NSNull的空数据也是无法存入NSUserDefaults的。若数据中有NSNull类型空数据,把它置nil即可存入NSUserDefaults

总之,NSUserDefaults是一种操作简单的数据库

6.iOS中数据库使用什么技术实现的 ?

使用SqliteCoreData实现的

7.iOS中如何实现数据模型的存储?

 归档也是iOS提供给开发者的一种数据存储的方式,事实上,几乎所有的数据类型都可以通过归档来进行存取。其存储与读取的过程,主要封装在两个类中:NSKeyedArchiverNSKeyedUnarchiver

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:]进行判断。


forward.jpeg


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.能否向编译后得到的类中增加实例变量?能否向运行时创建的类中添加实例变量?为什么?

  

  1. 不能向编译后得到的类增加实例变量
  2. 能向运行时创建的类中添加实例变量

解释:

  1. 编译后的类已经注册在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,正如其名,loop表示某种循环,和run放在一起就表示一直在运行着的循环。
实际上,run loop和线程是紧密相连的,可以这样说run loop是为了线程而生,没有线程,它就没有存在的必要。
Run loops是线程的基础架构部分,Cocoa和CoreFundation都提供了run loop对象方便配置和管理线程的run loop。
每个线程,包括程序的主线程(main thread)都有与之相应的run loop对象。
18.runloop的mode是用来做什么的?有几种mode?

用来控制一些特殊操作只能在指定模式下运行,一般可以通过指定操作的运行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和线程的关系:主线程的run loop默认是启动的, 子线程的runloop默认是不开启的,需要我们自己手动开启循环; 。
19.为什么把NSTimer对象以NSDefaultRunLoopMode(kCFRunLoopDefaultMode)添加到主运行循环以后,滑动scrollview的时候NSTimer却不动了?

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.介绍一下分类,能用分类做什么?内部是如何实现的?它为什么会覆盖掉原来的方法?

因为分类方法加入类中这一操作是在运行期系统加载分类时完成的,运行期系统会把分类中所实现的每一个方法都加入类的方法列表中,具体步骤如下:

  1. category的实例方法、协议以及属性添加到类上

  2. 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之后才可以被使用,同样没有机会再添加成员变量。

24.objc中向一个nil对象发送消息将会发生什么?(返回值是对象,是标量,结构体)

在 Objective-C 中向 nil 发送消息是完全有效的——只是在运行时不会有任何作用:

  1. 如果一个方法返回值是一个对象,那么发送给nil的消息将返回0(nil)。例如:
    Person * motherInlaw = [[aPerson spouse] mother];
    如果 spouse 对象为 nil,那么发送给 nil 的消息 mother 也将返回 nil。
  2. 如果方法返回值为指针类型,其指针大小为小于或者等于sizeof(void*),float,double,long double 或者 long long 的整型标量,发送给 nil 的消息将返回0。
  3. 如果方法返回值为结构体,发送给 nil 的消息将返回0。结构体中各个字段的值将都是0。
  4. 如果方法的返回值不是上述提到的几种情况,那么发送给 nil 的消息的返回值将是未定义的。
 
原文地址:https://www.cnblogs.com/wn-blog/p/7183347.html