接上文【iOS面试总结】疫情隔离中,线上面试的问题集合(第一部分)
6、Runtime
6.1 介绍下Runtime?
oc是一门动态语言,所谓动态语言就是在编译阶段无法确定调用的函数以及属性的类型,只有在运行阶段首次确定类型和调用的函数。
runtime就是动态语言下核心的一个库,底层都会通过obj_sendMsg来处理消息转发机制。也是因为拥有runtime使得oc语言灵活性比较强,能够具有动态、动态绑定、动态解析的特性。
总结:可在程序在运行时改变结构,如添加方法,交换方法等。
6.2 runtime调用流程?
1、当调用个对象的时候,会通过obj_oject的isa指针找对对应的归属类。
2、从归属类(obj_class)类中的obj_cache中寻找对应的相等的sel方法编号。
3、如果没有找到,继续obj_class中的obj_method_lish中查找,如果找到写入obj_cache中。
4、如果没有到找到,会一直找到它的元类上。
5、如果元类也没有的话,会调用消息动态解析方法(resovleInstace和resloveClass)的方法,查看是否存在绑定的方法。
6、如果没有绑定方法,会调用消息转发方法(forwardingTagert)的方法。查看是否存在转发对象。
7、如果没有存在消息转发对象,会调用(methodSinature)的方法,查看是否有方法签名返回类型和参数类型。
8、如果不存在签名方法和类型,就会崩溃,找不到方法。
9、存在签名的方法,就是继续执行forwardingInvocation方法,最后一次通知绑定对象寻找IMP地址。
10、如果在forwardingInvocation没有找到IMP,就会调用找不到方法。
6.3 消息发送的流程是怎样的?
OC中的方法调用会转化成给对象发送消息,发送消息会调用这个方法:
objc_msgSend(receiver, @selector(message))
该过程有以下关键步骤:
1、先确定调用方法的类已经都加载完毕,如果没加载完毕的话进行加载
2、从cache中查找方法
3、cache中没有找到对应的方法,则到方法列表中查,查到则缓存
4、如果本类中查询到没有结果,则遍历所有父类重复上面的查找过程,直到NSObject
6.4 runtime如何通过selector找到对应的IMP地址?
每一个类对象中都一个方法列表,方法列表中记录着方法的名称,方法实现,以及参数类型,其实selector本质就是方法名称,通过这个方法名称就可以在方法列表中找到对应的方法实现.
7、Runloop
7.1 Runloop的运行模式有哪些?
RunLoop的运行模式共有5种,RunLoop只会运行在一个模式下,要切换模式,就要暂停当前模式,重写启动一个运行模式
1、kCFRunLoopDefaultMode, App的默认运行模式,通常主线程是在这个运行模式下运行
2、UITrackingRunLoopMode, 跟踪用户交互事件(用于 ScrollView 追踪触摸滑动,保证界面滑动时不受其他Mode影响)
3、kCFRunLoopCommonModes, 伪模式,不是一种真正的运行模式
4、UIInitializationRunLoopMode:在刚启动App时第进入的第一个Mode,启动完成后就不再使用
5、GSEventReceiveRunLoopMode:接受系统内部事件,通常用不到
7.2 介绍下Runloop的内部逻辑
8、Block
8.1 开发中Block有哪几种形式?
8.1.1 全局 Block
当我们声明一个block时,如果这个block没有捕获外部的变量,那么这个block就位于全局区,此时对NSGlobalBlock的retain、copy、release操作都无效。ARC和MRC环境下都是如此。
8.1.2 栈 Block
如果我们在声明一个block的时候,使用了__weak或者__unsafe__unretained的修饰符,那么系统就不会为我们做copy的操作,不会将其迁移到堆区。
8.1.3 堆 Block
在ARC环境下,__strong修饰的(默认)block只要捕获了外部变量就会位于堆区,NSMallocBlock支持retain、release,会对其引用计数+1或 -1。
9、App与H5交互
9.1 App与H5交互的几种方法?
9.1.1 UIWebView拦截Url
利用拦截webView响应的url,对url进行处理,同时把需要执行的方法名和参数都放入url中,实现app和H5之前的方法交互:
//遵守UIWebViewDelegate代理协议。 -(BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType{ //拿到网页的实时url NSString *requestStr = [[request.URL absoluteString] stringByRemovingPercentEncoding]; // NSString* requestStr = @"H5GetAPPMethod://test?data={\"name\":\"wangyujian\",\"sex\":\"男\",\"age\":\"18\"}"; //在url中寻找自定义协议头"H5GetAPPMethod://" if ([requestStr hasPrefix:@"H5GetAPPMethod://"]) { NSArray* arr1 = [url componentsSeparatedByString:@"://"]; if (arr1.count >= 2) { NSArray* arr2 = [arr1[1] componentsSeparatedByString:@"?"]; if (arr2.count >= 2) { NSString* method = arr2[0]; NSString* jsonStr = [arr2[1] substringFromIndex:@"data=".length]; NSData *JSONData = [jsonStr dataUsingEncoding:NSUTF8StringEncoding]; NSDictionary *dic = [NSJSONSerialization JSONObjectWithData:JSONData options:NSJSONReadingMutableLeaves error:nil]; NSLog(@"method = %@,xxx = %@,dic = %@",method,jsonStr,dic); // 方法名和参数解析 [self runMethodWithName:method param:dic]; } } return NO; } return YES; } // 方法名和参数解析&&运行方法 - (void)runMethodWithName:(NSString*)methodName param:(NSDictionary*)param{ SEL selector = NSSelectorFromString(methodName); self.param = param; // // 这里报警告:PerformSelector may cause a leak because its selector is unknown // if ([self respondsToSelector:selector]) { // [self performSelector:selector]; // } if ([self respondsToSelector:selector]){ IMP imp = [self methodForSelector:selector]; void (*func)(id, SEL) = (void *)imp; func(self, selector); } } - (void)test{ NSLog(@"xxxxxxxx"); }
9.1.2 WKUserContentController
这个属性是WKWebView才有的属性,主要是通过WKScriptMessageHandler的代理方法- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message
进行交互。
- (void)creatWebView{ WKWebViewConfiguration *configuration = [[WKWebViewConfiguration alloc] init] WKUserContentController *userContentController =[[WKUserContentController alloc]init]; configuration.userContentController = userContentController; // 根据需要去设置对应的属性 WKWebView *webView = [[WKWebView alloc]initWithFrame:self.view.bounds configuration:configuration]; webView.navigationDelegate = self; [self.view addSubview:webView]; NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:@"URL地址"]]; [self.webView loadRequest:request]; [userContentController addScriptMessageHandler:delegateController name:@"goBack"]; } //WKScriptMessageHandler代理方法 - (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message { if ([message.name isEqualToString:@"goBack"]) { // 如果监控到前端调用了 'goBack' 方法代码就会走进来,在这里做处理就行了 } }
9.1.3 第三方WebViewJavascriptBridge
1、建立 WebViewJavaScriptBridge 和 WebView 之间的关系
_jsBridge = [WebViewJavascriptBridge bridgeForWebView:_webView];
2、方法调用
1)oc调js方法(通过data可以传值,通过 response可以接受js那边的返回值 )
id data = @{@"greetingFromObjC": @"Hi there, JS!" }; [_bridgecallHandler:@"testJavascriptHandler" data:data responseCallback:^(idresponse) { NSLog(@"testJavascriptHandlerresponded: %@", response); }];
2)js调oc方法(可以通过data给oc方法传值,使用responseCallback将值再返回给js)
[_bridgeregisterHandler:@"testObjcCallback" handler:^(id data,WVJBResponseCallback responseCallback) { NSLog(@"testObjcCallback called:%@", data); responseCallback(@"Response fromtestObjcCallback"); }];
最后:iOS调用H5方法
UIWebView:NSString *result = [webView stringByEvaluatingJavaScriptFromString:@"javascript:add(3,5);"]; WKWebView: [self.webView evaluateJavaScript:@"show()" completionHandler:^(id _Nullable response, NSError * _Nullable error) { //TODO }];
10、设计模式
10.1 单例模式
10.1.1 单例模式的作用
1)可以保证在程序运行过程,一个类只有一个实例,而且该实例易于供外界访问
2)从而方便地控制了实例个数,并节约系统资源
10.1.2 单例模式的实现
//1. 在.m中保留一个全局的static的实例 static id _instance; //2.重写allocWithZone:方法,在这里创建唯一的实例(注意线程安全) + (instancetype)allocWithZone:(struct _NSZone *)zone { static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ _instance = [super allocWithZone:zone]; }); return _instance; } //3.提供1个类方法让外界访问唯一的实例 + (instancetype)sharedInstance { static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ _instance = [[self alloc] init]; }); return _instance; } //4.实现copyWithZone:方法 - (id)copyWithZone:(struct _NSZone *)zone { return _instance; }
11、MVC 和 MVVM
11.1 MVC
11.1.1 MVC的弊端
1) 厚重的View Controller
M:模型model的对象通常非常的简单。根据Apple的文档,model应包括数据和操作数据的业务逻辑。而在实践中,model层往往非常薄,不管怎样,model层的业务逻辑不应被拖入到controller。
V:视图view通常是UIKit控件(component,这里根据习惯译为控件)或者编码定义的UIKit控件的集合。View的如何构建(PS:IB或者手写界面)何必让Controller知晓,同时View不应该直接引用model(PS:现实中,你懂的!),并且仅仅通过IBAction事件引用controller。业务逻辑很明显不归入view,视图本身没有任何业务。
C:控制器controller。Controller是app的“胶水代码”:协调模型和视图之间的所有交互。控制器负责管理他们所拥有的视图的视图层次结构,还要响应视图的loading、appearing、disappearing等等,同时往往也会充满我们不愿暴露的model的模型逻辑以及不愿暴露给视图的业务逻辑。网络数据的请求及后续处理,本地数据库操作,以及一些带有工具性质辅助方法都加大了Massive View Controller的产生。
2)遗失(无处安放)的网络逻辑
苹果使用的MVC的定义是这么说的:所有的对象都可以被归类为一个model,一个view,或是一个controller。
你可能试着把它放在Model对象里,但是也会很棘手,因为网络调用应该使用异步,这样如果一个网络请求比持有它的model生命周期更长,事情将变的复杂。显然View里面做网络请求那就更格格不入了,因此只剩下Controller了。若这样,这又加剧了Massive View Controller的问题。若不这样,何处才是网络逻辑的家呢?
3)较差的可测试性
由于View Controller混合了视图处理逻辑和业务逻辑,分离这些成分的单元测试成了一个艰巨的任务。
11.1.2 MVVM
一种可以很好地解决Massive View Controller
问题的办法就是将 Controller 中的展示逻辑抽取出来,放置到一个专门的地方,而这个地方就是 viewModel
。MVVM衍生于MVC,是对 MVC 的一种演进,它促进了 UI 代码与业务逻辑的分离。它正式规范了视图和控制器紧耦合的性质,并引入新的组件。他们之间的结构关系如下:
11.1.3 MVVM 的基本概念
1)在MVVM
中,view
和 view controller
正式联系在一起,我们把它们视为一个组件
2)view
和 view controller
都不能直接引用model
,而是引用视图模型(viewModel
)
3)viewModel
是一个放置用户输入验证逻辑,视图显示逻辑,发起网络请求和其他代码的地方
4)使用MVVM
会轻微的增加代码量,但总体上减少了代码的复杂性
11.1.4 MVVM 的注意事项
view
引用viewModel
,但反过来不行(即不要在viewModel
中引入#import UIKit.h
,任何视图本身的引用都不应该放在viewModel
中)(PS:基本要求,必须满足)
1)viewModel
引用model
,但反过来不行* MVVM 的使用建议
2)MVVM
可以兼容你当下使用的MVC
架构。
3)MVVM
增加你的应用的可测试性。
4)MVVM
配合一个绑定机制效果最好(PS:ReactiveCocoa你值得拥有)。
5)viewController
尽量不涉及业务逻辑,让 viewModel
去做这些事情。
6)viewController
只是一个中间人,接收 view
的事件、调用 viewModel
的方法、响应 viewModel
的变化。
7)viewModel
绝对不能包含视图 view(UIKit.h)
,不然就跟 view
产生了耦合,不方便复用和测试。
8)viewModel
之间可以有依赖。
9)viewModel
避免过于臃肿,否则重蹈Controller
的覆辙,变得难以维护。
11.1.5 MVVM 的优势
1)低耦合:View
可以独立于Model
变化和修改,一个 viewModel
可以绑定到不同的 View
上
2)可重用性:可以把一些视图逻辑放在一个 viewModel
里面,让很多 view
重用这段视图逻辑
3)独立开发:开发人员可以专注于业务逻辑和数据的开发 viewModel
,设计人员可以专注于页面设计
4)可测试:通常界面是比较难于测试的,而 MVVM
模式可以针对 viewModel
来进行测试
11.1.6 MVVM 的弊端
1)数据绑定使得Bug
很难被调试。你看到界面异常了,有可能是你 View
的代码有 Bug
,也可能是 Model
的代码有问题。数据绑定使得一个位置的 Bug
被快速传递到别的位置,要定位原始出问题的地方就变得不那么容易了。
2)对于过大的项目,数据绑定和数据转化需要花费更多的内存(成本)。主要成本在于:
3)数组内容的转化成本较高:数组里面每项都要转化成Item
对象,如果Item对象中还有类似数组,就很头疼。
4)转化之后的数据在大部分情况是不能直接被展示的,为了能够被展示,还需要第二次转化。
5)只有在API返回的数据高度标准化时,这些对象原型(Item
)的可复用程度才高,否则容易出现类型爆炸,提高维护成本。
6)调试时通过对象原型查看数据内容不如直接通过NSDictionary/NSArray
直观。
7)同一API的数据被不同View展示时,难以控制数据转化的代码,它们有可能会散落在任何需要的地方。
12、iOS基础知识掌握
12.1 category能否添加属性,为什么?能否添加实例变量,为什么?
可以添加属性,这里的属性指@property,但跟类里的@property又不一样。正常的@property为:实例变量Ivar + Setter + Getter 方法,分类里的@property这三者都没有,需要我们手动实现。
分类是运行时被编译的,这时类的结构已经固定了,所以我们无法添加实例变量。
对于分类自定义Setter和Getter方法,我们可以通过关联对象(Associated Object)进行实现。
12.2 Autoreleasepool的原理
Autoreleasepool的原理是一个基于双向列表的栈结构,它会对加入其中的对象实现延迟释放。当Autoreleasepool调用drain方法时会释放内部标记为autorelease的对象。
它存在一个哨兵对象,哨兵对象类似一个指针,指向自动释放池的栈顶位置,它的作用就是用于标记当前自动释放池需要释放内部对象时,释放到那个地方结束,每次入栈时它用于确定添加的位置,然后再次移动到栈顶。
12.3 触摸事件的响应过程
一个触摸事件的响应过程如下: 1. 用户触摸屏幕时,UIKit会生成UIEvent对象来描述触摸事件。对象内部包含了触摸点坐标等信息。 2. 通过Hit Test确定用户触摸的是哪一个UIView。这个步骤通过- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event方法来完成。 3. 找到被触摸的UIView之后,如果它能够响应用户事件,相应的响应函数就会被调用。如果不能响应,就会沿着响应链(Responder Chain)寻找能够响应的UIResponder对象(UIView是UIResponder的子类)来响应触摸事件。
12.4 事件响应链
UIView-->ViewController-->UIWindow-->UIAplication-->AppDelagate
12.5 UIView和UICLayer区别?
UIView继承UIResponder,接收点击事件,CALayer直接继承 NSObject,并没有相应的处理事件的接口。
UIView是CALayer的delegate
UIView主要处理事件,CALayer负责绘制就更好
12.6 是否了解JLRoute框架?
路由:其中运行机制为:保存一个全局的map,key是url,value是对应存放的block数组,url和block都会常驻在内存中,当打开一个URL时,JLRoutes就可以遍历 , 这个全局的map,通过url来执行对应的block。
12.7 Atomic 和 nonAtomic区别?
Atomic * 是默认的 * 会保证 CPU 能在别的线程来访问这个属性之前,先执行完当前流程 * 速度不快,因为要保证操作整体完成 Non-Atomic * 不是默认的 * 更快 * 线程不安全 * 如有两个线程访问同一个属性,会出现无法预料的结果
12.8 class和struct区别?
swift中,class是引用类型,struct是值类型。值类型在传递和赋值时将进行复制,而引用类型则只会使用引用对象的一个"指向"。所以他们两者之间的区别就是两个类型的区别。 class有这几个功能struct没有的: * class可以继承,这样子类可以使用父类的特性和方法 * 类型转换可以在runtime的时候检查和解释一个实例的类型 * 可以用deinit来释放资源 * 一个类可以被多次引用 struct也有这样几个优势: * 结构较小,适用于复制操作,相比于一个class的实例被多次引用更加安全。 * 无须担心内存memory leak或者多线程冲突问题
12.9 instanceType的用法?
instancetype只能作为函数返回值,不能用来定义定义变量。
用instancetype作为函数返回值返回的是函数所在类型的类型。
12.10 weak的原理,weak和asign的区别?
weak表其实是一个哈希表,key是所指对象的指针,value是weak指针的地址数组。(value是数组的原因是:因为一个对象可能被多个弱引用指针指向)。同时Runtime维护了一张weak表,用来存储某个对象的所有的weak指针。
weak是弱引用,用weak来修饰、描述所引用对象的计数器并不会加1,而且weak会在引用对象被释放的时候自动置为nil,这也就避免了野指针访问坏内存而引起奔溃的情况,另外weak也可以解决循环引用。
assign对象被释放的时候不会指向nil,对象被释放了还是指向原来的地址。调用的话容易产生野指针。
assign可以修对象和基本数据类型。
12.11 rutime的catagroy以及和Extendtion的区别?
catagroy是动态性的,在不改变原有类的内存结构下,增加自己的方法和属性。
Extendstion是静态的。也就是在编译阶段就要确定方法和类型了。而且Extendtion只是拓展一些方法,具体的实现还是在本类的.m中
去实现,如果对系统的类增加方法和增加属性书没有办法的。