美文网首页iOS面试相关iOS面试iOS 开发每天分享优质文章
2019年年初iOS招人心得笔记 答案 (一)

2019年年初iOS招人心得笔记 答案 (一)

作者: 摩卡奇 | 来源:发表于2019-10-08 13:57 被阅读0次

    这是题目
    http://www.cocoachina.com/cms/wap.php?action=article&id=26253

    技术基础

    1、我们说的Objective-C是动态运行时语言是什么意思?

    OC的动态特性表现为三个方面:动态类型、动态绑定、动态加载。

    (1)动态类型——id
    实际上静态类型因为其固定性和可预知性而得到更广泛的应用。静态类型是强类型,而动态类型属于弱类型,运行时决定接收者。

    这里解释一下强、弱类型:语言有无类型、强类型和弱类型三种。无类型的不做任何检查,甚至不区分指令和数据。弱类型的检查很弱,仅区分指令和数据。强类型在编译器进行严格检查,强类型语言在没有强制类型转化前,不允许两种不同类型的变量相互操作。

    (2)动态绑定——@selector/SEL
    让代码在运行的时候判断需要调用的方法,而不是在编译时。与其他面向对象语言一样,方法调用和代码并没有在编译时连接在一起啊,而是在消息发送时才进行链接。运行时决定调用哪个方法。

    (3)动态加载
    让程序在运行时添加代码模块和资源的时候,用户可以根据需要加载一些可执行代码和资源,而不是在启动时就加载所有组件。可执行代码中可以含有程序运行时整合的新类。

    2、讲一下MVC和MVVM,MVP?

    Model-View-Controller

    MVC的缺点:

    (1)越来越笨重的Controller

    控制器Controller是app的胶水代码,协调Model和VIew之间的交互。控制器负责管理视图层次结构,还要响应视图的loading、appearing、disappearing等等。同时往往也会充满我们不愿意暴露Model的模型逻辑和不愿意放在Controller的视图业务逻辑。

    在实际开发中,ViewController同时扮演了C和部分V的角色,显得非常庞大,其中包含了各种业务和视图层逻辑,不利于测试。


    Massive ViewController.png

    (2)太过于轻量化的Model

    Model层就是几个属性,ARC普及之后,m文件中基本看不到代码。同时和控制器代码越来越厚形成强烈反差

    (3)遗失的网络逻辑

    苹果使用的MVC的定义是这么说的:所有的对象都可以归类为Model、View、Controller。就这样的话,网络逻辑放置在哪里就很棘手。

    你可以试着把网络放在Model对象里,但是很棘手,因为网络调用应该使用异步,如果一个网络请求比持有它的Model生命周期更长,事情将变得复杂。显然也不应该把网络代码放在View里,因此只能放在控制器了。这也不是不能接收的,但是这样会加厚控制器。

    (4)较差的可测试性

    MVC的一个大问题是控制器混合了视图逻辑和业务逻辑,分离视图和业务并写单元测试成为一个艰巨的任务。大多数人选择不做任何测试。

    • MVP

    Model View Presenter(协调器)

    MVP

    MVP中的Presenter并没有对ViewController的生命周期做任何改变,因此View可以很容易测试。在Presenter中主要负责更新View的数据和状态,并没有布局相关的代码。

    MVP特点:

    • 职责拆分——我们将主要的任务分到Presenter和Model中,View的功能减少
    • 可测试——基于功能简单的View层,可以测试大部分业务逻辑
    • 易用性——MVP各代码结构职责清晰

    MVP优势:
    模型和视图完全分离,我们可以修改视图而不影响模型
    我们可以把逻辑放在Presenter中,那么我们可以脱离用户借口来测试这些逻辑

    • MVVM
    MVVM

    VIewModel是一个防止用户输入验证逻辑,视图展示逻辑,发起网络请求和其他各种各样代码的地方。

    3、为什么代理要用weak?代理的delegate和dataSource有什么区别?block和代理的区别?

    防止代理和被代理对象相互强引用造成内存泄漏。

    delegate主要负责view的交互 datasource主要负责提供数据

    block是一个对象,代理是一个设计模式。

    代理的好处:
    delegate运行成本低,block成本高。
    block出栈需要将使用的数据从栈内存拷贝到堆内存,使用完成后还需要销毁block。delegate只是保存了一个对象指针,直接回调,没有额外的消耗。相对于C,只多了一个查表的操作。

    block:
    写法简单,不需要写protocol、函数等等
    block需要防止循环引用

    什么时候用代理或者block
    公共借口,方法比较多可以选择用delegate进行解耦
    异步和简单的回调用block比较好

    4、属性的实质是什么?包括哪几个部分?属性默认的关键字都有哪些?@dynamic关键字和@synthesize关键字是用来做什么的?

    属性的实质是什么?包括哪几个部分?
    @property = ivar(实例变量) + getter + setter
    利用class_copyPropertyList查看类的属性
    利用class_copyIvarList查看类的所有成员变量
    利用class_copyMethodList查看类的所有方法

    属性默认的关键字都有哪些?

    • 原子性 nonatomic atomic。当自己没有指定原子性的时候,默认是atomic,并且在自己定义存取方法,应该遵守与属性特质相符的原子性。
    • 读写权限 readwrite readonly。
    • 内存管理语意 assign strong weak unsafe_unretained copy。assign只针对纯量类型(scalar type)的简单赋值操作。unsafe_unretained中当目标对象销毁之后属性只不会清空。
    • 方法名 setter getter。

    @dynamic关键字和@synthesize关键字是用来做什么的?

    • @synthesize可以用来指定实例变量的名称
    • @dynamic告诉编译器不要自动生成属性所用的实例变量,也不要为其创建存取方法,自己进行手动管理。

    5、属性的默认关键字是什么?

    • 原子性 默认是atomic
    • 读写权限 默认是readwrite
    • 内存管理语意 纯量变量是assign 对象是strong
    • 方法名 无

    6、NSString为什么要用copy关键字,如果用strong会有什么问题?(注意:这里没有说用strong就一定不行。使用copy和strong是看情况而定的)

    主要是防止属性被污染,当把一个可变(nsmutable)对象赋值给strong的属性时,改变对象可能导致属性发生改变。

    如果想在model内直接实时反应数据的变化就使用strong,要防止数据更改变化使用copy。

    7、如何令自己所写的对象具有拷贝功能?

    自定义对象分为可变版本和不可变版本,需要同时实现NSCopying和NSMutablecopying协议

    @protocol NSCopying
    
    - (id)copyWithZone:(nullable NSZone *)zone;
    
    @end
    
    @protocol NSMutableCopying
    
    - (id)mutableCopyWithZone:(nullable NSZone *)zone;
    
    @end
    

    8、简述kvo、kvc、Delegate他们之间的区别?

    KVC(key value coding)键值编码,iOS开发中,可以允许开发者通过key名直接访问对象属性,或者给对象的属性赋值。而不需要调用明确的存取方法。这样就可以在运行时动态的访问和修改对象的属性。而不是编译时确定。

    KVC的定义都是对NSObject的扩展来实现的,OC中有个显式的NSKeyValueCoding类别名,所以对于所有继承了NSObject的类型,都能使用KVC(一些纯swift类和结构体是不支持KVC的,因为没有继承NSObject),下面是KVC最为重要的四个方法:

    - (nullable id)valueForKey:(NSString *)key;                          //直接通过Key来取值
    
    - (void)setValue:(nullable id)value forKey:(NSString *)key;          //通过Key来设值
    
    - (nullable id)valueForKeyPath:(NSString *)keyPath;                  //通过KeyPath来取值
    
    - (void)setValue:(nullable id)value forKeyPath:(NSString *)keyPath;  //通过KeyPath来设值
    

    NSKeyValueCoding类别中其他的一些方法:

    + (BOOL)accessInstanceVariablesDirectly;
    //默认返回YES,表示如果没有找到Set<Key>方法的话,会按照_key,_iskey,key,iskey的顺序搜索成员,设置成NO就不这样搜索
    
    - (BOOL)validateValue:(inout id __nullable * __nonnull)ioValue forKey:(NSString *)inKey error:(out NSError **)outError;
    //KVC提供属性值正确性验证的API,它可以用来检查set的值是否正确、为不正确的值做一个替换值或者拒绝设置新值并返回错误原因。
    
    - (NSMutableArray *)mutableArrayValueForKey:(NSString *)key;
    //这是集合操作的API,里面还有一系列这样的API,如果属性是一个NSMutableArray,那么可以用这个方法来返回。
    
    - (nullable id)valueForUndefinedKey:(NSString *)key;
    //如果Key不存在,且没有KVC无法搜索到任何和Key有关的字段或者属性,则会调用这个方法,默认是抛出异常。
    
    - (void)setValue:(nullable id)value forUndefinedKey:(NSString *)key;
    //和上一个方法一样,但这个方法是设值。
    
    - (void)setNilValueForKey:(NSString *)key;
    //如果你在SetValue方法时面给Value传nil,则会调用这个方法
    
    - (NSDictionary<NSString *, id> *)dictionaryWithValuesForKeys:(NSArray<NSString *> *)keys;
    //输入一组key,返回该组key对应的Value,再转成字典返回,用于将Model转到字典。
    

    KVO 即 Key-Value Observing,翻译成键值观察。它是一种观察者模式的衍生。其基本思想是,对目标对象的某属性添加观察,当该属性发生变化时,通过触发观察者对象实现的KVO接口方法,来自动的通知观察者。

    简单来说KVO可以通过监听key,来获得value的变化,用来在对象之间监听状态变化。KVO的定义都是对NSObject的扩展来实现的,OC中有个显式的NSKeyValueObserving类别名,所以对于所有继承了NSObject的类型,都能使用KVO(纯swift类和结构体是不支持KVC的,因为没有继承NSObject)。

    KVO的实现依赖于Runtime的强大动态能力。

    即当一个类型为ObjectA的对象,被添加观察后,系统会生成一个NSKVONotifying_ObjectA类,并将对象的isa指针指向新的类,也就是说这个对象的类型发生了变化。这个类相对于ObjectA,会重写

    setter方法

    - (void)willChangeValueForKey:(NSString *)key;
    - (void)didChangeValueForKey:(NSString *)key;
    

    然后在didChangeValueForKey:中,去调用:

    - (void)observeValueForKeyPath:(nullable NSString *)keyPath
                          ofObject:(nullable id)object
                            change:(nullable NSDictionary<NSKeyValueChangeKey, id> *)change
                           context:(nullable void *)context;
    

    包括了新值和旧值的通知。因为KVO的原理是修改setter方法,因此使用KVO必须调用setter。若直接访问属性对象则没有效果。

    重写class

    当修改了isa指向后,class的返回值不会变,但isa的值则发生改变。

    重写dealloc

    系统重写dealloc方法释放资源

    重写_isKVOA

    这个私有方法是用来标示该类是一个KVO机制声称的类。

    Delegate

    顾名思义委托别人办事,就是当一件事情发生之后,自己不去处理,让被人来处理。

    一般委托者需要做的事:

    1. 创建协议(也就是代理需要实现的方法)
    2. 声明委托变量(也就是delegate属性)
    3. 设置代理(也可以在代理对象中设置)
    4. 利用委托变量调用协议方法(也就是让代理着开始执行协议)

    代理对象需要做的事:

    1. 遵守协议
    2. 实现协议方法

    9、#include与#import的区别?#import与@class的区别?

    import可以防止#include引起的循环引用

    import会包含这个类的全部信息,包括实体变量和方法(.h文件),而@class只能告诉编译器,其后面声明的名称是类的名称,至于这个类是如何定义的,后面会有。所以一般在头文件中使用@class来声明这个名称,在类实现里面,使用#import引用实体变量和方法。

    10、nonatomic和atomic的区别?atomic是绝对的线程安全么?为什么?如果不是,那应该如何实现?

    atomic:原子属性,多线程写入属性时,同一时间只能有一个线程能够写入操作。
    atomic 内部有一个互斥锁

    什么是自旋锁(spin)和互斥锁(mutex)

    锁用于解决线程争夺资源的问题,一般分为两种,自旋锁和互斥锁。

    互斥锁可以解释为线程获取锁,发现锁被占用,就向系统申请锁空闲时唤醒他并立刻休眠。

    自旋锁比较简单,当线程发现锁被占用时,会不断循环判断锁的状态,直到获取。

    原子操作的颗粒度最小,只限于读写,对于性能的要求很高,如果使用了互斥锁势必在切换线程上耗费大量资源。相比之下,由于读写操作耗时比较小,能够在一个时间片内完成,自旋更适合这个场景。

    自旋锁的坑

    但是iOS 10之后,苹果因为一个巨大的缺陷弃用了 OSSpinLock 改为新的 os_unfair_lock。

    新版 iOS 中,系统维护了 5 个不同的线程优先级/QoS: background,utility,default,user-initiated,user-interactive。高优先级线程始终会在低优先级线程前执行,一个线程不会受到比它更低优先级线程的干扰。这种线程调度算法会产生潜在的优先级反转问题,从而破坏了 spin lock。

    描述引用自 ibireme 大神的文章。
    https://blog.ibireme.com/2016/01/16/spinlock_is_unsafe_in_ios/

    我的理解是,当低优先级线程获取了锁,高优先级线程访问时陷入忙等状态,由于是循环调用,所以占用了系统调度资源,导致低优先级线程迟迟不能处理资源并释放锁,导致陷入死锁。

    当使用atomic时,虽然对属性的读和写是原子性的,但是仍然可能出现线程错误:当线程A进行写操作,这时其他线程的读或者写操作会因为等该操作而等待。当A线程的写操作结束后,B线程进行写操作,所有这些不同线程上的操作都将依次顺序执行——也就是说,如果一个线程正在执行 getter/setter,其他线程就得等待。如果有线程C在A线程读操作之前release了该属性,那么还会导致程序崩溃。所以仅仅使用atomic并不会使得线程安全,我们还要为线程添加lock来确保线程的安全。

    更准确的说应该是读写安全,但并不是线程安全的,因为别的线程还能进行读写之外的其他操作。线程安全需要开发者自己来保证。

    其实无论是否是原子性的只是针对于getter和setter而言,比如用atomic去操作一个NSMutableArray ,如果一个线程循环读数据,一个线程循环写数据,肯定会产生内存问题,这个就跟getter和setter就木有关系了。

    这里写一下在MRC下的setter/getter代码实现

    static NSString *const objLock = @"objLock";
    
    @interface ViewController ()
    @property (retain, atomic) NSObject *obj;
    @end
    
    @implementation ViewController
    - (void)setObj:(NSObject *)obj {
        @synchronized(objLock) {
            [obj retain];
            [_obj release];
            _obj = obj;
        }
    }
    
    - (NSObject *)obj {
        @synchronized(objLock) {
            return _obj;
        }
    }
    @end
    
    为什么不能保证绝对的线程安全?

    单独的原子操作绝对是线程安全的,但是组合一起的操作就不能保证。

    - (void)competition {
        self.intSource = 0;
    
        dispatch_async(queue1, ^{
          for (int i = 0; i < 10000; i++) {
              self.intSource = self.intSource + 1;
          }
        });
    
        dispatch_async(queue2, ^{
          for (int i = 0; i < 10000; i++) {
              self.intSource = self.intSource + 1;
          }
        });
    }
    

    最终得到的结果肯定小于20000。当获取值的时候都是原子线程安全操作,比如两个线程依序获取了当前值 0,于是分别增量后变为了 1,所以两个队列依序写入值都是 1,所以不是线程安全的。

    解决的办法应该是增加颗粒度,将读写两个操作合并为一个原子操作,从而解决写入过期数据的问题。

    os_unfair_lock_t unfairLock;
    - (void)competition {
        self.intSource = 0;
    
        unfairLock = &(OS_UNFAIR_LOCK_INIT);
        dispatch_async(queue1, ^{
          for (int i = 0; i < 10000; i++) {
              os_unfair_lock_lock(unfairLock);
              self.intSource = self.intSource + 1;
              os_unfair_lock_unlock(unfairLock);
          }
        });
    
        dispatch_async(queue2, ^{
          for (int i = 0; i < 10000; i++) {
              os_unfair_lock_lock(unfairLock);
              self.intSource = self.intSource + 1;
              os_unfair_lock_unlock(unfairLock);
          }
        });
    }
    

    参考文章
    https://www.jianshu.com/p/740ec0c85e97
    https://blog.ibireme.com/2016/01/16/spinlock_is_unsafe_in_ios/

    相关文章

      网友评论

        本文标题:2019年年初iOS招人心得笔记 答案 (一)

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