基本用法:
Person文件:
#import <Foundation/Foundation.h>
@interface Person : NSObject
- (void)run;
+ (void)run2;
@end
#import "Person.h"
@implementation Person
- (void)run{
NSLog(@"run");
}
+ (void)run2{
NSLog(@"run2");
}
@end
Person+Test文件(Test分类)
#import "Person.h"
@interface Person (Test)
- (void)test;
+ (void)test2;
- (void)run;
@end
#import "Person+Test.h"
@implementation Person (Test)
- (void)test{
NSLog(@"test");
}
+ (void)test2{
NSLog(@"test2");
}
- (void)run{
NSLog(@"test - run");
}
@end
Person+Eat文件(Eat分类)
#import "Person.h"
@interface Person (Eat)
- (void)eat;
+ (void)eat2;
- (void)run;
@end
#import "Person+Eat.h"
@implementation Person (Eat)
- (void)eat{
NSLog(@"eat");
}
+ (void)eat2{
NSLog(@"eat2");
}
- (void)run{
NSLog(@"eat - run");
}
@end
Person *person = [[Person alloc] init];
[person run];
[person test];
[person eat];
[Person run2];
[Person test2];
[Person eat2];
通过runtime动态将分类的方法合并到类对象、元类对象中(程序运行过程中合并,而不是编译时)
程序编译时,所有的分类都变成了结构体,方法数据结构都存在结构体中
调用的run方法是Eat分类还是Test分类中的run方法,取决于最后编译的文件是哪个,最后编译的文件的方法列表会最先调用,如何判断先编译哪个文件,查看如下顺序。
Category的加载处理过程
1、通过Runtime加载某个类的所有Category数据
2、把所有Category的方法、属性、协议数据,合并到一个大数据中,后面参与编译的Category数据,会在数组的前面
3、将合并后的分类数据(方法、属性、协议),插入到类原来数据的前面
类扩展:
相当于.h文件里的属性或者方法,挪到.m文件中。公有化变私有化,编译时就在类对象里边了。和分类是不一样的。
#import "Person.h"
//class extension (匿名分类)也可以叫类扩展
@interface Person ()
{
int _abc;
}
@property (nonatomic,assign) int age;
- (void)abc;
@end
@implementation Person
- (void)abc{
NSLog(@"类扩展 - abc");
}
- (void)run{
NSLog(@"run");
}
@end
探究Category中的load、initialize方法
+load方法
- +load方法会在runtime加载类、分类时调用
- 每个类、分类的+load,在程序运行过程中只调用一次
- 调用顺序:
1.先调用类的+load:按照编译先后顺序调用(先编译,先调用);调用子类的+load之前会先调用父类的+load
2.再调用分类的+load:按照编译先后顺序调用(先编译,先调用)
新建继承自NSObject的Person类、Person的分类Test1、和Person的分类Test2、继承自Person类的Student类、Student类的分类Test1、Student的分类Test2.
Person文件
#import <Foundation/Foundation.h>
@interface Person : NSObject
@end
#import "Person.h"
@implementation Person
+ (void)load{
NSLog(@"Person + load");
}
+ (void)test{
NSLog(@"Person + test");
}
@end
Person+Test1文件
#import "Person.h"
@interface Person (Test1)
@end
#import "Person+Test1.h"
@implementation Person (Test1)
+ (void)load{
NSLog(@"Person(Test1) + load");
}
+ (void)test{
NSLog(@"Person(Test1) + test");
}
@end
Person+Test2文件
#import "Person.h"
@interface Person (Test2)
@end
#import "Person+Test2.h"
@implementation Person (Test2)
+ (void)load{
NSLog(@"Person(Test2) + load");
}
+ (void)test{
NSLog(@"Person(Test2) + test");
}
@end
Student文件
#import "Person.h"
@interface Student : Person
@end
#import "Student.h"
@implementation Student
+ (void)load{
NSLog(@"Student + load");
}
@end
Student+Test1文件
#import "Student.h"
@interface Student (Test1)
@end
#import "Student+Test1.h"
@implementation Student (Test1)
+ (void)load{
NSLog(@"Student (Test1) + load");
}
@end
Student+Test2文件
#import "Student.h"
@interface Student (Test2)
@end
#import "Student+Test2.h"
@implementation Student (Test2)
+ (void)load{
NSLog(@"Student (Test2) + load");
}
@end
运行之后lldb打印结果
2023-03-23 15:56:32.764696+0800 interview[45205:125689529] Person + load
2023-03-23 15:56:32.765497+0800 interview[45205:125689529] Student + load
2023-03-23 15:56:32.765526+0800 interview[45205:125689529] Person(Test1) + load
2023-03-23 15:56:32.765547+0800 interview[45205:125689529] Person(Test2) + load
2023-03-23 15:56:32.765565+0800 interview[45205:125689529] Student (Test1) + load
2023-03-23 15:56:32.765584+0800 interview[45205:125689529] Student (Test2) + load
+ initialize方法
Person文件
#import <Foundation/Foundation.h>
@interface Person : NSObject
@end
#import "Person.h"
@implementation Person
+ (void)initialize{
NSLog(@"Person + initialize");
}
+ (void)test{
NSLog(@"Person + test");
}
@end
Person+Test1文件
#import "Person.h"
@interface Person (Test1)
@end
#import "Person+Test1.h"
@implementation Person (Test1)
+ (void)initialize{
NSLog(@"Person(Test1) + initialize");
}
+ (void)test{
NSLog(@"Person(Test1) + test");
}
@end
Person+Test2文件
#import "Person.h"
@interface Person (Test2)
@end
#import "Person+Test2.h"
@implementation Person (Test2)
+ (void)initialize{
NSLog(@"Person(Test2) + initialize");
}
+ (void)test{
NSLog(@"Person(Test2) + test");
}
@end
Student文件
#import "Person.h"
@interface Student : Person
@end
#import "Student.h"
@implementation Student
+ (void)initialize{
NSLog(@"Student + initialize");
}
@end
Student+Test1文件
#import "Student.h"
@interface Student (Test1)
@end
#import "Student+Test1.h"
@implementation Student (Test1)
+ (void)initialize{
NSLog(@"Student (Test1) + initialize");
}
@end
Student+Test2文件
#import "Student.h"
@interface Student (Test2)
@end
#import "Student+Test2.h"
@implementation Student (Test2)
+ (void)initialize{
NSLog(@"Student (Test2) + initialize");
}
@end
Dog文件
#import <Foundation/Foundation.h>
@interface Dog : NSObject
@end
#import "Dog.h"
@implementation Dog
+ (void)initialize{
NSLog(@"Dog + initialize");
}
@end
Cat文件
#import <Foundation/Foundation.h>
@interface Cat : NSObject
@end
#import "Cat.h"
@implementation Cat
+ (void)initialize{
NSLog(@"Cat + initialize");
}
@end
Teacher文件,(继承自Person,但是.m文件中没有实现initialize方法)
#import "Person.h"
@interface Teacher : Person
@end
#import "Teacher.h"
@implementation Teacher
@end
main.m文件
[Person alloc];
lldb打印结果:
2023-03-23 17:00:02.419047+0800 interview[46841:125744184] Person(Test2) + initialize
[Student alloc];
lldb打印结果:
2023-03-23 18:54:33.026981+0800 interview[47435:125764517] Person(Test2) + initialize
2023-03-23 18:54:33.027458+0800 interview[47435:125764517] Student (Test2) + initialize
[Person alloc];
[Student alloc];
lldb打印结果:
2023-03-23 18:56:17.971696+0800 interview[47484:125766784] Person(Test2) + initialize
2023-03-23 18:56:17.972027+0800 interview[47484:125766784] Student (Test2) + initialize
[Teacher alloc];
lldb打印结果:
2023-03-23 19:30:35.313728+0800 interview[48235:125793488] Person(Test2) + initialize
2023-03-23 19:30:35.314508+0800 interview[48235:125793488] Person(Test2) + initialize
- initialize方法会在类第一次接收到消息时调用
- 调用顺序:
1.先调用父类的+initialize,再调用子类的+initialize
用Person和Student类举例:(伪代码)
if(Student 没有初始化){
if(Person没有初始化){
objc_msgSend([Person class],@selector(initialize));
}
objc_msgSend([Student class],@selector(initialize));
}
2.先初始化父类,再初始化子类,每个类只会初始化一次
- +initialize和+load的很大区别是,+ initialize是通过objc_msgSend进行调用的,所以有以下特点
1.如果子类没有实现+ initialize,会调用父类的+ initialize(所以父类的 + initialize可能会被调用多次)
2.如果分类实现了+ initialize,就覆盖类本身的+ initialize调用
关联对象 - 分类添加属性
分类中直接写@property属性的话,只能实现set、get方法的声明,不能声明成员变量和set、get方法的实现。
那么首先想到第一种办法,在分类的.m文件中声明一个全局变量,保存传进来的数据,代码如下:
Person.h文件
#import <Foundation/Foundation.h>
@interface Person : NSObject
@property (nonatomic,assign) int age;
@end
Person.m文件
#import "Person.h"
@implementation Person
@end
Person+Test.h文件
#import "Person.h"
@interface Person (Test)
@property (nonatomic,assign) int weight;
@end
Person+Test.m文件
#import "Person+Test.h"
@implementation Person (Test)
int weight_;
- (void)setWeight:(int)weight{
weight_ = weight;
}
- (int)weight{
return weight_;
}
@end
main文件
Person *person1 = [[Person alloc] init];
person1.age = 10;
person1.weight = 20;
Person *person2 = [[Person alloc] init];
person2.age = 100;
person2.weight = 200;
NSLog(@"age1 is %d,weight1 is %d",person1.age,person1.weight);
NSLog(@"age2 is %d,weight2 is %d",person2.age,person2.weight);
打印结果:
age1 is 10,weight1 is 200
age2 is 100,weight2 is 200
但是这种方法存在一种弊端,就是实例化多个person对象,会共用一个weight全局变量。所以不能保证每一个person有一个自己对应的weight。
那么这种方案不行,继续改进,用一个NSMutableDictionary字典去实现每一个person对象有一个自己对应的weight,分类.m文件代码修改如下
Person+Test.m文件
#import "Person+Test.h"
@implementation Person (Test)
NSMutableDictionary *weights_;
+ (void)load{
weights_ = [NSMutableDictionary dictionary];
}
- (void)setWeight:(int)weight{
NSString *key = [NSString stringWithFormat:@"%p",self];
weights_[key] = @(weight);
}
- (int)weight{
NSString *key = [NSString stringWithFormat:@"%p",self];
return [weights_[key] intValue];
}
@end
main文件
Person *person1 = [[Person alloc] init];
person1.age = 10;
person1.weight = 20;
Person *person2 = [[Person alloc] init];
person2.age = 100;
person2.weight = 200;
NSLog(@"age1 is %d,weight1 is %d",person1.age,person1.weight);
NSLog(@"age2 is %d,weight2 is %d",person2.age,person2.weight);
打印结果:
age1 is 10,weight1 is 20
age2 is 100,weight2 is 200
那么这种方案也存在一定的弊端:比如内存泄漏、线程安全等问题,还有一个问题就是,添加多个属性的话,会重复写多个字典、set、get方法,比较麻烦。
那么第三种方案就是采用关联对象的方案:
关联对象提供了以下API:
- 添加关联对象:
void objc_setAssociatedObject(id object,const void * key, id value, objc_AssociationPolicy policy) - 获得关联对象
id objc_getAssociatedObject(id object, const void * key) -
移除所有的关联对象
objc_AssociationPolicy:
void objc_removeAssociatedObjects(id object)
Person+Test.h文件
#import "Person.h"
@interface Person (Test)
@property (nonatomic,copy) NSString *name;
@end
Person+Test.m文件
#import "Person+Test.h"
#import <objc/runtime.h>
@implementation Person (Test)
const void *NameKey = &NameKey;
- (void)setName:(NSString *)name{
objc_setAssociatedObject(self, NameKey, name, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
- (NSString *)name{
return objc_getAssociatedObject(self, NameKey);
}
@end
上边的方案有一个问题,就是全局变量const void *NameKey = &NameKey;在外部其他的文件中是可以访问的, extern const void *NameKey;因为上边的代码逻辑是想要文件内部访问的,并不想文件外部也能访问的到,所以需要改进一下,把const void *NameKey = &NameKey;前边加一个static修饰词,外部就无法访问,代码如下:
Person+Test.m文件
#import "Person+Test.h"
#import <objc/runtime.h>
@implementation Person (Test)
static const void *NameKey = &NameKey;
- (void)setName:(NSString *)name{
objc_setAssociatedObject(self, NameKey, name, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
- (NSString *)name{
return objc_getAssociatedObject(self, NameKey);
}
@end
上边的代码,关联对象的key还可以进一步进行优化,代码如下:
Person+Test.m文件
#import "Person+Test.h"
#import <objc/runtime.h>
@implementation Person (Test)
static const char NameKey;
- (void)setName:(NSString *)name{
objc_setAssociatedObject(self, &NameKey, name, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
- (NSString *)name{
return objc_getAssociatedObject(self, &NameKey);
}
@end
还有一种方案,key直接写成固定的字符串:
Person+Test.m文件
#import "Person+Test.h"
#import <objc/runtime.h>
@implementation Person (Test)
- (void)setName:(NSString *)name{
objc_setAssociatedObject(self, @"name", name, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
- (NSString *)name{
return objc_getAssociatedObject(self, @"name");
}
@end
最后一种方案,key的写法:
#import "Person+Test.h"
#import <objc/runtime.h>
@implementation Person (Test)
- (void)setName:(NSString *)name{
objc_setAssociatedObject(self, @selector(name), name, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
- (NSString *)name{
return objc_getAssociatedObject(self, @selector(name));
}
@end
或者写成
#import "Person+Test.h"
#import <objc/runtime.h>
@implementation Person (Test)
- (void)setName:(NSString *)name{
objc_setAssociatedObject(self, @selector(name), name, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
- (NSString *)name{
return objc_getAssociatedObject(self, _cmd);
}//_cmd == @selector(当前方法名)
@end
关联对象的原理
-
实现关联对象技术的核心对象有:
objc4源码解读:objc-references.mm
AssociationsManager
AssociationsHashMap
ObjectAssociationMap
ObjcAssociation
AssociationsManager 对象里边有一个AssociationsHashMap,AssociationsHashMap里边放字典,ObjectAssociationMap作为AssociationsHashMap里的value值,ObjectAssociationMap里边也是放字典,ObjcAssociation是作为ObjectAssociationMap里边字典的value。ObjcAssociation里边放了两个冬秀,一个policy,一个value
所以关联对象里边的值,并不是放在了原来的类的实例对象里边,而是单独维护了一份Manager等四个类。
- 关联对象并不是存储在被关联对象本身内存中
- 关联对象存储在全局的统一的一个AssociationManager中
- 设置关联对象为
nil
时,就相当于是移除关联对象 - 一旦被关联的对象销毁,那么他所对应的Map也会被销毁