iOS底層-17:類(lèi)的加載之category

category

1、category的本質(zhì)

今天我們先從category講起,那到底什么是category,我們借助clang看一下它在底層的結(jié)構(gòu)。
代碼還是上一章的代碼,添加分類(lèi):

@protocol LRPersonDelegate <NSObject>

- (void)delegateMethod;

@end


NS_ASSUME_NONNULL_BEGIN

@interface LRPerson (LR)

- (void)instanceMethod;
+ (void)classMethod;

@end


@implementation LRPerson (LR)

- (void)instanceMethod {
    NSLog(@"category----%s",__func__);
}
+ (void)classMethod {
    NSLog(@"category----%s",__func__);
}

@end

clang命令:clang -rewrite-objc main.m -o main.cpp

打開(kāi)生成的.cpp文件,搜索LRPerson找到以下代碼

這是一個(gè)_category_t的結(jié)構(gòu),里面有一個(gè)LRPerson的名字,還有兩個(gè)_method_list_t:一個(gè)_CATEGORY_INSTANCE_METHODS_,一個(gè)_CATEGORY_CLASS_METHODS_

  • 搜索_category_t
struct _category_t {
    const char *name;
    struct _class_t *cls;
    const struct _method_list_t *instance_methods;
    const struct _method_list_t *class_methods;
    const struct _protocol_list_t *protocols;
    const struct _prop_list_t *properties;
};

這是一個(gè)結(jié)構(gòu)體,里面有6個(gè)變量
name : 類(lèi)名
cls:屬于哪個(gè)類(lèi)
instance_methods:實(shí)例方法
class_methods:類(lèi)方法
protocols:協(xié)議
properties:屬性

  • 搜索_CATEGORY_INSTANCE_METHODS_

分類(lèi)中實(shí)現(xiàn)的instanceMethod保存在這里,是一個(gè)method_t結(jié)構(gòu)

struct method_t {
SEL name;
const char *types;
MethodListIMP imp;
}

  • 搜索_CATEGORY_CLASS_METHODS_

    分類(lèi)中實(shí)現(xiàn)的classMethod保存在這里,是一個(gè)method_t結(jié)構(gòu)

綜上所述,category在底層其實(shí)是一個(gè)_category_t結(jié)構(gòu)體,有6個(gè)變量。

attachToClass

上一篇文章分析完了prepareMethodLists,這里我們接著分析attachToClass


通過(guò)斷點(diǎn)可以看到LRPerson類(lèi)走的是最下面的方法,不是元類(lèi) 傳入的參數(shù)為ATTACH_CLASS。

  • attachToClass
void attachToClass(Class cls, Class previously, int flags)
    {
        runtimeLock.assertLocked();
        ASSERT((flags & ATTACH_CLASS) ||
               (flags & ATTACH_METACLASS) ||
               (flags & ATTACH_CLASS_AND_METACLASS));

        auto &map = get();
        auto it = map.find(previously);

        if (it != map.end()) {
            category_list &list = it->second;
            if (flags & ATTACH_CLASS_AND_METACLASS) {
                int otherFlags = flags & ~ATTACH_CLASS_AND_METACLASS;
                attachCategories(cls, list.array(), list.count(), otherFlags | ATTACH_CLASS);
                attachCategories(cls->ISA(), list.array(), list.count(), otherFlags | ATTACH_METACLASS);
            } else {
                attachCategories(cls, list.array(), list.count(), flags);
            }
            map.erase(it);
        }
    }

我們發(fā)現(xiàn)LRPerson類(lèi)根本就沒(méi)有走if里面的方法。

那么分類(lèi)是什么時(shí)候加載的呢?

category和類(lèi)的加載一樣,懶加載分類(lèi)和非懶加載分類(lèi)有一定的區(qū)別:

1.非懶加載類(lèi) + 非懶加載分類(lèi)
load_images -> loadAllCategories() -> load_categories_nolock -> attachCategories -> prepareMethodLists -> attachLists

2.懶加載類(lèi) + 非懶加載分類(lèi)
load_images -> prepare_load_methods -> realizeClassWithoutSwift -> methodizeClass -> attachToClass -> attachCategories -> prepareMethodLists -> attachLists

3.非懶加載類(lèi) + 懶加載分類(lèi)
直接寫(xiě)入mach-O

4.懶加載類(lèi) + 懶加載分類(lèi)
直接寫(xiě)入mach-O

1.非懶加載類(lèi) + 非懶加載分類(lèi)


@interface LRPerson : NSObject

@property (nonatomic,strong) NSString *name;
@property (nonatomic,strong) NSString *icon;

- (void)sayHello;
- (void)sayHappy;
- (void)instanceMethod;
+ (void)classMethod;

@end
@implementation LRPerson

//- (void)sayHello {
//    NSLog(@"%@",_cmd);
//}
- (void)sayHappy {
    NSLog(@"%s",__func__);
}
- (void)instanceMethod {
    NSLog(@"%s",__func__);
}
+ (void)classMethod {
    NSLog(@"%s",__func__);
}


+ (void)load {
    NSLog(@"%s",__func__);
}
@end


@protocol LRPersonDelegate <NSObject>

- (void)delegateMethod;

@end


NS_ASSUME_NONNULL_BEGIN

@interface LRPerson (LR)

- (void)cateA;

@end

NS_ASSUME_NONNULL_END

@implementation LRPerson (LR)


- (void)instanceMethod {
    NSLog(@"category----%s",__func__);
}
+ (void)classMethod {
    NSLog(@"category----%s",__func__);
}
- (void)cateA{
    NSLog(@"category----%s",__func__);
}
+ (void)load {
    NSLog(@"category----%s",__func__);
}

@end
  • 進(jìn)入methodizeClass斷點(diǎn)

  • 打印list


    此時(shí)category還沒(méi)有加載進(jìn)來(lái)。

  • 斷點(diǎn)進(jìn)入attachToClass方法


    在這里打一個(gè)斷點(diǎn),LLDB調(diào)試

    itmap.end()相等,并不會(huì)走以下條件。

  • 進(jìn)入attachCategories方法

在斷點(diǎn)處打印堆棧:


從堆棧可以看出是從load_images -> loadAllCategories() -> load_categories_nolock -> attachCategories 進(jìn)來(lái)的


for循環(huán)category數(shù)組開(kāi)始處理method_list_t、property_list_t、protocol_list_t

static void
attachCategories(Class cls, const locstamped_category_t *cats_list, uint32_t cats_count,
                 int flags)
{
    if (slowpath(PrintReplacedMethods)) {
        printReplacements(cls, cats_list, cats_count);
    }
    if (slowpath(PrintConnecting)) {
        _objc_inform("CLASS: attaching %d categories to%s class '%s'%s",
                     cats_count, (flags & ATTACH_EXISTING) ? " existing" : "",
                     cls->nameForLogging(), (flags & ATTACH_METACLASS) ? " (meta)" : "");
    }

 
    constexpr uint32_t ATTACH_BUFSIZ = 64;
    method_list_t   *mlists[ATTACH_BUFSIZ];
    property_list_t *proplists[ATTACH_BUFSIZ];
    protocol_list_t *protolists[ATTACH_BUFSIZ];

    uint32_t mcount = 0;
    uint32_t propcount = 0;
    uint32_t protocount = 0;
    bool fromBundle = NO;
    bool isMeta = (flags & ATTACH_METACLASS);
    auto rwe = cls->data()->extAllocIfNeeded();//rwe 初始化


    //開(kāi)始處理category數(shù)據(jù),為寫(xiě)到類(lèi)中做準(zhǔn)備
    for (uint32_t i = 0; i < cats_count; i++) {
        auto& entry = cats_list[i];

        method_list_t *mlist = entry.cat->methodsForMeta(isMeta);
        if (mlist) {
            if (mcount == ATTACH_BUFSIZ) {
                prepareMethodLists(cls, mlists, mcount, NO, fromBundle);
                rwe->methods.attachLists(mlists, mcount);
                mcount = 0;
            }
            //將mlist插入mlists數(shù)組的最后一位  mlists是一個(gè)二維數(shù)組,里面的元素還是method_list_t
            mlists[ATTACH_BUFSIZ - ++mcount] = mlist;
            fromBundle |= entry.hi->isBundle();
        }

        property_list_t *proplist =
            entry.cat->propertiesForMeta(isMeta, entry.hi);
        if (proplist) {
            if (propcount == ATTACH_BUFSIZ) {
                rwe->properties.attachLists(proplists, propcount);
                propcount = 0;
            }
            proplists[ATTACH_BUFSIZ - ++propcount] = proplist;
        }

        protocol_list_t *protolist = entry.cat->protocolsForMeta(isMeta);
        if (protolist) {
            if (protocount == ATTACH_BUFSIZ) {
                rwe->protocols.attachLists(protolists, protocount);
                protocount = 0;
            }
            protolists[ATTACH_BUFSIZ - ++protocount] = protolist;
        }
    }

    if (mcount > 0) {
        //排序mlists里的元素method_list_t  從插入的最后位置到數(shù)組的最后位置
        //排序的是mlists數(shù)組里的元素?cái)?shù)組
        prepareMethodLists(cls, mlists + ATTACH_BUFSIZ - mcount, mcount, NO, fromBundle);
        //attachLists把方法添加到列表里面
        rwe->methods.attachLists(mlists + ATTACH_BUFSIZ - mcount, mcount);
        if (flags & ATTACH_EXISTING) flushCaches(cls);
    }

    rwe->properties.attachLists(proplists + ATTACH_BUFSIZ - propcount, propcount);

    rwe->protocols.attachLists(protolists + ATTACH_BUFSIZ - protocount, protocount);
}

這里穿插一個(gè)小細(xì)節(jié),對(duì)比下面兩張圖可以發(fā)現(xiàn)。編譯時(shí),category是以類(lèi)名LRPerson命名的,運(yùn)行時(shí)category名字變成了LR,并且cls已經(jīng)跟我們的LRPerson關(guān)聯(lián)起來(lái)了。

  • attachLists
void attachLists(List* const * addedLists, uint32_t addedCount) {
        if (addedCount == 0) return;

        if (hasArray()) {
            //當(dāng)下面第三步 setArray()之后,再有新的list要插入就要走這個(gè)條件
            // many lists -> many lists
            uint32_t oldCount = array()->count;
            uint32_t newCount = oldCount + addedCount;
            setArray((array_t *)realloc(array(), array_t::byteSize(newCount)));
            array()->count = newCount;
            memmove(array()->lists + addedCount, array()->lists, 
                    oldCount * sizeof(array()->lists[0]));//將oldArray里面的元素移動(dòng)到后面
            memcpy(array()->lists, addedLists, 
                   addedCount * sizeof(array()->lists[0]));//將newArray里面的元素添加到數(shù)組前面
        }
        else if (!list  &&  addedCount == 1) {
            // 0 lists -> 1 list
            // 一維數(shù)組
            list = addedLists[0];
        } 
        else {
            // 1 list -> many lists
            List* oldList = list;
            uint32_t oldCount = oldList ? 1 : 0;
            uint32_t newCount = oldCount + addedCount;//求出新數(shù)組容量
            setArray((array_t *)malloc(array_t::byteSize(newCount)));//創(chuàng)建一個(gè)數(shù)組
            array()->count = newCount;
            if (oldList) array()->lists[addedCount] = oldList;//將oldList放到array()的最后一個(gè)位置
            memcpy(array()->lists, addedLists, 
                   addedCount * sizeof(array()->lists[0]));//從array()->lists的0號(hào)位置,放入addedLists,放入的大小是 addedCount * sizeof(array()->lists[0],也就是把a(bǔ)ddedLists平移放入array()數(shù)組,從0號(hào)位置開(kāi)始
        }
    }

category加入到rwe的流程是:
1.初始化rwe,給rwe賦值ro里的原始類(lèi)數(shù)據(jù)
2.調(diào)用attachLists,創(chuàng)建一個(gè)list保存baseMethods
3.當(dāng)有一個(gè)分類(lèi)時(shí),再次調(diào)用attachLists,走else方法。創(chuàng)建一個(gè)數(shù)組,將原來(lái)的baseMethods放到末尾,分類(lèi)里的數(shù)據(jù)放到前面。
4.再有分類(lèi)進(jìn)來(lái)時(shí),走if方法,創(chuàng)建新數(shù)組,將老數(shù)據(jù)放到末尾,新數(shù)據(jù)放前面。
5.還有分類(lèi)的話,重復(fù)第四步。

extAllocIfNeeded

attachCategories有一個(gè)取rwe的方法extAllocIfNeeded。這里簡(jiǎn)單看一下源碼

class_rw_ext_t *
class_rw_t::extAlloc(const class_ro_t *ro, bool deepCopy)
{
    runtimeLock.assertLocked();

    auto rwe = objc::zalloc<class_rw_ext_t>();

    rwe->version = (ro->flags & RO_META) ? 7 : 0;

    method_list_t *list = ro->baseMethods();
    if (list) {
        if (deepCopy) list = list->duplicate();
        rwe->methods.attachLists(&list, 1);
    }

    property_list_t *proplist = ro->baseProperties;
    if (proplist) {
        rwe->properties.attachLists(&proplist, 1);
    }

    protocol_list_t *protolist = ro->baseProtocols;
    if (protolist) {
        rwe->protocols.attachLists(&protolist, 1);
    }

    set_ro_or_rwe(rwe, ro);
    return rwe;
}

主要流程:
1.開(kāi)辟一個(gè)空間給rwe
2.設(shè)置rwe的version
3.從ro中獲取原始類(lèi)的baseMethods、baseProperties、baseProtocols
4.設(shè)置set_ro_or_rwe

2.懶加載類(lèi) + 非懶加載分類(lèi)
attachCategories打上斷點(diǎn),等程序進(jìn)入斷點(diǎn)后,bt打印堆棧信息。

  • bt


    從圖上可以看出,attachCategories是在load_images之后調(diào)用的。

  • 搜索load_images,打上斷點(diǎn),重新運(yùn)行程序

  • 發(fā)現(xiàn)走入prepare_load_methods

  • prepare_load_methods

void prepare_load_methods(const headerType *mhdr)
{
    size_t count, i;

    runtimeLock.assertLocked();
    //獲取非懶加載的類(lèi)列表
    classref_t const *classlist = 
        _getObjc2NonlazyClassList(mhdr, &count);
    //為所有的非懶加載類(lèi)和他的父類(lèi)遞歸調(diào)用 +load方法
    for (i = 0; i < count; i++) {
        schedule_class_load(remapClass(classlist[i]));
    }
    //獲取非懶加載的分類(lèi)列表
    category_t * const *categorylist = _getObjc2NonlazyCategoryList(mhdr, &count);
    for (i = 0; i < count; i++) {
        category_t *cat = categorylist[i];
        Class cls = remapClass(cat->cls);//關(guān)聯(lián)上類(lèi)和category
        if (!cls) continue;  // category for ignored weak-linked class
        if (cls->isSwiftStable()) {
            _objc_fatal("Swift class extensions and categories on Swift "
                        "classes are not allowed to have +load methods");
        }
        realizeClassWithoutSwift(cls, nil);
        ASSERT(cls->ISA()->isRealized());
        add_category_to_loadable_list(cat);
    }
}
  • prepare_load_methods中加入以下代碼,打上斷點(diǎn)
 //—— mark 2021-5-5
          const char *mangledName = cls->mangledName();
          const char *className = "LRPerson";
          if (strcmp(mangledName, className) == 0){
          printf("來(lái)了 \n");
          }
          //
  • 跟蹤斷點(diǎn),進(jìn)入realizeClassWithoutSwift
    realizeClassWithoutSwift之后,category后面的加載流程與第一種情況一致

  • 進(jìn)入methodizeClass 準(zhǔn)備好要插入的數(shù)據(jù)

  • 進(jìn)入attachToClass

  • 進(jìn)入attachCategories 獲取method_list_t、property_list_t、protocol_list_t數(shù)據(jù),rwe在這之前初始化

  • 進(jìn)入prepareMethodLists 排序method_list_t

  • 進(jìn)入attachLists 寫(xiě)入到rwe

3.非懶加載類(lèi) + 懶加載分類(lèi)
attachCategories打上斷點(diǎn),等程序進(jìn)入斷點(diǎn),再bt。

然而結(jié)果出人意料,程序根本沒(méi)有進(jìn)入斷點(diǎn)。然而我們?cè)诳刂婆_(tái),發(fā)現(xiàn)了一些信息,雖然沒(méi)有進(jìn)入attachCategories,但是他調(diào)用了我研究的其他三個(gè)方法。(這里有一些小失誤,沒(méi)有打印方法名)
我們?cè)?code>+load方法里打斷點(diǎn),再bt。


通過(guò)堆棧我們發(fā)現(xiàn),他并沒(méi)有走與Category相關(guān)的方法。
category的加載是否是在read_images時(shí)候呢?

首先在read_imagesdoneOnce的時(shí)候,加上以下代碼讀取一下Mach-O文件里的數(shù)據(jù),查看此時(shí)的baseMethods


運(yùn)行程序,進(jìn)入斷點(diǎn)時(shí),打印list

我們意外的發(fā)現(xiàn),baseMethods里的方法已經(jīng)有了category中重寫(xiě)的方法,而且他的總數(shù)count = 9,是我們LRPerson類(lèi)和category (LR)的總和。因此我們猜測(cè)category的加載,可能是在編譯期直接寫(xiě)入了mach-O文件。

4.懶加載類(lèi) + 懶加載分類(lèi)
同樣我們先查看mach-O文件的數(shù)據(jù)。


我們發(fā)現(xiàn)baseMethods里的方法已經(jīng)有了category中重寫(xiě)的方法。

補(bǔ)充

前面只是舉例了單個(gè)category,多個(gè)category有點(diǎn)不一樣。
我們?cè)?code>新建一個(gè)LRPerson+LRB,重寫(xiě)instanceMethod`

- (void)instanceMethod {
    NSLog(@"category----%s",__func__);
}

attachCategories打斷點(diǎn)然后bt

1.非懶加載類(lèi) + 非懶加載分類(lèi)
read_images的時(shí)候LRB已經(jīng)寫(xiě)入了mach-O


區(qū)別在于加載第二個(gè)category。attachCategories里的打印信息為:

load_images -> loadAllCategories() -> load_categories_nolock -> attachCategories

2.懶加載類(lèi) + 非懶加載分類(lèi)
read_images的時(shí)候LRB已經(jīng)寫(xiě)入了mach-O


attachCategories里的打印信息為:

load_images -> prepare_load_methods -> realizeClassWithoutSwift -> methodizeClass -> attachToClass -> attachCategories

3.非懶加載類(lèi) + 懶加載分類(lèi)
read_images的時(shí)候,兩個(gè)分類(lèi)LRLRB已經(jīng)寫(xiě)入了mach-O

(lldb) p *list
warning: could not find Objective-C class data in the process. This may reduce the quality of type information available.
(method_list_t) $0 = {
  entsize_list_tt<method_t, method_list_t, 3> = {
    entsizeAndFlags = 24
    count = 10
    first = {
      name = "instanceMethod"
      types = 0x0000000100000e55 "v16@0:8"
      imp = 0x0000000100000cc0 (KCObjc`-[LRPerson(LR) instanceMethod])
    }
  }
}
(lldb) p $0.get(0)
(method_t) $1 = {
  name = "instanceMethod"
  types = 0x0000000100000e55 "v16@0:8"
  imp = 0x0000000100000cc0 (KCObjc`-[LRPerson(LR) instanceMethod])
}
(lldb) p $0.get(1)
(method_t) $2 = {
  name = "cateA"
  types = 0x0000000100000e55 "v16@0:8"
  imp = 0x0000000100000cf0 (KCObjc`-[LRPerson(LR) cateA])
}
(lldb) p $0.get(2)
(method_t) $3 = {
  name = "instanceMethod"
  types = 0x0000000100000e55 "v16@0:8"
  imp = 0x0000000100000c60 (KCObjc`-[LRPerson(LRB) instanceMethod])
}
(lldb) p $0.get(3)
(method_t) $4 = {
  name = "sayHappy"
  types = 0x0000000100000e55 "v16@0:8"
  imp = 0x0000000100000b20 (KCObjc`-[LRPerson sayHappy] at LRPerson.m:15)
}
(lldb) p $0.get(4)
(method_t) $5 = {
  name = "instanceMethod"
  types = 0x0000000100000e55 "v16@0:8"
  imp = 0x0000000100000b50 (KCObjc`-[LRPerson instanceMethod] at LRPerson.m:18)
}
(lldb) p $0.get(5)
(method_t) $6 = {
  name = ".cxx_destruct"
  types = 0x0000000100000e55 "v16@0:8"
  imp = 0x0000000100000b80 (KCObjc`-[LRPerson .cxx_destruct] at LRPerson.m:10)
}
(lldb) p $0.get(6)
(method_t) $7 = {
  name = "name"
  types = 0x0000000100000e69 "@16@0:8"
  imp = 0x0000000100000bc0 (KCObjc`-[LRPerson name] at LRPerson.h:15)
}
(lldb) p $0.get(7)
(method_t) $8 = {
  name = "setName:"
  types = 0x0000000100000e71 "v24@0:8@16"
  imp = 0x0000000100000be0 (KCObjc`-[LRPerson setName:] at LRPerson.h:15)
}
(lldb) p $0.get(8)
(method_t) $9 = {
  name = "icon"
  types = 0x0000000100000e69 "@16@0:8"
  imp = 0x0000000100000c10 (KCObjc`-[LRPerson icon] at LRPerson.h:16)
}
(lldb) p $0.get(9)
(method_t) $10 = {
  name = "setIcon:"
  types = 0x0000000100000e71 "v24@0:8@16"
  imp = 0x0000000100000c30 (KCObjc`-[LRPerson setIcon:] at LRPerson.h:16)
}
(lldb) p $0.get(10)
Assertion failed: (i < count), function get, file /Users/liuyang/Desktop/可編譯objc源碼/runtime/objc-runtime-new.h, line 438.
error: Execution was interrupted, reason: signal SIGABRT.
The process has been returned to the state before expression evaluation.
(lldb) 

4.懶加載類(lèi) + 懶加載分類(lèi)
read_images的時(shí)候,兩個(gè)分類(lèi)LRLRB已經(jīng)寫(xiě)入了mach-O

(lldb) p *list
warning: could not find Objective-C class data in the process. This may reduce the quality of type information available.
(method_list_t) $0 = {
  entsize_list_tt<method_t, method_list_t, 3> = {
    entsizeAndFlags = 24
    count = 10
    first = {
      name = "instanceMethod"
      types = 0x0000000100000e60 "v16@0:8"
      imp = 0x0000000100000cd0 (KCObjc`-[LRPerson(LR) instanceMethod])
    }
  }
}
(lldb) p $0.get(0)
(method_t) $1 = {
  name = "instanceMethod"
  types = 0x0000000100000e60 "v16@0:8"
  imp = 0x0000000100000cd0 (KCObjc`-[LRPerson(LR) instanceMethod])
}
(lldb) p $0.get(1)
(method_t) $2 = {
  name = "cateA"
  types = 0x0000000100000e60 "v16@0:8"
  imp = 0x0000000100000d00 (KCObjc`-[LRPerson(LR) cateA])
}
(lldb) p $0.get(2)
(method_t) $3 = {
  name = "instanceMethod"
  types = 0x0000000100000e60 "v16@0:8"
  imp = 0x0000000100000c70 (KCObjc`-[LRPerson(LRB) instanceMethod])
}
(lldb) p $0.get(3)
(method_t) $4 = {
  name = "sayHappy"
  types = 0x0000000100000e60 "v16@0:8"
  imp = 0x0000000100000b30 (KCObjc`-[LRPerson sayHappy] at LRPerson.m:15)
}
(lldb) p $0.get(4)
(method_t) $5 = {
  name = "instanceMethod"
  types = 0x0000000100000e60 "v16@0:8"
  imp = 0x0000000100000b60 (KCObjc`-[LRPerson instanceMethod] at LRPerson.m:18)
}
(lldb) p $0.get(5)
(method_t) $6 = {
  name = ".cxx_destruct"
  types = 0x0000000100000e60 "v16@0:8"
  imp = 0x0000000100000b90 (KCObjc`-[LRPerson .cxx_destruct] at LRPerson.m:10)
}
(lldb) p $0.get(6)
(method_t) $7 = {
  name = "name"
  types = 0x0000000100000e74 "@16@0:8"
  imp = 0x0000000100000bd0 (KCObjc`-[LRPerson name] at LRPerson.h:15)
}
(lldb) p $0.get(7)
(method_t) $8 = {
  name = "setName:"
  types = 0x0000000100000e7c "v24@0:8@16"
  imp = 0x0000000100000bf0 (KCObjc`-[LRPerson setName:] at LRPerson.h:15)
}
(lldb) p $0.get(8)
(method_t) $9 = {
  name = "icon"
  types = 0x0000000100000e74 "@16@0:8"
  imp = 0x0000000100000c20 (KCObjc`-[LRPerson icon] at LRPerson.h:16)
}
(lldb) p $0.get(9)
(method_t) $10 = {
  name = "setIcon:"
  types = 0x0000000100000e7c "v24@0:8@16"
  imp = 0x0000000100000c40 (KCObjc`-[LRPerson setIcon:] at LRPerson.h:16)
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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

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