当前位置: 首页 > 编程日记 > 正文

iOS RunLoop详解

一、简介

CFRunLoopRef源码

RunLoop是一个对象,这个对象在循环中用来处理程序运行过程中出现的各种事件(比如说触摸事件、UI刷新事件、定时器事件、Selector事件),从而保持程序的持续运行;而且在没有事件处理的时候,会进入睡眠模式,从而节省CPU资源,提高程序性能。

RunLoop的代码逻辑:详细解释请看这里

// 用DefaultMode启动
void CFRunLoopRun(void) {    /* DOES CALLOUT */int32_t result;do {result = CFRunLoopRunSpecific(CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 1.0e10, false);CHECK_FOR_FORK();} while (kCFRunLoopRunStopped != result && kCFRunLoopRunFinished != result);
}
  1. 这种模型通常被称作 Event Loop。 Event Loop 在很多系统和框架里都有实现,比如 Node.js 的事件处理,比如 Windows 程序的消息循环,再比如 OSX/iOS 里的 RunLoop。实现这种模型的关键点在于:如何管理事件/消息,如何让线程在没有处理消息时休眠以避免资源占用、在有消息到来时立刻被唤醒。
  2. RunLoop管理了其需要处理的事件和消息,并提供了一个入口函数来执行上面 Event Loop 的逻辑。线程执行了这个函数后,就会一直处于这个函数内部 "接受消息->等待->处理" 的循环中,直到这个循环结束(比如传入 quit 的消息),函数返回。

二、RunLoop的深入分析

1. 从程序入口main函数开始

int main(int argc, char * argv[]) {@autoreleasepool {return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));}
}

程序主线程一开始,就会一直跑,那么猜想其内部一定是开启了一个和主线程对应的RunLoop并且可以看出函数返回的是一个int返回值的 UIApplicationMain()函数

2. 我们继续深入UIApplicationMain函数

UIKIT_EXTERN int UIApplicationMain
(int argc, 
char *argv[], 
NSString * __nullable principalClassName,NSString * __nullable delegateClassName
);

我们发现它返回的是一个int类型的值,那么我们对main函数做一些修改:

int main(int argc, char * argv[]) {@autoreleasepool {NSLog(@"开始");int re = UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));NSLog(@"结束");return re;}
}

运行程序,我们发现只会打印开始,并不会打印结束,这再次说明在UIApplicationMain函数中,开启了一个和主线程相关的RunLoop,导致UIApplicationMain不会返回,一直在运行中,也就保证了程序的持续运行。

3. 继续学习CFRunLoopRef

RunLoop对象包括Fundation中的NSRunLoop对象和CoreFoundation中的CFRunLoopRef对象。因为Fundation框架是基于CoreFoundation的封装,因此我们学习RunLoop还是要研究CFRunLoopRef 源码。

获取RunLoop对象

//Foundation
[NSRunLoop currentRunLoop]; // 获得当前线程的RunLoop对象
[NSRunLoop mainRunLoop]; // 获得主线程的RunLoop对象
//Core Foundation
CFRunLoopGetCurrent(); // 获得当前线程的RunLoop对象
CFRunLoopGetMain(); // 获得主线程的RunLoop对象

1. 主线程获取CFRunLoopRef源码

   // 创建字典CFMutableDictionaryRef dict = CFDictionaryCreateMutable(kCFAllocatorSystemDefault, 0, NULL, &kCFTypeDictionaryValueCallBacks);
// 创建主线程 根据传入的主线程创建主线程对应的RunLoopCFRunLoopRef mainLoop = __CFRunLoopCreate(pthread_main_thread_np());
// 保存主线程 将主线程-key和RunLoop-Value保存到字典中CFDictionarySetValue(dict, pthreadPointer(pthread_main_thread_np()), mainLoop);

2. 创建与子线程相关联的CFRunLoopRe源码苹果不允许直接创建 RunLoop,它只提供了两个自动获取的函数:CFRunLoopGetMain() 和 CFRunLoopGetCurrent()。

// 全局的Dictionary,key 是 pthread_t, value 是 CFRunLoopRef
static CFMutableDictionaryRef loopsDic;
// 访问 loopsDic 时的锁
static CFSpinLock_t loopsLock;
// 获取一个 pthread 对应的 RunLoop。
CFRunLoopRef _CFRunLoopGet(pthread_t thread) {OSSpinLockLock(&loopsLock);if (!loopsDic) {// 第一次进入时,初始化全局Dic,并先为主线程创建一个 RunLoop。loopsDic = CFDictionaryCreateMutable();CFRunLoopRef mainLoop = _CFRunLoopCreate();CFDictionarySetValue(loopsDic, pthread_main_thread_np(), mainLoop);}// 直接从 Dictionary 里获取。CFRunLoopRef loop = CFDictionaryGetValue(loopsDic, thread));if (!loop) {// 取不到时,创建一个loop = _CFRunLoopCreate();CFDictionarySetValue(loopsDic, thread, loop);// 注册一个回调,当线程销毁时,顺便也销毁其对应的 RunLoop。_CFSetTSD(..., thread, loop, __CFFinalizeRunLoop);}OSSpinLockUnLock(&loopsLock);return loop;
}
CFRunLoopRef CFRunLoopGetMain() {return _CFRunLoopGet(pthread_main_thread_np());
}
CFRunLoopRef CFRunLoopGetCurrent() {return _CFRunLoopGet(pthread_self());
}

3. CFRunloopRef与线程之间的关系

  1. 首先,iOS 开发中能遇到两个线程对象: pthread_t 和 NSThread。过去苹果有份文档标明了 NSThread 只是 pthread_t 的封装,但那份文档已经失效了,现在它们也有可能都是直接包装自最底层的 mach thread。苹果并没有提供这两个对象相互转换的接口,但不管怎么样,可以肯定的是 pthread_t 和 NSThread 是一一对应的。比如,你可以通过 pthread_main_np() 或 [NSThread mainThread] 来获取主线程;也可以通过 pthread_self() 或 [NSThread currentThread] 来获取当前线程。CFRunLoop 是基于 pthread 来管理的。
  2. CFRunLoopRef源码可以看出,线程和 RunLoop 之间是一一对应的,其关系是保存在一个全局的 Dictionary 里。线程刚创建时并没有 RunLoop,如果你不主动获取,那它一直都不会有。RunLoop 的创建是发生在第一次获取时,RunLoop 的销毁是发生在线程结束时。你只能在一个线程的内部获取其 RunLoop(主线程除外)。[NSRunLoop currentRunLoop];方法调用时,会先看一下字典里有没有存子线程相对用的RunLoop,如果有则直接返回RunLoop,如果没有则会创建一个,并将与之对应的子线程存入字典中。

. 总结来说. CFRunloopRef与线程之间的关系

  1. 线程在处理完自己的任务后一般会退出,为了实现线程不退出能够随时处理任务的机制被称为EventLoop,node.js 的事件处理,windows程序的消息循环,iOS、OSX的RunLoop都是这种机制。
  2. 线程和RunLoop是一一对应的,关系保存在全局的字典里。在主线程中,程序启动时,系统默认添加了有kCFRunLoopDefaultMode 和 UITrackingRunLoopMode两个预置Mode的RunLoop,保证程序处于等待状态,如果接收到来自触摸事件等,就会执行任务,否则处于休眠中。
  3. 线程创建时并没有RunLoop,(主线程除外),RunLoop不能创建,只能主动获取才会有。RunLoop的创建是在第一次获取时,RunLoop的销毁是发生在线程结束时。只能在一个线程中获取自己和主线程的RunLoop。

Core Foundation中关于RunLoop的5个类

CFRunLoopRef  //获得当前RunLoop和主RunLoop
CFRunLoopModeRef  //运行模式,只能选择一种,在不同模式中做不同的操作
CFRunLoopSourceRef  //事件源,输入源
CFRunLoopTimerRef //定时器时间
CFRunLoopObserverRef //观察者

CFRunLoopModeRef详细内容请点击这里1.简介:

每个CFRunLoopRef 包含若干个 Mode,每个 Mode 又包含若干个 Source/Timer/Observer。每次调用 RunLoop 的主函数时,只能指定其中一个 Mode,这个Mode被称作 CurrentMode。如果需要切换 Mode,只能退出 Loop,再重新指定一个 Mode 进入。这样做主要是为了分隔开不同组的 Source/Timer/Observer,让其互不影响。

CFRunLoopModeRef 类并没有对外暴露,只是通过 CFRunLoopRef 的接口进行了封装。他们的关系如下:

CFRunLoopRef获取Mode的接口:

CFRunLoopAddCommonMode(CFRunLoopRef runloop, CFStringRef modeName);
CFRunLoopRunInMode(CFStringRef modeName, ...);

2.CFRunLoopMode的类型

  1. kCFRunLoopDefaultModeApp的默认Mode,通常主线程是在这个Mode下运行

  2. UITrackingRunLoopMode:界面跟踪 Mode,用于 ScrollView 追踪触摸滑动,保证界面滑动时不受其他 Mode 影响
  3. UIInitializationRunLoopMode:在刚启动 App 时第进入的第一个 Mode,启动完成后就不再使用
  4. GSEventReceiveRunLoopMode:接受系统事件的内部 Mode,通常用不到
  5. kCFRunLoopCommonModes:这是一个占位用的Mode,作为标记kCFRunLoopDefaultMode和UITrackingRunLoopMode用,并不是一种真正的Mode

3.CFRunLoopMode 和 CFRunLoop 的结构

struct __CFRunLoopMode {CFStringRef _name;            // Mode Name, 例如 @"kCFRunLoopDefaultMode"CFMutableSetRef _sources0;    // Set CFMutableSetRef _sources1;    // Set CFMutableArrayRef _observers; // Array CFMutableArrayRef _timers;    // Array ...
};struct __CFRunLoop {CFMutableSetRef _commonModes;     // Set CFMutableSetRef _commonModeItems; // Set CFRunLoopModeRef _currentMode;    // Current Runloop ModeCFMutableSetRef _modes;           // Set ...
};

CFRunLoopSourceRef

  1. 是事件产生的地方。Source有两个版本:Source0 和 Source1。
  2. Source0 只包含了一个回调(函数指针),它并不能主动触发事件。使用时,你需要先调用 CFRunLoopSourceSignal(source),将这个 Source 标记为待处理,然后手动调用 CFRunLoopWakeUp(runloop) 来唤醒 RunLoop,让其处理这个事件。
  3. Source1 包含了一个 mach_port 和一个回调(函数指针),被用于通过内核和其他线程相互发送消息。这种 Source 能主动唤醒 RunLoop 的线程.

CFRunLoopObserverRef

CFRunLoopObserverRef是观察者,能够监听RunLoop的状态改变。

我们直接来看代码,给RunLoop添加监听者,监听其运行状态:

 //创建监听者/*第一个参数 CFAllocatorRef allocator:分配存储空间 CFAllocatorGetDefault()默认分配第二个参数 CFOptionFlags activities:要监听的状态 kCFRunLoopAllActivities 监听所有状态第三个参数 Boolean repeats:YES:持续监听 NO:不持续第四个参数 CFIndex order:优先级,一般填0即可第五个参数 :回调 两个参数observer:监听者 activity:监听的事件*//*所有事件typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {kCFRunLoopEntry = (1UL <&lt; 0),   //   即将进入RunLoopkCFRunLoopBeforeTimers = (1UL <&lt; 1), // 即将处理TimerkCFRunLoopBeforeSources = (1UL <&lt; 2), // 即将处理SourcekCFRunLoopBeforeWaiting = (1UL <&lt; 5), //即将进入休眠kCFRunLoopAfterWaiting = (1UL <&lt; 6),// 刚从休眠中唤醒kCFRunLoopExit = (1UL <&lt; 7),// 即将退出RunLoopkCFRunLoopAllActivities = 0x0FFFFFFFU};*/CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(CFAllocatorGetDefault(), kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {switch (activity) {case kCFRunLoopEntry:NSLog(@"RunLoop进入");break;case kCFRunLoopBeforeTimers:NSLog(@"RunLoop要处理Timers了");break;case kCFRunLoopBeforeSources:NSLog(@"RunLoop要处理Sources了");break;case kCFRunLoopBeforeWaiting:NSLog(@"RunLoop要休息了");break;case kCFRunLoopAfterWaiting:NSLog(@"RunLoop醒来了");break;case kCFRunLoopExit:NSLog(@"RunLoop退出了");break;default:break;}});// 给RunLoop添加监听者/*第一个参数 CFRunLoopRef rl:要监听哪个RunLoop,这里监听的是主线程的RunLoop第二个参数 CFRunLoopObserverRef observer 监听者第三个参数 CFStringRef mode 要监听RunLoop在哪种运行模式下的状态*/CFRunLoopAddObserver(CFRunLoopGetCurrent(), observer, kCFRunLoopDefaultMode);/*CF的内存管理(Core Foundation)凡是带有Create、Copy、Retain等字眼的函数,创建出来的对象,都需要在最后做一次releaseGCD本来在iOS6.0之前也是需要我们释放的,6.0之后GCD已经纳入到了ARC中,所以我们不需要管了*/CFRelease(observer);

相关文章:

开源库jemalloc简介

jemalloc是通用的malloc(3)实现&#xff0c;它强调避免碎片和可扩展的并发支持。它的源码位于https://github.com/jemalloc/jemalloc&#xff0c;最新稳定版本为5.2.1。 glibc的内存分配算法是基于dlmalloc实现的ptmalloc&#xff1b;tcmalloc是Google开发的内存分配器&#x…

改善深度学习训练的trick总结 | CSDN博文精选

扫码参与CSDN“原力计划”作者 | ZesenChen来源 | CSDN博客精选在深度学习中&#xff0c;同样一个模型用不同的初始化&#xff0c;数据处理&#xff0c;batch size&#xff0c;学习率&#xff0c;优化器都能得到不同性能的参数。我根据自己参与过的比赛中经常用到的一些trick进…

jQuery中的Ajax----03

为什么80%的码农都做不了架构师&#xff1f;>>> $.ajax(0方式是jQuery最底层的Ajax实现。 它的结构为: $.ajax(options) 该方法只有1个参数&#xff0c;但在这个对象里包含了$.ajax()方法所需要的请求设置以及回调函数等信息。参数以key/value的形式存在&#xff0…

Docker容器中数据两种持久化存储方式:卷和挂载宿主目录

镜像使用的是分层存储&#xff0c;容器也是如此。每一个容器运行时&#xff0c;是以镜像为基础层&#xff0c;在其上创建一个当前容器的存储层&#xff0c;我们可以称这个为容器运行时读写而准备的存储层为容器存储层。容器存储层的生存周期和容器一样&#xff0c;容器消亡时&a…

CFRunLoopRef 的内部逻辑(向 ibireme学习)

据苹果在文档里的说明&#xff0c;RunLoop 内部的逻辑大致如下:/// 用DefaultMode启动 void CFRunLoopRun(void) {CFRunLoopRunSpecific(CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 1.0e10, false); }/// 用指定的Mode启动&#xff0c;允许设置RunLoop超时时间 int CFRunL…

倒计时 3 天!「2019 嵌入式智能国际大会」全日程大公开!

立即抢购&#xff1a;https://t.csdnimg.cn/otBk还有3天&#xff0c;大伙期待的「2019嵌入式智能国际大会」正式开幕了&#xff01;2019年12月6日-7日&#xff0c;我们在深圳市人才研修院见&#xff01;大会以“万物互联泛在智能”为主题&#xff0c;邀请30位海内外顶级专家作为…

Fckeditor PHP/ASP File Upload Vul

目录 1. 漏洞描述 2. 漏洞触发条件 3. 漏洞影响范围 4. 漏洞代码分析 5. 防御方法 6. 攻防思考 1. 漏洞描述 FCKeditor是目前最优秀的可见即可得网页编辑器之一&#xff0c;它采用JavaScript编写。具备功能强大、配置容易、跨浏览器、支持多种编程语言、开源等特点。它非常流行…

iOS App上架流程(2016详细版),真心很详细。

一、前言&#xff1a;作为一名iOSer&#xff0c;把开发出来的App上传到App Store是必要的。下面就来详细讲解一下具体流程步骤。二、准备&#xff1a;一个已付费的开发者账号&#xff08;账号类型分为个人&#xff08;Individual&#xff09;、公司&#xff08;Company&#xf…

飞机的“黑色十分钟”能被人工智能消灭吗?

【导读】近年来&#xff0c;“AI的应用和落地”逐渐成了具化的关键词&#xff0c;它和很多事物很多行业结合在一起&#xff0c;形成了奇妙的“化学反应”。例如&#xff0c;在日常生活中&#xff0c;AI可以推送我们喜欢的新闻或视频&#xff0c;可以在拍照的时候识别场景提升照…

Jenkins简介及在Windows上的简单使用示例

Jenkins是一款开源CI(Continuous Integration&#xff0c;持续集成)&CD(Continuous Delivery&#xff0c;持续交付)软件&#xff0c;用于自动化各种任务&#xff0c;包括构建、测试和部署软件&#xff0c;源码在https://github.com/jenkinsci/jenkins &#xff0c;License为…

IOS开发之数据sqlite使用

一、引入工具包引入工具包libsqlite3.dylib,该工具包为C语言工具包。 二、代码操作数据库1、创建并且链接数据库 - (void) _connectDB{//1>获取沙盒路径作为数据库创建时候的初始化路径NSString * pathNSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDom…

通过Dockerfile构建Docker镜像

Dockerfile是一个文本文件,其内包含了一条条的指令(Instruction),每一条指令构建一层,因此每一条指令的内容,就是描述该层应当如何构建. Dockerfile支持Shell类的行尾添加"\"的命令换行方式,以及行首"#"进行注释的格式. 使用Dockerfile构建Docker镜像时注…

华为腾讯百度众安微众360大咖齐聚,2019中国区块链开发者大会首批议程曝光!...

作者 | Aholiab出品 | 区块链大本营&#xff08;blockchain_camp&#xff09;随着区块链被定义为国家战略&#xff0c;区块链技术得到升温。据有关国际研究机构预测&#xff0c;三年后全球区块链市场规模将达到139.6亿美元&#xff08;约合986.23亿元人民币&#xff09;&#x…

iOS GCD使用

dispatch_queue_t queue dispatch_queue_create("testQueue", DISPATCH_QUEUE_CONCURRENT); dispatch_async(queue, ^{ // 追加任务1[self.hud show:YES];for (int i 0; i < 2; i) { [NSThread sleepForTimeInterval:2]; // 模拟耗时操作[self reqHopwork];} })…

01 http协议概念及工作流程

一&#xff1a;HTTP协议 重要性: 无论是以后用webserverice ,还是用rest做大型架构,都离不开对HTTP协议的认识. 甚至可以简化的说: webservice http协议XML Rest HTTP协议 json 各种API,也一般是用httpXML/json来实现的. 往小说:做采集,小偷站,也需要对HTTP协议有所了解, 以…

iOS原生与html交互 使用第三方WebViewJavascriptBridge

HTML页面代码 <!DOCTYPE html><html xmlns:http"http://www.w3.org/1999/xhtml"><head> <meta charset"utf-8"> <title>迎新好礼</title> <meta name"viewport" content"widthdevice-width,initial…

Docker容器中挂载NFS共享目录

之前在https://blog.csdn.net/fengbingchun/article/details/110561129 介绍过使用Dockerfile构建ubuntu 16.04镜像,并在容器中编译执行Messy_Test项目.这里介绍下如何在容器中挂载NFS服务器上的共享目录. Dockerfile内容如下&#xff1a; FROM ubuntu:16.04 LABEL maintaine…

倒计时1天 | 2019 中国大数据技术大会(BDTC)报名通道即将关闭(附参会提醒)...

2019年12月5-7日&#xff0c;由中国计算机学会主办&#xff0c;CCF 大数据专家委员会承办&#xff0c;CSDN、中科天玑数据科技股份有限公司协办的中国大数据技术大会&#xff08;BDTC 2019&#xff09;将于北京长城饭店隆重举行。届时&#xff0c;超过百位顶尖技术专家将齐聚于…

Android TextView的一些小知识

2019独角兽企业重金招聘Python工程师标准>>> 1.设置文字行距 android:lineSpacingExtra"8dp" 或者 android:lineSpacingMultiplier"1.5" 2.设置字间距 在API21里可以设置 API 21 android:letterSpacing"0.5f" //字间距 注意&#x…

iOS WKWebView带进度条封装(只用传入url,可改变进度条颜色)

1 NSTimeraddition.h #import <Foundation/Foundation.h> interface NSTimer (addition) /** 暂停时间 */ - (void)w_pauseTime; /** 获取内容所在当前时间 */ - (void)w_webPageTime; /** 当前时间 time 秒后的时间 */ - (void)w_webPageTimeWithTimeInterval:(NSTimeIn…

Ubuntu上配置VS Code调试C++

直接使用GDB在Ubuntu上调试C code&#xff0c;有时不是很方便&#xff0c;这里介绍下在Ubuntu上通过Visual Studio Code调试C code操作步骤&#xff0c;通过CMake编译。 安装所需依赖&#xff1a; (1).在Ubuntu上安装Visual Studio Code最新稳定版本1.51.1&#xff1b; (2).…

因果关系是通向强AI的阶梯or作用被夸大?

整理 | 夕颜出品 | AI科技大本营&#xff08;ID:rgznai100&#xff09;一直以来&#xff0c;机器学习和统计学之间的界限就比较模糊&#xff0c;比如诺奖得主托马斯萨金特就曾经说过人工智能其实就是统计学&#xff0c;只不过用了一个很华丽的辞藻。但同时也有人认为&#xff0…

Android中设置TextView的颜色setTextColor

tv.setTextColor(Color.parseColor("#FFFFFF")); tv.setTextColor(Color.WHITE); tv.setTextColor(Color.rgb(255, 255, 255)); //注意Color是大写C&#xff0c;不是color.holo_orange_dark,这样错误并没效果的 tv.setBackgroundResource(R.drawable.icon_bg_rectan…

iOS 跑马灯封装(带点击事件)

1.WAdvertScrollView.h#import <UIKit/UIKit.h>class WAdvertScrollView;typedef enum : NSUInteger {/// 一行文字滚动样式WAdvertScrollViewStyleNormal,/// 二行文字滚动样式WAdvertScrollViewStyleMore, } WAdvertScrollViewStyle;protocol WAdvertScrollViewDelegat…

日期与unix时间戳之间的转换C++实现

之前在https://blog.csdn.net/fengbingchun/article/details/107023645 中介绍过gmtime和localtime的区别&#xff0c;这里介绍下日期与Unix时间戳之间转换的实现&#xff0c;其中也会用到这两个函数。 Unix时间戳(Unix timestamp)&#xff1a;是一种时间表示方式&#xff0c;…

模型训练完才是业务的开始?说说模型监控 | CSDN博文精选

扫码参与CSDN“原力计划”作者 | A字头来源 | 数据札记倌(ID:Data_Groom)“模型训练结束后才是业务真正的开始”简述每次模型训练完成后&#xff0c;并不意味着项目的结束&#xff0c;在训练模型后&#xff0c;我们还需要将其稳定上线&#xff0c;然后部署一套相应的监控体系&a…

后端码农谈前端(CSS篇)第一课:CSS概述

一、从扮演浏览器开始 扮演浏览器是Head First图书中很有意义的一个环节。可作者忘记了告诉我们扮演浏览器的台本。我们从这里开始。 上图是webkit内核渲染html和css的流程图。从该图我们可以知道以下几个关键信息&#xff1a; HTML的解析过程和CSS的解析过程是独立完成的。HTM…

远场语音识别错误率降低30%,百度提基于复数CNN网络的新技术

【12月公开课预告】&#xff0c;入群直接获取报名地址12月11日晚8点直播主题&#xff1a;人工智能消化道病理辅助诊断平台——从方法到落地12月12日晚8点直播&#xff1a;利用容器技术打造AI公司技术中台12月17日晚8点直播主题&#xff1a;可重构计算&#xff1a;能效比、通用性…

深度神经网络中的局部响应归一化LRN简介及实现

Alex、Hinton等人在2012年的NIPS论文《ImageNet Classification with Deep Convolutional Neural Networks》中将LRN应用于深度神经网络中(AlexNet)。论文见&#xff1a;http://www.cs.toronto.edu/~hinton/absps/imagenet.pdf &#xff0c;截图如下&#xff1a; 公式解释&…

iOS 被拒解析

原因&#xff1a; Your app uses the "prefs:root" non-public URL scheme, which is a private entity. The use of non-public APIs is not permitted on the App Store because it can lead to a poor user experience should these APIs change.Continuing to us…