美文网首页iOS开发
iOS 面试题<基础篇(一)>——精心整理 (持续更新...)

iOS 面试题<基础篇(一)>——精心整理 (持续更新...)

作者: Just丶Go | 来源:发表于2018-03-11 12:21 被阅读0次

    2018/3/29 完善(2)消息机制的转发流程<增加转发前的方法验签过程>
    2018/3/29 验证(3)weak、assign的区别
    2018/3/29 验证(4)__block修饰外部基本变量与未修饰的区别
    2018/6/29 改进 (8) 加入深拷贝、浅拷贝的理解
    2018/6/29 修正 (7) 原问题错误。造成误导
    2018/7/2 增加 (11)内存管理答案

    1.为什么说Objective-C是一门动态语言

    1.Objective-C是一门运行时语言。在编译阶段不确定所有的东西,在运行时,根据运行环境确定对象的类型,对象对应类的方法实现等。
    2.它的动态主要体现在3个方面,动态类型、动态绑定、动态加载
    3.动态类型如id(弱类型);
      动态绑定(将方法调用的时机推迟到运行时,在编译阶段,方法的调用不和代码绑定。只有消息发送出来之后,才会确定调用的代码);
      动态加载(如动态资源加载,@2x、@3x)
    

    2.什么是消息机制

    在Objective-C中,方法调用是一个消息发送的过程(在java,C++等静态语言中是函数调用)。
    OC对象发送一个消息的过程如下:
    1.向对象发送消息
    2.在缓存中查找是否有匹配方法。如果有则响应,否则继续
    3.在对象的类的方法列表中(method list)查找是否有匹配方法,如果有则响应,否则继续
    4.在对象的父类的缓存中查找是否有匹配方法。如果有则响应,否则继续
    5.在对象的父类的方法列表中(method list)查找是否有匹配方法。如果有则响应,否则继续
    6.进入动态解析 -(BOOL)resolveInstanceMethod:(SEL)sel;- (BOOL)resolveClassMethod:(SEL)sel;查看是否有动态添加方法实现。如果有则响应,否则继续
    7.进入消息重定向 -(id)forwardingTargetForSelector:(SEL)sel;如果有指定消息接收对象则响应,否则(指:返回nil或者self)继续
    8.进入方法签名- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector返回方法签名并进入下一步,否则调用doesNotRecongnizeSelector:方法并抛出异常。
    9.进入消息转发 - (void)forwardInvocation:(NSInvocation *)aInvocation;如果有指定消息转发对象则响应,否则调用doesNotRecongnizeSelector:方法并抛出异常。
    

    3.说下属性修饰词 copy、strong、weak、assign

    copy: copy关键字的作用是产生一份副本,断开与源对象的关联。即源对象的修改不影响副本对象。从而避免在无意情况下修改了源对象,而造成副本对象的改变。
    copy只能对遵守NSCopying、NSMutableCopying协议的对象进行操作。
    copy不可变对象,是对源对象的引用计数+1,进行一次强引用。即浅拷贝;拷贝后的对象也是不可变对象。
    copy可变对象,会重新开辟一份内存空间,存放源对象存储的值。即深拷贝;拷贝后的对象是不可变对象。
    copy产生的对象都是不可变对象;mutableCopy产生的对象都是可变对象。
    
    strong和weak修饰的对象都指向源对象值的地址。源对象值修改,也随之修改。区别是前者会对原对象的引用计数+1,防止原对象被释放;后者不会,当原对象引用计数为0时被释放时,weak的值也为nil了。
    weak和assign都是弱引用一个对象,不会对源对象的引用计数产生影响(+1)。但是当源对象的引用计数为0时,assign修饰的对象会产生野指针,原因是assign虽然不会对源对象的引用计数+1,但指针仍然指向源对象。
    而weak修饰的对象的指针会随源对象的销毁而销毁。
    
    retain与strong相同。retain用于MRC,strong用于ARC。
    
    

    weak,assign修饰的属性验证

    图片.png
    图片.png
    图片.png

    4.block的使用及它的存储域问题

    block是OC对象,继承自NSBlock类。
    block可能会存储在全局数据区(_NSConcreteGlobalBlock)、堆区(_NSConcreteMallocBlock)、栈区(_NSConcreteStackBlock)。
    在ARC环境下,block声明既可以用copy也可以用strong。使用copy是在MRC时代遗留的习惯问题。因为在ARC时,编译器会自动将block从栈区拷贝到堆区。避免在使用时,已被释放。
    weak属性经常配合block使用,避免循环引用问题。<注:weak修饰符在iOS 5引入,只能在ARC环境下使用>
    __block修饰基本数据类型。可以使其在block内部进行修改其值。原因从下图源码中可以看出,正常定义一个变量 a 时, block的实现中 是与外部变量 a 的定义结构相同;当使用__block修饰时,a会变为一个指针,使用指针引用外部变量 a 的地址。
    
    图片.png

    这是MyBlock的源码实现

    图片.png
    图片.png
    最后附一篇介绍block及__block的文章

    5.MVC设计模式

    MVC是一种架构模式,M表示MOdel,V表示视图View,C表示控制器Controller:
    
    Model负责存储、定义、操作数据;
    
    View用来展示数据给用户,和用户进行操作交互;
    
    Controller是Model和View的协调者,Controller把Model中的数据拿过来给View用。
    Controller可以直接与Model和View进行通信,而View不能和Controller直接通信。
    View与Controller通信需要利用代理协议的方式,当有数据更新时,MOdel也要与Controller进行通信,这个时候就要用Notification和KVO,这个方式就像一个广播一样,MOdel发信号,
    Controller设置监听接受信号,当有数据更新时就发信号给Controller,Model和View不能直接进行通信,这样会违背MVC设计模式。
    

    6.在定义 property 的时候,atomic 和 nonatomic 有何区别?

    atomic 和 nonatomic 的区别在于,系统自动生成的getter/setter 方法不一样。如果你自己写 
    getter/setter,那 atomic/nonatomic/retain/assign/copy 这些关键字只起提示作用,
    写不写都一样。
    
    对于atomic的属性,系统生成的 getter/setter 会保证 get、set 操作的完整性,不受其他线程影响。
    比如,线程 A 的 getter 方法运行到一半,线程 B 调用了 setter:那么线程 A 的 getter 还是能
    得到一个完好无损的对象。
    
    而nonatomic就没有这个保证了。所以,nonatomic的速度要比atomic快。
    
    不过atomic可并不能保证线程安全。如果线程 A 调了 getter,与此同时线程 B 、线程 C 都调了 
    setter——那最后线程 A get 到的值,3种都有可能:可能是 B、C set 之前原始的值,也可能是 B set 
    的值,也可能是 C set 的值。同时,最终这个属性的值,可能是 B set 的值,也有可能是 C set 的
    值。
    
    转自链接:https://www.jianshu.com/p/7288eacbb1a2
    
    
    

    简单验证如下:

    - (void)gcdGroup
    {
        
        /// init property name
        self.name = @"origin";
        dispatch_group_t group = dispatch_group_create();
        dispatch_queue_t queue = dispatch_queue_create("com.bonlion.queue", DISPATCH_QUEUE_CONCURRENT);
        
        /// thread A
        dispatch_group_async(group, queue, ^{
            self.name = @"A";
            NSLog(@"thread A->%@", [NSThread currentThread]);
        });
        /// thread B
        dispatch_group_async(group, queue, ^{
            self.name = @"B";
            NSLog(@"thread B->%@", [NSThread currentThread]);
        });
        /// thread C
        dispatch_group_async(group, queue, ^{
            NSLog(@"name value is ->%@", self.name);
            NSLog(@"thread C->%@", [NSThread currentThread]);
        });
        
        dispatch_group_notify(group, queue, ^{
            NSLog(@"notify thread is used");
        });
    }
    
    image.png
    上图结果证明:在多线程情况下确实可以保证数据存取的完整性,但是并不能保证可以得到你想要的结果。此处可以使用GCD的信号量dispatch_semaphore来上锁,确保得到想要的结果
    

    7.用copy修饰的NSMutableString、NSMutableArray、NSMutableDictionary类型的属性,如果改用strong修饰,可能会造成什么结果?
    错误问题:(用@property声明的 NSString / NSArray / NSDictionary 经常使用 copy 关键字,为什么?如果改用strong关键字,可能造成什么问题?)

    答:用copy修饰可变对象。会开辟一份新的内存空间,存放不可变类型,存储(源对象所存储)的值。从而避免源对象值的改变影响该对象的值(即产生一个副本对象)。
    若使用strong修饰可变对象。即对源对象的引用计数+1。该对象的指针指向源对象的内存地址。当源对象值改变时,该对象的值也会随之改变。从而造成取值错误。
    

    8.系统对象的 copy 与 mutableCopy 方法

    不管是集合类对象(NSArray、NSDictionary、NSSet ... 之类的对象),还是非集合类对象
    (NSString, NSNumber ... 之类的对象),接收到copy和mutableCopy消息时,都遵循以下准则:
    1. copy 返回的是不可变对象(immutableObject);如果用copy返回值调用mutable对象的方法就会crash。
    2. mutableCopy 返回的是可变对象(mutableObject)。
    3. 只有对不可变对象进行copy操作是指针复制(浅复制),其它情况都是内容复制(深复制)!
    
    何谓深拷贝、浅拷贝:区别在于深拷贝是重新开辟一块内存空间,指针指向新这块新的内存地址;
    浅拷贝不开辟新的内存空间,指针还是指向源对象的内存地址
    

    9.KVC底层实现

    当一个对象调用setValue:forKey: or valueForKey:方法时,方法内部会做以下操作:
    1). 检查是否存在相应的 key 的setter or getter方法,如果存在,就调用setter or getter方法。
    2). 如果setter or getter 方法不存在,就会查找与 key 相同名称并且带下划线的成员变量,
         如果有,则直接给成员变量属性赋值 or 取值。
    3). 如果没有找到_key,就会查找相同名称的属性key,如果有就直接赋值 or 取值。
    4). 如果还没有找到,则调用 setValue:forUndefinedKey: 和valueForUndefinedKey: 方法。
    以上两个方法的默认实现都是抛出异常,我们可以根据需要进行重写。
    

    10.KVO的原理
    附一份KVO简单实现——有详细注释
    KVO深层次的讲解
    facebook的自释放KVO

    基本概念:
    KVO又称键(key)值(value)监听(observing)。
    KVO的底层实现依赖于强大的runtime库。
    KVO通过addObserver:forKeyPath:options:context:添加观察者对象(observer),观察目标对象
    的某个属性(key)的新值或者旧值(options)的改变,关键字(context)可以作为区分使用 的方式实现值改变的回调机制。
    
    使用场景:
    1.一般使用于对scrollView的偏移量(contentOffset)值改变的监听。
    2.商城购物车中改变商品数量,更新总价
    
    底层原理:
    在注册观察者之后,编译器会通过runtime的API动态创建注册一个中间类(派生类),继承自原本类(父类),然后将父类的 isa 指针指向这个子类(中间类)。同时,为子类动态添加了设置(setter)方法。
    随后在 setter 方法中实现成对出现的 willChangeValueForKey: 与 didChangeValueForKey: 方法及父类的 setter 方法。然后再在didChangeValueForKey:方法中实现监听者的 observeValueForKeyPath: 方法,实现监听属性改变的过程。
    
    

    大致过程如下:

    - (void)viewDidLoad {
        [super viewDidLoad];
        
        Person *p = [[Person alloc] init];
        
        // 表明观察策略--> 观察新值 与 旧值的改变
        NSKeyValueObservingOptions options = NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld;
        // 注册观察者,监听者为当前控制器,监听对象为 p。
        [p addObserver:self forKeyPath:@"age" options:options context:@"as you like"];
        
    }
    
    - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context
    {
        NSLog(@"\nkeyPath->%@\nchange->%@\ncontext->%@", keyPath, change, context);
    }
    
    图片.png
    断点之后,发现使用runtime获取到的p对象所属的类为 NSKVONotifying_Person
    中间类的setter方法实现大致如下
    图片.png
    PS:附->可以通过runtime得知,中间类的类名及其对应的方法列表的方法名。中间类只有setter方法,同时还有一个 class 方法,说明它重写了NSObject类的class方法。为什么这么做呢?因为它内部的实现大致这样- (Class)class{return object_getClass(class_getSuperclass(self));}
    就可以做到过程欺骗。因为你使用[p class]方法得到的类还是Person类,除非使用runtime API 才能获得到真实类。
    图片.png

    11.iOS是如何管理内存的

    在iOS中,使用引用计数来管理OC对象的内存。
    一个新创建的对象引用计数是1,当引用计数为0时,OC对象就会销毁,释放其占用的内存。
    调用retain会使引用计数+1,调用release会使引用计数-1。
    内存管理经验总结:
        当调用alloc、new、copy、mutableCopy方法时都会返回一个对象,在不需要这个对象时,要调用release或autorelease使其引用计数-1。
        想要拥有某个对象就使其引用计数+1,不需要某个对象就使其引用计数-1。
        
    

    相关文章

      网友评论

        本文标题:iOS 面试题<基础篇(一)>——精心整理 (持续更新...)

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