继承是所有高级语言都实现的一种特征,比如java、python甚至现在的js也有类似继承的写法。在表层目的是减少重复代码,深层目的是为了匹配业务与程序间的映射关系。
先来看下OOP设计思起中的继承的关系表示
再结合实例来看下完整的关系表示
一个简单的例子示例:
@interface SubClass: NSObject{ParentClass *par;
}
- (int) side;
- (int) area;
继承概述
子类会继承父类的所有功能和属性,在Objc中默认所有类的超类都是NSObject。比如初始化对象时常用的alloc和init方法就是在NSObject中定义的。继承的语法格式为:
@interface InterfaceName: SupperInterfaceName
NSObject类中提供了很多与继承相关的工具方法,比如:isKindOfClass、isMemberOfClass、performSelector等,这些方法的工作原理也比较简单,就是在子类中拥有一个父类的指针。函数方法被调用时先从子类中寻找,如果没有的话再去父类中寻找相应的方法。
super:指向父类的指针;
self:指向当前类的指针;
在Objc语言中不支持多继承,多继承可以用分类来实现。
@interface ParentClassName:NSObject
...
@end@interface ChildClassName:Fraction
...
@end
继承示例(一)
下图是一个简单的类图,定义一个抽象的图形父类,然后具像化两个标准的图形类:圆和正方形。
定义父类-Rectangle
#import <Foundation/Foundation.h>NS_ASSUME_NONNULL_BEGIN@interface Rectangle : NSObject{int size;int length;
}//公共方法
- (void) setSize:(int)size;
- (void) setLength:(int)length;//由各子类实现的个性化方法
- (void) print;@endNS_ASSUME_NONNULL_END//---------实现代码
#import "Rectangle.h"@implementation Rectangle
- (void) setSize:(int)si{size = si;
}
- (void) setLength:(int)le{length = le;
}
@end
定义子类1-Square
#import "Rectangle.h"NS_ASSUME_NONNULL_BEGIN@interface Square : Rectangle@endNS_ASSUME_NONNULL_END#import "Square.h"@implementation Square- (void) print{NSLog(@"This is Square Object, size=%d and length=%d", size, length);
}@end
定义子类2-Circle
#import "Rectangle.h"NS_ASSUME_NONNULL_BEGIN@interface Circle : Rectangle@endNS_ASSUME_NONNULL_END#import "Circle.h"@implementation Circle- (void) print{NSLog(@"This is Circe Object, size=%d and length=%d", size, length);
}@end
测试子类
#import <Foundation/Foundation.h>
#import "Square.h"
#import "Circle.h"
#import "Rectangle.h"int main(int argc, const char * argv[]) {@autoreleasepool {/* 注意上面两种调用方法的不同,通常建议的是第二种调用方式*/Square *square = [[Square alloc] init];[square setSize:1];[square setLength:2];[square print];Rectangle *s = [[Square alloc] init];[s setSize:5];[s setLength:6];[s print];Circle *circle = [[Circle alloc] init];[circle setSize:3];[circle setLength:4];[circle print];}return 0;
}
其程序执行过程如下:
扩展
扩展父类功能
在子类中可以扩展父类没有的属于特定子类特有的属性和方法,比如再新建一个名为RectExtProperty的类,其代码实现如下:
- 子类定义
@interface RectExtProperty : Rectangle{
@privateint radius; //扩展的属性,如果用private来修饰的话意味着不可再被继承
}- (void) setRadius; //扩展的方法@endNS_ASSUME_NONNULL_END
- 子类实现
#import "RectExtProperty.h"@implementation RectExtProperty
- (void) print{NSLog(@"This is Square Object, size=%d and length=%d", size, length);NSLog(@"radius=%d", radius);
}- (void) setRadius{radius = size * length;
}
@end
- 测试代码
RectExtProperty *rep = [[RectExtProperty alloc] init];[rep setSize:3];[rep setLength:4];[rep setRadius];rep.print;/*~~2024-03-26 22:58:45.575058+0800 inheritanceDemo[50224:5458478] This is Square Object, size=3 and length=4
2024-03-26 22:58:45.575079+0800 inheritanceDemo[50224:5458478] radius=12
*/
覆盖父类功能
**一般只有方法覆盖,不会存在属性覆盖的情况发生,如果属性覆盖则会报重名异常。**此种情况一般适用于当父类有了默认实现时,子类想扩展默认实现的功能时才会使用,还是拿上面的RectExtProperty为例。
在Rectangle类中添加默认实现,此时发现输出不变,因为父类的方法被子类覆盖掉了。
- (void) print{NSLog(@"I am a parentClass");
}
在RectExtProperty子类中添加以下代码,则就会打印上面的 I am a parentClass 这一行代码。注意这里的super用法,它表示指向父类的指针。
- (void) print{[super print]; //新增的代码NSLog(@"This is Square Object, size=%d and length=%d", size, length);NSLog(@"radius=%d", radius);
}
其调用链路如下:
继承示例(二)
这个例子和上面没啥区别,只不过是用一些注解简化了代码。
定义父类-Rectangle
#import <Foundation/Foundation.h>@interface Rectangle : NSObject{int flag;
}@property int width, height;- (int) area;
- (int) perimeter;
- (void) setWidth: (int)w andHeight: (int)h;@end//----------父类实现---------------------
#import "Rectangle.h"@implementation Rectangle- (void) setWidth: (int)w andHeight: (int)h{width = w;height = h;
}- (int) area{return width * height;
}- (int) perimeter{return (width+height)*2;
}@end
定义子类-Square
#import "Rectangle.h"@interface Square : Rectangle- (void) setSide: (int)s;
- (int) side;@end//------------子类实现-------------------
#import "Square.h"@implementation Square: Rectangle- (void) setSide: (int)s{[self setWidth:s andHeight:s];
}- (int) side{return self.width; //用这种方法可以取得父类的私有变量。
}@end
测试子类
#import "Square.h"int main(int argc, const char * argv[]) {@autoreleasepool {Square *square = [[Square alloc] init];[square setWidth:5];NSLog(@"This is = %i", square.perimeter);}return 0;
}
类定义常用的注解与注意事项
@class 类依赖优化
@class会创建一个前向引用,它是@import的替代品,它的作用:
- 可以缩短编译时间,编译效率会高于@import。比如有100个.m文件都导入了同一个.h文件,如果.h修改了则这100个.m文件都需要重新进行编译;
- 解决循环依赖的问题,比如A依赖B,B又依赖了A,如果使用@import那么会出现编译错误,使用@class则没有问题。
示例代码如下:
#import <Foundation/Foundation.h>
#improt "XYPoint.h" //待替换@implementation Rectangle{XYPoint *origin;
}
上述代码可替换为如下代码,@class XYPoint; 代码的意思就是XYPoint是一个类,程序只会通过指针来引用它,真正运行时再从指针地址取到实际的值。
#import <Foundation/Foundation.h>
@class XYPoint;@interface Rectangle:NSObject
@property int width, height;-(XYPoint *) origin;
-(void) setOrigin : (XYPoint *) pt;
这两个标签的区别,简单理解就好比是@import是复制代码到当前文件中,而@class是引用代码并不复制。
@selector SEL函数变量
可以为一个函数生成一个SEL类型的值,类似于一种安全检查,SEL可做为参数使用,也可用于对象是否拥有特定函数前的逻辑判断。比如如下代码,其实就是判断类Fraction是否实现了接口定义的setTo方法。
SEL action = @selector(setTo:over:); //为setTo方法生成一个SEL
Boolean boolean = [Fraction instanceMethodForSelector: @selector(setTo:over:)]; //验证类是否响应了setTo方法id obj;
[obj performSelector: setTo:over:] //调用setTo:over:方法
综合示例
Square *mySquare = [[Square alloc] init];
if ( [mySquare isMemberOfClass:[Square class]] == YES ){NSLog(@" yes");
}if ( [mySquare isKindOfClass:[Square class]] == YES ){NSLog(@" yes");
}if ( [mySquare respondsToSelector :@selector(setSide:)] == YES ){NSLog(@" yes");
}if ( [Square instancesRespondToSelector :@selector(setSide:)] == YES ){NSLog(@" yes");
}2023-12-20 21:09:50.751682+0800 FractionTest[35384:5245425] yes
2023-12-20 21:09:50.751721+0800 FractionTest[35384:5245425] yes
2023-12-20 21:09:50.751749+0800 FractionTest[35384:5245425] yes
2023-12-20 21:09:50.751776+0800 FractionTest[35384:5245425] yes
关于抽象类
在ObjectiveC并没有实现介于实现和接口中间状态的抽象类型(比如java中的抽象类)。但在ObjectiveC中也有抽象的概念,只不过它的抽象类有点特殊,ObjectiveC语言提供的抽象更像是一种中间层代理。比如Foundation中提供的 NSNumber 就是一个处理数字的抽象类,它有很多个子类但都是私有的。当使用 NSNumber 时系统会自运选择一个合适的子类来创建,这种处理方式在ObjC中有一个专门的名称,称为簇(cluster)。
ObjectiveC中的抽象类似于一个工厂设计模式,对比下传统OOP中抽象和ObjectiveC中的抽象设计可能会更清楚。
…end
下一节内容,笔者会详细讲述下ObjectiveC OOP编程中的分类的使用,分类是一个非常重要的特性,有了分类就可以很容易实现模块化开发。