------Java培训、Android培训、iOS培训、.Net培训、期待与您交流! -------
OC回顾-面向对象
一、self和super关键字
1、介绍
OC提供了两个保留字self和super,用于在方法定义中引用执行该方法的对象。
OC语言中的self,就相当于C++、Java中的this指针。
设置器与访问器,提供外接操作类内部属性的一个通道。
假如不提供这两个方法,那么这个属性的值,就不能为外界改变。
因为类的属性,默认是@protect(受保护类型)。属性一般是私有的。
super发送消息可以将方法实现分发其父类。
可以重写一个已有的方法来修改或将它添加到现有的方法中,仍然可以将原始的方法纳入到修改后的方法当中。
2、self的应用场景
1)用在类方法中
2)用在对象方法中
3)访问成员变量
4)self在OC的内存管理特殊使用
二、self用在对象方法中
self在对象方法中使用,其指代的是调用当前方法的对象。
使用场景:
在一个类中,如果想要调用本类的其他方法,就使用[self 方法名]来调用,而self就代表当前对象。谁调用这个方法,self就代表谁。
//主函数
#import <Foundation/Foundation.h>
#import "Dog.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
Dog *dog=[Dog new];
[dog eat:@"骨头"];
//self和dog的地址相同
NSLog(@"%p",dog);
}
return 0;
}
//Dog类的声明
// Dog.h
#import <Foundation/Foundation.h>
@interface Dog : NSObject
-(void)run;
-(void)eat:(NSString *) food;
@end
//Dog类的实现
// Dog.m
#import "Dog.h"
@implementation Dog
-(void)run{
NSLog(@"旺财正在跑!");
}
-(void)eat:(NSString *) food{
NSLog(@"旺财正在吃%@",food);
[self run];
NSLog(@"%p",self);
}
@end
运行结果:
2015-10-24 19:13:00.599 3-self用在对象方法中[825:67021] 旺财正在吃骨头
2015-10-24 19:13:00.599 3-self用在对象方法中[825:67021] 旺财正在跑!
2015-10-24 19:13:00.600 3-self用在对象方法中[825:67021] 0x100300080
2015-10-24 19:13:00.600 3-self用在对象方法中[825:67021] 0x100300080
Program ended with exit code: 0
三、self用在类方法中
self在类方法中使用其指代的是当前类。
此时不能再用[self 对象方法名] 来调用对象方法,因为此时self代表的是类。
//主函数
// main.m
#import <Foundation/Foundation.h>
#import "Person.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
Person *person=[Person new];
[Person run];
//[person calss]代表类
NSLog(@"%p",[person class]);
}
return 0;
}
//Person类的声明
// Person.h
#import <Foundation/Foundation.h>
@interface Person : NSObject
+(void)sing;
+(void)run;
@end
//Person类的实现
// Person.m
#import "Person.h"
@implementation Person
+(void)sing{
NSLog(@"人正在唱歌!");
}
+(void)run;{
NSLog(@"人正在跑!");
[self sing];
NSLog(@"%p",self);
}
@end
运行结果:
2015-10-24 19:15:17.606 4-self用在类方法中[835:68614] 人正在跑!
2015-10-24 19:15:17.607 4-self用在类方法中[835:68614] 人正在唱歌!
2015-10-24 19:15:17.607 4-self用在类方法中[835:68614] 0x1000011c0
2015-10-24 19:15:17.607 4-self用在类方法中[835:68614] 0x1000011c0
Program ended with exit code: 0
四、self修饰实例变量
self在对象方法中,可以利用self->属性名称 的方式访问成员变量
应用场景:
存在局部变量名和成员变量名同名的时候
//
// main.m
#import <Foundation/Foundation.h>
#import "Dog.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
Dog *d=[Dog new];
[d setSpeed:80];
NSLog(@"狗的速度是:%d",[d speed]);
}
return 0;
}
//
// Dog.h
#import <Foundation/Foundation.h>
@interface Dog : NSObject
{
int speed;
}
-(void)setSpeed:(int) speed;
-(int)speed;
@end
//
// Dog.m
#import "Dog.h"
@implementation Dog
-(void)setSpeed:(int) speed{
//speed = speed;是不能给成员变量赋值的,局部变量屏蔽全局变量
self->speed=speed;//用self可以给成员变量赋值。
}
-(int)speed{
return speed;
}
@end
运行结果:
2015-10-24 19:16:47.395 5-self修饰变量[845:69797] 狗的速度是:80
Program ended with exit code: 0
self使用总结
self 谁调用当前方法,self就代表谁
self在对象方法中,self代表当前对象;
self在类方法中,self代表类
[self 方法名称] 调用其他方法(类方法/对象方法)
注意:
如果有同名的对象方法和类方法存在的情况下,self不会调错。
五、类的继承和派生
1、类的继承和派生的概念。
例如:
交通工具是一个基类(也称为父类),通常情况下所有交通工具所共同具有的特性是速度与额定载人的数量。
汽车类和飞机类同样具有速度和额定载人数量这样的特性,而且飞机类和汽车类的特性是在交通工具原有特性基础上增加而来的,那么飞机类和汽车类就是交通工具类的派生类(也称为子类)。依次类推,层层递增,这种子类获得父类特性的过程就是继承。
2、类的继承的代码实现
//主函数
#import <Foundation/Foundation.h>
#import "Animal.h"
#import "Dog.h"
#import "BigYellowDog.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
BigYellowDog *dog=[BigYellowDog new];
[dog setAge:1];
NSLog(@"大黄狗的狗龄是:%d",[dog age]);
[dog run];
[dog protect];
}
return 0;
}
//父类的声明
// Animal.h
#import <Foundation/Foundation.h>
@interface Animal : NSObject
{
int _age;
}
-(void)setAge:(int) age;
-(int)age;
-(void)eat:(NSString *) foodName;
-(void)run;
@end
//父类的实现
// Animal.m
#import "Animal.h"
@implementation Animal
-(void)setAge:(int) age{
_age=age;
}
-(int)age{
return _age;
}
-(void)eat:(NSString *) foodName{
NSLog(@"正在吃%@",foodName);
}
-(void)run{
NSLog(@"正在跑!!!");
}
@end
//Dog类的声明
// Dog.h
#import "Animal.h"
@interface Dog : Animal
-(void)protect;
@end
//
// Dog.m
#import "Dog.h"
@implementation Dog
-(void)protect{
NSLog(@"狗狗正在戒备!!!");
}
@end
//
// BigYellowDog.h
#import "Dog.h"
@interface BigYellowDog : Dog
-(void)catchMouise;
@end
//
// BigYellowDog.m
#import "BigYellowDog.h"
@implementation BigYellowDog
-(void)catchMouise{
NSLog(@"狗拿耗子,多管闲事!!!");
}
@end
运行结果:
2015-10-24 19:19:29.716 8-类的继承的代码实现[857:71933] 大黄狗的狗龄是:1
2015-10-24 19:19:29.716 8-类的继承的代码实现[857:71933] 正在跑!!!
2015-10-24 19:19:29.716 8-类的继承的代码实现[857:71933] 狗狗正在戒备!!!
Program ended with exit code: 0
六、基类和派生类之间的关系
1、派生类
派生类方法属性=基类方法属性+派生类自己新增的方法和属性
注意:
1)基类的私有属性能被继承,但不能被使用。
2)OC中继承是单继承:也就是说一个类只能有一个父类,不能继承多个父类。
3)继承的合理性:“人是人他妈生的,妖是妖他妈生的!”
2、方法的重写
从父类继承的方法,可能并不适合子类,可以在子类中重写父类的方法。
重写之后,父类的对象调用父类的方法;子类的对象,调用子类的方法,不会引起冲突。
从父类继承的方法,不适用于子类时,可以直接声明父类的同名方法,并定义。
//主函数
// main.m
#import <Foundation/Foundation.h>
#import "Animal.h"
#import "Dog.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
Animal *a=[Animal new];
[a eat];//派生类中重写父类的方法不会对父类方法有影响。
Dog *dog=[Dog new];
[dog eat];
}
return 0;
}
//
// Animal.h
#import <Foundation/Foundation.h>
@interface Animal : NSObject
-(void)eat;
@end
//
// Animal.m
#import "Animal.h"
@implementation Animal
-(void)eat{
NSLog(@"动物正在吃东西!");
}
@end
//
// Dog.h
#import "Animal.h"
@interface Dog : Animal
-(void)eat;
@end
//
// Dog.m
#import "Dog.h"
@implementation Dog
-(void)eat{
NSLog(@"狗正在吃东西!");//父类方法的重写
}
@end
运行结果:
2015-10-24 19:21:05.426 9-基类和派生类之间的关系[868:73147] 动物正在吃东西!
2015-10-24 19:21:05.427 9-基类和派生类之间的关系[868:73147] 狗正在吃东西!
Program ended with exit code: 0
七、继承的注意事项
1、继承的注意事项
1)子类不能定义和父类同名的变量,但是可以继承父类的变量。
2)OC类支持单一继承,不支持多继承
3)OC类支持多层继承
动物类(Animal)——>狗类(Dog)——>大黄狗类(BigYellowDog)
2、继承体系中方法调用的顺序
1)在自己类中找
2)如果没有,去父类中找
3)如果父类没有,就去父类的父类找
4)如果还没有,一直往上找,直到找到基类(NSObject)
5)如果NSObject也没有就报错了。
八、实例变量修饰符
1、实例变量修饰符介绍
1)@public(公开的)在有对象的前提下,任何地方都可以直接访问。
2)@protected(受保护的)只能在当前类和子类的对象方法中访问。
3)@private(私有的)只能在当前类的对象方法中才能直接访问。
修饰符的作用域:
从修饰符开始到下一个修饰符或者大括号结束。
2、实例变量修饰符对子类的影响
1)@private能被子类继承,但是不能被子类访问。
2)@public能被子类继承,也能被子类访问
3)@protected能够被子类继承和访问。
面试题:
@private能不能被子类继承,能不能被子类访问?
能继承,但是不能访问。
3、实例变量作用域使用注意事项
1)在@interface和@end之间声明的成员变量如果不做特别的说明,默认是protected的。
2)一个类继承了另一个类,那么就拥有了父类的所有成员变量和方法,注意所有的成员变量都有,只是有的它不能直接访问。
九、OC中的私有变量和私有方法
1、OC中的私有变量
在.m文件(类的实现文件)中也可以声明成员变量,但是因为其他文件中通常都只是包含头文件而不会包含实现文件,所以在.m文件中声明的成员变量是@private的。在.m中定义的成员变量不能和它的头文件.h中的成员变量同名。是纯私有变量。
此时的纯私有变量即不能被子类继承,也不能被子类访问。
2、OC中私有(相对私有)方法
OC中并没有想Java或者其他语言中提供的私有方法,OC中的私有,可以理解为相对私有。
OC中私有方法是实现有两种方式:
1)方法在.m中实现,不在.h中声明(相对私有)
私有方法只是对外隐藏了方法。
不能被子类继承,子类中也不能使用
十、description方法介绍及重写
1、description方法概述
description方法默认返回对象的描述信息(默认返回类名和对象的内存地址)
NSLog(@“%@”,对象名);
这会自动调用对象的description方法来输出对象的描述信息。
description方法是基类NSObject所带的方法。
使用NSLog输出对象的类名和内存地址意义不是很大,因此经常会重写description方法,覆盖description方法的默认实现。
2、description方法的重写
//
// main.m
#import <Foundation/Foundation.h>
#import "Dog.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
Dog *dog=[Dog new];
[dog setAge:3];
[dog setName:@"大黄"];
NSLog(@"%@",dog);
NSLog(@"%@",[dog class]);
}
return 0;
}
//
// Dog.h
#import <Foundation/Foundation.h>
@interface Dog : NSObject
{
int _age;
NSString *_name;
}
-(void)setAge:(int) age;
-(void)setName:(NSString *) name;
-(int)age;
-(NSString *)name;
@end
//
// Dog.m
#import "Dog.h"
@implementation Dog
-(void)setAge:(int) age{
_age=age;
}
-(void)setName:(NSString *) name{
_name=name;
}
-(int)age{
return _age;
}
-(NSString *)name{
return _name;
}
//desccription方法的重写
//对象方法
-(NSString *)description{
return [NSString stringWithFormat:@"狗的名字是:%@,狗的年龄是:%d",_name,_age];
}
+(NSString *)description{
return @"这是一个Dog类";
}
@end
运行结果:
2015-10-24 19:23:11.554 15-description方法的概述及重写[876:74262] 狗的名字是:大黄,狗的年龄是:3
2015-10-24 19:23:11.555 15-description方法的概述及重写[876:74262] 这是一个Dog类
Program ended with exit code: 0
3、description陷阱
千万不要再对象description方法中同时使用%@和self
同时使用%@和self,代表要调用self的description方法,因此最终会导致循环调用description方法,使程序陷入死循环。
十一、多态
1、多态的概念
多态:某一类事物的多种形态。
程序中的多态:
不同对象以自己的方式响应相同名称方法的能力称为多态
或者说: 多态是同一个行为具有多个不同表现形式或形态的能力。
2、多态的条件
多态的条件:
父类的声明变量指向子类对象。
有继承关系、有方法重写。
总结:用父类的类型指针指向了子类的对象。
3、多态的优点
多态的主要好处就是简化了编程接口。
它容许在类和类之间重用一些习惯性地命名,而不用为每一个新加的函数命名一个新名字。
这样,编程接口就是一些抽象的行为的集合,从而和实现接口的类区分开来。
多态也使得代码可以分散在不同的对象中而不用试图在一个函数中考虑到所有可能的对象。
这样使得代码的扩展性和复用性更好一些。
当一个新的情景出现是,无需对现有的代码进行改动,而值需要增加一个新的类和新的同名方法。
4、多态的实现
1)如何实现多态
Animal是父类,子类有Dog,Dog的子类有BIgYellowDog。
//
// main.m
// 17-多态的实现
#import <Foundation/Foundation.h>
#import "BigYellowDog.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
Animal *a1=[Animal new];
[a1 run];
Animal *a2=[Dog new];
//a2虽然是Animal类的对象,但指向了Dog类。
[a2 run];
Animal *a3=[BigYellowDog new];
[a3 run];
//父类调用子类不同命的方法
Animal *a4=[Dog new];
[(Dog *)a4 eat];
//如果是Animal *a5=[Animal new];
//[(Dog *)a5 eat];
//这样写是错误的,因为a5指向的是Animal的类,不能强转成Dog类。
}
return 0;
}
//
// Animal.h
#import <Foundation/Foundation.h>
@interface Animal : NSObject
-(void)run;
@end
//
// Animal.m
#import "Animal.h"
@implementation Animal
-(void)run{
NSLog(@"动物在跑!");
}
@end
//
// Dog.h
#import "Animal.h"
@interface Dog : Animal
-(void)eat;
@end
//
// Dog.m
#import "Dog.h"
@implementation Dog
-(void)run{
NSLog(@"狗在跑!!!");
}
-(void)eat{
NSLog(@"够在啃骨头");
}
@end
//
// BigYellowDog.h
#import "Dog.h"
@interface BigYellowDog : Dog
@end
//
// BigYellowDog.m
#import "BigYellowDog.h"
@implementation BigYellowDog
-(void)run{
NSLog(@"大黄狗在跑!!!");
}
@end
运行结果:
2015-10-24 19:25:27.924 17-多态的实现[886:75870] 动物在跑!
2015-10-24 19:25:27.924 17-多态的实现[886:75870] 狗在跑!!!
2015-10-24 19:25:27.924 17-多态的实现[886:75870] 大黄狗在跑!!!
2015-10-24 19:25:27.924 17-多态的实现[886:75870] 够在啃骨头
Program ended with exit code: 0
2)多态的原理
动态绑定:
动态类型能使程序直到执行时才确定对象所属类型
动态类型绑定能使程序直到执行是才确定要对对象调用的实际方法
OC不同于传统程序设计语言,它可以在运行时加入新的数据类型和新的程序模块:
动态类型识别、动态绑定、动态加载。
Id类型:
通用指针类型,弱类型,编译时不进行类型检查。
3)多态的注意点
(1)如果存在多态,父类是可以访问子类特有的方法
Animal *a2=[Dog new];
//a2虽然是Animal类的对象,但指向了Dog类。
[a2 run];
Animal *a3=[BigYellowDog new];
[a3 run];
(2)如果不存在多态,父类是不可以访问子类特有的方法的。
就是父类指针没有指向子类对象,这时父类对象无法强制转换成子类对象。
//父类调用子类不同命的方法
Animal *a4=[Dog new];
[(Dog *)a4 eat];
//如果是Animal *a5=[Animal new];
//[(Dog *)a5 eat];
//这样写是错误的,因为a5指向的是Animal的类,不能强转成Dog类。
十二、类的本质
1、类类型的变量(实例对象)
Person *p=[Person new];
把类代码加载到代码区
产生一个类对象,类型是Class,isa指向代码区的Person类
实例化对象,包括三件事:申请空间,初始化,返回地址
对象isa指向类对象
2、类的本质
类的本质其实也是一个对象(类对象),是一个class类型的对象,简称类对象。
类对象:
1)类对象在程序运行时一直存在。存在于堆内存。
2)类对象是一种数据结构,存储类的基本信息:类大小,类名称,类的版本以及消息与函数的映射表等。
3)类对象所保存的信息在程序编译时确定,在第一次使用该类的时候被加载到内存中。
4)类对象代表类,class代表类对象,类方法属于类对象
5)如果消息的接受者是类名,则类名代表类对象
6)运行是,所有累的饿实例都有类对象生成,类对象会把实例的isa的值改成自己的地址,每个实例的isa都指向该实例的类对象。
7)从类对象里可以知道父类信息、可以响应的方法等。
8)类对象只能使用类方法,不能使用实例方法。
Person *p=[Person new]; p为实例对象
Person也是一个对象(类对象),是Class类型
3、类对象如何获取
1)通过实例对象获取
2)通过类名获取(类名其实就是类对象)
//
// main.m
// 18-类的本质
#import <Foundation/Foundation.h>
#import "Dog.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
Dog *d1=[Dog new];
Dog *d2=[Dog new];
//通过实例对象获取类对象。
Class c1=[d1 class];
Class c2=[d2 class];
NSLog(@"%p",c1);
NSLog(@"%p",c2);
//通过类名获取类对象。
Class c3=[Dog class];
NSLog(@"%p",c3);
}
return 0;
}
//
#import <Foundation/Foundation.h>
@interface Dog : NSObject
@end
//
// Dog.m
#import "Dog.h"
@implementation Dog
@end
运行结果:
2015-10-24 19:28:32.914 18-类的本质[896:77942] 0x100001138
2015-10-24 19:28:32.914 18-类的本质[896:77942] 0x100001138
2015-10-24 19:28:32.914 18-类的本质[896:77942] 0x100001138
Program ended with exit code: 0
十三、类对象的使用
1、可以用来调用方法
Dog *d1=[Dog new];
Class c1=[d1 class];
//用类名调用类方法
[Dog test];
[c1 test];
2、可以用来实例化实例对象
//用类对象穿件实例对象
Dog *d2=[c1 new];
[d2 test];
类对象的存储
Person *p=[Person new];之后
p在栈区
Person类对象在堆区 有isa指针isa1
实例变量的具体内容在堆区,有isa指针isa2
Person类的具体信息在代码区
isa2指向isa1,而isa1指向Person类在代码区的地址。
十四、SEL类型
SEL:全称 selector
一种用来表示方法名类型的数据类型(方法名)。
SEL类型作用:
1)可以定义变量
2)可以用来作为方法的形参
3)可以用来作为方法的实参
类中方法的存储原理:
1)类里面的方法都是被转换成SEL变量进行存储的
2)当类声明一个对象,对象调用方法的时候,系统会把这个方法转换成SEL,然后拿这个SEL到类方法中取匹配
寻找方法的过程:
(1)首先把test这个方法名包装成SEL类型的数据
(2)根据SEL数据找到对应的方法地址
(3)根据方法地址调用相应地方法
(4)在这个操作过程中有缓存,第一次找得时候是一个一个的找,非常耗性能,之后再用刀的时候就是直接使用。
注意:SEL其实是对方法的一种包装,将方法包装成一个SEL类型的数据,去寻找对应的方法地址,找到方法地址后就可以调用方法。
这些都是运行是特性,发消息就是发送SEL,然后根据SEL找到地址,调用方法。
我们还可以自己手动把方法转换成SEL,然后用这个SEL去查找方法。
//
// main.m
// 20-SEL类型
#import <Foundation/Foundation.h>
#import "Dog.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
Dog *d=[Dog new];
[d run];
SEL s=@selector(run);
[d performSelector:s];
}
return 0;
}
//
// Dog.h
#import <Foundation/Foundation.h>
@interface Dog : NSObject
-(void)run;
@end
//
// Dog.m
#import "Dog.h"
@implementation Dog
-(void)run{
NSLog(@"狗正在跑!!!");
}
@end