几个常见的iOS面试题(GCD重点讲解)

7 篇文章 0 订阅

1、GET 和 POST 的区别

答:GET 所有的参数都拼接在URL后面 (安全性比POST要差,
所有GET登陆请求都会生成日志并且保存到手机里面!)。
POST 参数不拼接到URL后面,所有参数都存放在请求体中。

2、MVC、MVP、MVVM
MVC模型中,C为(controller)。主要处理逻辑为:View触发事件,controller响应并处理逻辑,调用Model,Model处理完成后将数据发送给View,View更新。
MVP模型中,P为Presenter,并以Presenter为核心,负责从model获取数据,并填充到View中。该模型使得Model和View不再有联系,且View被称为“被动视图”,暴露出setter接口。
MVVM模型中,VM为ViewModel,同样是以VM为核心,但是不同于MVP,MVVM采用了数据双向绑定的方案,替代了繁琐复杂的DOM操作。该模型中,View与VM保持同步,View绑定到VM的属性上,如果VM数据发生变化,通过数据绑定的方式,View会自动更新视图;VM同样也暴露出Model中的数据。

3、单例、代理、KVO
单例:简单的来说,一个单例类,在整个程序中只有一个实例,并且提供一个类方法供全局调用,在编译时初始化这个类,然后一直保存在内存中,到程序(APP)退出时由系统自动释放这部分内存。
代理:代理是一种设计模式,委托方声明协议,定义需要实现的接口,代理方按照协议实现方法一般用weak来避免循环引用
观察者:使用观察者模式用于实现跨层传递信息的机制。传递方式是一对多
KVO是观察者的另一实现
注意:使用setter方法改变值KVO会生效,使用KVC改变值KVO也会生效,因为KVC会调用setter方法,直接赋值成员变量不会触发KVO,因为不会调用setter方法,需要加上willChangeValueForKey和didChangeValueForKey。

  • 4、数据持久化
  1. NSUserDefaults
  2. plist
  3. Keychain(钥匙串)
  4. 归档
  5. 沙盒
  6. 数据库

5、录音,音频,视频
AVFoundation库

6、NSThread/NSOperation/GCD线程
只在主线程刷新访问UI
如果要防止资源抢夺,得用synchronized进行加锁保护
如果异步操作要保证线程安全等问题, 尽量使用GCD(有些函数默认 就是安全的)

项目中使用NSOperation的优点是NSOperation是对线程的高度抽象,在项目中使用它,会使项目的程序结构更好,子类化NSOperation的设计思路,是具有面向对象的优点(复用、封装),使得实现多线程支持,而接口简单,建议在复杂项目中使用。
项目中使用GCD的优点是GCD本身非常简单、易用,对于不复杂的多线程操作,会节省代码量,而Block参数的使用,会是代码更为易读,建议在简单项目中使用。

对于a,b,c三个线程,如何使用用NSOpertion和NSOpertionQueue实现执行完a,b后再执行c?
添加依赖

7、GCD

进程:是一个应用程序在处理机上的一次执行过程,每个进程之间是独立的,每个进程均运行在其专用的且受保护的内存中;
线程:是指进程内的一个执行单元,也是进程内的可调度实体(线程是进程中的一部分,进程包含多个线程在运行)

GCD是苹果开发中多线程操作的解决方案,它主要用于优化应用程序以支持多核处理器以及其他对称多处理系统
GCD任务和队列
任务:就是执行操作,即可以执行的代码;执行任务有两种方式:同步和异步
同步:
阻塞线程:同步添加任务到指定的队列中,在添加的任务执行结束之前,会一直等待,直到队列里面的任务完成之后再继续执行;不可以开辟新的线程
异步:
不会阻塞线程:异步添加任务到指定的队列中,它不会做任何等待,可以继续执行任务; 可以开辟新的线程

队列:
指执行任务的等待队列,即用来存放任务的队列,队列是一种特殊的线性表,采用FIFO(先进先出)的原则,即新任务总是被插入到队列的末尾,而读取任务的时候总是从队列的头部开始读区。每读取一个任务,则从队列中释放一个任务。

GCD中有两种队列:串行队列 和 并发队列。两者都符合FIFO的原则。两者的主要区别是:执行顺序不同,以及开启线程数不同。
串行队列:
每次只有一个任务被执行。让任务一个接一个的执行。(指开启一个线程,一个任务执行完毕后,再执行下一个任务)
并发队列:
可以让多个任务并发(同时)执行。可以开启多个线程
并发队列的并发功能只有在异步方法下才有效。

队列的创建方法/获取方法
可以使用dispatch_queue_create 方法来创建队列,该方法需要传入两个参数:
DISPATCH_QUEUE_SERIAL表示串行队列
DISPATCH_QUEUE_CONCURRENT表示并发队列

串行队列,GCD默认提供了:主队列
所有放在主队列中的任务,都会放到主线程中的执行。
可使用dispatch_get_main_queue()方法获得主队列

对于并发队列,GCD默认提供了全局并发队列
可以使用disoatch_get_global_queue方法来获取全局并发队列。

区别并发队列串行队列主队列
同步没有开启新线程,串行执行任务没有开启新线程,串行执行任务死锁卡住不执行
异步有开启新线程,并发执行有开启新线程(1条),串行执行任务没有开启新线程,串行执行任务

创建串行队列
dispatch_queue_t queue = dispatch_queue_create("test.queue", DISPATCH_QUEUE_SERIAL)
异步执行+串行队列
dispatch_async(queue, ^{//异步执行})
在异步执行队列中追加同步任务

dispatch_async(queue, ^{
	dispatch_sync(queue, ^{
	//追加任务1
	[NSThread sleepForTimeInterval:2];
	NSLog(@"1---%@",[NSThread currentThread]);
	})
})

执行上面代码会导致 串行队列中追加的任务和串行队列中的原有的任务 两者之间相互等待,阻塞了串行队列,最终造成了串行队列所在的线程(子线程)死锁问题。

dispatch_queue_t queue = dispatch_queue_create("test.queue", DISPATCH_QUEUE_SERIAL);
dispatch_async(queue, ^{    // 异步执行 + 串行队列
    dispatch_sync(queue, ^{  // 同步执行 + 当前串行队列
        // 追加任务 1
        [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
        NSLog(@"1---%@",[NSThread currentThread]);      // 打印当前线程
    });
});
区别异步执行+并发队列 嵌套同一个并发队列同步执行+并发队列 嵌套一个并发队列异步执行+串行队列嵌套同一个串行队列同步执行+串行队列嵌套一个串行队列
同步没有开启新的线程,串行执行任务没有开启新线程,串行执行任务死锁卡住不执行死锁卡住不执行
异步有开启新线程,并发执行任务有开启新线程,并发执行任务有开启新线程(1条),串行执行任务有开启新线程(1条),串行执行任务

强烈推荐讲述iOS多线程之GCD

GCD栅栏:dispatch_barrier_async
会等待前边追加到并发队列中的任务全部执行完毕之后,再将指定的任务追加到该异步队列中。然后dispatch_barrier_async方法追加的任务执行完毕之后。

GCD执行一次:dispatch_once
在创建单例、或者有整个程序运行过程中只执行一次的代码

GCD队列组:dispatch_group
场景:当需要异步执行多个耗时任务,然后执行完毕后到主线程;
dispatch_group_notify:监听group中任务的完成状态,当所有的任务都执行完成后,追加任务到group中,并执行任务

GCD 信号量:dispatch_semaphore
信号量是基于计数器的一种多线程同步机制,用来管理对资源的并发访问。
GCD中的信号量是指持有计数的信号。在Dispatch Semaphore中,使用计数来完成这个功能,信号量为0则阻塞线程,大于0则不会阻塞。我们通过改变信号量的值,来控制是否阻塞线程,从而达到线程同步。

Dispatch Semaphore提供了三个方法:
dispatch_semaphore_create:创建一个Semaphore并初始化信号的总量
dispatch_semaphore_signal:发送一个信号,让信号总量加1
dispatch_semaphore_wait:可以使总量减1,信号总量小于0时就会一直等待。
信号量dispatch_semaphore主要用于两个方面:保持线程同步、为线程加锁

8、runtime
怎么理解OC是动态语言,Runtime又是什么?
静态语言:如c语言,编译阶段就要决定调用哪个函数,如果函数未实现就会编译报错。
动态语言:如OC语言,编译阶段并不能决定真正调用哪个函数,只要函数声明过即使没实现也不会报错。

Runtime是一套底层纯c语言API,OC代码最终都会被编译器转化为运行时代码,通过消息机制决定函数调用方式,这也是OC作为动态语言使用的基础。

理解消息机制的基本原理
OC的方法调用都是类似【receiver selector】的形式,其实每次都是一个运行时消息发送过程。
第一步:编译阶段
【receiver selector】方法被编译器转化,分为两种情况:
1、不带参数的方法被编译为:obj_msgSend(receiver, selector)
2.带参数的方法被编译为:obj_msgSend(receiver, selector, org1, org2, …)
第二步:运行时阶段
消息接受者receiver寻找对应的selector,也分为两种情况:
1.接受者能找到对应的selector,直接执行接受receiver对象的selector方法
2.接受者找不到对应的selector,消息被转发货者临时向接受者添加这个selector对应的实现内容,否则崩溃。

在c语言中,将代码转换为可执行程序,一般要经历三个步骤,即编译、链接、运行。在链接的时候,对象的类型、方法的实现就已经确定好了。
而在OC中,却将一些在编译和链接过程中的工作,放到了运行阶段,也就是说,就算事一个编译好的.ipa包,在程序没运行的时候,也不知道调用一个方法会发生什么。这也为后来大行其道的热修复提供了可能。因此我们呢称oc为一门动态语言。

这样的设计使oc变得灵活,甚至可以让我们在程序运行的时候,在动态修改一个方法的实现,而实现这一切的基础就是Runtime。
简单呢来说,Runtime是一个库,这个库使我们可以在程序运行时创建对象、检查对象,修改类和对象的方法。

runtime是在将代码转换为可执行程序时,一般经过三个过程,编译、链接、运行。在oc中编译只声明函数,不管有没有实现。在运行的时候去调用方法。这样的设计使oc变得灵活,甚至可以在我们程序运行的时候,去动态修改一个方法的实现。

消息发送
在oc中方法调用称为向对象发送消息
方法被调用(消息发送后)经过编译就是调用objc_msgSend方法。
消息机制的核心就是NSObject,绝大数对象都继承自它。

先被编译成objc_msgSend
沿着入参myClass的isa指针,找到myClass的类对象(Class),也就是MyClass
接着在MyClass的方法列表methodLists中,找到对应的Method
最后找到Method中的IMP指针,执行具体实现

如何提高方法查找的效率
方法通过isa指针,查找Class中的methodLists的。如果子类没实现对应的方法实现,还会沿着父类去查找。整个工程,可能成万上亿个方法,是如何解决性能问题的呢?

Class Cache 认为,当一个方法被调用,那么它之后被调用的可能性就越大。

如果方法列表(methodLists)没找到对应的selector
系统会提供三次补救机会。
第一次:

+ (Bool)resolveInstanceMethod:(SEL)sel{}(实例方法)
+ (Bool)resolveClassMethod:(SEL)sel {}(类方法)

这两个方法一个针对实例方法;一个针对类方法。返回值都是Bool

如果到了三次机会,还没找到对应的实现,就会crash

另:

运行时阶段的消息发送的详细步骤如下:
1、检测selector是不是需要忽略的。
2.检测target是不是nil对象。ObjC的特性是允许对一个nil对象执行任何一个方法不会Crash,因为会被忽略掉。
3.如果上面两个都过了,那就是开始查找这个类的IMP,先从cache里面找,若可以找得到就跳到对应的函数去执行。
4.如果在cache里找不到就找一下方法列表methodLists。
5.如果methodLists找不到,就到超类的方法列表里寻找,一直找,直到找到NSObject类为止。
6.如果还找不到,runtime就提供了如下三种方法来处理:动态方法解析、消息接受者重定向、消息重定向,

9、深拷贝和浅拷贝

浅拷贝就是拷贝后,并没有进行真正的复制,而是复制的对象和原对象都指向同一个地址。
深拷贝是真正的复制了一份,复制的对象指向了新的地址

开发过程中,大体上会区分为对象和容器两个概念
对象的拷贝,无论是copy还是mutabCopy都会产生新的对象,均为深拷贝

NSString 的copy都是属于浅拷贝
NSMutableString是属于深拷贝

10、OC运行时(runtime)机制?
对象调用方法的过程:(Objective-C Runtime)
(1)在对象类的dispatch table中尝试找到该消息。如果找到了,跳到相应的函数IMP去执行实现代码
(2)如果没找到,Runtime会发送+resolveInstanceMethod:或者+resolveClassMethod:尝试去resolve这个消息
(3)如果resolve方法返回NO,Runtime就发送-forwardingTargetForSelector:允许你把这个消息转发给另一个对象;
(4)如果没有新的目标对象返回,Runtime就会发送-methodSignatureForSelector:和-forwardInvocation:消息。
你可以发送-invokeWithTarget:消息来手动转发消息或发送-doesNotRecognizeSlector:抛出异常。

11、单例:它是用来限制一个类只能创建一个对象。这个对象中的属性可以存储全局共享的数据。所有的类都能访问、设置此单例中的属性数据。
优点:是它只会创建一个对象容易供外界访问,节约性能。
缺点:是一个类只有一个对象,可能造成责任过重,在一定程度上违背了“单一职责原则”。单例模式中没有抽象层,所以单例类的扩展有很大的困难。不能过多创建单例,因为单例从创建到程序关闭前会一直存在,过多的单例会影响性能,浪费系统资源。
观察者:(kvo)它提供了观察某一属性变化的方法。当指定对象的属性发生改变后,它会自动通知相应的观察者。
优点:能提供被观察者属性的新值与旧值。用keypaths来观察属性,因此也可以观察嵌套对象。
缺点:需要注册观察者,实现observeValueForKeyPath:方法,属性可以通过KVC的方法来修改。否则观察者收不到通知。
(3)**代理:**可以实现类与类之间一对一的通信。
优点:代理协议方法都有清晰的定义。代理方法可以设置可选货必须实现。
缺点:需要定义协议方法,需要设置代理对象,代理对象实现协议方法。内存管理方面,需要注意循环引用问题。
12、weak与assign的区别
weak用来修饰对象,不能修饰基本数据类型。assign一般用来修饰基本数据类型。weak修饰对象,在对象释放之后会把对象置为nil。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

~轻舟~

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值