iOS面试题

作者: RocketsChen | 来源:发表于2018-03-01 19:33 被阅读165次

    1.KVO实现原理?

    首先了解下什么是KVO(Key-Value Observing)?
    
    文档解释如下:
    Key-value observing is a mechanism that allows objects to be notified of changes to specified properties of other objects.
    
    翻译:
    他是提供一种机制,当指定的对象的属性被修改后,其观察者就会接受到通知
    

    简单的说就是每次指定的被观察的对象(Key)的属性被修改后,KVO就会自动通知相应的观察者。主要依靠Objective-C的动态性和runtime

    我通过一个小demo显示来具体分析KVO,代码如下
    准备工作:
    • 创建一个模型和一个属性如下:
    /* 测试属性 */
    @property (nonatomic, copy) NSString *change;
    
    • 在控制器中创建一个button,和label。按钮作为点击事件,label作为展示值更新(UI层)。

    核心代码:

        /*
         首先注册showLabel对象为我们的被观察者
         这里option 我们选择:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld
         */
        
        self.demoItem = [DemoItem new];
        [self.demoItem addObserver:self forKeyPath:@"change" options:NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew context:nil];
        
    
    #pragma mark - 只要object的keyPath属性发生变化,就会调用此回调方法
    - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context
    {
        if ([keyPath isEqualToString:@"change"] && object == self.demoItem) {
            
            self.showLabel.text = [NSString stringWithFormat:@"%@",[change valueForKey:@"new"]];
            
            //change字典中的新、旧值的这两个方法
            NSLog(@"\noldDate:%@ newDate:%@",[change valueForKey:@"old"],[change valueForKey:@"new"]);
        }
    }
    
    #pragma mark - 按钮点击
    - (IBAction)buttonClick:(id)sender {
        //每次添加一个你好
        self.demoItem.change = [self.showLabel.text stringByAppendingString:@"你好啊"];
    }
    
    • 打印


      如图
    NSKeyValueObservingOptions
     NSKeyValueObservingOptionNew = 0x01,  : 提供更改前的值
     NSKeyValueObservingOptionOld = 0x02,  : 提供更改后的值
     NSKeyValueObservingOptionInitial API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0)) = 0x04, : 观察最初的值
     NSKeyValueObservingOptionPrior API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0)) = 0x08 : 分别在值修改前后触发方法
    
    总结Demo步骤
    • 1.注册观察者,对观察者实施监听。
    • 2.在回调方法中处理属性发生的变化(更新UI等)。
    • 3.移除观察者(注意)
    基本的原理

    当观察某对象 A 时,KVO机制动态创建一个对象A当前类的子类,并为这个新的子类重写了被观察属性keyPathsetter方法。setter方法随后负责通知观察对象属性的改变情况。

    2.说说你理解的无痕埋点?

    什么是无痕埋点?

    移动端无痕埋点的自动化采集技术,降低开发成本、提高采集效率和数据质量;通过无痕埋点平台对埋点数据的集中管理,实现可配置、即埋即生 效的机制;并建立以资源位数据和透传数据自动采集为基础的引导效果体系。

    为了解决前端埋点的准确性、及时性、开发效率等问题,业内各家公司从不同角度,提出了多种技术方案,这些方案大体上可以归为三类:

    • 第一类是代码埋点,即在需要埋点的节点调用接口直接上传埋点数据,友盟百度统计等第三方数据统计服务商大都采用这种方案;

    • 第二类是可视化埋点,即通过可视化工具配置采集节点,在前端自动解析配置并上报埋点数据,从而实现所谓的“无痕埋点”, 代表方案是已经开源的Mixpanel

    • 第三类是“无埋点”,它并不是真正的不需要埋点,而是前端自动采集全部事件并上报埋点数据,在后端数据计算时过滤出有用数据,代表方案是国内的GrowingIO

    这里推荐一个博客,值得看一下美团点评前端无痕埋点实践

    3.消息转发机制原理?

    • 利用下图来详细讲解下:


      如图
    • 如上图消息转发的过程中,我们看出来接受者在每一步中均有机会处理消息。步骤越往后,处理消息的代价越大。最好能在第一步处理完成,这样的话系统就可以将此方法缓存起来了。
    第一阶段:首先征询接受者,所属类,看看其是否能动态的添加方法,以用来处理当前这个未知的选择子(unknown selector),这样也可以称为动态解析。
    第二阶段:是完整的消息转发机制(full forwarding mechanism)。
    • 动态方法解析:对象在收到无法解读的消息后,首先将调用所属类的下列方法:
    + (BOOL)resolveInstanceMethod:(SEL)sel
    

    该方法的参数就是那个未知的选择子(SEL),其返回值为Boolean类型,表示这个类是否能新增一个实例方法用来处理此选择子(SEL)。再继续往下执行转发机制之前,本类有机会新增一个处理选择子的方法。假如不是实例方法是类方法的话那就用如下方法:

    + (BOOL)resolveClassMethod:(SEL)sel
    
    • 备援接受者:当前接受者还有第二次机会能处理未知的选择子,在这一步中,运行期系统会问她:能不能把这条消息转发给其他接受者来处理。
    - (id)forwardingTargetForSelector:(SEL)aSelector
    

    如果找不到的话则返回nil。我们无法操作经由这一步所转发的消息。若是想在发给备援者之前先修改消息内容,那就得通过完整的消息转发机制来做。

    • 完成的消息转发机制:如若转发的步骤已经走到这一步,那么只有通过完整的消息转发机制来完成。首先创建NSInvocation对象,把尚未处理的消息有关的细节都封于其中:选择子(SEL),目标(Target)以及参数。此对象出发后”消息派发系统“将把消息指派给目标对象:
    - (void)forwardInvocation:(NSInvocation *)anInvocation
    

    这个方法只需改变调用目标,让消息在新的目标上调用即可。如果调用操作不由本类处理,则需要用超类的同名方法。如用NSObject的方法和doesNotRecognizeSelector抛出异常表明消息最终未得到处理。

    4.说说你理解weak属性?

    weak关键字在OC中属于比较基础的知识此特性表明该属性定义了一种关系“非拥有关系”(nonowning relationship)。为这种属性设置新值得时,设置方法既不保留新值,也不释放旧值。此特性同assign类似,然后在属性所指的对象遭到摧毁时,属性值也会清空(nil out)。

    延伸什么情况使用 weak 关键字?

    在ARC中,在可能出现循环引用的时候,往往要通过其中一端使用Weak来解决,比如说代理(delegate)。或者拖线IBOutlet一般也是用weak来修饰。
    一般我们还会把weak属性和assign两者相比较:assign可以用于非OC对象,而weak必须用于OC对象。

    5.假如Controller太臃肿,如何优化?

    说道Controller优化瘦身那我们首先得联想到老生常谈的设计模式:MVC;
    MVC的的定义很容易造成将大量的代码是现在控制器(Controller)中。从而形成了控制器有个专业词汇Massive ViewControler(臃肿的视图控制器)。下面是我总结的几天关于控制器瘦身的技巧:

    1.分离数据源(Data Source)等协议(Protocol)
    2.把业务逻辑移至 Model
    3.创建Store类
    4.把视图相关的代码移至View层
    这里推荐一个博客,值得看一下猿题库 iOS 客户端架构设计
    这里顺带谈谈项目中一般考虑如何去优化
    • 1.启动优化

      • 启动过程中尽可能做少量的事(如果涉及到接口建议合并处理)
      • 不要再UI线程上做耗时操作,数据处理放入子线程中,处理完返回数据到主线程并刷新界面。
      • 尽量减少包的大小。
      • 做好预加载,如果用户指引等.
    • 2.操作流畅度上优化

      • tableView的Cell的加载优化(重用机制)
      • 控制器之间跳转优化(可以预先做好数据准备)
    • 3.数据库优化

      • 数据库上的重构优化
      • 查询语句的优化
      • 分库分表
    • 4.代码质量上

      • 重构
      • 封装

    6.项目中网络层如何做安全处理?

    1.判断API的调用请求是否来自于经过授权的APP。如若不是则拒绝请求访问
    2.在数据请求的过程中进行URL加密处理:防止反编译,接口信息被静态分析。
    3.数据传输加密:对客户端传输数据提供有效的加密方案,以防止网络接口的拦截。
    如果可以尽量使用HTTPS,可以有效的避免接口数据在传输中被攻击。

    7.main()之前的过程有哪些?

    在iOS中 main.m 是我们所熟悉的程序入口。但是在在此之前其实程序以及做了很多事了。如系统会获取dyld的路径,并加载。加载程序中的依赖库。调用所有的+ load方法,并返回main函数地址。

    动态链接:在iOS中会用到系统framework都是动态链接,好比插头和插排,静态链接的代码在编译成功后的过程是将插头和插排一个个插好,运行时直接执行二进制文件;而动态则不相同:需在程序启动时去完成”插插销“这个过程,所以在执行之前,动态链接需要完成准备工作。

    • 这个是在 Xcode 中看到的 Link 列表,如下这些 framework 将会在动态链接过程中被加载。


      如图
    • 如下截图方法展示打印 link 的 framework,首先找到项目模拟器路径下找到项目APP,如案例上用的是KVODemo


      路径
    • 找到后cd 路径下再通过otool命令:

    $ otool -L TestMain
    
    • 如下截图打印信息


      image.png

    除了多了的CoreGraphics(被 UIKit 依赖)外,有两个默认添加的 lib:libobjc 即 objc 和 runtime,libSystem 中包含了很多系统级别 lib,列几个熟知的:

    • libdispatch ( GCD )
    • libsystem_c ( C语言库 )
    • libsystem_blocks ( Block )
    • libcommonCrypto ( 加密库,比如常用的 md5 函数 )

    这些 lib 都是dylib格式(如 windows 中的 dll ),系统使用动态链接有几点好处:

    • 代码共用:很多程序都动态链接了这些 lib,但它们在内存和磁盘中中只有一份
    • 易于维护:由于被依赖的 lib 是程序执行时才 link 的,所以这些 lib 很容易做更新,比如libSystem.dylib 是 libSystem.B.dylib 的替身,哪天想升级直接换成 libSystem.C.dylib 然后再替换替身就行了
    • 减少可执行文件体积:相比静态链接,动态链接在编译时不需要打进去,所以可执行文件的体积要小很多

    dyld:动态连接器(the dynamic link editor)。系统kernel做好启动程序的初始准备之后,交给dyld负责。dyld在github上还是开源的,开源地址:dyld

    kernel对dyld的调用顺心如下:

    • 从 kernel 留下的原始调用栈引导和启动自己
    • 将程序依赖的动态链接库递归加载进内存,当然这里有缓存机制
    • non-lazy 符号立即 link 到可执行文件,lazy 的存表里
    • Runs static initializers for the executable
    • 找到可执行文件的 main 函数,准备参数并调用
    • 程序执行中负责绑定 lazy 符号、提供 runtime dynamic loading services、提供调试器接口
    • 程序main函数 return 后执行 static terminator
    • 某些场景下 main 函数结束后调 libSystem 的 _exit 函数

    ImageLoader: 用于辅助加载特定可执行文件格式的类,程序中对应实例可简称为image(如程序可执行文件,Framework库,bundle文件)。

    • 在程序运行时它先将动态链接的 image 递归加载 (也就是上面测试栈中一串的递归调用的时刻)。
    • 再从可执行文件 image 递归加载所有符号。

    main函数被调用:当所有一切都结束时,dyld会清理现场,将调用栈回归

    本条重度参考sunnyxx的博客iOS 程序 main 函数之前发生了什么

    相关文章

      网友评论

      本文标题:iOS面试题

      本文链接:https://www.haomeiwen.com/subject/juogxftx.html