2019 iOS 面试 - 基础题部分

答案参考

1. 为什么说Objective-C是一门动态的语言?

①. 解释动态语言、静态语言

动态类型语言:动态类型语言是指在运行期间才去做数据类型检查的语言,也就是说,在用动态类型的语言编程时,永远也不用给任何变量指定数据类型,该语言会在你第一次赋值给变量时,在内部将数据类型记录下来。Python和Ruby就是一种典型的动态类型语言,其他的各种脚本语言如VBScript也多少属于动态类型语言。
静态类型语言:静态类型语言与动态类型语言刚好相反,它的数据类型是在编译其间检查的,也就是说在写程序时要声明所有变量的数据类型,C/C++是静态类型语言的典型代表,其他的静态类型语言还有C#、JAVA等。Objective-C虽然在写程序时声明了变量的数据类型,但是在编译期间并没有检查。因为Objective-C类和对象都是在运行时候创建的,所以编译期间没办法检查。
总结::两者的区别在于是否在编译期间做数据类型检查。

②. OC是动态语言的原因

Objective-C 是C 的超集,在C 语言的基础上添加了面向对象特性,并且利用Runtime 这个运行时机制,为Objective-C 增添了动态的特性。  
Objective-C 可以通过Runtime 这个运行时机制,在运行时动态的添加变量、方法、类等,所以说Objective-C 是一门动态的语言。
Objective-C 使用的是 “消息结构” 并非 “函数调用”:使用消息结构的的语言,其运行时所应执行的代码由运行期决定;而使用函数调用的语言,则由编译器决定。
总结:OC利用Runtime可以在运行的时候创建类,修改类,修改对象调用的方法。

2. 讲一下MVC和MVVM,MVP?

参考链接:iOS 架构模式--解密 MVC,MVP,MVVM以及VIPER架构
MVC 是苹果默认采用的设计模式,但是并不严格,尤其 V 和 C ,耦合很严重。
同时 MVC 也是最简单最易于理解的模式,代码量也是最少的。
其他模式的话代码量都会多过于 MVC ,不过就是拆分、拆分、拆分。

3. 为什么代理要用weak?代理的delegate和dataSource有什么区别?block和代理的区别?

防止循环引用
一个重交互,一个重数据
参考链接:协议代理,block的区别

* delegate:
1. “一对一”,对同一个协议,一个对象只能设置一个代理delegate,任何人,任何对象,只要接受,只要允许,只要遵守了相关的协议,TA就可以使用代理
2. 代理更注重过程信息的传输:比如发起一个网络请求,可能想要知道此时请求是否已经开始、是否收到了数据、数据是否已经接受完成、数据接收失败

* block:
1. 写法更简练,不需要写protocol、函数等等
2. block注重结果的传输:比如对于一个事件,只想知道成功或者失败,并不需要知道进行了多少或者额外的一些信息
3. block需要注意防止循环引用:

*代理的好处:
delegate运行成本低,block成本很高的。
因为 block 出栈需要将使用的数据从栈内存拷贝到堆内存,当然对象的话就是加计数,使用完或者 block 置 nil 后才消除;delegate 只是保存了一个对象指针,直接回调,没有额外消耗。相对C的函数指针,只多做了一个查表动作

*什么时候用代理,什么时候用block:
公共接口,方法较多也选择用delegate进行解耦:iOS有很多例子比如最常用tableViewDelegate,textViewDelegate;
异步和简单的回调用block更好:iOS有很多例子比如常用的网络库AFNetwork,ASIHTTP库,UIAlertView类。

4. 属性的实质是什么?包括哪几个部分?@dynamic关键字和@synthesize关键字是用来做什么的?

参考:iOS关于属性关键字
iOS 属性(property)大揭秘

@property = ivar + getter + setter;

@dynamic告诉编译器,属性的setter与getter方法由用户自己实现。

@synthesize的语义是如果你没有手动实现setter方法和getter方法,那么编译器会自动为你加上这两个方法。

5.属性默认的关键字都有哪些?

基本数据:atomic,readwrite,assign,
普通的 OC 对象: atomic,readwrite,strong

参考:copy,strong,weak,assign的区别。
可变变量中,copy是重新开辟一个内存,strong,weak,assgin后三者不开辟内存,只是指针指向原来保存值的内存的位置,storng指向后会对该内存引用计数+1,而weak,assgin不会。weak,assgin会在引用保存值的内存引用计数为0的时候值为空,并且weak会将内存值设为nil,assign不会,assign在内存没有被重写前依旧可以输出,但一旦被重写将出现奔溃
不可变变量中,因为值本身不可被改变,copy没必要开辟出一块内存存放和原来内存一模一样的值,所以内存管理系统默认都是浅拷贝。其他地方和可变变量一样,如weak修饰的变量同样会在内存引用计数为0时变为nil。
容器本身遵守上面准则,但容器内部的每个值都是浅拷贝。
综上所述,当创建property构造器创建变量value1的时候,使用copy,strong,weak,assign根据具体使用情况来决定。value1 = value2,如果你希望value1和value2的修改不会互相影响的就用用copy,反之用strong,weak,assign。如果你还希望原来值C为nil的时候,你的变量不为nil就用strong,反之用weak和assign。

6.NSString为什么要用copy关键字,如果用strong会有什么问题?

NSString *A = NSString *B;
如果想要 A 跟随 B 的变化而变化,用 strong ;
如果想要 A 不会因为 B 的变化而变化,用 copy ;

7. 如何令自己所写的对象具有拷贝功能?

参考:iOS之对象复制

自定义类本身不具备 copy 功能,可以通过实现 NSCopying 协议;然后实现 copyWithZone: 方法。
或者可以通过 NSObject+RMCopyable 分类实现所有自定义对象都有 copy 功能。

8. 可变集合类 和 不可变集合类的 copy 和 mutablecopy有什么区别?如果是集合是内容复制的话,集合里面的元素也是内容复制么?

系统对象的 copy 与 mutableCopy 区别:

copy方法用于复制对象的副本。通常来说,copy方法总是返回对象的不可修改的副本,即使对象本身是可修改的。例如,程序调用NSMutableString的copy方法,将会返回不可修改的字符串对象,
mutableCopy方法用于复制对象的可变副本。通常来说,mutableCopy方法总是返回对象可修改的副本,即使被复制的对象本身是不可修改的,调用mutableCopy方法复制出来的副本也是可修改的。例如,程序调用NSString的mutableCopy方法,将会返回一个NSMutableString对象。
  
注:可变对象的属性不要用 copy 关键字,否则如果对该对象进行可变操作时,必然会引起崩溃。

NSMutableArray,NSMutableString-->copy-->深拷贝(新开辟一个地址保存),但是对于NSMutableArray的元素是浅拷贝。
NSString,NSArray-->copy-->浅拷贝(因为其本身不可变,所以没必要新开辟一个地址)

集合的深复制有两种方法。

1、可以用initWithDictionary:copyItems:将第二个参数设置为YES即可深复制

NSDictionary shallowCopyDict=[[NSDictionary alloc] initWithDictionary:someDictionary copyItems:YES];

如果你用这种方法深复制,集合里的每个对象都会收到copyWithZone:消息。如果集合里的对象遵循NSCopying协议,那么对象就会被深复制到新的集合。如果对象没有遵循NSCopying协议,而尝试用这种方法进行深复制,会在运行时出错。copyWithZone:这种拷贝方式只能够提供一层内存拷贝(one-level-deepcopy),而非真正的深复制。

2、是将集合进行归档(archive),然后解档(unarchive),如:

NSArray*trueDeepCopyArray=[NSKeyedUnarchiver unarchiveObjectWithData:[NSKeyedArchiver archivedDataWithRootObject:oldArray]];

9. 为什么IBOutlet修饰的UIView也适用weak关键字?

因为A(controller)牵着(strong)B(view),B(view)牵着(strong)C(subView),A没必要再牵着C了,而iboutlet连出来的视图属性一般都是view的子视图,用weak就可以了,这时A释放了B,C自然也被释放了,但如果A释放B,但又要使C不释放的话,就需要用strong了

10. nonatomic和atomic的区别?atomic是绝对的线程安全么?为什么?如果不是,那应该如何实现?

nonatomic和atomic用来决定编译器生成的getter和setter操作是否为原子操作。

atomic不是绝对的线程安全。atomic的本意是指属性的存取方法是线程安全的,并不保证整个对象是线程安全的。如:
声明一个NSMutableArray的原子属性stuff,此时self.stuff 和 self.stuff = otherstuff都是线程安全的。但是使用[self.stuff objectAtIndex:index]就不是线程安全的。需要用互斥锁来保证线程安全性。

iOS中保证线程安全的几种方式与性能对比

OSSpinLock和dispatch_semaphore的效率远远高于其他,但是 OSSpinLock 已经被苹果弃用了。

@synchronized和NSConditionLock效率较差。

鉴于OSSpinLock的不安全,所以我们在开发中如果考虑性能的话,建议使用dispatch_semaphore。

如果不考虑性能,只是图个方便的话,那就使用@synchronized。

11. UICollectionView自定义layout如何实现?

其实可以分三个步骤:

  1. 覆写prepareLayout方法,并在里面事先就计算好必要的布局信息并存储起来。
  2. 基于prepareLayout方法中的布局信息,使用collectionViewContentSize方法返回UICollectionView的内容尺寸。
  3. 使用layoutAttributesForElementsInRect:方法返回指定区域cell、Supplementary View和Decoration View的布局属性。

12. 用StoryBoard开发界面有什么弊端?如何避免?

  1. 多人开发容易冲突,不好解决。避免:每人新开一个SB,尽量业务不要交叉,冲突通过阅读XML解决;
  2. 效率比较低,每次打开SB耗时比较长;
  3. bug 不好定位;

13. 进程和线程的区别?同步异步的区别?并行和并发的区别?

  1. iOS-线程&&进程的深入理解:
  2. 简单点讲,同步异步是相对于线程来说的。串行和并行是相对于队列,或者说任务来说的,是任务的执行先后顺序,不关系到线程。
  3. 并发:并发(Concurrent),在操作系统中,是指一个时间段中有几个程序都处于已启动运行到运行完毕之间,且这几个程序都是在同一个处理机上运行。并行:并行(Parallel),当系统有一个以上CPU时,当一个CPU执行一个进程时,另一个CPU可以执行另一个进程,两个进程互不抢占CPU资源,可以同时进行,这种方式我们称之为并行(Parallel).总的来说,并发,指的是多个事情,在同一时间段内同时发生了;并行,指的是多个事情,在同一时间点上同时发生了。这两者都是处理多个事情,区别就是在于是否“同时”。(注:并发指单核或者说单CPU,并行指多核、多CPU运行)

14. 线程间通信?

  1. 主线程切换到子线程
  2. 子线程切换到主线程
线程间通信常用方法
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUntilDone:(BOOL)wait;
- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(id)arg waitUntilDone:(BOOL)wait;

- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUntilDone:(BOOL)wait (或者-(void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(id)arg waitUntilDone:(BOOL) wait;方法传递主线程[NSThread mainThread])
[NSOperationQueue mainQueue] addOperationWithBlock:
dispatch_sync(dispatch_get_main_queue(), ^{})

15. GCD的一些常用的函数?(group,barrier,信号量,线程同步)

  1. iOS开发系列--并行开发其实很容易
    GCD执行任务的方法并非只有简单的同步调用方法和异步调用方法,还有其他一些常用方法:

dispatch_apply():重复执行某个任务,但是注意这个方法没有办法异步执行(为了不阻塞线程可以使用dispatch_async()包装一下再执行)。
dispatch_once():单次执行一个任务,此方法中的任务只会执行一次,重复调用也没办法重复执行(单例模式中常用此方法)。
dispatch_time():延迟一定的时间后执行。
dispatch_barrier_async():使用此方法创建的任务首先会查看队列中有没有别的任务要执行,如果有,则会等待已有任务执行完毕再执行;同时在此方法后添加的任务必须等待此方法中任务执行后才能执行。(利用这个方法可以控制执行顺序,例如前面先加载最后一张图片的需求就可以先使用这个方法将最后一张图片加载的操作添加到队列,然后调用dispatch_async()添加其他图片加载任务)
dispatch_group_async():实现对任务分组管理,如果一组任务全部完成可以通过dispatch_group_notify()方法获得完成通知(需要定义dispatch_group_t作为分组标识)。

另外,在上面的代码中”抢占资源“_imageNames定义成了成员变量,这么做是不明智的,应该定义为“原子属性”。对于被抢占资源来说将其定义为原子属性是一个很好的习惯,因为有时候很难保证同一个资源不在别处读取和修改。nonatomic属性读取的是内存数据(寄存器计算好的结果),而atomic就保证直接读取寄存器的数据,这样一来就不会出现一个线程正在修改数据,而另一个线程读取了修改之前(存储在内存中)的数据,永远保证同时只有一个线程在访问一个属性。

  1. 浅谈GCD中的信号量
    简单来讲 信号量为0则阻塞线程,大于0则不会阻塞。则我们通过改变信号量的值,来控制是否阻塞线程,从而达到线程同步。

16. 如何使用队列来避免资源抢夺?

dispatch_barrier_async()

17. 数据持久化的几个方案(fmdb用没用过)

plist文件(属性列表)
preference(偏好设置)
NSKeyedArchiver(归档)
SQLite 3(FMDB)
CoreData
参考:

18. 说一下AppDelegate的几个方法?从后台到前台调用了哪些方法?第一次启动调用了哪些方法?从前台到后台调用了哪些方法?

1.当程序第一次运行并且将要显示窗口的时候执行,在该方法中我们完成的操作

  • (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions

2.程序进入后台的时候首先执行程序将要取消活跃该方法

  • (void)applicationWillResignActive:(UIApplication *)application

3.该方法当应用程序进入后台的时候调用

  • (void)applicationDidEnterBackground:(UIApplication *)application

4.当程序进入将要前台的时候调用

  • (void)applicationWillEnterForeground:(UIApplication *)application

5.应用程序已经变得活跃(应用程序的运行状态)

  • (void)applicationDidBecomeActive:(UIApplication *)application

6.当程序将要退出的时候调用,如果应用程序支持后台运行,该方法被applicationDidEnterBackground:替换

  • (void)applicationWillTerminate:(UIApplication *)application

19. NSCache优于NSDictionary的几点?

NSCache 是一个容器类,类似于NSDIctionary,通过key-value 形式存储和查询值,用于临时存储对象。
注意一点它和NSDictionary区别就是,NSCache 中的key不必实现copy,NSDictionary中的key必须实现copy。
NSCache中存储的对象也不必实现NSCoding协议,因为毕竟是临时存储,类似于内存缓存,程序退出后就被释放了。

1.NSCache可以提供自动删减缓存功能,而且保证线程安全,与字典不同,不会拷贝键。
2.NSCache可以设置缓存上限,限制对象个数和总缓存开销。定义了删除缓存对象的时机。这个机制只对NSCache起到指导作用,不会一定执行。
3.NSPurgeableData搭配NSCache使用,可以自动清除数据。
4.只有那种“重新计算很费劲”的数据才值得放入缓存。

20. 知不知道Designated Initializer?使用它的时候有什么需要注意的问题?

正确编写Designated Initializer的几个原则

21. 实现description方法能取到什么效果?

description是nsobject的一个实例的方法,返回的是一个nsstring。当我们使用nslog打印的时候,打印出来的一般都是对象的内存地址,如果我们实现description方法时,我们就可以使用nslog打印对象的时候,我们可以把它里面的属性值和内存地址一起打印出来.打印什么,就是看你写什么了

1.NSLog(@"%@", objectA);这会自动调用objectA的description方法来输出ObjectA的描述信息.
2.description方法默认返回对象的描述信息(默认实现是返回类名和对象的内存地址)
3.description方法是基类NSObject 所带的方法,因为其默认实现是返回类名和对象的内存地址, 这样的话,使用NSLog输出OC对象,意义就不是很大,因为我们并不关心对象的内存地址,比较关心的是对象内部的一些成变量的值。因此,会经常重写description方法,覆盖description方法的默认实现。

22. objc使用什么机制管理对象内存?

使用内存管理计数器,来管理内存的。当内存管理计数器为0的时候,对象就会被释放。

通过 retainCount 的机制来决定对象是否需要释放。 每次 runloop 的时候,都会检查对象的 retainCount,如果retainCount 为 0,说明该对象没有地方需要继续使用了,可以释放掉了。

相关阅读:

1、iOS 面试题 --- 中级篇 Block
2、iOS 面试题 --- 中级篇 Runtime
3、iOS 面试题 --- 中级篇 Runloop
4、iOS 面试题 --- 高级篇

你可能感兴趣的:(2019 iOS 面试 - 基础题部分)