iOS面试总结题目

iOS开发之高级面试题(二)

2019-03-06  本文已影响159人  0de1595b4560

小编加了一个不错的面试题和iOS技巧分享群,883872094群资料可以自取。分享BAT,阿里面试题、面试经验,讨论技术, 大家一起交流学习成长!希望帮助开发者少走弯路。

iOS开发之高级面试题(一)

iOS开发之高级面试题(三)

iOS开发之高级面试题(四)
9. @property中有哪些属性关键字?/ @property 后面可以有哪些修饰符?

属性可以拥有的特质分为四类:

  1. 原子性--- nonatomic 特质

    在默认情况下,由编译器合成的方法会通过锁定机制确保其原子性(atomicity)。如果属性具备 nonatomic 特质,则不使用同步锁。请注意,尽管没有名为“atomic”的特质(如果某属性不具备 nonatomic 特质,那它就是“原子的” ( atomic) ),但是仍然可以在属性特质中写明这一点,编译器不会报错。若是自己定义存取方法,那么就应该遵从与属性特质相符的原子性。

  2. 读/写权限---readwrite(读写)readonly (只读)

  3. 内存管理语义---assignstrongweakunsafe_unretainedcopy

  4. 方法名---getter=<name>setter=<name>

    getter=<name>的样式:

          @property (nonatomic, getter=isOn) BOOL on;
    

    setter=这种不常用,也不推荐使用。故不在这里给出写法。)

    setter=<name>一般用在特殊的情境下,比如:

在数据反序列化、转模型的过程中,服务器返回的字段如果以 init 开头,所以你需要定义一个 init 开头的属性,但默认生成的 settergetter 方法也会以 init 开头,而编译器会把所有以 init 开头的方法当成初始化方法,而初始化方法只能返回 self 类型,因此编译器会报错。

这时你就可以使用下面的方式来避免编译器报错:

@property(nonatomic, strong, getter=p_initBy, setter=setP_initBy:)NSString *initBy;

另外也可以用关键字进行特殊说明,来避免编译器报错:

@property(nonatomic, readwrite, copy, null_resettable) NSString *initBy;
- (NSString *)initBy __attribute__((objc_method_family(none)));
  1. 不常用的:nonnull,null_resettable,nullable

10. weak属性需要在dealloc中置nil么?

不需要。

在ARC环境无论是强指针还是弱指针都无需在 dealloc 设置为 nil , ARC 会自动帮我们处理

即便是编译器不帮我们做这些,weak也不需要在 dealloc 中置nil:

正如上文的:runtime 如何实现 weak 属性 中提到的:

我们模拟下 weak 的 setter 方法,应该如下:

- (void)setObject:(NSObject *)object
{
    objc_setAssociatedObject(self, "object", object, OBJC_ASSOCIATION_ASSIGN);
    [object cyl_runAtDealloc:^{
        _object = nil;
    }];
}

也即:

在属性所指的对象遭到摧毁时,属性值也会清空(nil out)。

11. @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 方法同样会导致崩溃。编译时没问题,运行时才执行相应的方法,这就是所谓的动态绑定。

12. ARC下,不显式指定任何属性关键字时,默认的关键字都有哪些?

  1. 对应基本数据类型默认关键字是

    atomic,readwrite,assign

  2. 对于普通的 Objective-C 对象

    atomic,readwrite,strong

参考链接:

  1. Objective-C ARC: strong vs retain and weak vs assign

  2. Variable property attributes or Modifiers in iOS

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

  1. 因为父类指针可以指向子类对象,使用 copy 的目的是为了让本对象的属性不受外界影响,使用 copy 无论给我传入是一个可变对象还是不可对象,我本身持有的就是一个不可变的副本.

  2. 如果我们使用是 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
)

(详见仓库内附录的 Demo。)

为了理解这种做法,首先要知道,两种情况:

  1. 对非集合类对象的 copy 与 mutableCopy 操作;
  2. 对集合类对象的 copy 与 mutableCopy 操作。

1. 对非集合类对象的copy操作:

在非集合类对象中:对 immutable 对象进行 copy 操作,是指针复制,mutableCopy 操作时内容复制;对 mutable 对象进行 copy 和 mutableCopy 都是内容复制。用代码简单表示如下:

比如以下代码:

NSMutableString *string = [NSMutableString stringWithString:@"origin"];//copy
NSString *stringCopy = [string copy];

查看内存,会发现 string、stringCopy 内存地址都不一样,说明此时都是做内容拷贝、深拷贝。即使你进行如下操作:

[string appendString:@"origion!"]

stringCopy 的值也不会因此改变,但是如果不使用 copy,stringCopy 的值就会被改变。 集合类对象以此类推。 所以,

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

2、集合类对象的copy与mutableCopy

集合类对象是指 NSArray、NSDictionary、NSSet ... 之类的对象。下面先看集合类immutable对象使用 copy 和 mutableCopy 的一个例子:

NSArray *array = @[@[@"a", @"b"], @[@"c", @"d"]];
NSArray *copyArray = [array copy];
NSMutableArray *mCopyArray = [array mutableCopy];

查看内容,可以看到 copyArray 和 array 的地址是一样的,而 mCopyArray 和 array 的地址是不同的。说明 copy 操作进行了指针拷贝,mutableCopy 进行了内容拷贝。但需要强调的是:此处的内容拷贝,仅仅是拷贝 array 这个对象,array 集合内部的元素仍然是指针拷贝。这和上面的非集合 immutable 对象的拷贝还是挺相似的,那么mutable对象的拷贝会不会类似呢?我们继续往下,看 mutable 对象拷贝的例子:

NSMutableArray *array = [NSMutableArray arrayWithObjects:[NSMutableString stringWithString:@"a"],@"b",@"c",nil];
NSArray *copyArray = [array copy];
NSMutableArray *mCopyArray = [array mutableCopy];

查看内存,如我们所料,copyArray、mCopyArray和 array 的内存地址都不一样,说明 copyArray、mCopyArray 都对 array 进行了内容拷贝。同样,我们可以得出结论:

在集合类对象中,对 immutable 对象进行 copy,是指针复制, mutableCopy 是内容复制;对 mutable 对象进行 copy 和 mutableCopy 都是内容复制。但是:集合对象的内容复制仅限于对象本身,对象元素仍然是指针复制。用代码简单表示如下:

[immutableObject copy] // 浅复制
[immutableObject mutableCopy] //单层深复制
[mutableObject copy] //单层深复制
[mutableObject mutableCopy] //单层深复制

这个代码结论和非集合类的非常相似。

参考链接:iOS 集合的深复制与浅复制

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

在回答之前先说明下一个概念:

实例变量 = 成员变量 = ivar

这些说法,笔者下文中,可能都会用到,指的是一个东西。

正如 Apple官方文档 You Can Customize Synthesized Instance Variable Names 所说:

enter image description here

如果使用了属性的话,那么编译器就会自动编写访问属性所需的方法,此过程叫做“自动合成”( auto synthesis)。需要强调的是,这个过程由编译器在编译期执行,所以编辑器里看不到这些“合成方法” (synthesized method)的源代码。除了生成方法代码之外,编译器还要自动向类中添加适当类型的实例变量,并且在属性名前面加下划线,以此作为实例变量的名字。

@interface CYLPerson : NSObject 
@property NSString *firstName; 
@property NSString *lastName; 
@end

在上例中,会生成两个实例变量,其名称分别为 _firstName_lastName。也可以在类的实现代码里通过 @synthesize 语法来指定实例变量的名字:

@implementation CYLPerson 
@synthesize firstName = _myFirstName; 
@synthesize lastName = _myLastName; 
@end 

上述语法会将生成的实例变量命名为 _myFirstName_myLastName ,而不再使用默认的名字。一般情况下无须修改默认的实例变量名,但是如果你不喜欢以下划线来命名实例变量,那么可以用这个办法将其改为自己想要的名字。笔者还是推荐使用默认的命名方案,因为如果所有人都坚持这套方案,那么写出来的代码大家都能看得懂。

总结下 @synthesize 合成实例变量的规则,有以下几点:

  1. 如果指定了成员变量的名称,会生成一个指定的名称的成员变量,

  2. 如果这个成员已经存在了就不再生成了.

  3. 如果是 @synthesize foo; 还会生成一个名称为foo的成员变量,也就是说:

    如果没有指定成员变量的名称会自动生成一个属性同名的成员变量,

  4. 如果是 @synthesize foo = _foo; 就不会生成成员变量了.

假如 property 名为 foo,存在一个名为 _foo 的实例变量,那么还会自动合成新变量么? 不会。如下图:

enter image description here

15. 在有了自动合成属性实例变量之后,@synthesize还有哪些使用场景?

回答这个问题前,我们要搞清楚一个问题,什么情况下不会autosynthesis(自动合成)?

  1. 同时重写了 setter 和 getter 时

  2. 重写了只读属性的 getter 时

  3. 使用了 @dynamic 时

  4. 在 @protocol 中定义的所有属性

  5. 在 category 中定义的所有属性

  6. 重载的属性

    当你在子类中重载了父类中的属性,你必须 使用 @synthesize 来手动合成ivar。

除了后三条,对其他几个我们可以总结出一个规律:当你想手动管理 @property 的所有内容时,你就会尝试通过实现 @property 的所有“存取方法”(the accessor methods)或者使用 @dynamic 来达到这个目的,这时编译器就会认为你打算手动管理 @property,于是编译器就禁用了 autosynthesis(自动合成)。

因为有了 autosynthesis(自动合成),大部分开发者已经习惯不去手动定义ivar,而是依赖于 autosynthesis(自动合成),但是一旦你需要使用ivar,而 autosynthesis(自动合成)又失效了,如果不去手动定义ivar,那么你就得借助 @synthesize 来手动合成 ivar。

其实,@synthesize 语法还有一个应用场景,但是不太建议大家使用:

可以在类的实现代码里通过 @synthesize 语法来指定实例变量的名字:

@implementation CYLPerson 
@synthesize firstName = _myFirstName; 
@synthesize lastName = _myLastName; 
@end 

上述语法会将生成的实例变量命名为 _myFirstName_myLastName,而不再使用默认的名字。一般情况下无须修改默认的实例变量名,但是如果你不喜欢以下划线来命名实例变量,那么可以用这个办法将其改为自己想要的名字。笔者还是推荐使用默认的命名方案,因为如果所有人都坚持这套方案,那么写出来的代码大家都能看得懂。

举例说明:应用场景:

//
// .m文件
// http://weibo.com/luohanchenyilong/ (微博@iOS程序犭袁)
// https://github.com/ChenYilong
// 打开第14行和第17行中任意一行,就可编译成功

@import Foundation;

@interface CYLObject : NSObject
@property (nonatomic, copy) NSString *title;
@end

@implementation CYLObject {
    //    NSString *_title;
}

//@synthesize title = _title;

- (instancetype)init
{
    self = [super init];
    if (self) {
        _title = @"微博@iOS程序犭袁";
    }
    return self;
}

- (NSString *)title {
    return _title;
}

- (void)setTitle:(NSString *)title {
    _title = [title copy];
}

@end

结果编译器报错:

enter image description here

当你同时重写了 setter 和 getter 时,系统就不会生成 ivar(实例变量/成员变量)。这时候有两种选择:

  1. 要么如第14行:手动创建 ivar
  2. 要么如第17行:使用@synthesize foo = _foo; ,关联 @property 与 ivar。

更多信息,请戳- 》 When should I use @synthesize explicitly?

16. objc中向一个nil对象发送消息将会发生什么?

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

  1. 如果一个方法返回值是一个对象,那么发送给nil的消息将返回0(nil)。例如:

    Person * motherInlaw = [[aPerson spouse] mother];
    

    如果 spouse 对象为 nil,那么发送给 nil 的消息 mother 也将返回 nil。

  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地址返回了,所以不会出现任何错误。

17. objc中向一个对象发送消息[obj foo]和objc_msgSend()函数之间有什么关系?

具体原因同上题:该方法编译之后就是objc_msgSend()函数调用.

我们用 clang 分析下,clang 提供一个命令,可以将Objective-C的源码改写成C++语言,借此可以研究下[obj foo]和objc_msgSend()函数之间有什么关系。

以下面的代码为例,由于 clang 后的代码达到了10万多行,为了便于区分,添加了一个叫 iOSinit 方法,

//
//  main.m
//  http://weibo.com/luohanchenyilong/
//  https://github.com/ChenYilong
//  Copyright (c) 2015年 微博@iOS程序犭袁. All rights reserved.
//

#import "CYLTest.h"

int main(int argc, char * argv[]) {
    @autoreleasepool {
        CYLTest *test = [[CYLTest alloc] init];
        [test performSelector:(@selector(iOSinit))];
        return 0;
    }
}

在终端中输入

clang -rewrite-objc main.m

就可以生成一个main.cpp的文件,在最低端(10万4千行左右)

enter image description here

我们可以看到大概是这样的:

((void ()(id, SEL))(void )objc_msgSend)((id)obj, sel_registerName("foo"));

也就是说:

[obj foo];在objc动态编译时,会被转意为:objc_msgSend(obj, @selector(foo));

18. 什么时候会报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:消息给目标对象。

为了能更清晰地理解这些方法的作用,git仓库里也给出了一个Demo,名称叫“ _objc_msgForward_demo ”,可运行起来看看。

19. 一个objc对象如何进行内存布局?(考虑有父类的情况)

每个 Objective-C 对象都有相同的结构,如下图所示:

enter image description here

翻译过来就是

Objective-C 对象的结构图
ISA指针
根类的实例变量
倒数第二层父类的实例变量
...
父类的实例变量
类的实例变量

如图:

enter image description here

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

指向他的类对象,从而可以找到对象上的方法

小编加了一个不错的面试题和iOS技巧分享群,883872094群资料可以自取
iOS开发之高级面试题(一)

iOS开发之高级面试题(三)

iOS开发之高级面试题(四)

小编加了一个不错的面试题和iOS技巧分享群,883872094群资料可以自取。分享BAT,阿里面试题、面试经验,讨论技术, 大家一起交流学习成长!希望帮助开发者少走弯路。
原文地址
图文来源于网络,如有侵权请联系小编删除

上一篇 下一篇

猜你喜欢

热点阅读