iOS底层原理总结 - Category的本质

iOS中Category的底层实现原理 1. Category的使用场景 Category也叫分类或类别,是OC提供的一种扩展类的方式。不管是自定义的类还是系统的类,我们都可以通过Category给原有类扩展方法(实例方法和类方法都可以),而且扩展的方法和原有的方法的调用方式是一模一样的。比如我项目中经常需要统计一个字符串中字母的个数,但是系统没有提供这个方法,那我们就可以用Category给NSString类扩展一个方法,然后只需引... 阅读详情

面试题

Category的实现原理,以及Category为什么只能加方法不能加属性。
Category中有load方法吗?load方法是什么时候调用的?load 方法能继承吗?
load、initialize的区别,以及它们在category重写的时候的调用的次序

Category的本质

首先我们写一段简单的代码,之后的分析都基于这段代码。

Presen类 
// Presen.h
#import <Foundation/Foundation.h>
@interface Preson : NSObject
{
    int _age;
}
- (void)run;
@end

// Presen.m
#import "Preson.h"
@implementation Preson
- (void)run
{
    NSLog(@"Person - run");
}
@end

Presen扩展1
// Presen+Test.h
#import "Preson.h"
@interface Preson (Test) <NSCopying>
- (void)test;
+ (void)abc;
@property (assign, nonatomic) int age;
- (void)setAge:(int)age;
- (int)age;
@end

// Presen+Test.m
#import "Preson+Test.h"
@implementation Preson (Test)
- (void)test
{
}

+ (void)abc
{
}
- (void)setAge:(int)age
{
}
- (int)age
{
    return 10;
}
@end

Presen分类2
// Preson+Test2.h
#import "Preson.h"
@interface Preson (Test2)
@end

// Preson+Test2.m
#import "Preson+Test2.h"
@implementation Preson (Test2)
- (void)run
{
    NSLog(@"Person (Test2) - run");
}
@end

我们之前讲到过实例对象的isa指针指向类对象,类对象的isa指针指向元类对象,当p调用run方法时,类对象的isa指针找到类对象的isa指针,然后在类对象中查找对象方法,如果没有找到,就通过类对象的superclass指针找到父类对象,接着去寻找run方法。
那么当调用分类的方法时,步骤是否和调用对象方法一样呢?
分类中的对象方法依然是存储在类对象中的,同对象方法在同一个地方,那么调用步骤也同调用对象方法一样。如果是类方法的话,也同样是存储在元类对象中。
那么分类方法是如何存储在类对象中的,我们来通过源码看一下分类的底层结构。

分类的底层结构

如何验证上述问题?通过查看分类的源码我们可以找到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);
};

从源码基本可以看出我们平时使用categroy的方式,对象方法,类方法,协议,和属性都可以找到对应的存储方式。并且我们发现分类结构体中是不存在成员变量的,因此分类中是不允许添加成员变量的。分类中添加的属性并不会帮助我们自动生成成员变量,只会生成get set方法的声明,需要我们自己去实现。
通过源码我们发现,分类的方法,协议,属性等好像确实是存放在categroy结构体里面的,那么他又是如何存储在类对象中的呢?
我们来看一下底层的内部方法探寻其中的原理。
首先我们通过命令行将Preson+Test.m文件转化为c++文件,查看其中的编译过程。

xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc Preson+Test.m

在分类转化为c++文件中可以看出_category_t结构体中,存放着类名,对象方法列表,类方法列表,协议列表,以及属性列表。
这里写图片描述

紧接着,我们可以看到_method_list_t类型的结构体,如下图所示
这里写图片描述

紧接着,我们可以看到_method_list_t类型的结构体,如下图所示
这里写图片描述

上图中我们发现这个结构体_OBJC_$_CATEGORY_INSTANCE_METHODS_Preson_$_Test从名称可以看出是INSTANCE_METHODS对象方法,并且一一对应为上面结构体内赋值。我们可以看到结构体中存储了方法占用的内存,方法数量,以及方法列表。并且从上图中找到分类中我们实现对应的对象方法,test , setAge, age三个方法
接下来我们发现同样的_method_list_t类型的类方法结构体,如下图所示
这里写图片描述

同上面对象方法列表一样,这个我们可以看出是类方法列表结构体 _OBJC_$_CATEGORY_CLASS_METHODS_Preson_$_Test,同对象方法结构体相同,同样可以看到我们实现的类方法,abc。

接下来是协议方法列表
这里写图片描述
通过上述源码可以看到先将协议方法通过_method_list_t结构体存储,之后通过_protocol_t结构体存储在_OBJC_CATEGORY_PROTOCOLS_$_Preson_$_Test中同_protocol_list_t结构体一一对应,分别为protocol_count 协议数量以及存储了协议方法的_protocol_t结构体。
最后我们可以看到属性列表
这里写图片描述

属性列表结构体_OBJC_$_PROP_LIST_Preson_$_Test同_prop_list_t结构体对应,存储属性的占用空间,属性属性数量,以及属性列表,从上图中可以看到我们自己写的age属性。
最后我们可以看到定义了OBJC_CATEGORY_Preson__CATEGORY_Preson__Test结构体,并且将我们上面着重分析的结构体一一赋值,我们通过两张图片对照一下。
这里写图片描述

这里写图片描述

上下两张图一一对应,并且我们看到定义class_t类型的OBJC_CLASS_Preson结构体,最后将_OBJC__Preson结构体,最后将_OBJC_CATEGORY_Preson_Test的cls指针指向OBJC_CLASS__Test的cls指针指向OBJC_CLASS__Preson结构体地址。我们这里可以看出,cls指针指向的应该是分类的主类类对象的地址。
通过以上分析我们发现。分类源码中确实是将我们定义的对象方法,类方法,属性等都存放在catagory_t结构体中。接下来我们在回到runtime源码查看catagory_t存储的方法,属性,协议等是如何存储在类对象中的。
首先来到runtime初始化函数
这里写图片描述

接着我们来到 &map_images读取模块(images这里代表模块),来到map_images_nolock函数中找到_read_images函数,在_read_images函数中我们找到分类相关代码
这里写图片描述

从上述代码中我们可以知道这段代码是用来查找有没有分类的。通过_getObjc2CategoryList函数获取到分类列表之后,进行遍历,获取其中的方法,协议,属性等。可以看到最终都调用了remethodizeClass(cls);函数。我们来到remethodizeClass(cls);函数内部查看。
这里写图片描述
通过上述代码我们发现attachCategories函数接收了类对象cls和分类数组cats,如我们一开始写的代码所示,一个类可以有多个分类。之前我们说到分类信息存储在category_t结构体中,那么多个分类则保存在category_list中。
我们来到attachCategories函数内部

这里写图片描述

上述源码中可以看出,首先根据方法列表,属性列表,协议列表,malloc分配内存,根据多少个分类以及每一块方法需要多少内存来分配相应的内存地址。之后从分类数组里面往三个数组里面存放分类数组里面存放的分类方法,属性以及协议放入对应mlist、proplists、protolosts数组中,这三个数组放着所有分类的方法,属性和协议。
之后通过类对象的data()方法,拿到类对象的class_rw_t结构体rw,在class结构中我们介绍过,class_rw_t中存放着类对象的方法,属性和协议等数据,rw结构体通过类对象的data方法获取,所以rw里面存放这类对象里面的数据。
之后分别通过rw调用方法列表、属性列表、协议列表的attachList函数,将所有的分类的方法、属性、协议列表数组传进去,我们大致可以猜想到在attachList方法内部将分类和本类相应的对象方法,属性,和协议进行了合并。
我们来看一下attachLists函数内部。
这里写图片描述
上述源代码中有两个重要的数组
array()->lists: 类对象原来的方法列表,属性列表,协议列表。
addedLists:传入所有分类的方法列表,属性列表,协议列表。
attachLists函数中最重要的两个方法为memmove内存移动和memcpy内存拷贝。我们先来分别看一下这两个函数

// memmove :内存移动。
/*  __dst : 移动内存的目的地
*   __src : 被移动的内存首地址
*   __len : 被移动的内存长度
*   将__src的内存移动__len块内存到__dst中
*/
void    *memmove(void *__dst, const void *__src, size_t __len);

// memcpy :内存拷贝。
/*  __dst : 拷贝内存的拷贝目的地
*   __src : 被拷贝的内存首地址
*   __n : 被移动的内存长度
*   将__src的内存拷贝__n块内存到__dst中
*/
void    *memcpy(void *__dst, const void *__src, size_t __n);

下面我们图示经过memmove和memcpy方法过后的内存变化。
这里写图片描述
经过memmove方法之后,内存变化为

// array()->lists 原来方法、属性、协议列表数组
// addedCount 分类数组长度
// oldCount * sizeof(array()->lists[0]) 原来数组占据的空间
memmove(array()->lists + addedCount, array()->lists, 
                  oldCount * sizeof(array()->lists[0]));

这里写图片描述
经过memmove方法之后,我们发现,虽然本类的方法,属性,协议列表会分别后移,但是本类的对应数组的指针依然指向原始位置。

memcpy方法之后,内存变化

// array()->lists 原来方法、属性、协议列表数组
// addedLists 分类方法、属性、协议列表数组
// addedCount * sizeof(array()->lists[0]) 原来数组占据的空间
memcpy(array()->lists, addedLists, 
               addedCount * sizeof(array()->lists[0]));

这里写图片描述

我们发现原来指针并没有改变,至始至终指向开头的位置。并且经过memmove和memcpy方法之后,分类的方法,属性,协议列表被放在了类对象中原本存储的方法,属性,协议列表前面。
那么为什么要将分类方法的列表追加到本来的对象方法前面呢,这样做的目的是为了保证分类方法优先调用,我们知道当分类重写本类的方法时,会覆盖本类的方法。
其实经过上面的分析我们知道本质上并不是覆盖,而是优先调用。本类的方法依然在内存中的。我们可以通过打印所有类的所有方法名来查看

- (void)printMethodNamesOfClass:(Class)cls
{
    unsigned int count;
    // 获得方法数组
    Method *methodList = class_copyMethodList(cls, &count);
    // 存储方法名
    NSMutableString *methodNames = [NSMutableString string];
    // 遍历所有的方法
    for (int i = 0; i < count; i++) {
        // 获得方法
        Method method = methodList[i];
        // 获得方法名
        NSString *methodName = NSStringFromSelector(method_getName(method));
        // 拼接方法名
        [methodNames appendString:methodName];
        [methodNames appendString:@", "];
    }
    // 释放
    free(methodList);
    // 打印方法名
    NSLog(@"%@ - %@", cls, methodNames);
}
- (void)viewDidLoad {
    [super viewDidLoad];    
    Preson *p = [[Preson alloc] init];
    [p run];
    [self printMethodNamesOfClass:[Preson class]];
}

通过下图中打印内容可以发现,调用的是Test2中的run方法,并且Person类中存储着两个run方法。
这里写图片描述

总结:

问: Category的实现原理,以及Category为什么只能加方法不能加属性?
答:分类的实现原理是将category中的方法,属性,协议数据放在category_t结构体中,然后将结构体内的方法列表拷贝到类对象的方法列表中。
Category可以添加属性,但是并不会自动生成成员变量及set/get方法。因为category_t结构体中并不存在成员变量。通过之前对对象的分析我们知道成员变量是存放在实例对象中的,并且编译的那一刻就已经决定好了。而分类是在运行时才去加载的。那么我们就无法再程序运行时将分类的成员变量中添加到实例对象的结构体中。因此分类中不可以添加成员变量。
load 和 initialize
load方法会在程序启动就会调用,当装载类信息的时候就会调用。
调用顺序看一下源代码。
这里写图片描述

通过源码我们发现是优先调用类的load方法,之后调用分类的load方法。
我们通过代码验证一下: 我们添加Student继承Presen类,并添加Student+Test分类,分别重写只+load方法,其他什么都不做通过打印发现

这里写图片描述
确实是优先调用类的load方法之后调用分类的load方法,不过调用类的load方法之前会保证其父类已经调用过load方法。
之后我们为Preson、Student 、Student+Test 添加initialize方法。
我们知道当类第一次接收到消息时,就会调用initialize,相当于第一次使用类的时候就会调用initialize方法。调用子类的initialize之前,会先保证调用父类的initialize方法。如果之前已经调用过initialize,就不会再调用initialize方法了。当分类重写initialize方法时会先调用分类的方法。但是load方法并不会被覆盖,首先我们来看一下initialize的源码。
这里写图片描述
上图中我们发现,initialize是通过消息发送机制调用的,消息发送机制通过isa指针找到对应的方法与实现,因此先找到分类方法中的实现,会优先调用分类方法中的实现。

我们再来看一下load方法的调用源码
这里写图片描述
我们看到load方法中直接拿到load方法的内存地址直接调用方法,不在是通过消息发送机制调用。

这里写图片描述
我们可以看到分类中也是通过直接拿到load方法的地址进行调用。因此正如我们之前试验的一样,分类中重写load方法,并不会优先调用分类的load方法,而不调用本类中的load方法了。

总结

问:Category中有load方法吗?load方法是什么时候调用的?load 方法能继承吗?
答:Category中有load方法,load方法在程序启动装载类信息的时候就会调用。load方法可以继承。调用子类的load方法之前,会先调用父类的load方法
问:load、initialize的区别,以及它们在category重写的时候的调用的次序。
答:区别在于调用方式和调用时刻
调用方式:load是根据函数地址直接调用,initialize是通过objc_msgSend调用
调用时刻:load是runtime加载类、分类的时候调用(只会调用1次),initialize是类第一次接收到消息的时候调用,每一个类只会initialize一次(父类的initialize方法可能会被调用多次)
调用顺序:先调用类的load方法,先编译那个类,就先调用load。在调用load之前会先调用父类的load方法。分类中load方法不会覆盖本类的load方法,先编译的分类优先调用load方法。initialize先初始化父类,之后再初始化子类。如果子类没有实现+initialize,会调用父类的+initialize(所以父类的+initialize可能会被调用多次),如果分类实现了+initialize,就覆盖类本身的+initialize调用。
这里写图片描述

上图中我们发现这个结构体_OBJC_$_CATEGORY_INSTANCE_METHODS_Preson_$_Test从名称可以看出是INSTANCE_METHODS对象方法,并且一一对应为上面结构体内赋值。我们可以看到结构体中存储了方法占用的内存,方法数量,以及方法列表。并且从上图中找到分类中我们实现对应的对象方法,test , setAge, age三个方法
接下来我们发现同样的_method_list_t类型的类方法结构体,如下图所示
这里写图片描述

同上面对象方法列表一样,这个我们可以看出是类方法列表结构体 _OBJC_$_CATEGORY_CLASS_METHODS_Preson_$_Test,同对象方法结构体相同,同样可以看到我们实现的类方法,abc。

接下来是协议方法列表
这里写图片描述

通过上述源码可以看到先将协议方法通过_method_list_t结构体存储,之后通过_protocol_t结构体存储在_OBJC_CATEGORY_PROTOCOLS_$_Preson_$_Test中同_protocol_list_t结构体一一对应,分别为protocol_count 协议数量以及存储了协议方法的_protocol_t结构体。
最后我们可以看到属性列表
这里写图片描述

属性列表结构体_OBJC_$_PROP_LIST_Preson_$_Test同_prop_list_t结构体对应,存储属性的占用空间,属性属性数量,以及属性列表,从上图中可以看到我们自己写的age属性。
最后我们可以看到定义了OBJC_CATEGORY_Preson__CATEGORY_Preson__Test结构体,并且将我们上面着重分析的结构体一一赋值,我们通过两张图片对照一下。
这里写图片描述

这里写图片描述
上下两张图一一对应,并且我们看到定义class_t类型的OBJC_CLASS_Preson结构体,最后将_OBJC__Preson结构体,最后将_OBJC_CATEGORY_Preson_Test的cls指针指向OBJC_CLASS__Test的cls指针指向OBJC_CLASS__Preson结构体地址。我们这里可以看出,cls指针指向的应该是分类的主类类对象的地址。
通过以上分析我们发现。分类源码中确实是将我们定义的对象方法,类方法,属性等都存放在catagory_t结构体中。接下来我们在回到runtime源码查看catagory_t存储的方法,属性,协议等是如何存储在类对象中的。
首先来到runtime初始化函数
这里写图片描述

接着我们来到 &map_images读取模块(images这里代表模块),来到map_images_nolock函数中找到_read_images函数,在_read_images函数中我们找到分类相关代码

这里写图片描述

从上述代码中我们可以知道这段代码是用来查找有没有分类的。通过_getObjc2CategoryList函数获取到分类列表之后,进行遍历,获取其中的方法,协议,属性等。可以看到最终都调用了remethodizeClass(cls);函数。我们来到remethodizeClass(cls);函数内部查看。
这里写图片描述
通过上述代码我们发现attachCategories函数接收了类对象cls和分类数组cats,如我们一开始写的代码所示,一个类可以有多个分类。之前我们说到分类信息存储在category_t结构体中,那么多个分类则保存在category_list中。
我们来到attachCategories函数内部

这里写图片描述

上述源码中可以看出,首先根据方法列表,属性列表,协议列表,malloc分配内存,根据多少个分类以及每一块方法需要多少内存来分配相应的内存地址。之后从分类数组里面往三个数组里面存放分类数组里面存放的分类方法,属性以及协议放入对应mlist、proplists、protolosts数组中,这三个数组放着所有分类的方法,属性和协议。
之后通过类对象的data()方法,拿到类对象的class_rw_t结构体rw,在class结构中我们介绍过,class_rw_t中存放着类对象的方法,属性和协议等数据,rw结构体通过类对象的data方法获取,所以rw里面存放这类对象里面的数据。
之后分别通过rw调用方法列表、属性列表、协议列表的attachList函数,将所有的分类的方法、属性、协议列表数组传进去,我们大致可以猜想到在attachList方法内部将分类和本类相应的对象方法,属性,和协议进行了合并。
我们来看一下attachLists函数内部。
这里写图片描述
上述源代码中有两个重要的数组
array()->lists: 类对象原来的方法列表,属性列表,协议列表。
addedLists:传入所有分类的方法列表,属性列表,协议列表。
attachLists函数中最重要的两个方法为memmove内存移动和memcpy内存拷贝。我们先来分别看一下这两个函数

// memmove :内存移动。
/*  __dst : 移动内存的目的地
*   __src : 被移动的内存首地址
*   __len : 被移动的内存长度
*   将__src的内存移动__len块内存到__dst中
*/
void    *memmove(void *__dst, const void *__src, size_t __len);

// memcpy :内存拷贝。
/*  __dst : 拷贝内存的拷贝目的地
*   __src : 被拷贝的内存首地址
*   __n : 被移动的内存长度
*   将__src的内存拷贝__n块内存到__dst中
*/
void    *memcpy(void *__dst, const void *__src, size_t __n);

下面我们图示经过memmove和memcpy方法过后的内存变化。
这里写图片描述
经过memmove方法之后,内存变化为

objective-c
// array()->lists 原来方法、属性、协议列表数组
// addedCount 分类数组长度
// oldCount * sizeof(array()->lists[0]) 原来数组占据的空间
memmove(array()->lists + addedCount, array()->lists, 
                  oldCount * sizeof(array()->lists[0]));

这里写图片描述

经过memmove方法之后,我们发现,虽然本类的方法,属性,协议列表会分别后移,但是本类的对应数组的指针依然指向原始位置。

memcpy方法之后,内存变化

// array()->lists 原来方法、属性、协议列表数组
// addedLists 分类方法、属性、协议列表数组
// addedCount * sizeof(array()->lists[0]) 原来数组占据的空间
memcpy(array()->lists, addedLists, 
               addedCount * sizeof(array()->lists[0]));

这里写图片描述

我们发现原来指针并没有改变,至始至终指向开头的位置。并且经过memmove和memcpy方法之后,分类的方法,属性,协议列表被放在了类对象中原本存储的方法,属性,协议列表前面。
那么为什么要将分类方法的列表追加到本来的对象方法前面呢,这样做的目的是为了保证分类方法优先调用,我们知道当分类重写本类的方法时,会覆盖本类的方法。
其实经过上面的分析我们知道本质上并不是覆盖,而是优先调用。本类的方法依然在内存中的。我们可以通过打印所有类的所有方法名来查看

- (void)printMethodNamesOfClass:(Class)cls
{
    unsigned int count;
    // 获得方法数组
    Method *methodList = class_copyMethodList(cls, &count);
    // 存储方法名
    NSMutableString *methodNames = [NSMutableString string];
    // 遍历所有的方法
    for (int i = 0; i < count; i++) {
        // 获得方法
        Method method = methodList[i];
        // 获得方法名
        NSString *methodName = NSStringFromSelector(method_getName(method));
        // 拼接方法名
        [methodNames appendString:methodName];
        [methodNames appendString:@", "];
    }
    // 释放
    free(methodList);
    // 打印方法名
    NSLog(@"%@ - %@", cls, methodNames);
}
- (void)viewDidLoad {
    [super viewDidLoad];    
    Preson *p = [[Preson alloc] init];
    [p run];
    [self printMethodNamesOfClass:[Preson class]];
}

通过下图中打印内容可以发现,调用的是Test2中的run方法,并且Person类中存储着两个run方法。
这里写图片描述

总结:

问: Category的实现原理,以及Category为什么只能加方法不能加属性?
答:分类的实现原理是将category中的方法,属性,协议数据放在category_t结构体中,然后将结构体内的方法列表拷贝到类对象的方法列表中。
Category可以添加属性,但是并不会自动生成成员变量及set/get方法。因为category_t结构体中并不存在成员变量。通过之前对对象的分析我们知道成员变量是存放在实例对象中的,并且编译的那一刻就已经决定好了。而分类是在运行时才去加载的。那么我们就无法再程序运行时将分类的成员变量中添加到实例对象的结构体中。因此分类中不可以添加成员变量。
load 和 initialize
load方法会在程序启动就会调用,当装载类信息的时候就会调用。
调用顺序看一下源代码。
这里写图片描述

通过源码我们发现是优先调用类的load方法,之后调用分类的load方法。
我们通过代码验证一下: 我们添加Student继承Presen类,并添加Student+Test分类,分别重写只+load方法,其他什么都不做通过打印发现
这里写图片描述

确实是优先调用类的load方法之后调用分类的load方法,不过调用类的load方法之前会保证其父类已经调用过load方法。
之后我们为Preson、Student 、Student+Test 添加initialize方法。
我们知道当类第一次接收到消息时,就会调用initialize,相当于第一次使用类的时候就会调用initialize方法。调用子类的initialize之前,会先保证调用父类的initialize方法。如果之前已经调用过initialize,就不会再调用initialize方法了。当分类重写initialize方法时会先调用分类的方法。但是load方法并不会被覆盖,首先我们来看一下initialize的源码。
这里写图片描述
上图中我们发现,initialize是通过消息发送机制调用的,消息发送机制通过isa指针找到对应的方法与实现,因此先找到分类方法中的实现,会优先调用分类方法中的实现。

我们再来看一下load方法的调用源码
这里写图片描述

我们看到load方法中直接拿到load方法的内存地址直接调用方法,不在是通过消息发送机制调用。
这里写图片描述
我们可以看到分类中也是通过直接拿到load方法的地址进行调用。因此正如我们之前试验的一样,分类中重写load方法,并不会优先调用分类的load方法,而不调用本类中的load方法了。

总结

问:Category中有load方法吗?load方法是什么时候调用的?load 方法能继承吗?
答:Category中有load方法,load方法在程序启动装载类信息的时候就会调用。load方法可以继承。调用子类的load方法之前,会先调用父类的load方法
问:load、initialize的区别,以及它们在category重写的时候的调用的次序。
答:区别在于调用方式和调用时刻
调用方式:load是根据函数地址直接调用,initialize是通过objc_msgSend调用
调用时刻:load是runtime加载类、分类的时候调用(只会调用1次),initialize是类第一次接收到消息的时候调用,每一个类只会initialize一次(父类的initialize方法可能会被调用多次)
调用顺序:先调用类的load方法,先编译那个类,就先调用load。在调用load之前会先调用父类的load方法。分类中load方法不会覆盖本类的load方法,先编译的分类优先调用load方法。initialize先初始化父类,之后再初始化子类。如果子类没有实现+initialize,会调用父类的+initialize(所以父类的+initialize可能会被调用多次),如果分类实现了+initialize,就覆盖类本身的+initialize调用。

iOS - Category本质 iOS 分类原理,应用 阅读详情

相关推荐

iOS之深入解析类加载的底层原理:分类如何加载到类以及分类和类的配合使用

一、分类的本质 ① Xcode Documentation 通过 Xcode 文档搜索,在 Documentation 搜索 Category 关键字: 点击 Category ,如下: ② 通过 objc 源码搜索 category_t ③ 通过 clang 在 main 中定义 YDWperson 的分类 DW: @interface YDWPerson (DW) @property (nonatomic, copy) NSString *cate_name; @prop

╰つ栺尖篴夢ゞ 1万+

详解Runtime运行时机制

简介 Runtime 又叫运行时,是一套底层的 C 语言 API,其为 iOS 内部的核心之一,我们平时编写的 OC 代码,底层都是基于它来实现的。比如: [receiver message]; // 底层运行时会被编译器转化为: objc_msgSend(receiver, selector) // 如果其还有参数比如: [receiver message:(id)arg...]; //

追梦 2149

ai获客系统功能代码

AI 获客系统是一个复杂的体系,以下为你提供一个简化的 Python 示例,聚焦于客户行为分析与销售线索筛选功能,模拟实现从多渠道收集数据后的初步处理流程。假设已有从官网、线上商城等渠道收集到的客户数据,以字典列表形式存储,每个字典包含客户基本信息及购买行为数据。通过分析客户购买频率与金额,筛选出高价值销售线索。python上述代码仅为基础示例,实际 AI 获客系统还涉及更复杂的数据采集、清洗、存储,以及运用机器学习算法进行深度客户洞察等功能。

weixin_ggwwsscc的博客 892

深入理解Objective-C:Category

摘要 无论一个类设计的多么完美,在未来的需求演进中,都有可能会碰到一些无法预测的情况。那怎么扩展已有的类呢?一般而言,继承和组合是不错的选择。但是在Objective-C 2.0中,又提供了category这个语言特性,可以动态地为已有类添加新行为。如今category已经遍布于Objective-C代码的各个角落,从Apple官方的framework到各个开源框架,从功能繁复的大...

美团技术团队 959

从加权最小二乘(WLS)到IAA:手把手推导DOA估计中的迭代自适应核心公式

本文深入解析了从加权最小二乘(WLS)到迭代自适应算法(IAA)在DOA估计中的数学推导与应用。通过动态调整加权矩阵,IAA在低快拍数、相干源等复杂场景中展现出卓越性能,显著提升角度分辨率和检测概率。文章详细介绍了算法核心步骤、工程优化策略及前沿发展,为雷达探测和无线通信领域提供重要参考。

weixin_30847865的博客 287

详解一典型的反激式开关电源方案

如图,在某系统方案上看到下图所示的单端反激式开关电源方案。

alala120的博客 1万+

ios app的分类与本质,感想

app的主要功能是数据的查询和展示,几乎没有人把app当服务器来用的。首先苹果对苹果手机网络的定义是不太稳定的网络,app不能严重依赖网络,其次手机大多采用的是arm架构的芯片,不追求最强大,最全面,追求的够用就可以。所以从网络和硬件方面决定了app不适合当服务器,并且现在的阿里云服务基本是免费的,并非要你买昂贵的服务器。既然明白了它的主要功能就可以把它进一步具体化,过于抽象的东东我不喜欢,没办法立

jia12216的专栏 1780

ZedBoard+Vivado(二)——PS + PL实现流水灯

硬件:Zedboard 软件:Vivado2018.2 + Win10 本文参考了http://blog.chinaaet.com/cuter521/p/35978的设计 1 设计 功能:PS + PL流水灯 语言:C 流程:创建工程-&amp;gt;IP Integrator-&amp;gt;综合、实现、生成Bitstream-&amp;gt;SDK 2 流程 2.1 创建工程 跟上一篇步骤完全相同,这里不用添加源文...

q416524389的博客 3695

iOS OC 底层原理之 category、load、initialize

不能直接添加成员变量,但能通过runtime间接添加。property在category中只生成setter和getter方法声明。

xymspace 1767

PHP 网页游戏开发入门教程

我们还链接了一个名为 “game.js” 的 JavaScript 文件,稍后将在其中编写游戏逻辑。这只是一个简单的示例,但它演示了如何使用 PHP 和 JavaScript 开发一个基本的网页游戏。以下是一个示例游戏的源代码和解释。现在,您可以在浏览器中打开 “game.php” 文件,即可看到一个带有红色游戏角色的游戏板。在上述代码中,我们使用 JavaScript 创建了一个游戏角色元素,并将其添加到游戏板容器中。最后,我们需要将游戏元素的样式设置为绝对定位,以便能够正确地控制它们的位置。

KkowServer的博客 678

一桶令牌的时间账本:四类限流算法的边界推演与Guava RateLimiter源码精读

限流的难点不在背出算法名,而在算清每一笔账:按哪一秒计数、超额请求排队还是拒绝、空闲后能否突发、集群总量如何约束。本文在可复现的虚拟时间实验台上对比固定窗口、滑动窗口、漏桶与令牌桶,实测边界双倍突发与分桶滑动的真实上界,拆解Nginx limit_req的千分位记账与延迟机制,纠正Resilience4j常被误称漏桶的说法,并逐行精读Guava SmoothRateLimiter的预消费、懒惰补账与预热积分,复刻迷你限流器与其完成七万次零差异对账,最后给出集群配额、分层防护与参数确定的方法。

曾经“等你生日那天”都遥远得像未来,如今却可欢愉的挥手说“下个十年见” 86万+

iOS 开发:『Runtime』详解(三)Category 底层原理

本文首发于我的个人博客:『不羁阁』 文章链接:传送门 本文更新时间:2019年07月24日20:15:36 本文用来介绍 iOS 开发中『Runtime』中的 Category 底层原理。通过本文,您将了解到: Category (分类)简介 Category 的实质 Category 的加载过程 Category(分类)和 Class(类)的 +lo...

程序员充电站(itcharge) 833

蓝桥杯第七届省赛试题--“模拟风扇控制系统”

题目要求 功能简述 “模拟风扇控制系统”能够模拟电风扇工作,通过按键控制风扇的转动速度和定时时间,数码管实时显示风扇的工作模式,动态倒计时显示剩余的定时时间,系统主要由数码管显示、单片机最小系统、按键输入和电机控制保护电路组成,系统框图如图 1 所示: 单总线驱动程序、CT107D 单片机考试平台电路原理图以及本题所涉及到的芯片数据手册,可参考计算机上的电子文档。程序流程图及相关工程文件请以考生...

qq_43280087的博客 2659

iOS Category的本质(一)

Category在平时的工作中也是经常用到,是开发中不可缺少的一个利器,简单介绍。 优点: 不需要通过增加子类而增加现有类的行为(方法),且分类中的方法与原始类方法基本没有区别;在日常开发中通过分类可以将庞大一个类的方法按照功能模块进行划分,从而便于代码的日后的维护、更新以及提高代码的阅读性 缺点: 1.无法向类目添加实例变量,如果需要添加实例变量,只能通过定义子类的方式。 2.类目中的方法与原始...

muziyuting的博客 370

【华为OD机试真题E卷】474、推荐多样性 | 机试真题+思路参考+代码解析(E卷复用)(C++、Java、Py)

题目描述 >推荐多样性需要从多个列表中选择元素,一次性要返回 N 屏数据(窗口数量),每屏展示 K 个元素(窗口大小),选择策略:   各个列表元素需要做穿插处理,即先从第一个列表中为每屏选择一个元素,再从第二个列表中为每屏选择一个元素,依次类推   每个列表的元素尽量均分为 N 份,如果不够 N 个,也要全部分配完,参考样例图:

KJ.JK 4434

SAP B1中文培訓教材_SDK

公司内部培训资料,对开发工具 ,DIAPI, UIAPI,Add-ON有详细解说

Category的本质

iOS底层原理总结 - Category的本质 面试题 Category的实现原理,以及Category为什么只能加方法不能加属性。 Category中有load方法吗?load方法是什么时候调用的?load 方法能继承吗? load、initialize的区别,以及它们在category重写的时候的调用的次序。 Category的本质 首先我们写一段简单的代码,之后的分析都基于这段代码。 Presen类 // Presen.h #import <Foundation/Found.

铭记_ 403

华为的C语言编程规范.pdf

华为技术有限公司内部技术规范。本规范制定了编写C语言程序的基本原则、规则和建议。从代码的清晰、简洁、可测试、安全、程序效率、可移植各个方面对C语言编程作出了具体指导。

ios底层(一)Category的本质,load,initialize方法的调用顺序

1.Category底层结构,如下 2.使用指令从编译后的c++代码,也可以看出是把在分类中写的方法都赋值给了结构体 将Objective-C代码转换为C\C++代码 xcrun-sdkiphoneosclang-archarm64-rewrite-objcOC源文件-o输出的CPP文件 如果需要链接其他框架,使用-framework参数。比如-framew...

feifeiwuxian的专栏 918

SIMATIC S7-1200, S7-1500 PID 控制[手册]

SIMATIC S7-1200, S7-1500 PID 控制[手册]

iOS 深挖 Category 与 Extension 本质区别

文章摘要:iOS开发中Category(分类)和Extension(扩展)常被混淆,但二者本质不同。Category是Objective-C的动态特性,用于运行时动态添加方法,支持关联对象间接扩展属性,但存在方法覆盖风险;Extension是Swift的静态语法,用于代码拆分、协议扩展和计算属性增强,编译安全但无法重写方法。OC项目应使用Category进行工具类扩展,Swift项目优先采用Extension实现模块化。

linwenglong7155的博客 479

iOS底层原理系列02-深入了解Objective-C

对于自定义类,其内存结构会更复杂,包含从父类继承的变量和自己声明的变量。int _age;@end包含从NSObject继承的isa指针(8字节)包含一个int类型变量_age(4字节)包含一个double类型变量_height(8字节)包含一个NSString*类型的_name指针(由于@property自动合成,8字节)理论上总大小为:8 + 4 + 8 + 8 = 28字节。但实际分配会考虑内存对齐,可能会是32字节。特性+load调用时机程序启动加载类和分类时。

但行好事,莫问前程;不忘初心,方得始终! 3208

iOS 分类category 源码解析

第一部分:有关分类的本质、原理 Q:分类的对象方法,类方法都存在哪里? 一个类的所有分类的 对象方法放在类对象中,所有分类的类方法存放在元类中 clang查看编译文件 xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc NSObject+Test.m 编译文件NSObject+Test.cpp中有关分类内容 //声明结构体 struct _c...

teropk的博客 445

【iOS】关联对象-在Category中添加属性

Category Category是Objective-C 2.0之后添加的语言特性,分类、类别 其实都是指的Category。 Category的主要作用是为已经存在的类添加方法。也可以说是将庞大的类代码按逻辑划入几个分区。 Objective-C 中的 Category 就是对装饰模式的一种具体实现。它的主要作用是在不改变原有类的前提下,动态地给这个类添加一些方法。...

kochunk1t的博客 1581
上一篇: 数据安全
下一篇: 自动闭包@autoclosure
Philm_iOS
博客等级 码龄8年 74粉丝 53原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值