【iOS】GCD详细总结

news2024/12/24 15:34:45

GCD详细总结

    • 1. GCD简介
    • 2. GCD任务和队列
      • 任务
      • 队列 (dispatch是派遣的意思)
      • 队列的创建方法和获取方法
    • 3.我的总结:同步和异步函数,并行和并发队列
      • 同步异步函数
      • 串行并发队列
      • 是否开启新线程,串行还是并发执行任务,如何分析?
      • 死锁条件总结:
    • 4. GCD基本使用
      • 同步串行队列
      • 同步并行队列
      • 异步串行队列
      • 异步并行队列
      • sync函数造成的线程死锁
    • 5. 进程间通信
    • 6. GCD的其他方法
      • 6.1 GCD栅栏方法:dispatch_barrier_async
      • 6.2、GCD 延时执行方法:dispatch_after
      • 6.3、GCD 一次性代码(只执行一次):dispatch_once
      • 6.4 dispatch_group
      • 6.5 GCD信号量: dispatch_semaphore

本章如何复习?

  1. 首先记住两个核心概念,任务和队列的概念,各自影响的是什么
  2. 然后直接去复习我对它们的总结,下面有
  3. 一定要理解同步函数和异步函数到底是什么意思: 同步会阻塞当前函数的返回异步函数会立即返回,区执行下面的代码
  4. 记住死锁产生条件:使用sync函数当前的串行的队列 中添加任务,就会产生死锁。其他情况都不会产生死锁。
  5. 看那张表,了解如何判断,是否开启新线程,串行还是并发执行任务

1. GCD简介

GCD(Grand Central Dispatch)是Apple开发的一个多核编程的较新的解决方法。它主要用于优化应用程序以支持多核处理器以及其他对称多处理系统。它是一个在线程池模式的基础上,执行的并发任务。

GCD的好处具体如下:

  • GCD可用于多核的并行计算
  • GCD会自动利用更多的CPU内核(比如双核,四核)
  • GCD会自动管理线程的生命周期(创建线程、调度任务、销毁线程)
  • 程序员只需要告诉GCD想要执行什么任务,不需要编写任何线程管理代码

2. GCD任务和队列

GCD有两个核心概念:任务和队列

任务

任务:就是执行操作的意思,换句话说就是你在线程中执行的那段代码。在GCD中是放在block中的

  • 同步执行(sync)
    • 同步添加任务到指定的队列中,在添加的任务执行结束之前,会一直等待,直到里面的任务完成之后再继续执行。
    • 只能在当前线程中执行任务,不具备开启新线程的能力
  • 异步执行(async)
    • 异步添加任务到指定的队列中,它不会做任何等待,可以继续执行后面的任务。
    • 可以在新的线程中执行任务,具备开启新线程的能力。
// 同步执行任务创建方法
dispatch_sync(queue, ^{
// 这里放同步执行任务代码
});
// 异步执行任务创建方法
dispatch_async(queue, ^{
// 这里放异步执行任务代码
});

队列 (dispatch是派遣的意思)

队列(Dispatch Queue) : 这里的队列指执行任务的等待队列,即用来存放任务的队列。队列是一种特殊的线性表,采用FIFO(先进线出)的原则,即新任务总是被插入到队列的末尾,而读取任务的时候总是从队列的头部开始读取。每读取一个任务,从队列中释放一个任务。

  • 串行队列(Serial Dispatch Queue)
    • 每次只有一个任务被执行。让任务一个接着一个地执行。(开启一个线程,一个任务执行完毕后,再执行下一个任务)
  • 并发队列(Concurrent Dispatch Queue)
    • 可以让多个任务并发(同时)执行。(可以开启多个线程,并且同时执行任务)

并发队列的并发功能只有在异步函数(dispatch_async)下才有效

队列的创建方法和获取方法

可以使用dispatch_queue_create来创建队列,需要传入两个参数,第一个参数表示队列的唯一标识符,用于DEBUG,可以为空,Dispatch Queue的名称推荐使用应用程序ID这中逆序全称域名;第二个参数用来识别是串行队列还是并发队列。DISPATCH_QUEUE_SERIAL表示串行队列,DISPATCH_QUEUE_CONCURRENT表示并发队列。


// 串行队列的创建方法
dispatch_queue_t queue = dispatch_queue_create("net.bujige.testQueue", DISPATCH_QUEUE_SERIAL);
// 并发队列的创建方法
dispatch_queue_t queue = dispatch_queue_create("net.bujige.testQueue", DISPATCH_QUEUE_CONCURRENT);

对于串行队列,GCD提供了一种特殊的串行队列:主队列(Main Dispatch Queue)

  • 所有放在主队列中的任务,都会放到主线程中执行
  • 可使用dispatch_get_main_queue()获得主队列

// 主队列的获取方法
dispatch_queue_t queue = dispatch_get_main_queue();

对于并发队列,GCD默认提供了全局并发队列(Global Dispatch Queue)
可以使用dispatch_get_global_queue来获取。需要传入两个参数。第一个参数表示队列优先级,一般用DISPATCH_QUEUE_PRIORITY_DEFAULT。第二个参数暂时没有用,传0即可。


// 全局并发队列的获取方法
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

在这里插入图片描述

结论:

  1. 使用sync函数往串行队列中添加任务,会卡住当前的串行队列,产生死锁
  2. 并发功能只有在异步函数才会生效

3.我的总结:同步和异步函数,并行和并发队列

同步异步函数

同步函数(sync)和异步函数(async),只能决定能否开启新的线程执行任务,不能决定函数是串行执行还是并行执行

同步:

  • 在当前线程中执行,不具备开启新线程的能力
  • 后面的内容需要等同步函数返回了,才能执行。所以就在当前线程执行就行,不需要开启新的线程,因此也不具备开新线程的能力
    异步:
  • 在新的线程中执行,具备开启新线程的能力(注意,是具备开新线程的能力,不一定会开启新线程)
  • 后面的内容不需要等异步函数返回才执行,后面的内容可以直接执行。所以需要开启新线程,因此具备开新线程的能力

同步函数和异步函数:同步会阻塞当前函数的返回异步函数会立即返回,区执行下面的代码

最重要的一句理解:!!!

同步函数后面的内容需要等同步函数返回(执行完了),才能执行。异步函数后面的内容不需要等异步函数返回才执行,可以直接执行(就好像可以跳过这个异步函数一样),而这个异步函数里面的内容可能就需要开启一个新线程来执行。

串行并发队列

队列:并发和串行。主要影响任务的执行方式,能不能开启新线程取决于上面的两个函数
并发:多个任务并发执行
串行:一个任务执行完毕后,再执行下一个任务

是否开启新线程,串行还是并发执行任务,如何分析?

  1. 同步函数(sync),代表没有开新线程,一定是串行执行

  2. 异步函数(async),代表如果需要,可以开新线程

    • 如果传的是并发队列,就并发执行任务(可能开启多条线程,由操作系统决定)
    • 如果传的是手动创建的串行队列,就在子线程串行执行(开启一条新线程)
    • 如果传的是主队列,就在主线程中串行执行(没有开启新线程)
    • 什么情况下不会开启新线程呢?传入的是主队列

在这里插入图片描述

死锁条件总结:

使用sync函数当前的串行的队列 中添加任务,就会产生死锁。其他情况都不会产生死锁。
三个关键点:

  1. 同步函数
  2. 当前队列 (在两个不同的队列就不会)
  3. 当前队列是串行队列 (并发队列就不会)

在主线程中用同步函数往(当前)主队列中添加任务,是常见产生死锁的情况之一
(注意要是在主线程中添加,如果你新开了一个线程,在新线程中,用同步函数往主队列添加任务是不会产生死锁的)

4. GCD基本使用

同步串行队列


dispatch_queue_t queue = dispatch_queue_create("test", DISPATCH_QUEUE_SERIAL);
dispatch_sync(queue, ^{
	// 追加任务1
	for (int i = 0; i < 2; ++i) {
		NSLog(@"1---%@",[NSThread currentThread]);
	}
});

dispatch_sync(queue, ^{
	// 追加任务2
	for (int i = 0; i < 2; ++i) {
		NSLog(@"2---%@",[NSThread currentThread]);
	}
});

在这里插入图片描述

结论:同步串行队列即没有开启新的线程,也没有异步执行

同步并行队列


dispatch_queue_t queue = dispatch_queue_create("test", DISPATCH_QUEUE_CONCURRENT);
dispatch_sync(queue, ^{
	// 追加任务1
	for (int i = 0; i < 2; ++i) {
		NSLog(@"1---%@",[NSThread currentThread]);
	}
});

dispatch_sync(queue, ^{
	// 追加任务2
	for (int i = 0; i < 2; ++i) {
		NSLog(@"2---%@",[NSThread currentThread]);
	}
});

在这里插入图片描述

根据两种打印我们发现:同步函数既不会开启新的线程,也不会执行并发任务

异步串行队列


NSLog(@"主线程:%@",[NSThread currentThread]);
dispatch_queue_t queue = dispatch_queue_create("test", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
	for (int i = 0; i < 2; ++i) {
		NSLog(@"1====%@",[NSThread currentThread]);      // 打印当前线程
	}
});

dispatch_async(queue, ^{
	for (int i = 0; i < 2; ++i) {
		NSLog(@"2====%@",[NSThread currentThread]);      // 打印当前线程
	}

});

在这里插入图片描述

结果:有开启新的线程,串行执行任务

异步并行队列


NSLog(@"主线程:%@",[NSThread currentThread]);
dispatch_queue_t queue = dispatch_queue_create("test", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
	for (int i = 0; i < 2; ++i) {
		[NSThread sleepForTimeInterval:2];
		NSLog(@"1====%@",[NSThread currentThread]);      // 打印当前线程
	}

});
dispatch_async(queue, ^{
	for (int i = 0; i < 2; ++i) {
		[NSThread sleepForTimeInterval:2];
		NSLog(@"2====%@",[NSThread currentThread]);      // 打印当前线程
	}

});

在这里插入图片描述

结果:有开启新的线程,并发执行任务。想要出现明显的并发执行效果,可以sleep一下

sync函数造成的线程死锁

首先你要理解同步和异步执行的概念,同步和异步目的不是为了是否创建一个新的线程.
同步会阻塞当前函数的返回异步函数会立即返回,区执行下面的代码
队列是一种数据结构,队列有FIFO,LIFO等,控制任务的执行顺序,至于是否开辟一个新的线程,因为同步函数会等待函数的返回,所以在当前线程执行就行了,没必要浪费资源再开辟新的线程,如果是异步函数,当前线程需要立即函数返回,然后往下执行,所以函数里面的任务必须要开辟一个新的线程去执行这个任务。

队列上是放任务的,而线程是去执行队列上的任务的

问题一:以下代码是在主线程执行的,会不会产生死锁?会!


NSLog(@"执行任务1");
dispatch_queue_t queue = dispatch_get_main_queue();
dispatch_sync(queue, ^{
	NSLog(@"执行任务2");
});

NSLog(@"执行任务3");

在这里插入图片描述

dispatch_sync立马在当前线程同步执行任务
分析:

    1. 主线程中任务执行: 任务1、sync、任务3
    1. 主队列: viewDidLoad、任务2

主队列中有首先有ViewDidLoad,当执行到第31行时,往主队列中同步添加了任务2。此时正确的执行顺序应该是,把主线程中的ViewDidLoad方法执行完,才能执行任务2。但是从这个方法出发,必须得先执行完任务2,才可能把ViewDidLoad执行完。所以造成死锁。与任务3无关。

问题2: 以下代码是,在一个新线程中,往主线程同步添加任务。会不会产生死锁?不会!


- (void)viewDidLoad {
    [super viewDidLoad];

    dispatch_queue_t queue = dispatch_queue_create("test", DISPATCH_QUEUE_SERIAL);
    
    dispatch_async(queue, ^{
        NSLog(@"任务1");

        dispatch_sync(dispatch_get_main_queue(), ^{
            NSLog(@"任务2");
        });

        NSLog(@"任务3");
    });
    
    NSLog(@"任务4");
}

分析:

    1. 主线程中任务执行:viewDidLoad,(async不在主队列执行),任务2
    1. 新开了一个新线程 : async(任务1,sync,任务2)

这里面的sync的意思也是,viewDidLoad执行完,才能执行任务2,而主线程里面ViewDidLoad确实执行完了,因为async根本不在主线程。所以执行完viewDidLoad以后,再执行加进主队列的任务2,没有问题。

5. 进程间通信


- (void)communication {
    //获取全局并发队列
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    
    dispatch_queue_t mainQueue = dispatch_get_main_queue();
    
    dispatch_async(queue, ^{
        //异步追加任务
        for (int i = 0; i < 2; i++) {
            [NSThread sleepForTimeInterval:2];
            NSLog(@"1----%@", [NSThread currentThread]);
        }
        
        //回到主线程
        dispatch_async(mainQueue, ^{
            //追加在主线程执行的任务
            [NSThread sleepForTimeInterval:2];
            NSLog(@"2----%@", [NSThread currentThread]);
        });
    });
}

- (void)viewDidLoad {
    [super viewDidLoad];
    
    [self communication];
}

在这里插入图片描述

这就是一个异步,网络请求,加载图片,然后执行结束后返回主队列(给主队列添加任务,比如把数据传给主线程)。而主线程不会等待这个网络请求方法结束,会直接继续执行后续的代码。

6. GCD的其他方法

6.1 GCD栅栏方法:dispatch_barrier_async

在这里插入图片描述

就是我们在异步执行一些操作的时候,我们使用dispatch_barrier_async函数把异步操作暂时性的做成同步操作,就行一个栅栏一样分开。


- (void)viewDidLoad {
	[super viewDidLoad];

	self.queue = dispatch_queue_create("rw_queue", DISPATCH_QUEUE_CONCURRENT);

	for (int i = 0; i < 10; i++) {
		[self read];
		[self read];
		[self read];
		[self write];
	}
}

- (void)read {
	dispatch_async(self.queue, ^{
		sleep(1);
		NSLog(@"read");
	});
}

- (void)write
{
	dispatch_barrier_async(self.queue, ^{
		sleep(1);
		NSLog(@"write");
	});
}

在这里插入图片描述

我们观察时间可以看到在执行dispatch_barrier_async写操作的时候是同步执行的,不会出现异步情况

6.2、GCD 延时执行方法:dispatch_after

我们经常会遇到这样的需求:在指定时间(例如3秒)之后执行某个任务。可以用 GCD 的dispatch_after函数来实现。
需要注意的是:dispatch_after函数并不是在指定时间之后才开始执行处理,而是在指定时间之后将任务追加到主队列中。严格来说,这个时间并不是绝对准确的,但想要大致延迟执行任务,dispatch_after函数是很有效的。

6.3、GCD 一次性代码(只执行一次):dispatch_once

我们在创建单例、或者有整个程序运行过程中只执行一次的代码时,我们就用到了 GCD 的 dispatch_once 函数


static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
// 只执行1次的代码(这里面默认是线程安全的)
});

6.4 dispatch_group

有时候我们会有这样的需求:分别异步执行2个耗时任务,然后当2个耗时任务都执行完毕后再回到主线程执行任务。这时候我们可以用到 GCD 的队列组

  • 调用队列组的 dispatch_group_async , 这个方法的作用是先把任务放到队列中,然后将队列放入队列组中。或者使用队列组的 dispatch_group_enterdispatch_group_leave 组合 来实现dispatch_group_async
  • 调用队列组的 dispatch_group_notify 回到指定线程执行任务。或者使用 dispatch_group_wait 回到当前线程继续向下执行(会阻塞当前线程)。
  • dispatch_group_enter 标志着一个任务追加到 group,执行一次,相当于 group 中未执行完毕任务数+1
  • dispatch_group_leave 标志着一个任务离开了 group,执行一次,相当于 group 中未执行完毕任务数-1
  • 当 group 中未执行完毕任务数为0的时候,才会使dispatch_group_wait解除阻塞,以及执行追加到dispatch_group_notify中的任务。

/**
* 队列组 dispatch_group_notify
*/
- (void)groupNotify {
	NSLog(@"currentThread---%@",[NSThread currentThread]);  // 打印当前线程
	NSLog(@"group---begin");

	dispatch_group_t group =  dispatch_group_create();

	dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
		// 追加任务1
		for (int i = 0; i < 2; ++i) {
			[NSThread sleepForTimeInterval:2];              // 模拟耗时操作
			NSLog(@"1---%@",[NSThread currentThread]);      // 打印当前线程
		}
	});

	dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
		// 追加任务2
		for (int i = 0; i < 2; ++i) {
			[NSThread sleepForTimeInterval:2];              // 模拟耗时操作
			NSLog(@"2---%@",[NSThread currentThread]);      // 打印当前线程
		}
	});

	dispatch_group_notify(group, dispatch_get_main_queue(), ^{
		// 等前面的异步任务1、任务2都执行完毕后,回到主线程执行下边任务
		for (int i = 0; i < 2; ++i) {
			[NSThread sleepForTimeInterval:2];              // 模拟耗时操作
			NSLog(@"3---%@",[NSThread currentThread]);      // 打印当前线程
		}
		NSLog(@"group---end");
	});
}

6.5 GCD信号量: dispatch_semaphore

Dispatch Semaphore 提供了三个函数。

  • dispatch_semaphore_create:创建一个信号量,具有整形的数值,即为信号的总量。
  • dispatch_semaphore_signal:发送一个信号,让信号总量加1
  • dispatch_semaphore_wait:可以使总信号量减1,当信号总量为0时就会一直等待(阻塞所在线程),否则就可以正常执行。

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

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

相关文章

【网络技术】堆叠通用部署

相关文章推荐 点击查看&#xff1a; 华为交换机堆叠技术 华为交换机组建堆叠案例 【技术分享】堆叠交换机替换指导 交换机为什么要堆叠&#xff1f; 配置交换机1 <HUAWEI> system-view [HUAWEI] sysname Switch1 [Switch1] interface stack-port 0/1 [Switch1-stack…

一文读懂 服务器

你好,我是Qiuner. 为帮助别人少走弯路和记录自己编程学习过程而写博客 这是我的 github https://github.com/Qiuner ⭐️ ​ gitee https://gitee.com/Qiuner &#x1f339; 如果本篇文章帮到了你 不妨点个赞吧~ 我会很高兴的 &#x1f604; (^ ~ ^) 想看更多 那就点个关注吧 我…

分数取模怎么办

我们遇到负数的话要先加上 mod 再取模 那么遇到分数的话怎么办 分数则由分子乘以分母的逆元&#xff0c;然后再对积取模。 #define _CRT_SECURE_NO_WARNINGS #include<bits/stdc.h> using namespace std;// 如果用杨辉三角形做的话空间会爆炸 // 我是sb&#xff0c;只有三…

Find My充气宝|苹果Find My技术与充气宝结合,智能防丢,全球定位

随着人们生活水平的提高&#xff0c;汽车已经走进了千家万户&#xff0c;汽车的普及也导致了停车位资源的稀缺。很多新手司机在停车和行车时经常会碰到轮胎被扎或者气压不足的问题&#xff0c;最近的骑行文化盛行&#xff0c;很多的骑手也会带着自己的山地自行车开启一段骑行之…

[Git][分支设计规范]详细讲解

目录 0.概览1.master分支2.release分支3.develop分支4.feature分支5.hotfix分支 0.概览 以下是常用的分支和环境的搭配&#xff0c;可视情况而定不同的策略 分支名称适用环境master主分支生产环境release预发布分支预发布/测试环境develop开发分支开发环境feature需求开发分支本…

第6章>>实验8:PS(ARM)端Linux RT与PL端FPGA之间(通过FIFO队列进行通信和交互)-《LabVIEW ZYNQ FPGA宝典》

1、实验内容 上一节实验里面介绍的Memory存储器通道比较适合在PS端和PL端之间传递数组或者向量等数据&#xff0c;也就是多个相同类型的元素&#xff0c;如果要传递像ADC采集这样的连续数据流&#xff0c;Memory存储器通道就不是很合适了。 本节实验我们向大家讲解如何借助FIFO…

加速 Spring Boot 3.3 迁移

1. 关键要点 为什么你应该升级你的服务迁移到 Spring Boot 3.3 时需要更新的内容OpenRewrite 如何帮助使升级更轻松、更快捷 2. 前言 现在Spring Boot 已经到了3.3&#xff0c;但是你在哪里&#xff1f;在过去的 3.x 版本更新中&#xff0c;我们看到了许多新功能&#xff0c;…

SAP EPPM-CPM(商业项目管理)模块功能演示:创建主项目

今天跟大家展示一下如何通过SAP CPM维护商业项目以及计划结构。 CPM的主要操作界面是SAP之前推出的新一代UX Fiori&#xff0c;如果需要在CPM操作&#xff0c;可分配SAP提供的标准复合角色&#xff1a;SAP_BPR_CPD_USER_1。 因为在CPM模块的宗旨是构建一个项目的全局视角门户…

触屏交互设备的安全风险

现实中的绝大多数电子设备都具有交互性&#xff0c;而现在越来越多的公共场合有布置越来越多的带触屏的交互设备&#xff0c;功能有简单的&#xff0c;有复杂的&#xff0c;布置的场所和应用的场合也各有不同&#xff0c;几乎在任何一个大型公共场合都可以看到这样的设备&#…

Android14音频进阶调试之命令播放mp3/aac非裸流音频(八十)

简介: CSDN博客专家,专注Android/Linux系统,分享多mic语音方案、音视频、编解码等技术,与大家一起成长! 新书发布:《Android系统多媒体进阶实战》🚀 优质专栏: Audio工程师进阶系列【原创干货持续更新中……】🚀 优质专栏: 多媒体系统工程师系列【原创干货持续更…

如何理解openfoam案例里面的blockMesh文件里面的simpleGrading

总结&#xff1a; simpleGrading参数分为xyz三个方向。如果你想使得网格在某个方向上更密集&#xff0c;可以在simpleGrading中将该方向的渐变率设置为小于 1 .更稀疏则设置大于1. 一、案例 比如我这个爆炸案例&#xff1a; 对应的blockMeshDIct文件如下&#xff1a; // 定…

第20周:Pytorch文本分类入门

目录 前言 一、前期准备 1.1 环境安装导入包 1.2 加载数据 1.3 构建词典 1.4 生成数据批次和迭代器 二、准备模型 2.1 定义模型 2.2 定义示例 2.3 定义训练函数与评估函数 三、训练模型 3.1 拆分数据集并运行模型 3.2 使用测试数据集评估模型 总结 前言 &#x1…

游戏盾是什么,如何保护网络游戏的安全

在数字化浪潮的推动下&#xff0c;网络游戏已成为人们休闲娱乐不可或缺的一部分。然而&#xff0c;随着游戏行业的蓬勃发展&#xff0c;网络安全问题也日益严峻&#xff0c;黑客攻击频发&#xff0c;给游戏玩家和游戏运营商带来了巨大困扰。为了应对这些挑战&#xff0c;应用加…

机器学习·L2W3-模型评估

模型评估 划分数据集为训练集、验证集、测试集 60%训练集、20%测试集和验证集 x_train,x_,y_train,y_train_test_split(X_train,y_train,test_size0.4) x_cv,x_test,y_cv,y_testtrain_test_split(x_train,y_train,test_size0.5)交叉验证-模型选择 使用交叉验证计算模型的损失…

新来的小姐姐,微软便笺程序打不开了

网管小贾 / sysadm.cc 公司新来了一位小姐姐&#xff0c;听说跟老板沾点关系。 这一天老板出差&#xff0c;午休时大家趁着小姐姐去取外卖&#xff0c;开始了各自的调侃。 部门主管丽姐开了个头&#xff0c;当着众人先抱怨上了。 “你们看看&#xff0c;你们看看&#xff0c;…

国内顶级 AI 的回答令人“贻笑大方”:看来苹果秃头码农们暂时还不会失业吧?

概览 在苹果 App 的日常开发中&#xff0c;利用 Xcode 预览可以帮我们极大的提高界面调试的效率。而且&#xff0c;若能进一步判断出当前 App 是否运行在 Preview 环境中则会更让秃头码农们“笑逐颜开”。 那么到底有没有简单的方法来完成这一任务呢&#xff1f;答案是肯定的…

苹果数据恢复攻略:3大秘籍,助你重建“数据高塔”

在数字时代&#xff0c;苹果设备如iPhone、iPad和Mac已成为我们生活中不可或缺的一部分&#xff0c;存储着大量珍贵的照片、视频、文件和联系信息。然而&#xff0c;意外的删除、系统更新或硬件故障等问题时常威胁着数据的安全。当数据“高塔”崩塌时&#xff0c;苹果数据恢复要…

海量数据处理商用短链接生成器平台 - 6

第十二章 海量数据下的分库分表技术栈讲解 第1集 大话业界常见数据库分库分表中间件介绍 简介&#xff1a; 大话业界常见分库分表中间件介绍 业界常见分库分表中间件 Cobar&#xff08;已经被淘汰没使用了&#xff09;TDDL 淘宝根据自己的业务特点开发了 TDDL &#xff08;T…

基于JSP的智能仓储系统

你好&#xff0c;我是专注于智能系统开发的码农小野。如果对智能仓储系统感兴趣&#xff0c;欢迎私信交流。 开发语言 Java 数据库 MySQL 技术 JSP技术 工具 MyEclipse、Tomcat 系统展示 首页 [插入论文中的系统首页图片] 管理员功能界面 员工功能界面 供应商功能界…

MATLAB代码下载|蚁群算法|计算一元函数最小值

程序总述 程序使用蚁群优化的方法&#xff0c;计算一元函数&#xff08;单输入单输出非线性函数&#xff09;在定义域内的最小值。 函数形式 待计算最小值的函数形式如下&#xff1a; x 4 − 0.2 ∗ c o s ( 3 x ∗ π ) 0.6 x^4 - 0.2 * cos(3x * \pi) 0.6 x4−0.2∗cos…