写点什么

iOS 基础原理题目汇总

用户头像
关注
发布于: 2021 年 05 月 31 日
iOS基础原理题目汇总

基础部分

1.为什么说 OC 是一门动态的语言?

  • 动态和静态是相对的,OC 通过runtime运行时机制可以做到纯静态语言做不到的事情:例如动态地增加、删除、替换ivar或者方法等

  • Objective-C 使用的是“消息结构”并非“函数调用”:使用消息结构的的语言,其运行时所应执行的代码由运行期决定;而使用函数调用的语言,则由编译器决定

2.讲一下MVC和MVVM,MVP

  • MVC

  • M:业务数据, V:视图,负责展示 C:控制器,负责协调 M、V

  • C 作为 M 和 V 之间的连接, 负责响应视图事件,界面的跳转,view 的声明周期,获取业务数据, 然后将处理后的数据输出到界面上做相应展示, 在数据有更新时, C 需要及时提交相应更新到界面展示。View 和 Model 之间没有直接的联系,AppleMVC规范,理想的模型图如下:

  • 在实际开发中,Model 往往只有轻量级的数据,甚至.m 中没有任何实现,View 和 Controller 成对出现,导致 Controller 中,越来越多的属性、协议、网络数据处理等,View 和 Controller 紧紧耦合在一起:

  • MVP

  • M:业务数据, V:视图, P:协调器,业务处理层

  • P:业务逻辑的处理者,作为 M、V 的桥梁。当获取到数据后进行相应处理, 处理完成后会通知绑定的 View 数据有更新,View 收到更新通知后从 P 获取格式化好的数据进行页面渲染。相比较 MVC,MVP 把业务逻辑和 View 的展示分离开:

  • MVVM

  • M:业务数据, V:视图, VM:视图模型,展示逻辑

  • 相比较 MVC,View/ViewController 不直接引用 Model,而是通过 ViewModel,ViewModel 负责用户交互逻辑,视图显示逻辑,发起网络请求等

  • 听说 MVVM 和 RAC 更配~

  • 关于设计模式,这篇杂谈: MVC/MVP/MVVMiOS 架构模式--解密 MVC,MVP,MVVM以及VIPER架构有详细介绍

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

  • 避免循环引用,weak表示该对象并不持有该delegate对象,delegate对象的销毁由外部控制;如果用strong则该对象强引用delegate,外界不能销毁delegate对象,会导致循环引用(Retain Cycles)

  • delegate是委托的意思,在OC中表示一个类委托另一个类实现某个方法。当一个对象接受到某个事件或者通知的时候,会向它的delegate对象查询它是否能够响应这个事件或者通知,如果可以这个对象就会给它的delegate对象发送一个消息(执行一个方法调用)。 datasource字面是数据源,一般和delegate伴生,这时数据源处理的数据就是delegate中发送委托的类中的数据,并通过datasource发送给接受委托的类。 Instead of being delegated control of the user interface, a data source is delegated control of data.官网上这句话解释的比较好,我们可以发现,delegate控制的是 UI,是上层的东西;而datasource控制的是数据。他们本质都是回调,只是回调的对象不同。官网原文

  • delegateblock都可以实现回调传值。block写法简练,可以直接访问上下文,代码阅读性好,适合与状态无关的操作,更加面向结果,使用过程中需要注意避免造成循环引用。delegate更像一个生产流水线,每个回调方法是生产线上的一个处理步骤,一个回调的变动可能会引起另一个回调的变动,其更加面向过程,当有多个相关方法时建议使用delegate

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

  • 属性@"property" = ivar + getter + setter

  • 原子性: nonatomicatomic

  • 读写特性: readwritereadonly

  • 内存管理特性: assign:修饰简单数据类型。 weak:弱引用,多数用来修饰delegateoutletcopy:拷贝,多用于修饰NSStringblock等,其作为属性修饰符时是将_propertyrelease (_property release),然后拷贝参数内容(_property copy),创建一块新的内存地址,最后_property = property。strong:强引用,其作为属性修饰符时是将_propertyrelease (_property release),然后将参数retain(_property retain),最后_property = propertyretain:MRC 下特有,等同于strongunsafe_unretained:和weak 一样,唯一的区别便是,对象即使被销毁,指针也不会自动置空, 此时指针指向的是一个无用的野地址。如果使用此指针,程序会抛出 BAD_ACCESS 的异常。

  • @synthesize 让编译器自动生成getter/setter方法。当有自定义的存取方法时,会覆盖该方法

  • @dynamic告诉编译器,不自动生成getter/setter方法。由自己实现存取方法或存取方法在运行时动态创建绑定

5.属性的默认关键字是什么?

ARC 下:


  • 基本数据类型默认关键字是 atomic,readwrite,assign

  • 其他类型默认关键字是 atomic,readwrite,strong


MRC 下:


  • 基本数据类型默认关键字是 atomic,readwrite,assign

  • 其他类型默认关键字是 atomic,readwrite,retain

6.NSString 为什么要用 copy 关键字,如果用 strong 会有什么问题?(注意:这里没有说用 strong 就一定不行。使用 copy 和 strong 是看情况而定的)

@interface Person : NSObject
@property(nonatomic ,copy)NSString *cpName;@property(nonatomic ,strong)NSString *stName;
@end
@implementation Person@end
main(){ NSMutableString *string = [NSMutableString stringWithFormat:@"name"];
Person *person = [[Person alloc]init]; person.cpName = string; person.stName = string;
[string appendString:@"name"]; NSLog(@"cpName:%@ stName:%@",person.cpName,person.stName);}
复制代码


打印结果如下:


cpName:name stName:namename
复制代码


至于为什么要用 copy, 大概是为了防止 mutableString 被无意中修改。

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

  • 若想让自己所写的对象具有拷贝功能,则需实现 NSCopying 协议。如果自定义的对象分为可变版本与不可变版本,那么就要同时实现 NSCopyingNSMutableCopying 协议。

  • 要注意浅拷贝/深拷贝的不同。

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

//不可变字符串NSString *string = @"string";NSString *cpString1 = [string copy];            //指针拷贝NSString *mcpString1 = [string mutableCopy];    //内容拷贝,生成可变对象NSMutableString *mstr = [string mutableCopy];   //同上NSLog(@"%p %p %p %p",string,cpString1,mcpString1,mstr);打印结果如下:0x100001070 0x100001070 0x10051bbc0 0x100600e10
//可变字符串NSMutableString *mstr1 = [[NSMutableString alloc]initWithString:@"abcde"];NSMutableString *mstr2 = [mstr1 copy]; //内容拷贝,生成不可变字符串NSString *cpstring2 = [mstr1 copy]; //同上NSMutableString *mstr3 = [mstr1 mutableCopy]; //内容拷贝NSLog(@"%p %p %p %p",mstr1,mstr2, cpstring2,mstr3);打印结果如下:0x102063110 0x656463626155 0x656463626155 0x102063160
复制代码


NS* NSMutable*等集合类的copy、mutableCopy同上述一样,需要注意的是,集合里面的元素并没有内容拷贝!若集合层级很多,且需要完全内容拷贝,可以利用NSKeyedArchiver实现。

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

  • 防止(Retain Cycles) 更新:感谢各位指出错误!

  • 在 storyboard 或者 xib 中创建的 UIView,本身会被它的superView强引用,以UILable为例子:

  • UIViewController -> UIView -> subView -> UILable

  • 此时控件拖线会默认为weak属性,因为UIlable已经被UIView拥有,当 UIViewController 释放的时候,UIView 释放,UILable 才可以释放,所以正常情况下 UILable 和 UIView 的生命周期是一样的。设置成strong也没什么大问题, 但是当 UILable 从其父视图 UIView 上remove掉,UIViewController 对其还有一个strong强引用,UILable 无法释放,这时就比较尴尬了...

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

  • nonatomic:表示非原子性,不安全,但是效率高。

  • atomic:表示原子性,安全,但是效率较低。

  • atomic:通过锁定机制来确保其原子性,但只是读/写安全,不能绝对保证线程的安全,当多线程同时访问的时候,会造成线程不安全。可以使用线程锁来保证线程的安全。

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

  • UICollectionViewLayout是为向UICollectionView提供布局信息的类,包括cell的布局信息等。

  • UICollectionView的自定义布局可以分为三种方式:

  • 初始化时传入的 UICollectionViewLayout 对象,通过设置 UICollectionViewLayout 对象属性的值可以设置 item 的基本布局,包括大小,间距等。

  • 实现 UICollectionViewLayoutDelegate 协议对应的方法,返回布局需要的值。

  • 继承 UICollectionViewLayout 类实现自定义的 MyCollectionViewLayout,重写相关方法返回自定义的布局。

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

  • 难以维护,难以定位问题。

  • ...

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

  • 进程是具有一定独立功能的程序关于某个数据集合上的一次运行活动,进程是系统进行资源分配和调度的一个独立单位

  • 进程中所包含的一个或多个执行单元称为线程thread

  • 同步:多个任务情况下,一个任务 A 执行结束,才可以执行另一个任务 B。


  • 异步:多个任务情况下,一个任务 A 正在执行,同时可以执行另一个任务 B。任务 B 不用等待任务 A 结束才执行。异步虽然具有开启新线程的能力,但是并不一定开启新线程,跟任务所指定的队列类型有关。同步和异步的主要区别在于会不会阻塞当前线程


  • 并行:指两个或多个事件在同一时刻发生。多核 CUP 同时开启多条线程供多个任务同时执行,互不干扰。


  • 并发:指两个或多个事件在同一时间间隔内发生。可以在某条线程和其他线程之间反复多次进行上下文切换,看上去就好像一个 CPU 能够并且执行多个线程一样。其实是伪异步。如下并发图,在同一线程,任务 A 先执行了 20%,然后 A 停止,任务 B 重新开始接管线程开始执行。


  • 并发的关键是你有处理多个任务的能力,不一定要同时。 并行的关键是你有同时处理多个任务的能力。

14.线程间通信?

  • 在 1 个进程中,线程往往不是孤立存在的,多个线程之间需要经常进行通信。可以利用 pthread,NSthread,GCD,NSOperation 进行相关操作。如:

  • performSelector函数


- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUntilDone:(BOOL)wait;- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(id)arg waitUntilDone:(BOOL)wait;
复制代码


  • GCD


dispatch_async(otherQueue, ^{    // dosth});
复制代码


  • NSOperation


[otherQueue addOperationWithBlock:^{    // dosth}];
复制代码

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

  • group : 当所有任务都执行完成之后,才执行dispatch_group_notify中的任务 dosthNotify。



- (void)gcdGroup{ dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ // dosth1; }); dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ // dosth2; }); dispatch_group_notify(group, dispatch_get_main_queue(), ^{ // dosthNotify; });}
复制代码


  • barrier : 栅栏方法,当栅栏前一组操作执行完之后,才能开始执行后一组方法


- (void)barrier {    dispatch_queue_t queue = dispatch_queue_create("net.bujige.testQueue", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{ // dosth1; }); dispatch_async(queue, ^{ // dosth2; }); dispatch_barrier_async(queue, ^{ // doBarrier; }); dispatch_async(queue, ^{ // dosth4; }); dispatch_async(queue, ^{ // dosth5; });}
复制代码


  • 信号量 : dispatch_semaphore

  • 主要作用:

  • 1.保持线程同步,将异步执行任务转换为同步执行任务


      dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);      dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);      dispatch_async(queue, ^{          // dosth1          // 使信号量+1并返回          dispatch_semaphore_signal(semaphore);      });      // 若信号的信号量为0,则会阻塞当前线程,直到信号量大于0或者经过输入的时间值;若信号量大于0,则会使信号量减1并返回,程序继续住下执行      dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);      // dosth2 ,只有当dosth1执行完,信号量+1之后,才会执行这里
复制代码


*   2.保证线程安全,为线程加锁:信号总量设为 1 时也可以当作锁来用
复制代码

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

  • 将需要访问同一块资源的任务添加到一个非异步并行队列执行

  • 使用 GCD 的 group,barrier,semaphore 函数等

  • 线程加锁,如 : @synchronized 关键字加锁、NSLock 对象锁、NSConditionLock 条件锁、NSRecursiveLock 递归锁、pthread_mutex 互斥锁等。

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

  • plist 文件

  • preference 偏好设置

  • NSKeyedArchiver

  • SQLite 3

  • CoreData

  • fmdb

  • realm

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

// 当应用程序启动时(不包括已在后台的情况下转到前台),调用此回调。launchOptions是启动参数,假如用户通过点击push通知启动的应用,这个参数里会存储一些push通知的信息– (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions NS_AVAILABLE_IOS(3_0);– (void)applicationDidBecomeActive:(UIApplication *)application;
//应用即将从前台状态转入后台- (void)applicationWillResignActive:(UIApplication *)application;– (void)applicationDidEnterBackground:(UIApplication *)application NS_AVAILABLE_IOS(4_0);
//从后台到前台调用了:– (void)applicationWillEnterForeground:(UIApplication *)application NS_AVAILABLE_IOS(4_0);– (void)applicationDidBecomeActive:(UIApplication *)application;
复制代码


App 启动过程如下:


19.NSCache 优于 NSDictionary 的几点?

  • NSCache 线程安全,在多线程操作时,不需要手动加锁

  • NSCache 按照 LRU 规则,会对超出限制的数据进行自动清除,并且在系统发出低内存通知时,会自动删减缓存

  • NSCache 的 Key 只是对对象的 strong 引用,对象不需要实现 NSCopying 协议,NSCache 也不会像 NSDictionary 一样拷贝键。

20.知不知道 Designated Initializer(指定初始化)?使用它的时候有什么需要注意的问题?

  • 在 OC 中,对象的生成分为两步:内存分配,初始化实例变量


NSObject *object = [[NSObject alloc] init];
复制代码


类方法+ alloc,其根据要创建的实例对象对应的类来分配足够的内存空间。除了分配内存空间,其实+ alloc方法还做了其他事情,包括将对象的引用计数记为 1,将对象的 isa 指针指向对应的运行时类对象,以及将对象的成员变量置为对应的 0 值(0、nil、NULL)。但是+ alloc方法返回的对象还是不可用的,在之后完成初始化方法的调用后,对象的创建工作才算完成。初始化方法会设置对象的成员变量为一个正确的合理的值,以及获取一些其他额外的资源。

1.Designated Initializer 指定初始化方法

所有对象都是要初始化的,而且很多情况下,对象在初始化时是需要接收额外的参数,这就可能会提供多个初始化方法。


- (instancetype)initWithTimeIntervalSinceReferenceDate:(NSTimeInterval)ti NS_DESIGNATED_INITIALIZER;- (instancetype)initWithTimeIntervalSinceNow:(NSTimeInterval)secs;- (instancetype)initWithTimeIntervalSince1970:(NSTimeInterval)secs;- (instancetype)initWithTimeInterval:(NSTimeInterval)secsToBeAdded sinceDate:(NSDate *)date;
复制代码


根据规范,通常选择一个接收参数最多的初始化方法作为指定初始化方法,真正的数据分配和其他相关初始化操作在这个方法中完成。而其他的初始化方法则作为便捷初始化方法去调用这个指定初始化方法。这样当实现改变时,只要修改指定初始化方法就可以了。便捷初始化方法接收的参数更少,它会在内部调用指定初始化方法时,直接设置未接收参数的默认值。便捷初始化方法也可以不直接调用指定初始化方法,它可以调用其他便捷初始化方法,但不管调用几层,最终是要调用到指定初始化方法的,因为真正的实现操作是在指定初始化方法中完成的。所有初始化方法统一以- init 开始。如上例代码所示,- initWithTimeIntervalSinceReferenceDate方法是一个指定初始化方法,而其他初始化方法最终是要调用它的。

2.子类实现指定初始化方法

当子类继承父类后实现了新的指定初始化方法,此时如果调用父类中的指定初始化方法则无法调用到子类新实现的初始化逻辑,所以子类同时还要重写父类的指定初始化方法,将其变为一个便捷初始化方法,最终去调用子类自己的指定初始化方法。而为了保证父类初始化逻辑的执行,在子类指定初始化方法中,首先要通过关键字 super 调用父类的指定初始化方法。 详见正确编写Designated Initializer的几个原则

3.initWithCoder

如果父类也实现了协议,首先要调用父类的- initWithCoder:方法,如果父类没有实现,则调用父类的指定初始化方法。

4.NS_DESIGNATED_INITIALIZER

当在接口中指定初始化方法的后面加上该宏,编译器就会检查我们实现的初始化调用链是否符合规则,并提示相应的警告。


- (instancetype)init NS_DESIGNATED_INITIALIZER;复制代码
复制代码

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

  • 当使用 log 打印该对象时,可以详细的知道该对象的信息,方便代码调试。

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

  • 1.MRC(MannulReference Counting)

  • 2.Xcode4.2+引入了 ARC(Automatic Reference Counting)

  • 3.在 ObjC 中内存的管理是依赖对象引用计数器来进行的:在 ObjC 中每个对象内部都有一个与之对应的整数(retainCount),叫“引用计数器”,当一个对象在创建之后它的引用计数器为 1,当调用这个对象的 alloc、retain、new、copy、mutableCopy 方法之后该对象 retainCount+1(ObjC 中调用一个对象的方法就是给这个对象发送一个消息),当调用这个对象的 release 方法之后它的引用计数器减 1,如果一个对象的引用计数器为 0,则系统会自动调用这个对象的 dealloc 方法来销毁这个对象。

  • 4.内存管理遵存这一规则:

  • 1.凡是用 alloc,retain,new(或使用 new 开头),copy(或使用 copy 开头的方法),mutableCopy(或使用 mutableCopy 开头的方法)“创建”对的对象都必须使用 release 或者 autoRelease 方法“释放”。

  • 2.谁(在哪里)创建谁释放(哪个类创建,哪个类释放;谁写 alloc,谁写 release)

  • 5.autorelease : 自动释放。 当某对象调用 autorelease 方法后,在其所在的 NSAutoreleasePool 废弃时,都将调用其 release 方法

  • 6.autoreleasePool : 自动释放池。


文末推荐:iOS 热门文集 &视频解析


用户头像

关注

你的努力没人会看到,可成功会让人羡慕。 2020.12.08 加入

iOS交流群:642363427 公众号:iOS进阶宝典 抖音:iOS 普拉斯 视频学习:https://space.bilibili.com/107521719 感谢支持与关注

评论

发布
暂无评论
iOS基础原理题目汇总