几道iOS面试题(二)
- 为什么说Objective-C是一门动态的语言?
- 为什么代理要用weak?代理的delegate和dataSource有什么区别?block和代理的区别?
- 属性的实质是什么?包括哪几个部分?属性默认的关键字都有哪些?@dynamic关键字和@synthesize关键字是用来做什么的?
- NSString为什么要用copy关键字,如果用strong会有什么问题?
- 如何令自己所写的对象具有拷贝功能?
- 如何理解 copy 和 mutablecopy?
- 为什么IBOutlet修饰的UIView也适用weak关键字?
- nonatomic和atomic的区别?atomic是绝对的线程安全么?为什么?如果不是,那应该如何实现?
- 进程和线程的区别?同步异步的区别?并行和并发的区别?
- 线程间如何进行通信的?
- GCD有哪些常用的函数?
- 如何使用队列来避免资源抢夺?
- 数据持久化的几个方案
- NSCache优于NSDictionary的几点?
- 讲一讲 指定初始化函数 Designated Initializer
- 讲一讲内存管理机制
为什么说Objective-C是一门动态的语言?
Objective-C的动态性
(动态类型、动态绑定、动态载入)让程序可以在运行时判断其该有的行为,而不是像C语言等静态语言是在编译构建时就确定下来的。
为什么代理要用weak?代理的delegate和dataSource有什么区别?block和代理的区别?
为了
避免循环引用
。
delegate和dataSource的区别
-
delegate和datasource区别是信息流的
传递方向
。 -
delegate 传递的是
事件
,datasource 传递的是数据
。
block和代理的区别
-
delegate是个对象,然后对象 --> 代理协议 --> 函数来完成整个流程。
-
block是传递一个函数指针,利用函数指针执行来进行回调完成整个流程。
属性的实质是什么?包括哪几个部分?属性默认的关键字都有哪些?@dynamic关键字和@synthesize关键字是用来做什么的?
实质:封装对象数据,提供
存取入口
。
属性组成 : @property = ivar(实例变量)
+setter
+ getter
关键字:strong,weak,assign,copy,nonatomic,atomic,readonly等
-
@dynamic :修饰的属性,其getter和setter方法编译器是不会自动帮你生成。必须自己是实现的。
-
@synthesize:修饰的属性,其getter和setter方法编译器是会自动帮你生成,不必自己实现。且指定与属性相对应的成员变量。
NSString为什么要用copy关键字,如果用strong会有什么问题?
copy通常用来修饰
不可变
属性。即明确告诉使用者,设计者不希望
且不能
直接修改属性对象所指向的内存地址的值。
但如果我们使用是strong,那么这个属性就有可能指向一个可变对象
。如果这个可变对象在外部被修改了,那么会影响该属性。
如何令自己所写的对象具有拷贝功能?
copy只需要实现NSCopying协议,并且实现以下方法
-(id)copyWithZone:(NSZone *)zone;
而mutableCopy,则需要实现NSMutableCopying协议,并且实现以下方法
-(id)mutableCopyWithZone:(NSZone*)zone;
如何理解 copy 和 mutablecopy?
拷贝的本质即复制,目的是产生副本,遵循源对象和副本的相互独立,互不干扰的宗旨。
- 不可变拷贝:即 copy 方法,无论源对象是否可变,都产生不可变副本;
- 可变拷贝:即 mutableCopy 方法,无论源对象是否可变,都产生可变副本;
- 深拷贝:内容拷贝,产生新的对象;
- 浅拷贝:指针拷贝,不产生新的对象;
由上可知,copy 和深拷贝是两个概念,两者并不一定相等:
- 源对象不可变时,copy 方法就是浅拷贝;
- 源对象可变时,copy 方法就是深拷贝;
- mutableCopy 方法无论何种情况都是深拷贝
为什么IBOutlet修饰的UIView也适用weak关键字?
当控件被拖到storyboard或者xib上时,会生成对象,我们可以理解为stroyboard已经强持有了它。
所以我们在viewController中使用Outlet属性的时候,只需要弱持有它防止内存泄漏。所以使用weak进行修饰。
nonatomic和atomic的区别?atomic是绝对的线程安全么?为什么?如果不是,那应该如何实现?
区别
- atomic系统自动生成的getter/setter方法会进行
加锁操作
- nonatomic系统自动生成的getter/setter方法不会进行加锁操作
atomic不是绝对的线程安全,它只是保证了属性的读写安全。
atomic只是对属性进行了加锁操作,这种安全仅仅是属性的读写安全。线程安全还有读写之外的其他操作(比如:如果当一个线程正在get或set时,又有另一个线程同时在进行release操作,可能会直接crash)
人为实现线程安全 可以考虑在线程上加锁。 比如 互斥锁@synchronized
进程和线程的区别?同步异步的区别?并行和并发的区别?
- 进程和线程
-
进程 是操作系统
内存
、资源分配
的基本单位 -
线程 是
任务调度
和执行
的基本单位一个进程 --->(线程、线程、线程 ... ... ) 多线程
一个进程 --->(线程)单线程
-
同步和异步强调的是
消息通信机制
。所谓同步,就是由“调用者”主动等待这个“调用”的结果,没有结果就一直等。
异步则是相反,当一个异步过程调用发出后,调用者不会一直等待结果。而是有结果了,通过通知获得。
-
并行和并发
- 并发是两个队列 交替 使用一台咖啡机。
- 并行是两个队列 同时 使用两台咖啡机。
如果串行,一个队列使用一台咖啡机,那么哪怕前面那个人便秘了去厕所呆半天,后面的人也只能死等着他回来才能去接咖啡,这效率无疑是最低的。
并发和并行都可以是很多个线程,就看这些线程能不能同时被(多个)cpu执行,如果可以就说明是并行。而并发是多个线程被(一个)cpu 轮流切换着执行。
线程间如何进行通信的?
- 1个线程传递数据给另1个线程
- 在1个线程中执行完特定任务后,转到另1个线程继续执行任务
线程间通信常用方法 :
NSThread
、GCD
、NSOperation
线程间通信示例 – 图片下载功能

GCD有哪些常用的函数?
-
栅栏函数 dispatch_barrier_xxx
// 栅栏函数: 用来控制并发队列的执行顺序 dispatch_barrier_async(queue, ^{ NSLog(@"i'm a barrier"); });
-
延迟函数 dispatch_after
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ NSLog(@"%@",[NSThread currentThread]); });
-
一次性执行 dispatch_once_t
- (void)once{ static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ NSLog(@"once"); }); }
-
快速迭代 dispatch_apply
dispatch_apply(10, dispatch_get_global_queue(0, 0), ^(size_t index){ // 执行10次代码,index顺序不确定 });
-
队列组 dispatch_group_t
// 开始了 dispatch_group_t fGroup = dispatch_group_create(); dispatch_queue_t queue = dispatch_get_global_queue(0, 0); dispatch_group_enter(fGroup); dispatch_group_async(fGroup, queue, ^{ // 发起网络请求 // 成功或者失败都掉用这个方法. dispatch_group_leave(fGroup); }); dispatch_group_enter(fGroup); dispatch_group_async(fGroup, queue, ^{ // 发起网络请求 // 成功或者失败都掉用这个方法. dispatch_group_leave(fGroup); }); dispatch_group_notify(fGroup, queue, ^{ // 所有的任务都执行完成后的回调. NSLog(@"都执行完啦"); }); // 结束了
-
信号量 dispatch_semaphore
- dispatch_semaphore_create(value) 创建一个值为value的信号量
- dispatch_semaphore_wait(信号量,等待时间)如果该信号量的值大于0,则使其信号量的值-1。否则,阻塞线程直到该信号量的值大于0或者达到等待时间。
- dispatch_semaphore_signal(信号量)释放信号量,使得该信号量的值+1。
dispatch_semaphore_t semaphore = dispatch_semaphore_create(2);
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
//任务1
dispatch_async(queue, ^{
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
NSLog(@"run task 1");
sleep(1);
NSLog(@"complete task 1");
dispatch_semaphore_signal(semaphore);
});
//任务2
dispatch_async(queue, ^{
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
NSLog(@"run task 2");
sleep(1);
NSLog(@"complete task 2");
dispatch_semaphore_signal(semaphore);
});
//任务3
dispatch_async(queue, ^{
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
NSLog(@"run task 3");
sleep(1);
NSLog(@"complete task 3");
dispatch_semaphore_signal(semaphore);
});
2019-02-24 18:31:02.447769+0800 Semaphore[33286:12284312] run task 1
2019-02-24 18:31:02.447767+0800 Semaphore[33286:12284310] run task 2
2019-02-24 18:31:03.450756+0800 Semaphore[33286:12284312] complete task 1
2019-02-24 18:31:03.450756+0800 Semaphore[33286:12284310] complete task 2
2019-02-24 18:31:03.450997+0800 Semaphore[33286:12284311] run task 3
2019-02-24 18:31:04.454259+0800 Semaphore[33286:12284311] complete task 3
如何使用队列来避免资源抢夺
- 信号量 dispatch_semaphore
- 队列组 dispatch_group_t
- 栅栏函数 dispatch_barrier_xxx
控制线程
完成顺序
即可。
数据持久化的几个方案
- NSUserDefaults
- plist
- keychain(钥匙串)
- 归档
- 沙盒
- 数据库

NSCache优于NSDictionary的几点
- NSCache 提供自动删减缓存功能,是可以自动释放内存的。
- NSCache 是线程安全的,我们可以在不同的线程中添加,删除和查询缓存中的对象。
- NSCache 不会拷贝key对象。
讲一讲 指定初始化函数 Designated Initializer
指定初始化方法的机制保证了对象会依次执行从父类到子类的所有初始化逻辑,实现的规则为:
-
便捷初始化方法只能调用本类中的其他初始化方法,并最终调用到指定初始化方法。
-
子类的指定初始化方法要调用父类的指定初始化方法,以保证父类的初始化逻辑可以执行。
-
当子类实现了自己的指定初始化方法后,父类的指定初始化方法要重写为便捷初始化方法,以保证所有初始化方法都能调用到子类的初始化逻辑。

内存管理机制
- MRC 手动引用计数
- ARC 自动引用计数
引用计数
通过
引用计数器
的机制来决定对象是否需要释放。 每次 runloop 的时候,都会检查对象的引用计数,如果引用计数=0,系统则进行内存回收。
转载自:https://juejin.cn/post/6985830546467192862