百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 编程网 > 正文

Objective-C的本质(objective-c c区别)

yuyutoo 2024-11-17 03:42 1 浏览 0 评论

我们平时编写的Objective-C代码,底层实现其实都是C\C++代码,所以Objective-C的面向对象都是基于C\C++的数据结构实现的

OC对象的本质

Objective-C的对象、类主要是基于C\C++的结构体实现的

通过下面的命令可以将OC代码转换为C++代码来查看

clang -rewrite-objc OC源文件 -o 输出的CPP文件

由于Clang会根据不同平台转换的C++代码有所差异,所以针对iOS平台用下面的命令来转换

// 意为:通过Xcode运行iPhone平台arm64架构,重写OC文件到C++文件
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc OC源文件 -o 输出的CPP文件

如果需要链接其他框架,使用-framework参数。比如-framework UIKit

凡是继承自NSObject的对象,都会自带一个类型是Class的isa的成员变量,将其转成C++,就可以看到NSObject本质上是一个叫做NSObject_IMPL的结构体,其成员变量isa本质上也是一个指向objc_class结构体的指针

OC对象的内存布局

一个OC对象在内存中的布局是这样的,系统会在堆中开辟一块内存空间存放该对象,这块空间里还包含成员变量和isa指针。然后栈里的局部变量指向这块存储空间的地址

OC对象的内存占用大小

系统会给NSObject对象自动分配16个字节的内存,而NSObject对象实际只占用了8个字节的内存。这8个字节的大小就是成员变量isa指针的大小,多余的8个字节是系统为了内存对齐而分配的

// 获取实例对象的内存大小,实际是获取对象成员变量的内存大小
#import <objc/runtime.h>class_getInstanceSize([NSObject class]);// 获取实例对象的内存大小,实际是获取系统真正分配了多少内存#import <malloc/malloc.h>malloc_size((__bridge const void *)obj);


NSObject *obj = [[NSObject alloc] init];
        
// 获得NSObject实例对象的成员变量所占用的大小 >> 8
NSLog(@"%zd", class_getInstanceSize([NSObject class]));
   
// 获得obj指针所指向内存的大小 >> 16
NSLog(@"%zd", malloc_size((__bridge const void *)obj));

验证方法

1.源码验证

下载苹果开源框架 https://opensource.apple.com/tarballs/objc4/

选择最大版本下载

在头文件objc-runtime-new.h中找到对应代码

 inline size_t instanceSize(size_t extraBytes) const {
   if (fastpath(cache.hasFastInstanceSize(extraBytes))) {
       return cache.fastInstanceSize(extraBytes);
   }

   size_t size = alignedInstanceSize() + extraBytes;
   // CF requires all objects be at least 16 bytes.
   // 只要小于16个字节都会被赋值16
   if (size < 16) size = 16;
   return size;
}

2.内存验证

运行Xcode,选择Debug->Debug Workflow -> View Memory查看内存数据

输入obj的内存地址可以看到只有前8个字节有值,但已经分配了16个字节的内存空间

3.LLDB打印验证

利用LLDBmemory read读取对象的内存地址,可以看到也是分配的16个字节

OC对象的分类

OC对象主要分为三种

  • instance对象(实例对象)
  • class对象(类对象)
  • meta-class对象(元类对象)

instance对象

instance对象就是通过类alloc出来的对象,每次调用alloc都会产生新的instance对象

// object1、object2是NSObject的instance对象(实例对象)
NSObject *object1 = [[NSObject alloc] init];
NSObject *object2 = [[NSObject alloc] init];

// 通过打印可以看出,它们是不同的两个对象,分别占据着两块不同的内存
NSLog(@"instance - %p %p",
	    object1,
	    object2);

instance对象在内存中存储的信息

  • isa指针
  • 其他成员变量的具体值

class对象

每个类在内存中有且只有一个class对象

Class objectClass1 = [object1 class];
Class objectClass2 = [object2 class];
Class objectClass3 = object_getClass(object1);
Class objectClass4 = object_getClass(object2);
Class objectClass5 = [NSObject class];

// 通过打印可以看出,上面几个方法返回的都是同一个类对象,内存地址都一样
NSLog(@"class - %p %p %p %p %p %d",
		objectClass1,
		objectClass2,
		objectClass3,
		objectClass4,
		objectClass5);

注意: class方法返回的一直是类对象,所以哪怕这样写还是会返回类对象

Class objectMetaClass2 = [[[NSObject class] class] class];

class对象在内存中存储的信息

  • isa指针- superclass指针- 类的属性信息(@property)、类的对象方法信息(instance method)- 类的协议信息(protocol)、类的成员变量信息(ivar)
  • ....

meta-class对象

objectMetaClass是NSObject的meta-class对象(元类对象),每个类在内存中有且只有一个meta-class对象

Class objectMetaClass = object_getClass(objectClass5);

meta-class对象和class对象的内存结构是一样的,但是用途不一样,在内存中存储的信息主要包括

  • isa指针- superclass指针- 类的类方法信息(class method)
  • ....

使用class_isMetaClass(Class _Nullable cls)来查看Class是否为meta-class的方法

NSLog(@"objectMetaClass - %p %d", objectMetaClass, class_isMetaClass(objectMetaClass));

isa和superclass

每个类的实例对象、类对象、元类对象都有一个isa指针

  • instance的isa指向class - 当调用对象方法时,通过instance的isa找到class,最后找到对象方法的实现进行调用
  • class的isa指向meta-class - 当调用类方法时,通过class的isa找到meta-class,最后找到类方法的实现进行调用
  • meta-class的isa指向基类的meta-class每个类的类对象、元类对象都有一个superclass指针
  • class的superclass指针指向父类的class
    • 如果没有父类,superclass指针为nil
  • meta-class的superclass指向父类的meta-class
    • 基类的meta-class的superclass指向基类的class

instance调用对象方法的轨迹

  • isa找到class,方法不存在,就通过superclass找父类

class调用类方法的轨迹

  • isa找meta-class,方法不存在,就通过superclass找父类

Class类型的底层结构

我们可以从源码objc-runtime-new.h文件中找到Class类型的本质是结构体objc_class类型,里面包含了superclass指针、cache方法缓存,以及获取具体的类信息的class_data_bits_t类型的属性表

struct objc_class : objc_object {
    // Class ISA;
    // superclass指针
    Class superclass;
    // 方法缓存
    cache_t cache;             // formerly cache pointer and vtable
    // 用于获取具体的类信息
    class_data_bits_t bits;    // class_rw_t * plus custom rr/alloc flags
    
    // rw意为readwrite,可读可写,t意为table,表格
    class_rw_t *data() const {
        return bits.data();
    }
    void setData(class_rw_t *newData) {
        bits.setData(newData);
    }
}

继承的父类objc_object里有一个isa指针

// 继承的父类结构体里面有一个isa指针
struct objc_object {
private:
	isa_t isa;
	
public:

    Class ISA(bool authenticated = false);
    Class rawISA();
    Class getIsa();
    uintptr_t isaBits() const;
    
    ....
};

分析class_data_bits_t这个类型里面的结构可以看出,bits & FAST_DATA_MASK就可以得到class_rw_t类型的表的内存

// class_data_bits_t结构体里的具体分析
struct class_data_bits_t {
    friend objc_class;

    class_rw_t* data() const {
        return (class_rw_t *)(bits & FAST_DATA_MASK);
    }
    
    void setData(class_rw_t *newData)
    {
        ASSERT(!data()  ||  (newData->flags & (RW_REALIZING | RW_FUTURE)));
        
        uintptr_t newBits = (bits & ~FAST_DATA_MASK) | (uintptr_t)newData;
        atomic_thread_fence(memory_order_release);
        bits = newBits;
    }
}

分析class_rw_t这个类型里面的结构可以看出,里面有方法列表、属性列表、协议列表,以及class_ro_t类型的属性表

// class_rw_t结构体里的具体分析
struct class_rw_t {
    // Be warned that Symbolication knows the layout of this structure.
    uint32_t flags;
    uint16_t witness;
#if SUPPORT_INDEXED_ISA
    uint16_t index;
#endif

// ro意为readonly,只读
const class_ro_t *ro() const {
   auto v = get_ro_or_rwe();
   if (slowpath(v.is<class_rw_ext_t *>())) {
       return v.get<class_rw_ext_t *>(&ro_or_rw_ext)->ro;
   }
   return v.get<const class_ro_t *>(&ro_or_rw_ext);
}

void set_ro(const class_ro_t *ro) {
   auto v = get_ro_or_rwe();
   if (v.is<class_rw_ext_t *>()) {
       v.get<class_rw_ext_t *>(&ro_or_rw_ext)->ro = ro;
   } else {
       set_ro_or_rwe(ro);
   }
}

// 方法列表
const method_array_t methods() const {
   auto v = get_ro_or_rwe();
   if (v.is<class_rw_ext_t *>()) {
       return v.get<class_rw_ext_t *>(&ro_or_rw_ext)->methods;
   } else {
       return method_array_t{v.get<const class_ro_t *>(&ro_or_rw_ext)->baseMethods()};
   }
}

// 属性列表
const property_array_t properties() const {
   auto v = get_ro_or_rwe();
   if (v.is<class_rw_ext_t *>()) {
       return v.get<class_rw_ext_t *>(&ro_or_rw_ext)->properties;
   } else {
       return property_array_t{v.get<const class_ro_t *>(&ro_or_rw_ext)->baseProperties};
   }
}

// 协议列表
const protocol_array_t protocols() const {
   auto v = get_ro_or_rwe();
   if (v.is<class_rw_ext_t *>()) {
       return v.get<class_rw_ext_t *>(&ro_or_rw_ext)->protocols;
   } else {
       return protocol_array_t{v.get<const class_ro_t *>(&ro_or_rw_ext)->baseProtocols};
   }
}

分析class_ro_t这个类型的结构可以看出,instanceSize意为实例对象所占用的内存空间,name存储的是类名,ivars存储的成员变量列表

struct class_ro_t {
    uint32_t flags;
    uint32_t instanceStart;
    // 实例对象占用内存大小空间
    uint32_t instanceSize;
#ifdef __LP64__
    uint32_t reserved;
#endif

	// 类名
	explicit_atomic<const char *> name;
	void *baseMethodList;
    protocol_list_t * baseProtocols;
    // 成员变量列表
    const ivar_list_t * ivars;
}

总结:

上述分析可以简单用一张图来概述

isa指针

在arm64架构之前,isa就是一个普通的指针,存储着Class、Meta-Class对象的内存地址

从arm64架构开始,对isa进行了优化,变成了一个isa_t类型的共用体(union)结构,共用体就是多种数据结构都共用同一块存储空间,里面包含了bits、cls、ISA_BITFIELD结构体以及其他的函数或变量,它们都是共用同一块内存空间的

union isa_t {
    isa_t() { }
    isa_t(uintptr_t value) : bits(value) { }

    uintptr_t bits;
    
private:
    Class cls;

public:
#if defined(ISA_BITFIELD)
    struct {
        ISA_BITFIELD; // 现在的版本用一个宏来定义 
    };
}

isa.h中查看ISA_BITFIELD这个结构体,里面的每一个值都是位域。不同架构下的掩码和位域都是不一样的,我们只以arm64架构的来分析

// 在isa.h中查看ISA_BITFIELD
// 每个变量后面标的数字就是位域
// 类似ISA_MASK这种宏的都叫掩码

# if __arm64__
#   if __has_feature(ptrauth_calls) || TARGET_OS_SIMULATOR
#     define ISA_MASK        0x007ffffffffffff8ULL
#     define ISA_MAGIC_MASK  0x0000000000000001ULL
#     define ISA_MAGIC_VALUE 0x0000000000000001ULL
#     define ISA_HAS_CXX_DTOR_BIT 0
#     define ISA_BITFIELD                                                      \
        uintptr_t nonpointer        : 1;                                       \
        uintptr_t has_assoc         : 1;                                       \
        uintptr_t weakly_referenced : 1;                                       \
        uintptr_t shiftcls_and_sig  : 52;                                      \
        uintptr_t has_sidetable_rc  : 1;                                       \
        uintptr_t extra_rc          : 8
#     define RC_ONE   (1ULL<<56)
#     define RC_HALF  (1ULL<<7)
#   else
#     define ISA_MASK        0x0000000ffffffff8ULL
#     define ISA_MAGIC_MASK  0x000003f000000001ULL
#     define ISA_MAGIC_VALUE 0x000001a000000001ULL
#     define ISA_HAS_CXX_DTOR_BIT 1
#     define ISA_BITFIELD                                                      \
        uintptr_t nonpointer        : 1;                                       \
        uintptr_t has_assoc         : 1;                                       \
        uintptr_t has_cxx_dtor      : 1;                                       \
        uintptr_t shiftcls          : 33; /*MACH_VM_MAX_ADDRESS 0x1000000000*/ \
        uintptr_t magic             : 6;                                       \
        uintptr_t weakly_referenced : 1;                                       \
        uintptr_t unused            : 1;                                       \
        uintptr_t has_sidetable_rc  : 1;                                       \
        uintptr_t extra_rc          : 19
#     define RC_ONE   (1ULL<<45)
#     define RC_HALF  (1ULL<<18)
#   endif

# elif __x86_64__
	....
# else
#   error unknown architecture for packed isa
# endif

// SUPPORT_PACKED_ISA
#endif

每一位位域对应的二进制位的排序都是从右向左的,下面是对应的每个位域的含义

上述代码里类似ISA_MASK这样的值都是掩码,以掩码ISA_MASK为例,转成二进制发现对应是1的部分都是用来取值的

而且一共有33位的1,正好对应着shiftcls这个位域的位数,shiftcls又是存储着类对象和元类对象的地址值,那么就能说明在arm64架构之后的isa里存储着更多的信息,需要&ISA_MASK进行一次位运算之后才能将类对象和元类对象的真实地址值取出来

位运算的运用实例

利用共用体和位运算来优化属性的内存空间

创建Person.h文件,然后手动实现setter和getter

@interface Person : NSObject
//@property (assign, nonatomic, getter=isTall) BOOL tall;
//@property (assign, nonatomic, getter=isRich) BOOL rich;
//@property (assign, nonatomic, getter=isHansome) BOOL handsome;

- (void)setTall:(BOOL)tall;
- (void)setRich:(BOOL)rich;
- (void)setHandsome:(BOOL)handsome;

- (BOOL)isTall;
- (BOOL)isRich;
- (BOOL)isHandsome;

@end

利用共用体的本质,在Person.m的类扩展中创建一个私有的共用体类型的变量

@interface Person()
{
    union {
        char bits;
        
        struct {
            char tall : 1;
            char rich : 1;
            char handsome : 1;
        };
    } _tallRichHandsome;
}
@end

该共用体一共只占有1个字节,都是根据char bits来分配大小的。而sturct结构体是对这1个字节大小的占用做说明的,里面每一个的1就是位域,指明占用了1个二进制位,虽然是char类型的,但都是根据位域后面给定的值来确定实际占用大小的。tall、rich、handsome三个变量都是占用着同一个内存区域,也就是值都会存储在一个字节里,这就是共用体的本质,这么做主要是为了做优化,节省内存空间。而且写不写这个结构体都是根据char bits来确定了分配空间大小的,没有影响的

由于上述结构体里的三个变量占用一个字节大小就足够了,那么我们对应每一个变量用一个二进制位来存取值。我们先分别设定三个掩码对应三个值

// 0x0000 0001
#define TallMask (1<<0)
// 0x0000 0010
#define RichMask (1<<1)
// 0x0000 0100
#define HandsomeMask (1<<2)

setter的实现如下,如果参数为YES,那么将掩码进行按位或运算;如果参数为NO,那么先将掩码取反,然后再进行按位与运算

@implementation Person

- (void)setTall:(BOOL)tall
{
    if (tall) {
        _tallRichHandsome.bits |= TallMask;
    } else {
        _tallRichHandsome.bits &= ~TallMask;
    }
}

- (void)setRich:(BOOL)rich
{
    if (rich) {
        _tallRichHandsome.bits |= RichMask;
    } else {
        _tallRichHandsome.bits &= ~RichMask;
    }
}

- (void)setHandsome:(BOOL)handsome
{
    if (handsome) {
        _tallRichHandsome.bits |= HandsomeMask;
    } else {
        _tallRichHandsome.bits &= ~HandsomeMask;
    }
}

@end

getter的实现如下,先将掩码进行按位与运算,然后再取反两次;因为返回值是BOOL类型,那么不是0就是1,所以按位与运算后的值只要不是0的都是有值的,那么取反两次肯定就得到的不是0就是1了

- (BOOL)isTall
{
    return !!(_tallRichHandsome.bits & TallMask);
}

- (BOOL)isRich
{
    return !!(_tallRichHandsome.bits & RichMask);
}

- (BOOL)isHandsome
{
    return !!(_tallRichHandsome.bits & HandsomeMask);
}

如此一来,我们就做到了优化了属性的内存空间,而且也实现了setter和getter

利用位运算进行位移枚举的实现

创建一个位移枚举,每一个值都对应一个二进制位

typedef enum {
	OptionsNone = 0,    // 0b0000
  	OptionsOne = 1<<0,   // 0b0001
	OptionsTwo = 1<<1,   // 0b0010
  	OptionsThree = 1<<2, // 0b0100
  	OptionsFour = 1<<3   // 0b1000
} Options;

和对应的枚举值进行按位与运算,就能得到是否存在该枚举值

@implementation ViewController

- (void)setOptions:(Options)options
{
    if (options & OptionsOne) {
        NSLog(@"包含了OptionsOne");
    }
    
    if (options & OptionsTwo) {
        NSLog(@"包含了OptionsTwo");
    }
    
    if (options & OptionsThree) {
        NSLog(@"包含了OptionsThree");
    }
    
    if (options & OptionsFour) {
        NSLog(@"包含了OptionsFour");
    }
}

- (void)viewDidLoad {
    [super viewDidLoad];
    
	[self setOptions: OptionsOne | OptionsFour];
}

@end

面试题

1.一个NSObject对象占用多少内存?

系统分配了16个字节给NSObject对象(通过malloc_size函数获得)
但NSObject对象内部只使用了8个字节的空间(64bit环境下,可以通过class_getInstanceSize函数获得)

2.看下面代码,分别描述Person和Student对应的内存占用

@interface Person : NSObject
{
    int _height;
}
@end

@interface Student : Person
{
    int _weight;
}
@end

Person *p = [[Person alloc] init];               
NSLog(@"%zd %zd", class_getInstanceSize([Person class]), // 16
              malloc_size((__bridge const void *)(p))); // 16
        
Student *s = [[Student alloc] init];
NSLog(@"%zd %zd", class_getInstanceSize([Student class]), // 16
              malloc_size((__bridge const void *)(s))); // 16

默认在64bit处理器下,由于Person继承自NSObject,所以根据内存对齐,系统给NSObject对象分配了16个字节存放isa指针。Person的成员变量height由于是Int类型,占用4个字节。因为isa指针实际只占用了8个字节,还有多余的8个字节空间,所以无需再多分配内存,那么Person的实际占用和系统分配都是16个字节(内存对齐一般以成员变量占比最大的倍数来增加:isa指针占用8个字节,占用最大,所以是8的倍数)

Student继承自Person,isa指针和成员变量height实际占用了12个字节,还有多余的4个字节。而成员变量weight正好又占用4个字节,那么也不用再分配更多的内存空间,Stuent对象的实际占用和系统分配也都是16个字节

3.看下面代码,描述Person的内存占用

@interface Person : NSObject
{
    int _age;
    int _height;
    int _no;
}
@end

Person *p = [[Person alloc] init];               
NSLog(@"%zd %zd", class_getInstanceSize([Person class]), // 24
              malloc_size((__bridge const void *)(p))); // 32

默认在64bit处理器下,由于Person继承自NSObject,里面的isa指针实际占用了8个字节,而Person里面有三个Int类型的成员变量,实际占用是12个字节,由于结构体的内存对齐原则,系统要分配24个字节(也就是3倍的isa指针的8个字节)才能容纳所有的成员变量,所以Person对象的实际占用为24个字节。

但系统本身都会以16的倍数来进行内存分配,所以要分配大于实际占用字节的两倍才可以,所以Person对象的系统分配为分配32个字节

4.看下面代码,简述Student的对象方法调用轨迹,然后分别注释掉 + (void)test 方法和 - (void)test 方法后会怎样调用

@interface NSObject (Test)

+ (void)test;
- (void)test;

@end

@implementation NSObject (Test)

+ (void)test
{
    NSLog(@"+[NSObject test] - %p", self);
}

- (void)test
{
    NSLog(@"-[NSObject test] - %p", self);
}

@interface Person : NSObject

+ (void)test;
- (void)test;
@end

@interface Student : Person

+ (void)test;
- (void)test;
@end


 Student *s = [[Student alloc] init];
[s test];
[Student test];

1.[s test] 这个方法调用,首先Student的实例对象会根据isa指针去Student的类对象里面查找- (void)test方法,如果找到了则调用该方法。如果没找到,那么就根据superclass指针去父类Person的类对象里查找,如果找到了则调用Person的- (void)test方法。如果没找到,那么就根据superclass指针去基类NSObject的类对象里查找,如果找到了则调用NSObject的- (void)test方法。

如果注释掉了NSObject的- (void)test方法,那么Student实例对象在基类NSObject的类对象里也找不到该方法,由于NSObject类对象的superclass指针指向nil,那么就会crash

2.[Student test] 这个方法调用,首先Student的类对象会根据isa指针去Student的元类对象里查找+ (void)test方法,如果找到了则调用该方法。如果没找到,那么就根据superclass指针去父类Person的元类对象里查找,如果找到了则调用Person的+ (void)test方法。如果没找到,那么就根据superclass指针去基类NSObject的元类对象里查找,如果找到了则调用NSObject的+ (void)test方法。

如果注释掉了NSObject的+ (void)test方法,那么Student的类对象在基类NSObject的元类对象里也找不到该方法,由于NSObject元类对象的superclass指针指向NSObject的类对象,所以就会调用NSObject类对象的- (void)test方法。

如果NSObject的两个方法都注释掉了,那么由于上一步的逻辑会去NSObject类对象里调用- (void)test方法,该方法也找不到,那么NSObject类对象的superclass指针是指向nil的,最后还是会crash

iOS的消息机制本质就是消息调用,所以不会真的区分类方法和对象方法,都是根据方法名进行查找

5.isMemberOfClass、isKindOfClass、isSubclassOfClass的区别,并说下原理

我们先通过一段代码打印可以得知

Person *person = [[Person alloc] init]; // Person对象
NSObject *obj = [[NSObject alloc] init]; // NSObject对象
   
Class person_class = [person class]; // Person类对象
Class obj_class = [obj class]; // NSObject类对象
   
Class person_meta_class = object_getClass(person_class); // Person元类对象
Class obj_meta_class = object_getClass(obj_class); // NSObject元类对象
   
Class person_meta_meta_class = object_getClass(person_meta_class); // NSObject元类对象
Class obj_meta_meta_class = object_getClass(obj_meta_class); // NSObject元类对象
   
// Person对象, NSObject对象, Person类对象,NSObject类对象
NSLog(@"%@, %@, %@, %@", person, obj, person_class, obj_class);
// Person元类对象, NSObject元类对象, NSObject元类对象,NSObject元类对象
NSLog(@"%@, %@, %@, %@", person_meta_class, obj_meta_class, person_meta_meta_class, obj_meta_meta_class);

isMemberOfClass

我们在objc4源码的NSObject.mm里可以看到,isMemberOfClass的类方法会拿到isa指针所指的对象和传进来的类型做比较;对象方法会拿当前类对象来做比较

+ (BOOL)isMemberOfClass:(Class)cls {
    return self->ISA() == cls;
}

- (BOOL)isMemberOfClass:(Class)cls {
    return [self class] == cls;
}

我们可以通过一段代码打印来分析比较

// Person类对象, Person类对象
NSLog(@"%d", [person isMemberOfClass:person_class]); // 1
// Person类对象, NSObject类对象
NSLog(@"%d", [person isMemberOfClass:obj_class]); // 0
// NSObject类对象, NSObject类对象
NSLog(@"%d", [obj isMemberOfClass:obj_class]); // 1
   
// Person元类对象, Person类对象
NSLog(@"%d", [person_class isMemberOfClass:person_class]); // 0
// Person元类对象, NSObject类对象
NSLog(@"%d", [person_class isMemberOfClass:obj_class]); // 0
// NSObject元类对象, NSObject类对象
NSLog(@"%d", [obj_class isMemberOfClass:obj_class]); // 0
   
// Person元类对象, Person元类对象
NSLog(@"%d", [person_class isMemberOfClass:person_meta_class]); // 1
// Person元类对象, NSObject元类对象
NSLog(@"%d", [person_class isMemberOfClass:obj_meta_class]); // 0
// NSObject元类对象, NSObject元类对象
NSLog(@"%d", [obj_class isMemberOfClass:obj_meta_class]); // 1
   
// 所有类型的元类对象的isa指针都指向NSObject的元类对象,包括NSObject的元类对象自己

// NSObject元类对象, Person元类对象
NSLog(@"%d", [person_meta_class isMemberOfClass:person_meta_class]); // 0
// NSObject元类对象, NSObject元类对象
NSLog(@"%d", [person_meta_class isMemberOfClass:obj_meta_class]); // 1
// NSObject元类对象, NSObject元类对象
NSLog(@"%d", [obj_meta_class isMemberOfClass:obj_meta_class]); // 1

isKindOfClass

isKindOfClass的类方法会拿到isa指针所指向的对象以及该对象的superclass指针所指向的对象和传进来的类型做比较;对象方法会拿当前类对象以及该对象的superclass指针所指向的对象来做比较

+ (BOOL)isKindOfClass:(Class)cls {
    for (Class tcls = self->ISA(); tcls; tcls = tcls->getSuperclass()) {
        if (tcls == cls) return YES;
    }
    return NO;
}

- (BOOL)isKindOfClass:(Class)cls {
    for (Class tcls = [self class]; tcls; tcls = tcls->getSuperclass()) {
        if (tcls == cls) return YES;
    }
    return NO;
}

我们可以通过一段代码打印来分析比较

// Person类对象, Person类对象
NSLog(@"%d", [person isKindOfClass:person_class]); // 1
// Person类对象, NSObject类对象
NSLog(@"%d", [person isKindOfClass:obj_class]); // 1
// NSObject类对象, NSObject类对象
NSLog(@"%d", [obj isKindOfClass:obj_class]); // 1
    
// Person元类对象, Person类对象
NSLog(@"%d", [person_class isKindOfClass:person_class]); // 0
// Person元类对象的superclass指向NSObject元类对象,而NSObject元类对象的superclass指向的就是NSObject类对象
// Person元类对象, NSObject类对象
NSLog(@"%d", [person_class isKindOfClass:obj_class]); // 1
// NSObject元类对象, NSObject类对象
NSLog(@"%d", [obj_class isKindOfClass:obj_class]); // 1
    
// Person元类对象, Person元类对象
NSLog(@"%d", [person_class isKindOfClass:person_meta_class]); // 1
// Person元类对象, NSObject元类对象
NSLog(@"%d", [person_class isKindOfClass:obj_meta_class]); // 1
// NSObject元类对象, NSObject元类对象
NSLog(@"%d", [obj_class isKindOfClass:obj_meta_class]); // 1
    
// NSObject元类对象, Person元类对象
NSLog(@"%d", [person_meta_class isKindOfClass:person_meta_class]); // 0
// NSObject元类对象, NSObject元类对象
NSLog(@"%d", [person_meta_class isKindOfClass:obj_meta_class]); // 1
// NSObject元类对象, NSObject元类对象
NSLog(@"%d", [obj_meta_class isKindOfClass:obj_meta_class]); // 1

isSubclassOfClass

isSubclassOfClass的类方法会拿到当前类对象以及superclass指针所指向的对象和传进来的类型做比较;该方法没有对象方法

+ (BOOL)isSubclassOfClass:(Class)cls {
    for (Class tcls = self; tcls; tcls = tcls->getSuperclass()) {
        if (tcls == cls) return YES;
    }
    return NO;
}

我们可以通过一段代码打印来分析比较

// Person类对象, Person类对象
NSLog(@"%d", [person_class isSubclassOfClass:person_class]); // 1
// Person类对象, NSObject类对象
NSLog(@"%d", [person_class isSubclassOfClass:obj_class]); // 1
// NSObject类对象, NSObject类对象
NSLog(@"%d", [obj_class isSubclassOfClass:obj_class]); // 1
    
// Person类对象, Person元类对象
NSLog(@"%d", [person_class isSubclassOfClass:person_meta_class]); // 0
// Person类对象, NSObject元类对象
NSLog(@"%d", [person_class isSubclassOfClass:obj_meta_class]); // 0
// NSObject类对象, NSObject元类对象
NSLog(@"%d", [obj_class isSubclassOfClass:obj_meta_class]); // 0
    
// Person元类对象, Person元类对象
NSLog(@"%d", [person_meta_class isSubclassOfClass:person_meta_class]); // 1
// Person元类对象, NSObject元类对象
NSLog(@"%d", [person_meta_class isSubclassOfClass:obj_meta_class]); // 1
// NSObject元类对象, NSObject元类对象
NSLog(@"%d", [obj_meta_class isSubclassOfClass:obj_meta_class]); // 1
    
// Person元类对象, Person类对象
NSLog(@"%d", [person_meta_class isSubclassOfClass:person_class]); // 0
// Person元类对象, NSObject类对象
NSLog(@"%d", [person_meta_class isSubclassOfClass:obj_class]); // 1
// NSObject元类对象, NSObject类对象
NSLog(@"%d", [obj_meta_class isSubclassOfClass:obj_class]); // 1

相关推荐

掌握 Objective-C 中的类和对象:构建面向对象的世界

掌握Objective-C中的类和对象:构建面向对象的世界Objective-C是一门面向对象的编程语言,它的核心概念之一就是类和对象。在这篇文章中,我们将深入探讨Objective-C中的...

深入探索 Objective-C 数据类型:从基本类型到对象类型

深入探索Objective-C数据类型:从基本类型到对象类型Objective-C是一门引入了面向对象概念的编程语言,它在C语言的基础上扩展了更多的功能和特性。在Objective-C中...

深入了解 Objective-C 中的协议与代理模式

深入了解Objective-C中的协议与代理模式在Objective-C中,协议和代理是两个关键的概念,它们为对象之间的通信和交互提供了灵活的机制。在本文中,我们将深入探讨协议的定义、遵循以及...

理解 Objective-C 基础:从 C 扩展到面向对象

理解Objective-C基础:从C扩展到面向对象Objective-C作为一门面向对象的编程语言,在苹果的开发生态系统中扮演着重要的角色。它融合了C语言的功能和面向对象的思想,为开发者...

使用Objective-C绘制基本图形的两种方式

Objective-C是一门面向对象的编程语言,广泛应用于iOS和macOS应用程序的开发。除了常规的编程功能之外,Objective-C还提供了绘制图形的API,可以用来绘制线条、矩形...

4月编程语言排行Fortran 击败 Objective-C

时隔7年,Objective-C首次跌出TOP20,Swift上位成功。随着跨平台工具的崛起,以及全球数字化进程的加快,还有多少单一做移动开发的程序员?...

IT博物馆之Objective-C诞生(博物馆icon)

1984年,Objective-C诞生。设计者:布莱德·考克斯(BradCox)、汤姆·洛夫(TomLove)Objective-C是面向对象的通用、高级编程语言。它扩展了标准的ANSIC,将...

Objective C浅拷贝和深拷贝(浅拷贝与深拷贝java)

ObjectiveC浅拷贝和深拷贝##浅拷贝浅拷贝就是对内存地址的复制,让目标对象指针和源对象指向同一片内存空间。如:char*str=(char*)malloc(100);char*s...

将Swift与Objective-C相结合(swift和objective-c)

原文:SwiftyObjective-C作者:PeterSteinberger/MichaelOchs/MatejBukovinski译者:孙薇审校:唐小引(@唐门教主),欢迎技术投...

使用Objective-C语言写一个&quot;Hello, World!&quot; 程序

本文默认您已安装好Objective-C开发环境。即在macOS上使用Xcode或者在命令行中使用clang编译器。Objective-C,是扩充C的面向对象编程语言,用于开发基于macO...

Objective-c 代理模式-面试之一(cglib代理原理)

一·什么是代理模式?代理模式是在oc中经常遇到的一种设计模式,那什么叫做代理模式呢?举个例子:有一个工人,他要找工作,可是他不知道哪儿有工作,于是他就找了人才中介,让中介去帮他找工作,于是他和中...

objective-C中Category分类你知多少

在OC中,使用category会让我们在开发中非常方便,可以为某个类增添方法,对类别自己有一点小小的体会,首先先来介绍一下类别1.分类的创建:1.类别的作用(1)可以将类的实现分散到多个不同的文件或...

深入了解 Objective-C 中的属性和方法

深入了解Objective-C中的属性和方法Objective-C是一门面向对象的编程语言,其中属性和方法是构建对象的核心组成部分。在本文中,我们将深入探讨属性和方法的概念,了解它们在Obje...

Apple离开了Objective-C,使iOS 13中Swift的使用率翻了一番

分析表明,苹果自己在iOS13中对Swift的使用增加了一倍,因为它与Objective-C的距离越来越远。Swift最初由Apple于2014年推出,现在已成为跨Cupertino平台进行开发的主...

大势所趋:Swift受欢迎度即将赶超Objective C

Swift是Apple在WWDC2014所发布的一门编程语言,用来撰写OSX和iOS应用程序。不到两年时间,在iOS开发者中Swift语言便凭借着简洁的语法和优秀的特性打动了开发者,之前用于iOS和...

取消回复欢迎 发表评论: