互联网科技

互联网iOS 面试题 Objective-C

作者:金沙国际官网    发布时间:2020-04-27 18:31     浏览次数 :152

[返回]

原因在于滑动时当前线程的runloop切换了mode用于列表滑动,导致timer暂停。

  1. 请说明并比较以下关键词:strong,weak, assign, copy

互联网 1

runloop中的mode主要用来指定事件在runloop中的优先级,有以下几种:

  • strong表示指向并拥有该对象。其修饰的对象引用计数会增加1。该对象只要引用计数不为0则不会被销毁。当然强行将其设为nil可以销毁它。
  • weak表示指向但不拥有该对象。其修饰的对象引用计数不会增加。无需手动设置,该对象会自行在内存中销毁。
  • assign主要用于修饰基本数据类型,如NSInteger和CGFloat,这些数值主要存在于栈上。
  • weak 一般用来修饰对象,assign一般用来修饰基本数据类型。原因是assign修饰的对象被释放后,指针的地址依然存在,造成野指针,在堆上容易造成崩溃。而栈上的内存系统会自动处理,不会造成野指针。
  • copy与strong类似。不同之处是strong的复制是多个指针指向同一个地址,而copy的复制每次会在内存中拷贝一份对象,指针指向不同地址。copy一般用在修饰有可变对应类型的不可变对象上,如NSString, NSArray, NSDictionary。
  • Objective-C 中,基本数据类型的默认关键字是atomic, readwrite, assign;普通属性的默认关键字是atomic, readwrite, strong。

我们在使用timer的时候多多少少都遇到过一些坑,今天就来说说timer使用中的那些坑

Default(NSDefaultRunLoopMode):默认,一般情况下使用;Connection(NSConnectionReplyMode):一般系统用来处理NSConnection相关事件,开发者一般用不到;Modal(NSModalPanelRunLoopMode):处理modal panels事件;Event Tracking(NSEventTrackingRunLoopMode):用于处理拖拽和用户交互的模式。Common(NSRunloopCommonModes):模式合集。默认包括Default,Modal,Event Tracking三大模式,可以处理几乎所有事件。
  1. 请说明并比较以下关键词:__weak__block
@implementation SecondController { NSTimer *_testTimer;}- viewDidLoad { _testTimer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(handleTestTimer) userInfo:nil repeats:NO];}- dealloc { NSLog(@"dealloc!"); [_testTimer invalidate];}- handleTestTimer { NSLog(@"hello world!");}@end

回到题中的情境。滑动列表时,runloop的mode由原来的Default模式切换到了Event Tracking模式,timer原来好好的运行在Default模式中,被关闭后自然就停止工作了。

  • __weak与weak基本相同。前者用于修饰变量(variable),后者用于修饰属性(property)。__weak主要用于防止block中的循环引用。
  • __block也用于修饰变量。它是引用修饰,所以其修饰的值是动态变化的,即可以被重新赋值的。__block用于修饰某些block内部将要修改的外部变量。
  • __weak和__block的使用场景几乎与block息息相关。而所谓block,就是Objective-C对于闭包的实现。闭包就是没有名字的函数,或者理解为指向函数的指针。

我们可能写过类似上面的代码,一般情况下它是可以正常执行的,我们并没有过多的去想timer的问题,但是实际上这样写是有问题的。如果创建timer时repeats:YES,再运行的话,我们发现dealloc函数就永远不会调用了(我们这里SecondController是被另一个vc push进来的)。

解决方法其一是将timer加入到NSRunloopCommonModes中。其二是将timer放到另一个线程中,然后开启另一个线程的runloop,这样可以保证与主线程互不干扰,而现在主线程正在处理页面滑动。示例代码如下:

  1. 请说明并比较以下关键词:atomatic, nonatomic

引起这个问题的原因就是:timer会强引用自己的target,在上面的例子中,我们的vc是强引用_testTimer对象的,但是创建这个timer的时候我们的target传入的是self,此时就导致了tiemr也强引用了self,导致循环引用的产生。

// 方法1[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];// 方法2dispatch_async(dispatch_get_global_queue, ^{ timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector userInfo:nil repeats:true]; [[NSRunLoop currentRunLoop] run];});
  • atomic修饰的对象会保证setter和getter的完整性,任何线程对其访问都可以得到一个完整的初始化后的对象。因为要保证操作完成,所以速度慢。它比nonatomic安全,但也并不是绝对的线程安全,例如多个线程同时调用set和get就会导致获得的对象值不一样。绝对的线程安全就要用 @synchronize。
  • nonatomic修饰的对象不保证setter和getter的完整性,所以多个线程对它进行访问,它可能会返回未初始化的对象。正因为如此,它比atomic快,但也是线程不安全的。

准确的说,timer在isValid为YES的时候是强引用自己的target的,所以一般我们都把invalidate的时机放在viewWillDisappear:或viewDidDisappear:的时候,这样vc就会正常释放了。

  1. 什么是ARC?
@implementation SecondController { NSTimer *_testTimer;}- viewDidLoad { [super viewDidLoad]; _testTimer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(handleTestTimer) userInfo:nil repeats:YES];}- viewWillDisappear:animated { [super viewWillDisappear:animated]; if (_testTimer.isValid) { [_testTimer invalidate]; }}- dealloc { NSLog(@"dealloc!");}- handleTestTimer { NSLog(@"hello world!");}@end
  • ARC全称是 Automatic Reference Counting,是Objective-C的内存管理机制。简单地来说,就是代码中自动加入了retain/release,原先需要手动添加的用来处理内存管理的引用计数的代码可以自动地由编译器完成了。
  • ARC的使用是为了解决对象retain和release匹配的问题。以前手动管理造成内存泄漏或者重复释放的问题将不复存在。
  • 以前需要手动的通过retain去为对象获取内存,并用release释放内存。所以以前的操作称为MRC (Manual Reference Counting)。

上面这样就可以正常释放了,但这里还要说一点:

  1. 什么情况下会出现循环引用?
    循环引用是指2个或以上对象互相强引用,导致所有对象无法释放的现象。这是内存泄漏的一种情况。举个例子:

循环引用和内存泄露还是稍有不同的

就拿我们的timer举例,如果我们在创建timer的时候repeats:NO,但是触发的时机是30s,但是我们在这个vc中停留小于30s,就会造成暂时的循环引用,但是这种情况也不能说是内存泄露,从我们退出vc开始到timer触发时的这一段时间内,vc和timer造成了循环引用,但是当timer触发后,你会发现,vc的dealloc也被调用了,此时vc和timer的内存都能够得到释放,循环引用是有暂时性的,所以要理解循环引用和内存泄露是稍有不同的。

class Father
@interface Father: NSObject
@property (strong, nonatomic) Son *son;
@end
class Son
@interface Son: NSObject
@property (strong, nonatomic) Father *father; 
@end

可能有人会想,为什么非要用一个全部变量呢?直接:

上述代码有两个类,分别为爸爸和儿子。爸爸对儿子强引用,儿子对爸爸强引用。这样释放儿子必须先释放爸爸,要释放爸爸必须先释放儿子。如此一来,两个对象都无法释放。

[NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(handleTestTimer) userInfo:nil repeats:YES];

解决方法是将Father中的Son对象属性从strong改为weak。

不引入任何全局或局部变量不行吗?答案是不行,即使像上面那样,创建了timer,虽然没有引入任何变量,但是依然会产生循环引用,如果repeats:YES,就会造成内存泄露了。