• 【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中
    去实现,如果对系统的类增加方法和增加属性书没有办法的。
  • 相关阅读:
    每周一坑WAF防护域名主节点异常+阿里远程登录不了
    SmsForwarder转发阿里云登录验证码
    python统计IP段 (基础实现版)
    每周一坑nginx日志写不到elk索引
    阿里云WAF本地验证及误拦截处理
    php 1020
    php 1017
    php 1018
    php 1015
    php 1014
  • 原文地址:https://www.cnblogs.com/xjf125/p/16180167.html
Copyright © 2020-2023  润新知