我的Objective-C语言学习笔记(四)

我的Objective-C语言学习笔记(四)

4.0 继承

4.1 Xcode文档

我们知道Xcode中苹果公司提供了很多很多的框架,这些框架中有很多类、很多函数,还提供了很多数据类型。但是我们不禁要想:我们要怎么样才能知道到底有哪些类、哪些框架、哪些函数,以及如何去使用这些类呢?毕竟不可能一下子学习所有的框架、类和函数。

其实所有的这一切,苹果公司提供了Xcode文档,详细描述了这些信息。

快捷方式shift+command+0就可以打开Developer Documentation。这就是我们说的Xcode文档,这个文档中包含了所有Xcode中涉及的框架、类、函数等的定义、使用方法等,查起来很方便简单。希望今后能够常常使用。

4.2 static关键字

我们在C语言中已经学过了static关键字。

它可以修饰局部变量、全局变量以及函数。

这里就不多赘述。

其实,static关键字在OC中也是可以使用的。

但是,static在OC中无法修饰属性和方法。

不过,它可以修饰方法中的局部变量。如果它修饰了方法中的局部变量,那么这个变量就会被变成静态变量,存储在常量区,当方法执行完毕后,不会回收,下次再执行这个方法的时候,直接使用而不会再声明了。

4.3 self的使用方法

我们可以做一个试验,在方法的内部定义一个和属性名相同的局部变量,我们可以发现如果在方法中访问这个同名的变量,得到的结果是局部变量的值,而不是对象属性的值。

那这个时候,我就是想要访问这个对象属性的值呢?

在一个对象方法中要调用当前对象的另外一个对象方法要怎么办呢?

这个时候就要引入self这个关键字。

它可以在对象方法和类方法中使用。self是一个指针,在对象方法中self指向当前对象,在类方法中self指向当前类。

self用在对象方法中

self在对象方法中指向当前对象。

当前对象:谁调用方法谁就是当前对象。

那么就可以使用self来访问当前对象的属性。

例如:self->属性名,这代表访问的是当前对象的这个属性。

也可以使用self来调用当前对象的其他对象方法。

以下场景必须使用self:

如果在方法中存在和属性同名的局部变量,如果想要方法挡墙对象的同名属性,就必须使用self。

在对象方法中,如果要调用当前对象的其他的对象方法,必须使用self。

不过,我们知道,按照定义属性时我们提到的规范,所有的属性名都应以下划线开头,例如_name等,局部变量不要求以下划线开头,如果按照这个规范来的话,实际上在方法内部是不会存在重名的问题的。所以,只要按照这个规范,我们能用到self的地方就只有在对象方法中调用当前对象的其他对象方法这一条了。

self用在类方法中

在类方法中,self也是一个指针,指向当前这个类在代码段中的地址。self在类方法中就相当于是当前这个类。

这里总结一下取到类在代码段中的地址的方式:

调试查看对象的isa指针的值

在类方法中查看self的值

调用对象的对象方法class,就会返回这个对象所属的类在代码段中的地址

调用类的类方法class就会返回这个类在代码段中的地址

什么场景下可以在类方法中使用self:

可以在类方法中使用self来调用本类的其他类方法。如果要在当前类方法中调用本类的其他类方法,虽然可以直接使用类名,但是还是建议使用self。

总结一下注意事项:

在对象方法中,self代表当前对象,所以可以通过self访问当前对象的属性,但是在对象方法中不能使用self调用当前类的类方法

在类方法中,self代表当前这个类。所以可以通过self调用当前类的其他类方法,但是在类方法中不能使用self来访问当前类的对象的属性和调用对象方法。

4.4 继承的使用方法

我们学习到现在已经会发现,不同的类之间有可能会有相同名字的属性,目前的做法是通过复制黏贴的方式来创建。但是,这种做法会导致代码冗余,而且后期如果需要修改一个属性名维护起来非常不方便。

而发明OC的人其实也意识到了这个问题,于是就引入了“继承”这个概念。

继承的目的是子类想要拥有父类中的所有属性,但是并不通过自己去定义,而是想凭空拥有。

继承的语法:

@interface 类名 : 父类的名字

@end

继承的效果:

子类一旦继承了父类,那么子类就拥有了父类的所有属性和行为,不用自己定义。

几个术语:

假设@interface Student:Person

我们说Student类从Person类继承,Student类是Person类的子类,Person类是Student的父类

也可以说Student类从Person类派生,Student类是Person类的派生类,Person类是Student类的基类。

注意事项:

在Xcode中,新建一个类时,在选择了Cocoa Class之后,我们可以在Subclass of里面写上想要继承的父类名,既可以获得相关的代码,而不需要自己再写了。

继承是类在继承,而不是对象继承。子类从父类中继承,子类中就拥有了父类中定义的所有属性,但这只是类继承。创建的对象与对象之间是没有继承关系的。

什么时候应该使用继承?如果发现另外一个类中的成员我也想有,那么这个时候就可以使用继承。但千万不要为了继承而去继承,不要为了仅仅是为了得到某个类的属性和行为而就不顾伦理的去乱继承。

一般来说满足继承的关系要满足is a……这种条件。即当A类是一个B类的时候,那么A类就可以从B类继承。

如果有一个属性或行为不是所有子类都拥有的,那么这个属性或行为就不应该定义在父类之中。

父类中只定义所有的子类都拥有的。

子类中不能存在和父类同名的属性和行为。因为子类已经继承了父类所有的属性和行为,如果再定义一个同名的属性和行为,就相当于变成了重复定义,这很明显是冲突了。

继承的特点:

单根性

即一个类只能有一个父类,不能有多个父类。

传递性

A类从B类继承,B类从C类继承,那么A类就同时拥有B和C类所有的属性和行为。

NSObject类:

我们前面一直在使用的NSObject其实也是一个类,它是Foundation框架中的类,在这个类中有一个类方法叫做new,这个方法是用来创建对象的。这个方法的返回值是创建的这个对象的指针。

也就是说,如果要创建类的对象,就必须要调用这个new方法。也就是说我们必须直接的或者间接的从NSObject类继承。

另外,NSObject类之中还定义了一个属性,这个属性叫做isa指针,所以每一个子类对象中都有一个叫做isa的指针。

综上所述,NSObject类是OC所有类的父类,因为OC中的类全部都是直接或者间接的从它继承而来。

super关键字:

super关键字可以用在类方法和对象方法中。

在对象方法中可以使用super关键字调用当前对象从父类继承过来的对象方法。这个时候super的作用类似于self,用法也是一样的。

在类方法中,也可以使用super关键字调用当前类从父类继承过来的类方法。类方法也能被子类继承,父类中的类方法可以使用父类名来调用,也可以使用子类名来调用,还可以使用self来调用,并且也可以使用super来调用。

但是super不能像self那样来访问属性。

所以super特指这个方法是从父类继承过来的。所以如果有一个方法是从父类继承过来的,那么最好是使用super关键字。

最后我们来总结一下继承:

子类从父类继承相当于子类模板中拥有了父类模板中所有的内容。

创建一个子类对象,仍然是根据子类模板来创建对象,只不过子类模板中拥有父类模板中所有的内容。所以子类对象中既有子类的内容也有父类的内容。

4.5 访问修饰符

访问修饰符是用来修饰属性,可以限定对象的属性在哪一段范围之中访问。

下面介绍一下常见的一些访问修饰符:

@private

这个修饰符表示私有,被它修饰的属性只能在本类的内部访问。只能在本类的方法实现中访问。

@protected

这个修饰符表示受保护的,被它修饰的属性只能在本类和本类的子类中访问。只能在本类和本类的子类的方法实现中访问。

@package

这个修饰符表示被它修饰的属性,可以在当前框架中访问。

@public

这个修饰符表示公共的,被它修饰的属性可以在任意的地方被访问。

如果不为属性指定访问修饰符,那么默认是@protected。

另外,子类仍然可以继承父类的私有属性,只不过在子类中无法直接访问从父类继承过来的私有属性。但是如果父类中有一个方法为属性赋值或者取值的话,那么子类就可以间接的使用这些方法来访问父类的私有属性。

访问修饰符的作用域:

从当前访问修饰符的地方开始往下,直到遇到另外一个访问修饰符或者大括号技术为止,中间所有的属性都应用当前这个访问修饰符。

使用建议:

@public一般情况下都不要使用,属性不建议直接暴露给外界。

@private 如果属性只想在本类中使用,不想在子类中使用,那么就使用这个修饰符。

@protected 如果你希望属性只在本类和本类的子类中使用,推荐一般情况下都使用这个修饰符

最后请注意:访问修饰符只能用来修饰属性,不能用来修饰方法。

4.6 私有属性和私有方法

上一节我们介绍了几个属性的访问修饰符,这一节重点讲一下私有属性和私有方法。

我们在一个类的属性定义前添加了访问修饰符@private之后,我们说这个类的属性就变成了私有属性。但是,我们会发现在main函数中如果创建了一个这个类的对象以后,我们输入“对象名->属性名”的时候还是会发现Xcode会提示这个类对象有哪些属性,只是这些属性划了红线表示无法访问。

这个时候,我们不禁要想,虽然是私有属性,但是外界还是能够知道这个类对象中有哪些属性,能不能改成不提示让外界不知道这个类中有哪些属性呢?

要实现这种目的,我们可以这样操作:将属性放进大括号中,并放置在“@implementation 类名”下。

这个时候这些属性就变成了真正的私有,连提示都不会有了。

其实将属性定义在@implementation之中和将属性定义在@interface之中并标记为@private的唯一区别就是在Xcode中提示与不提示,都不能在外界访问。

现在我们再来谈谈私有方法:

我们知道,默认状态下,无论怎样声明和实现了的方法在外界都可以被调用。

那要怎样才能不让外界调用一些方法呢?

其实很简单,一个方法如果只写实现而不写声明的时候就变成了私有方法。即只在@implementation中写实现,而不在@interface中写声明就可以了。这个时候外界是无法调用这个方法的,但是类内部却是可以使用这个方法的。

这就是我们要介绍的私有属性和私有方法。

4.7 里氏替换原则

我们先来假设一种情况:

我们建立了两个类,分别是Person和Student,其中Student是从Person继承的。

在main函数中,我们输入了如下代码:

Person *p1 = [Person new];
Student *s1 = [Student new];
Person *p2 = [Student new];

你会发现,第三句代码是否是有问题的,因为p2的类指针类型是Person类型的,但是却指向了Student类中的一个新对象。可是,这样写用Xcode编译了以后却没有报错警告,这是怎么回事?这样做可以吗?

我们从逻辑上来谈谈这个情况,第三句代码的意思是我们定义了一个Person类的指针p2,它指向的对象是一个Student类型的新对象。这在逻辑上是完全说得通的,难道不是吗?Student继承了Person类的所有属性和行为,可以这么说“Student is a Person“,所以这种情况是完全可行的。

其实,我们把这种情况叫做里氏替换原则(LSP)。

里氏替换原则是子类可以替换父类的位置,并且程序的功能不受影响。

LSP 里氏替换原则的作用:

一个指针不仅可以存储本类对象的地址,还可以存储子类对象的地址。

如果一个指针的类型是NSObject类型的,那个这个指针可以存储任意的OC对象地址。

如果一个数组的元素的类型是一个OC指针类型,那么这个数组中不仅可以存储本类对象,还可以存储子类对象。

如果一个数组的元素是NSObject指针类型,那么意味着任意的OC对象都可以存储在这个数组之中。

如果一个方法的参数是一个对象,那么我们在为这个参数传值的时候,可以传递一个本类对象,也可以传递一个子类对象,对方法中的代码不会有丝毫的影响。

需要注意的是:当一个父类指针指向一个子类对象的时候,通过这个父类指针就只能调用子类对象中的父类成员。子类独有的成员是无法访问的。

4.8 方法重写

我们知道子类从父类继承,子类就继承了父类的方法。子类继承了父类的方法,就意味着子类拥有了这些功能。但是有时候,虽然子类也拥有父类的行为,但是这个行为的具体实现可能会和父类有所不同。

那要怎么做呢?

这个时候需要进行方法重写。

如何方法重新呢?很简单,直接在类的实现中将这个方法单独实现一遍不同的代码就可以了。

注意:当一个父类指针指向一个子类对象的时候,通过这个父类指针调用的方法,如果这个方法在子类中被重写了,调用的就是子类重写的方法。

4.9 多态

多态指的是同一个行为对于不同的事物具有完全不同的表现形式。

我们举个例子:

有这样三个人,分别是医生、演员和理发师。首先他们肯定都是人类,然后他们都有一个行为名字叫做cut。但是当执行cut这个行为的时候,三个人得到的结果是不同的。

医生可能是做手术的cut

演员可能是完成拍摄工作的cut

理发师可能是剪发的cut

这种同一个行为,具备的多种形态就是多态。

而OC代码中也是有一样的情况的。比如我们定义了人类,有三个子类,比如说北京人、上海人和广州人。这三个子类都有一个行为是sayHi。我们知道在编写这三个子类的时候,这个sayHi的行为是需要方法重写的,因为这三个子类的sayHi行为使用的语言都是不同的,北京人用北京话,上海人用上海话,广州人用粤语。所以,我们在设计这些代码时就需要考虑这些问题,而这种情况就是多态。

4.10 description方法

我们知道在使用NSLog的时候使用%p打印的是指针变量的值,使用%@打印的是指针指向的对象。

如果我们使用%@打印一个对象,它输出的是:

<对象所属的类名: 对象的内存地址>

为什么会是这样的结果呢?

原理:

当我们使用%@打印一个对象的时候,NSLog函数的底层实现:

调用传入的对象的description方法。

拿到这个方法的返回值,这个返回值是一个字符串

将这个字符串输出

那么description是什么呢?

description方法是定义在NSObject类之中的,所以每一个OC对象都有这个方法。

这个方法在NSObject类中的实现是这样的:

返回的字符串格式:@“<对象所属的类名: 对象地址>”

我们可以通过方法重写的方式来重写description方法来实现打印特定对象的内容,而不是只打印其内存地址。

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注

Optimized with PageSpeed Ninja