OC-类和对象

类(Class)

OC中的类是由Class来定义的,其实际上就是一个指向objc_class结构体的指针,其定义为

typedef struct objc_class *Class;

而objc_class又是什么样的呢,查看其定义为

struct objc_class {
    Class isa  OBJC_ISA_AVAILABILITY;
    #if !__OBJC2__
    Class super_class         OBJC2_UNAVAILABLE;  // 父类
    const char *name          OBJC2_UNAVAILABLE;  // 类名
    long version              OBJC2_UNAVAILABLE;  // 类的版本号
    long info                OBJC2_UNAVAILABLE;  // 类信息
    long instance_size      OBJC2_UNAVAILABLE;  // 类的实例大小
    struct objc_ivar_list *ivars            OBJC2_UNAVAILABLE;  // 成员变量列表
    struct objc_method_list **methodLists   OBJC2_UNAVAILABLE;  // 方法列表
    struct objc_cache *cache                OBJC2_UNAVAILABLE;  // 方法缓存
    struct objc_protocol_list *protocols    OBJC2_UNAVAILABLE;  // 协议列表
    #endif
} OBJC2_UNAVAILABLE;
对象(id)

OC中的万能对象用id来表示,其实际上就是一个指向 objc_object 结构体的指针,其定义如下:

typedef struct objc_object *id;

而objc_object又是什么呢,查看其定义:

struct objc_object {
    Class isa  OBJC_ISA_AVAILABILITY;
};
  • isa
    由类和对象的定义,我们可以看出,其成员变量中都含有isa指针,isa指向的是一个类,其指向该对象所属的类。我们可以把isa当作是一个对象的标志,因此类也是一个对象,我们称之为类对象,其isa指针,指向该类对象所属的类,我们称之为元类(metaClass)。

    isa指针指向的类,我们可以用[object class]来获取,但是,如果是类对象,其只能返回当前类,获取不到元类。object_getClass()函数也可以获取到isa指针指向的类,包括元类,下面是class方法和object_getClass()函数的具体实现。
    - (Class)class {
    return object_getClass(self);
    }
    + (Class)class {
    return self;
    }
    Class object_getClass(id obj)
    {
    if (obj) return obj->getIsa();
    else return Nil;
    }

  • super_class
    指向该对象的父类,如果该对象,已经是最顶层的根类(如NSObject),则该值为null

那么isa,类,父类,元类之间的关系又是怎样的?由于元类无法打印出来,因此我们采用查看其地址的方法来探究其关系。

先看代码:
#import <Foundation/Foundation.h>

@interface Person: NSObject
@end;
@interface Student: Person
@end;

int main(int argc, const char * argv[]) {

@autoreleasepool {
    
    Person *p = [[Person alloc] init];
    Student *s = [[Student alloc] init];
    
    NSLog(@"对象 s --> isa 指向地址是 %p",[s class]);
    NSLog(@"Student --> isa 指向地址是 %p",object_getClass([Student class]));
    NSLog(@"Student --> isa --> isa 指向的地址是 %p",object_getClass(object_getClass([Student class])));
    NSLog(@"Student --> isa --> superclass  的地址是 %p",[object_getClass([Student class]) superclass]);
    NSLog(@"s --> superclass 的地址 %p",[s superclass]);

    NSLog(@"============================");

    NSLog(@"对象 p --> isa 指向的地址是 %p",[p class]);
    NSLog(@"Person --> isa 指向的地址是 %p",object_getClass([Person class]));
    NSLog(@"Person --> isa --> isa 指向的地址是 %p",object_getClass(object_getClass([Person class])));
    NSLog(@"Person --> isa --> superclass 的地址是 %p",[object_getClass([Person class]) superclass]);
    NSLog(@"p --> superclass 的地址 %p",[p superclass]);

    NSLog(@"============================");

    
    NSLog(@"NSObject 的地址 %p",[NSObject class]);
    NSLog(@"NSObject --> isa 的地址是 %p",object_getClass([NSObject class]));
    NSLog(@"NSObject --> isa --> isa 的地址是 %p",object_getClass(object_getClass([NSObject class])));
    NSLog(@"NSObject --> isa --> superclass 的地址是 %p",[object_getClass([NSObject class]) superclass]);
    
    NSLog(@"NSObject --> superclass 的地址 %p",[NSObject superclass]);  
  }
return 0;
}

输出结果:

对象 s --> isa 指向地址是 0x1000049a0
Student --> isa 指向地址是 0x100004978
Student --> isa --> isa 指向的地址是 0x7fff7f835118
Student --> isa --> superclass  的地址是 0x100004928
s --> superclass 的地址 0x100004950
============================
对象 p --> isa 指向的地址是 0x100004950
Person --> isa 指向的地址是 0x100004928
Person --> isa --> isa 指向的地址是 0x7fff7f835118
Person --> isa --> superclass 的地址是 0x7fff7f835118
p --> superclass 的地址 0x7fff7f8350f0
============================
NSObject 的地址 0x7fff7f8350f0
NSObject --> isa 的地址是 0x7fff7f835118
NSObject --> isa --> isa 的地址是 0x7fff7f835118
NSObject --> isa --> superclass 的地址是 0x7fff7f8350f0
NSObject --> superclass 的地址 0x0

得到关系图如下:


image.png

由图我们可以得出以下结论:

  • 实例对象的isa指向该对象所属的类
  • 类对象的isa指向该类对象所属的类,即元类(metaclass)
  • 所有类对象的元类的isa指向该类对象的顶级父类(NSObject)的元类
  • 类对象的元类的superclass指向该类对象的父类的元类
  • 顶级类对象(NSObject)的元类的isa指向元类自己
  • 顶级类对象的superclass指向nil(0x0)

好了,有了这个关系图,我们来小运用一下:

@interface Person: NSObject
@end;

int main(int argc, const char * argv[]) {

@autoreleasepool {
    Person *p = [[Person alloc] init];
    
    BOOL isEqual = [p isKindOfClass:[Person class]]; 
    BOOL isEqual1 = [Person isKindOfClass:[Person class]]; 
    BOOL isEqual2 = [NSObject isKindOfClass:[NSObject class]]; 

    BOOL isEqual3 = [p isMemberOfClass:[Person class]]; 
    BOOL isEqual4 = [Person isMemberOfClass:[Person class]]; 
    BOOL isEqual5 = [NSObject isMemberOfClass:[NSObject class]]; 

    NSLog(@"%d %d %d %d %d %d",isEqual,isEqual1,isEqual2,isEqual3,isEqual4,isEqual5);
    
  }
  return 0;
}

输出结果是怎样的呢?
运行后,我们看到结果是:

 1 0 1 1 0 0

为什么结果是这样的呢?我们先来看isKindOfClass:和isMemberOfClass:这两个方法是怎么实现的。

NSObject.mm
+ (BOOL)isKindOfClass:(Class)cls {
for (Class tcls = object_getClass((id)self); tcls; tcls = tcls->superclass) {
if (tcls == cls) return YES;
}
return NO;
}
- (BOOL)isKindOfClass:(Class)cls {
for (Class tcls = [self class]; tcls; tcls = tcls->superclass) {
if (tcls == cls) return YES;
}
return NO;
}
+ (BOOL)isMemberOfClass:(Class)cls {
return object_getClass((id)self) == cls;
}
- (BOOL)isMemberOfClass:(Class)cls {
return [self class] == cls;
}
不难发现,这两个方法,都是用对象的isa,去和参数cls做比较,所不同的是,如果第一次比较,两者不相同,isKindOfClass:会去寻找isa的superclass,如果一直比较到最顶层的superclas(nil),仍然没有找到相同的,才返回NO。

下面来看isEqual1的比较过程:

第一次: Person的metaClass 和 Person相比 == NO
第二次:Person的metaClass的superclass即NSObject的metaClass 和 Person相比 == NO
第三次:NSObject的metaClass的superclass 和 Person相比 == NO
最后:Person 的superClass == nil,退出比较,直接返回NO

isEqual2的比较过程:

第一次: NSObject的metaClass 和 NSObject相比 == NO
第二次:NSObject的metaClass的superclass 即NSObject 和 NSObject相比 == YES
最后,退出比较,返回YES

其他的比较类似,这里不做累述。

动态创建类

说了这么多,我们来动态创建个类,在动态创建之前,先介绍几个函数:

  • objc_allocateClassPair(Class superclass, const char *name, size_t extraBytes)
    该方法用来动态创建一个类,
    superclass:为要创建的类的父类,如果传nil,则表示要创建根类。
    name: 要创建的新类
    extraBytes: 分配给类和元类尾部索引变量的字节数,通常为0

  • void objc_disposeClassPair(Class cls)
    该方法用于销毁一个类和其关联的元类,注意:如果该类的实例或其任何一个子类存在的时候,不要调用该方法。

  • void objc_registerClassPair(Class cls)
    注册一个类

  • BOOL class_addMethod(Class cls, SEL name, IMP imp, const char *types)
    给类动态添加方法
    name: 要增加的方法名称的映射
    imp:方法的实现,该方法必须至少包含两个参数,self 和 _cmd
    types: 类型编码,用一个C字符串来表示方法的返回值和参数,列举几个常用的类型编码:

Code 含义
v 表示void
@ 表示一个对象
: 表示一个方法选择器 SEL

由于方法的实现必须包含self和_cmd两个参数,因此types的第二个和第三个字符必须是"@:"(第一个是返回值类型)

  • BOOL class_addIvar(Class cls, const char *name, size_t size, uint8_t alignment, const char *types)
    给类添加成员变量, 该方法只能在动态创建类的时候使用,不能向已存在的类中添加成员变量
    cls: 要添加成员变量的类,该类不能是元类
    name: 变量名称
    size:变量类型大小,通常可用sizeof来获取
    alignment: 偏移量,实例变量的最小偏移量是1<<align,如果是指针类型,我们可以传log2(sizeof(pointer_type)).

    size和alignment我们也可以通过如下方式来得到,如
    NSUInteger size;
    NSUInteger alignment;
    NSGetSizeAndAlignment("*", &size, &alignment);

    types: 类型编码。

动态创建的类必须从objc_allocateClassPair开始,直到 class_addMethod、 class_addIvar等方法都执行完之后,最后再用objc_registerClassPair注册该类。

代码实践
本例子动态创建了一个类,并向其中添加了方法和成员变量,最后,执行了新添加的方法并获取了新添加的变量的值。

 #import <Foundation/Foundation.h>
 #import <objc/runtime.h>

 void myMethodIMP(id self, SEL _cmd)
 {
    NSLog(@"我是新类----%@",[self class]);
 }

int main(int argc, const char * argv[]) {

@autoreleasepool {

    Class cls =  objc_allocateClassPair([NSObject class],"myClass", 0);
    class_addMethod(cls, NSSelectorFromString(@"myMethodIMP"),(IMP)myMethodIMP, "v@:");
    BOOL flag = class_addIvar(cls, "name", sizeof(NSString *), log2(sizeof(NSString *)), "@");
    objc_registerClassPair(cls);
    
    id instance = [[cls alloc] init];
    [instance setValue:@"百客" forKey:@"name"]; // 这里用kvc的时候,最好判断下该变量是否存在,或者用下面的这种方法
//        object_setIvar(instance, class_getInstanceVariable(cls, "_age"), @"10");
    [instance performSelector:@selector(myMethodIMP)];
    
    Ivar v = class_getInstanceVariable(cls, "name");
    id o = object_getIvar(instance, v);
    
    NSLog(@"动态添加的成员变量name的值为---%@",o);
    
}
    return 0;
}

输出结果:

我是新类----myClass
动态添加的成员变量name的值为---百客

附:OC Runtime库地址: http://opensource.apple.com/tarballs/objc4/

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,456评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,370评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,337评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,583评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,596评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,572评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,936评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,595评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,850评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,601评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,685评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,371评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,951评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,934评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,167评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,636评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,411评论 2 342

推荐阅读更多精彩内容