美文网首页iOS 基础iOS学习
iOS基础面试题及答案

iOS基础面试题及答案

作者: 凯文Kevin21 | 来源:发表于2018-03-08 15:32 被阅读152次

    考察一个面试者基础咋样,基本上问一个 @property 就够了:

      1. @property 后面可以有哪些修饰符?
        答 :
        线程安全的: atomic, nonatomic
        访问权限的: readonly, readwrite
        内存管理(ARC) assign,strong(强引用,指针拷贝),weak(弱引用),copy(内容拷贝)
        内存管理(MRC)assign, retain(强引用,指针拷贝),copy(内容拷贝)
      1. 什么情况使用 weak 关键字,相比 assign 有什么不同?
        UI控件使用weak的原因:
    UI控件之所以可以使用弱指针,是因为控制器有强指针指向UIView  
    UIView 有强指针指向Subviews数组,数组中由强指针指向控件
    

    代理必须使用weak的原因:

    代理必须是weak,因为代理一般都是指向控制器,会造成循环引用,无法释放,造成内存泄露
    

    weak 和 assign的区别:

    相同点: 
    assign和weak不会牵扯到内存管理,不会增加引用计数 
    不同点: 
    assign 可修饰基本数据类型 也可修饰OC对象, 但如果修饰对象类型指向的是一个强指针, 当他指向的这个指针释放后, 他仍指向这块内存,必须手动给其置为nil, 否则就会产生野指针,如果还通过此指针操作那块内存,便会导致EXC_BAD_ACCESS错误,调用了已经释放的内存空间; 而weak 只能修饰OC对象, 且相对assign比较安全, 如果指向的对象消失了,那么他会自动置为nil,不会产生野指针.
    
    在ARC,出现循环引用的时候,必须有一端使用weak
    weak修饰的对象销毁的时候,指针会自动设置为nil
    而assign不会,assign可以用于非OC对象,而weak必须用于OC对象
    
    
      1. 关键字copy和strong的区别,如何使用它们?
        NSString、NSArray、NSDictionary常用copy,为什么不用strong?
    strong是强引用,指向的是同一个内存地址,copy是内容拷贝,会另外开辟内存空间,指针指向一个不同的内存地址,copy返回的是一个不可变对象,如果使用strong修饰可变对象,那么对象就会有可能被不经意间修改,有时不是我们想要的,而copy不会发生这种意外。
    

    copy、strong 和 可变、不可变类型, 先看下面这张图:


    property.png
    1) copy 和 strong 都可修饰不可变类型,但一般用copy 
    一般用copy修饰不可变的, 因为安全, 可以保证其封闭性. 
    因为用copy修饰,setter方法中会自动判断如果来源,如果是不可变的,那和Strong一样,进行浅拷贝,会增加其引用计数,如果是可变的那么就深拷贝,不会增加其引用计数. 所以如果如果项目中这样的不可变对象(比如NSString)多的话,当一定数量if判断消耗的时间累加起来就会影响性能. 
    所以,只需要记住一点,当你给你的不可变对象 赋值时, 如果来源是可变的,那么就用copy, 如果来源是不可变类型的,就用strong.
    
    注:如果当strong修饰不可变的, 如果来源是不可变得,那么同上,没有问题. 如果来源是可变的时, 那么当源对象变化时,我们的不可变属性也会跟着变化,那么就破坏了其封闭性, 就不安全了.
    
    
    2) 如果用 copy 修饰 可变类型 会出现什么问题? 
    Copy 修饰 可变的对象的话, 会生成一个不可变的NSCFConstantSting对象,赋值给可变属性,编译没问题, 调方法修改其内容时崩溃. unrecognized selector sent to instance
    
    
    总结 
    1、copy 修饰 不可变的 要看赋值来源 
    (1)来源是可变的话, 会自动进行深拷贝, 来源对象的变化不会影响我们的不可变属性 
    (2)来源是不可变的话,那么就和strong一样大胆的指针拷贝,反正都不会改变. 
    2、copy 修饰可变的. 
    那么会生成一个不可变对象,赋值给可变属性,编译没问题,调方法修改其内容时会崩溃unrecognized selector sent to instance 
    3、 Strong修饰不可变的 也要看来源 
    (1)如果来源是不可变的, 那就没什么问题 
    (2)如果来源是可变的, 那么当源对象的内容发生改变时,我们的不可变属性的值也会发生改变,那么就破坏的其封闭性, 不安全. 
    4、strong修饰可变的 也要看来源 
    (1)如果来源是不可变的, 那么会直接报警告运行出错 unrecognized selector sent to instance 
    (2) 如果来源是可变的,那么没问题.
    
      1. 这个写法会出什么问题: @property (copy) NSMutableArray *array;
        那么会生成一个不可变对象,赋值给可变属性,编译没问题,调方法修改其内容时会崩溃unrecognized
      1. 如何让自己的类用 copy 修饰符?如何重写带 copy 关键字的 setter?
    若想令自己所写的对象具有拷贝功能,则需实现 NSCopying 协议。如果自定义的对象分为可变版本与不可变版本,那么就要同时实现 NSCopying 与 NSMutableCopying 协议。
    这一套问题区分度比较大,如果上面的问题都能回答正确,可以延伸问更深入点的:
    
    **具体步骤:
    
    1.需声明该类遵从 NSCopying 协议
    实现 NSCopying 协议。该协议只有一个方法:
    
    - (id)copyWithZone:(NSZone *)zone;
    注意:一提到让自己的类用 copy 修饰符,我们总是想覆写copy方法,其实真正需要实现的却是 “copyWithZone” 方法。
    

    至于如何重写带 copy 关键字的 setter这个问题,

    如果抛开本例来回答的话,如下:
    - (void)setName:(NSString *)name {
        //[_name release];
        _name = [name copy];
    }
    

    参考答案-孙源博客

      1. @property 的本质是什么?ivar、getter、setter 是如何生成并添加到这个类中的
        1.@property的本质是什么
    @property的本质 = ivar (实例变量) + getter (取方法) + setter (存方法)
    
    “属性”(property)有两大概念:实例变量(ivar)、存取方法(getter + setter)
    
     2、ivar、 getter 、setter 是如何生成并添加到这个类中的
    
    这是编译器自动合成的,通过@synthesize 关键字指定,若不指定,默认为@synthesize  propertyName = _propertyName;若手动实现了getter/setter 方法,则不会自动合成。
    
    现在编译器已经默认为我们添加了@synthesize  propertyName = _propertyName;因此不再手动添加了,除非你真的要改变成员变量名字。
    
    生成getter方法时,会判断当前属性名是否有“_”,比如声明属性为@property(nonatomic,copy)NSString *_name;那么所生成的成员变量名就会变成“_name”,如果我们要手动生成getter 方法,就要判断是否以“_”开头了。
    
      1. @protocol 和 category 中如何使用 @property
    1)   在protocol中视同property只会生成setter getter方法声明,我们使用属性的目的是希望遵守我协议的对象能实现该属性
    
    2)   在category使用@property也是只会生成setter,getter方法的声明,如果我们挣得需要给category增加属性的实现,需要借助于运行时的两个函数
    
    objc_setAssociatedObject
    objc_getAssociatedObject
    
      1. runtime 如何实现 weak 属性
    runtime 对注册的类, 会进行布局,对于 weak 对象会放入一个 hash 表中。 用 weak 指向的对象内存地址作为 key,当此对象的引用计数为 
    0 的时候会 dealloc,假如 weak 指向的对象内存地址是 a ,那么就会以 a 为键, 在这个 weak 表中搜索,找到所有以 a 为键的 weak 
    对象,从而设置为 nil 。
    
      1. weak属性需要在dealloc中置nil么?

    不需要,在 ARC 环境下,无论是强指针还是弱指针都不需要在 dealloc 中置为 nil ,ARC 会自动处理的。

    • 10.@synthesize和@dynamic分别有什么作用?
    1. @property有两个对应的词,一个是 @synthesize,一个是 @dynamic。如果 @synthesize和 @dynamic都没写,那么默认的就是@syntheszie var = _var;
    
    2.@synthesize 的语义是如果你没有手动实现 setter 方法和 getter 方法,那么编译器会自动为你加上这两个方法。
    
    3. @dynamic 告诉编译器:属性的 setter 与 getter 方法由用户自己实现,不自动生成。(当然对于 readonly 的属性只需提供 getter 即可)。假如一个属性被声明为 @dynamic var,然后你没有提供 @setter方法和 @getter 方法,编译的时候没问题,但是当程序运行到 instance.var = someVar,由于缺 setter 方法会导致程序崩溃;或者当运行到 someVar = var 时,由于缺 getter 方法同样会导致崩溃。编译时没问题,运行时才执行相应的方法,这就是所谓的动态绑定。
    
    • 11.ARC下,不显式指定任何属性关键字时,默认的关键字都有哪些?
      atomic,readwrite,strong(对象),assgin(基本数据类型)。

    *12. 用@property声明的NSString(或NSArray,NSDictionary)经常使用copy关键字,为什么?如果改用strong关键字,可能造成什么问题?

    这个问题无非就是考察你对copy、strong这个两个修饰符的理解。简单来讲,strong是强引用,仍旧指向同一个内存地址;copy是内容拷贝,会另外开辟一个内存空间来存储被拷贝的内容,指针指向了一个不同的内存地址。注意,copy返回的是一个不可变对象。如果用strong修饰可变对象,那么这个对象就会有可能在不经意间被修改,有时候这并不是我们的想要看到的,而用copy便不会有这种意外发生了。
    

    *13. @synthesize合成实例变量的规则是什么?假如property名为foo,存在一个名为_foo的实例变量,那么还会自动合成新变量么?

    @synthesize表示由编译器来自动实现属性的getter/setter方法,不需要你自己再手动去实现。默认情况下,不需要指定实例变量的名称,编译器会自动生成一个属性名前加“_”的实例变量。当然也可以在实现代码里通过@synthesize语法来指定实例变量的名字。比如@synthesize foo = oof(oof为你想要定义的名称)。
    如果property名为foo并且存在一个名为_foo的实例变量,编译器便不会为我们自动合成新变量了。下面有一段代码大家可以试一下帮助更好的理解。

    @interface ViewController : UIViewController
    @property (copy, nonatomic) NSString *testA;
    @property (copy, nonatomic) NSString *testB;
    @end
    
    @interface ViewController ()
    {
        NSString *_testA;
        NSString *_testB;
    }
    @end
    
    @implementation ViewController
    
    @synthesize testB = testBBBBB;
    
    - (void)viewDidLoad {
        [super viewDidLoad];
        
        self.testA = @"1111";
        self.testB = @"1111";
        //输出结果为:self.testA = 1111,_testA = 1111,self.testB = 1111,testBBBBB = 1111,_testB = (null)
        NSLog(@"self.testA = %@,_testA = %@,self.testB = %@,testBBBBB = %@,_testB = %@",self.testA,_testA,self.testB,testBBBBB,_testB);
        
        _testA = @"2222222";
        _testB = @"2222222";
        //输出结果为:self.testA = 2222222,_testA = 2222222,self.testB = 1111,_testB = 2222222,testBBBBB = 1111
        NSLog(@"self.testA = %@,_testA = %@,self.testB = %@,_testB = %@,testBBBBB = %@",self.testA,_testA,self.testB,_testB,testBBBBB);
        
        testBBBBB = @"333333";
        //输出结果:self.testB = 333333,testBBBBB = 333333,_testB =2222222
        NSLog(@"self.testB = %@,testBBBBB = %@,_testB =%@",self.testB,testBBBBB,_testB);
    

    *14. objc中向一个nil对象发送消息将会发生什么?
    在 Objective-C 中向 nil 发送消息是完全有效的——只是在运行时不会有任何作用:

    (1). 如果一个方法返回值是一个对象,那么发送给nil的消息将返回0(nil)。例如:
    Person * motherInlaw = [[aPerson spouse] mother];
    (2). 如果方法返回值为指针类型,其指针大小为小于或者等于sizeof(void*),float,double,long double 或者 long long 的整型标量,发送给 nil 的消息将返回0。
    (3). 如果方法返回值为结构体,发送给 nil 的消息将返回0。结构体中各个字段的值将都是0。
    (4). 如果方法的返回值不是上述提到的几种情况,那么发送给 nil 的消息的返回值将是未定义的。

    具体原因如下:

    objc是动态语言,每个方法在运行时会被动态转为消息发送,即:objc_msgSend(receiver, selector)。

    那么,为了方便理解这个内容,还是贴一个objc的源代码:

    // runtime.h(类在runtime中的定义)
    // http://weibo.com/luohanchenyilong/
    // https://github.com/ChenYilong
    
    struct objc_class {
        Class isa OBJC_ISA_AVAILABILITY; //isa指针指向Meta Class,因为Objc的类的本身也是一个Object,为了处理这个关系,runtime就创造了Meta Class,当给类发送[NSObject alloc]这样消息时,实际上是把这个消息发给了Class Object
    #if !__OBJC2__
        Class super_class OBJC2_UNAVAILABLE; // 父类
        const char *name OBJC2_UNAVAILABLE; // 类名
        long version OBJC2_UNAVAILABLE; // 类的版本信息,默认为0
        long info OBJC2_UNAVAILABLE; // 类信息,供运行期使用的一些位标识
        long instance_size OBJC2_UNAVAILABLE; // 该类的实例变量大小
        struct objc_ivar_list *ivars OBJC2_UNAVAILABLE; // 该类的成员变量链表
        struct objc_method_list **methodLists OBJC2_UNAVAILABLE; // 方法定义的链表
        struct objc_cache *cache OBJC2_UNAVAILABLE; // 方法缓存,对象接到一个消息会根据isa指针查找消息对象,这时会在method Lists中遍历,如果cache了,常用的方法调用时就能够提高调用的效率。
        struct objc_protocol_list *protocols OBJC2_UNAVAILABLE; // 协议链表
    #endif
    } OBJC2_UNAVAILABLE;
    

    总结: objc在向一个对象发送消息时,runtime库会根据对象的isa指针找到该对象实际所属的类,然后在该类中的方法列表以及其父类方法列表中寻找方法运行,然后在发送消息的时候,objc_msgSend方法不会返回值,所谓的返回内容都是具体调用时执行的。那么,回到本题,如果向一个nil对象发送消息,首先在寻找对象的isa指针时就是0地址返回了,所以不会出现任何错误。

    *15. KVO实现的原理:

    Apple 使用了 isa 混写(isa-swizzling)来实现 KVO 。

    下面做下详细解释:
    键值观察通知依赖于 NSObject 的两个方法: willChangeValueForKey: 和 didChangevlueForKey: 。在一个被观察属性发生改变之前, willChangeValueForKey: 一定会被调用,这就会记录旧的值。而当改变发生后, observeValueForKey:ofObject:change:context: 会被调用,继而 didChangeValueForKey: 也会被调用。可以手动实现这些调用,但很少有人这么做。一般我们只在希望能控制回调的调用时机时才会这么做。大部分情况下,改变通知会自动调用。
    比如调用 setNow: 时,系统还会以某种方式在中间插入 wilChangeValueForKey: 、 didChangeValueForKey: 和 observeValueForKeyPath:ofObject:change:context: 的调用。大家可能以为这是因为 setNow: 是合成方法,有时候我们也能看到有人这么写代码:

    • (void)setNow:(NSDate *)aDate {
      [self willChangeValueForKey:@"now"]; // 没有必要
      _now = aDate;
      [self didChangeValueForKey:@"now"];// 没有必要
      }

    键值观察通知依赖于 NSObject 的两个方法: willChangeValueForKey: 和 didChangevlueForKey:

    相关文章

      网友评论

      本文标题:iOS基础面试题及答案

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