【iOS面试总结】疫情隔离中,线上面试的问题集合(第二部分)

  接上文【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

 

      8afe30384d1db67349848087ec9c62e6.png

        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 代码与业务逻辑的分离。它正式规范了视图和控制器紧耦合的性质,并引入新的组件。他们之间的结构关系如下:

 

        7698d48cd85dfc26ae28f0298cbeed7d.png

       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 UIViewUICLayer区别?

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 classstruct区别?

swift中,class是引用类型,struct是值类型。值类型在传递和赋值时将进行复制,而引用类型则只会使用引用对象的一个"指向"。所以他们两者之间的区别就是两个类型的区别。
class有这几个功能struct没有的:
* class可以继承,这样子类可以使用父类的特性和方法
* 类型转换可以在runtime的时候检查和解释一个实例的类型
* 可以用deinit来释放资源
* 一个类可以被多次引用
struct也有这样几个优势:
* 结构较小,适用于复制操作,相比于一个class的实例被多次引用更加安全。
* 无须担心内存memory leak或者多线程冲突问题

    12.9 instanceType的用法?

instancetype只能作为函数返回值,不能用来定义定义变量。
用instancetype作为函数返回值返回的是函数所在类型的类型。

    12.10 weak的原理,weakasign的区别?

weak表其实是一个哈希表,key是所指对象的指针,value是weak指针的地址数组。(value是数组的原因是:因为一个对象可能被多个弱引用指针指向)。同时Runtime维护了一张weak表,用来存储某个对象的所有的weak指针。

weak是弱引用,用weak来修饰、描述所引用对象的计数器并不会加1,而且weak会在引用对象被释放的时候自动置为nil,这也就避免了野指针访问坏内存而引起奔溃的情况,另外weak也可以解决循环引用。
assign对象被释放的时候不会指向nil,对象被释放了还是指向原来的地址。调用的话容易产生野指针。
assign可以修对象和基本数据类型。

    12.11 rutimecatagroy以及和Extendtion的区别?

catagroy是动态性的,在不改变原有类的内存结构下,增加自己的方法和属性。
Extendstion是静态的。也就是在编译阶段就要确定方法和类型了。而且Extendtion只是拓展一些方法,具体的实现还是在本类的.m中
去实现,如果对系统的类增加方法和增加属性书没有办法的。

 

posted @ 2022-04-22 18:41  Mr·Xu  阅读(164)  评论(0编辑  收藏  举报