iOS runtime 八: 协议,分类和扩展

protocol_t

struct protocol_t : objc_object {
    const char *mangledName;
    struct protocol_list_t *protocols;
    method_list_t *instanceMethods;
    method_list_t *classMethods;
    method_list_t *optionalInstanceMethods;
    method_list_t *optionalClassMethods;
    property_list_t *instanceProperties;
    uint32_t size;   // sizeof(protocol_t)
    uint32_t flags;
    // Fields below this point are not always present on disk.
    const char **_extendedMethodTypes;
    const char *_demangledName;
    property_list_t *_classProperties;
...
}

mangledName是协议的名称.在存储的时候由mangledName和_demangledName共同作用生成.
protocols是遵循的上级协议,后面看protocol_list_t这个类型.
instanceMethods是在协议中声明的实例方法
classMethods是类方法
optionalInstanceMethods是@optional修饰的实例方法
optionalClassMethods是@optional修饰的类方法
instanceProperties是声明的属性
_classProperties是@property中用class关键字修饰的属性

可以看到方法和属性都是entsize_list_tt的.

typedef uintptr_t protocol_ref_t;  // protocol_t *, but unremapped

struct protocol_list_t {
    // count is pointer-sized by accident.
    uintptr_t count;
    protocol_ref_t list[0]; // variable-size

    size_t byteSize() const {
        return sizeof(*this) + count*sizeof(list[0]);
    }
    protocol_list_t *duplicate() const {
        return (protocol_list_t *)memdup(this, this->byteSize());
    }
    typedef protocol_ref_t* iterator;
    typedef const protocol_ref_t* const_iterator;
    const_iterator begin() const {
        return list;
    }
    iterator begin() {
        return list;
    }
    const_iterator end() const {
        return list + count;
    }
    iterator end() {
        return list + count;
    }
};

protocol_list_t类似method_list_t那些, 但是它不是继承自entsize_list_tt了.
两个成员count和list.
上面定义了一个protocol_ref_t,实质是protocol_t * .
下面又把protocol_ref_t * 换名迭代器iterator,注意又加了一次 * .
通过begin()和end()来将迭代器置于首位或者末位.
所以begin()返回iterator,也就是protocol_ref_t * ,protocol_ref_t又是protocol_t * ,
最终**begin()才是protocol_t.

runtime的协议

runtime源码中只有两种数据类型继承自objc_object,一个是类objc_class,一个是协议protocol_t.
class有方法,属性,protocol也一样;
协议和类一样,既可以加载静态,也可以在运行时构造.
在运行时,可以使用objc_allocate系列函数创建一个新的class,可以使用class_add系列函数向class添加新的方法,属性;
同样也可以使用objc_allocate系列函数创建新的协议,使用protocol_add系列函数向协议添加新的方法,属性.
这些是class与protocol的相似之处.

@protocol BaseProtocol <NSObject>
- (void)baseMetod;
@end


@interface MyClass : NSObject
@end


@interface MySubClass : MyClass <BaseProtocol>
@end


//main.m

Protocol *p = objc_allocateProtocol("newProtocol");
protocol_addProtocol(p, objc_getProtocol("BaseProtocol"));
objc_registerProtocol(p);
class_addProtocol(MyClass.class, p);

定义了一个类MyClass,没有遵循任何协议.
另外又定义了类MySubClass继承自MyClass.
定义了一个协议BaseProtocol,类MySubClass遵循它.

在main.m中:
runtime的objc_allocate系列方法有两个,objc_allocateClassPair构造类,objc_allocateProtocol构造协议.
这里构造了一个协议newProtocol,
然后protocol_addProtocol(Protocol *proto_gen, Protocol *addition_gen) 是给协议添加上级协议,
proto_gen是需要添加上级的协议,addition_gen是上级协议.
objc_getProtocol是获取一个已经注册的协议,
注意BaseProtocol写出来必须找一个类遵循,不然build之后符号表里没有这个协议,会获取不到.
最后和构造类的objc_registerClassPair一样,需要调用objc_registerProtocol.

运行观察内存:

(lldb) p objc_getProtocol("BaseProtocol")
(Protocol *) $0 = 0x00000001000087d0
(lldb) p (protocol_t *)$0
(protocol_t *) $3 = 0x00000001000087d0
(lldb) p *$3
(protocol_t) $4 = {
  objc_object = (isa_storage = "\xc80\U00000004\t\U00000001")
  mangledName = 0x0000000100003e67 "BaseProtocol"
  protocols = 0x0000000100008368
  instanceMethods = 0x0000000100008380
  classMethods = nil
  optionalInstanceMethods = nil
  optionalClassMethods = nil
  instanceProperties = nil
  size = 96
  flags = 0
  _extendedMethodTypes = 0x00000001000083a0
  _demangledName = 0x0000000000000000
  _classProperties = nil
}
(lldb) p $4.instanceMethods
(method_list_t *) $5 = 0x0000000100008380
(lldb) p *$5
(method_list_t) $6 = {
  entsize_list_tt<method_t, method_list_t, 4294901763, method_t::pointer_modifier> = (entsizeAndFlags = 24, count = 1)
}
(lldb) p $6.get(0)
(method_t) $7 = {}
(lldb) p $7.big()
(method_t::big) $8 = {
  name = "baseMetod"
  types = 0x0000000100003f32 "v16@0:8"
  imp = 0x0000000000000000
}

objc_getProtocol("BaseProtocol")拿到了一个protocol_t,说明BaseProtocol通过静态加载的方式被注册.
mangledName是BaseProtocol,
输出instanceMethods到$5,是entsize_list_tt类型的,get(0)然后输出big().
看到name是baseMetod.

观察main.m中的Protocol *p,

(lldb) p (protocol_t *)p
(protocol_t *) $1 = 0x00000001095bf8b0
(lldb) p *$1
(protocol_t) $2 = {
  objc_object = (isa_storage = "\xc90\U00000004\t\U00000001\x80\U0000001d\U000000029<")
  mangledName = 0x0000000100003c39 "newProtocol"
  protocols = 0x00000001095b6250
  instanceMethods = nil
  classMethods = nil
  optionalInstanceMethods = nil
  optionalClassMethods = nil
  instanceProperties = nil
  size = 96
  flags = 0
  _extendedMethodTypes = 0x0000000000000000
  _demangledName = 0x0000000000000000
  _classProperties = nil
}

newProtocol是动态构建的,newProtocol是遵循了BaseProtocol的,会存在protocols中,试一下把他找出来.

(lldb) p $2.protocols
(protocol_list_t *) $28 = 0x00000001095b6250
(lldb) p *$3
(protocol_list_t) $4 = (count = 1, list = protocol_ref_t [] @ 0x0000600001e8daf8)
(lldb) p $4.begin()
(protocol_list_t::iterator) $5 = 0x00000001095b6258
(lldb) p *$5
(protocol_ref_t) $6 = 4295002064
(lldb) p (protocol_t *)$7
(protocol_t *) $8 = 0x00000001000087d0
(lldb) p *$8
(protocol_t) $9 = {
  objc_object = (isa_storage = "\xc80\U00000004\t\U00000001")
  mangledName = 0x0000000100003e67 "BaseProtocol"
  protocols = 0x0000000100008368
  instanceMethods = 0x0000000100008380
  classMethods = nil
  optionalInstanceMethods = nil
  optionalClassMethods = nil
  instanceProperties = nil
  size = 96
  flags = 0
  _extendedMethodTypes = 0x00000001000083a0
  _demangledName = 0x0000000000000000
  _classProperties = nil
}

$4.begin()获取到iterator,也就是protocol_ref_t *
然后* iterator拿到protocol_ref_t,也就是protocol_t * ,
最后*$8拿到protocol_t.
可以看到mangledName是BaseProtocol.

最关键的是$8也就是protocol_t *和上面objc_getProtocol("BaseProtocol")拿到的地址是一样的,都是0x00000001000087d0,
所以协议在内存中只有一份.

还可以由BaseProtocol继续查看上级协议,

(lldb) p $9.protocols
(protocol_list_t *) $10 = 0x0000000100008368
(lldb) p *$10
(protocol_list_t) $11 = (count = 1, list = protocol_ref_t [] @ 0x0000600001ea7588)
(lldb) p $11.begin()
(protocol_list_t::iterator) $12 = 0x0000000100008370
(lldb) p *$12
(protocol_ref_t) $13 = 4295001968
(lldb) p (protocol_t *)$13
(protocol_t *) $14 = 0x0000000100008770
(lldb) p *$14
(protocol_t) $15 = {
  objc_object = (isa_storage = "")
  mangledName = 0x0000000100003e74 "NSObject"
  protocols = nil
  instanceMethods = 0x0000000100008090
  classMethods = nil
  optionalInstanceMethods = 0x0000000100008260
  optionalClassMethods = nil
  instanceProperties = 0x0000000100008280
  size = 96
  flags = 0
  _extendedMethodTypes = 0x00000001000082c8
  _demangledName = 0x0000000000000000
  _classProperties = nil
}

可以看到mangledName是"NSObject",
它没有上级协议,

lldb) p $41.instanceMethods
(method_list_t *) $42 = 0x0000000100008090
(lldb) p *$42
(method_list_t) $43 = {
  entsize_list_tt<method_t, method_list_t, 4294901763, method_t::pointer_modifier> = (entsizeAndFlags = 24, count = 19)
}
(lldb) p $43.get(0).big()
(method_t::big) $44 = {
  name = "isEqual:"
  types = 0x0000000100003e85 "c24@0:8@16"
  imp = 0x0000000000000000
}
(lldb) p $41.instanceProperties
(property_list_t *) $45 = 0x0000000100008280
(lldb) p *$45
(property_list_t) $46 = {
  entsize_list_tt<property_t, property_list_t, 0, PointerModifierNop> = (entsizeAndFlags = 16, count = 4)
}
(lldb) p $46.get(0)
(property_t) $47 = (name = "hash", attributes = "TQ,R")
(lldb) p $46.get(1)
(property_t) $48 = (name = "superclass", attributes = "T#,R")
(lldb) p $46.get(2)
(property_t) $49 = (name = "description", attributes = "T@\"NSString\",R,C")
(lldb) p $46.get(3)
(property_t) $50 = (name = "debugDescription", attributes = "T@\"NSString\",R,C")
(lldb) p $41.optionalInstanceMethods
(method_list_t *) $51 = 0x0000000100008260
(lldb) p *$51
(method_list_t) $52 = {
  entsize_list_tt<method_t, method_list_t, 4294901763, method_t::pointer_modifier> = (entsizeAndFlags = 24, count = 1)
}
(lldb) p $52.get(0).big()
(method_t::big) $53 = {
  name = "debugDescription"
  types = 0x0000000100003e98 "@16@0:8"
  imp = 0x0000000000000000
}

这是NSObject协议的一些内容,有20个方法,只有一个是optional,
有4个属性,hash,superclass,description和debugDescription.
也就是下面这些东西:

@protocol NSObject

- (BOOL)isEqual:(id)object;
@property (readonly) NSUInteger hash;

@property (readonly) Class superclass;
- (Class)class OBJC_SWIFT_UNAVAILABLE("use 'type(of: anObject)' instead");
- (instancetype)self;

- (id)performSelector:(SEL)aSelector;
- (id)performSelector:(SEL)aSelector withObject:(id)object;
- (id)performSelector:(SEL)aSelector withObject:(id)object1 withObject:(id)object2;

- (BOOL)isProxy;

- (BOOL)isKindOfClass:(Class)aClass;
- (BOOL)isMemberOfClass:(Class)aClass;
- (BOOL)conformsToProtocol:(Protocol *)aProtocol;

- (BOOL)respondsToSelector:(SEL)aSelector;

- (instancetype)retain OBJC_ARC_UNAVAILABLE;
- (oneway void)release OBJC_ARC_UNAVAILABLE;
- (instancetype)autorelease OBJC_ARC_UNAVAILABLE;
- (NSUInteger)retainCount OBJC_ARC_UNAVAILABLE;

- (struct _NSZone *)zone OBJC_ARC_UNAVAILABLE;

@property (readonly, copy) NSString *description;
@optional
@property (readonly, copy) NSString *debugDescription;

@end

category_ t

struct category_t {
    const char *name;
    classref_t cls;
    WrappedPtr<method_list_t, method_list_t::Ptrauth> instanceMethods;
    WrappedPtr<method_list_t, method_list_t::Ptrauth> classMethods;
    struct protocol_list_t *protocols;
    struct property_list_t *instanceProperties;
    // Fields below this point are not always present on disk.
    struct property_list_t *_classProperties;
    method_list_t *methodsForMeta(bool isMeta) {
        if (isMeta) return classMethods;
        else return instanceMethods;
    }
    property_list_t *propertiesForMeta(bool isMeta, struct header_info *hi);
    protocol_list_t *protocolsForMeta(bool isMeta) {
        if (isMeta) return nullptr;
        else return protocols;
    }
};

内容不多,主要是:
名称name
classref_t是class_t*.指向的类cls,
方法列表instanceMethods和classMethods,
协议列表protocols,
属性列表instanceProperties和_classProperties.

category没有allocate,add之类的方法, 只能静态加载,也就是只能读取编译器生成的分类.

分类中的属性与方法

分类不仅不能动态添加,类本身也不持有category_t相关的数据结构,而是通过合并的方式来获取分类的内容.
先跳过类和分类的加载, 看看分类被加载后做了什么.

在runtime-new.mm中有一个UnattachedCategories的类,用于处理分类与类的合并.
调用了一个关键函数

static void
attachCategories(Class cls, const locstamped_category_t *cats_list, uint32_t cats_count,
                 int flags)
{
   //....
}

这个函数是这样说明的,将分类的属性,方法,协议合并到类中,并且先加载分类先合并.
// Attach method lists and properties and protocols from categories to a class.
// Assumes the categories in cats are all loaded and sorted by load order,
// oldest categories first.

这个函数,cls可以是类或元类,cats_list是该类的category_t数组.

当分类被加载之后,就会进入这个函数,首先创建一个分类来观察一下.

///MyClass.h
@interface MyClass : NSObject
@end

///MyClass.m
@implementation MyClass

+ (void)load{
    printf("myClass load\n");
}

@end


///MyClass+myCat.h
@interface MyClass (myCat)

@property(nonatomic, assign) NSInteger a;

- (void)catMethod;

@end

///MyClass+myCat.m
@implementation MyClass (myCat)

+ (void)load{
    printf("myCat load\n");
}

- (NSInteger)a{
  return 0;
}
- (void)setA:(NSInteger)a{}
- (void)catMethod{}

@end

这里给类和分类都实现+load方法,为什么这么做与类与分类的加载有关,这篇先不涉及.

然后在attachCategories函数的for循环中添加:

//...
 for (uint32_t i = 0; i < cats_count; i++) {
        auto& entry = cats_list[i];
        if(strcmp(cls->nonlazyMangledName(), "MyClass") == 0){
            printf("这是MyClass\n");
        }
//...

现在进入printf断点的时候,entry就是分类'myCat'了.

(lldb) po entry.cat->name
"myCat"
(lldb) p entry.cat->instanceMethods
(WrappedPtr<method_list_t, method_list_t::Ptrauth>) $2 = {
  ptr = 0x0000000100008218
}
(lldb) p *$2
(method_list_t) $3 = {
  entsize_list_tt<method_t, method_list_t, 4294901763, method_t::pointer_modifier> = (entsizeAndFlags = 24, count = 3)
}
(lldb) p $3.get(0).big()
(method_t::big) $4 = {
  name = "a"
  types = 0x0000000100003ed6 "q16@0:8"
  imp = 0x0000000100003b30 (KCObjcBuild`-[MyClass(myCat) a])
}
(lldb) p $3.get(1).big()
(method_t::big) $5 = {
  name = "setA:"
  types = 0x0000000100003ede "v24@0:8q16"
  imp = 0x0000000100003b40 (KCObjcBuild`-[MyClass(myCat) setA:])
}
(lldb) p $3.get(2).big()
(method_t::big) $6 = {
  name = "catMethod"
  types = 0x0000000100003ebb "v16@0:8"
  imp = 0x0000000100003b60 (KCObjcBuild`-[MyClass(myCat) catMethod])
}

现在可以看到三个方法,属性a的setter和getter都有,而且对应的imp也有,因为我们都实现了.

如果注释掉

//- (NSInteger)a{
//    return 0;
//}
//
//- (void)setA:(NSInteger)a{
//
//}

再运行观察,就只有一个catMethod方法了.不管加不加@dynamic

(method_list_t) $1 = {
  entsize_list_tt<method_t, method_list_t, 4294901763, method_t::pointer_modifier> = (entsizeAndFlags = 24, count = 1)
}
(lldb) p $1.get(0).big()
(method_t::big) $2 = {
  name = "catMethod"
  types = 0x0000000100003ec5 "v16@0:8"
  imp = 0x0000000100003b70 (KCObjcBuild`-[MyClass(myCat) catMethod])
}

关于属性和成员变量
1.类如果声明了@property var,在加载类的时候,会生成property_t添加到ro的baseProperties,同时会生成ivar_t添加到ro的ivars.

2.在gcc时代,声明属性需要声明成员变量_var,然后@property var,然后@synthesize var来生成set和get方法;
后来@property自带了@synthesize的功能,并且也不需要再写一遍_var了,在类中@property直接生成成员变量和set,get方法;
但是如果遵循的协议中有@property var, 那么类可以选择在interface中再声明一遍@property var,或者在implementation中@synthesize var.

3.@synthesize是作用于@property的,可以是类的interface或者扩展,或者遵循的协议;只是声明成员变量,然后添加@synthesize,会报错"Property implementation must have its declaration in interface 'xxxClass' or one of its extensions".
这里指明了需要在interface或者extensions中声明property,extension经常被叫做匿名分类,就是

@interface XXX () //<遵循的协议>
/*
声明属性和方法
**/
@end

4.因为指明了要在interface或者extensions声明属性,所以给分类添加属性然后@synthesize是会报错的.
"@synthesize not allowed in a category's implementation"
@synthesize是@property青春版的一部分,既然@synthesize不行那分类的@property肯定也不会生成set和get.

5.在分类中声明@property, 编译器确实会生成set和get的声明,所以调用set或者get是可以编译通过的,
但是没有实现set和get方法,在mach-o中是没有对应符号的,运行程序loadimage之后也没有这两个selector,
category_t里也就没有对应的method_t.
并且在分类中声明@property ivar,没有生成成员变量,即使自己实现set和get,也是写不出_ivar的.
至于动态绑定就是另一回事了,与成员变量无关.

合并分类

然后再来仔细看看attachCategories函数,

static void
attachCategories(Class cls, const locstamped_category_t *cats_list, uint32_t cats_count, int flags)
{
 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();

    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, __func__);
                rwe->methods.attachLists(mlists, mcount);
                mcount = 0;
            }
            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) {
        prepareMethodLists(cls, mlists + ATTACH_BUFSIZ - mcount, mcount,
                           NO, fromBundle, __func__);
        rwe->methods.attachLists(mlists + ATTACH_BUFSIZ - mcount, mcount);
        if (flags & ATTACH_EXISTING) {
            flushCaches(cls, __func__, [](Class c){
                // constant caches have been dealt with in prepareMethodLists
                // if the class still is constant here, it's fine to keep
                return !c->cache.isConstantOptimizedCache();
            });
        }
    }
    rwe->properties.attachLists(proplists + ATTACH_BUFSIZ - propcount, propcount);
    rwe->protocols.attachLists(protolists + ATTACH_BUFSIZ - protocount, protocount);
}

用上ATTACH_BUFSIZ的时候是极少的,可以忽略相关的代码
首先获取类的rw,cls->data()->extAllocIfNeeded();分类中的内容都是添加到rw里的,不过这里不是class_rw_t,是class_rw_t的下级数据结构class_rw_ext_t,当需要写入rw的时候调用cls->data()extAllocIfNeeded()获取它,需要读取rw的时候调用cls->data()即可.
也就是这个东西.

struct class_rw_ext_t {
    DECLARE_AUTHED_PTR_TEMPLATE(class_ro_t)
    class_ro_t_authed_ptr<const class_ro_t> ro;
    method_array_t methods;
    property_array_t properties;
    protocol_array_t protocols;
    const char *demangledName;
    uint32_t version;
};

可以看到在for循环中, mlist, proplist, protolist分别往方法methods,属性properties,和协议protocols里添加.
这三个成员都是list_array_tt,通过调用attachLists函数添加.
list_array_tt的原理在这一篇
总之结论上来说,attachLists每次都是往最前面添加,分类的内容会被添加在类原本内容的前面,

在慢速查找时,是从前到后查找的,因此如果方法名相同,分类的方法会覆盖类的方法.
同样的如果方法名相同,后面编译的分类也会覆盖前面编译的分类的方法.

可以看到合并分类的时候并没有给ro添加ivar,这并不是因为ivar不能动态添加,即使在运行时,也可以调用class_addIvar来添加.
这主要与设计有关,swift的extension也不能添加属性.

类扩展

类扩展,extension,也就是匿名分类,只有@interface没有@implementation.
不管是声明在类文件中,还是单独一个文件,都不会产生分类,不会走attachCategories,在编译阶段就被合并到类中.

//MyClass.h
@interface MyClass : NSObject
@end

///MyClass.m
@interface MyClass()
@property(nonatomic, assign) NSInteger bCatpp;
@end
@implementation MyClass
@end

///MyClass+MyEx.h
@interface MyClass ()
@property(nonatomic, assign) NSInteger cCatpp;
@end

在.m添加一个扩展,并且另外创建一个扩展文件
执行 clang -rewrite-objc MyClass.m -o My.cpp

然后拉倒最后可以看到_bCatpp和_cCatpp的定义,以及set和get和

//成员变量_bCatpp和_cCatpp
extern "C" unsigned long OBJC_IVAR_$_MyClass$_bCatpp;
extern "C" unsigned long OBJC_IVAR_$_MyClass$_cCatpp;
struct MyClass_IMPL {
    struct NSObject_IMPL NSObject_IVARS;
    NSInteger _bCatpp;
    NSInteger _cCatpp;
};

//bCatpp的get方法实现
static NSInteger _I_MyClass_bCatpp(MyClass * self, SEL _cmd) { return (*(NSInteger *)((char *)self + OBJC_IVAR_$_MyClass$_bCatpp)); }
//bCatpp的set方法实现
static void _I_MyClass_setBCatpp_(MyClass * self, SEL _cmd, NSInteger bCatpp) { (*(NSInteger *)((char *)self + OBJC_IVAR_$_MyClass$_bCatpp)) = bCatpp; }

//cCatpp的get方法实现
static NSInteger _I_MyClass_cCatpp(MyClass * self, SEL _cmd) { return (*(NSInteger *)((char *)self + OBJC_IVAR_$_MyClass$_cCatpp)); }
//cCatpp的set方法实现
static void _I_MyClass_setCCatpp_(MyClass * self, SEL _cmd, NSInteger cCatpp) { (*(NSInteger *)((char *)self + OBJC_IVAR_$_MyClass$_cCatpp)) = cCatpp; }

//MyClass的方法列表,包含所有的SEL
static struct /*_method_list_t*/ {
    unsigned int entsize;  // sizeof(struct _objc_method)
    unsigned int method_count;
    struct _objc_method method_list[9];
} _OBJC_$_INSTANCE_METHODS_MyClass __attribute__ ((used, section ("__DATA,__objc_const"))) = {
    sizeof(_objc_method),
    9,
//这里我编译的时候还写了一个init方法
    {{(struct objc_selector *)"init", "@16@0:8", (void *)_I_MyClass_init},
    {(struct objc_selector *)"bCatpp", "q16@0:8", (void *)_I_MyClass_bCatpp},
    {(struct objc_selector *)"setBCatpp:", "v24@0:8q16", (void *)_I_MyClass_setBCatpp_},
    {(struct objc_selector *)"cCatpp", "q16@0:8", (void *)_I_MyClass_cCatpp},
    {(struct objc_selector *)"setCCatpp:", "v24@0:8q16", (void *)_I_MyClass_setCCatpp_},
    {(struct objc_selector *)"bCatpp", "q16@0:8", (void *)_I_MyClass_bCatpp},
    {(struct objc_selector *)"setBCatpp:", "v24@0:8q16", (void *)_I_MyClass_setBCatpp_},
    {(struct objc_selector *)"cCatpp", "q16@0:8", (void *)_I_MyClass_cCatpp},
    {(struct objc_selector *)"setCCatpp:", "v24@0:8q16", (void *)_I_MyClass_setCCatpp_}}
};
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,362评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,330评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,247评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,560评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,580评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,569评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,929评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,587评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,840评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,596评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,678评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,366评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,945评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,929评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,165评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,271评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,403评论 2 342

推荐阅读更多精彩内容