iOS runtime 五: 方法和消息

Method

struct method_t {
    static const uint32_t smallMethodListFlag = 0x80000000;
    method_t(const method_t &other) = delete;
    struct big {
        SEL name;
        const char *types;
        MethodListIMP imp;
    };

method_t是一個(gè)沒有成員變量的結(jié)構(gòu)體,只有成員函數(shù),在C++中這種結(jié)構(gòu)體占1個(gè)字節(jié).
二級結(jié)構(gòu)big,一個(gè)SEL,表示方法名,一個(gè)char*表示參數(shù)類型和返回值,相當(dāng)與方法的模板;最后是一個(gè)MethodListIMP指針,它是IMP的簽名包裝.

 struct small {
        RelativePointer<const void *> name;
        RelativePointer<const char *> types;
        RelativePointer<IMP, /*isNullable*/false> imp;
}

還有一個(gè)small結(jié)構(gòu)體,這個(gè)用來描述方法在image中的存儲結(jié)構(gòu),是三個(gè)偏移地址.

 big &big() const {
        ASSERT(!isSmall());
        return *(struct big *)this;
    }

提供了一個(gè)big函數(shù)用來獲取big結(jié)構(gòu)體.this是形參,實(shí)際調(diào)用時(shí)傳的是對象指針.所以它做了強(qiáng)轉(zhuǎn),返回新的指針,新的指針是struct big類型,大小也不再是一個(gè)字節(jié).

    ALWAYS_INLINE SEL name() const {
        if (isSmall()) {
            if (small().inSharedCache()) {
                return (SEL)small().name.get(sharedCacheRelativeMethodBase());
            } else {
                // Outside of the shared cache, relative methods point to a selRef
                return *(SEL *)small().name.get();
            }
        } else {
            return big().name;
        }
    }

    const char *types() const {
        return isSmall() ? small().types.get() : big().types;
    }

    IMP imp(bool needsLock) const {
        if (isSmall()) {
            IMP smallIMP = ptrauth_sign_unauthenticated(small().imp.get(),
                                                        ptrauth_key_function_pointer, 0);
            asm ("": : "r" (smallIMP) :);
            IMP remappedIMP = remappedImp(needsLock);
            if (remappedIMP)
                return remappedIMP;
            return smallIMP;
        }
        return big().imp;
    }

這三個(gè)函數(shù)用于獲取SEL, types和imp.實(shí)際內(nèi)容也沒啥,big的話直接取big結(jié)構(gòu)體,small的話調(diào)用samll的成員函數(shù)取值.

觀察一下內(nèi)存布局

@interface MyClass : NSObject

@property(nonatomic, strong) NSNumber *number;
@property(nonatomic, assign) NSInteger integer;
@property(atomic, assign) NSInteger atomic;
@property(nonatomic, copy) NSString *Str;
@property(nonatomic, weak) NSObject *weak;
@property(nonatomic, strong, readonly) NSObject *readonly;

- (void)instanceMethod;

@end
(lldb) p my.class
(Class) $0 = 0x0000000100008450
(lldb) p (objc_class *)$0
(objc_class *) $1 = 0x0000000100008450
(lldb) p $1->safe_ro()
(const class_ro_t *) $2 = 0x0000000100008348
(lldb) p *$2
(const class_ro_t) $3 = {
  flags = 388
  instanceStart = 8
  instanceSize = 56
  reserved = 0
   = {
    ivarLayout = 0x0000000100003f43 "\U00000001!\U00000011"
    nonMetaclass = 0x0000000100003f43
  }
  name = {
    std::__1::atomic<const char *> = "MyClass" {
      Value = 0x0000000100003f3b "MyClass"
    }
  }
  baseMethods = {
    ptr = 0x00000001000080d8
  }
  baseProtocols = nil
  ivars = 0x0000000100008218
  weakIvarLayout = 0x0000000100003f47 "A"
  baseProperties = 0x00000001000082e0
  _swiftMetadataInitializer_NEVER_USE = {}
}
(lldb) p $3.baseMethods.ptr
(method_list_t *const) $4 = 0x00000001000080d8
(lldb) p $4->get(0)
(method_t) $5 = {}
(lldb) p $5.big()
(method_t::big) $6 = {
  name = "setWeak:"
  types = 0x0000000100003f59 "v24@0:8@16"
  imp = 0x0000000100003bd0 (KCObjcBuild`-[MyClass setWeak:])
}

(lldb) p sizeof($5)
(unsigned long) $7 = 1
(lldb) p &$5
(method_t *) $9 = 0x00000001000080e0
(lldb) p &$6
(method_t::big *) $10 = 0x00000001000080e0


首先獲取class_ro_t,然后輸出baseMethods的get(0),是一個(gè)空的結(jié)構(gòu)體,然后調(diào)用big()可以獲取到big結(jié)構(gòu)體.
下面查看空method_t的大小是1字節(jié),并且method_t的地址和big結(jié)構(gòu)體的地址是一樣的,和big函數(shù)描述的一樣.

然后還可以看到SEL是setWeak,這是@property(nonatomic, weak) NSObject *weak的setter.

(lldb) p $4.count
(uint32_t) $11 = 13
(lldb) p *$4
(method_list_t) $12 = {
  entsize_list_tt<method_t, method_list_t, 4294901763, method_t::pointer_modifier> = (entsizeAndFlags = 27, count = 13)
}

(lldb) p $12.get(1)->big()
(method_t::big) $13 = {
  name = "instanceMethod"
  types = 0x0000000100003f49 "v16@0:8"
  imp = 0x0000000100003a50 (KCObjcBuild`-[MyClass instanceMethod])
}
  Fix-it applied, fixed expression was: 
    $12.get(1).big()
(lldb) p $12.get(1).big()
(method_t::big) $14 = {
  name = "instanceMethod"
  types = 0x0000000100003f49 "v16@0:8"
  imp = 0x0000000100003a50 (KCObjcBuild`-[MyClass instanceMethod])
}
(lldb) p $12.get(2).big()
(method_t::big) $15 = {
  name = "Str"
  types = 0x0000000100003f51 "@16@0:8"
  imp = 0x0000000100003b50 (KCObjcBuild`-[MyClass Str])
}
(lldb) p $12.get(3).big()
(method_t::big) $16 = {
  name = ".cxx_destruct"
  types = 0x0000000100003f49 "v16@0:8"
  imp = 0x0000000100003c20 (KCObjcBuild`-[MyClass .cxx_destruct])
}
(lldb) p $12.get(4).big()
(method_t::big) $17 = {
  name = "number"
  types = 0x0000000100003f51 "@16@0:8"
  imp = 0x0000000100003a80 (KCObjcBuild`-[MyClass number])
}
(lldb) p $12.get(5).big()
(method_t::big) $18 = {
  name = "setNumber:"
  types = 0x0000000100003f59 "v24@0:8@16"
  imp = 0x0000000100003aa0 (KCObjcBuild`-[MyClass setNumber:])
}
(lldb) p $12.get(6).big()
(method_t::big) $19 = {
  name = "readonly"
  types = 0x0000000100003f51 "@16@0:8"
  imp = 0x0000000100003c00 (KCObjcBuild`-[MyClass readonly])
}
(lldb) p $12.get(7).big()
(method_t::big) $20 = {
  name = "setAtomic:"
  types = 0x0000000100003f6c "v24@0:8q16"
  imp = 0x0000000100003b30 (KCObjcBuild`-[MyClass setAtomic:])
}
(lldb) p $12.get(8).big()
(method_t::big) $21 = {
  name = "atomic"
  types = 0x0000000100003f64 "q16@0:8"
  imp = 0x0000000100003b10 (KCObjcBuild`-[MyClass atomic])
}
(lldb) p $12.get(9).big()
(method_t::big) $22 = {
  name = "weak"
  types = 0x0000000100003f51 "@16@0:8"
  imp = 0x0000000100003ba0 (KCObjcBuild`-[MyClass weak])
}
(lldb) p $12.get(10).big()
(method_t::big) $23 = {
  name = "integer"
  types = 0x0000000100003f64 "q16@0:8"
  imp = 0x0000000100003ad0 (KCObjcBuild`-[MyClass integer])
}
(lldb) p $12.get(11).big()
(method_t::big) $24 = {
  name = "setInteger:"
  types = 0x0000000100003f6c "v24@0:8q16"
  imp = 0x0000000100003af0 (KCObjcBuild`-[MyClass setInteger:])
}
(lldb) p $12.get(12).big()
(method_t::big) $25 = {
  name = "setStr:"
  types = 0x0000000100003f59 "v24@0:8@16"
  imp = 0x0000000100003b70 (KCObjcBuild`-[MyClass setStr:])
}

baseMethods里一共有13個(gè)方法,全部輸出,6個(gè)property,其中一個(gè)是readonly,它只有g(shù)etter沒有setter,
還有一個(gè)實(shí)例方法instanceMethod以及一個(gè)來cxx_destruct,這個(gè)函數(shù)用于釋放成員變量.

///注釋Myclass的屬性再運(yùn)行
(lldb) p $5.count
(uint32_t) $6 = 1
(lldb) p $5.get(0).big()
(method_t::big) $7 = {
  name = "instanceMethod"
  types = 0x0000000100003fa4 "v16@0:8"
  imp = 0x0000000100003e70 (KCObjcBuild`-[MyClass instanceMethod])
}

假如把Myclass的property都注了,只留一個(gè)實(shí)例方法,此時(shí)cxx_destruct就沒了.

SEL與IMP

/// An opaque type that represents a method selector.
typedef struct objc_selector *SEL;

SEL是一個(gè)不透明的objc_selector結(jié)構(gòu)體指針,但是上面觀察method_t的時(shí)候可以看到它就是一個(gè)字符串,或者說可以當(dāng)做一個(gè)char *來看待.
它用于表示方法的名稱,從上面的例子也可以看到,name等于方法名,所以它不區(qū)分是來自那個(gè)類對象或者元類,
這些方法名被添加在一張表中,runtime可以通過sel_registerName或者NSSelectorFormString獲取SEL,編譯時(shí)可以通過編譯器指令@selector()來創(chuàng)建SEL.

/// A pointer to the function of a method implementation. 
#if !OBJC_OLD_DISPATCH_PROTOTYPES
typedef void (*IMP)(void /* id, SEL, ... */ ); 
#else
typedef id _Nullable (*IMP)(id _Nonnull, SEL _Nonnull, ...); 
#endif

IMP是一個(gè)函數(shù)指針,它的類型是 id ()(id, SEL, ...)或者void ()(...)

首先回憶一下C++的函數(shù)指針.

void aMethod(int a){
    printf("aMethod = %d\n", a);
}

int main(int argc, const char * argv[]) {
    @autoreleasepool {

        void (*methodPointer)(int) = aMethod;
        methodPointer(1);
        
        NSLog(@"Hello, World!");
    }
    return 0;
}

void (* methodPointer)(int) = aMethod定義了一個(gè)函數(shù)指針,
指針要有類型,比如int * char*,這個(gè)指針的類型是void ()(int)這么一種函數(shù).
void是函數(shù)的返回值類型,int是參數(shù)列表,methodPointer是指針的名字.
然后= aMethod是讓指針methodPointer指向了函數(shù)aMethod.

(lldb) p &aMethod
(void (*)(int)) $0 = 0x0000000100003d50 (KCObjcBuild`aMethod at main.m:25)
(lldb) p methodPointer
(void (*)(int)) $1 = 0x0000000100003d50 (KCObjcBuild`aMethod at main.m:25)

調(diào)用這個(gè)函數(shù),可以是aMethod(1),也可以是methodPointer(1),他們實(shí)質(zhì)是一樣的,函數(shù)名就像是數(shù)組名,訪問數(shù)組名就是訪問數(shù)組內(nèi)存的起始,函數(shù)名也代表了這個(gè)函數(shù)在內(nèi)存代碼區(qū)的起始地址.

typedef void (*MY_SEL)(int);
MY_SEL a_sel = aMethod;
a_sel(1);

函數(shù)指針也使用typedef來取別名,這里定義了一個(gè)函數(shù)指針類型,MY_SEL,是void (* )(int).
就類似于typedef MY_INT int;然后就可以MY_INT a = 1;
所以IMP就是void ( * )()類型的指針.
而且不管它指向什么函數(shù),有什么返回值,什么參數(shù),都用這個(gè)類型的指針.

int aMethod(int a){
    printf("aMethod = %d\n", a);
    return  a;
}

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        class_addMethod(MyClass.class, @selector(aMethod:), (IMP)aMethod, "i@:i");
        NSLog(@"Hello, World!");
    }
    return 0;

運(yùn)行斷點(diǎn)

(objc_class *) $0 = 0x0000000100008120
(lldb) p $0->data()
(class_rw_t *) $1 = 0x0000000108e27920
(lldb) p $1->methods()
(const method_array_t) $2 = {
  list_array_tt<method_t, method_list_t, method_list_t_authed_ptr> = {
     = {
      list = {
        ptr = 0x0000000108e498b1
      }
      arrayAndFlag = 4444166321
    }
  }
}
(lldb) p $2.begin()
(list_array_tt<method_t, method_list_t, method_list_t_authed_ptr>::iterator) $3 = {
  lists = 0x0000000108e498b8
  listsEnd = 0x0000000108e498c8
  m = (entsize = 24, index = 0, element = 0x0000000108e49898)
  mEnd = (entsize = 24, index = 1, element = 0x0000000108e498b0)
}
(lldb) p $3.m
(list_array_tt<method_t, method_list_t, method_list_t_authed_ptr>::iteratorImpl<false>::ListIterator<false>::Type) $4 = (entsize = 24, index = 0, element = 0x0000000108e49898)
(lldb) p $4.element
(entsize_list_tt<method_t, method_list_t, 4294901763, method_t::pointer_modifier>::iteratorImpl<false>::ElementPtr) $5 = 0x0000000108e49898
(lldb) p $5->big()
(method_t::big) $6 = {
  name = "aMethod:"
  types = 0x0000000100003f60 "i@:i"
  imp = 0x0000000100003de0 (KCObjcBuild`aMethod at main.m:25)
}
(lldb) p *$6.imp
(void (*)()) $7 = 0x0000000100003de0 (KCObjcBuild`aMethod at main.m:25)

可以看到IMP是void (*)()類型指針,但是我們給MyClass添加的方法指定的imp是aMethod,類型是int:int

(lldb) p $5.imp
(MethodListIMP) $6 = 0x0000000100003db0 (KCObjcBuild`aMethod at main.m:25)
(lldb) p *$6
(void (*)()) $7 = 0x0000000100003db0 (KCObjcBuild`aMethod at main.m:25)
(lldb) p (int (*)(int))$7
(int (*)(int)) $8 = 0x0000000100003db0 (KCObjcBuild`aMethod at main.m:25)
(lldb) p $8(1)
aMethod = 1
(int) $9 = 1

所以我們想要調(diào)用這個(gè)函數(shù)需要轉(zhuǎn)換指針類型,把(void ( * )())轉(zhuǎn)換成(int (*)(int)).然后就可以調(diào)用aMethod了.

addMethod

static IMP 
addMethod(Class cls, SEL name, IMP imp, const char *types, bool replace)
{
    IMP result = nil;
    method_t *m;
    if ((m = getMethodNoSuper_nolock(cls, name))) {
        // already exists
        if (!replace) {
            result = m->imp(false);
        } else {
            result = _method_setImplementation(cls, m, imp);
        }
    } else {
        // fixme optimize
        method_list_t *newlist;
        newlist = (method_list_t *)calloc(method_list_t::byteSize(method_t::bigSize, 1), 1);
        newlist->entsizeAndFlags = 
            (uint32_t)sizeof(struct method_t::big) | fixed_up_method_list;
        newlist->count = 1;
        auto &first = newlist->begin()->big();
        first.name = name;
        first.types = strdupIfMutable(types);
        first.imp = imp;
        addMethods_finish(cls, newlist);
        result = nil;
    }
    return result;
}

runtime可以動態(tài)添加方法,這個(gè)函數(shù)可以指定是否替換replace原來的方法.
getMethodNoSuper_nolock這個(gè)函數(shù)用于在類中通過SEL查找方法method_t,這部分屬于方法慢速查找流程,之后在細(xì)說.
如果找到了,需要替換就替換,替換不替換都是返回原來的那個(gè)IMP,因?yàn)開method_setImplementation返回舊的IMP.
如果沒找到,就創(chuàng)建一個(gè)新的method_list_t,給首位元素賦值,SEL,types和IMP.

static void
addMethods_finish(Class cls, method_list_t *newlist)
{
    auto rwe = cls->data()->extAllocIfNeeded();
    if (newlist->count > 1) {
        method_t::SortBySELAddress sorter;
        std::stable_sort(&newlist->begin()->big(), &newlist->end()->big(), sorter);
    }
    prepareMethodLists(cls, &newlist, 1, NO, NO, __func__);
    rwe->methods.attachLists(&newlist, 1);
    flushCaches(cls, __func__, [](Class c){
        return !c->cache.isConstantOptimizedCache();
    });
}

最后調(diào)用addMethods_finish,
先對newlist進(jìn)行了排序,這個(gè)排序在查找中會用到.
rwe是objc_class的class_rw_t的rw_ext_t,也就是rw的內(nèi)容,可以獲取到methods,是list_array_tt類型,
所以后面調(diào)用了list_array_tt的attachLists來添加新的method_list_t;
然后添加了緩存,runtime添加的方法,添加的時(shí)候就已經(jīng)放在緩存里了.

最后如果addMethod成功添加了一個(gè)新的,result就有沒有值,如果已經(jīng)存在舊的SEL,那result就有值,
所以class_addMethod最后才會這么寫

BOOL 
class_addMethod(Class cls, SEL name, IMP imp, const char *types)
{
    if (!cls) return NO;
    mutex_locker_t lock(runtimeLock);
    return !addMethod(cls, name, imp, types ?: "", NO);
}

如果addMethod返回了有值,那就添加失敗了.

objc_msgSend

#if !OBJC_OLD_DISPATCH_PROTOTYPES
OBJC_EXPORT void
objc_msgSend(void /* id self, SEL op, ... */ )
    OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0);

這里OBJC_OLD_DISPATCH_PROTOTYPES是0,所以最終objc_msgSend是這么定義的.
但是在使用的時(shí)候會進(jìn)行強(qiáng)制類型轉(zhuǎn)換,來匹配使用場景下的真實(shí)返回值和參數(shù).
objc_msgSend是一個(gè)函數(shù),

- (id)performSelector:(SEL)sel withObject:(id)obj {
    if (!sel) [self doesNotRecognizeSelector:sel];
    return ((id(*)(id, SEL, id))objc_msgSend)(self, sel, obj);
}

比如performSelector的實(shí)現(xiàn)是這樣的,最終是調(diào)用的objc_msgSend函數(shù),不過需要轉(zhuǎn)換類型

(lldb) p objc_msgSend
error: expression failed to parse:
error: <user expression 0>:1:1: 'objc_msgSend' has unknown type; cast it to its declared type to use it

objc_msgSend是一個(gè)不完整的類型,如果在這里斷點(diǎn),輸出p objc_msgSend,
會報(bào)錯(cuò),讓你轉(zhuǎn)換類型再輸出

- (id)performSelector:(SEL)sel withObject:(id)obj {
    if (!sel) [self doesNotRecognizeSelector:sel];
    id(*my_objc_msgSend)(id, SEL, id) = (id(*)(id, SEL, id))objc_msgSend;
    return my_objc_msgSend(self, sel, obj);
}

(lldb) p my_objc_msgSend
(id (*)(id, SEL, id)) $0 = 0x0000000108f9bf80 (libobjc.A.dylib`objc_msgSend)

我們可以改造一下這個(gè)方法,先轉(zhuǎn)換,再調(diào)用,也是可以生效的.
objc4中使用objc_msgSend的時(shí)候,都進(jìn)行了強(qiáng)制類型轉(zhuǎn)換.

@interface Myclass : NSObject
@property(nonatomic, strong) NSNumber *myName;
@end

@implementation Myclass
@end

int main(int argc, char * argv[]) {
    id obj = [Myclass alloc];
    Myclass *my = [obj init];
    return  0;
}

修改main.m的代碼,隨便什么類型的工程都行.添加一個(gè)MyClass,然后alloc ,init.

clang -rewrite-objc main.m -o main.cpp

把main.m編譯成C++代碼.

int main(int argc, char * argv[]) {
    id obj = ((Myclass *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("Myclass"), sel_registerName("alloc"));
    Myclass *my = ((id (*)(id, SEL))(void *)objc_msgSend)((id)obj, sel_registerName("init"));
    return 0;
}

找到main函數(shù),
原來的OC代碼是Myclass *my = [[Myclass alloc]init];現(xiàn)在被轉(zhuǎn)換成兩個(gè)objc_msgSend函數(shù)調(diào)用.
這里(void *)是做了兩次類型轉(zhuǎn)換,void 可以指向任意類型的數(shù)據(jù),
所以第一句是把objc_msgSend 轉(zhuǎn)換成(Myclass ()(id, SEL)),然后調(diào)用,傳了兩個(gè)參數(shù),一個(gè)Class指針,一個(gè)SEL.
第二句是把objc_msgSend轉(zhuǎn)換成(id (
)(id, SEL))然后調(diào)用,傳了兩個(gè)參數(shù)obj和一個(gè)SEL.
alloc傳的第一個(gè)參數(shù)是類對象,init傳的第一個(gè)參數(shù)是實(shí)例對象,這對應(yīng)了alloc是+方法,init是-方法.

objc_msgSend沒有C++的實(shí)現(xiàn),而是直接匯編實(shí)現(xiàn).具體是在source文件夾里的幾個(gè).s文件,比如objc-msg-arm64.s
并且它還是一個(gè)系列的函數(shù),在message.h中定義了其他很多函數(shù).

OBJC_EXPORT void
objc_msgSendSuper(void /* struct objc_super *super, SEL op, ... */ )
    OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0);

objc_msgSend_stret(void /* id self, SEL op, ... */ )
    OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0)
    OBJC_ARM64_UNAVAILABLE;

OBJC_EXPORT void
objc_msgSendSuper_stret(void /* struct objc_super *super, SEL op, ... */ )
    OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0)
    OBJC_ARM64_UNAVAILABLE;

這些函數(shù)都是匯編實(shí)現(xiàn),和objc_msgSend在相同的位置,
不過特別的是,除了objc_msgSend之外,其他函數(shù)沒有在匯編之外的地方調(diào)用過.
但是他們聲明在外面,所以也可以主動調(diào)用看看.

objc_msgSendSuper需要一個(gè)struct objc_super *,一個(gè)SEL

struct objc_super {
    __unsafe_unretained _Nonnull id receiver;
    __unsafe_unretained _Nonnull Class super_class;
};

objc_super結(jié)構(gòu)體是這樣的,指定一個(gè)實(shí)例對象receiver,還有父類.

//MyClass
- (int)instanceMethod:(NSNumber *)a{
    NSLog(@"instanceMethod = %d", [a intValue]);
    return [a intValue];
}

//main.m
#import <objc/message.h>

MySubClass *sub = [[MySubClass alloc]init];
struct objc_super s;
s.receiver = sub;
s.super_class = [MyClass class];
((id (*)(struct objc_super*, SEL, NSNumber *))objc_msgSendSuper)(&s, sel_registerName("instanceMethod:"), @9);

需要頭文件#import <objc/message.h>
objc_msgSendSuper同樣也需要強(qiáng)制轉(zhuǎn)換類型,順便帶上參數(shù),
指定objc_super的接受者是sub對象,父類是MyClass.class.
instanceMethod:是父類MyClass的實(shí)例方法,MySubClass沒有重寫.
運(yùn)行可以正常輸出

instanceMethod = 9

performSelector

- (int)instanceMethod:(int)a{
    NSLog(@"instanceMethod = %d", a);
    return a;
}

//- (id)performSelector:(SEL)sel withObject:(id)obj 

假如instanceMethod是這么寫的,它接收一個(gè)int型的參數(shù),那么performSelector就不好使了,
因?yàn)閜erformSelector的參數(shù)必須是id,也就是objc_object *類型.
并且performSelector只提供了無參,一個(gè)參數(shù)和兩個(gè)參數(shù)兩種版本.

MyClass *my = [[MyClass alloc]init];
((int (*)(id, SEL, int))objc_msgSend)(my, sel_registerName("instanceMethod:"), 9);

這個(gè)只能用objc_msgSend來調(diào)用

        MyClass *my = [[MyClass alloc]init];
        int num = 9;
        SEL sel = sel_registerName("instanceMethod:");
        NSMethodSignature *signature = [my methodSignatureForSelector:sel];
        NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];
        [invocation setArgument:&num atIndex:2];
        invocation.selector = sel;
        invocation.target = my;
        [invocation invoke];
        
        int result;
        [invocation getReturnValue:&result];
        NSLog(@"result = %d", result);

或者使用NSInvocation.關(guān)于NSInvocation的細(xì)節(jié)在之后消息轉(zhuǎn)發(fā)中研究.

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

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

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