真正理解Objective-C中的类(二)

这篇文章较长,请拿出一些耐心,一定能帮到你

Part 1 Objective-C 中的类

类的定义和简单解释

在面向对象程序设计,类(英语:class)是一种面向对象计算机编程语言的构造,是创建对象的蓝图,描述了所创建的对象共同的属性和方法。

类的更严格的定义是由某种特定的元数据所组成的内聚的包,它描述了一些对象的行为规则,而这些对象就被称为该类的实例。

简单解释一下以上文字描述:

  • 类作为一种聚合体,不仅仅聚合了数据,同时也聚合了一些方法在其内部
  • 这些方法就是这个类的实例对象所拥有的行为规则

注意第二条,类聚合的一些方法,就是这个类的实例对象所拥有的规则,再简明一点:

  • 类的内部聚合了实例方法

对的,类的内部拥有的方法都是实例方法,都是-号开头所定义的方法

你心中一定会有疑惑,因为平时用过类似以下使用类名调用的方法,例如:

...[NSObject alloc]...

以及

NSString * srr = [NSString stringWithFormat:@"%@",someStringObeject];

不要着急,后面会讲到。

一个基本的Objective-C类的表示:

下面继续以前一篇文章中讲到的类为例子看一下OC中一个具体的类

MTTStudent.h

#import <Foundation/Foundation.h>

@interface MTTStudent : NSObject {
    @public
    NSString *_name;
    NSUInteger _age;
    CGFloat _height;
}


@end

(实例变量属于 OC2.0以前的写法,2.0之后大多数使用属性,2.0兼容以前的版本)

解释一下以上代码:
  • MTTStudent类继承自NSObject,大家都知道NSObject类是所有OC对象的根类

  • {}内是该类的实例变量,也可以称为成员变量

  • @public关键字是对以下将要定义的实例变量的是在该类外部的访问限制修饰,默认是@protected,还有一个@private,从字面上不难看出其含义

接下来看看他的基本用法:
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // insert code here...
        MTTStudent * Kangkang = [[MTTStudent alloc] init];
        Kangkang->_age = 25;//实例变量age声明的时候是public,可在外部直接访问的
    }
    return 0;
}
  • 使用MTTStudent *生命了一个对象Kangkang,并对其进行初始化

  • 访问Kangkang对象的成员变量age,为其赋值为25

其中Kangkang->_age = 25;这一熟悉的语句,和结构体变量的访问如出一辙

但是,这里你需要细心一点,这是一个缩写缩写缩写实际的代码可以是:

(* &kangkang)->_age = 25;

所以Kangkang这个变量是指向一块某个地址的存放了某种结构数据的的一个指针变量,这种引用类型几乎覆盖类所有OC对象

所以基本上在OC中你使用的每一个对象都是在操作指针

类的实例方法

但是OC对象有一些比结构体多出来的特性,接下来将以上代码替换为下面的代码:

MTTStudent.h

#import <Foundation/Foundation.h>

@interface MTTStudent : NSObject {
    @public
    NSString *_name;
    NSUInteger _age;
    CGFloat _height;
}

- (void)writeArticles;

@end

MTTStudent.m

#import "MTTStudent.h"

@implementation MTTStudent

- (void)writeArticles {
    NSLog(@"feel good,feel high");
}

@end

以上代码,在MTTStudent.h中声明了一个方法- (void)writeArticles;,这是在 C 结构体中所没有的

这个就很熟悉,使用一个该类的实例可以调用实例方法

(但是如果要讲仅仅就这些,我就没有必要BB了)

Part 2 Objective-C 类的继承体系

前面提到过面对对象的三大特性:继承、封装、多态,下面的内容主要和继承有关

  • 继承关系的定义是怎么描述的需要你再回顾一下

  • 绝大部分OC对象的根类都是NSOject类, NSOject类是个什么东西,要好好一起思考一下了

  • 上面提到过Objective-C类的定义中,类的结构体内部只定义了实例方法,那么类方法在哪儿需要搞清楚

继承的特性

如果一个类A“继承自”另一个类B,就把这个A称为“B的子类”,而把B称为“A的父类”也可以称“B是A的超”。

  • 继承(inheritance)是面向对象软件技术当中的一个概念。

  • 继承可以使得子类具有父类的各种属性和方法,而不需要再次编写相同的代码。

  • 在令子类继承父类的同时,可以重新定义某些属性,并重写某些方法,即覆盖父类的原有属性和方法,使其获得与父类不同的功能。

  • 另外,为子类追加新的属性和方法也是常见的做法。

NSOject 的结构和功能

因为所有OC中的类都继承自NSOject类,根据继承的特点,我们只需要搞清楚这个NSOject类是个什么鬼就行了

我们来一起看一下Apple暴露出来的头文件

NSObject.h

@interface NSObject <NSObject> {
    Class isa ;
}
//方法列表省略很多,只保留一个
+ (void)load;
...
...
//方法列表结束

@end

  • 他的成员只有一个,Class isa ;,这一句表明,他的成员只有一个Class类型的变量,名字是isa,通常被称作”is a”指针

  • 这个”is a”指针变量的功能是定义了对象所属的类,例如:

    NSString * potinterVariable = @"Some String";
    

    这句中的potinterVariable 变量的‘is a’指针 就指向了NSString类,表明了potinterVariable这个对象所属的类是NSString

  • 类的定义中看到了+ (void)load;这是一个类方法

    刚才不是说类方法不是定义在类中的吗?这个问题有一丝丝蛋疼,类的方法信息是存放在其对应的结构体中的,后面再讲

我们再来看看这个Class类是个什么东西,点进去看一下Class的定义:

点进去到了objc.h中:

objc.h

typedef struct objc_class *Class;

前面回顾过typedef 的作用,一起看看这里是个什么鬼,慢慢来,这句代码的含义是:

  • 使用C的typedef为一个类型指定了一个别名,肯定是为一个复杂的结构类型取了一个简单别名

  • 这个复杂的类型结构是 truct objc_class

  • C语言中声明变量的时候*后面一定跟的是变量名,也就别名为Class

简洁一点:这句代码就是用typedefstruct objc_class这个结构体重新命名为Class

接下来在看看struct objc_class,这个结构体本身是个什么东西,点进去看一下:

runtime.h

struct objc_class {
    Class isa ;
    Class super_class ;
    //OC2.0之后精简了很多
} 

  • objc_class结构体本身也有一个“is a”指针

  • NSOject类中有一个”is a”指针 指向其所代表的类:

将上面的NSObject类的内部还原一下:

@interface NSObject <NSObject> {
   struct objc_class * isa ;   
}

@end

初步小结论

  • 某个类的实例的isa 指针指向了存放这个类信息的结构体

  • 使用这个实例的时候,数据全部来自于这个结构体

  • NSObject类的内部 有一个is a 指针变量,这个变量存放的是一个struct objc_class结构体的地址,也称为isa 指向的是一个objc_class结构体实例,这个结构体中的某一个字符串成员的值,在以后就被称为了这个类的类名

  • NSObject类的一些相关信息,包括实例方法全部存放在isa 指针指向的这个struct objc_class结构体中

  • NSObject类中的+方法(类方法)并不定义在这个struct objc_class结构体中

  • OC的类本质就是结构体,通过封装一些映射方法,使你能更加方便地通过类和对象的方式来操作和使用这个结构体中的数据(吐槽:为什么数据结构和算法就是编程的核心啊 :) )

  • 这个结构体存放了类的”元数据”(metadata),例如类的实例实现了哪些和哪几个方法,具备哪些和哪几个个实例变量等信息

特别注意objc_class结构体里面还需要注意另一个变量super_class

super_class指向的就是这个类的父类,super_class就是继承体系构成的关键,它的作用在运行时期某个对象调用方法的时候在某些情况下可以根据它的指向查找到父类中的实例变量和方法

对初步结论的深究

  • 元数据metadata是一个很抽象的概念,可以理解为一张需要填写诸多项目的表格中规定的项目名称

  • struct objc_class中的isa指针也是Classstruct objc_class)类型,感觉好奇怪,到底啥意思啊?

  • 类的类方法到底定义在哪里?

第二点和第三点是一个相关的问题,下面会讲清楚

如何理解指向是其本身类型的ISA指针

其实这个标题其实是很奇怪的,奇怪在哪儿呢?奇怪在于这句话有语意错误

  • 一个指针指向的应该是一个对象,而不是类型
  • 指针可以存放任何的地址,也就是它可以指向任何对象

之所以会拿出这个标题,是因为很多人在试图理解中的isa是什么东西的时候看到如下代码的时候发现 struct objc_class结构体的首个成员是Class isa ; ,而同时Class类型本身就是struct objc_class类型 ,于是懵逼了 :口,至少我是懵逼了很久,哈哈

runtime.h

struct objc_class {
    Class isa ;
    Class super_class ;
    ...
    ...
} 

看完下面之后你要是还懵逼的话可以私信联系我 ; )

类对象

也许你已经有过一个模糊的概念:Objective-C中的类也是一种对象?

首先确认一下这个说法的正确性:是对的。

类也是一种对象,可以把它叫做类对象

每个类对象都是程序运行期间的一个单例对象,也就是在程序运行期间只允许唯一的一个存在。
你的Xcode在你创建了重名的类的时候会抛出错误,这属于编译时期诊断出来的错误,IDE只是提前了这一步骤,告诉你每个类只允许存在一个。

类对象 所属的类被称为元类(metaclass),元类这个结构体很特殊,我们平时基本不可能直接使用到它。

当你创建一个类的实例时候,同时会创建与这个类唯一相关的元类,元类的结构和struct objc_class的结构是一致的,只是实例话之后的元类对象的存放地址可能不一样,这就是上面分析struct objc_class 的is a指针居然也是struct objc_class的原因,因为这时候的实例的is a指针指向这个类的结构体,而类的isa指针指向的是存放元类的结构体实例的地址

Part 3 总结

Objective-C中类的本质是对结构体的封装

你操作的所有的实例对象和类对象的数据以及其方法,都是通过OC中的类和对象的指针关系,最终到对应的结构体中去找到相应的数据和方法的,而方法的本质又是对数据的操作罢了,聚合这些数据的方式都是结构体

Objective-C 中的继承关系如下,需要注意的是 is a指针的指向:

《真正理解Objective-C中的类(二)》

文字再叙述:一遍:

  • 当你创建了某个类SomeClass class的时候,同时也会存在这个类的父类NSObejct class和这个类唯一相关的元类SomeClass metaclass

  • 当你实例化这个类的对象的时候,这个实例SomeClass instanceis a指针指向其类SomeClass classSomeClass类对象的is a指向唯一的SomeClass metaclass元类,类对象的super_class指针指向NSObject class 这个类,NSObject classisa 指针指向NSobject metaclass,同样的你的类对象的元类的super_class指针指向NSObject metaclass,还是很绕口,但是有必要理解清楚

真的清楚了吗?试着解释一下以下代码的含义,深入到结构体层面

  NSString * potinterVariable = @"Some String";

期待你的评论,与我一起交流

《真正理解Objective-C中的类(二)》 我的个人微信:lyle92

Objective-C 的对象好像并不是很强大?后面我会讲一讲runtime

参考:维基百科,《Effective Objective-C 2.0》

    原文作者:柳豪
    原文地址: https://www.jianshu.com/p/7e1e53755f35
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞