初始化方法与对象初始化
NSObject提供的init 方法虽然可以完成初始化,但由于它只是完成最基本的初始化,因此,对象的所有成员变量依然为0。
在实际编程过程中,我们可以提供了自己的init 方法,这个init 方法实际上就是重写NSObject的init方法。当开发者重写init方法时,开发者可以加入任意的自定义处理代码对属性执行初始化。
便利的初始化方法
重写init方法初始化总是固定的,不能根据参数执行动态初始化。实际上,我们还会根据业务需要,为Objective-C类提供更多便利的初始化方法,这些初始化方法通常会以init开头,并允许自带一些参数。
比如,如下程序定义了person类,该类会自定义一些更便利的初始化方法,这些初始化方法可以更加灵活地初始化person对象的属性
person.h
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface person : NSObject
@property (nonatomic , copy) NSString* name ;
@property (nonatomic , copy) NSString* age ;
- (id) myinit: (NSString*) name;
- (id) myinit1:(NSString*) name age:(NSString*) age;
@end;
NS_ASSUME_NONNULL_END
person.m
#import "person.h"
@implementation person
@synthesize name = _name;
@synthesize age = _age;
- (id) init
{
if (self = [super init])
{
self.name = @"付闯";
self.age = @"19";
}
return self;
}
- (id) myinit: (NSString*) name
{
if (self = [super init])
{
self. name = name;
self. age = @"20";
}
return self;
}
- (id) myinit1:(NSString*) name age:(NSString*) age
{
if (self = [self myinit: name])
{
self.age = age;
}
return self;
}
@end
main.m
#import <Foundation/Foundation.h>
#import "person.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
person* p = [[person alloc] init];
NSLog(@"他的名字为:%@,年龄为;%@", p.name, p.age);
person* p1 = [[person alloc] myinit:@"fuchuang"];
NSLog(@"他的名字为:%@,年龄为;%@", p1.name, p1.age);
person* p2 = [[person alloc] myinit1:@"fu chuang"age:@"20"];
NSLog(@"他的名字为:%@,年龄为;%@", p2.name, p2.age);
}
return 0;
}
第一个初始化方法创建的person对象的属性总是固定的,第二、三个初始化方法创建的person对象的属性会根据传入的参数动态改变。
类的继承
OC的继承通过父类的语法来实现,实现继承的类被称为子类,被继承的类被称为父类。OC的继承有单继承的特点,每一个子类只能有一个父类。
OC中子类继承父类的语法如下:
@interface SubClass : SuperClass {
//成员变量定义
}
//方法定义
@end
继承关系的本质是一种“由一般到特殊”的关系,子类继承父类,子类是一种特殊的父类。举个例子,苹果属于一种水果,苹果是水果的子类,是一种特殊的水果。或者说,苹果是水果的扩展,Apple类扩展了Fruit类。
当子类扩展父类时,子类继承的东西:
- 全部成员变量
- 全部方法(包括初始化方法)
main.m
#import <Foundation/Foundation.h>
#import "ClassB.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
ClassB* p = [[ClassB alloc] init];
p.name = @"fuchuang";
[p info];
}
return 0;
}
ClassA.h
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface ClassA : NSObject
@property (nonatomic , assign) NSString* name;
- (void) info;
@end
NS_ASSUME_NONNULL_END
ClassA.m
#import "ClassA.h"
@implementation ClassA
@synthesize name;
- (void) info
{
NSLog(@"%@", name);
}
@end
ClassB.h
#import "ClassA.h"
NS_ASSUME_NONNULL_BEGIN
@interface ClassB : ClassA
@end
NS_ASSUME_NONNULL_END
ClassB.m
#import "ClassB.h"
@implementation ClassB
@end
上面程序的main()函数创建了ClassB对象之后,可以访问该ClassB对象的name属
性和info方法,这表明FKApple对象也具有weight属性和info方法,这就是继承的作用。
重写父类
子类扩展了父类,子类是一个特殊的父类。大部分时候,子类总是以父类为基础,额外增加新的Field和方法。但有一种情况例外:子类需要重写父类的方法。例如,鸟类都包含了飞翔的方法,其中鸵鸟是一种特殊的鸟类,因此,鸵鸟应该是鸟的子类,它也将从鸟类获得飞翔方法,但这个飞翔方法明显不适合鸵鸟,为此,鸵鸟需要重写鸟类的方法。
main.m
#import <Foundation/Foundation.h>
# import "Strich.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
Strich* p = [[Strich alloc] init];
[p fly];
[p info];
}
return 0;
}
Bird.h
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface Bird : NSObject
- (void) fly;
@end
NS_ASSUME_NONNULL_END
Bird.m
#import "Bird.h"
@implementation Bird
- (void) fly
{
NSLog(@"我可以飞");
}
@end
Strich.h
#import "Bird.h"
NS_ASSUME_NONNULL_BEGIN
@interface Strich : Bird
- (void) info;
@end
NS_ASSUME_NONNULL_END
Strich.m
#import "Strich.h"
@implementation Strich
- (void) fly
{
NSLog(@"我只能跑");
}
- (void) info
{
[super fly];
}
@end
执行上面的程序,将看到执行[p fly];时不再执行Bird类的fly方法,而是执行Stidh类的fly方法。
这种子类包含与父类同名方法的现象被称为方法重写,也被称为方法覆盖(Override)。可以说,子类重写了父类的方法,也可以说子类覆盖了父类的方法。
super关键字
如果需要在子类方法中调用父类被覆盖的实例方法,可使用super关键来调用父类被覆盖
的实例方法(上面代码已实现)。
super是OC提供的一个关键字,用于限定该对象调用从它父类继承得到的属性或方法。与self类似,super也不能出现在类方法中。因为类方法的调用者只能是类本身,而不是对象。
成员变量重名
OC的子类接口部分不允许定义与父类接口部分重名的成员变量,因为子类本就继承父类的成员变量。但类实现部分定义的成员变量被限制在该类内部,因此父类实现部分定义的成员变量不会限制子类定义重名的成员变量。反过来说,子类在实现部分定义的成员变量也不受父类接口部分定义的成员变量影响,若这样定义,父类在接口部分定义的成员变量将会隐藏。因此,子类方法很难直接访问父类的成员变量,此时可以通过调用父类的方法来访问父类中被隐藏的成员变量。
#import <Foundation/Foundation.h>
@interface Parent : NSObject {
int _a;
}
@property(nonatomic, assign)int a;
@end
@implementation Parent
@synthesize a = _a;
- (id)init {
if (self = [super init]) {
self->_a = 5;
}
return self;
}
@end
@interface Sub : Parent
- (void)accessOwner;
@end
@implementation Sub {
int _a;
}
- (id)init {
if(self = [super init]) {
self->_a = 7;
}
return self;
}
- (void)accessOwner {
NSLog(@"子类中_a成员变量:%d", _a);
NSLog(@"父类中被隐藏的_a成员变量:%d", super.a);
}
@end
int main() {
@autoreleasepool {
Sub *sub = [[Sub alloc] init];
[sub accessOwner];
}
}
程序通过super关键字强制指定调用父类的a属性(实际上就是获取getter方法返回值),通过这种方式可以访问到父类中被隐藏的成员变量。
多态
OC种指针类型变量有两个:一个是编译时的类型,一个是运行时的类型;编译时的类型由声明该变量时使用的类型决定,运行时使用的类型由实际赋给该变量的对象决定。如果编译时类型和运行时类型不一致,就可能出现多态
main.m
#import <Foundation/Foundation.h>
#import "ClassB.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
ClassA* bc = [[ClassA alloc] init];
[bc base];
[bc test];
ClassB* sc = [[ClassB alloc] init];
[sc base];
[sc test];
[sc sub];
ClassA* cd = [[ClassB alloc] init];//只能调用子类中在父类存在的方法
[cd base];
[cd test];
//[cd sub];
id sd = cd; //转化为ClassB类型
[sd sub];
}
return 0;
}
ClassA.h
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface ClassA : NSObject
- (void) base;
- (void) test;
@end
NS_ASSUME_NONNULL_END
ClassA.m
@implementation ClassA
- (void) base
{
NSLog(@"父类的普通base方法");
}
- (void) test
{
NSLog(@"父类被覆盖的test方法");
}
@end
CLassB.h
#import "ClassA.h"
NS_ASSUME_NONNULL_BEGIN
@interface ClassB : ClassA
- (void) sub;
@end
NS_ASSUME_NONNULL_END
ClassB.m
#import "ClassB.h"
@implementation ClassB
- (void) test
{
NSLog(@"子类覆盖父类的test方法");
}
- (void) sub
{
NSLog(@"子类的sub方法");
}
@end
该程序中main函数创建了4个指针类型的变量。对于前两个变量bc和sc,他们编译和运行时类型相同,因此调用方法正常。
而对于第三个指针变量cd来说,它编译时类型是ClassA,运行时类型是ClassB,当调用该指针的test方法时,此时调用的是该指针变量实际所指对象的方法,所以实际执行的是ClassB类中覆盖后的test方法,这就出现了多态。
对于第四个指针变量sd来说,它是id类型声明的变量,因此程序会自动追踪对象所属的类,并成功调用sub方法。因此我们说“通过id类型的变量来调用方法时,OC会执行动态绑定,即OC会追踪对象所属的类,在运行时判断对象所属的类、确定需要动态调用的方法,而不是在编译时确定”。
在OC中,子类是一种特殊的父类,因此允许把一个子类对象直接赋给一个父类指针变量,这被称为向上转型(upcasting),由系统自动完成。
当完成该操作后,该变量编译时类型与运行时类型不一样,其方法总是表现出子类的方法特征,出现了相同类型的变量调用同一个方法时呈现出多种不同的行为特征,这就是多态。
指针变量的强制类型转换
编写OC程序时,除了id变量外,指针变量只能调用它编译时类型的方法。如果要让这个指针变量调用运行时类型的方法,必须强制转换。
OC为我们提供了类型转换运算符(),与基本数据类型的转换类似,其用法为 (type*)variable ,这种用法将variable转换成一个type类型的变量。
需要注意的是,这种强制转换要满足向上转型,类似于c语言中无法将字符串转换为整数形式,两者必须满足“父子关系”才能转换。
判断指针变量的实际类型
为保证程序正常运行,在强制转换之前要先判断能否强制转换(满足父子关系),OC为我们提供了两个方法:
– (BOOL)isKindOfClass:clazz 判断该对象是否为clazz或其子类的实例
– (BOOL)isSubclassOfClass:clazz 判断该对象是否为clazz子类的实例
上面两个方法可使用任何对象作为调用者,接着向该方法传入任意类,该方法将会返回一
个BOOL类型的值,用于表明该变量实际指向对象的类型。
#import <Foundation/Foundation.h>
int main() {
@autoreleasepool {
//声明hello时使用的是NSObject类
//hello的编译类型是NSObject
//hello的实际行类型是NSString
NSObject *hello = @"hello";
NSLog(@"字符串是否为NSObject的实例:%d", ([hello isKindOfClass:[NSObject class]]));
NSLog(@"字符串是否为NSString的实例:%d", ([hello isKindOfClass:[NSString class]]));
NSLog(@"字符串是否为NSDate的实例:%d", ([hello isKindOfClass:[NSDate class]]));
NSString *a = @"hello";
NSLog(@"a是否为NSDate的实例:%d", ([a isKindOfClass:[NSDate class]]));
}
}
在实际程序中,使用该方法可以先判断前一个对象是否是该类的实例,是否可以成功转换,从而保证代码更加健壮。