iOS面试题上

2016-03-26  本文已影响139人  forvert

面试题上

  1. 什么情况使用 weak 关键字,相比 assign 有什么不同?

    • 什么情况使用 weak 关键字?
      1. 在 ARC 中,在有可能出现循环引用的时候,往往要通过让其中一端使用 weak 来解决,比如: delegate 代理属性
      2. 自身已经对它进行一次强引用,没有必要再强引用一次,此时也会使用 weak,自定义 IBOutlet 控件属性一般也使用 weak;当然,也可以使用strong。
    • 不同点:
      1. weak 此特质表明该属性定义了一种“非拥有关系” (nonowning relationship)。为这种属性设置新值时,设置方法既不保留新值,也不释放旧值。此特质同assign类似, 然而在属性所指的对象遭到摧毁时,属性值也会清空(nil out)。 而 assign 的“设置方法”只会执行针对“纯量类型” (scalar type,例如 CGFloat 或 NSlnteger 等)的简单赋值操作。
      2. assigin 可以用非 OC 对象,而 weak 必须用于 OC 对象
  2. 怎么用 copy 关键字?

    • 用途:
      1. NSString、NSArray、NSDictionary 等等经常使用copy关键字,是因为他们有对应的可变类型:NSMutableString、NSMutableArray、NSMutableDictionary;
      2. block 也经常使用 copy 关键字.block 使用 copy 是从 MRC 遗留下来的“传统”,在 MRC 中,方法内部的 block 是在栈区的,使用 copy 可以把它放到堆区.在 ARC 中写不写都行:对于 block 使用 copy 还是strong效果是一样的,但写上copy也无伤大雅,还能时刻提醒我们:编译器自动对block进行了copy操作。如果不写copy,该类的调用者有可能会忘记或者根本不知道“编译器会自动对block进行了copy操作”,他们有可能会在调用之前自行拷贝属性值。这种操作多余而低效.
      3. copy 此特质所表达的所属关系与 strong 类似。然而设置方法并不保留新值,而是将其“拷贝” (copy)。 当属性类型为 NSString 时,经常用此特质来保护其封装性,因为传递给设置方法的新值有可能指向一个 NSMutableString 类的实例。这个类是 NSString 的子类,表示一种可修改其值的字符串,此时若是不拷贝字符串,那么设置完属性之后,字符串的值就可能会在对象不知情的情况下遭人更改。所以,这时就要拷贝一份“不可变” (immutable)的字符串,确保对象中的字符串值不会无意间变动。只要实现属性所用的对象是“可变的” (mutable),就应该在设置新属性值时拷贝一份。

      用 @property 声明 NSString、NSArray、NSDictionary 经常使用 copy 关键字,是因为他们有对应的可变类型:NSMutableString、NSMutableArray、NSMutableDictionary,他们之间可能进行赋值操作,为确保对象中的字符串值不会无意间变动,应该在设置新属性值时拷贝一份。

  3. 这个写法会出什么问题:

    @property (copy) NSMutableArray *array;
    
    • 添加,删除,修改数组内的元素的时候,程序会因为找不到对应的方法而崩溃.因为 copy 就是复制一个不可变 NSArray 的对象;例如:
    // 定义属性
    @property (nonatomic, copy) NSMutableArray *mutableArray;
    
    NSMutableArray *array = [NSMutableArray arrayWithObjects:@1,@2,nil];
    

self.mutableArray = array;
[self.mutableArray removeObjectAtIndex:0];

// 崩溃信息
 -[__NSArrayI removeObjectAtIndex:]: unrecognized selector sent to instance 0x7fcd1bc30460
```
- 使用了 atomic 属性会严重影响性能 ;
    1. 该属性使用了同步锁,会在创建时生成一些额外的代码用于帮助编写多线程程序,这会带来性能问题,通过声明 nonatomic 可以节省这些虽然很小但是不必要额外开销
    2. 一般情况下并不要求属性必须是“原子的”,因为这并不能保证“线程安全” ( thread safety),若要实现“线程安全”的操作,还需采用更为深层的锁定机制才行。
    3. `这条回答了:使用atomic一定安全吗?`一个线程在连续多次读取某属性值的过程中有别的线程在同时改写该值,那么即便将属性声明为 atomic,也还是会读到不同的属性值。
  1. 如何让自己的类用 copy 修饰符?如何重写带 copy 关键字的 setter?

    若想令自己所写的对象具有拷贝功能,则需实现 NSCopying 协议。如果自定义的对象分为可变版本与不可变版本,那么就要同时实现 NSCopying 与 NSMutableCopying 协议
    - 具体步骤:
    1. 需声明该类遵从 NSCopying 协议
    2. 实现 NSCopying 协议。该协议只有一个方法
    objc - (id)copyWithZone:(NSZone *)zone;
    - 注意:一提到让自己的类用 copy 修饰符,我们总是想覆写copy方法,其实真正需要实现的却是 “copyWithZone” 方法。

  2. @property 的本质是什么?ivar、getter、setter 是如何生成并添加到这个类中的?

    • @property 的本质是什么?
    // “属性” (property)有两大概念:ivar(实例变量)、存取方法(access method = getter + setter)。
    @property = ivar + getter + setter;
    
    • “属性” (property)作为 Objective-C 的一项特性,主要的作用就在于封装对象中的数据。 Objective-C 对象通常会把其所需要的数据保存为各种实例变量。实例变量一般通过“存取方法”(access method)来访问。其中,“获取方法” (getter)用于读取变量值,而“设置方法” (setter)用于写入变量值。这个概念已经定型,并且经由“属性”这一特性而成为 Objective-C 2.0 的一部分。 而在正规的 Objective-C 编码风格中,存取方法有着严格的命名规范。 正因为有了这种严格的命名规范,所以 Objective-C 这门语言才能根据名称自动创建出存取方法。其实也可以把属性当做一种关键字,其表示:
    // 编译器会自动写出一套存取方法,用以访问给定类型中具有给定名称的变量。 所以你也可以这么说:
    @property = getter + setter;
    
    • ivar、getter、setter 是如何生成并添加到这个类中的?
      1. “自动合成”( autosynthesis)完成属性定义后,编译器会自动编写访问这些属性所需的方法,此过程叫做“自动合成”(autosynthesis)。需要强调的是,这个过程由编译 器在编译期执行,所以编辑器里看不到这些“合成方法”(synthesized method)的源代码。除了生成方法代码 getter、setter 之外,编译器还要自动向类中添加适当类型的实例变量,并且在属性名前面加下划线,以此作为实例变量的名字。也可以在类的实现代码里通过 @synthesize 语法来指定实例变量的名字.
  3. @protocol 和 category 中如何使用 @property

    • 在 protocol 中使用 property 只会生成 setter 和 getter 方法声明,我们使用属性的目的,是希望遵守我协议的对象能实现该属性
    • category 使用 @property 也是只会生成 setter 和 getter 方法的声明,如果我们真的需要给 category 增加属性的实现,需要借助于运行时的两个函数
    objc_setAssociatedObject
    objc_getAssociatedObject
    
  4. runtime 如何实现 weak 属性?

    • weak 属性的特点:
      • weak 此特质表明该属性定义了一种“非拥有关系” (nonowning relationship)。为这种属性设置新值时,设置方法既不保留新值,也不释放旧值。此特质同 assign 类似, 然而在属性所指的对象遭到摧毁时,属性值也会清空(nil out)。
    • runtime 如何实现 weak 变量的自动置nil?
      • runtime 对注册的类, 会进行布局,对于 weak 对象会放入一个 hash 表中。 用 weak 指向的对象内存地址作为 key,当此对象的引用计数为0的时候会 dealloc,假如 weak 指向的对象内存地址是a,那么就会以a为键, 在这个 weak 表中搜索,找到所有以a为键的 weak 对象,从而设置为 nil。
  5. @property中有哪些属性关键字?/ @property 后面可以有哪些修饰符?

    • 属性可以拥有的特质分为四类:
      • 原子性--- nonatomic 特质
        • 在默认情况下,由编译器合成的方法会通过锁定机制确保其原子性(atomicity)。如果属性具备 nonatomic 特质,则不使用同步锁。请注意,尽管没有名为“atomic”的特质(如果某属性不具备 nonatomic 特质,那它就是“原子的” ( atomic) ),但是仍然可以在属性特质中写明这一点,编译器不会报错。若是自己定义存取方法,那么就应该遵从与属性特质相符的原子性。
      • 读/写权限---readwrite(读写)、readonly (只读)
      • 内存管理语义---assign、strong、 weak、unsafe_unretained、copy
      • 方法名---getter=<name> 、setter=<name>
        • getter=<name>的样式:
         @property (nonatomic, getter=isOn) BOOL on;
        
        • setter=<name>一般用在特殊的情境下,比如:
          在数据反序列化、转模型的过程中,服务器返回的字段如果以 init 开头,所以你需要定义一个 init 开头的属性,但默认生成的 setter 与 getter 方法也会以 init 开头,而编译器会把所有以 init 开头的方法当成初始化方法,而初始化方法只能返回 self 类型,因此编译器会报错。这时你就可以使用下面的方式来避免编译器报错:
        @property(nonatomic, strong, getter=p_initBy, setter=setP_initBy:)NSString *initBy;
        
        
        • 不常用的:nonnull,null_resettable,nullable
  6. weak属性需要在dealloc中置nil么?

    • 不需要。
    • 在ARC环境无论是强指针还是弱指针都无需在 dealloc 设置为 nil , ARC 会自动帮我们处理
    • 即便是编译器不帮我们做这些,weak也不需要在 dealloc 中置nil:
    • 在属性所指的对象遭到摧毁时,属性值也会清空(nil out)。
  7. @synthesize和@dynamic分别有什么作用?

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

    • 对应基本数据类型默认关键字是atomic,readwrite,assign
    • 对于普通的 Objective-C 对象atomic,readwrite,strong
  2. 用@property声明的NSString(或NSArray,NSDictionary)经常使用copy关键字,为什么?如果改用strong关键字,可能造成什么问题?

    • 因为父类指针可以指向子类对象,使用 copy 的目的是为了让本对象的属性不受外界影响,使用 copy 无论给我传入是一个可变对象还是不可对象,我本身持有的就是一个不可变的副本.
    • 如果我们使用是 strong ,那么这个属性就有可能指向一个可变对象,如果这个可变对象在外部被修改了,那么会影响该属性.
    • copy 此特质所表达的所属关系与 strong 类似。然而设置方法并不保留新值,而是将其“拷贝” (copy)。 当属性类型为 NSString 时,经常用此特质来保护其封装性,因为传递给设置方法的新值有可能指向一个 NSMutableString 类的实例。这个类是 NSString 的子类,表示一种可修改其值的字符串,此时若是不拷贝字符串,那么设置完属性之后,字符串的值就可能会在对象不知情的情况下遭人更改。所以,这时就要拷贝一份“不可变” (immutable)的字符串,确保对象中的字符串值不会无意间变动。只要实现属性所用的对象是“可变的” (mutable),就应该在设置新属性值时拷贝一份
    // 定义一个以 strong 修饰的 array:
    @property (nonatomic ,readwrite, strong) NSArray *array;
    // 然后进行下面的操作:
    NSMutableArray *mutableArray = [[NSMutableArray alloc] init];
    NSArray *array = @[ @1, @2, @3, @4 ];
    self.array = mutableArray;
    [mutableArray removeAllObjects];;
    NSLog(@"%@",self.array);
    
    [mutableArray addObjectsFromArray:array];
    self.array = [mutableArray copy];
    [mutableArray removeAllObjects];;
    NSLog(@"%@",self.array);
    
    // 打印结果
    2015-09-27 19:10:32.523 CYLArrayCopyDmo[10681:713670] (
    

)
2015-09-27 19:10:32.524 CYLArrayCopyDmo[10681:713670] (
1,
2,
3,
4
)
```



  1. 什么情况下不会autosynthesis(自动合成)?

    • 同时重写了 setter 和 getter 时
    • 重写了只读属性的 getter 时
    • 使用了 @dynamic 时
    • 在 @protocol 中定义的所有属性
    • 在 category 中定义的所有属性
    • 重载的属性:当你在子类中重载了父类中的属性,你必须 使用 @synthesize 来手动合成ivar。
  2. objc中向一个nil对象发送消息将会发生什么?

    在 Objective-C 中向 nil 发送消息是完全有效的——只是在运行时不会有任何作用:

    • 如果一个方法返回值是一个对象,那么发送给nil的消息将返回0(nil)。例如:
    Person * motherInlaw = [[aPerson spouse] mother];
    //如果 spouse 对象为 nil,那么发送给 nil 的消息 mother 也将返回 nil
    
    • 如果方法返回值为指针类型,其指针大小为小于或者等于sizeof(void*),float,double,long double 或者 long long 的整型标量,发送给 nil 的消息将返回0。
    • 如果方法返回值为结构体,发送给 nil 的消息将返回0。结构体中各个字段的值将都是0。
    • 如果方法的返回值不是上述提到的几种情况,那么发送给 nil 的消息的返回值将是未定义的。
    • objc在向一个对象发送消息时,runtime库会根据对象的isa指针找到该对象实际所属的类,然后在该类中的方法列表以及其父类方法列表中寻找方法运行,然后在发送消息的时候,objc_msgSend方法不会返回值,所谓的返回内容都是具体调用时执行的。 那么,回到本题,如果向一个nil对象发送消息,首先在寻找对象的isa指针时就是0地址返回了,所以不会出现任何错误。
  3. objc中向一个对象发送消息[obj foo]和objc_msgSend()函数之间有什么关系?

    • [obj foo];在objc动态编译时,会被转意为:objc_msgSend(obj, @selector(foo));。
  4. 什么时候会报unrecognized selector的异常?

    • 当调用该对象上某个方法,而该对象上没有实现这个方法的时候, 可以通过“消息转发”进行解决。
    • objc是动态语言,每个方法在运行时会被动态转为消息发送,即:objc_msgSend(receiver, selector)。
    • objc在向一个对象发送消息时,runtime库会根据对象的isa指针找到该对象实际所属的类,然后在该类中的方法列表以及其父类方法列表中寻找方法运行,如果,在最顶层的父类中依然找不到相应的方法时,程序在运行时会挂掉并抛出异常unrecognized selector sent to XXX 。但是在这之前,objc的运行时会给出三次拯救程序崩溃的机会:
      1. Method resolution:objc运行时会调用+resolveInstanceMethod:或者 +resolveClassMethod:,让你有机会提供一个函数实现。如果你添加了函数,那运行时系统就会重新启动一次消息发送的过程,否则 ,运行时就会移到下一步,消息转发(Message Forwarding)
      2. Fast forwarding如果目标对象实现了-forwardingTargetForSelector:,Runtime 这时就会调用这个方法,给你把这个消息转发给其他对象的机会。 只要这个方法返回的不是nil和self,整个消息发送的过程就会被重启,当然发送的对象会变成你返回的那个对象。否则,就会继续Normal Fowarding。 这里叫Fast,只是为了区别下一步的转发机制。因为这一步不会创建任何新的对象,但下一步转发会创建一个NSInvocation对象,所以相对更快点。
      3. Normal forwarding这一步是Runtime最后一次给你挽救的机会。首先它会发送-methodSignatureForSelector:消息获得函数的参数和返回值类型。如果-methodSignatureForSelector:返回nil,Runtime则会发出-doesNotRecognizeSelector:消息,程序这时也就挂掉了。如果返回了一个函数签名,Runtime就会创建一个NSInvocation对象并发送-forwardInvocation:消息给目标对象。
  5. 一个objc对象如何进行内存布局?(考虑有父类的情况)

    • 所有父类的成员变量和自己的成员变量都会存放在该对象所对应的存储空间中.
    • 每一个对象内部都有一个isa指针,指向他的类对象,类对象中存放着本对象的
      1. 对象方法列表(对象能够接收的消息列表,保存在它所对应的类对象中)
      2. 成员变量的列表,
      3. 属性列表,

      它内部也有一个isa指针指向元对象(meta class),元对象内部存放的是类方法列表,类对象内部还有一个superclass的指针,指向他的父类对象。
      - 每个 Objective-C 对象都有相同的结构,如下图所示:



      - 根对象就是NSobject,它的superclass指针指向nil
      - 类对象既然称为对象,那它也是一个实例。类对象中也有一个isa指针指向它的元类(meta class),即类对象是元类的实例。元类内部存放的是类方法列表,根元类的isa指针指向自己,superclass指针指向NSObject类。


  6. 一个objc对象的isa的指针指向什么?有什么作用?

    • 指向他的类对象,从而可以找到对象上的方法
  7. 下面的代码输出什么?

    @implementation Son : Father
    - (id)init
    {
        self = [super init];
        if (self) {
            NSLog(@"%@", NSStringFromClass([self class]));
            NSLog(@"%@", NSStringFromClass([super class]));
        }
        return self;
    }
    @end
    
    • 都输出 Son
    NSStringFromClass([self class]) = Son
    

NSStringFromClass([super class]) = Son
```
- self 是类的隐藏参数,指向当前调用方法的这个类的实例
- 其实 super 是一个 Magic Keyword, 它本质是一个编译器标示符,和 self 是指向的同一个消息接受者!他们两个的不同点在于:super 会告诉编译器,调用 class 这个方法时,要去父类的方法,而不是本类里的。
- 上面的例子不管调用[self class]还是[super class],接受消息的对象都是当前 Son *xxx 这个对象。
- 当使用 self 调用方法时,会从当前类的方法列表中开始找,如果没有,就从父类中再找;而当使用 super 时,则从父类的方法列表中开始找。然后调用父类的这个方法。

19题没整明白.......

  1. runtime如何通过selector找到对应的IMP地址?(分别考虑类方法和实例方法)

    • 每一个类对象中都一个方法列表,方法列表中记录着方法的名称,方法实现,以及参数类型,其实selector本质就是方法名称,通过这个方法名称就可以在方法列表中找到对应的方法实现.
  2. 使用runtime Associate方法关联的对象,需要在主对象dealloc的时候释放么?

  3. objc中的类方法和实例方法有什么本质区别和联系?

    • 类方法:
      • 类方法是属于类对象的
      • 类方法只能通过类对象调用
      • 类方法中的self是类对象
      • 类方法可以调用其他的类方法
      • 类方法中不能访问成员变量
      • 类方法中不定直接调用对象方法
    • 实例方法:
      • 实例方法是属于实例对象的
      • 实例方法只能通过实例对象调用
      • 实例方法中的self是实例对象
      • 实例方法中可以访问成员变量
      • 实例方法中直接调用实例方法
      • 实例方法中也可以调用类方法(通过类名)

本文完全转自@《招聘一个靠谱的 iOS》

上一篇下一篇

猜你喜欢

热点阅读