OC runtime 类与对象

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指向自己。

  1. class_getName 获取类名
  2. class_getSuperclass 获取父类
  3. class_isMetaClass 是否是元类
  4. class_getInstanceSize 类的实例大小
  5. Class objc_allocateClassPair & void objc_registerClassPair(Class cls); 动态创建一个可用的新类
  6. 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就是其成员变量名称。更多的关于特性对应字符值点这里

更多APi

说了这么多,有什么用?

来一个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
};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值