iOS中的网络和多线程编程(十)

简介: iOS中的网络和多线程编程(十)

iOS中如何触发定时任务或延时任务


定时任务指周期性地调用某个方法,实现任务的反复执行,如倒计时等;延时任务指等待一定的时间后再执行某个任务,如页面的延时跳转等。iOS中控制任务的延时或定时执行的方法有很多,使用中要注意是同步还是异步,是否会阻塞主线程等问题。延时和定时的实现方法依次如下。


1.performSelector实现延时任务


延时任务可以通过当前UIViewController的performSelector隐式创建子线程实现,不会阻塞主线程。


/*延迟10s执行任务*/
[self performSelector:@selector(task) withObject:nil afterDelay:10];
- (void)task
{
    //delay task
}


2.利用sleep实现后面任务的等待

慎用,会阻塞主线程NSThreadsleepForTimeInterval:10.0];


3.GCD实现延时或定时任务

通过GCD实现block代码块的延时执行。


dispatch_time_t delay = dispatch_time(DISPATCH_TIME_NOW, 10 * NSEC_PER_SEC);
dispatch_after(delay, dispatch_get_main_queue(), ^{
    //delay task
});


GCD还可以用来实现定时器功能,还能设置延时开启计时器,使用中注意一定要定义强引用指针来指向计时器对象才可让计时器生效。


/*必须要用强引用指针,计时器才会生效*/
@property (nonatomic, strong) dispatch_source_t timer;
/*在指定线程上定义计时器*/
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_source_t _timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
/*开始的时间*/
dispatch_time_t when = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC));
/*设置计时器*/
dispatch_source_set_timer(_timer, when, 1.0 * NSEC_PER_SEC, 0);
/*计时器回调block*/
dispatch_source_set_event_handler(_timer, ^{
   NSLog(@"dispatch_source_set_timer is working!");
});
/*开启计时器*/
dispatch_resume(_timer);
/*强引用计时器对象*/
self.timer = _timer;


4.NSTimer实现定时任务


NSTimer主要用于开启定时任务,但要正确使用才能保证它能够正常有效地运行。尤其要注意以下两点:


1)确保NSTimer已经添加到当前RunLoop。

2)确保当前RunLoop已经启动。


创建NSTimer有两种方法,代码如下:


+ (NSTimer *)timerWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(nullable id)userInfo repeats:(BOOL)yesOrNo;
+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(nullable id)userInfo repeats:(BOOL)yesOrNo;


这两种方法的主要区别为,使用timerWithTimeInterval创建的timer不会自动添加到当前RunLoop中,需要手动添加并指定RunLoop的模式:[[NSRunLoop currentRunLoop] addTimer:mainThreadTimer forMode:NSDefaultRunLoopMode];而使用scheduledTimerWithTimeInterval创建的RunLoop会默认添加到当前RunLoop中。


NSTimer可能在主线程中创建,也可能在子线程中创建。主线程中的RunLoop默认是启动的,所以timer只要添加到主线程RunLoop中就会被执行;而子线程中的RunLoop默认是不启动的,所以timer添加到子线程RunLoop中后,还要手动启动RunLoop才能使timer被执行。


NSTimer只有添加到启动起来的RunLoop中才会正常运行。NSTimer通常不建议添加到主线程中执行,因为界面的更新在主线程中进行,这会影响NSTimer的准确性。

以下代码为4种情形下NSTimer的正确使用方法。


- (void)viewDidLoad {
    [super viewDidLoad];
    /*第一种,主线程中创建timer,需要手动添加到RunLoop中*/
    NSTimer *mainThreadTimer = [NSTimer timerWithTimeInterval:10.0 target:self selector:@selector(mainThreadTimer_SEL) userInfo:nil repeats:YES];
    /*第二种,主线程中创建timer,不需要手动添加到RunLoop中*/
    NSTimer *mainThreadSchduledTimer = [NSTimer scheduledTimerWithTimeInterval:10.0 target:self selector:@selector(mainThreadSchduledTimer_SEL) userInfo:nil repeats:YES];
    /*将mainThreadTimer添加到主线程runloop*/
    [[NSRunLoop currentRunLoop] addTimer:mainThreadTimer forMode:NSDefaultRunLoopMode];
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        /*第三种,子线程中创建timer,需要手动添加到RunLoop中*/
        NSTimer *subThreadTimer = [NSTimer timerWithTimeInterval:10.0 target:self selector:@selector(subThreadTimer_SEL) userInfo:nil repeats:YES];
        /*第四种,子线程中创建timer,不需要手动添加到RunLoop中*/
        NSTimer *subThreadScheduledTimer = [NSTimer scheduledTimerWithTimeInterval:10.0 target:self selector:@selector(subThreadSchduledTimer_SEL) userInfo:nil repeats:YES];
        /*将subThreadTimer添加到子线程runloop*/
        [[NSRunLoop currentRunLoop] addTimer:subThreadTimer forMode:NSDefaultRunLoopMode];
        /*启动子线程runloop*/
        [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
    });
- (void)maniThreadTimer_SEL{
    NSLog(@"mainThreadTimer is working!");
}
- (void)mainThreadScheduledTimer_SEL{
    NSLog(@"mainThreadScheduledTimer is working!");
}
- (void)subThreadTimer_SEL{
    NSLog(@"subThreadTimer is working!");
}
- (void)subThreadScheduledImer_SEL{
    NSLog(@"subThreadSchduledTimer is working!");
}


上述代码的打印结果为:


2466108-96a3f757aac4ce07.webp.jpg


NSTimer的释放方法:[timer invalidate];


5.CADisplayLink实现定时任务


CADisplayLink实现的定时器与屏幕刷新频率绑定在一起,是一种帧率刷新,适用于界面的不断重绘(例如流畅动画和视频播放等)。CADisplayLink以特定模式注册到RunLoop后,每当屏幕显示内容刷新结束后就会向CADisplayLink指定的target发送一次消息,实现target的每帧调用。根据需求也可以设置每几帧调用一次,默认每帧都调用。另外,通过CADisplayLink还可以获取帧率和时间等信息。


CADisplayLink实现的定时器精度非常高,但如果调用的方法十分耗时,超过一帧的时间间隔,那么会导致跳帧,跳帧次数取决于CPU的忙碌程度。


下面是CADisplayLink定时器的实现:


- (void)viewDidLoad {
    [super viewDidLoad];
    CADisplayLink *displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(displayLink_SEL)];
    /*添加到当前运行的RunLoop中启动*/
    [displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
    /*暂停、继续对selector的调用*/
//    [displayLink setPaused:YES];
//    [displayLink setPaused:NO];
    /*设置每几帧调用一次selector,默认为1*/
//    [displayLink setPreferredFramesPerSecond:2];
    /*移除,不再使用*/
//    [displayLink invalidate];
//    displayLink = nil;
}
- (void)displayLink_SEL{
    NSLog(@"displayLink is working!");
}


打印结果如下,每一帧都调用selector。


2466108-1c418eafb3649cce.webp.jpg


如何解决网络请求的依赖关系


当一个网络的请求需要依赖于另一个网络请求的结果时,如何解决网络请求的依赖关系?

假设一个网络请求B依赖于另一个网络请求A,即网络请求B需要网络请求A的结果,这就需要开发者采取措施来维护A和B的顺序,实现网络请求B同步等待网络请求A。思路主要有以下两个:


1)一种是通过线程管理来实现,即线程同步,让线程B等待线程A。iOS中实现线程同步的方法也很多:可以设置NSOperation操作依赖来实现,即让操作B依赖于操作A,操作B会在操作A结束后才会开始执行([B addDependency:A];);也可以通过GCD来实现,包括组队列(dispatch_group)、阻塞任务(dispatch_barrier_(a)sync)和信号量机制(dispatch_semaphore)。


2)另外也可以直接通过逻辑衔接来实现。即在网络请求A的响应回调中编写网络请求B的逻辑,缺点是耦合度上会高一些。


目录
相关文章
|
2月前
|
Java 开发者
Java多线程编程中的常见误区与最佳实践####
本文深入剖析了Java多线程编程中开发者常遇到的几个典型误区,如对`start()`与`run()`方法的混淆使用、忽视线程安全问题、错误处理未同步的共享变量等,并针对这些问题提出了具体的解决方案和最佳实践。通过实例代码对比,直观展示了正确与错误的实现方式,旨在帮助读者构建更加健壮、高效的多线程应用程序。 ####
|
2月前
|
安全 Java UED
深入浅出Java多线程编程
【10月更文挑战第40天】在Java的世界中,多线程是提升应用性能和响应能力的关键。本文将通过浅显易懂的方式介绍Java中的多线程编程,从基础概念到高级特性,再到实际应用案例,带你一步步深入了解如何在Java中高效地使用多线程。文章不仅涵盖了理论知识,还提供了实用的代码示例,帮助你在实际开发中更好地应用多线程技术。
64 5
|
21天前
|
存储 安全 Java
Java多线程编程秘籍:各种方案一网打尽,不要错过!
Java 中实现多线程的方式主要有四种:继承 Thread 类、实现 Runnable 接口、实现 Callable 接口和使用线程池。每种方式各有优缺点,适用于不同的场景。继承 Thread 类最简单,实现 Runnable 接口更灵活,Callable 接口支持返回结果,线程池则便于管理和复用线程。实际应用中可根据需求选择合适的方式。此外,还介绍了多线程相关的常见面试问题及答案,涵盖线程概念、线程安全、线程池等知识点。
116 2
|
2月前
|
设计模式 Java 开发者
Java多线程编程的陷阱与解决方案####
本文深入探讨了Java多线程编程中常见的问题及其解决策略。通过分析竞态条件、死锁、活锁等典型场景,并结合代码示例和实用技巧,帮助开发者有效避免这些陷阱,提升并发程序的稳定性和性能。 ####
|
1月前
|
缓存 Java 调度
多线程编程核心:上下文切换深度解析
在现代计算机系统中,多线程编程已成为提高程序性能和响应速度的关键技术。然而,多线程编程中一个不可避免的概念就是上下文切换(Context Switching)。本文将深入探讨上下文切换的概念、原因、影响以及优化策略,帮助你在工作和学习中深入理解这一技术干货。
51 10
|
2月前
|
缓存 Java 开发者
Java多线程编程的陷阱与最佳实践####
本文深入探讨了Java多线程编程中常见的陷阱,如竞态条件、死锁和内存一致性错误,并提供了实用的避免策略。通过分析典型错误案例,本文旨在帮助开发者更好地理解和掌握多线程环境下的编程技巧,从而提升并发程序的稳定性和性能。 ####
|
1月前
|
安全 算法 Java
Java多线程编程中的陷阱与最佳实践####
本文探讨了Java多线程编程中常见的陷阱,并介绍了如何通过最佳实践来避免这些问题。我们将从基础概念入手,逐步深入到具体的代码示例,帮助开发者更好地理解和应用多线程技术。无论是初学者还是有经验的开发者,都能从中获得有价值的见解和建议。 ####
|
1月前
|
Java 调度
Java中的多线程编程与并发控制
本文深入探讨了Java编程语言中多线程编程的基础知识和并发控制机制。文章首先介绍了多线程的基本概念,包括线程的定义、生命周期以及在Java中创建和管理线程的方法。接着,详细讲解了Java提供的同步机制,如synchronized关键字、wait()和notify()方法等,以及如何通过这些机制实现线程间的协调与通信。最后,本文还讨论了一些常见的并发问题,例如死锁、竞态条件等,并提供了相应的解决策略。
59 3
|
1月前
|
算法 调度 开发者
多线程编程核心:上下文切换深度解析
在多线程编程中,上下文切换是一个至关重要的概念,它直接影响到程序的性能和响应速度。本文将深入探讨上下文切换的含义、原因、影响以及如何优化,帮助你在工作和学习中更好地理解和应用多线程技术。
45 4
|
1月前
|
Java 调度 Android开发
安卓与iOS开发中的线程管理差异解析
在移动应用开发的广阔天地中,安卓和iOS两大平台各自拥有独特的魅力。如同东西方文化的差异,它们在处理多线程任务时也展现出不同的哲学。本文将带你穿梭于这两个平台之间,比较它们在线程管理上的核心理念、实现方式及性能考量,助你成为跨平台的编程高手。