Run Loops

news2024/10/5 22:22:35

Run Loops


运行循环是与线程相关的基本基础结构的一部分。运行循环是事件处理循环,用于安排工作并协调传入事件的接收。运行循环的目的是在有工作要做时让线程保持忙碌,在没有工作要做时让线程休眠。

运行循环管理不是完全自动的。您仍然必须设计线程代码以在适当的时间启动运行循环并响应传入事件。Cocoa和Core Foundation都提供了运行循环对象来帮助您配置和管理线程的运行循环。您的应用程序不需要显式地创建这些对象;每个线程,包括应用程序的主线程,都有一个相关的运行循环对象。但是,只有子要线程需要显式地运行它们的run循环。作为应用程序启动过程的一部分,应用程序框架会自动在主线程上设置并运行run循环。

以下部分提供了有关运行循环以及如何为应用程序配置它们的更多信息。有关运行循环对象的更多信息,请参见NSRunLoop类参考和CFRunLoop参考。

Anatomy of a Run Loop

运行循环在线程进入并用于运行事件处理程序以响应传入事件的循环。您的代码提供了用于实现运行循环的实际循环部分的控制语句——换句话说,您的代码提供了驱动运行循环的while或for循环。在循环中,使用运行循环对象“运行”接收事件并调用已安装处理程序的事件处理代码。

运行循环从两种不同类型的源接收事件。输入源提供异步事件,通常是来自另一个线程或不同应用程序的消息。计时器源提供同步事件,在预定时间或重复间隔发生。这两种类型的源都使用特定于应用程序的处理程序例程来处理事件。

图3-1展示了运行循环和各种源的概念结构。输入源将异步事件传递给相应的处理程序,并因runUntilDate:方法(在线程的相关NSRunLoop对象上调用)退出。计时器源将事件传递给它们的处理程序例程,但不会导致运行循环退出。

 除了处理输入源外,运行循环还生成关于运行循环行为的通知。已注册的运行循环观察者可以接收这些通知,并使用它们在线程上执行额外的处理。您可以使用Core Foundation在线程上安装运行循环观察器。

以下部分提供了有关运行循环组件及其操作模式的更多信息。它们还描述了在处理事件期间在不同时间生成的通知。

Run Loop Modes

运行循环模式是要监视的输入源和计时器的集合,以及要通知的运行循环观察器的集合。每次运行运行循环时,都指定(显式或隐式)运行的特定“模式”。在运行循环的传递过程中,只有与该模式关联的源被监视并允许传递它们的事件。(类似地,只有与该模式相关的观察者才会被通知运行循环的进度。)与其他模式关联的源保留任何新事件,直到后续的事件以适当的模式通过循环。

在代码中,通过名称标识模式。Cocoa和Core Foundation都定义了一个默认模式和几个常用模式,以及用于在代码中指定这些模式的字符串。您可以通过简单地为模式名指定一个自定义字符串来定义自定义模式。尽管分配给自定义模式的名称是任意的,但这些模式的内容不是。您必须确保将一个或多个输入源、计时器或运行循环观察器添加到您为它们创建的任何模式中,以便它们发挥作用。

在运行循环的特定传递过程中,使用模式从不需要的源中过滤事件。大多数情况下,您希望以系统定义的“默认”模式运行运行循环。然而,模态面板可能以“模态”模式运行。在这种模式下,只有与模态面板相关的源才会将事件传递给线程。对于辅助线程,可以使用自定义模式来防止低优先级源在时间关键型操作期间传递事件。

表3-1列出了Cocoa和Core Foundation定义的标准模式以及使用该模式的说明。name列列出了用于在代码中指定模式的实际常量。

ModeNameDescription
Default

NSDefaultRunLoopMode (Cocoa)

kCFRunLoopDefaultMode (Core Foundation)

默认模式是用于大多数操作的模式。大多数情况下,您应该使用此模式启动运行循环并配置输入源。
ConnectionNSConnectionReplyMode (Cocoa)Cocoa将此模式与NSConnection对象一起使用来监视响应,一般很少需要使用这种模式。
Modal

NSModalPanelRunLoopMode (Cocoa)

Cocoa使用此模式来标识用于模式面板的事件。

Event tracking

NSEventTrackingRunLoopMode (Cocoa)

Cocoa使用此模式在鼠标拖动循环和其他类型的用户界面跟踪循环期间限制传入事件。

Common modes

NSRunLoopCommonModes (Cocoa)

kCFRunLoopCommonModes (Core Foundation)

这是一组可配置的常用模式。将输入源与此模式相关联也会将其与组中的每个模式相关联。对于Cocoa应用程序,这个集合默认包括默认模式、模式和事件跟踪模式。Core Foundation最初只包含默认模式。您可以使用CFRunLoopAddCommonMode函数向集合中添加自定义模式。

Input Sources

输入源将事件异步地传递给线程。事件的来源取决于输入源的类型,通常是两类之一。基于端口的输入源监视应用程序的Mach端口。自定义输入源监视自定义事件源。就运行循环而言,输入源是基于端口的还是自定义的并不重要。系统通常实现两种类型的输入源,您可以按原样使用它们。这两个源之间唯一的区别是它们的信号方式。基于端口的源由内核自动发出信号,而自定义源必须从另一个线程手动发出信号。

创建输入源时,将其分配给运行循环的一个或多个模式。模式影响在任何给定时刻监视哪些输入源。大多数情况下,以默认模式运行运行循环,但也可以指定自定义模式。如果输入源不在当前监视模式中,则它生成的任何事件都将保持,直到运行循环以正确的模式运行为止。

下面几节描述了一些输入源。

Port-Based Sources

Cocoa和Core Foundation为使用端口相关对象和函数创建基于端口的输入源提供了内置支持。例如,在Cocoa中,你根本不需要直接创建一个输入源。您只需创建一个端口对象,并使用NSPort的方法将该端口添加到运行循环。端口对象为您处理所需输入源的创建和配置。

在Core Foundation中,必须手动创建端口及其运行循环源。在这两种情况下,您都使用与端口不透明类型(CFMachPortRef、CFMessagePortRef或CFSocketRef)关联的函数来创建适当的对象。

eg:

    NSPort* myPort = [NSMachPort port];
    if (myPort)

    {
        // This class handles incoming port messages.
        [myPort setDelegate:self];

        // Install the port as an input source on the current run loop.
        [[NSRunLoop currentRunLoop] addPort:myPort forMode:NSDefaultRunLoopMode];

        // Detach the thread. Let the worker release the port.
        [NSThread detachNewThreadSelector:@selector(LaunchThreadWithPort:)
               toTarget:[MyWorkerClass class] withObject:myPort];
    }
}

Custom Input Sources

要创建自定义输入源,必须使用Core Foundation中与CFRunLoopSourceRef不透明类型关联的函数。您可以使用几个回调函数配置自定义输入源。Core Foundation在不同的位置调用这些函数来配置源、处理任何传入事件,并在源从运行循环中删除时将其删除。

除了定义事件到达时自定义源的行为外,还必须定义事件交付机制。源的这一部分运行在单独的线程上,负责向输入源提供其数据,并在数据准备好进行处理时向输入源发出信号。事件交付机制由您决定,但不必过于复杂。

Cocoa Perform Selector Sources

除了基于端口的源,Cocoa还定义了一个自定义输入源,允许您在任何线程上执行选择器。与基于端口的源一样,执行选择器请求在目标线程上序列化,减轻了在一个线程上运行多个方法时可能出现的许多同步问题。与基于端口的源不同,执行选择器源在执行它的选择器后将自己从运行循环中移除。

当在另一个线程上执行选择器时,目标线程必须有一个活动的运行循环。对于您创建的线程,这意味着要等到代码显式地启动运行循环。但是,因为主线程启动了自己的运行循环,所以只要应用程序调用应用程序委托的applicationDidFinishLaunching:方法,您就可以开始对该线程发出调用。运行循环在每次循环中处理所有排队的执行选择器调用,而不是在每次循环迭代中处理一次。

下列出了NSObject中定义的方法,这些方法可用于在其他线程上执行选择器。因为这些方法是在NSObject上声明的,所以你可以在任何可以访问Objective-C对象的线程中使用它们,包括POSIX线程。这些方法实际上并不创建一个新线程来执行选择器。

MethodsDescription

performSelectorOnMainThread:withObject:waitUntilDone:

performSelectorOnMainThread:withObject:waitUntilDone:modes:

在应用程序主线程的下一个运行循环周期中,在该主线程上执行指定的选择器。这些方法提供了阻塞当前线程的选项,直到执行选择器为止。

performSelector:onThread:withObject:waitUntilDone:

performSelector:onThread:withObject:waitUntilDone:modes:

在你有NSThread对象的任何线程上执行指定的选择器。这些方法提供了阻塞当前线程的选项,直到执行选择器为止。

performSelector:withObject:afterDelay:

performSelector:withObject:afterDelay:inModes:

在下一个运行循环周期期间和可选延迟期之后,对当前线程执行指定的选择器。因为它要等到下一个运行循环周期才能执行选择器,所以这些方法从当前执行的代码中自动提供了一个最小的延迟。多个队列选择器按照它们排队的顺序依次执行。

cancelPreviousPerformRequestsWithTarget:

cancelPreviousPerformRequestsWithTarget:selector:object:

让你取消一个消息发送到当前线程使用performSelector:withObject:afterDelay:或performSelector:withObject:afterDelay:inModes:方法。

The Run Loop Sequence of Events

每次运行时,线程的运行循环都会处理挂起的事件,并为任何附加的观察者生成通知。它这样做的顺序非常具体,如下所示:

  1. 通知观察者已经进入运行循环。
  2. 通知观察员任何准备就绪的计时器即将启动。
  3. 通知观察者任何非基于端口的输入源即将触发。
  4. 触发任何准备触发的非基于端口的输入源。
  5. 如果基于端口的输入源已经准备好并等待触发,则立即处理该事件。执行步骤9。
  6. 通知观察者线程即将进入睡眠状态。
  7. 让线程进入睡眠状态,直到发生以下事件之一:

    1.一个事件到达基于端口的输入源。

    2.计时器触发。

    3.为运行循环设置的超时值过期。

    4.运行循环被显式唤醒。

  8. 通知观察者线程刚刚被唤醒。
  9. 处理挂起事件。
  10. 通知观察者运行循环已经退出。

由于计时器和输入源的观察者通知是在这些事件实际发生之前交付的,因此通知的时间和实际事件的时间之间可能存在间隙。如果这些事件之间的时间间隔很重要,则可以使用sleep和从睡眠中醒来的通知来帮助关联实际事件之间的时间间隔。 

When Would You Use a Run Loop? 

只有在为应用程序创建辅助线程时,才需要显式地运行运行循环。应用程序主线程的运行循环是基础结构的关键部分。因此,应用程序框架提供了运行主应用程序循环的代码,并自动启动该循环。iOS中的UIApplication(或OS X中的NSApplication)的run方法作为正常启动序列的一部分启动应用程序的主循环。

对于子线程,您需要决定是否需要运行循环,如果需要,则自己配置并启动它。在所有情况下都不需要启动线程的run循环。例如,如果使用线程执行一些长时间运行的预定任务,则可能可以避免启动运行循环。运行循环适用于需要与线程进行更多交互的情况。例如,如果您计划执行以下任何操作,则需要启动一个运行循环:

  1. 使用端口或自定义输入源与其他线程通信。
  2. 在线程上使用计时器。
  3. 在Cocoa应用程序中使用任何performSelector…方法。
  4. 保持线程执行周期性任务。

Using Run Loop Objects


运行循环对象提供了向运行循环添加输入源、计时器和运行循环观察器并运行它的主接口。每个线程都有一个与之关联的运行循环对象。在Cocoa中,这个对象是NSRunLoop类的一个实例。在低级应用程序中,它是指向CFRunLoopRef不透明类型的指针。

Getting a Run Loop Object

要获得当前线程的运行循环,您可以使用以下方法之一:

  1. 在Cocoa应用程序中,使用NSRunLoop的currentRunLoop类方法来检索NSRunLoop对象。
    NSRunLoop *theLoop = [NSRunLoop currentRunLoop];
    
    [theLoop acceptInputForMode:NSDefaultRunLoopMode beforeDate:[theLoop 
        limitDateForMode:NSDefaultRunLoopMode]];
    
  2. 使用CFRunLoopGetCurrent函数。

虽然它们不是免费的桥接类型,但当需要时,你可以从NSRunLoop对象获得CFRunLoopRef不透明类型。NSRunLoop类定义了一个getCFRunLoop方法,它返回一个CFRunLoopRef类型,你可以把它传递给Core Foundation例程。因为两个对象都引用同一个运行循环,你可以根据需要混合调用NSRunLoop对象和CFRunLoopRef不透明类型

Configuring the Run Loop

在次要线程上运行运行循环之前,必须至少向其添加一个输入源或计时器。如果运行循环没有任何要监视的源,则在尝试运行它时立即退出。有关如何向运行循环添加源的示例,请参见配置运行循环源。

除了安装源之外,还可以安装运行循环观察器,并使用它们来检测运行循环的不同执行阶段。要安装运行循环观察器,您可以创建CFRunLoopObserverRef不透明类型,并使用CFRunLoopAddObserver函数将其添加到运行循环中。运行循环观察器必须使用Core Foundation创建,即使对于Cocoa应用程序也是如此。

清单3-1显示了将运行循环观察者附加到其运行循环的线程的主例程。该示例的目的是向您展示如何创建运行循环观察器,因此代码只是设置了一个运行循环观察器来监视所有运行循环活动。基本处理程序例程(未显示)只是在处理计时器请求时记录运行循环活动。

listing3-1Createa run loop observer

 在为长期存在的线程配置运行循环时,最好至少添加一个输入源来接收消息。虽然您可以只带一个附加的计时器进入运行循环,但一旦计时器触发,它通常会失效,这将导致运行循环退出。附加一个重复计时器可以使运行循环在更长的时间内运行,但这将涉及定期触发计时器以唤醒线程,这实际上是另一种轮询形式。相比之下,输入源等待事件发生,让线程处于睡眠状态,直到事件发生。

Starting the Run Loop

只有应用程序中的子线程才需要启动运行循环。运行循环必须至少有一个要监视的输入源或计时器。如果没有附加,则运行循环立即退出。

有几种方法可以启动运行循环,包括以下方法:

  1. Unconditionally
  2. With a set time limit
  3. In a particular mode
//[[NSRunLoop currentRunLoop] run];
//[[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:1]];
 [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
            

无条件地进入运行循环是最简单的选择,但也是最不可取的。无条件地运行运行循环将线程置于永久循环中,这使您对运行循环本身几乎没有控制。您可以添加和删除输入源和计时器,但是停止运行循环的唯一方法是终止它。也没有办法在自定义模式下运行运行循环。

与其无条件地运行运行循环,不如使用超时值运行运行循环。使用超时值时,运行循环将运行到事件到达或分配的时间到期为止。如果事件到达,则将该事件分派给处理程序进行处理,然后运行循环退出。然后,代码可以重新启动运行循环以处理下一个事件。如果分配的时间过期了,您可以简单地重新启动运行循环,或者使用这段时间做任何必要的管理工作。

除了超时值,还可以使用特定模式运行运行循环。模式和超时值并不互斥,在开始运行循环时都可以使用。模式限制向运行循环传递事件的源的类型,在运行循环模式中有更详细的描述。

清单3-2显示了线程主入口例程的框架版本。本例的关键部分展示了运行循环的基本结构。实际上,您将输入源和计时器添加到运行循环中,然后重复调用其中一个例程来启动运行循环。每次run循环例程返回时,您都要检查是否出现了任何可能导致退出线程的条件。该示例使用Core Foundation运行循环例程,以便它可以检查返回结果并确定运行循环退出的原因。如果你正在使用Cocoa并且不需要检查返回值,你也可以使用NSRunLoop类的方法以类似的方式运行运行循环。

Listing 3-2  Running a run loop

递归地运行运行循环是可能的。换句话说,你可以调用CFRunLoopRun, CFRunLoopRunInMode,或任何NSRunLoop方法,从输入源或定时器的处理程序例程中启动运行循环。这样做时,您可以使用想要运行嵌套运行循环的任何模式,包括外部运行循环使用的模式。 

Exiting the Run Loop

有两种方法可以让运行循环在处理事件之前退出:

  1. 将运行循环配置为使用超时值运行。
  2. 告诉运行循环停止。

如果可以管理,使用超时值当然是首选。指定超时值可以让运行循环在退出之前完成所有正常处理,包括向运行循环观察者发送通知。

使用CFRunLoopStop函数显式地停止运行循环会产生类似于超时的结果。运行循环发出所有剩余的运行循环通知,然后退出。不同之处在于,您可以在无条件启动的运行循环上使用此技术。

尽管删除运行循环的输入源和计时器也可能导致运行循环退出,但这不是停止运行循环的可靠方法。一些系统例程将输入源添加到运行循环中以处理所需的事件。因为您的代码可能不知道这些输入源,所以它将无法删除它们,这将阻止运行循环退出。

Thread Safety and Run Loop Objects

线程安全性取决于您使用哪个API来操作运行循环。Core Foundation中的函数通常是线程安全的,可以从任何线程调用。但是,如果您正在执行更改运行循环配置的操作,那么尽可能从拥有运行循环的线程执行操作仍然是一种良好的实践。

Cocoa NSRunLoop类并不像Core Foundation类那样具有内在的线程安全性。如果你正在使用NSRunLoop类来修改你的运行循环,你应该只在拥有该运行循环的同一个线程中这样做。向属于不同线程的运行循环添加输入源或计时器可能导致代码崩溃或以意想不到的方式运行。

Run Loop 常见用途


1.Creating and scheduling timers using NSTimer

NSRunLoop* myRunLoop = [NSRunLoop currentRunLoop];

// Create and schedule the first timer.
NSDate* futureDate = [NSDate dateWithTimeIntervalSinceNow:1.0];
NSTimer* myTimer = [[NSTimer alloc] initWithFireDate:futureDate
                        interval:0.1
                        target:self
                        selector:@selector(myDoFireTimer1:)
                        userInfo:nil
                        repeats:YES];
[myRunLoop addTimer:myTimer forMode:NSDefaultRunLoopMode];

// Create and schedule the second timer.
[NSTimer scheduledTimerWithTimeInterval:0.2
                        target:self
                        selector:@selector(myDoFireTimer2:)
                        userInfo:nil
                        repeats:YES];

2.检测卡顿

FPS检测

FPS :Frames Per Second 的简称缩写,意思是每秒传输帧数,FPS值越低就越卡顿,所以这个值在一定程度上可以衡量应用在图像绘制渲染处理时的性能。iOS系统中正常的屏幕刷新率为60Hz(60次每秒)。根据CADisplayLink 来查看屏幕刷新频率保持一致 一般情况保持50—60就认为还是可以的。

#import "FPSMonitor.h"
#import "UIKit/UIKit.h"
@interface FPSMonitor()
@property(nonatomic,strong) CADisplayLink *link;
@property(nonatomic,assign) NSInteger count;
@property(nonatomic,assign) NSTimeInterval lastTime;
@end

@implementation FPSMonitor
-(void)beginMonitor{
    _link = [CADisplayLink displayLinkWithTarget:self selector:@selector(fpsInfoCaculate:)];
    [_link addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes];
    
}
-(void)fpsInfoCaculate:(CADisplayLink *)sender{
    if (_lastTime==0) {
        _lastTime = sender.timestamp;
    }
    _count ++;
    double deltaTime = sender.timestamp - _lastTime;
    if (deltaTime >= 1) {
        NSInteger FPS = _count/deltaTime;
        _lastTime = sender.timestamp;
        _count = 0;
        NSLog(@"FPS:%li",(NSInteger)ceil(FPS+0.5));
    }
}

runloop监控:

通过RunLoop知道主线程上都调用了哪些方法,通过监听 nsrunloop 的状态,知道调用方法是否执行时间过长,从而判断出是否卡顿。根据RunLoop从任务开始beforeSoure 到任务任务结束 beforeWating 如果时间过长 ,基本认为卡顿了。

//
//  RunloopMonitor.m
//  DemoTest2022
//
//  Created by wy on 2022/6/22.
//

#import "RunloopMonitor.h"
@interface RunloopMonitor()
@property(nonatomic,strong) dispatch_semaphore_t dispatchSemaphore;
@property(nonatomic,assign) CFRunLoopObserverRef runLoopBeginObserver;
@property(nonatomic,assign) CFRunLoopObserverRef runLoopEndObserver;
@property(nonatomic,assign) CFRunLoopActivity runLoopBeginActivity;
@property(nonatomic,assign) CFRunLoopActivity runLoopEndActivity;
@property(nonatomic,assign) float timeoutCount;
@end
@implementation RunloopMonitor
-(void)beginMonitor{
    self.dispatchSemaphore = dispatch_semaphore_create(0);
    //第一个监控,监控是否处于运行状态
    CFRunLoopObserverContext context ={0,(__bridge void*) self,NULL,NULL,NULL};
    self.runLoopBeginObserver = CFRunLoopObserverCreate(kCFAllocatorDefault, kCFRunLoopAllActivities, YES, LONG_MIN, &myRunLoopBeginCallback, &context);
    //  第二个监控,监控是否处于 睡眠状态
    self.runLoopEndObserver = CFRunLoopObserverCreate(kCFAllocatorDefault, kCFRunLoopAllActivities, YES, LONG_MAX, &myRunLoopEndCallback, &context);
    CFRunLoopAddObserver(CFRunLoopGetMain(), self.runLoopBeginObserver, kCFRunLoopCommonModes);
    CFRunLoopAddObserver(CFRunLoopGetMain(), self.runLoopEndObserver, kCFRunLoopCommonModes);
    //创建子线程监控
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
       //子线程开启一个loop来进行监控
        while (YES) {
            long semaphoreWait = dispatch_semaphore_wait(self.dispatchSemaphore, dispatch_time(DISPATCH_TIME_NOW, 17 * NSEC_PER_MSEC));
            if (semaphoreWait != 0) {
                if (!self.runLoopBeginObserver || !self.runLoopEndObserver) {
                    self.timeoutCount = 0;
                    self.dispatchSemaphore = 0;
                    self.runLoopBeginActivity = 0;
                    self.runLoopEndActivity = 0;
                    
                }
                //两个runloop的状态beforsource和afterwaiting这两个状态区间时间能够检测到是否卡顿
                if ((self.runLoopBeginActivity == kCFRunLoopBeforeSources || self.runLoopBeginActivity == kCFRunLoopAfterWaiting) ||(self.runLoopEndActivity == kCFRunLoopBeforeSources || self.runLoopEndActivity==kCFRunLoopAfterWaiting)) {
                    //出现三次出结果
                    if (++self.timeoutCount<2) {
                        continue;
                    }
                    NSLog(@"调试,检测到卡顿");
                }
            }//end samapgore wait
            self.timeoutCount = 0;
        }
    });
    
    
    
}
// 第一个监控,监控是否处于 运行状态
void myRunLoopBeginCallback(CFRunLoopObserverRef observer,CFRunLoopActivity activity,void *info){
    RunloopMonitor *lagMonitor =(__bridge RunloopMonitor *)info;
    lagMonitor.runLoopBeginActivity = activity;
    dispatch_semaphore_t semaphare = lagMonitor.dispatchSemaphore;
    dispatch_semaphore_signal(semaphare);
}
//  第二个监控,监控是否处于 睡眠状态
void myRunLoopEndCallback(CFRunLoopObserverRef observer,CFRunLoopActivity activity,void *info){
    RunloopMonitor *lagMonitor =(__bridge RunloopMonitor *)info;
    lagMonitor.runLoopEndActivity = activity;
    dispatch_semaphore_t semaphare = lagMonitor.dispatchSemaphore;
    dispatch_semaphore_signal(semaphare);
}

@end

3.常驻线程

 __weak typeof(self) weakSelf = self;
    self.thread = [[MyThread alloc] initWithBlock:^{
        NSLog(@"新线程");
        CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(CFAllocatorGetDefault(), kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
            switch (activity) {
                case kCFRunLoopEntry:
                    NSLog(@"即将进入RunLoop");
                    break;
                case kCFRunLoopBeforeTimers:
                    NSLog(@"即将处理Timer");
                    break;
                case kCFRunLoopBeforeSources:
                    NSLog(@"即将处理Sources");
                    break;
                case kCFRunLoopBeforeWaiting:
                    NSLog(@"即将进入休眠");
                    break;
                case kCFRunLoopAfterWaiting:
                    NSLog(@"从休眠中唤醒");
                    break;
                case kCFRunLoopExit:
                    NSLog(@"即将退出RunLoop");
                    break;
                default:
                    break;
            }
        });
        // 监听RunLoop的状态变化
        CFRunLoopAddObserver(CFRunLoopGetCurrent(), observer, kCFRunLoopDefaultMode);
        
        [[NSRunLoop currentRunLoop] addPort:[[NSPort alloc]init] forMode:NSDefaultRunLoopMode];
        while (!weakSelf.stopped) {
//            [[NSRunLoop currentRunLoop] run];
//            [[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:1]];
            [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
            
            NSLog(@"-----------while---");
        }
        NSLog(@"end");
        
    }];
    self.thread.name=@"mytestthread";
    [self.thread start];

4.tableView滚动卡顿问题

利用Runloop在UIScrollView滑动时和App默认运行时的Model不同来实现
利用PerformSelector设置当前线程的Runloop的运行模式,
NSDefaultRunLoopMode:App的默认运行模式,通常主线程是在这个运行默认下运行的,
UITrackingRunLoopMode:跟踪用户交互事件(用于 ScrollView 追踪触摸滑动,保证界面滑动时不受其他Mode影响)然后 我们滑动UITableView时候 RunLoop的运行模式就会变为UITrackingRunLoopMode所以我们把给ImageView加载图片的方法用PerformSelector设置当前线程的RunLoop的运行模式kCFRunLoopDefaultMode 这样滑动时候就不会执行加载图片的方法了

[cell performSelector:@selector(setImage:) withObject:nil afterDelay:0.1 inModes:@[NSDefaultRunLoopMode]];

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/416508.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【Java Web】012 -- SpringBootWeb综合案例(登录功能、登录校验、异常处理)

目录 一、登录功能 1、基础登录功能 ①、SQL语句 ②、接口参数 ③、实现思路 ④、实现步骤 2、联调Bug&#xff08;没有Cookie或Session&#xff09; 二、登录校验 1、登录校验的实现思路 2、会话技术 ①、会话与会话跟踪 ②、会话跟踪方案对比 Cookie Session …

C# 多线程编程

1 线程与进程 进程&#xff1a;进程可以理解为一块包含了某些些资源的内存区域&#xff0c;操作系统通过进程这一方式把他的工作划分为不同的单元。一个应用程序可以对应于多个进程。 线程&#xff1a;线程是进程中的独立执行单元&#xff0c; 对于操作系统而言&#xff0c;他…

【学习笔记】unity脚本学习(四)【inputManager、键盘输入、鼠标输入、Raycast】

目录输入inputManagerHorizontal虚拟轴的各个属性含义&#xff08;摘选自ChatGpt&#xff0c;部分回答不准确&#xff09;键值的含义键名称命名约定&#xff1a;键盘输入静态函数GetKeyGetButtonKeyCodeGetButton/Down/upGetAxisGetAxisRaw 返回由 axisName 标识的虚拟轴的值&a…

【Gradle-2】一文搞懂Gradle配置

1、前言 “Gradle的配置太多了&#xff0c;经常版本更新还有变化&#xff0c;而且它还能扩展&#xff0c;记是记不住了&#xff0c;只能用到再搜了&#xff0c;哎&#xff0c;难顶” 真的难顶&#xff0c;但是我想挑战一下… 本文介绍的重点&#xff1a; Gradle配置简介Grad…

slam与导航开发

Gmapping是一种基于激光雷达数据的地图构建算法&#xff0c;可以用于建立机器人的环境地图。 Gmapping算法是基于粒子滤波器&#xff08;Particle Filter&#xff09;的SLAM算法&#xff0c;它通过将机器人在环境中的位姿和地图中的特征进行联合估计&#xff0c;实现了机器人在…

个人使用mac OS和win OS的差异

苹果 macOS 操作系统和 Windows 操作系统在很多方面有所不同&#xff0c;主要体现在以下几个方面&#xff1a; 用户界面&#xff1a;macOS 和 Windows 的用户界面风格不同。macOS 推崇简洁、优雅的设计&#xff0c;注重操作体验&#xff1b;Windows 软件更为丰富&#xff0c;但…

Doris(1):Doris介绍

1 Doris简介 Apache Doris是一个现代化的基于MPP&#xff08;大规模并行处理&#xff09;技术的分析型数据库产品。简单来说&#xff0c;MPP是将任务并行的分散到多个服务器和节点上&#xff0c;在每个节点上计算完成后&#xff0c;将各自部分的结果汇总在一起得到最终的结果(…

最小生成树和最短路径及其他

还是学过的&#xff0c;主要用于复习q v q 一、最小生成树 最小生成树的定义 用于无向图中&#xff0c;无向图指的是没有带方向路径的图&#xff0c;给定n个点&#xff0c;m条边&#xff0c;如果将这些点依次相连&#xff0c;求出连接这些点的最小数值 应用场景 根据这个算…

9 个JSON.stringify的秘密大多数开发人员却不知道

作为前端开发工程师&#xff0c;你一定用过JSON.stringify&#xff0c;但你知道它的全部秘密吗&#xff1f; 很久以前&#xff0c;我因此在工作中犯下了无法挽回的错误。如果我早点知道&#xff0c;就不会发生这样的悲剧。 理解 JSON.stringify 基本上&#xff0c;JSON.stri…

神经网络辐射场NeRF、实时NeRF Baking、有向距离场SDF、占用网络Occupancy、NeRF 自动驾驶

1 NeRF原理2 NeRF加速PlenoxelsKiloNeRFInstant NGPTensoRF3 SDF NeRF4 Occupancy NeRF5 NeRF应用简介常见应用实际应用的挑战6 NeRF自动驾驶1 NeRF原理 NeRF (Neural Radiance Fields&#xff0c;神经辐射场) 是2020年ECCV会议上的Best Paper&#xff0c;其将隐式表达推上了…

安卓玩机搞机----移植第三方rom修复 第三方GSI系统修复bug综合解析【一】

很多朋友热衷与刷写第三方非当前机型官方系统的rom。和刷写第三方gsi等等。例如 米系列机型刷写Flyme 一加机型刷写miui oppo刷写gsi等等。 很多友友也会尝试自己移植第三方rom。但此类操作最大的问题在于修复可以开机后的bug&#xff0c;今天的教程综合说明下这类修复思路…

STM32F103RCT6

STM32F103RCT6是一款由STMicroelectronics公司生产的基于ARM Cortex-M3内核的32位微控制器。 它具有高性能、低功耗和广泛的应用领域。 包括ADC&#xff08;模数转换器&#xff09; DAC&#xff08;数字模拟转换器&#xff09; TIM&#xff08;定时器&#xff09; USART&#x…

Python 中 SyntaxError: ‘yield‘ outside function 错误

当我们在函数外部使用 yield 关键字时&#xff0c;会出现 Python “SyntaxError: ‘yield’ outside function”。 要解决该错误&#xff0c;如果我们需要对每个元素执行一些运算符&#xff0c;请使用列表理解&#xff0c;或者缩进函数内部使用 yield 的代码。 下面是一个产生…

Maven(四):Maven的使用(中)

Maven&#xff08;四&#xff09;&#xff1a;Maven的使用&#xff08;中&#xff09;前言一、实验四&#xff1a;创建 Maven 版的 Web 工程1、说明2、操作3、生成的pom.xml4、生成的Web工程的目录结构5、创建 Servlet5.1 在 main 目录下创建 java 目录5.2 在 java 目录下创建 …

【从零开始学Skynet】实战篇《球球大作战》(六):gateway代码设计(中)

1、编码和解码 我们来实现两个辅助方法str_unpack和str_pack&#xff0c;用于消息的解码和编码。 &#xff08;1&#xff09;str_unpack代码 local str_unpack function(msgstr)local msg {}while true dolocal arg, rest string.match( msgstr, "(.-),(.*)")if…

TCP/IP协议及配置

文章目录一、TCP/IP概述1. TCP/IP协议族2. 主机与主机之间通信的三个要素二、什么是IP地址1. 用来标识一个网络节点的互联网地址&#xff08;如同电话的号码&#xff09;2. IPv4地址组成三、IP地址分类1. 常用的IP地址2. 组播及科研专用四、IP地址分类&#xff08;续&#xff0…

wsl下安装cuda各种踩坑记录.assets

执行nvcc -V, cuda版本位11.5 删除cuda sudo apt-get --purge remove "*cublas*" "*cufft*" "*curand*" \"*cusolver*" "*cusparse*" "*npp*" "*nvjpeg*" "cuda*" "nsight*"选择对…

打造自己特色远程桌面:SmartCode ViewerX Crack

SmartCode ViewerX VNC 查看器 ActiveX 毫不费力地将 VNC 查看器功能添加到您的应用程序 SmartCode ViewerX VNC 查看器 ActiveX 使开发人员可以使用一组直观的 ActiveX 属性和方法完全访问 VNC 查看器功能。借助ViewerX 控件&#xff0c;开发人员可以轻松地为其应用程序提供…

<呕心沥血>一文总结数据结构八大排序(持续更新)

目录 一、常见的八大排序 二、八大排序的算法思想 1、冒泡排序 2、选择排序 3、插入排序 4、希尔排序 5、归并排序 6、快速排序 7、堆排序 8、计数排序 三、八大排序的算法实现 一、常见的八大排序 常见的八大排序算法如下&#xff1a; 冒泡排序&#xff08;Bubble …

Android中的接口回调机制

文章目录1.回调的含义和用途2.java实现接口回调3.Android中接口回调的体现4.接口回调在异步任务中的体现1.回调的含义和用途 一般来说&#xff0c;模块之间都存在一定的调用关系&#xff0c;从调用方式上看&#xff0c;可以分为三类同步调用、异步调用和回调。同步调用是一种阻…