iOS消息發(fā)送流程

上一篇我們講到從objc_msgSend發(fā)送消息進(jìn)入到了匯編,然后通過CacheLookup---> CheckMiss---> __objc_msgSend_uncached---> MethodTableLookup---> __class_lookupMethodAndLoadCache3,但是我們在源代碼中卻找不到該方法,那就通過allways show來查找系統(tǒng)調(diào)用流程:

一、通過匯編找到方法的調(diào)用流程

  • 1.Debug ---> Debug workflow ----> allways show,打開allways show

  • 2.在調(diào)用方法那行打上斷點(diǎn)

image.png
  • 3.然后我們就會跳到該方法的匯編流程里了
image.png
  • 4、從上到下,我們會發(fā)現(xiàn)調(diào)用該方方法前后的調(diào)用順序,因?yàn)槲也榭吹氖莃y_eat5111方法,看到了該方法名,并且下面有個objc_msgSend,我們就能得知調(diào)用方法時候會調(diào)用objc_msgSend,我們在該方法這行加上一個斷點(diǎn),然后跳到這個斷點(diǎn)地方,再按住control 點(diǎn)擊step In進(jìn)入到該方法的調(diào)用流程中去:
image.png
  • 5.我們會進(jìn)入到objc_msgSend的流程,
image.png
  • 6.往下翻,我們看到很熟悉的_objc_msgSend_uncached方法,
image.png

然后依然加個斷點(diǎn),按住control點(diǎn)擊step in進(jìn)入到_objc_msgSend_uncached方法里

  • 7.然后我們發(fā)現(xiàn)在該函數(shù)里_class_lookupMethodAndLoadCache3的c++函數(shù),并且指示了在objc-runtime-new.mm文件的4846行


    image.png
  • 8.我們到objc-runtime-new.mm文件的4846行查看,確實(shí)有這個函數(shù),這樣我們就開始了C++、C函數(shù)的查找流程


    image.png

其實(shí)不管是C++和C語言函數(shù)最終都會編譯成機(jī)器可以識別的語言,也就是匯編,我們可以通過上述方法一層層查看,可以看到整個的方法調(diào)用邏輯

1、這次我們通過allways show方式看到了OC方法調(diào)用順序是objc_msgSend -- > _objc_msgSend_uncached -- > _class_lookupMethodAndLoadCache3 ...
2、跟我們從源碼分析有些不同,缺少了CacheLookup 、CheckMiss 、MethodTableLookup,猜測是編譯器優(yōu)化了一些調(diào)用流程,只編譯那些方法會走的流程,不走的流程直接舍棄了
3、假如我們不知道一個方法的調(diào)用邏輯,我們可以通過allways show這種方式一步一步去查找,很方便了就能看出一個方法的調(diào)用流程

二、方法查找流程

我們都知道當(dāng)調(diào)用了未實(shí)現(xiàn)的方法時候會導(dǎo)致程序崩潰,那我們就分析一下方法調(diào)用流程來了解下為啥會崩潰。

上面我們分析了從objc_msgSend到_class_lookupMethodAndLoadCache3的查找步驟,現(xiàn)在我們再通過源碼部分查看方法的查找流程,在方法關(guān)鍵位置加了注釋

  • 1、方法查找并緩存的總函數(shù):_class_lookupMethodAndLoadCache3,我們發(fā)現(xiàn)里面掉的是
IMP _class_lookupMethodAndLoadCache3(id obj, SEL sel, Class cls)
{
    /*
     cls:如果是實(shí)例方法那就是類,如果是類方法那就是元類
     sel:方法名
     obj:方法調(diào)用者
     */
    return lookUpImpOrForward(cls, sel, obj, 
                              YES/*initialize*/, NO/*cache*/, YES/*resolver*/);
}
  • 2、查找方法指針或消息轉(zhuǎn)發(fā)函數(shù):lookUpImpOrForward
    • a、我們發(fā)現(xiàn)進(jìn)入后會先從緩存再找一次,找到的話就直接返回了,

    • b、然后對類是否加載完進(jìn)行判斷,如果沒有加載完就再加載一遍,

    • c、然后設(shè)置了兩個局部域,
      (1)第一個是先從自己的方法列表里面找,如果沒找到在執(zhí)行第二個域
      (2)第二個域會遍歷父類,每遍歷一次就會先從父類的緩存中找,父類的緩存沒有的話再從父類的方法列表中找
      (3)執(zhí)行兩個域過程中,如果找到了就緩存在自己的cache_t里面,假如自己的列表和父類的列表都沒找到的話話,就開始了消息轉(zhuǎn)發(fā)函數(shù)_class_resolveMethod

    • e、_class_resolveMethod函數(shù)調(diào)用完成后會再走一下c流程,如果沒有的話,就會調(diào)用_objc_msgForward_impcache函數(shù),然后返回imp

IMP lookUpImpOrForward(Class cls, SEL sel, id inst, 
                       bool initialize, bool cache, bool resolver)
{
    IMP imp = nil;
    bool triedResolver = NO;

    runtimeLock.assertUnlocked();

    // Optimistic cache lookup
//如果需要從緩存里面查找,那需要先從緩存里面找,現(xiàn)在我們這邊傳進(jìn)來的是NO,也就不走這一步,但是消息轉(zhuǎn)發(fā)后傳進(jìn)來的是YES,需要走下
    if (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.
    //主要是對class的判斷,lock是為了防止多線程操作
    runtimeLock.lock();
    checkIsKnownClass(cls);

//為查找方法做準(zhǔn)備條件,判斷類有沒有加載好,如果沒有加載好,那就先加載一下類信息,準(zhǔn)備好父類、元類
    if (!cls->isRealized()) {
        realizeClass(cls);
    }
    //確定類已經(jīng)加載完成,
    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);
    //找到了直接跳到done
    if (imp) goto done;
    //大括號意思是為了形成局部域,避免局部變量命名重復(fù)
    // Try this class's method lists.
    {
        //先在自己類里面找方法
        Method meth = getMethodNoSuper_nolock(cls, sel);
        if (meth) {
            //如果找到了就進(jìn)入到了緩存邏輯cache_fill
            log_and_fill_cache(cls, meth->imp, sel, inst, cls);
            imp = meth->imp;
            goto done;//直接跳到done這個點(diǎn)
        }
    }

    // Try superclass caches and method lists.
    //自己b類中找不到的話再到父類方法列表中找
    {
        //這個不懂
        unsigned attempts = unreasonableClassCount();
        //遍歷f類f的父類們,
        for (Class curClass = cls->superclass;
             curClass != nil;
             curClass = curClass->superclass)
        {
            // Halt if there is a cycle in the superclass chain.
            if (--attempts == 0) {//內(nèi)存出錯
                _objc_fatal("Memory corruption in class list.");
            }
            
            // Superclass cache.
            //從父類的緩存中查找一下
            imp = cache_getImp(curClass, sel);
            if (imp) {
                ///如果不是_objc_msgForward_impcache這個方法,為啥???
                if (imp != (IMP)_objc_msgForward_impcache) {
                    // Found the method in a superclass. Cache it in this class.
                    ///就把這個方法f緩存在當(dāng)前類的緩存中
                    log_and_fill_cache(cls, imp, sel, inst, curClass);
                    goto done;
                }
                else {///如果是_objc_msgForward_impcache方法,就退出,不需要遍歷了,因?yàn)開objc_msgForward_impcache這個方法就是未實(shí)現(xiàn)時候轉(zhuǎn)發(fā)用的
                    // 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) {
        //如果找到了,就緩存在當(dāng)前類中,注意,從父類找到的方法只存在調(diào)用者類中,不存在父類中
                log_and_fill_cache(cls, meth->imp, sel, inst, curClass);
                imp = meth->imp;
                goto done;
            }
        }
    }

    // No implementation found. Try method resolver once.
    //如果方法仍然沒找到,就開始做方法消息動態(tài)解析了
    if (resolver  &&  !triedResolver) {
        runtimeLock.unlock();
        /*
         實(shí)例方法解析:resolveInstanceMethod
         類方法解析:resolveClassMethod
         */
        _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.
        //設(shè)置為NO,防止因?yàn)橄討B(tài)解析導(dǎo)致死循環(huán)
        triedResolver = YES;
        goto retry;
    }

    // No implementation found, and method resolver didn't help. 
    // Use forwarding.
    //如果第一次轉(zhuǎn)發(fā)還是沒用的話,就取出_objc_msgForward_impcache這個方法指針
    imp = (IMP)_objc_msgForward_impcache;
    //緩存起來將指針返回回去執(zhí)行
    cache_fill(cls, sel, imp, inst);

 done:
    runtimeLock.unlock();

    return imp;
}
  • 3、消息動態(tài)解析函數(shù):_class_resolveMethod
    • a、首先會判斷cls是不是元類,如果cls不是元類的話,說明調(diào)用的是實(shí)例方法,那就就會調(diào)用_class_resolveInstanceMethod函數(shù),

    • b、如果是元類的話,說明調(diào)用的是類方法,那么就會調(diào)用_class_resolveClassMethod函數(shù),并且調(diào)用完后會再次查找一下sel的指針,找到了就會返回,如果還是找不到的話會調(diào)用_class_resolveInstanceMethod函數(shù),這是為什么呢:
      (1)通過isa指向我們知道類的元類的父類都是NSObject的元類,而NSObject元類的父類是NSObject類,
      (2)而類里存放的都是對象方法,并且方法名是不分類方法和對象方法的
      (3)所以根據(jù)繼承鏈關(guān)系,在類方法的查找過程中最終會查找到NSObject類中,所以在消息動態(tài)決議中也需要調(diào)用一下_class_resolveInstanceMethod去動態(tài)決議一下(有點(diǎn)牽強(qiáng)哈)

    • c、_class_resolveInstanceMethod和_class_resolveClassMethod函數(shù)調(diào)用邏輯很類似,只是一個是解析類方法,一個是解析實(shí)例方法:

      (1)首先判斷類有沒有實(shí)現(xiàn)SEL_resolveInstanceMethod方法,其實(shí)也就是+ (BOOL)resolveInstanceMethod:(SEL)sel或者+ (BOOL)resolveClassMethod:(SEL)sel方法,我們通過在源碼搜索可以看到在NSObject類中已經(jīng)都實(shí)現(xiàn)了,并且都是一個空方法,并且返回NO,
      (2)然后會通過objc_msgSend函數(shù)發(fā)送SEL_resolveInstanceMethod消息,系統(tǒng)調(diào)用起resolveInstanceMethod方法,發(fā)送完成后,系統(tǒng)會再查找一下sel方法,然后回到主流程重新走一下方法查找邏輯

      • d、由上我們可以得出動態(tài)決議的所有方法最終都會轉(zhuǎn)發(fā)到重寫的resolveInstanceMethod方法里,我們可以集中在這里做處理,例如防崩潰措施等等,但是不建議放在這里,因?yàn)闀?dǎo)致耦合太大
void _class_resolveMethod(Class cls, SEL sel, id inst)
{
    if (! cls->isMetaClass()) {//判斷類不是元類,那sel就是實(shí)例方法,那就先轉(zhuǎn)發(fā)resolveInstanceMethod方法,判斷有沒有實(shí)現(xiàn)resolveInstanceMethod,沒實(shí)現(xiàn)就不做處理
        // try [cls resolveInstanceMethod:sel]

        _class_resolveInstanceMethod(cls, sel, inst);
    } 
    else {//如果是元類,那sel就是類方法
        // try [nonMetaClass resolveClassMethod:sel]
        // and [cls resolveInstanceMethod:sel]
    //先轉(zhuǎn)發(fā)resolveClassMethod,會先查找下resolveClassMethod,如果沒實(shí)現(xiàn)就不做處理
        _class_resolveClassMethod(cls, sel, inst);
        //再次查找下方法,如果沒有的話,就再轉(zhuǎn)發(fā)一下resolveInstanceMethod方法
        if (!lookUpImpOrNil(cls, sel, inst, 
                            NO/*initialize*/, YES/*cache*/, NO/*resolver*/)) 
        {
            _class_resolveInstanceMethod(cls, sel, inst);
        }
    }
}
static void _class_resolveInstanceMethod(Class cls, SEL sel, id inst)
{
    if (! lookUpImpOrNil(cls->ISA(), SEL_resolveInstanceMethod, cls, 
                         NO/*initialize*/, YES/*cache*/, NO/*resolver*/)) 
    {
        // Resolver not implemented.
        return;
    }
    //發(fā)送SEL_resolveInstanceMethod消息,
    BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
    bool resolved = msg(cls, SEL_resolveInstanceMethod, sel);

    // Cache the result (good or bad) so the resolver doesn't fire next time.
    // +resolveInstanceMethod adds to self a.k.a. cls
    //再次查找類中是否sel方法,因?yàn)閞esolveInstanceMethod方法執(zhí)行后可能動態(tài)進(jìn)行添加了,resolver是不要進(jìn)行消息轉(zhuǎn)發(fā)了
    IMP imp = lookUpImpOrNil(cls, sel, inst, 
                             NO/*initialize*/, YES/*cache*/, NO/*resolver*/);

    if (resolved  &&  PrintResolving) {


        if (imp) {
            _objc_inform("RESOLVE: method %c[%s %s] "
                         "dynamically resolved to %p", 
                         cls->isMetaClass() ? '+' : '-', 
                         cls->nameForLogging(), sel_getName(sel), imp);
        }
        else {
            // Method resolver didn't add anything?
            _objc_inform("RESOLVE: +[%s resolveInstanceMethod:%s] returned YES"
                         ", but no new implementation of %c[%s %s] was found",
                         cls->nameForLogging(), sel_getName(sel), 
                         cls->isMetaClass() ? '+' : '-', 
                         cls->nameForLogging(), sel_getName(sel));
        }
    }
}
static void _class_resolveClassMethod(Class cls, SEL sel, id inst)
{
    assert(cls->isMetaClass());
  //查找下類是否實(shí)現(xiàn)了resolveClassMethod方法,NSObject類已經(jīng)實(shí)現(xiàn)了
    if (! lookUpImpOrNil(cls, SEL_resolveClassMethod, inst, 
                         NO/*initialize*/, YES/*cache*/, NO/*resolver*/)) 
    {
        // Resolver not implemented.
        return;
    }

    BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
//記住,此處是向元類發(fā)送resolveClassMethod消息,也就是調(diào)用resolveClassMethod方法
    bool resolved = msg(_class_getNonMetaClass(cls, inst), 
                        SEL_resolveClassMethod, sel);

    // Cache the result (good or bad) so the resolver doesn't fire next time.
    // +resolveClassMethod adds to self->ISA() a.k.a. cls
    IMP imp = lookUpImpOrNil(cls, sel, inst, 
                             NO/*initialize*/, YES/*cache*/, NO/*resolver*/);

    if (resolved  &&  PrintResolving) {
        if (imp) {
            _objc_inform("RESOLVE: method %c[%s %s] "
                         "dynamically resolved to %p", 
                         cls->isMetaClass() ? '+' : '-', 
                         cls->nameForLogging(), sel_getName(sel), imp);
        }
        else {
            // Method resolver didn't add anything?
            _objc_inform("RESOLVE: +[%s resolveClassMethod:%s] returned YES"
                         ", but no new implementation of %c[%s %s] was found",
                         cls->nameForLogging(), sel_getName(sel), 
                         cls->isMetaClass() ? '+' : '-', 
                         cls->nameForLogging(), sel_getName(sel));
        }
    }
}
  • 4、當(dāng)轉(zhuǎn)發(fā)也完成后依然找不到IMP時候,系統(tǒng)就會調(diào)用__objc_msgForward_impcache,
    • a、通過全局查找,我們發(fā)現(xiàn)只在匯編有進(jìn)入該函數(shù)的入口,最終我們會發(fā)現(xiàn)調(diào)用了__objc_forward_handler函數(shù),
    • b、然后在查找_objc_forward_handler,當(dāng)我們把前面去掉一個會找到,然后發(fā)現(xiàn)里面內(nèi)容就是我經(jīng)常因?yàn)榉椒ú淮嬖诙鴮?dǎo)致崩潰所提示的信息
STATIC_ENTRY __objc_msgForward_impcache
    // Method cache version

    // THIS IS NOT A CALLABLE C FUNCTION
    // Out-of-band condition register is NE for stret, EQ otherwise.

    jne __objc_msgForward_stret
    jmp __objc_msgForward

    END_ENTRY __objc_msgForward_impcache
    
    
    ENTRY __objc_msgForward
    // Non-stret version

    movq    __objc_forward_handler(%rip), %r11
    jmp *%r11

    END_ENTRY __objc_msgForward
objc_defaultForwardHandler(id self, SEL sel)
{
    _objc_fatal("%c[%s %s]: unrecognized selector sent to instance %p "
                "(no message forward handler is installed)", 
                class_isMetaClass(object_getClass(self)) ? '+' : '-', 
                object_getClassName(self), sel_getName(sel), self);
}
void *_objc_forward_handler = (void*)objc_defaultForwardHandler;

總結(jié):

  • 1、先確定調(diào)用方法的類已經(jīng)都加載完畢,如果沒加載完畢的話進(jìn)行加載

  • 2、查找時候先加鎖,避免多線程導(dǎo)致出現(xiàn)問題,然后在從緩存里面取一遍,如果取到IMP直接返回IMP

  • 3、先從自己的方法列表中查找,如果找到就把找到的imp緩存到buckets中,然后返回imp

  • 4、再遍歷父類,遍歷父類時候,先從父類緩存中取,然后再從父類的方法列表中去,如果取到了會把該方法緩存在本類的buckets中,并且如果找到的方法是_objc_msgForward_impcache,會過濾掉并且終止查找,

  • 5、如果本類和父類中都沒找到那就開始走消息轉(zhuǎn)發(fā)流程,實(shí)例方法會轉(zhuǎn)發(fā)+(BOOL) resolveInstanceMethod:(SEL)sel;類方法會轉(zhuǎn)發(fā)+(BOOL) resolveClassMethod:(SEL)sel,并且類方法轉(zhuǎn)發(fā)完后會再次走查找流程,如果還沒找到的話會走一下實(shí)例方法轉(zhuǎn)發(fā)流程;轉(zhuǎn)發(fā)邏輯完成后,會再次走一下方法查找邏輯

  • 6、如果第一次轉(zhuǎn)發(fā)依然后還沒找到IMP,那么就會返回_objc_msgForward_impcache方法指針

  • 7、通過全局搜索_objc_msgForward_impcache,最后只在匯編代碼中找到該函數(shù),然后會發(fā)現(xiàn)最后調(diào)用的是_objc_forward_handler函數(shù),最后在_objc_forward_handler實(shí)現(xiàn)里面找到了經(jīng)典的因?yàn)榉椒]實(shí)現(xiàn)導(dǎo)致報錯的錯誤信息

源碼:

試驗(yàn)

既然知道當(dāng)沒調(diào)用了不存在的方法時候會轉(zhuǎn)發(fā)resolveClassMethod或resolveInstanceMethod方法,那我們在類中實(shí)現(xiàn)一下,然后做一下試驗(yàn):

代碼
-(void)sayHello1{
    
    NSLog(@"消息轉(zhuǎn)發(fā)過來的實(shí)例方法");
}

+(void)sayHello2{

    NSLog(@"消息轉(zhuǎn)發(fā)過來的類方法");
}

+(BOOL)resolveInstanceMethod:(SEL)sel{

    NSLog(@"%s",__func__);

    IMP sayHIMP = class_getMethodImplementation(self.class, @selector(sayHello1));

    Method sayHMethod = class_getInstanceMethod(self.class, @selector(sayHello1));

    const char *sayHType = method_getTypeEncoding(sayHMethod);

    return class_addMethod(self.class, sel, sayHIMP, sayHType);
}

+(BOOL)resolveClassMethod:(SEL)sel{

    NSLog(@"%s",__func__);

    IMP sayHIMP = class_getMethodImplementation(self, @selector(sayHello2));

    Method sayHMethod = class_getClassMethod(self, @selector(sayHello2));
    
    const char *sayHType = method_getTypeEncoding(sayHMethod);

    return class_addMethod(self, sel, sayHIMP, sayHType);

}

結(jié)果

2019-12-29 10:49:52.843118+0800 LGTest[39900:965223] +[BYTestCache_t resolveInstanceMethod:]
2019-12-29 10:49:52.844133+0800 LGTest[39900:965223] 消息轉(zhuǎn)發(fā)過來的實(shí)例方法

我們發(fā)現(xiàn)調(diào)用不存在的實(shí)例方法時候,動態(tài)給sel添加IMP后確實(shí)沒有崩潰,說明之前的驗(yàn)證正確

但是當(dāng)我們調(diào)用不存在的類方法時候結(jié)果:

2019-12-29 10:52:29.232292+0800 LGTest[40044:968519] +[BYTestCache_t resolveClassMethod:]
2019-12-29 10:52:29.232951+0800 LGTest[40044:968519] +[BYTestCache_t resolveInstanceMethod:]
2019-12-29 10:52:29.233782+0800 LGTest[40044:968519] +[BYTestCache_t by_run1]: unrecognized selector sent to class 0x100002888
2019-12-29 10:52:29.239341+0800 LGTest[40044:968519] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '+[BYTestCache_t by_run1]: unrecognized selector sent to class 0x100002888'
*** First throw call stack:
(
    0   CoreFoundation                      0x00007fff51481acd __exceptionPreprocess + 256
    1   libobjc.A.dylib                     0x000000010035546a objc_exception_throw + 42
    2   CoreFoundation                      0x00007fff514fb826 __CFExceptionProem + 0
    3   CoreFoundation                      0x00007fff5142393f ___forwarding___ + 1485
    4   CoreFoundation                      0x00007fff514232e8 _CF_forwarding_prep_0 + 120
    5   LGTest                              0x00000001000018d4 main + 116
    6   libdyld.dylib                       0x00007fff7d3523d5 start + 1
    7   ???                                 0x0000000000000001 0x0 + 1
)
libc++abi.dylib: terminating with uncaught exception of type NSException

在調(diào)用了一次resolveClassMethod后,又調(diào)用了一次resolveInstanceMethod后,然后崩潰了,是因?yàn)轭惙椒ㄊ谴鎯υ谠愔?,而類的self.class是返回的是自己不是元類,所以需要改成object_getClass(self)才行

拓展問題:
1、假如調(diào)用了不存在的實(shí)例方法并且在resolveInstanceMethod直接返回NO,我們會發(fā)現(xiàn)resolveInstanceMethod方法會被調(diào)用兩回再崩潰
2、當(dāng)我們把實(shí)例方法替換成類方法的指針時候,或者將類方法替換成實(shí)例方法指針時候,如果不在resolveInstanceMethod里面做相應(yīng)的要替換方法判斷,就會導(dǎo)致死循環(huán),如果做了判斷,老方法會調(diào)用一次resolveInstanceMethod,新方法也會調(diào)用一次resolveInstanceMethod

另外附上一張經(jīng)典的消息轉(zhuǎn)發(fā)流程圖,一般人不告訴他:

消息轉(zhuǎn)發(fā)流程.png
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容