一些问题

文章目录

蒙提霍尔问题

也叫三门问题,这里就抄抄维基百科的度娘百科的答案

三门问题(Monty Hall problem)亦称为蒙提霍尔问题、蒙特霍问题或蒙提霍尔悖论,大致出自美国的电视游戏节目Let's Make a Deal。问题名字来自该节目的主持人蒙提·霍尔(Monty Hall)。参赛者会看见三扇关闭了的门,其中一扇的后面有一辆汽车,选中后面有车的那扇门可赢得该汽车,另外两扇门后面则各藏有一只山羊。当参赛者选定了一扇门,但未去开启它的时候,节目主持人开启剩下两扇门的其中一扇,露出其中一只山羊。主持人其后会问参赛者要不要换另一扇仍然关上的门。问题是:换另一扇门会否增加参赛者赢得汽车的机会率?如果严格按照上述的条件,即主持人清楚地知道,哪扇门后是羊,那么答案是会。

换门的话,赢得汽车的机率是2/3。

链接:蒙提霍尔问题

NSTimer

官方说明:

You use the NSTimer class to create timer objects or, more simply, timers. A timer waits until a certain time interval has elapsed and then fires, sending a specified message to a target object. For example, you could create an NSTimer object that sends a message to a window, telling it to update itself after a certain time interval.

Timers work in conjunction with run loops. To use a timer effectively, you should be aware of how run loops operate—see NSRunLoop and Threading Programming Guide. Note in particular that run loops maintain strong references to their timers, so you don’t have to maintain your own strong reference to a timer after you have added it to a run loop.

A timer is not a real-time mechanism; it fires only when one of the run loop modes to which the timer has been added is running and able to check if the timer’s firing time has passed.

You specify whether a timer is repeating or non-repeating at creation time. A non-repeating timer fires once and then invalidates itself automatically, thereby preventing the timer from firing again.By contrast, a repeating timer fires and then reschedules itself on the same run loop.

A repeating timer always schedules itself based on the scheduled firing time, as opposed to the actual firing time. For example, if a timer is scheduled to fire at a particular time and every 5 seconds after that, the scheduled firing time will always fall on the original 5 second time intervals, even if the actual firing time gets delayed. If the firing time is delayed so far that it passes one or more of the scheduled firing times, the timer is fired only once for that time period; the timer is then rescheduled, after firing, for the next scheduled firing time in the future.

A timer object can be registered in only one run loop at a time, although it can be added to multiple run loop modes within that run loop.

Once scheduled on a run loop, the timer fires at the specified interval until it is invalidated. A non-repeating timer invalidates itself immediately after it fires. However, for a repeating timer, you must invalidate the timer object yourself by calling its invalidate method. Calling this method requests the removal of the timer from the current run loop; as a result, you should always call the invalidate method from the same thread on which the timer was installed. Invalidating the timer immediately disables it so that it no longer affects the run loop. The run loop then removes the timer (and the strong reference it had to the timer), either just before the invalidate method returns or at some later point. Once invalidated, timer objects cannot be reused.

可见:

一个timer创建后,会在特定时间间隔后,向对象发送消息;

一个timer必须加入到一个runloop中,才能执行,且runloop会强引用timer;

一个timer只有在runloop运行,且能检查timer的时间间隔过去后,才会让timer向特定对象发送消息;

一个重复执行的timer如果错过执行时间,即使错过数个,也只在那段时间间隔内,向对象发送一次消息,然后进入下次循环;

调用schedule开头的初始化方法,在初始化一个timer后,会加入到当前的runloop中,其余的需要手动加入;

非重复timer会自动从runloop中移除,重复执行的timer需要手动移除,调用了invalidate后,会解除runloop对timer的强引用,以及timer对传入对象的强引用;

循环引用问题?

来看下一个初始化方法

1+ (NSTimer *)timerWithTimeInterval:(NSTimeInterval)ti 
2                            target:(id)aTarget 
3                          selector:(SEL)aSelector 
4                          userInfo:(id)userInfo 
5                           repeats:(BOOL)yesOrNo;

ti

The number of seconds between firings of the timer. If ti is less than or equal to 0.0, this method chooses the nonnegative value of 0.1 milliseconds instead.

target

The object to which to send the message specified by aSelector when the timer fires. The timer maintains a strong reference to this object until it (the timer) is invalidated.

aSelector

The message to send to target when the timer fires.

The selector should have the following signature: timerFireMethod: (including a colon to indicate that the method takes an argument). The timer passes itself as the argument, thus the method would adopt the following pattern:

1- (void)timerFireMethod:(NSTimer *)timer

userInfo

Custom user info for the timer.

The timer maintains a strong reference to this object until it (the timer) is invalidated. This parameter may be nil.

repeats

If YES, the timer will repeatedly reschedule itself until invalidated. If NO, the timer will be invalidated after it fires.

Return Value

A new NSTimer object, configured according to the specified parameters.

Discussion

You must add the new timer to a run loop, using addTimer:forMode:. Then, after ti seconds have elapsed, the timer fires, sending the message aSelector to target. (If the timer is configured to repeat, there is no need to subsequently re-add the timer to the run loop.)

可以看到,会被timer强引用的有target与userInfo

然后看看invalidate方法

invalidate

Stops the receiver from ever firing again and requests its removal from its run loop.

Discussion

This method is the only way to remove a timer from an NSRunLoop object. The NSRunLoop object removes its strong reference to the timer, either just before the invalidate method returns or at some later point.

If it was configured with target and user info objects, the receiver(在这里也就是timer) removes its strong references to those objects as well.

Special Considerations

You must send this message from the thread on which the timer was installed. If you send this message from another thread, the input source associated with the timer may not be removed from its run loop, which could prevent the thread from exiting properly.

是啊,也就是说,如果正常初始化了,也调用了invalidate,那么是不会有内存泄露或循环引用的问题,那么,在哪里会出现这种问题呢?

忘记调用invalidate的时候,或者,由于某种原因invalidate没有被系统正常调用的时候

不调用invalidate是不行的,但不让被引用对象的引用计数增加是可以的,所以我觉得当时自己被问的有点蒙了,寻找一个不调用invalidate的方法,本来就错了,归根到底,还是对API不熟悉,一般来说,遇上不常使用的东西,都会坑爹;

一个对象引用计数为0时,才调用dealloc,如果在这个方法中调用invalidate,是无法执行到的,因为对象的引用计数一直大于0,所以只在包含NSTimer成员变量的对象的dealloc方法中调用invalidate方法,是永远无法执行到的,必须手动触发;

也许可以考虑传入target的weak引用,但这个无效,timer还是会对target强引用,让它的引用计数增加,就像retain一个autorelease对象一样;

那么只要让target的引用计数不增加就行了,然后在它的dealloc中调用invalidate方法,这里有一个使用block的例子: 使用block

 1+ (void)eoc_scheduledTimerWithTimeInterval:(NSTimeInterval)interval
 2 block:(void(^)())block
 3 repeats:(BOOL)repeats
 4{
 5  return [self scheduledTimerWithTimeInterval:interval
 6  target:self
 7  selector:@selector(eoc_blockInvoke:)
 8  userInfo:[block copy]
 9  repeats:repeats];
10}
11
12+ (void)eoc_blockInvoke:(NSTimer*)timer {
13  void (^block)() = timer.userInfo;
14    if (block) {
15    block();
16  }
17}

这个方法首先为NSTimer创建一个类别,添加了一个方法

首先获取A对象的一个weak引用,将A对象调用方法的行为封装为一个block

PS:多次访问Self来执行方法时,需要在block中做转换,使用strongSelf,防止访问时,可能已经释放self,特别是在block的迭代层数过深时;

然后将它传入创建的NSTimer方法中,再在NSTimer内部调用另一个方法,执行block,这样对target和userInfo的强引用,转化为对timer自身与block的强引用,可以将对象桥接为CoreFoudation类型,然后验证引用计数的变化。

1NSLog(@"%ld",CFGetRetainCount((__bridge CFTypeRef)Object));

那么这样还会不会有问题呢?继续讨论

补充:关于Block的一些笔记

在栈上生成的Block不能强引用一个对象,因为栈上的资源由系统自动管理,资源的申请与销毁都是自动的,无法控制,如果由它来强引用一个对象,我们无法保证下次引用该对象时,该对象是否已经因为失去引用而被销毁。但是如果对Block调用copy方法,则会把Block复制到堆上(首次复制,接下来只增加引用计数),这样就交付我们手动管理(虽然ARC几乎已经完成了全部管理),此时会同时存在两个Block,而它的__forwarding成员变量都指向堆上的那个Block,如此一来,即使调用原先栈上的Block,也会使用堆上的那个,就不会引起歧义。另外一点,如果在Block中访问本类的成员变量,也会对self做retain操作,因为Objective-C对象本身就是一个结构体,直接对成员变量的访问与调用点语法都会引起对self的retain操作,此时引用循环只会在self对Block有直接或间接引用时才会发生,对于在栈上生成的没有被引用的Block来说,是不回发生循环引用的。内存管理的本质是对一个个内存块的管理,底层的实现依赖于引用计数与C/C++的内存管理机制。

NSRunLoop

官方文档:

Your application cannot either create or explicitly manage NSRunLoop objects. Each NSThread object, including the application’s main thread, has an NSRunLoop object automatically created for it as needed. If you need to access the current thread’s run loop, you do so with the class method currentRunLoop.

The NSRunLoop class is generally not considered to be thread-safe and its methods should only be called within the context of the current thread. You should never try to call the methods of an NSRunLoop object running in a different thread, as doing so might cause unexpected results.

The purpose of a run loop is to keep your thread busy when there is work to do and put your thread to sleep when there is none.