iOS 四月份百度面试总结

412 阅读11分钟

1.blcok相关知识?

在ARC环境下,编译器会根据情况自动将栈上的block进行一次copy操作,将block复制到堆上。

//block拷贝到堆上的几种情况:

  1. 调用block的copy实例方法
  2. Block作为函数返回值
  3. 将block赋值给有__strong修饰符的id类型的类或block类型成员变量时
  4. 在方法名中含有usingBlock的cocoa框架方法或GCD的API传递blokc时<code>

我们使用block有两种方式,逃逸和非逃逸(借用swift中的说法)。

非逃逸:声明的block的生命周期就是声明所在的函数体的生命周期。我们在函数体中声明一个block,这个block会在函数体结束时释放。

逃逸:声明的block生命周期和声明所在的函数体无关了。我们在函数A中声明的block,在B中也可以调用。

1.NSGlobalBlock (不捕获自动变量的类型或者捕获的是静态局部变量)

此处指的不捕获自动变量,变量不包含全局变量,因为全局变量的特殊生命周期,不需要捕获,也可以在block中访问。

1.值捕获:捕获的变量为其指针指向的值,或基础数据类型的值

指针指向的值:

2.地址捕获:捕获的变量为其指针本身,或指向基础数据类型的指针

4.解决循环引用,打破block对对象的强引用即可,两种方式:__weak对象,__block对象(需在block内将变量主动置空)

对于 MRC 环境,使用 Copy 修饰 Block,会将栈区的 Block 拷贝到堆区。

对于 ARC 环境,使用 Strong、Copy 修饰 Block,都会将栈区的 Block 拷贝到堆区。

所以,Block 不是一定要用 Copy 来修饰的,在 ARC 环境下面 Strong 和 Copy 修饰效果是一样的。

补充:一个block要使用self,会处理成在外部声明一个weak变量指向self,然而为何有时会出现在block里又声明一个strong变量指向weakSelf?

原因:block会把写在block里的变量copy一份,如果直接在block里使用self,(self对变量默认是强引用)self对block持有,block对self持有,导致循环引用,所以这里需要声明一个弱引用weakSelf,让block引用weakSelf,打破循环引用。

而这样会导致另外一个问题,因为weakSelf是对self的弱引用,如果这个时候控制器pop或者其他的方式引用计数为0,就会释放,如果这个block是异步调用而且调用的时候self已经释放了,这个时候weakSelf已就变成了nil。

当控制器(也可以是其他的控件)pop回来之后(或者一些其他的原因导致释放),网络请求完成,如果这个时候需要控制器做出反映,需要strongSelf再对weakSelf强引用一下。

但是,你可能会疑问,strongSelf对weakSelf强引用,weakSelf对self弱引用,最终不也是对self进行了强引用,会导致循环引用吗。不会的,因为strongSelf是在block里面声明的一个指针,当block执行完毕后,strongSelf会释放,这个时候将不再强引用weakSelf,所以self会正确的释放。

2.数组的深拷贝和浅拷贝?

@property (nonatomic, strong) NSArray *array0;

@property (nonatomic, copy) NSArray *array1;

@property (nonatomic, strong) NSMutableArray *array2;

@property (nonatomic, copy) NSMutableArray *array3;

第一种写法不推荐使用,是对传递对象的强引用,不管是传递 NSArray 还是 NSMutableArray 对象都是多了一个强引用的指针而已。当外面传递的是 NSMutableArray 对象,在该类中使用该属性时就要注意外面也可能随时修改该对象。

第二种写法为推荐写法,如果传递的是 NSArray 对象,则只是对原先对象的一份强引用(应该是编译器优化的),但是如果传递的是 NSMutableArray 对象,则是对原先对象的一次“单层深拷贝”,生成的 NSArray 对象是一份新内存地址的对象,但是其中的元素还是原先的。

第三种写法为推荐写法,是对传递 NSMutableArray 对象的一个强引用。该类中使用该属性时要注意外面也可能随时修改该对象。

第四种写法为错误写法,是对传递 NSMutableArray 对象的一个“单层深拷贝”,而且生成的对象是 NSArray 类型而不是 NSMutableArray 类型,在该类中对该属性做增删操作就会出现unrecognized method send to … 引发crash。

NSString 与 NSMutableString 和上面的结论是一样的,只是没有单层深拷贝的概念。

3. 浅拷贝、单层深拷贝、深拷贝

浅拷贝

所谓的浅拷贝,就是指只是将对象内存地址多了一个引用,也就是说,拷贝结束之后,两个对象的值不仅相同,而且对象所指的内存地址都是一样的。

单层深拷贝

对于不可变的容器类对象(如NSArray、NSSet、NSDictionary)进 mutableCopy 操作,内存地址发生了变化,但是其中的元素内存地址并没有发生变化,属于单层深拷贝。

对于可变集合类对象(如NSMutableArray、NSMutableSet、NSMutableDictionary),不管是进行 copy 操作还是 mutableCopy 操作,其内存地址都发生了变化,但是其中的元素内存地址都没有发生变化,属于单层深拷贝。

深拷贝

所谓深拷贝,就是指拷贝一个对象的具体内容,拷贝结束之后,两个对象的值虽然是相同的,但是指向的内存地址是不同的。两个对象之间也互不影响,互不干扰。

对 NSArray 进行 copy 操作的时候,数组的内存地址没有发生变化,但是进行 mutableCopy 操作时,其内存地址发生了变化,结论跟非集合类的差不多。

但是,这里的深拷贝和非集合类的深拷贝还是不太一样的,上面我们打印出了数组的第一个元素的内存地址,可以发现,进行 mutableCopy 操作时,虽然数组内存地址发生了变化,但是数组元素的内存地址并没有发生变化。

这个属于一个特例,我们称它为单层深复制。并不是理论上的完全深复制。

对 NSMutableArray 进行 copy 和 mutableCopy 操作,其内存地址都发生了变化,但是,对于数组中的元素,不管是进行的哪种操作,内存地址始终都没有发生变化,所以属于单层深拷贝。

所以,我们可以得出,对于不可变的集合类对象进行 copy 操作,只是改变了指针,其内存地址并没有发生变化;进行 mutableCopy 操作,内存地址发生了变化,但是其中的元素内存地址并没有发生变化。

对于可变集合类对象,不管是进行 copy 操作还是 mutableCopy 操作,其内存地址都发生了变化,但是其中的元素内存地址都没有发生变化,属于单层深拷贝。

深拷贝就是内容拷贝,浅拷贝就是指针拷贝。本质区别在于:

是否开启新的内存地址

是否影响内存地址的引用计数

特别注意的是:对于集合类的可变对象来说,深拷贝并非严格意义上的深复制,只能算是单层深复制,即虽然新开辟了内存地址,但是存放在内存上的值(也就是数组里的元素仍然之乡员数组元素值,并没有另外复制一份),这就叫做单层深复制。

No1:可变对象的copy和mutableCopy方法都是深拷贝(区别完全深拷贝与单层深拷贝) 。

No2:不可变对象的copy方法是浅拷贝,mutableCopy方法是深拷贝。

No3:copy方法返回的对象都是不可变对象。

在修改原值之前,marry1、marry2、marr3 地址都不一样,很明显copy和mutableCopy都是深拷贝,但是从修改原值后的打印结果来看,这里的深拷贝只是单层深拷贝:新开辟了内存地址,但是数组中的值还是指向原数组的,这样才能在修改原值后,marry2 marr3中的值都修改了。另外,从打印的数组元素地址可以很明显的看出来,修改前后marry1、marry、marr3的数组元素地址都是一模一样的,更加佐证了这一点。

但是修改数组的元素的个数,只有当前数组的个数会改变,数组之间不会互相影响,修改元素的值会互相影响 ,所有的数组的元素的值都会改变

[mstr1 appendFormat:@"aaa"];这样修改元素的值,所有数组的元素都会修改。

[marry3 replaceObjectAtIndex:0 withObject:@"value1---"];这样修改元素的值,只有当前数组的元素会修改。

3.隐式动画和显式动画的区别?

显式动画是指用户自己通过beginAnimations:context:和commitAnimations创建的动画。

隐式动画是指通过UIView的animateWithDuration:animations:方法创建的动画。

动画事务--CATransaction

隐式动画一直存在 如需关闭需设置;显式动画是不存在,如需显式 要开启(创建)。

显式动画是指用户自己通过beginAnimations:context:和commitAnimations创建的动画。隐式动画是指通过UIView的animateWithDuration:animations:方法创建的动画。

隐式动画是系统框架自动完成的。Core Animation在每个runloop周期中自动开始一次新的事务,即使你不显式的用[CATransaction begin]开始一次事务,任何在一次runloop循环中属性的改变都会被集中起来,然后做一次0.25秒的动画。在iOS4中,苹果对UIView添加了一种基于block的动画方法:+animateWithDuration:animations:。这样写对做一堆的属性动画在语法上会更加简单,但实质上它们都是在做同样的事情。CATransaction的+begin和+commit方法在+animateWithDuration:animations:内部自动调用,这样block中所有属性的改变都会被事务所包含

4.给对象赋值nil是做了什么操作?

nil在字典,数组中有特殊含义–元素结束标记

5.自动释放池的相关知识?

每一个自动释放池都是由一系列的 AutoreleasePoolPage 组成的,并且每一个 AutoreleasePoolPage 的大小都是 4096 字节(16 进制 0x1000)自动释放池中的 AutoreleasePoolPage 是以双向链表的形式连接起来的:

autorelease 方法

NSAutoreleasePool*pool = [[NSAutoreleasePoolalloc]init ];//创建一个自动释放池

Person *person = [[Person alloc]init];

//调autorelease方法将对象加入到自动释放池//注意使用该方法,对象不会自己加入到自动释放池,需要人为调用autorelease方法加入

[person autorelease];

//,手动释放自动释放池执行完这行代码是,自动释放池会对加入他中的对象做一次release操作

[pool release];

自动释放池销毁时机:[pool release]代码执行完后

每一个自动释放池没有单独的结构,每一个autorealeasePool对象都是由若干个个autoreleasePoolPage通过双向链表连接而成,当一个对象调用了autorelease方法,这个对象就会被加入到当前自动释放池的最新的autoreleasePoolPage中,关于autoreleasePoolPage/,请看下面

当我们向自动释放池 pool 发送 release 消息,将会向池中临时对象发送一条 release 消息,并且自身也会被销毁。

一、autorelease 对象会在什么时候释放?

分两种情况:

使用 @autoreleasepool,会在大括号结束时释放

不使用 @autoreleasepool,这个会由系统自动释放,释放时机是在当前 runloop 结束时释放,因为系统会自动为每个 runloop 执行自动释放池的 push 和 pop 操作

Autorelease对象什么时候释放?

这个问题拿来做面试题,问过很多人,没有几个能答对的。很多答案都是“当前作用域大括号结束时释放”,显然木有正确理解Autorelease机制。

在没有手加Autorelease Pool的情况下,Autorelease对象是在当前的runloop迭代结束时释放的,而它能够释放的原因是系统在每个runloop迭代中都加入了自动释放池Push和Pop

ARC下,我们使用@autoreleasepool{}来使用一个AutoreleasePool,随后编译器将其改写成下面的样子:

void *context = objc_autoreleasePoolPush();

// {}中的代码

objc_autoreleasePoolPop(context);

而这两个函数都是对AutoreleasePoolPage的简单封装,所以自动释放机制的核心就在于这个类。