⼏个常见的iOS⾯试题(GCD重点讲解)
1、GET和POST的区别
答:GET所有的参数都拼接在URL后⾯(安全性⽐POST要差,
所有GET登陆请求都会⽣成⽇志并且保存到⼿机⾥⾯!)。
POST参数不拼接到URL后⾯,所有参数都存放在请求体中。
2、MVC、MVP、MVVM
MVC模型中,C为(controller)。主要处理逻辑为:View触发事件,controller响应并处理逻辑,调⽤Model,Model处理完成后将数据
发送给View,View更新。
MVP模型中,P为Prenter,并以Prenter为核⼼,负责从model获取数据,并填充到View中。该模型使得Model和View不再有联
系,且View被称为“被动视图”,暴露出tter接⼝。
MVVM模型中,VM为ViewModel,同样是以VM为核⼼,但是不同于MVP,MVVM采⽤了数据双向绑定的⽅案,替代了繁琐复杂的
DOM操作。该模型中,View与VM保持同步,View绑定到VM的属性上,如果VM数据发⽣变化,通过数据绑定的⽅式,View会⾃动更新
视图;VM同样也暴露出Model中的数据。
3、单例、代理、KVO
单例:简单的来说,⼀个单例类,在整个程序中只有⼀个实例,并且提供⼀个类⽅法供全局调⽤,在编译时初始化这个类,然后⼀直保存在
内存中,到程序(APP)退出时由系统⾃动释放这部分内存。
代理:代理是⼀种设计模式,委托⽅声明协议,定义需要实现的接⼝,代理⽅按照协议实现⽅法⼀般⽤weak来避免循环引⽤
观察者:使⽤观察者模式⽤于实现跨层传递信息的机制。传递⽅式是⼀对多
KVO是观察者的另⼀实现
注意:使⽤tter⽅法改变值KVO会⽣效,使⽤KVC改变值KVO也会⽣效,因为KVC会调⽤tter⽅法,直接赋值成员变量不会触发
KVO,因为不会调⽤tter⽅法,需要加上willChangeValueForKey和didChangeValueForKey。
4、数据持久化
Defaults
in(钥匙串)
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_tqueue=dispatch_queue_create("",DISPATCH_QUEUE_SERIAL)
异步执⾏+串⾏队列
dispatch_async(queue,^{//异步执⾏})
在异步执⾏队列中追加同步任务
dispatch_async(queue,^{
dispatch_sync(queue,^{
//追加任务1
[NSThreadsleepForTimeInterval:2];
NSLog(@"1---%@",[NSThreadcurrentThread]);
})
})
执⾏上⾯代码会导致串⾏队列中追加的任务和串⾏队列中的原有的任务两者之间相互等待,阻塞了串⾏队列,最终造成了串⾏队列所在的
线程(⼦线程)死锁问题。
dispatch_queue_tqueue=dispatch_queue_create("",DISPATCH_QUEUE_SERIAL);
dispatch_async(queue,^{//异步执⾏+串⾏队列
dispatch_sync(queue,^{//同步执⾏+当前串⾏队列
//追加任务1
[NSThreadsleepForTimeInterval:2];//模拟耗时操作
NSLog(@"1---%@",[NSThreadcurrentThread]);//打印当前线程
});
});
区
别
异步执⾏+并发队列嵌套同⼀个并
发队列
同步执⾏+并发队列嵌套⼀个并
发队列
异步执⾏+串⾏队列嵌套同⼀个串
⾏队列
同步执⾏+串⾏队列嵌套⼀个串
⾏队列
同
步
没有开启新的线程,串⾏执⾏任务没有开启新线程,串⾏执⾏任务死锁卡住不执⾏死锁卡住不执⾏
异
步
有开启新线程,并发执⾏任务有开启新线程,并发执⾏任务
有开启新线程(1条),串⾏执⾏任
务
有开启新线程(1条),串⾏执⾏
任务
强烈推荐讲述
GCD栅栏:dispatch_barrier_async
会等待前边追加到并发队列中的任务全部执⾏完毕之后,再将指定的任务追加到该异步队列中。然后dispatch_barrier_async⽅法追加的任务
执⾏完毕之后。
GCD执⾏⼀次:dispatch_once
在创建单例、或者有整个程序运⾏过程中只执⾏⼀次的代码
GCD队列组:dispatch_group
场景:当需要异步执⾏多个耗时任务,然后执⾏完毕后到主线程;
dispatch_group_notify:监听group中任务的完成状态,当所有的任务都执⾏完成后,追加任务到group中,并执⾏任务
GCD信号量:dispatch_maphore
信号量是基于计数器的⼀种多线程同步机制,⽤来管理对资源的并发访问。
GCD中的信号量是指持有计数的信号。在DispatchSemaphore中,使⽤计数来完成这个功能,信号量为0则阻塞线程,⼤于0则不会阻
塞。我们通过改变信号量的值,来控制是否阻塞线程,从⽽达到线程同步。
DispatchSemaphore提供了三个⽅法:
dispatch_maphore_create:创建⼀个Semaphore并初始化信号的总量
dispatch_maphore_signal:发送⼀个信号,让信号总量加1
dispatch_maphore_wait:可以使总量减1,信号总量⼩于0时就会⼀直等待。
信号量dispatch_maphore主要⽤于两个⽅⾯:保持线程同步、为线程加锁
8、runtime
怎么理解OC是动态语⾔,Runtime⼜是什么?
静态语⾔:如c语⾔,编译阶段就要决定调⽤哪个函数,如果函数未实现就会编译报错。
动态语⾔:如OC语⾔,编译阶段并不能决定真正调⽤哪个函数,只要函数声明过即使没实现也不会报错。
Runtime是⼀套底层纯c语⾔API,OC代码最终都会被编译器转化为运⾏时代码,通过消息机制决定函数调⽤⽅式,这也是OC作为动态语
⾔使⽤的基础。
理解消息机制的基本原理
OC的⽅法调⽤都是类似【receiverlector】的形式,其实每次都是⼀个运⾏时消息发送过程。
第⼀步:编译阶段
【receiverlector】⽅法被编译器转化,分为两种情况:
1、不带参数的⽅法被编译为:obj_msgSend(receiver,lector)
2.带参数的⽅法被编译为:obj_msgSend(receiver,lector,org1,org2,…)
第⼆步:运⾏时阶段
消息接受者receiver寻找对应的lector,也分为两种情况:
1.接受者能找到对应的lector,直接执⾏接受receiver对象的lector⽅法
2.接受者找不到对应的lector,消息被转发货者临时向接受者添加这个lector对应的实现内容,否则崩溃。
在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的。如果⼦类没实现对应的⽅法实现,还会沿着⽗类去查找。整个⼯程,可能成万上亿个⽅
法,是如何解决性能问题的呢?
ClassCache认为,当⼀个⽅法被调⽤,那么它之后被调⽤的可能性就越⼤。
如果⽅法列表(methodLists)没找到对应的lector
系统会提供三次补救机会。
第⼀次:
+(Bool)resolveInstanceMethod:(SEL)l{}(实例⽅法)
+(Bool)resolveClassMethod:(SEL)l{}(类⽅法)
这两个⽅法⼀个针对实例⽅法;⼀个针对类⽅法。返回值都是Bool。
如果到了三次机会,还没找到对应的实现,就会crash
另:
运⾏时阶段的消息发送的详细步骤如下:
1、检测lector是不是需要忽略的。
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-CRuntime)
(1)在对象类的dispatchtable中尝试找到该消息。如果找到了,跳到相应的函数IMP去执⾏实现代码
(2)如果没找到,Runtime会发送+resolveInstanceMethod:或者+resolveClassMethod:尝试去resolve这个消息
(3)如果resolve⽅法返回NO,Runtime就发送-forwardingTargetForSelector:允许你把这个消息转发给另⼀个对象;
(4)如果没有新的⽬标对象返回,Runtime就会发送-methodSignatureForSelector:和-forwardInvocation:消息。
你可以发送-invokeWithTarget:消息来⼿动转发消息或发送-doesNotRecognizeSlector:抛出异常。
11、单例:它是⽤来限制⼀个类只能创建⼀个对象。这个对象中的属性可以存储全局共享的数据。所有的类都能访问、设置此单例中的属
性数据。
优点:是它只会创建⼀个对象容易供外界访问,节约性能。
缺点:是⼀个类只有⼀个对象,可能造成责任过重,在⼀定程度上违背了“单⼀职责原则”。单例模式中没有抽象层,所以单例类的扩展有
很⼤的困难。不能过多创建单例,因为单例从创建到程序关闭前会⼀直存在,过多的单例会影响性能,浪费系统资源。
观察者:(kvo)它提供了观察某⼀属性变化的⽅法。当指定对象的属性发⽣改变后,它会⾃动通知相应的观察者。
优点:能提供被观察者属性的新值与旧值。⽤keypaths来观察属性,因此也可以观察嵌套对象。
缺点:需要注册观察者,实现obrveValueForKeyPath:⽅法,属性可以通过KVC的⽅法来修改。否则观察者收不到通知。
(3)**代理:**可以实现类与类之间⼀对⼀的通信。
优点:代理协议⽅法都有清晰的定义。代理⽅法可以设置可选货必须实现。
缺点:需要定义协议⽅法,需要设置代理对象,代理对象实现协议⽅法。内存管理⽅⾯,需要注意循环引⽤问题。
12、weak与assign的区别
weak⽤来修饰对象,不能修饰基本数据类型。assign⼀般⽤来修饰基本数据类型。weak修饰对象,在对象释放之后会把对象置为nil。
本文发布于:2023-03-03 16:20:16,感谢您对本站的认可!
本文链接:https://www.wtabcd.cn/fanwen/zuowen/1677831617125076.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
本文word下载地址:ios应用锁.doc
本文 PDF 下载地址:ios应用锁.pdf
留言与评论(共有 0 条评论) |