复习:
1. Xcode文档的安装与使用.
2. static关键字修饰方法中的局部变量.
3. self关键字.
1). 在对象方法中.
父类的成员对于子类来讲,也是属于子类的.所以父类的成员在子类的方法中也可以使用self
2). 在类方法中.
4. 继承.
1). 继承的目的:
2). 继承的语法:
3). 继承的效果:
4). 继承是类在继承而不是对象在继承.
5). 满足继承的关系.
is a
6). 继承的特点.
7). NSObject类.
8). 访问修饰符.
5. 私有属性和私有方法.
6. 里氏替换原则.
1).什么是LSP
Person *p1 = [Student new];
2).为什么LSP是可以的.
3).LSP的表现形式. 父类指针指向子类对象.
4).LSP的好处.
5).当1个父类指针指向1个子类对象的时候.只能通过这个父类指针访问这个对象中的父类成员.
7. 方法重写
1). 什么时候需要方法重写?
2). 如何重写
3). LSP的时候 通过指针调用的方法被子类重写了 那么调用的是子类重写的.
8. 多态.
02-继承的本质
1. 创建1个对象,这个对象在内存中是如何分配的.
1). 子类对象中有自己的属性和所有父类的属性.
2). 代码段中的每1个类都有1个叫做isa的指针,这个指针指向它的父类.
一直指到NSObject
[p1 sayHi]; //假设p1是Person对象.
先根据p1指针找到p1指向的对象,然后根据对象的isa指针找到Person类.
搜索Person类中是否有这个sayHi方法 如果有执行
如果没有 就根据类的isa指针找父类 。。。。。
NSObject 中如果没有就报错.
03-作业
1.人类
姓名 性别 年龄.
2.书类
书名 作者 出版社 出版日期.
3.学生类
姓名 性别 年龄 学号 书.
读书
日期: 年 月 日.
------------------------------------------------
1. 结构体与类的相同点
都可以将多个数据封装为1个整体.
struct Date
{
int year;
int month;
int day;
};
@interface Date : NSObject
{
int year;
int month;
int day;
}
@end
2. 结构体与类的不同点
1). 结构体只能封装数据,而类不仅可以封装数据还可以封装行为.
2). 结构体变量分配在栈空间 (如果是1个局部变量的情况下)
而对象分配在堆空间.
栈的特点: 空间相对较小. 但是存储在栈中的数据访问的效率更高一些.
堆的特点: 空间相对较大. 但是存储在堆中的数据访问的效率相对要低
存储在栈中的数据访问效率高 存储在堆中的数据访问效率低.
3). 赋值.
结构体 Student
类: Person
Student s1 = {"jack",19,GenderMale};
Student s2 = s1;
Person *p1 = [Person new];
Person *p2 = p1;
3. 应用场景.
1). 如果表示的这个实体 不仅是由多个数据组成, 这个是实体还有行为,只能使用类.
2). 如果表示的实体没有行为.光有属性.
a. 如果属性较少.只有几个. 那么这个时候就定义为结构体 分配在栈 提高效率.
b. 如果属性较多.不要定义成结构体. 因为这样结构体变量会在栈中占据很大1块空间
反而会影响效率.
定义为类.
04-类的本质
1. 内存中的五大区域.
栈
堆
BSS段
数据段
代码段.
代码段:是用来存储代码的.
类加载. 当类第1次被访问的时候 这个类就会被加载到代码段存储起来.
2. 讨论三个问题
1). 类什么时候加载到代码段.
类第1次被访问的时候,类就会被加载到代码段存储 类加载.
2). 类以什么样的形式存储在代码段.
3). 类一旦被加载到代码段之后 什么时候回收.
是不会被回收的 除非程序结束.
3. 类是以什么样的形式存储在代码段的.
1). 任何存储在内存中的数据都有1个数据类型.
int num = 12;
float 12.12f
'a'
任何在内存中申请的空间也有自己的类型.
Person *p1 = [Person new];
2). 在代码段存储类的那块空间是个什么类型的.
在代码段中存储类的步骤
a. 先在代码段中创建1个Class对象, Class是Foundation框架中的1个类.
这个Class对象就是用来存储类信息的.
b. 将类的信息存储在这个Class对象之中.
这个Class对象.至少有3个属性
类名: 存储的这个类的名称.
属性s: 存储的这个类具有哪些属性
方法s: 存储的这个类具有哪些方法.
所以.类是以Class对象的形式存储在代码段的.
存储类的这个Class对象 我们也叫做类对象. 用来存储类的1个对象.
所以,存储类的类对象也有1个叫做isa指针的属性 这个指针指向存储父类的类对象.
4. 如何拿到存储在代码段中的类对象.
1). 调用类的类方法 class 就可以得到存储类的类对象的地址.
2). 调用对象的对象方法 class 就可以得到存储这个对象所属的类的Class对象的地址.
3). 对象中的isa指针的值其实就是代码段中存储类的类对象的地址.
注意:
声明Class指针的时候 不需要加* 因为在typedef的时候已经加了*了.
5. 如何使用类对象.
1). 拿到存储类的类对象以后.
Class c1 = [Person class];
c1对象就是Person类.
c1 完全等价于 Person
2). 使用类对象来调用类的类方法.
因为类对象就代表存储在这个类对象中类.
Class c1 = [Person class];
c1就代表Person类.
所以在使用Person的地方完全可以使用c1代替.
比如我们使用类名来调用类方法.
[Person sayHi];
完全可以使用c1来调用. 因为c1就是Person
[c1 sayHi];
3). 可以使用类对象来调用new方法 创建存储在类对象中的类的对象.
Person *p1 = [Person new];
Class c1 = [Person class];
其实创建Person对象 也可以这么做.
Person *p2 = [c1 new];
4).注意:
使用类对象 只能调用类的类方法,因为类对象就等价于存在其中的类.
Class c1 = [Person class];
c1就是Person。
-------------------------------
1. 类是以Class对象的形式存储在代码段之中的.
2. 如何拿到存储类的类对象.
3. 有神马用?
可以使用类对象调用类的类方法.
Class c1 = [Person class];
要调用Person的类方法 可以使用Person去调用.
也可以使用c1去调用.
05-SEL
1. SEL 全称叫做 selector 选择器.
SEL 是1个数据类型. 所以要在内存中申请空间存储数据.
SEL其实是1个类. SEL对象是用来存储1个方法的.
2. 类是以Class对象的形式存储在代码段之中.
类名:存储的这个类的类名. NSString
还要将方法存储在类对象之中.如何将方法存储在类对象之中.
1). 先创建1个SEL对象.
2). 将方法的信息存储在这个SEL对象之中.
3). 再将这个SEL对象作为类对象的属性.
3. 拿到存储 方法的SEL对象.
1). 因为SEL是1个typedef类型的 在自定义的时候已经加*了.
所以 我们在声明SEL指针的时候 不需要加*
2). 取到存储方法的SEL对象,
SEL s1 = @selector(方法名);
4. 调用方法的本质.
[p1 sayHi];
内部的原理:
1). 先拿到存储sayHi方法的SEL对象,也就是拿到存储sayHi方法的SEL数据. SEL消息.
2). 将这个SEL消息发送给p1对象.
3). 这个时候,p1对象接收到这个SEL消息以后 就知道要调用方法
4). 根据对象的isa指针找到存储类的类对象.
5). 找到这个类对象以后 在这个类对象中去搜寻是否有和传入的SEL数据相匹配的.
如果有 就执行 如果没有再找父类 直到NSObject
OC最重要的1个机制:消息机制.
调用方法的本质其实就是为对象发送SEL消息.
[p1 sayHi]; 为p1对象发送1条sayHi消息.
5. 重点掌握:
1).方法是以SEL对象的形式存储起来.
2).如何拿到存储方法的SEL对象.
6. 手动的为对象发送SEL消息.
1). 先得到方法的SEL数据.
2). 将这个SEL消息发送给p1对象.
调用对象的方法 将SEL数据发送给对象.
- (id)performSelector:(SEL)aSelector;
Person *p1 = [Person new];
SEL s1 = @selector(sayHi);
[p1 performSelector:s1]; //与 [p1 sayHi]效果是完全一样的.
3). 调用1个对象的方法有两种.
1). [对象名 方法名];
2). 手动的为对象发送SEL消息.
7. 注意事项:
1). 如果方法有参数 那么方法名是带了冒号的.
2). 如果方法有参数,如何传递参数.
那么就调用另外1个方法.
- (id)performSelector:(SEL)aSelector withObject:(id)object;
- (id)performSelector:(SEL)aSelector withObject:(id)object1 withObject:(id)object2;
3). 如果有多个参数
把多个参数封装在一个对象里面。。
---------------------------------
总结:
1. 类是以Class对象的形式存储在代码段.
2. 如何取到存储类的类对象.
3. 如何使用类对象调用类的类方法
4. 方法是以SEL数据的形式存储的.
5. 调用方法的两种方式.
06-点语法
1. Java、 C# 对象可以使用点语法来访问对象的成员.
OC中也有点语法. OC中也可以使用点语法来访问对象的属性.
但是OC的点语法和Java C# 是完全不一样的.
OC的对象如果要为属性赋值或者取值 就要调用对应的getter或者setter.
2. 使用点语法来访问对象的属性.
语法:
对象名.去掉下划线的属性名;
p1.name = @"jack"; 这个时候就会将@"jack"赋值给p1对象的_name属性.
NSString *name = p1.name; 把p1对象的_name属性的值取出来.
3.点语法的原理.
p1.age = 18;
这句话的本质并不是把18直接赋值给p1对象的_age属性.
点语法在编译器编译的时候.其实会将点语法转换为调用setter、getter的代码.
1). 当使用点语法赋值的时候. 这个时候编译器会将点语法转换为调用setter方法的代码.
对象名.去掉下划线的属性名 = 数据;
转换为:
[对象名 set去掉下划线的属性名首字母大写:数据];
p1.age = 10;
[p1 setAge:10];
2).当使用点语法取值的时候.这个时候编译器会将点语法转换为调用getter方法的代码.
对象名.去掉下划线的属性名;
转换为:
[对象名 去掉下划线的属性名];
int age = p1.age;
int age = [p1 age];
4.注意.
1). 在getter和setter中慎用点语法,因为有可能会造成无限递归 而程序崩溃,
2). 点语法在编译器编译的时候 会转换为调用setter getter方法的代码.
p1.name = @"jack";
[p1 setName:@"jack"]
NSString *name = p1.name;
NSString *name = [p1 name];
如果我们的setter方法和getter方法名不符合规范 那么点语法就会出问题.
3). 如果属性没有封装getter setter 是无法使用点语法的
因为点语法的本质是getter setter方法.
07-@property
1. 我们写1个类.
a. 要先为类写属性.
b. 在声明属性的getter setter
c. 再实现getter setter
哎! 1点点技术含量都没有.有没有更为简单的方式来实现同样的代码效果呢
2. @property
1). 作用: 自动生成getter、setter方法的声明.
因为是生成方法的声明,所以应该写在@interface类的声明之中.(大括号之后)
2). 语法:
@property 数据类型 名称;
@interface Pro : NSObject{
NSString *_name;
int _age;
float _height;
}
@property NSString *name;
@property int age;
@property float height;
@end
3). 原理:
编译器在编译的时候.会根据@property生成getter和setter方法的实现.
@property 数据类型 名称;
生成为:
- (void)set首字母大写的名称:(数据类型)名称;
- (数据类型)名称;
@property int age;
- (void)setAge:(int)age;
- (int)age;
3. 使用@property注意.
1). @property的类型和属性的类型一致.
@property的名称和属性的名称一致(去掉下划线)
不要乱写.
2). @property的名称决定了生成的getter和setter方法的名称.
所以,@property的名称要和属性的名称一致 去掉下划线 否则生成的方法名就是不符合规范的
@property的数据类型决定了生成的setter方法的参数类型 和 getter方法的返回值类型.
3). @property只是生成getter和setter方法的声明. 实现还要自己来. 属性还要自己定义.
08-@synthesize
1. @property 只能生成getter和setter的声明.
实现还要我们自己来.
而实现也是没有什么任何技术含量. 方法实现的代码能不能也可以自动生成呢?
2. @synthesize
1).作用: 自动生成getter、setter方法的实现.
所以,应该写在类的实现之中.
2).语法:
@synthesize @property名称;
@interface Person : NSObject
{
int _age;
}
@property int age;
@end
@implmentation Person
@synthesize age;
@end
3).@synthesize做的事情.
@implmentation Person
@synthesize age;
@end
@implementaion Person
{
int age;
}
- (void)setAge:(int)age
{
self->age = age;
}
- (int)age
{
return age;
}
@end
a. 生成1个真私有的属性.属性的类型和@synthesize对应的@property类型一致.
属性的名字和@synthesize对应的@property名字一致.
b. 自动生成setter方法的实现.
实现的方式: 将参数直接赋值给自动生成的那个私有属性.并且没有做任何的逻辑验证.
c. 自动生成getter方法的实现.
实现的方式: 将生成的私有属性的值返回.
3. 希望@synthesize不要去自动生成私有属性了.
getter setter的实现中操作我们已经写好的属性就可以了.
语法:
@synthesize @property名称 = 已经存在的属性名;
@synthesize age = _age;
1). 不会再去生成私有属性.
2). 直接生成setter getter的实现,
setter的实现: 把参数的值直接赋值给指定的属性.
gettter的实现: 直接返回指定的属性的值.
4. 注意:
1). 如果直接写1个@synthesize(自动生成私有属性)
@synthesize name;
2). 如果指定操作的属性.
@synthesize name = _name;
3). 生成的setter方法实现中 是没有做任何逻辑验证的 是直接赋值.
生成的getter方法的实现中 是直接返回属性的值.
如果setter或者getter有自己的逻辑验证 那么就自己在类的实现中重写就可以了.
5. 批量声明
1). 如果多个@property的类型一致. 可以批量声明.
@property float height,weight;
2). @synthesize也可以批量声明.
@synthesize name = _name,age = _age,weight = _weight,height = _height;
09-@property增强
1. @property & @synthesize 做啥:
@property只是生成getter setter 的声明.
@synthesize是生成getter setter 的实现.
这种写法是Xcode4.4之前的写法. 从Xcode4.4以后.Xcode对@property做了1个增强
2. @property增强
只需要写1个@property 编译器就会自动
1) 生成私有属性.
2).生成getter setter的声明.
3).生成getter setter的实现.
@property NSString *name;
做的事情
1). 自动的生成1个私有属性,属性的类型和@property类型一致 属性的名称和@property的名称一致 属性的名称自动的加1个下划线.
2). 自动的生成这个属性的getter setter方法的声明
3). 自动的生成这个属性的getter setter方法的实现.
setter的实现: 直接将参数的值赋值给自动生成的私有属性.
getter的实现: 直接返回生成的私有属性的值.
3.使用注意.
1). @property的类型一定要和属性的类型一致.
名称要和属性的名称一致 只是去掉下划线.
2). 也可以批量声明相同类型的@property
3). @property生成的方法实现没有做任何逻辑验证.
setter: 直接赋值
getter: 直接返回.
所以,我们可以重写setter来自定义验证逻辑.如果重写了setter 还会自动生成getter
如果重写了getter 还会自动生成setter
如果同时重写getter setter 那么就不会自动生成私有属性了.
4. 如果你想为类写1个属性 并且为这个属性封装getter setter,1个@property就搞定.
5. 继承.
父类的@property一样可以被子类继承.
@property生成的属性是私有的 在子类的内部无法直接访问生成的私有属性。
但是可以通过setter getter来访问。
10-动态类型和静态类型
1. OC是1门弱语言.
编译器在编译的时候.语法检查的时候没有那么严格.
不管你怎么写都是可以的.
int num = 12.12;
优点: 灵活 咋个写都行.
缺点: 太灵活
强类型的语言: 编译器在编译的时候 做语法检查的时候 行就是行 不行就是不行.
2. 静态类型:
指的是1个指针指向的对象是1个本类对象.
动态类型:
指的是1个指针指向的对象不是本类对象.
3. 编译检查.
编译器在编译的时候,能不能通过1个指针去调用指针指向的对象的方法.
判断原则: 看指针所属的类型之中是有这个方法,如果有就认为可以调用 编译通过.
如果这个类中没有 那么编译报错.
这个叫做编译检查. 在编译的时候 能不能调用对象的方法主要是看指针的类型.
我们可以将指针的类型做转换,来达到骗过编译器的目的.
4. 运行检查.
编译检查只是骗过了编译器. 但是这个方法究竟能不能执行.
所以在运行的时候.运行时会去检查对象中是否真的有这个方法.如果有就执行 如果没有就报错误.
5. LSP
父类指针指向子类对象.
实际上任意的指针可以执行任意的对象.编译器是不会报错的.
当1个子类指针执行1个父类对象的时候,编译器运行通过子类指针去调用子类独有的方法.
但是在运行的时候是会出问题的.因为父类对象中根本没有子类成员.
11-id类型
1. NSObject.
是OC中所有类的基类.根据LSP NSObject指针就可以指向任意的OC对象.
所以.NSObject指针是1个万能指针.可以执行任意的OC对象.
缺点: 如果要调用指向的子类对象的独有的方法.就必须要做类型转换.
2. id指针.
是1个万能指针,可以指向任意的OC对象.
1). id是1个typedef自定义类型 在定义的时候已经加了*
所以,声明id指针的时候不需要再加*了.
2) id指针是1个万能指针,任意的OC对象都可以指.
3. NSObject和id的异同.
相同点: 万能指针 都可以执行任意的OC对象.
不同点: 通过NSObject指针去调用对象的方法的时候.编译器会做编译检查.
通过id类型的指针去调用对象的方法的时候,编译器直接通过.无论你调用什么方法.
注意: id指针只能调用对象的方法 不能使用点语法.如果使用点语法就会直接报编译错误 。
如果我们要声明1个万能指针 千万不要使用NSObject 而是使用id
4. 父类中的类方法创建1个父类对象返回.
1). 如果返回值写为父类类型的.那么子类来调用这个方法得到的就是父类指针.
解决的方式: 把返回值改为id类型的.
2). 方法的内部创建的对象的是 不要写死. 因为写死创建的对象就固定了.
我们希望那1个类来调用这个方法就创建那1个类的对象.
把类名写为self 那1个类来调用这个方法 self就指的是那1个类.创建的就是那1个类的对象.
3). 方法的返回值是id类型的.问题就是任意指针都可以接收这个方法的返回值.
编译器连个警告都没有.
如果方法的返回值是instancetype
代表方法的返回值是当前这个类的对象.
5. 使用建议
1). 如果方法内部是在创建当前类的对象,不要写死成类名 【类名 new】;
而是用self代替类名.
2). 如果方法的返回值是当前类的对象,也不要写死了. 而是写instancetype
6. id和instancetype的区别.
1).instancetype只能作为方法的返回值.不能在别的地方使用.
id既可以声明指针变量 也可以作为参数 也可以作为返回值.
2). instancetype 是1个有类型的 代表当前类的对象.
id是1个无类型的指针 仅仅是1个地址.没有类型的指针.
12-动态类型检测
1. 编译检查.
苹果编译器的名字: LLVM
编译器在编译的时候. 判断1个指针是否可以调用指向的对象的方法.
判断的准则就是指针的类型.
我们可以很轻松的把编译器给骗过.
2. 就算骗过了编译器,程序在运行的时候还会做运行检查.
我们写的程序就算编译通过了.不意味着可以完美的执行.
3. 我们就希望.我们可以写代码来先判断1下.对象中是否有这个方法.如果有再去执行.
如果没有就别去执行.
1). 判断对象中是否有这个方法可以执行.
- (BOOL)respondsToSelector:(SEL)aSelector;
Student *s1 = [Student person];
BOOL b1 = [s1 respondsToSelector:@selector(lenth)];
if (b1 == YES) {
[s1 setName:@"王铁锤" andAge:30];
}
else{
NSLog(@"没有方法");
}
2). 判断指定的对象是否为 指定类的对象或者子类对象.
- (BOOL)isKindOfClass:(Class)aClass;
BOOL b1 = [s1 isKindOfClass:[Person class]];
Student *s1 = [Student person];
[s1 setName:@"王铁锤" andAge:30];
BOOL b1 = [s1 isKindOfClass:[Person class]];
if (b1 == YES) {
NSLog(@"name:%@ age:%d",s1.name,s1.age);
}
else{
NSLog(@"不是Person类");
}
判断b1对象是否为Student对象或者Person的子类对象.
3). 判断对象是否为指定类的对象 不包括子类.
- (BOOL)isMemberOfClass:(Class)aClass;
Student *s1 = [Student person];
[s1 setName:@"王铁锤" andAge:30];
BOOL b1 = [s1 isMemberOfClass:[Student class]];
if (b1 == YES) {
NSLog(@"name:%@ age:%d",s1.name,s1.age);
}
else{
NSLog(@"不是Student类");
}
判断b1对象是否为1个Student对象. 不包括Student的子类对象.
4). 判断类是否为另外1个类的子类.
+ (BOOL)isSubclassOfClass:(Class)aClass;
Student *s1 = [Student person];
[s1 setName:@"王铁锤" andAge:30];
BOOL b1 = [Student isSubclassOfClass:[Person class]];
if (b1 == YES) {
NSLog(@"name:%@ age:%d",s1.name,s1.age);
}
else{
NSLog(@"不是Student类");
}
最常用的是这个方法.
判断类中是否有指定的类方法.
+ (BOOL)instancesRespondToSelector:(SEL)aSelector;
Student *s1 = [Student person];
[s1 setName:@"王铁锤" andAge:30];
BOOL b1 = [Student instancesRespondToSelector:@selector(setName:andAge:)];
if (b1 == YES) {
NSLog(@"name:%@ age:%d",s1.name,s1.age);
}
else{
NSLog(@"不是Student类");
}
13-构造方法
1. 创建对象,我们之前说:
类名 *指针名 = [类名 new];
new实际上是1个类方法.
new方法的作用:
-> 创建对象。
-> 初始化对象
-> 把对象的地址返回.
new方法的内部,其实是先调用的alloc方法. 再调用的init方法.
alloc方法是1个类方法,作用: 那1个类调用这个方法 就创建那个类的对象,并把对象返回.
init方法 是1个对象方法,作用: 初始化对象.
创建对象的完整步骤:
应该是先使用alloc创建1个对象,然后再使用init初始化这个对象 才可以使用这个对象.
虽然没有初始化的对象 有的时候 也可以使用. 但是千万不要这么做.
使用1个未经初始化的对象是极其危险的.
Person *p1 = [Person new];
完全等价于
Person *p1 = [[Person alloc] init];
2. init方法.
作用: 初始化对象,为对象的属性赋初始值 这个init方法我们叫做构造方法.
init方法做的事情:初始化对象.
为对象的属性赋默认值.
如果属性的类型是基本数据类型就赋值为0
C指针 NULL
OC指针 nil
所以.我们创建1个对象如果没有为这个对象的属性赋值 这个对象的属性是有默认值的.
所以,我们每次新创建1个对象,这个对象的属性都被初始化了.
3. 我们想要让创建的对象的属性的默认值不是 nil NULL 0
而是我们自定义的.
那么这个时候,我们就可以重写init方法. 在这个方法中按照我们自己的想法为对象的属性赋值.
重写init方法的规范:
1). 必须要先调用父类的init方法.然后将方法的返回值赋值给self
2). 调用init方法初始化对象有可能会失败,如果初始化失败.返回的就是nil
3). 判断父类是否初始化成功. 判断self的值是否为nil 如果不为nil说明初始化成功.
4). 如果初始化成功 就初始化当前对象的属性.
5). 最后 返回self的值.
解惑
1). 为什么要调用父类的init方法.
因为父类的init方法 会初始化父类的属性. 所以必须保证当前对象中的父类属性也同时被初始化.
2). 为什么要赋值给self?
因为.调用父类的init方法 会返回初始化成功的对象
实际上返回的就是当前对象。但是我们要判断是否初始化成功.
无论如何,记住重写init方法的规范.
- (instancetype)init
{
if(self = [super init])
{
//初始化当前类的属性的代码;
}
retrun self;
}
什么时候需要重写init方法:
如果你希望创建出来的对象的属性的默认值不是 nil NULL 0 而是我们指定的值.
那么这个时候我们就可以重写init方法.
3. 重写init方法以后.
稍稍不爽的: 这样每次创建出来的对象的属性的值都是一样的.
创建对象的时候,对象的属性的值由创建对象的人来指定.而不是写死在init方法中
自定义构造方法.
规范:
1). 自定义构造方法的返回值必须是instancetype
2). 自定义构造方法的名称必须以initWith开头.
3). 方法的实现和init的要求一样.
什么时候要自定义构造方法?
如果希望在构造对象的同时就指定对象的同时。 .....
*