OC中的类是如何定义的?请看下面源码
struct objc_class {
Class isa;
//实例的isa指向类对象,类对象的isa指向元类
#if !__OBJC2__
Class super_class; //指向父类
const char *name; //类名
long version; // 类的版本信息,初始化默认为0,可以通过runtime函数class_setVersion和class_getVersion进行修改、读取
long info; // 一些标识信息,如CLS_CLASS (0x1L) 表示该类为普通 class ,其中包含对象方法和成员变量;CLS_META (0x2L) 表示该类为 metaclass,其中包含类方法;
long instance_size; // 该类的实例变量大小(包括从父类继承下来的实例变量);
struct objc_ivar_list *ivars // 成员变量列表
struct objc_method_list **methodLists; // 方法列表
struct objc_cache *cache;// 缓存,存储最近使用的方法指针,用于提升效率
struct objc_protocol_list *protocols // 协议列表
#endif
} OBJC2_UNAVAILABLE;
/* Use `Class` instead of `struct objc_class *` */
typedef struct objc_class *Class
OC中的类就是一个objc_class类型的指针,用Class表示,我们申明的类可以看作是Class的实例。
typedef struct objc_object {
Class isa;
} *id;
oc中的对象则是一个objc_object,我们通常申明的是一个对象的指针,如
NSString *name = @"kobe";
但是我们使用id的时候就不需要*了,因为id本身就是一个指针.
在objc_object结构体中只有一个isa用来指向自己所属的类,也就是上面所提到的Class。一个对象所拥有的成员变量,可执行的方法,遵守的协议及所占内存大小都是由这个isa所指向的类来决定的。
NSString *name = [NSString alloc] init];
name 就是一个objc__object,它的isa指向了objc_class实例NSString。objc_class结构体中的成员就是NSString的具体属性了,NSString
的大小,成员变量,函数等.
cache:用于缓冲使用过的方法。当一个类有大量的方法时,每次方法调用都要在所有的方法中查找要调用的会耗费很长的时间,runtime会把使用过的方法放在这个cache中,下次使用的时候先到cache中查找,如果没有再到方法列表中查找。
对象能够调用的方法是通过isa所指向的类来决定的,那么类所调用的方法呢?
[NSString stringWithFormat:@""];
在OC中类也是一个对象,类能够调用的方法也是由其isa来决定的,isa指向这个类的元类(meta-class)。meta-class 也是一个objc__class,它存储着一个类的所有类方法。每个类都会有一个单独的meta-class,因为每个类的类方法基本不可能完全相同。meta-class也是一个objc_class,那么它也存在着isa,它的isa指向了其父类的meta-class. NSObject的meta-class的isa指向自己。
- class_getName 获取类名
- class_getSuperclass 获取父类
- class_isMetaClass 是否是元类
- class_getInstanceSize 类的实例大小
- Class objc_allocateClassPair & void objc_registerClassPair(Class cls); 动态创建一个可用的新类
- objc_disposeClassPair 销毁 class和其元类
NSLog(@"class name = %@", [NSString stringWithUTF8String:class_getName([self class])]);
NSLog(@"super class = %@", class_getSuperclass([self class]));
Class myViewController = objc_allocateClassPair([ViewController class], "MyViewController", 0);
objc_registerClassPair(myViewController);
id controller = [[myViewController alloc] init];
NSLog(@"%@", [controller class]);
objc_disposeClassPair(myViewController);
id controller1 = [[myViewController alloc] init];
NSLog(@"%@", [controller1 class]);
上面的代码会发生奔溃,因为已经通过objc_disposeClassPair销毁了myViewController就不能再使用了。
成员变量与属性
成员变量Ivar 自定义在.h或.m中或者由属性附带生成
typedef struct objc_ivar *Ivar;
struct objc_ivar {
char *ivar_name OBJC2_UNAVAILABLE; // 变量名
char *ivar_type OBJC2_UNAVAILABLE; // 变量类型
int ivar_offset OBJC2_UNAVAILABLE; // 基地址偏移字节
#ifdef __LP64__
int space OBJC2_UNAVAILABLE;
#endif
}
class_getInstanceVariable 获取实例成员变量
class_getClassVariable 获取类成员变量
Ivar nameIvar = class_getInstanceVariable([UIViewController class], "_view"); //由属性生成的成员变量名一定是以下划线开头的
BOOL class_addIvar(Class cls, const char *name, size_t size,uint8_t alignment, const char *types) 添加成员变量
只能给动态生成的类添加,不能给元类添加。如果添加成功返回YES,添加失败返回NO(添加的已经存在)
alignment:对齐方式一般其值为log2(sizeof(指针类型))
type:类型,这里用到了TypeEncoding(下面详解)
Ivar * class_copyIvarList(Class cls, unsigned int *outCount);获取所有成员变量列表,outcount 成员变量的个数
Class cls = objc_allocateClassPair([MyObject class], "MySubClass", 0);
class_addIvar(cls, "_ivar1", sizeof(NSString *), log(sizeof(NSString *)), "@"); // @ 表示一个对象类型
objc_registerClassPair(cls);
id instance = [[cls alloc] init];
[instance setValue:@"1234" forKey:@"_ivar1"];
NSLog(@"%@",[instance valueForKey:@"_ivar1"]);
TypeEncoding
作为对Runtime的补充,编译器将每个方法的返回值和参数类型编码为一个字符串,并将其与方法的selector关联在一起。这种编码方案在其它情况下也是非常有用的,因此我们可以使用@encode编译器指令来获取它。当给定一个类型时,@encode返回这个类型的字符串编码。这些类型可以是诸如int、指针这样的基本类型,也可以是结构体、类等类型。事实上,任何可以作为sizeof()操作参数的类型都可以用于@encode()。Type encoding 列表
属性类型
typedef struct objc_property *objc_property_t;
属性特性
objc_property_attribute_t
typedef struct {
const char *name; // 特性名
const char *value; // 特性值
} objc_property_attribute_t;
objc_property t class getProperty(Class cls, const char *name) 通过名称获取属性
const char *property_getAttributes(objc_property_t property) 获取属性的特性返回一个字符串
objc_property_t property = class_getProperty([MyObject class], "status");
const char *name = property_getName(property);
const char *attributes = property_getAttributes(property);
NSLog(@"property name = %s", name);
NSLog(@"property attributes = %s", attributes);
property name = status
property attributes = T@”NSString”,&,N,V_status
属性的特性输出是一个类似于TypeEncoding的字符串,T后成的是类型, &表示retain, N标识nonatomic,V就是其成员变量名称。更多的关于特性对应字符值点这里
说了这么多,有什么用?
来一个demo看看,现有一个类Person,定义如下
#import <Foundation/Foundation.h>
@interface Person : NSObject
@property (nonatomic, strong) NSString *name;
@property (nonatomic, strong) NSString *address;
@property (nonatomic, strong) NSString *sex;
- (instancetype)initWithDictionary:(NSDictionary *)dict;
@end
在.mk中看到的实现可能如下
- (instancetype)initWithDictionary:(NSDictionary *)dict {
if (self = [super init]) {
self.name = dict[@"name"];
self.address = dict[@"address"];
self.sex = dict[@"sex"];
}
return self;
}
这种写法没什么问题(不考虑为null或空的时候),但是属性太多的时候就不好办了,代码会非常庞大。如果我们能够通过dict的key来取得属性然后通过dict的value赋值。
- (instancetype)initWithDictionary:(NSDictionary *)dict {
if (self = [super init]) {
[dict enumerateKeysAndObjectsUsingBlock:^(id _Nonnull key, id _Nonnull obj, BOOL * _Nonnull stop) {
objc_property_t property = class_getProperty([self class], [key UTF8String]);
if (property) {
[self setValue:obj forKey:key];
}
}];
}
return self;
}
Person *person = [[Person alloc] initWithDictionary:@{@"name":@"kobe", @"sex":@"famle"}];
NSLog(@"name = %@",person.name);
NSLog(@"sex = %@",person.sex);
name = kobe
sex = famle
我们经常使用的一些json与NSDictionary的转化像YYModel,JSonModel的理论基础也是这样的。
通过分类我们可以给已经存在的类添加方法,但却不能添加成员变量,但是通过runtime这个就很容易了,我们需要用到这个两个函数
void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)
objc_getAssociatedObject(id object, const void *key)
假设我们要为UIViewContoller添加一个Id的成员变量来作为身份标识,我们来生成一个分类
#import <UIKit/UIKit.h>
@interface UIViewController (Tracking)
@property (nonatomic, strong) NSString *vcID;
@end
看到这里你也许想只要在.m里加上getter,setter就ok了
-(void)setVcID:(NSString *)vcID {
_vcID = vcID
}
- (NSString *)vcID {
return _vcID;
}
很不幸(必然)报错了,因为在分类中是无法添加成员变量的,即使添加了属性也不会自动生成成员变量。正确的姿势
static char *vcIdIdentifier;
-(void)setVcID:(NSString *)vcID {
objc_setAssociatedObject(self, vcIdIdentifier, vcID, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (NSString *)vcID {
return objc_getAssociatedObject(self, vcIdIdentifier);
}
- (void)viewDidLoad {
[super viewDidLoad];
self.vcID = @"3456";
NSLog(@"self.vcId = %@", self.vcID);
}
输出 self.vcId = 3456
objc_setAssociatedObject的最后一个参数是所添加的成员变量的内存管理属性
typedef OBJC_ENUM(uintptr_t, objc_AssociationPolicy) {
OBJC_ASSOCIATION_ASSIGN = 0, //weak
OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1, //retain and nonatomtic
OBJC_ASSOCIATION_COPY_NONATOMIC = 3, // copy and nonatomtic
OBJC_ASSOCIATION_RETAIN = 01401, //retain
OBJC_ASSOCIATION_COPY = 01403 //copy
};