前言

Runtime是近年来面试遇到的一个高频方向,也是我们平时开发中或多或少接触的一个领域,那么什么是runtime呢?它又可以用来做什么呢?

什么是Runtime?平时项目中有用过么?

  • OC是一门动态性比较强的编程语言,允许很多操作推迟到程序运行时再进行
  • OC的动态性就是由Runtime来支撑和实现的,Runtime是一套C语言的API,封装了很多动 态性相关的函数
  • 平时编写的OC代码,底层都是转换成了Runtime API进行调用

具体应用

  • 利用关联对象(AssociatedObject)给分类添加属性
  • 遍历类的所有成员变量(修改textfield的占位文字颜色、字典转模型、自动归档解档)
  • 交换方法实现(交换系统的方法)
  • 利用消息转发机制解决方法找不到的异常问题

详解isa

我们在研究对象的本质的时候提到过isa,当时说的是isa是个指针存储的是个类对象或者元类对象的地址

  • 实例对象的isa指向类对象
  • 类对象的isa指向元类对象

确实,在arm64架构(真机环境)前,isa单纯的就是一个指针,里面存储着类对象或者元类对象地址,但是arm64架构后,系统对isa指针进行了优化,我们在源码中可以探其结构:
在这里插入图片描述
可以看到,isa是个isa_t类型的数据,我们在点进去看一下isa_t是什么数据:
在这里插入图片描述
isa_t是个union结构,里面包含了一个结构体,结构体里面是个宏ISA_BITFIELD,我们看看这个宏是什么?
在这里插入图片描述
也就是这个结构体里面包含很多东西,但是究竟是什么东西要根据系统来确定。

那么在arm64架构下,isa指针的真实结构是:
在这里插入图片描述
在我们具体分析isa内部各个参数分别代表什么之前,我们需要弄清楚这个union是什么呢?我们看着这个union和结构体的结构很像,这两者的区别如下↓↓

  • union:共用体,顾名思义,就是多个成员共用一块内存。在编译时会选取成员中长度最长的来声明。共用体内存=MAX(各变量)
  • struct:结构体,每个成员都是独立的一块内存。 结构的内存=sizeof(各变量之和)+内存对齐

也就是说,union共用体内所有的变量,都用同一块内存,而struct结构体内的变量是各个变量有各个变量自己的内存,举例说明:

在这里插入图片描述
我们分别定义了一个共用体test1和一个结构体test2,里面都各自有八个char变量,打印出来各自占用内存我们发现共用体只占用了1个内存,而结构体占用了8个内存.

其实结构体占用8个内存很好理解,8个char变量,每个char占用一个,所以是8;而union共用体为什么只占用一个呢?这是因为他们共享同一个内存存储东西,他们的内存结构是这样的:
在这里插入图片描述
我们看到te就一个内存空间,也就是所有的公用体成员公用一个空间,并且同一时间只能存储其中一个成员变量的值,这一点我们可以打断点或打印进行确认:

在这里插入图片描述
我们发现,第一次打印的时候,bdf这些值都是1的打印出来都是0,这是因为当te.g = ‘0’,执行完后,这个内存存储的是g的值0,所以访问的时候打印结果都是0。第二次打印同理,te.h执行完内存中存储的是1,再访问这块内存那么得到的结果都会是1。所以我们从这也可以看出

union共用体就是系统分配一个内存供里面的成员共同使用,某一时间只能存储其中某一个变量的值,这样做相比结构体而言可以很大程度的节省内存空间。

既然我们已经知道isa_t使用共用体的原因是为了最大限度节省内存空间,那么各个成员后面的数字代表什么呢?这就涉及到了位域.

我们看到union共用体为了节省空间是不断的进行值覆盖操作,也就是新值覆盖旧值,结合位域的话可以更大限度的节约内存空间还不用覆盖旧值。我们都知道一个字节是8个bit位,所以位域的作用就是将字节这个内存单位缩小为bit位来存储东西。我们把上面这个union共用体加上位域:

在这里插入图片描述
上面这段代码的意思就是,abcdefgh这八个char变量不再是不停地覆盖旧值操作了,而是将一个字节分成8个bit位,每个变量一个bit位,按照顺序从右到左一次排列。

我们都知道char变量占用一个字节,一个字节有8个bit位,也就是char变量有8位,那么te和te2的内存结构如下所示:
在这里插入图片描述

这个结构我们也可以通过打印来验证:te占用一个字节位置,内存地址对应的值是0xaa,转换成二进制正好是10101010,也就是a~h存储的值。

在这里插入图片描述
我们可以看到,现在是将一个字节中的8个bit位分别让给8个char变量存储数据,所以这些char变量存储的数据不是0就是1,可以看出来这种方式非常省内存空间,将一个字节分成8个bit位存储东西,物尽其用。

所以我们根据isa_t结构体中的所占用bit位加起来=64可以得知isa指针占用8个字节空间。

虽然位域极大限度的节省了内存空间,但是现在面临着一个问题,那就是如何给这些变量赋值或者取值呢?
普通结构体中因为每个变量都有自己的内存地址,所以直接根据地址读取值即可, 但是union共用体中是大家共用同一个内存地址,只是分布在不同的bit位上,所以是没有办法通过内存地址读取值的,那么这就用到了位运算符,我们需要知道以下几个概念:

  • &:按位与,同真为真,其余为假

  • |:按位或,有真则真,全假则假

  • <<:左移,表示左移动一位 (默认是00000001 那么1<<1 则变成了00000010 1<<2就是00000100)

  • ~:按位取反

  • 掩码 : 一般把用来进行按位与(&)运算来取出相应的值的值称之为掩码(Mask)。如 #define TallMask 0b00000100 :TallMask就是用来取出右边第三个bit位数据的掩码

好,那么我们来看下这些运算符是怎么可以做到取值赋值的呢?比如说我们上面的te共用体内有8个char,要是我们想出去char b的值怎么取呢?这就用到了&:
在这里插入图片描述
按位与&1<<1就可以取出b位的值了,b是1那么结果就是1,b是0那么结果就是0;

同理,当我们为f设置值的时候,也是类似的操作,就是在改变f的值的同时不影响其他值,这里我们要看赋的值是0还是1,不同值操作不同:
在这里插入图片描述
所以,这就是共同体中取值赋值的操作流程,那么我们接下来回到isa指针这个结构体中,看一下它里面的各个成员以及怎么取赋值的↓↓

/*nonpointer0,代表普通的指针,存储着Class、Meta-Class对象的内存地址1,代表优化过,使用位域存储更多的信息*/
uintptr_t nonpointer        : 1;                                       \/*has_assoc:是否有设置过关联对象,如果没有,释放时会更快*/
uintptr_t has_assoc         : 1;                                       \/*是否有C++的析构函数(.cxx_destruct),如果没有,释放时会更快*/
uintptr_t has_cxx_dtor      : 1;                                       \/*存储着Class、Meta-Class对象的内存地址信息*/
uintptr_t shiftcls          : 33; /*MACH_VM_MAX_ADDRESS 0x1000000000*/ \/*用于在调试时分辨对象是否未完成初始化*/
uintptr_t magic             : 6;                                       \/*是否有被弱引用指向过,如果没有,释放时会更快*/
uintptr_t weakly_referenced : 1;                                       \/*对象是否正在释放*/
uintptr_t deallocating      : 1;                                       \/*里面存储的值是引用计数器减1*/
uintptr_t has_sidetable_rc  : 1;                                       \/*引用计数器是否过大无法存储在isa中如果为1,那么引用计数会存储在一个叫SideTable的类的属性中*/
uintptr_t extra_rc          : 19;

我们看到,isa指针确实做了很大的优化,同样是占用8个字节,优化后的共用体不仅存放这类对象或元类对象地址,还存放了很多额外属性,接下来我们对这个结构进行验证:需要注意的是因为是arm64架构 所以这个验证需要是ios项目且需要运行在真机上 这样才会得出准确的结果

首先,我们来验证这个shiftcls是否就是类对象内存地址。
在这里插入图片描述
我们定义了一个dog对象,我们打印它的isa0x000001a102a48de1

从上面的分析我们得知,要取出shiftcls的值需要isa的值&ISA_MASK(这个isa_mask在源码中有定义),得出$1 = 0x000001a102a48de0

而$1的地址值正是我们上面打印出来Dog类对象的地址值,所以这也验证了isa_t的结构。
在这里插入图片描述
我们还可以来看一下其他一些成员,比如说是否被弱指针指向过?我们先将上面没有被__weak指向过的数据保存一下,其中红色框中的就是这个属性,0表示没有被指向过
在这里插入图片描述
然后我们修改代码,添加弱指针指向dog:

__weak Dog *weaKDog = dog;

注意:只要设置过关联对象或者弱引用引用过对象,has_assoc或weakly_referenced的值就会变成1,不论之后是否将关联对象置为nil或断开弱引用。

在这里插入图片描述
发现确实由0变成了1,所以可以验证isa_t的结构,这个实验要确保程序运行在真机才能出现这个结果。所以arm64后确实对isa指针做了优化处理,不在单纯的存放类对象或者元类对象的内存地址,而是除此之外存储了更多内容。

class的具体结构

我们之前在讲分类的时候讲到了类的大体结构,如下图所示:
在这里插入图片描述
就如我们之前讲到的,当我们调用方法的时候是从bits中的methods中查找方法。
分类的方法是排在主类方法前面的,所以调用同名方法是先调用分类的,而且究竟调用哪个分类的方法要取决于编译的先后顺序等等:
在这里插入图片描述

rw_t 和ro_t

那么这个rw_t中的methodsro_t中的methods有什么不一样呢?

  • 首先,ro_t中methods,是只包含原始类的方法,不包括分类的,而rw_t中的methods即包含原始类的也包含分类的;

  • 其次,ro_t中的methods只能读取不能修改,而rw_t中的methods既可以读取也可以修改,所以我们今后在动态添加方法修改方法的时候是在rw_t中的methods去操作的;

  • 然后,ro_t中的methods是个一维数组,里面存放着method_t(对方法/函数的封装,即一个method_t代表一个方法或函数),而rw_t中的methods是个二维数组,里面存放着各个分类和原始类的数组,分类和原始类的数组中存放着method_t。即:

在这里插入图片描述

我们也可以在源码中找到rw_t (rw:read and write 读写)和ro_t (ro:read only 只读)的关系:

static Class realizeClass(Class cls)
{runtimeLock.assertLocked();const class_ro_t *ro;class_rw_t *rw;Class supercls;Class metacls;bool isMeta;if (!cls) return nil;if (cls->isRealized()) return cls;assert(cls == remapClass(cls));// 最开始cls->data是指向ro的ro = (const class_ro_t *)cls->data();if (ro->flags & RO_FUTURE) {// rw已经初始化并且分配内存空间rw = cls->data(); // cls->data指向rwro = cls->data()->ro; // cls->data()->ro指向ro  即rw中的ro指向rocls->changeInfo(RW_REALIZED|RW_REALIZING, RW_FUTURE);} else {// 如果rw并不存在,则为rw分配空间rw = (class_rw_t *)calloc(sizeof(class_rw_t), 1);// 分配空间rw->ro = ro;// rw->ro重新指向rorw->flags = RW_REALIZED|RW_REALIZING;// 将rw传入setData函数,等于cls->data()重新指向rwcls->setData(rw);}
}

首先,cls->data(即bits)是指向存储类初始化信息的ro_t的,然后在运行时的运行过程中创建了class_rw_t,等rw_t分配好内存空间后,开始将cls->data指向了rw_t并将rw_t中的ro指向了存储初始化信息的ro_t。

那么ro_t和rw_t中存储的这个method_t是个什么结构呢?我们阅读源码发现结构如下,我们发现有三个成员:name、types、imp,我们一一来看:

method_t

在这里插入图片描述

  • name,表示方法的名称,一般叫做选择器,可以通过@selector()sel_registerName()获得。

/*
比如test方法,它的SEL就是@selector(test);或者sel_registerName(“test”);需要注意的一点就是不同类中的同名方法,它们的方法选择器是相同的,比如A、B两个类中都有test方法,那么这两个test方法的名称都是@selector(test);或者sel_registerName(“test”);
*/

  • types,表示方法的编码,即返回值、参数的类型,通过字符串拼接的方式将返回值和参数拼接成一个字符串,来代表函数返回值及参数。

/*
比如ViewDidload方法,我们都知道它的返回值是void,参数转为底层语言后是self和_cmd,即一个id类型和一个方法选择器,那么encode后就是v16@0:8(它所表示的意思是:返回值是void类型,参数一共占用16个字节,第一个参数是@类型,内存空间从0开始,第二个参数是:类型,内存空间从8开始),当然这里的数字可以不写,简写成V@:
*/

关于更多encode规则,可以查看下面这个表:
在这里插入图片描述
当然除了自己手写外,iOS提供了@encode的指令,可以将具体的类型转化成字符串编码。

NSLog(@"%s",@encode(int));
NSLog(@"%s",@encode(float));
NSLog(@"%s",@encode(id));
NSLog(@"%s",@encode(SEL));// 打印内容
Runtime-test[25275:9144176] i
Runtime-test[25275:9144176] f
Runtime-test[25275:9144176] @
Runtime-test[25275:9144176] :
  • imp,表示指向函数的指针(函数地址),即方法的具体实现,我们调用的方法实际上最后都是通过这个imp去进行最终操作的。

方法缓存

我们在分析清楚方法列表和方法的结构后,我们再来看一下方法的调用是怎么一个流程呢?是直接去方法列表里面遍历查找对应的方法吗?

其实不然,我们在分析类的结构的时候,除了bits(指向类的具体信息,包括rw_t、ro_t等等一些内容)外,还有一个方法缓存:cache,用来缓存曾经调用过的方法
在这里插入图片描述
所以系统查找对应方法不是通过遍历rw_t这个二维数组来寻找方法的,这样做太慢,效率太低。
真正的做法是:

系统是先从方法缓存中找有没有对应的方法,有的话就直接调用缓存里的方法,根据imp去调用方法,没有的话,就再去方法数组中遍历查找,找到后调用并保存到方法缓存里

流程如下:
在这里插入图片描述
那么方法是怎么缓存到cache中的呢?系统又是怎么查找缓存中的方法的呢?我们通过源码来看一下cache的结构:

cache_t

在这里插入图片描述

散列表(Hash table,也叫哈希表),是根据关键码值(Key value)而直接进行访问的数据结构。也就是说,它通过把关键码值映射到表中一个位置来访问记录,以加快查找的速度。这个映射函数叫做散列函数,存放记录的数组叫做散列表。

我们可以看到,cache_t里面就三个成员,后两个代表长度和数量,是int类型,肯定不是存储方法的地方,所以方法应该是存储在_buckets这个散列表中。散列存储的是一个个的bucket_t的结构体,那么这个bucket_t又是个什么结构呢?

bucket_t

在这里插入图片描述
所以cache_t底部结构是这样的:
 在这里插入图片描述
我们看到,bucket_t就两个值,一个key一个impkey的话就是方法名,也就是SEL,而imp就是Value,也就是当我们调用一个方法是来到方法缓存中查找,通过比对方法名是不是一致,一致的话就返回对应的imp,也就是方法地址,从而可以调用方法,那么这个散列表是怎么查找的呢?难道也是通过遍历吗?

方法查找

我们通过阅读源码来一探究竟:
在这里插入图片描述
在这里插入图片描述

通过上面代码的阅读,我们可以知道系统在cache_t中查找方法并不是通过遍历,而是通过方法名SEL&mask得到一个索引,直接去读数组索引中的方法,如果该方法的SEL与我们调用的方法名SEL一致,那么就返回这个方法,否则会判断当前架构,在x86或者i386架构中是向下寻找,在arm64架构中是向上寻找,直到找完为止。

好,既然取值的时候不是遍历,而是直接读的索引,那么讲方法存储到缓存中也肯定是通过这种方式了,直接方法名&mask拿到索引,然后将_key和_imp存储到对应的索引上,这一点我们通过源码也可以确认:
在这里插入图片描述
我们看到无论是存还是读,都是调用了find函数,查看SEL&mask对应的索引的方法,不合适的话再向下寻找直到找到合适的位置。

那么这里有两个疑问,为什么SEL&mask会出现不是该方法名(读)或者不为空(写)的情况呢?散列表扩容后方法还在吗?

首先,SEL&mask这个问题,是因为不同的方法名&mask可能出现同一个结果,比如test方法的SEL是011,run方法的SEL是010,mask是010,那么无论是test的SEL&mask还是run的SEL&mask 记过都是010,如果大家都存在这个索引里面是会出问题的,所以为了解决这个索引重复的问题需要先做判断,即拿到索引后先判断这个索引对应的值是不是你想要的,是的话你拿走用,不是的话向下继续找,方法缓存也是同样的道理。我们先调用test方法,缓存到010索引,再调用run方法,发现010位置不为空了,那就判断010下面的索引是否为空,为空的话就将run方法缓存到这个位置。

关于散列表扩容后,缓存方法在不在的问题,通过源码就可以知道,旧散列表已经释放掉了,所以是不存在的,再次调用的时候就得重新去rw_t中遍历找方法然后重新缓存到散列表中,比如下面这个例子:
在这里插入图片描述
到现在我们清楚了,那就是散列表中并不是按照索引依次排序或者遍历索引依次读取,那么就会出现个问题,因为SEL&mask是个小于mask的随机值且散列表存储空间超过3/4的时候就要扩容,那就会导致散列表中有一部分空间始终被限制。确实,散列表当分配内存后,每个地方最初都是null的,当某个位置的索引被用到时,对应的位置才会存储方法,其余位置仍处于空闲状态,但是这样做可以极大提高查找速度(比遍历快很多),所以这是一种空间换时间的方式。

在这里插入图片描述

方法的传递过程

我们现在已经清楚方法的调用顺序了,首先从缓存中找没有的话再去rw_t中找,那么在没有的话就去其父类中找,父类中查找也是如此,先去父类中的cache中查找,没有的话再去父类的rw_t中找,以此类推。如果查找到基类还没有呢?难道就直接报unrecognized selector sent to instance这个经典错误吗?

其实不是,方法的传递主要涉及到三个部分,这也是我们平时用得最多以及面试中经常出现的问题:

我们都知道,当我们调用一个方法是,其实底层是将这个方法转换成了objc_msgSend函数来进行调用,objc_msgSend的执行流程可以分为3大阶段:

消息发送->动态方法解析->消息转发

这个流程我们是可以从源码中得到确认,以下是源码:

/***********************************************************************
*_class_lookupMethodAndLoadCache.
* Method lookup for dispatchers ONLY. OTHER CODE SHOULD USE lookUpImp().
* This lookup avoids optimistic cache scan because the dispatcher 
* already tried that.
**********************************************************************/
MP _class_lookupMethodAndLoadCache3(id obj, SEL sel, Class cls)return lookUpImpOrForward(cls, sel, obj, YES/*initialize*/, NO/*cache*/, YES/*resolver*/);/***********************************************************************
* lookUpImpOrForward.
* The standard IMP lookup. 
* initialize==NO tries to avoid +initialize (but sometimes fails)
* cache==NO skips optimistic unlocked lookup (but uses cache elsewhere)
* Most callers should use initialize==YES and cache==YES.
* inst is an instance of cls or a subclass thereof, or nil if none is known. 
*   If cls is an un-initialized metaclass then a non-nil inst is faster.
* May return _objc_msgForward_impcache. IMPs destined for external use 
*   must be converted to _objc_msgForward or _objc_msgForward_stret.
*   If you don't want forwarding at all, use lookUpImpOrNil() instead.
**********************************************************************/
//这个函数是方法调用流程的函数 即消息发送->动态方法解析->消息转发
IMP lookUpImpOrForward(Class cls, SEL sel, id inst, bool initialize, bool cache, bool resolver)
{IMP imp = nil;bool triedResolver = NO;runtimeLock.assertUnlocked();// Optimistic cache lookupif (cache) {imp = cache_getImp(cls, sel);if (imp) return imp;}// runtimeLock is held during isRealized and isInitialized checking// to prevent races against concurrent realization.// runtimeLock is held during method search to make// method-lookup + cache-fill atomic with respect to method addition.// Otherwise, a category could be added but ignored indefinitely because// the cache was re-filled with the old value after the cache flush on// behalf of the category.runtimeLock.lock();checkIsKnownClass(cls);if (!cls->isRealized()) {realizeClass(cls);}if (initialize  &&  !cls->isInitialized()) {runtimeLock.unlock();_class_initialize (_class_getNonMetaClass(cls, inst));runtimeLock.lock();// If sel == initialize, _class_initialize will send +initialize and // then the messenger will send +initialize again after this // procedure finishes. Of course, if this is not being called // from the messenger then it won't happen. 2778172}retry:    runtimeLock.assertLocked();// Try this class's cache.//先从当前类对象的方法缓存中查看有没有对应方法imp = cache_getImp(cls, sel);if (imp) goto done;// Try this class's method lists.//没有的话再从类对象的方法列表中寻找{Method meth = getMethodNoSuper_nolock(cls, sel);if (meth) {log_and_fill_cache(cls, meth->imp, sel, inst, cls);imp = meth->imp;goto done;}}// Try superclass caches and method lists.{unsigned attempts = unreasonableClassCount();//遍历所有父类 知道其父类为空for (Class curClass = cls->superclass;curClass != nil;curClass = curClass->superclass){// Halt if there is a cycle in the superclass chain.if (--attempts == 0) {_objc_fatal("Memory corruption in class list.");}// Superclass cache.//先查找父类的方法缓存imp = cache_getImp(curClass, sel);if (imp) {if (imp != (IMP)_objc_msgForward_impcache) {// Found the method in a superclass. Cache it in this class.log_and_fill_cache(cls, imp, sel, inst, curClass);goto done;}else {// Found a forward:: entry in a superclass.// Stop searching, but don't cache yet; call method // resolver for this class first.break;}}// Superclass method list.//再查找父类的方法列表Method meth = getMethodNoSuper_nolock(curClass, sel);if (meth) {log_and_fill_cache(cls, meth->imp, sel, inst, curClass);imp = meth->imp;goto done;}}}// No implementation found. Try method resolver once.//消息发送阶段没找到imp 尝试进行一次动态方法解析if (resolver  &&  !triedResolver) {runtimeLock.unlock();_class_resolveMethod(cls, sel, inst);runtimeLock.lock();// Don't cache the result; we don't hold the lock so it may have // changed already. Re-do the search from scratch instead.triedResolver = YES;//跳转到retry入口  retry入口就在上面,也就是x消息发送过程即找缓存找rw_tgoto retry;}// No implementation found, and method resolver didn't help. // Use forwarding.//消息发送阶段没找到imp而且执行动态方法解析也没有帮助 那么就执行方法转发imp = (IMP)_objc_msgForward_impcache;cache_fill(cls, sel, imp, inst);done:runtimeLock.unlock();return imp;
}

消息发送

首先,消息发送,就是我们刚才提到的系统会先去cache_t中查找,有的话调用,没有的话去类对象的rw_t中查找,有的话调用并缓存到cache_t中,没有的话根据supperclass指针去父类中查找。父类查找也是如此,先去父类的cache_t中查找,有的话进行调用并添加到自己的cache_t中而不是父类的cache_t中,没有的话再去父类的rw_t中查找,有的话调用并缓存到自己的cache_t中,没有的话以此类推。流程如下:
在这里插入图片描述
当消息发送找到最后一个父类还没有找到对应的方法时,就会来到动态方法解析动态解析,就是意味着开发者可以在这里动态的往rw_t中添加方法实现,这样的话系统再次遍历rw_t就会找到对应的方法进行调用了。

动态方法解析

动态方法解析的流程示意图如下:
在这里插入图片描述
主要涉及到了两个方法:

+resolveInstanceMethod://添加对象方法  也就是-开头的方法
+resolveClassMethod://添加类方法  也就是+开头的方法

我们在实际项目中进行验证:

在这里插入图片描述
动态添加类方法也是如此,只不过是添加到元类对象中(此时run方法已经改成了个类方法)

在这里插入图片描述
而且我们也发现,动态添加方法的话其实无非就是找到方法实现,添加到类对象或元类对象中,至于这个方法实现是什么形式都没有关系,比如说我们再给对象方法添加方法实现时,这个实现方法可以是个类方法,同样给类方法动态添加方法实现时也可以是对象方法。也就是说系统根本没有区分类方法和对象方法,只要把imp添加到元类对象的rw_t中就是类方法,添加到类对象中就是对象方法。

在这里插入图片描述

消息转发

当我们在消息发送和动态消息解析阶段都没有找到对应的imp的时候,系统回来到最后一个消息转发阶段。所谓消息转发,就是你这个消息处理不了后可以找其他人或者其他方法来代替,消息转发的流程示意图如下:

在这里插入图片描述

即分为两步:
第一步是看能不能找其他人代你处理这方法,可以的话直接调用这个人的这个方法, 这一步不行的话就来到第二步
这个方法没有的话有没有可以替代的方法,有的话就执行替代方法。我们通过代码来验证:

我们调用dog的run方法是,因为dog本身没有实现这个方法,所以不能处理。正好cat实现了这个方法,所以我们就将这个方法转发给cat处理:
在这里插入图片描述
我们发现,确实调用了小猫run方法,但是只转发方法执行者太局限了,要求接收方法对象必须实现了同样的方法才行,否则还是无法处理,所以实用性不强。这时候,我们可以通过methodSignatureForSelector来进行更大限度的转发。

需要注意的是要想来到methodSignatureForSelector这一步需要将forwardingTargetForSelector返回nil(即默认状态)否则系统找到目标执行者后就不会再往下转发了。

开发者可以在forwardInvocation:方法中自定义任何逻辑。

为方法重新转发一个目标执行
//- (id)forwardingTargetForSelector:(SEL)aSelector{
//    if (aSelector == @selector(run)) {
//        //dog的run方法没有实现 所以我们将此方法转发到cat对象上去实现 也就是相当于将[dog run]转换成[cat run]
//        return [[Cat alloc] init];
//    }
//    return [super forwardingTargetForSelector:aSelector];
//}//方法签名
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{if (aSelector == @selector(run)) {//注意:这里返回的是我们要转发的方法的签名 比如我们现在是转发run方法 那就是返回的就是run方法的签名//1.可以使用methodSignatureForSelector:方法从实例中请求实例方法签名,或者从类中请求类方法签名。//2.也可以使用instanceMethodSignatureForSelector:方法从一个类中获取实例方法签名//这里使用self的话会进入死循环 所以不可以使用 如果其他方法中有同名方法可以将self换成其他类
//        return [self methodSignatureForSelector:aSelector];
//        return [NSMethodSignature instanceMethodSignatureForSelector:aSelector];//3.直接输入字符串return [NSMethodSignature signatureWithObjCTypes:"v@:"];}return [super methodSignatureForSelector:aSelector];
}
//当返回方法签名后 就会转发到这个方法  所以我们可以在这里做想要实现的功能  可操作空间很大
//这个anInvocation里面有转发方法的信息,比如方法调用者/SEL/types/参数等等信息
- (void)forwardInvocation:(NSInvocation *)anInvocation{//这样写不安全  可以导致cat被过早释放掉引发怀内存访问
//    anInvocation.target = [[Cat alloc] init];Cat *ca = [[Cat alloc] init];//指定targetanInvocation.target = ca;//对anInvocation做出修改后要执行invoke方法保存修改[anInvocation invoke];//或者干脆一行代码搞定[anInvocation invokeWithTarget:[[Cat alloc] init]];//上面这段代码相当于- (id)forwardingTargetForSelector:(SEL)aSelector{}中的操作//当然 转发到这里的话可操作性更大  也可以什么都不写 相当于转发到的这个方法是个空方法  也不会报方法找不到的错误//也可以在这里将报错信息提交给后台统计 比如说某个方法找不到提交给后台 方便线上错误收集//...很多用处
}

当然我们也可以访问修改anInvocation的参数,比如现在run有个age参数,

  // 参数顺序:receiver、selector、other argumentsint age;    //索引为2的参数已经放到了&age的内存中,我们可以通过age来访问[anInvocation getArgument:&age atIndex:2];NSLog(@"%d", age + 10);

我们发现,消息转发有两种情况,一种是forwardingTargetForSelector,一种是methodSignatureForSelector+forwardInvocation:

其实,第一种也称快速转发,特点就是简单方便,缺点就是能做的事情有限,只能转发消息调用者;第二种也称标准转发,缺点就是写起来麻烦点,需要写方法签名等信息,但是好处就是可以很大成都的自定义方法的转发,可以在找不到方法imp的时候做任何逻辑。

当然,我们上面的例子都是通过对象方法来演示消息转发的,类方法同样存在消息转发,只不过对应的方法都是类方法,也就是-变+

在这里插入图片描述
 所以,以上关于消息传递过程可以用下面这个流程图进一步总结:
 在这里插入图片描述
关于源码阅读指南:
在这里插入图片描述

super的相关内容

首先我们来看一下这段代码:
在这里插入图片描述
我们发现最终的打印结果和我们预期的不一样,按我们的思路Super就是指的的Dog的父类Animal,Animal调用class方法应该返回Animal 但是结果却不是这样,这是为什么?首先我们先将这段代码转换成c++底层代码来一探究竟:

static instancetype _I_Dog_init(Dog * self, SEL _cmd) {self = ((Dog *(*)(__rw_objc_super *, SEL))(void *)objc_msgSendSuper)((__rw_objc_super){(id)self, (id)class_getSuperclass(objc_getClass("Dog"))}, sel_registerName("init"));if (self) {// NSLog(@"%@",[self class]);NSLog((NSString *)&__NSConstantStringImpl__var_folders_f1_q0392lf551qfbg1b5sy48qb80000gn_T_Dog_db6ed5_mi_0,((Class (*)(id, SEL))(void *)objc_msgSend)((id)self, sel_registerName("class")));//NSLog(@"%@",[self superclass]);NSLog((NSString *)&__NSConstantStringImpl__var_folders_f1_q0392lf551qfbg1b5sy48qb80000gn_T_Dog_db6ed5_mi_1,((Class (*)(id, SEL))(void *)objc_msgSend)((id)self, sel_registerName("superclass")));//NSLog(@"%@",[super class]);NSLog((NSString *)&__NSConstantStringImpl__var_folders_f1_q0392lf551qfbg1b5sy48qb80000gn_T_Dog_db6ed5_mi_2,((Class (*)(__rw_objc_super *, SEL))(void *)objc_msgSendSuper)((__rw_objc_super){(id)self, (id)class_getSuperclass(objc_getClass("Dog"))}, sel_registerName("class")));//NSLog(@"%@",[super superclass]);NSLog((NSString *)&__NSConstantStringImpl__var_folders_f1_q0392lf551qfbg1b5sy48qb80000gn_T_Dog_db6ed5_mi_3,((Class (*)(__rw_objc_super *, SEL))(void *)objc_msgSendSuper)((__rw_objc_super){(id)self, (id)class_getSuperclass(objc_getClass("Dog"))}, sel_registerName("superclass")));}return self;
}

将上述代码简化后得到下面的结果:
 在这里插入图片描述

我们发现,当self调用class方法时,是执行的objc_msdSend(self,@selector(class))函数,消息的接收者是当前所在类的实例对象(Dog) , 这个时候就会去self所在类 Dog去查找class方法 , 如果当前类Dog没有class方法会向其父类Animal类找 class 方法, 如果Animal类也没有找到class方法,最终会找到最顶级父类NSObject的class方法, 最终找到NSObject的class方法 ,并调用了object_getClass(self) ,由于消息接收者是 self 当前类实例对象, 所以最终 [self class]输出Dog(class方法是返回方法调用者的类型,superclass方法是返回方法调用者的父类)

[self superclass] 也是同理,找到superclass方法,然后返回调用者的父类,即Animal;

但是当我们调用super的class方法时,底层不是转换成objc_msdSend而是变成了objc_msgSendSuper函数。这个函数有两个参数,第一个参数是个结构体,结构体中有两个成员:方法调用者和调用者的父类,第二个参数就是方法名,也就是class方法的SEL。

[super class] ->
objc_msgSendSuper(//第一个参数:结构体{self,//方法调用者class_getSuperclass(objc_getClass("Dog"))//当前类的父类},//第二个参数:方法名sel_registerName("class")));

所以,我们看到[self class][super class],他们转换成的底层实现都不一致。
objc_msgSendSuper函数的作用是告诉方法调用者去其父类中查找该方法,也就是相比objc_msdSend函数而言少了去自己类中查找方法这一步,而是直接去父类中找class方法,但是方法调用者还是没变,都是Dog。class方法和superclass它们都是返回方法调用者的类型或父类,所以[super class][super superclass]还是返回的Dog的类型和父类,所以打印结果是Dog和Animal,与[self class]和[self superclass]结果一致。

所以,总结起来就是,super方法底层会转换为objc_msgSendSuper函数的调用,这个函数的作用是告诉方法调用者去父类中查找方法。

runtime的常见API与应用案例

动态创建一个类(参数:父类,类名,额外的内存空间)
Class objc_allocateClassPair(Class superclass, const char *name, size_t extraBytes)注册一个类(要在类注册之前添加成员变量)
void objc_registerClassPair(Class cls)销毁一个类
void objc_disposeClassPair(Class cls)获取isa指向的Class
Class object_getClass(id obj)设置isa指向的Class
Class object_setClass(id obj, Class cls)判断一个OC对象是否为Class
BOOL object_isClass(id obj)判断一个Class是否为元类
BOOL class_isMetaClass(Class cls)获取父类
Class class_getSuperclass(Class cls)获取一个实例变量信息
Ivar class_getInstanceVariable(Class cls, const char *name)拷贝实例变量列表(最后需要调用free释放)
Ivar *class_copyIvarList(Class cls, unsigned int *outCount)设置和获取成员变量的值
void object_setIvar(id obj, Ivar ivar, id value)
id object_getIvar(id obj, Ivar ivar)动态添加成员变量(已经注册的类是不能动态添加成员变量的)
BOOL class_addIvar(Class cls, const char * name, size_t size, uint8_t alignment, const char * types)获取成员变量的相关信息
const char *ivar_getName(Ivar v)
const char *ivar_getTypeEncoding(Ivar v)获取一个属性
objc_property_t class_getProperty(Class cls, const char *name)拷贝属性列表(最后需要调用free释放)
objc_property_t *class_copyPropertyList(Class cls, unsigned int *outCount)动态添加属性
BOOL class_addProperty(Class cls, const char *name, const objc_property_attribute_t *attributes,unsigned int attributeCount)动态替换属性
void class_replaceProperty(Class cls, const char *name, const objc_property_attribute_t *attributes,unsigned int attributeCount)获取属性的一些信息
const char *property_getName(objc_property_t property)
const char *property_getAttributes(objc_property_t property)获得一个实例方法、类方法
Method class_getInstanceMethod(Class cls, SEL name)
Method class_getClassMethod(Class cls, SEL name)方法实现相关操作
IMP class_getMethodImplementation(Class cls, SEL name)
IMP method_setImplementation(Method m, IMP imp)
void method_exchangeImplementations(Method m1, Method m2)拷贝方法列表(最后需要调用free释放)
Method *class_copyMethodList(Class cls, unsigned int *outCount)动态添加方法
BOOL class_addMethod(Class cls, SEL name, IMP imp, const char *types)动态替换方法
IMP class_replaceMethod(Class cls, SEL name, IMP imp, const char *types)获取方法的相关信息(带有copy的需要调用free去释放)
SEL method_getName(Method m)
IMP method_getImplementation(Method m)
const char *method_getTypeEncoding(Method m)
unsigned int method_getNumberOfArguments(Method m)
char *method_copyReturnType(Method m)
char *method_copyArgumentType(Method m, unsigned int index)选择器相关
const char *sel_getName(SEL sel)
SEL sel_registerName(const char *str)用block作为方法实现
IMP imp_implementationWithBlock(id block)
id imp_getBlock(IMP anImp)
BOOL imp_removeBlock(IMP anImp)
查看全文
如若内容造成侵权/违法违规/事实不符,请联系编程学习网邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!

相关文章

  1. 哪些情况会造成小程序违规或下架

    1、避免诱导分享。在页面中不要出现“分享”、“强制转发”等字眼&#xff0c;更不要提示用户下载APP、转发微信群、分享红包等。具体有以下几种类型&#xff1a; ①强制诱导分享后才可以继续下一步操作。包括但不限于分享后方可解锁功能或能力&#xff0c;分享后方可查阅&…...

    2024/4/30 21:24:05
  2. IPV6概述

    目录 一、IPV6基础概念 1.IPV4----升级为IPV6的原因&#xff1a; 2.IPV6特征-具体升级点&#xff1a; 3.IPV6报头与IPV4区别&#xff1a; 4.IPV6地址的换算&#xff1a; 5.IPV6地址写法&#xff1a; 6.一些特殊地址简写 7.IPV6地址规划&#xff1a; 8.IPV6地址分类&am…...

    2024/4/13 3:28:01
  3. IT行业主要职业有什么?

    IT行业又称信息产业&#xff0c;它是运用信息手段和技术&#xff0c;收集、整理、储存、传递信息情报&#xff0c;提供信息服务&#xff0c;并提供相应的信息手段、信息技术等服务的产业。IT行业中的职业有很多&#xff0c;目前比较热门的有Java开发、前端开发、UI设计、软件测…...

    2024/4/26 11:56:30
  4. mapUnderscoreToCamelCase作用详解

    在数据库中&#xff0c;由于大多数数据库设置不区分大小写 &#xff0c;因此下画线方式的命名很常见&#xff0c;如user_name、user_email 。在 Java 中&#xff0c;一般都使用驼峰式命名&#xff0c;如 userName、userEmail因为数据库和 Java 中的这两种命名方式很常见&#x…...

    2024/4/14 22:42:21
  5. croc使用 —(两个电脑间传输文件)

    1. 安装 在github中选择对应的方式下载安装包 GitHub - schollz/croc: Easily and securely send things from one computer to another 选择 the latest release for your system 然后选择相对应的版本&#xff0c;下图以windows系统为例&#xff0c;下载windows-64-bit 2.…...

    2024/4/22 18:36:39
  6. GBase 8c 设置字符集

    initdb定义数据库默认字符集/编码&#xff0c;例如&#xff1a; initdb -E GKR 可以用--encoding代替-E。如果没有给出-E或者--encoding选项&#xff0c;initdb会尝试基于指定的或者默认的区域判断要使用的合适编码。 使用非默认字符集/编码时&#xff0c;应考虑其与所选择的…...

    2024/4/26 2:41:16
  7. python入门笔记——正则表达式②

    我们很多时候会使用转义字符\&#xff0c;特别的是要使用\字符的时候&#xff0c;会需要用\来表示 正则表达式也有这样的应用场景 print(re.match(c:\\\\a.txt,c:\\a.txt).group()) # 这里因为正则中的\也有转义要求&#xff0c;故需要再次转义当然&#xff0c;我们也有较好的…...

    2024/4/26 7:12:35
  8. QT5.11 首次创建QML程序

    第一步&#xff1a; 文件--》新建--》项目中 选择 qt quick application--empty 第二步&#xff1a;输入工程名称&#xff0c;其他默认...

    2024/4/15 7:32:52
  9. P3371 【模板】单源最短路径(弱化版)

    题目背景 本题测试数据为随机数据&#xff0c;在考试中可能会出现构造数据让SPFA不通过&#xff0c;如有需要请移步 P4779。 题目描述 如题&#xff0c;给出一个有向图&#xff0c;请输出从某一点出发到所有点的最短路径长度。 输入格式 第一行包含三个整数 n,m,sn,m,s&#xf…...

    2024/4/24 18:22:26
  10. keycloack主从集群以及负载均衡搭建

    keycloak 主从安装配置 部署情况 主机ip角色备注主192.168.88.161master主loadblance从slave 部署版本 keycloak-15.0.2.tar.gz主从模式之间会互相同步数据,即使设置从节点对数据库为只读权限 基于服务器之间的UDP也会将数据同步拓扑架构 数据库 在mysql创建keycloak数据库&…...

    2024/4/24 12:15:20
  11. 2022年全球市场矩形漆包铜线总体规模、主要生产商、主要地区、产品和应用细分研究报告

    【页数】&#xff1a;100 【图表数】&#xff1a;150 【出版时间】&#xff1a;2022年1月 内容摘要 本文研究全球市场、主要地区和主要国家矩形漆包铜线的销量、销售收入等&#xff0c;同时也重点分析全球范围内主要厂商&#xff08;品牌&#xff09;竞争态势&#xff0c;矩…...

    2024/5/2 0:41:11
  12. 获取DataTable中的数据

    方法一 //遍历DataTable&#xff0c;取出所有的ID &#xff1a; List<int> lstID (from d in dt.AsEnumerable() select d.Field<int>("ID")).ToList();方法二 List ls new List (); //存放一列所有的值 DataTable dt ; foreach (DataRow dr in dt…...

    2024/4/24 19:47:37
  13. 如何将doc后缀文件转换成pdf,去水印

    上一篇有用到freeMaker替换模板生成doc后缀的文件&#xff0c;这一篇即将doc文件转换为pdf 参考这个博客方法 ​​​​​​资源提取码&#xff1a;7ga6 引入依赖 (maven仓库是没有的&#xff0c;需要在项目中引用) <dependency><groupId>com.aspose</groupI…...

    2024/5/3 23:26:34
  14. 【挑战程序设计】- 2.5 图论(最短路、最小生成树)

    2.5 图论(最短路、最小生成树) 文章目录2.5 图论(最短路、最小生成树)2.5.1 定义们2.5.2 图的表示2.5.3 图的搜索2.5.4 最短路问题单源1&#xff1a;bellman-ford单源2&#xff1a;dijkstra算法&#xff08;单源3&#xff1a;spfa&#xff09;任意两点&#xff1a;floyd-warsha…...

    2024/5/1 10:24:39
  15. FFmpeg 任意格式转mp4格式

    命令格式&#xff1a; ffmpeg -i [输入文件名] [参数选项] -f [格式] [输出文件] 1、参数选项&#xff1a; (1) -an: 去掉音频 (2) -acodec: 音频选项&#xff0c; 一般后面加copy表示拷贝 (3) -vcodec:视频选项&#xff0c;一般后面加copy表示拷贝 2、格式&#xff1a; (1) h2…...

    2024/4/15 6:07:14
  16. 外观设计专利申请多长时间能授权

    一、外观设计专利申请多长时间能授权 外观设计专利经初步审查无驳回理由的&#xff0c;可以授予专利权&#xff0c;而从申请到初步审查需要多长时间&#xff0c;专利法未明确规定。 《中华人民共和国专利法》 第二十七条申请外观设计专利的&#xff0c;应当提交请求书、该外…...

    2024/4/20 11:32:30
  17. 后渗透流量加密

    &#x1f315;写在前面 &#x1f34a;博客主页&#xff1a;Scorpio_m7&#xff0c;github&#x1f389;欢迎关注&#x1f50e;点赞&#x1f44d;收藏⭐️留言&#x1f4dd;&#x1f31f;本文由 Scorpio_m7原创&#xff0c;CSDN首发&#xff01;&#x1f4c6;首发时间&#xff1…...

    2024/4/26 9:50:34
  18. CRM系统建立更加良好的客户关系

    随着经济的发展&#xff0c;企业之间的竞争除了企业本身技术和能力层面的竞争&#xff0c;客户早已成为企业核心竞争力的重要因素&#xff0c;客户早就是企业的一项重要的资产&#xff0c;企业的产品价值或者服务价值往往都需要从它的客户身上才能体现。 CRM系统是指利用计算机…...

    2024/4/15 16:21:04
  19. Jvm虚拟机中对象的创建过程

    执行new指令&#xff1a; 创建对象肯定要先执行new语句&#xff0c;当jvm执行到一条new指令的时候&#xff0c;会检查这个指令的参数能不能在常量池中定位到一个类的符号引用&#xff0c;并且检查这个符号引用对应的类有没有被加载、解析和初始化过 如果没有&#xff0c;会先执…...

    2024/4/13 3:28:55
  20. 11、容器数据卷挂载方式二

    容器数据卷挂载方式二-dockerfile进行数据挂载 进行测试 我们先去别写一段代码 编写dockerfile文件 FROM centos # 基于centos系统创建 VOLUME ["guazai01","juming02:guazai02"] #进行数据挂载 CMD /bin/bash #执行命令开启挂载 # 命令 docker build…...

    2024/4/27 13:13:04

最新文章

  1. 基于 AI 的数据库助手-Chat2DB

    序言 现在已经开始步入 AI 时代&#xff0c;AI 产品也已经络绎不绝。今天&#xff0c;给大家介绍一款数据库的 AI 产品 —— Chat2DB。 一、什么是 Chat2DB Chat2DB 由阿里提供的一个数据库管理、数据开发、数据分析的工具&#xff0c;它是一个 AI 原生的数据库管理工具&…...

    2024/5/4 1:51:04
  2. 梯度消失和梯度爆炸的一些处理方法

    在这里是记录一下梯度消失或梯度爆炸的一些处理技巧。全当学习总结了如有错误还请留言&#xff0c;在此感激不尽。 权重和梯度的更新公式如下&#xff1a; w w − η ⋅ ∇ w w w - \eta \cdot \nabla w ww−η⋅∇w 个人通俗的理解梯度消失就是网络模型在反向求导的时候出…...

    2024/3/20 10:50:27
  3. 分享一个Python爬虫入门实例(有源码,学习使用)

    一、爬虫基础知识 Python爬虫是一种使用Python编程语言实现的自动化获取网页数据的技术。它广泛应用于数据采集、数据分析、网络监测等领域。以下是对Python爬虫的详细介绍: 架构和组成:下载器:负责根据指定的URL下载网页内容,常用的库有Requests和urllib。解析器:用于解…...

    2024/5/2 2:37:38
  4. 论文阅读AI工具链

    文献检索 可以利用智谱清言来生成合适的文献检索式&#xff0c;并根据需要不断调整。 谷歌学术 在Google Scholar中进行检索时&#xff0c;您可以使用类似的逻辑来构建您的搜索式&#xff0c;但是语法会有所不同。Google Scholar的搜索框接受普通的文本搜索&#xff0c;但是…...

    2024/5/3 14:44:57
  5. 【外汇早评】美通胀数据走低,美元调整

    原标题:【外汇早评】美通胀数据走低,美元调整昨日美国方面公布了新一期的核心PCE物价指数数据,同比增长1.6%,低于前值和预期值的1.7%,距离美联储的通胀目标2%继续走低,通胀压力较低,且此前美国一季度GDP初值中的消费部分下滑明显,因此市场对美联储后续更可能降息的政策…...

    2024/5/1 17:30:59
  6. 【原油贵金属周评】原油多头拥挤,价格调整

    原标题:【原油贵金属周评】原油多头拥挤,价格调整本周国际劳动节,我们喜迎四天假期,但是整个金融市场确实流动性充沛,大事频发,各个商品波动剧烈。美国方面,在本周四凌晨公布5月份的利率决议和新闻发布会,维持联邦基金利率在2.25%-2.50%不变,符合市场预期。同时美联储…...

    2024/5/2 16:16:39
  7. 【外汇周评】靓丽非农不及疲软通胀影响

    原标题:【外汇周评】靓丽非农不及疲软通胀影响在刚结束的周五,美国方面公布了新一期的非农就业数据,大幅好于前值和预期,新增就业重新回到20万以上。具体数据: 美国4月非农就业人口变动 26.3万人,预期 19万人,前值 19.6万人。 美国4月失业率 3.6%,预期 3.8%,前值 3…...

    2024/4/29 2:29:43
  8. 【原油贵金属早评】库存继续增加,油价收跌

    原标题:【原油贵金属早评】库存继续增加,油价收跌周三清晨公布美国当周API原油库存数据,上周原油库存增加281万桶至4.692亿桶,增幅超过预期的74.4万桶。且有消息人士称,沙特阿美据悉将于6月向亚洲炼油厂额外出售更多原油,印度炼油商预计将每日获得至多20万桶的额外原油供…...

    2024/5/3 23:10:03
  9. 【外汇早评】日本央行会议纪要不改日元强势

    原标题:【外汇早评】日本央行会议纪要不改日元强势近两日日元大幅走强与近期市场风险情绪上升,避险资金回流日元有关,也与前一段时间的美日贸易谈判给日本缓冲期,日本方面对汇率问题也避免继续贬值有关。虽然今日早间日本央行公布的利率会议纪要仍然是支持宽松政策,但这符…...

    2024/4/27 17:58:04
  10. 【原油贵金属早评】欧佩克稳定市场,填补伊朗问题的影响

    原标题:【原油贵金属早评】欧佩克稳定市场,填补伊朗问题的影响近日伊朗局势升温,导致市场担忧影响原油供给,油价试图反弹。此时OPEC表态稳定市场。据消息人士透露,沙特6月石油出口料将低于700万桶/日,沙特已经收到石油消费国提出的6月份扩大出口的“适度要求”,沙特将满…...

    2024/4/27 14:22:49
  11. 【外汇早评】美欲与伊朗重谈协议

    原标题:【外汇早评】美欲与伊朗重谈协议美国对伊朗的制裁遭到伊朗的抗议,昨日伊朗方面提出将部分退出伊核协议。而此行为又遭到欧洲方面对伊朗的谴责和警告,伊朗外长昨日回应称,欧洲国家履行它们的义务,伊核协议就能保证存续。据传闻伊朗的导弹已经对准了以色列和美国的航…...

    2024/4/28 1:28:33
  12. 【原油贵金属早评】波动率飙升,市场情绪动荡

    原标题:【原油贵金属早评】波动率飙升,市场情绪动荡因中美贸易谈判不安情绪影响,金融市场各资产品种出现明显的波动。随着美国与中方开启第十一轮谈判之际,美国按照既定计划向中国2000亿商品征收25%的关税,市场情绪有所平复,已经开始接受这一事实。虽然波动率-恐慌指数VI…...

    2024/4/30 9:43:09
  13. 【原油贵金属周评】伊朗局势升温,黄金多头跃跃欲试

    原标题:【原油贵金属周评】伊朗局势升温,黄金多头跃跃欲试美国和伊朗的局势继续升温,市场风险情绪上升,避险黄金有向上突破阻力的迹象。原油方面稍显平稳,近期美国和OPEC加大供给及市场需求回落的影响,伊朗局势并未推升油价走强。近期中美贸易谈判摩擦再度升级,美国对中…...

    2024/4/27 17:59:30
  14. 【原油贵金属早评】市场情绪继续恶化,黄金上破

    原标题:【原油贵金属早评】市场情绪继续恶化,黄金上破周初中国针对于美国加征关税的进行的反制措施引发市场情绪的大幅波动,人民币汇率出现大幅的贬值动能,金融市场受到非常明显的冲击。尤其是波动率起来之后,对于股市的表现尤其不安。隔夜美国股市出现明显的下行走势,这…...

    2024/5/2 15:04:34
  15. 【外汇早评】美伊僵持,风险情绪继续升温

    原标题:【外汇早评】美伊僵持,风险情绪继续升温昨日沙特两艘油轮再次发生爆炸事件,导致波斯湾局势进一步恶化,市场担忧美伊可能会出现摩擦生火,避险品种获得支撑,黄金和日元大幅走强。美指受中美贸易问题影响而在低位震荡。继5月12日,四艘商船在阿联酋领海附近的阿曼湾、…...

    2024/4/28 1:34:08
  16. 【原油贵金属早评】贸易冲突导致需求低迷,油价弱势

    原标题:【原油贵金属早评】贸易冲突导致需求低迷,油价弱势近日虽然伊朗局势升温,中东地区几起油船被袭击事件影响,但油价并未走高,而是出于调整结构中。由于市场预期局势失控的可能性较低,而中美贸易问题导致的全球经济衰退风险更大,需求会持续低迷,因此油价调整压力较…...

    2024/4/26 19:03:37
  17. 氧生福地 玩美北湖(上)——为时光守候两千年

    原标题:氧生福地 玩美北湖(上)——为时光守候两千年一次说走就走的旅行,只有一张高铁票的距离~ 所以,湖南郴州,我来了~ 从广州南站出发,一个半小时就到达郴州西站了。在动车上,同时改票的南风兄和我居然被分到了一个车厢,所以一路非常愉快地聊了过来。 挺好,最起…...

    2024/4/29 20:46:55
  18. 氧生福地 玩美北湖(中)——永春梯田里的美与鲜

    原标题:氧生福地 玩美北湖(中)——永春梯田里的美与鲜一觉醒来,因为大家太爱“美”照,在柳毅山庄去寻找龙女而错过了早餐时间。近十点,向导坏坏还是带着饥肠辘辘的我们去吃郴州最富有盛名的“鱼头粉”。说这是“十二分推荐”,到郴州必吃的美食之一。 哇塞!那个味美香甜…...

    2024/4/30 22:21:04
  19. 氧生福地 玩美北湖(下)——奔跑吧骚年!

    原标题:氧生福地 玩美北湖(下)——奔跑吧骚年!让我们红尘做伴 活得潇潇洒洒 策马奔腾共享人世繁华 对酒当歌唱出心中喜悦 轰轰烈烈把握青春年华 让我们红尘做伴 活得潇潇洒洒 策马奔腾共享人世繁华 对酒当歌唱出心中喜悦 轰轰烈烈把握青春年华 啊……啊……啊 两…...

    2024/5/1 4:32:01
  20. 扒开伪装医用面膜,翻六倍价格宰客,小姐姐注意了!

    原标题:扒开伪装医用面膜,翻六倍价格宰客,小姐姐注意了!扒开伪装医用面膜,翻六倍价格宰客!当行业里的某一品项火爆了,就会有很多商家蹭热度,装逼忽悠,最近火爆朋友圈的医用面膜,被沾上了污点,到底怎么回事呢? “比普通面膜安全、效果好!痘痘、痘印、敏感肌都能用…...

    2024/4/27 23:24:42
  21. 「发现」铁皮石斛仙草之神奇功效用于医用面膜

    原标题:「发现」铁皮石斛仙草之神奇功效用于医用面膜丽彦妆铁皮石斛医用面膜|石斛多糖无菌修护补水贴19大优势: 1、铁皮石斛:自唐宋以来,一直被列为皇室贡品,铁皮石斛生于海拔1600米的悬崖峭壁之上,繁殖力差,产量极低,所以古代仅供皇室、贵族享用 2、铁皮石斛自古民间…...

    2024/4/28 5:48:52
  22. 丽彦妆\医用面膜\冷敷贴轻奢医学护肤引导者

    原标题:丽彦妆\医用面膜\冷敷贴轻奢医学护肤引导者【公司简介】 广州华彬企业隶属香港华彬集团有限公司,专注美业21年,其旗下品牌: 「圣茵美」私密荷尔蒙抗衰,产后修复 「圣仪轩」私密荷尔蒙抗衰,产后修复 「花茵莳」私密荷尔蒙抗衰,产后修复 「丽彦妆」专注医学护…...

    2024/4/30 9:42:22
  23. 广州械字号面膜生产厂家OEM/ODM4项须知!

    原标题:广州械字号面膜生产厂家OEM/ODM4项须知!广州械字号面膜生产厂家OEM/ODM流程及注意事项解读: 械字号医用面膜,其实在我国并没有严格的定义,通常我们说的医美面膜指的应该是一种「医用敷料」,也就是说,医用面膜其实算作「医疗器械」的一种,又称「医用冷敷贴」。 …...

    2024/5/2 9:07:46
  24. 械字号医用眼膜缓解用眼过度到底有无作用?

    原标题:械字号医用眼膜缓解用眼过度到底有无作用?医用眼膜/械字号眼膜/医用冷敷眼贴 凝胶层为亲水高分子材料,含70%以上的水分。体表皮肤温度传导到本产品的凝胶层,热量被凝胶内水分子吸收,通过水分的蒸发带走大量的热量,可迅速地降低体表皮肤局部温度,减轻局部皮肤的灼…...

    2024/4/30 9:42:49
  25. 配置失败还原请勿关闭计算机,电脑开机屏幕上面显示,配置失败还原更改 请勿关闭计算机 开不了机 这个问题怎么办...

    解析如下&#xff1a;1、长按电脑电源键直至关机&#xff0c;然后再按一次电源健重启电脑&#xff0c;按F8健进入安全模式2、安全模式下进入Windows系统桌面后&#xff0c;按住“winR”打开运行窗口&#xff0c;输入“services.msc”打开服务设置3、在服务界面&#xff0c;选中…...

    2022/11/19 21:17:18
  26. 错误使用 reshape要执行 RESHAPE,请勿更改元素数目。

    %读入6幅图像&#xff08;每一幅图像的大小是564*564&#xff09; f1 imread(WashingtonDC_Band1_564.tif); subplot(3,2,1),imshow(f1); f2 imread(WashingtonDC_Band2_564.tif); subplot(3,2,2),imshow(f2); f3 imread(WashingtonDC_Band3_564.tif); subplot(3,2,3),imsho…...

    2022/11/19 21:17:16
  27. 配置 已完成 请勿关闭计算机,win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机...

    win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机”问题的解决方法在win7系统关机时如果有升级系统的或者其他需要会直接进入一个 等待界面&#xff0c;在等待界面中我们需要等待操作结束才能关机&#xff0c;虽然这比较麻烦&#xff0c;但是对系统进行配置和升级…...

    2022/11/19 21:17:15
  28. 台式电脑显示配置100%请勿关闭计算机,“准备配置windows 请勿关闭计算机”的解决方法...

    有不少用户在重装Win7系统或更新系统后会遇到“准备配置windows&#xff0c;请勿关闭计算机”的提示&#xff0c;要过很久才能进入系统&#xff0c;有的用户甚至几个小时也无法进入&#xff0c;下面就教大家这个问题的解决方法。第一种方法&#xff1a;我们首先在左下角的“开始…...

    2022/11/19 21:17:14
  29. win7 正在配置 请勿关闭计算机,怎么办Win7开机显示正在配置Windows Update请勿关机...

    置信有很多用户都跟小编一样遇到过这样的问题&#xff0c;电脑时发现开机屏幕显现“正在配置Windows Update&#xff0c;请勿关机”(如下图所示)&#xff0c;而且还需求等大约5分钟才干进入系统。这是怎样回事呢&#xff1f;一切都是正常操作的&#xff0c;为什么开时机呈现“正…...

    2022/11/19 21:17:13
  30. 准备配置windows 请勿关闭计算机 蓝屏,Win7开机总是出现提示“配置Windows请勿关机”...

    Win7系统开机启动时总是出现“配置Windows请勿关机”的提示&#xff0c;没过几秒后电脑自动重启&#xff0c;每次开机都这样无法进入系统&#xff0c;此时碰到这种现象的用户就可以使用以下5种方法解决问题。方法一&#xff1a;开机按下F8&#xff0c;在出现的Windows高级启动选…...

    2022/11/19 21:17:12
  31. 准备windows请勿关闭计算机要多久,windows10系统提示正在准备windows请勿关闭计算机怎么办...

    有不少windows10系统用户反映说碰到这样一个情况&#xff0c;就是电脑提示正在准备windows请勿关闭计算机&#xff0c;碰到这样的问题该怎么解决呢&#xff0c;现在小编就给大家分享一下windows10系统提示正在准备windows请勿关闭计算机的具体第一种方法&#xff1a;1、2、依次…...

    2022/11/19 21:17:11
  32. 配置 已完成 请勿关闭计算机,win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机”的解决方法...

    今天和大家分享一下win7系统重装了Win7旗舰版系统后&#xff0c;每次关机的时候桌面上都会显示一个“配置Windows Update的界面&#xff0c;提示请勿关闭计算机”&#xff0c;每次停留好几分钟才能正常关机&#xff0c;导致什么情况引起的呢&#xff1f;出现配置Windows Update…...

    2022/11/19 21:17:10
  33. 电脑桌面一直是清理请关闭计算机,windows7一直卡在清理 请勿关闭计算机-win7清理请勿关机,win7配置更新35%不动...

    只能是等着&#xff0c;别无他法。说是卡着如果你看硬盘灯应该在读写。如果从 Win 10 无法正常回滚&#xff0c;只能是考虑备份数据后重装系统了。解决来方案一&#xff1a;管理员运行cmd&#xff1a;net stop WuAuServcd %windir%ren SoftwareDistribution SDoldnet start WuA…...

    2022/11/19 21:17:09
  34. 计算机配置更新不起,电脑提示“配置Windows Update请勿关闭计算机”怎么办?

    原标题&#xff1a;电脑提示“配置Windows Update请勿关闭计算机”怎么办&#xff1f;win7系统中在开机与关闭的时候总是显示“配置windows update请勿关闭计算机”相信有不少朋友都曾遇到过一次两次还能忍但经常遇到就叫人感到心烦了遇到这种问题怎么办呢&#xff1f;一般的方…...

    2022/11/19 21:17:08
  35. 计算机正在配置无法关机,关机提示 windows7 正在配置windows 请勿关闭计算机 ,然后等了一晚上也没有关掉。现在电脑无法正常关机...

    关机提示 windows7 正在配置windows 请勿关闭计算机 &#xff0c;然后等了一晚上也没有关掉。现在电脑无法正常关机以下文字资料是由(历史新知网www.lishixinzhi.com)小编为大家搜集整理后发布的内容&#xff0c;让我们赶快一起来看一下吧&#xff01;关机提示 windows7 正在配…...

    2022/11/19 21:17:05
  36. 钉钉提示请勿通过开发者调试模式_钉钉请勿通过开发者调试模式是真的吗好不好用...

    钉钉请勿通过开发者调试模式是真的吗好不好用 更新时间:2020-04-20 22:24:19 浏览次数:729次 区域: 南阳 > 卧龙 列举网提醒您:为保障您的权益,请不要提前支付任何费用! 虚拟位置外设器!!轨迹模拟&虚拟位置外设神器 专业用于:钉钉,外勤365,红圈通,企业微信和…...

    2022/11/19 21:17:05
  37. 配置失败还原请勿关闭计算机怎么办,win7系统出现“配置windows update失败 还原更改 请勿关闭计算机”,长时间没反应,无法进入系统的解决方案...

    前几天班里有位学生电脑(windows 7系统)出问题了&#xff0c;具体表现是开机时一直停留在“配置windows update失败 还原更改 请勿关闭计算机”这个界面&#xff0c;长时间没反应&#xff0c;无法进入系统。这个问题原来帮其他同学也解决过&#xff0c;网上搜了不少资料&#x…...

    2022/11/19 21:17:04
  38. 一个电脑无法关闭计算机你应该怎么办,电脑显示“清理请勿关闭计算机”怎么办?...

    本文为你提供了3个有效解决电脑显示“清理请勿关闭计算机”问题的方法&#xff0c;并在最后教给你1种保护系统安全的好方法&#xff0c;一起来看看&#xff01;电脑出现“清理请勿关闭计算机”在Windows 7(SP1)和Windows Server 2008 R2 SP1中&#xff0c;添加了1个新功能在“磁…...

    2022/11/19 21:17:03
  39. 请勿关闭计算机还原更改要多久,电脑显示:配置windows更新失败,正在还原更改,请勿关闭计算机怎么办...

    许多用户在长期不使用电脑的时候&#xff0c;开启电脑发现电脑显示&#xff1a;配置windows更新失败&#xff0c;正在还原更改&#xff0c;请勿关闭计算机。。.这要怎么办呢&#xff1f;下面小编就带着大家一起看看吧&#xff01;如果能够正常进入系统&#xff0c;建议您暂时移…...

    2022/11/19 21:17:02
  40. 还原更改请勿关闭计算机 要多久,配置windows update失败 还原更改 请勿关闭计算机,电脑开机后一直显示以...

    配置windows update失败 还原更改 请勿关闭计算机&#xff0c;电脑开机后一直显示以以下文字资料是由(历史新知网www.lishixinzhi.com)小编为大家搜集整理后发布的内容&#xff0c;让我们赶快一起来看一下吧&#xff01;配置windows update失败 还原更改 请勿关闭计算机&#x…...

    2022/11/19 21:17:01
  41. 电脑配置中请勿关闭计算机怎么办,准备配置windows请勿关闭计算机一直显示怎么办【图解】...

    不知道大家有没有遇到过这样的一个问题&#xff0c;就是我们的win7系统在关机的时候&#xff0c;总是喜欢显示“准备配置windows&#xff0c;请勿关机”这样的一个页面&#xff0c;没有什么大碍&#xff0c;但是如果一直等着的话就要两个小时甚至更久都关不了机&#xff0c;非常…...

    2022/11/19 21:17:00
  42. 正在准备配置请勿关闭计算机,正在准备配置windows请勿关闭计算机时间长了解决教程...

    当电脑出现正在准备配置windows请勿关闭计算机时&#xff0c;一般是您正对windows进行升级&#xff0c;但是这个要是长时间没有反应&#xff0c;我们不能再傻等下去了。可能是电脑出了别的问题了&#xff0c;来看看教程的说法。正在准备配置windows请勿关闭计算机时间长了方法一…...

    2022/11/19 21:16:59
  43. 配置失败还原请勿关闭计算机,配置Windows Update失败,还原更改请勿关闭计算机...

    我们使用电脑的过程中有时会遇到这种情况&#xff0c;当我们打开电脑之后&#xff0c;发现一直停留在一个界面&#xff1a;“配置Windows Update失败&#xff0c;还原更改请勿关闭计算机”&#xff0c;等了许久还是无法进入系统。如果我们遇到此类问题应该如何解决呢&#xff0…...

    2022/11/19 21:16:58
  44. 如何在iPhone上关闭“请勿打扰”

    Apple’s “Do Not Disturb While Driving” is a potentially lifesaving iPhone feature, but it doesn’t always turn on automatically at the appropriate time. For example, you might be a passenger in a moving car, but your iPhone may think you’re the one dri…...

    2022/11/19 21:16:57