美文网首页
iOS 分类

iOS 分类

作者: 陈盼同学 | 来源:发表于2021-01-27 11:36 被阅读0次

分类前情

分类里面可以写对象方法、类方法、协议、属性
不过这个属性不会生成_成员变量,只会生成属性的set和get方法的声明

打开源码搜索category,可以在objc-private.h里看到如下定义

#if __OBJC2__
typedef struct method_t *Method;
typedef struct ivar_t *Ivar;
typedef struct category_t *Category;
typedef struct property_t *objc_property_t;
#else
typedef struct old_method *Method;
typedef struct old_ivar *Ivar;
typedef struct old_category *Category;
typedef struct old_property *objc_property_t;
#endif

然后点击category_t查看

struct category_t {
    const char *name;  //可能是分类名称
    classref_t cls;    //可能是代表这个分类是属于哪个类
    struct method_list_t *instanceMethods;   //实例方法(对象方法)
    struct method_list_t *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);
};

看来看去发现这个分类结构体里就是没有ivars(成员变量),至少这个结构体说明分类是没法添加成员变量

下面通过转C++文件来查看下结构

新建MJPerson的分类

#import "MJPerson.h"
@interface MJPerson (Category)
@property (nonatomic , copy) NSString *myName;
@property (nonatomic , assign) int myNum;
-(void)catEat;
+(void)dogRun;
@end

#import "MJPerson+Category.h"
@implementation MJPerson (Category)
-(void)catEat{
    NSLog(@"---catEat---");
}
+(void)dogRun{
    NSLog(@"---dogRun---");
}
@end

然后通过xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc MJPerson+Category.m -o MJPerson+Category.cpp将g分类的.m文件转C++文件来查看下结构

struct _class_t {  //当前类信息
    struct _class_t *isa;
    struct _class_t *superclass;
    void *cache;
    void *vtable;
    struct _class_ro_t *ro;
};

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;
};
extern "C" __declspec(dllimport) struct objc_cache _objc_empty_cache;
#pragma warning(disable:4273)

static struct /*_method_list_t*/ {  //分类里的对象方法信息
    unsigned int entsize;  // sizeof(struct _objc_method)
    unsigned int method_count;
    struct _objc_method method_list[1];
} _OBJC_$_CATEGORY_INSTANCE_METHODS_MJPerson_$_Category __attribute__ ((used, section ("__DATA,__objc_const"))) = {
    sizeof(_objc_method),
    1,
    {{(struct objc_selector *)"catEat", "v16@0:8", (void *)_I_MJPerson_Category_catEat}}
};

static struct /*_method_list_t*/ {  //分类里的类方法信息
    unsigned int entsize;  // sizeof(struct _objc_method)
    unsigned int method_count;
    struct _objc_method method_list[1];
} _OBJC_$_CATEGORY_CLASS_METHODS_MJPerson_$_Category __attribute__ ((used, section ("__DATA,__objc_const"))) = {
    sizeof(_objc_method),
    1,
    {{(struct objc_selector *)"dogRun", "v16@0:8", (void *)_C_MJPerson_Category_dogRun}}
};

//如果有协议,也会生成分类里的协议信息
//...

static struct /*_prop_list_t*/ {  //分类里的属性信息
    unsigned int entsize;  // sizeof(struct _prop_t)
    unsigned int count_of_properties;
    struct _prop_t prop_list[2];
} _OBJC_$_PROP_LIST_MJPerson_$_Category __attribute__ ((used, section ("__DATA,__objc_const"))) = {
    sizeof(_prop_t),
    2,
    {{"myName","T@\"NSString\",C,N"},
    {"myNum","Ti,N"}}
};


extern "C" __declspec(dllimport) struct _class_t OBJC_CLASS_$_MJPerson;

static struct _category_t _OBJC_$_CATEGORY_MJPerson_$_Category __attribute__ ((used, section ("__DATA,__objc_const"))) =   //编译器生成了category本身_OBJC_$_CATEGORY_MJPerson_$_Category,并用前面生成的列表来初始化category本身。
{
    "MJPerson",  //赋值给const char *name;
    0, // &OBJC_CLASS_$_MJPerson,  //赋值给struct _class_t *cls;
    (const struct _method_list_t *)&_OBJC_$_CATEGORY_INSTANCE_METHODS_MJPerson_$_Category, //赋值给const struct _method_list_t *instance_methods;
    (const struct _method_list_t *)&_OBJC_$_CATEGORY_CLASS_METHODS_MJPerson_$_Category, //赋值给const struct _method_list_t *class_methods;
    0,  //赋值给const struct _protocol_list_t *protocols;
    (const struct _prop_list_t *)&_OBJC_$_PROP_LIST_MJPerson_$_Category,  //赋值给const struct _prop_list_t *properties;
};

通过上面可以看出,分类里的类方法和对象方法以及属性都有相应的生成,也是没有分类的成员变量的。(如果让分类继承<NSCopying>,在这里也是可以看到生成协议信息的)

分类底层结构里,是一个分类结构体,分类结构体里存以下东西(通过将文件转C++大致可以看出)(第一点这个结构体里没有_成员变量的存储列表;第二点成员变量(如果有的话)}的内存布局是在编译阶段就确定好的,而分类是通过运行时动态加载的。所以表面上就很多人认为分类不能添加属性 )
对象方法
类方法
属性
协议

所以通过源码,转C++,以及编译过程不同都可以知道分类不能添加成员变量了。

通过上面源码查看后可能会有如下的一个疑问
比如这个MJPerson类,如下调用分类里的方法

MJPerson *mj = [[MJPerson alloc]init];
[mj catEat];
[MJPerson dogRun];

上面两句调用,catEat是由实例对象mj的ISA指针指向MJPerson,然后调用MJPerson的catEat对象方法。dogRun是由MJPerson的isa指向元类对象,调用元类对象的dogRun类方法。但是上述的分类对象方法等是放在分类的结构体里的,怎么办 ?
开始源码分析
在objc-os.mm文件里找到程序运行时的入口函数(_objc_init)

void _objc_init(void)
{
    static bool initialized = false;
    if (initialized) return;
    initialized = true;
    
    // fixme defer initialization until an objc-using image is found?
    environ_init();
    tls_init();
    static_init();
    lock_init();
    exception_init();

    _dyld_objc_notify_register(&map_images, load_images, unmap_image);
}

category被附加到类上面是在map_images的时候发生的(上面源码里是将map_images的地址传进去,所以可以点击map_images查看map_images具体做了什么事)
点击map_images看到如下

map_images(unsigned count, const char * const paths[],
           const struct mach_header * const mhdrs[])
{
    mutex_locker_t lock(runtimeLock);
    return map_images_nolock(count, paths, mhdrs);
}

点击map_images_nolock看到如下

map_images_nolock(unsigned mhCount, const char * const mhPaths[],
                  const struct mach_header * const mhdrs[])
{
    
    ...//省略跟当前追踪无关代码
    
    if (hCount > 0) {
        _read_images(hList, hCount, totalClasses, unoptimizedTotalClasses);
    }
    
}

点击_read_images看到如下(read_images是读取模块的意思。上面几步流程简言之就是在new-ABI的标准下,_objc_init里面的调用的map_images最终会调用objc-runtime-new.mm里面的_read_images方法)

void _read_images(header_info **hList, uint32_t hCount, int totalClasses, int unoptimizedTotalClasses)
{
    
    ...//省略跟当前追踪无关代码
    
    // Discover categories.
    for (EACH_HEADER) {
        category_t **catlist =
            _getObjc2CategoryList(hi, &count);  //拿到一个类的分类数组
        bool hasClassProperties = hi->info()->hasCategoryClassProperties();

        for (i = 0; i < count; i++) {
            category_t *cat = catlist[i];  //取出一个类的其中一个分类
            Class cls = remapClass(cat->cls);

            if (!cls) {
                // Category's target class is missing (probably weak-linked).
                // Disavow any knowledge of this category.
                catlist[i] = nil;
                if (PrintConnecting) {
                    _objc_inform("CLASS: IGNORING category \?\?\?(%s) %p with "
                                 "missing weak-linked target class",
                                 cat->name, cat);
                }
                continue;
            }

            // Process this category.
            // First, register the category with its target class.
            // Then, rebuild the class's method lists (etc) if
            // the class is realized.
            bool classExists = NO;
            if (cat->instanceMethods ||  cat->protocols
                ||  cat->instanceProperties)   //分类如果有对象方法或者协议或者属性
            {
                addUnattachedCategoryForClass(cat, cls, hi); //利用addUnattachedCategoryForClass把类和category做一个关联映射
                if (cls->isRealized()) {  //Realized的翻译是已实现
                    remethodizeClass(cls); //remethodizeClass是真正去处理添加事宜的功臣,这里传的是类
                    classExists = YES;
                }
                if (PrintConnecting) {
                    _objc_inform("CLASS: found category -%s(%s) %s",
                                 cls->nameForLogging(), cat->name,
                                 classExists ? "on existing class" : "");
                }
            }

            if (cat->classMethods  ||  cat->protocols
                ||  (hasClassProperties && cat->_classProperties)) //分类如果有类方法或者协议或者类属性(这个类属性是什么鬼)
            {
                addUnattachedCategoryForClass(cat, cls->ISA(), hi);
                if (cls->ISA()->isRealized()) {
                    remethodizeClass(cls->ISA()); //remethodizeClass是真正去处理添加事宜的功臣,这里传的是元类了
                }
                if (PrintConnecting) {
                    _objc_inform("CLASS: found category +%s(%s)",
                                 cls->nameForLogging(), cat->name);
                }
            }
        }
    }
}

点击remethodizeClass看到如下代码

static void remethodizeClass(Class cls)
{
    category_list *cats;
    bool isMeta;

    runtimeLock.assertLocked();

    isMeta = cls->isMetaClass();

    //unattachedCategoriesForClass方法获取cls中未完成整合的所有分类
    // Re-methodizing: check for more categories
    if ((cats = unattachedCategoriesForClass(cls, false/*not realizing*/))) {
        if (PrintConnecting) {
            _objc_inform("CLASS: attaching categories to class '%s' %s",
                         cls->nameForLogging(), isMeta ? "(meta)" : "");
        }
        
        attachCategories(cls, cats, true /*flush caches*/); //attachCategories接收类,分类数组等参数,attachCategories方法是将cats拼接到cls上
        free(cats);
    }
}

点击attachCategories看到如下代码

static void attachCategories(Class cls, category_list *cats, bool flush_caches)
{
    if (!cats) return;
    if (PrintReplacedMethods) printReplacements(cls, cats);

    bool isMeta = cls->isMetaClass();

    // fixme rearrange to remove these intermediate allocations
    method_list_t **mlists = (method_list_t **)
        malloc(cats->count * sizeof(*mlists)); //二维数组
    property_list_t **proplists = (property_list_t **)
        malloc(cats->count * sizeof(*proplists));
    protocol_list_t **protolists = (protocol_list_t **)
        malloc(cats->count * sizeof(*protolists));

    // Count backwards through cats to get newest categories first
    int mcount = 0;
    int propcount = 0;
    int protocount = 0;
    int i = cats->count; //宿主的个数
    bool fromBundle = NO;
    while (i--) {  //while循环,括号里面代码实现是倒序遍历,最先访问最后编译的分类。注意点:多个分类有同名函数时,最终执行哪个分类的函数,就看哪个分类最后参与编译。
        
        auto& entry = cats->list[i]; //获取一个分类

        method_list_t *mlist = entry.cat->methodsForMeta(isMeta);//获取该分类的方法列表,即 装有方法的一维数组
        if (mlist) {
            mlists[mcount++] = mlist; //方法列表添加到二维数组里
            fromBundle |= entry.hi->isBundle();
        }

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

        protocol_list_t *protolist = entry.cat->protocols;
        if (protolist) {
            protolists[protocount++] = protolist;
        }
    }

    auto rw = cls->data();//获取cls里的rw结构数据

    prepareMethodLists(cls, mlists, mcount, NO, fromBundle);//主要是针对分类中有关内存管理相关方法情况下 一些特殊处理
    rw->methods.attachLists(mlists, mcount); //此时mlists已经是一个装有所有分类的所有实例方法的二维数组。rw->methods(拿到类对象里的方法列表数组)然后去调用attachLists,此时attachLists的作用是将含有mcount个元素的mlists拼接到rw的methods上
    free(mlists);
    if (flush_caches  &&  mcount > 0) flushCaches(cls);

    rw->properties.attachLists(proplists, propcount);//这里是把所有category的属性列表拼成了一个大的属性列表数组,然后转交给了attachLists方法
    free(proplists);

    rw->protocols.attachLists(protolists, protocount);//这里是把所有category的协议列表拼成了一个大的协议列表数组,然后转交给了attachLists方法
    free(protolists);
}

点击attachLists看到如下代码

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

    if (hasArray()) {
        // 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后如下
         [[],[],[],[原有的第一个元素],[原有的第二个元素]]
         
         */
        memmove(array()->lists + addedCount, array()->lists,oldCount * sizeof(array()->lists[0])); // 以实例方法举例: array()->lists类对象原来的方法列表
        
        /*内存拷贝,假设原来类有两个元素,分类共有三个元素,memcpy后如下
        
         [
             [addedLists里的第一个元素],
             [addedLists里的第二个元素],
             [addedLists里的第三个元素],
             [原有的第一个元素],
             [原有的第二个元素]
         
         
         ]
         
         这也就是分类会“覆盖”宿主类的方法的原因
         */
        memcpy(array()->lists, addedLists,
               addedCount * sizeof(array()->lists[0]));// 以实例方法举例:addedLists所有分类的方法列表
        //扩展:
        /*memmove和memcpy都是C语言的库函数
         void    *memmove(void *__dst, const void *__src, size_t __len);
         void    *memcpy(void *__dst, const void *__src, size_t __n);
         这两个函数都是将__src指向位置的__len/__n字节数据拷贝到__dst指向的位置,区别就在于关键字restrict, memcpy假定两块内存区域没有数据重叠,而memmove没有这个前提条件。如果复制的两个区域存在重叠时使用memcpy,其结果是不可预知的,有可能成功也有可能失败的,所以如果使用了memcpy,程序员自身必须确保两块内存没有重叠部分。
         */
    }
    else if (!list  &&  addedCount == 1) {
        // 0 lists -> 1 list
        list = addedLists[0];
    }
    else {
        // 1 list -> many lists
        List* oldList = list;
        uint32_t oldCount = oldList ? 1 : 0;
        uint32_t newCount = oldCount + addedCount;
        setArray((array_t *)malloc(array_t::byteSize(newCount)));
        array()->count = newCount;
        if (oldList) array()->lists[addedCount] = oldList;
        memcpy(array()->lists, addedLists,
               addedCount * sizeof(array()->lists[0]));
    }
}

上面一大堆底层代码,大致过程就是分类在编译的时候通过Runtime被装载;把所有Category的方法合并到一个大数组、属性合并到一个大数组、协议合并到一个大数组,合并到一个大数组中,然后通过memmove将类原来数据后移分类数组的长度,然后通过memcpy函数将合并后的分类数据(方法、 属性、协议),插入到类原来数据的前面
由此可以看出,以前所说的分类有跟原本类同名也就是相同的方法时,会调用分类的方法是有道理的,因为分类方法被装载在前面,原本类的同名方法被排在了后面,而运行时在查找方法的时候是顺着方法列表的顺序查找的,它只要一找到对应名字的方法,就会罢休。如果两个分类有同样方法,先调哪个看编译顺序。

那么如何证明原本类相同的方法还在呢?
通过前几天写的那个运行时打印类的所有对象方法的函数,一验证就知道了,打印结果会有两个同名函数

//写一个运行时,用来打印一个类的对象方法
- (void)printMethods:(Class)cls //这个cls可以传元类,如果传元类,那么就打印类方法;传类对象就打印对象方法
{
    unsigned int count;
    Method *methods = class_copyMethodList(cls, &count);
    
    NSMutableString *methodNames = [NSMutableString string];
    [methodNames appendFormat:@"%@ - ", cls];
    
    for (int i = 0; i < count; i++) {
        Method method = methods[i];
        
        NSString *methodName = NSStringFromSelector(method_getName(method));
        [methodNames appendString:methodName];
        [methodNames appendString:@" "];
    }
    
    NSLog(@"%@", methodNames);
    
    free(methods);
}

通过上述方法还可以验证新建一个分类后,当程序运行时,方法就已经被加载到方法列表里了,只是不引入分类文件不把这个这个分类方法暴露出来而已
比如

#import <Foundation/Foundation.h>
@interface MJPerson : NSObject
@end

#import "MJPerson.h"
@implementation MJPerson
@end

新建MJPerson的分类

#import "MJPerson.h"
@interface MJPerson (Category)<NSCopying>
-(void)catEat;
@end

#import "MJPerson+Category.h"
@implementation MJPerson (Category)
-(void)catEat{
    NSLog(@"--Category-catEat--");
}
@end

然后我们在控制器里通过[self printMethods:[MJPerson class]];打印,不引入分类文件也可以看到打印出
2019-11-26 10:18:28.315420+0800 Test[2014:55852] MJPerson - catEat
只不过不引入#import "MJPerson+Category.h"文件,系统不把分类的catEat暴露出来。

现在接着来验证类和分类有同名函数,不引入分类头文件,调用同名函数问题。
#import <Foundation/Foundation.h>
@interface MJPerson : NSObject
-(void)catEat;
+(void)animalEat;
@end

#import "MJPerson.h"
@implementation MJPerson
+(void)animalEat{
    NSLog(@"--MJPerson-animalEat---");
}
-(void)catEat{
    NSLog(@"--MJPerson-catEat---");
}
@end

新建MJPerson的分类
#import "MJPerson.h"
@interface MJPerson (Category)<NSCopying>
@property (nonatomic , copy) NSString *myName;
@property (nonatomic , assign) int myNum;
-(void)catEat;
+(void)animalEat;
@end

#import "MJPerson+Category.h"
@implementation MJPerson (Category)
+(void)animalEat{
    NSLog(@"--Category-animalEat---");
}
-(void)catEat{
    NSLog(@"--Category-catEat---");
}
@end

然后再控制器里不引入分类头文件,调用下列方法
MJPerson *person = [[MJPerson alloc]init];
[person catEat];
[MJPerson animalEat];
可以看到打印的是分类的方法
2019-11-26 10:25:56.605294+0800 Test[2114:59950] --Category-catEat---
2019-11-26 10:25:56.605347+0800 Test[2114:59950] --Category-animalEat---

由此也确实的验证了分类在编译的时候通过Runtime被装载,无论引不引入分类文件,只有类和分类有同名方法,调用这个同名方法,分类的方法都会“覆盖”类的方法。只不过不引入分类头文件,在没有同名函数的时候,分类方法是存在的,但不被直接暴露调用而已。

load方法

+load方法会在程序刚启动时,通过runtime加载类、分类时调用(给类、分类啊写load方法,并不使用这个类和分类,但是在程序启动的时候仍然可以看到类、分类的load方法被调用,可以看出+load方法会在runtime加载类、分类时调用。load一般是系统自动调用自动处理)
例:

MJPerson.m文件里实现load

#import <Foundation/Foundation.h>
@interface MJPerson : NSObject
@end

#import "MJPerson.h"
@implementation MJPerson
+ (void)load {
    NSLog(@"--MJPerson-load--");
}
@end

新建MJStudent继承MJPerson,然后MJStudent.m文件里实现load

#import "MJPerson.h"
@interface MJStudent : MJPerson
@end

#import "MJStudent.h"
@implementation MJStudent
+ (void)load {
    NSLog(@"--MJStudent-load--");
}
@end

新建MJStudent的分类,然后MJStudent分类文件里实现load
#import "MJStudent.h"
@interface MJStudent (Category)
@end

#import "MJStudent+Category.h"
@implementation MJStudent (Category)
+ (void)load {
    NSLog(@"--MJStudent (Category)-load--");
}
@end

然后在没有任何地方引入MJPerson、MJStudent、MJStudent分类的情况下启动项目,可以看到控制台打印如下
2019-11-26 10:35:20.617192+0800 Test[2303:65508] --MJPerson-load--
2019-11-26 10:35:20.617619+0800 Test[2303:65508] --MJStudent-load--
2019-11-26 10:35:20.617679+0800 Test[2303:65508] --MJStudent (Category)-load--

因此,说+load方法会在程序刚启动时,通过runtime加载类、分类时调用(给类、分类啊写load方法,并不使用这个类和分类,但是在程序启动的时候仍然可以看到类、分类的load方法被调用,可以看出+load方法会在runtime加载类、分类时调用)

每个类、分类的+load,在程序运行过程中只调用一次

load的调用顺序? (无论是类分类子类子类分类所有load方法只要重写都会打印)
1> 先调用类的load (如有继承,也是先调用父类即子类的load)
a) 先编译的类,优先调用load
b) 调用子类的load之前,会先调用父类的load

2> 再调用分类的load
a) 先编译的分类,优先调用load

通过代码可以验证书面结论
2019-11-26 10:43:21.962804+0800 Test[2458:71004] --MJPerson-load--
2019-11-26 10:43:21.963178+0800 Test[2458:71004] --MJStudent-load--
2019-11-26 10:43:21.963229+0800 Test[2458:71004] --MJStudent (Category1)-load--    //MJStudent的第二个分类
2019-11-26 10:43:21.963314+0800 Test[2458:71004] --MJStudent (Category)-load--     //MJStudent的第一个分类
2019-11-26 10:43:21.963376+0800 Test[2458:71004] --MJPerson (Category)-load--

initialize方法

initialize是类第一次接收到消息的时候(第一次使用这个类)调用,每一个类只会initialize一次
例:

#import <Foundation/Foundation.h>
@interface MJPerson : NSObject
@end

#import "MJPerson.h"
@implementation MJPerson
+(void)initialize {
    NSLog(@"--MJPerson-initialize--");
}
@end

然后在MJPerson的子类MJStudent以及分类都如上实现+(void)initialize方法。
然后运行程序,不使用类、分类,可以看到控制台没有打印。

但是当我们初始化或者调用类的方法时

MJPerson *p = [[MJPerson alloc]init];
就可以看到打印
2019-11-26 11:06:34.351296+0800 Test[3018:90114] --MJPerson (Category)-initialize--

然后我们初始化子类
MJStudent *p = [[MJStudent alloc]init];
就可以看到打印
2019-11-26 11:07:21.767492+0800 Test[3047:91570] --MJPerson (Category)-initialize--
2019-11-26 11:07:21.767591+0800 Test[3047:91570] --MJStudent (Category)-initialize--

因此可以得出下面initialize调用顺序结论

initialize调用顺序 (如果分类重写initialize方法会“覆盖”类的initialize)
1> 先初始化父类或者父类分类 (尽管只初始化子类,如果有继承,也是先调用父类或者父类分类的initialize)
2> 再初始化子类以及子类分类

话外:类,分类、子类、子类分类里都有+(void)animalRun;类方法时,调用子类的animalRun,只会打印子类分类的animalRun类方法。是因为分类会覆盖类,子类会覆盖父类。但是这个initialize是比较特殊的。

那为什么initialize方法会“覆盖”类的initialize,而load方法不会覆盖呢

是因为initialize是通过objc_msgSend调用(objc_msgSend消息发送机制本质就是通过ISA指针找对象),而load是根据类的函数地址直接调用(底层源码可以论证这些问题)

load、initialize方法的区别什么?
1.调用方式
1> load是根据函数地址直接调用
2> initialize是通过objc_msgSend调用 (objc_msgSend消息发送机制本质就是通过ISA指针找对象)

2.调用时刻
1> load是runtime加载类、分类的时候调用(只会调用1次)
2> initialize是类第一次接收到消息的时候调用,每一个类只会initialize一次

通过load和initialize学习,回答下面两道题目。

1.Category中有load方法吗?load方法是什么时候调用的?load方法能继承吗?
有load方法
load方法在runtime加载类、分类的时候调用
load方法可以继承,但是一般情况下不会主动去调用load方法,都是让系统自动调用

2.load、initialize方法的区别什么?它们在category中的调用的顺序?以及出现继承时他们之间的调用过程?

链接静态库有些方法找不到时
other linker flags设置成 -all_load

类扩展也叫匿名分类,跟分类没有关系。里面用来私有一些东西。
匿名分类(类扩展)的方法都是私有,那为什么还要写声明呢,直接写实现不行吗?
一般不用写的,有些写下声明是为了规范

相关文章

  • iOS分类的实现原理简记

    该文为分类原理的简单记录,总结自如下文章,感谢作者分享: iOS底层原理总结 iOS分类底层实现原理小记 1、分类...

  • iOS基础总结理解及相关面试题

    iOS基础 分类(Category)和类扩展(Class Extension)分类(Category)Catego...

  • iOS 分类和扩展

    iOS 分类和扩展 分类 Category 分类(Category)是OC中的特有语法,它是表示一个指向分类的结构...

  • ios 消息提醒

    文章纲要: 1. iOS app外消息提醒分类 2. iOS app 内消息提醒分类 正文: 消息提醒基本是每个a...

  • oc 分类(category)相关

    标签:ios开发入门 添加分类的格式: @interface HSHAnimal (HSHDog) 分类的好处: ...

  • iOS开发——OC篇&OC高级语法

    iOS开发高级语法之分类,拓展,协议,代码块详解 一:分类 什么是分类Category? 分类就是类的补充和扩展部...

  • 分类Category、扩展Extension 分析

    前言 引用 iOS分类(category),类扩展(extension)—史上最全攻略 分类 Category 概...

  • iOS - 分类

    [toc] 参考 category OC转C++ Category 的底层源码 objc所有类和对象都是c结构体(...

  • iOS分类

    分类 Category 概念:给一个对象提供一些方法声明跟实现不可以给改对象添加成员变量 可以添加@propert...

  • iOS分类

网友评论

      本文标题:iOS 分类

      本文链接:https://www.haomeiwen.com/subject/vpwmzktx.html