面试问题总结

基础类

1 ARC下的assign和weak区别

weak 比 assign 多了一个功能就是当属性所指向的对象消失的时(也就是内存引用计数为0)会自动赋值为 nil ,这样再向 weak 修饰的属性发送消息就不会导致野指针操作crash。

结论:在 ARC 模式下编程时,指针变量一定要用 weak 修饰,只有基本数据类型和结构体需要用 assgin ,例如 delegate ,一定要用 weak 修饰。

2 NSString到底使用Copy还是使用Strong属性,有什么区别

首先分别定义这个两个属性 NSString

1
2
@property (nonatomic,strong)NSString  *strongStr;
@property (nonatomic,copy )NSString *copyssStr;

然后声明一个NSMutableString对象,给这两个属性对象赋值,分别输出他们的指针和内存地址。

1
2
3
4
5
6
NSMutableString *string = [NSMutableString stringWithFormat:@"测试文字"];
self.strongStr = string;
self.copyssStr = string;
NSLog(@"测试文字 String: %p, %p", string, &string);
NSLog(@"Strong属性 String: %p, %p",_strongStr, &_strongStr);
NSLog(@"Copy 属性 String: %p, %p",_copyssStr, &_copyssStr);

输出如下:

1
2
3
测试文字   String: 0x7f892bf29760, 0x7fff592ca6d8
Strong属性 String: 0x7f892bf29760, 0x7f892bc42480
Copy 属性 String: 0x7f892be7d2f0, 0x7f892bc42488

此时Copy属性字符串已不再指向原来String对象,而是深拷贝了String字符串,且copyssString对象指向这个字符串。

在MRC环境下,输出两者的引用计数,可以看到String对象的引用计数是2,而copyssString对象的引用计数是1。如果去修改String字符串的话,可以看到:因为strongString与原始String是指向同一对象,所以strongString的值也会跟随着改变(此时strongString的类型实际上是NSMutableString,而不是NSString);而copyssString是指向另一个对象,并不会改变。

结论:Strong属性只是增加了原字符串的引用计数,而Copy属性则是对原字符串做了次深拷贝,产生一个新的对象,且Copy属性对象指向这个新的对象,且这个Copy属性对象的类型始终是NSString,而不是NSMutableString,因此其是不可变的。所以,在声明NSString属性时,到底是选择strong还是copy,可以根据实际情况来定。不过,一般我们将对象声明为NSString时,都不希望它改变,所以大多数情况下,我们建议用copy,以免因可变字符串的修改导致的一些非预期问题。

3 block为什么要用copy?

首先, block是一个对象, 所以block理论上是可以retain/release的. 但是block在创建的时候它的内存是默认是分配在栈(stack)上, 而不是堆(heap)上的. 所以它的作用域仅限创建时候的当前上下文(函数, 方法…), 当你在该作用域外调用该block时, 程序就会崩溃.

意思就是 : 一般情况下你不需要自行调用copy或者retain一个block. 只有当你需要在block定义域以外的地方使用时才需要copy. Copy将block从内存栈区移到堆区.

其实block使用copy是MRC留下来的也算是一个传统吧, 在MRC下, 如上述, 在方法中的block创建在栈区, 使用copy就能把他放到堆区, 这样在作用域外调用该block程序就不会崩溃. 但在ARC下, 使用copy与strong其实都一样, 因为block的retain就是用copy来实现的, 所以block使用copy还能装装逼, 说明自己是从MRC下走过来的..嘿嘿

4 如何让自定义对象支持 copy 操作?

需要让自定义类实现 NSCopying 协议,该协议只有一个方法:

1
- (id)copyWithZone:(nullable NSZone *)zone;

假如我们想让自定义对象支持 mutableCopy 操作,那又应该怎么操作呢?这需要自定义对象遵循 NSMutableCopying 协议, 该协议也只有一个方法:

1
- (id)mutableCopyWithZone:(nullable NSZone *)zone;

5 Delegate,Notification,KVO优缺点

delegate的优势:

1.很严格的语法,所有能响应的时间必须在协议中有清晰的定义

2.因为有严格的语法,所以编译器能帮你检查是否实现了所有应该实现的方法,不容易遗忘和出错

3.使用delegate的时候,逻辑很清楚,控制流程可跟踪和识别

4.在一个controller中可以定义多个协议,每个协议有不同的delegate

5.没有第三方要求保持/监视通信过程,所以假如出了问题,那我们可以比较方便的定位错误代码。

6.能够接受调用的协议方法的返回值,意味着delegate能够提供反馈信息给controller

delegate的缺点:

需要写的代码比较多

notification的优势:

1.不需要写多少代码,实现比较简单

2.一个对象发出的通知,多个对象能进行反应,一对多的方式实现很简单

缺点:

1.编译期不会接茬通知是否能被正确处理

2.释放注册的对象时候,需要在通知中心取消注册

3.调试的时候,程序的工作以及控制流程难跟踪

4.需要第三方来管理controller和观察者的联系

5.controller和观察者需要提前知道通知名称、UserInfo dictionary keys。如果这些没有在工作区间定义,那么会出现不同步的情况

6.通知发出后,发出通知的对象不能从观察者获得任何反馈。

KVO是一个对象能观察另一个对象属性的值,前两种模式更适合一个controller和其他的对象进行通信,而KVO适合任何对象监听另一个对象的改变,这是一个对象与另外一个对象保持同步的一种方法。KVO只能对属性做出反应,不会用来对方法或者动作做出反应。

优点:

1.提供一个简单地方法来实现两个对象的同步

2.能对非我们创建的对象做出反应

3.能够提供观察的属性的最新值和先前值

4.用keypaths 来观察属性,因此也可以观察嵌套对象

缺点:

1.观察的属性必须使用string来定义,因此编译器不会出现警告和检查

2.对属性的重构将导致观察不可用

3.复杂的“if”语句要求对象正在观察多个值,这是因为所有的观察都通过一个方法来指向

6 block的__blockweakSelf,StrongSelf

__block

没法在Block中改变局部变量的值,编译过程中就报编译错误。错误:

1
Variable is not assignable(missing __block type specifier)

因为局部变量是在执行Block语法的时候,被block捕获成为Block的结构体实例中,但是Block仅仅捕获了val的值,并没有捕获val的内存地址,所以在Block内部是无法修改自动变量的值。

_block修饰自动变量后,_block的变量也被转化成了一个结构体:__Block_byref_i_0,这个结构体有5个成员变量。

1
2
3
4
5
6
7
struct __Block_byref_i_0 {
void *__isa; 指针
__Block_byref_i_0 *__forwarding; 指向自身类型的 __forwarding指针
int __flags; 标记flag
int __size;大小
int i; 变量值
};

MRC环境下,只有copy,_block才会被复制到堆上,否则,_block一直都在栈上,block也只是 _NSStackBlock,这个时候_forwarding指针就只指向自己了。

ARC环境下,一旦Block赋值就会触发copy,_block就会copy到堆上,Block也是_NSMallocBlock。ARC环境下也是存在_NSStackBlock的时候,这种情况下,_block就在栈上

weakSelfStrongSelf

weakSelf是为了解决循环引用的问题。一定要分析清楚哪里出现了循环引用,只需要把其中一环加上weakSelf这类似的宏,就可以解决循环引用。_weak的实现原理,在原对象释放之后,_weak对象就会变成null,防止野指针。所以就输出了null了。

那么我们怎么才能在weakSelf之后,block里面还能继续使用weakSelf之后的对象呢?
究其根本原因就是weakSelf之后,无法控制什么时候会被释放,为了保证在block内不会被释放,需要添加_strong。

在block里面使用的_strong修饰的weakSelf是为了在函数生命周期中防止self提前释放。strongSelf是一个自动变量当block执行完毕就会释放自动变量strongSelf不会对self进行一直进行强引用。

7 arc下,不显示指定任何属性关键字时,默认的关键字都有哪些

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

2.对于普通的 Objective-C 对象 atomic,readwrite,strong

8 iOS开发中nil、Nil、NULL和[NSNull null]的区别

nil:当一个对象置为nil时,这个对象的内存地址就会被系统收回。置空之后是不能进行retain,copy等跟引用计数有关的任何操作的。

Nil:nil完全等同于Nil,只不过由于编程习惯,人们一般把对象置空用nil,把类置空用Nil。

NULL:这个是从C语言继承来的,就是一个简单的空指针。

[NSNull null]:[NSNull null]和nil的区别在于,nil是一个空对象,已经完全从内存中消失了,而如果我们想表达“我们需要有这样一个容器,但这个容器里什么也没有”的观念时,我们就用到[NSNull null],它就是为“值为空的对象”。如果你查阅开发文档你会发现NSNull这个类是继承NSObject,并且只有一个“+ (NSNull *) null;”类方法。这就说明NSNull对象拥有一个有效的内存地址,所以在程序中对它的任何引用都是不会导致程序崩溃的。

9 category,protocol,delegate,extension

category:类别,是一种为现有的类添加新方法的方式,可以为任何类添加category,哪怕是那些没有源码的类。注意: 无法向category中添加新的实例变量,运行时报错。可以用rumtime解决。当category中方法名与现有类名重名时,类别具有更高的优先级,将取代现有的方法。
NSString+NumberConvience.h

protocol:协议是一个方法签名列表,其中定义了若干个方法,@required这是委托对象必须实现的,@optional这是可选的。

delegate: 委托是一种对象,是实现协议的对象。比如类B设置了类A的delegate对象为其自身,那么类B就要实现类A中制定的某些方法。协议和代理在iOS中是配合使用的。

extension: 扩展可以理解成是没有名字的分类,扩展可以添加属性,成员变量,方法名称。类扩展写在.m中,这些内容就都是私有的了,只有本类才可见。

10 CALayer 和 UIView 的区别和联系

1.首先UIView可以响应事件,Layer不可以。

2.View和CALayer的Frame映射及View如何创建CALayer。一个 Layer 的 frame 是由它的 anchorPoint,position,bounds,和 transform 共同决定的,而一个 View 的 frame 只是简单的返回 Layer的 frame,同样 View 的 center和 bounds 也是返回 Layer 的一些属性。

3.UIView主要是对显示内容的管理而 CALayer 主要侧重显示内容的绘制。

4.在做 iOS 动画的时候,修改非 RootLayer的属性(譬如位置、背景色等)会默认产生隐式动画,而修改UIView则不会。

11 简述UIButton的继承关系

UIButton的父类是UIControl,UIControl的父类是UIView,UIView的父类是UIResponder

12 __block__weak的真正区别

1、__block本身并不能避免循环引用,避免循环引用需要在block内部把__block修饰的obj置为nil

2、__weak可以避免循环引用,但是其会导致外部对象释放了之后,block 内部也访问不到这个对象的问题,我们可以通过在 block 内部声明一个 __strong
的变量来指向 weakObj,使外部对象既能在 block 内部保持住,又能避免循环引用的问题

3、__block会持有该对象,即使超出了该对象的作用域,该对象还是会存在的,直到block对象从堆上销毁;而__weak仅仅是将该对象赋值给weak对象,当该对象销毁时,weak对象将指向nil

4、__block可以让block修改局部变量,而__weak不能。