OC基础-day06

时间:2023-03-08 23:56:53
OC基础-day06

#pragma mark - Day06_01_点语法

1. 点语法.

1). 如果要访问对象的属性,还要去调用属性对应的setter getter方法.好烦躁好烦躁.

2). 点语法的作用: 快速调用属性的getter setter方法.

3). 语法:

对象名.去掉下划线属性名;

如果是赋值,就自动调用这个属性对应的setter方法.

如果是取值,就自动调用这个属性对应的getter方法.

2. 使用点语法快速的调用setter方法为属性赋值

对象名.去掉下划线的属性名 = 数据;

MKPerson *p1 = [MKPerson new];

p1.name = @"小明"; //不是把@"小明"直接赋值给p1对象的_name属性

原理: 编译器在编译的时候,其实就把这个点语法替换为了调用setter方法的代码.

替换的规则:

对象名.属性名 = 数据;

会替换成如下代码:

[对象名 set首字母大写的属性名:数据];

p1.name = @"小明";

[p1 setName:@"小明"];

所以,使用点语法为属性赋值.本质上还是调用的setter方法.

3. 使用点语法快速调用属性的getter方法取出属性的值.

格式:  对象名.去掉下划线的属性名;

NSString *name = p1.name;

本质上 是调用p1对象的_name属性的getter方法.getter方法的返回值就是取到的值.

编译器在编译的时候. 其实就把这个点语法转换为了调用getter方法的代码.

对象名.去掉下划线的属性名;

[对象名 去掉下划线的属性名];

NSString *name = p1.name;

转换:  NSString *name = [p1 name];

4. 点语法是1个编译器特性.

编译器在编译的时候,其实就已经把点语法转换为了调用getter setter方法的代码了.

如果是通过点语法赋值.那么就转换为调用setter方法的代码.

如果是通过点语法取值.那么就转换为调用getter方法的代码.

所以 点语法的本质还是调用getter setter方法

点语法的语法:

要为属性赋值: 对象名.去掉下划线的属性名  =  数据.

要取出属性的值:  对象名.去掉下划线的属性名;

5. 几个使用注意

1). 在getter 和 setter方法中, 慎用点语法.

一不小心 就是死循环.

点语法的本质就是调用getter setter

2). 点语法的本质是调用getter setter

所以.你的属性如果没有封装getter setter 自然是使用不了点语法的.

3). 点语法是在编译器编译的时候,转换为调用对应的getter setter方法.

如果你的getter setter方法的名字不符合规范的话.

点语法也是无法使用的.

因为编译器在转换的时候.是按照规范来转换的.

对象名.去掉下划线的属性名 = 数据;

[对象名 set去掉下划线的属性名并且首字母大写:数据];

对象名.去掉下划线的属性名;

[对象名 去掉下划线的属性名];

#pragma mark - Day06_02_@property的使用

1. @property

1). 作用: 自动生成getter setter方法的声明.

因为是自动生成etter sette方法的声明 所以,应该写在@interface中.

2). 语法:

@property 数据类型 名称;

@property NSString *name;

原理:在编译器编译的时候.会根据这个@property自动的生成getter setter方法的声明.

生成方法的规则:

@property 数据类型 名称;

a. 先生成setter方法的声明

- (void)set名称首字母大写:(数据类型)名称;

b. 再生成getter方法的声明.

- (数据类型)名称;

@property int age;

- (void)setAge:(int)age;

- (int)age;

2. 使用@property注意.

1). @property的类型要和属性的类型一致.

2). @property的名称要和属性的名称一致(只是去掉下划线)

3). 一定要按照这个规则来.只有这样它自动生成的getter  setter方法名称才符合我们的规范.

4). @property可以批量声明.

当@property的类型相同的时候,可以批量声明.

类型不同是不可以批量声明的.

5). @property只是生成getter setter的声明.

实现还要自己去实现;

#pragma mark - Day06_03_@synthesize的使用

1. @property只能生成getter、setter方法的声明.

实现还要自己来.

2. @synthesize.

1). 作用: 自动生成getter setter方法的实现.

所以,它是写在@implementation中.

2). 使用格式:

@synthesize @pro名称;

注意,这个@synthesize后面的名称必须要是@interface中的@property的名称;

3). @synthesize的原理.

a. 先自动的生成1个私有属性.类型和@synthesize对应的@pro类型一致.

名称和@pro的名称一致. 和@pro的名称一致. 不会有下划线.

这个属性是1个真私有的属性,也就是声明在@implementation中.

b. 自动的生成setter方法的实现.

这个方法的内部的实现是如何实现的?

这个方法的内部什么都没有做. 直接将参数赋值给了它自动生成的那个私有属性.

c. 自动的生成getter方法的实现.

这个方法的内部的实现是如何实现的?

直接返回生成的那个私有属性的值.

#pragma mark - Day06_04_@synthesize的使用注意

1.  虽然@synthesize会自动的生成属性.

我认为他生成的这个属性的名称是很不符合规范.

所以,我希望@synthesize不要去自动生成私有属性.并且在getter setter的方法实现中操作的属性是已经存在的属性.

语法格式:

@synthesize @pro名称 = 已经存在的属性名;

@synthesize name = _name;

意义:

1). 不会再去自动生成私有属性了.

2). 自动生成setter方法的实现.

直接将参数的值赋值给后面指定的属性.

3). 自动生成getter方法的实现.

直接将返回后面指定的属性的值.

4. 使用注意.

@synthesize生成的setter、getter方法的实现.

没有逻辑判断. 是直接赋值或者返回的.

如果你有自己的逻辑判断.那么重写这个方法就可以了.

@synthesize仍然可以批量声明.

类型不同,也可以批量.

#pragma mark - Day06_05_@property的增强使用

1. 我们刚才讲的@property @synthesize 这些用法是在Xcode4.4之前的用法.

从Xcode4.4开始. 苹果对property做了1个增强

从4.4以后.只要写1个@property. 编译器就会自动的帮助你完成如下工作

1). 会自动的帮助你生成1个带下划线的、类型和@pro类型一致的属性.

2). 会自动的帮助你生成这个属性的getter setter方法声明.

3). 会自动的帮助你生成这个属性的getter setter方法实现.

2. @property的原理.

@interface MKPerson : NSObject

@property NSString *name;

@end

@implementation MKPerson

@end

---------------编译器在编译的时候-----------

@interface MKPerson: NSObject

- (void)setName:(NSString *)name;

- (NSString *)name;

@end

@implementation MKPerson

{

NSSteing *_name;

}

- (void)setName:(NSString *)name

{

_name = name;

}

- (NSString *)name

{

return _name;

}

@end

3. @property做的事情.

1). 生成1个和@pro类型相同、名称加1个下划线的私有属性.

这个属性是生成在@implementation中的.

2). 自动生成这个私有属性的getter setter方法的声明.

3). 自动生成这个私有属性的getter setter方法的实现.

setter如何实现: 直接将参数的值赋值给生成的那个私有属性

getter如何实现: 直接返回生成的那个私有属性的值;

#pragma mark - Day06_06_使用@property增强注意

1. 使用@property要注意的几个问题.

1). @property的类型要和需要生成的属性的类型一致.

@pro的名称要和想要生成的属性的名称一致.去掉下划线.

2). @property也是可以批量声明的. 前提是类型相同的情况下.

3). @property生成的方法实现是没有任何逻辑验证的.

如果你有逻辑验证,可以自己重写.

如果你重写了setter方法 @property仍然会自动生成私有属性 和 getter 方法.

如果你重写了getter方法.@property仍然会自动生成私有属性 和 setter 方法.

通过你同时重写了getter setter,那么@property就不会自动生成私有属性了.

5.  从今往后.我们不再为类声明属性、不再写getter setter方法的声明 不再写getter setter方法的实现.

如下几个情况除外.

1). 属性不需要被外界访问.

2). 有自己的逻辑验证;

"补充

如果@property使用位置不是在@interface{}外面(分类/协议),那么@property所做的事情会不一样

"练习

定义一个person类

属性:姓名 年龄 性别

使用@property声明属性(不要批量声明)

并且在main函数中使用点语法,设置数值和获取数值;

#pragma mark - Day06_07_任意的指针可以指向任意的对象

1. OC是1门弱语言.

强语言: Java C#...

编译器在检查语法的时候,非常的严格. 是怎样就是怎样.

1点错误都不能犯.

swift严格到让人发指

弱语言优点:*.灵活.想咋写就咋写.

缺点:出了错误以后,很难找. 错误只有在运行的时候发生.

强语言的缺点:不*,必须按照语言的语法来写.

优点: 可以提前发现错误.

OC是1门动态语言. 运行的时候才能确定一切.

2. 动态类型和静态类型.

1). 静态类型: 指针指向的对象是1个本类对象.

2). 动态类型: 指针指向的不是1个本类对象

所以,在OC中 任意的指针可以指向任意的对象.

编译器最多只是给1个警告而已.

当指向的是1个本类对象,或者子类对象的时候,编译器不会给警告.

要存储1个对象的地址,其实任意类型的指针变量都无所谓的啦.

因为指针变量都是占据8个字节. 无论什么类型都是这样的.

#pragma mark - Day06_08_编译检查

编译检查.

编译检查:是编译器在编译源代码的时候,主要是检查代码是否符合语法规范吧.

LLVM

1) 对象类型之间可以相互转换类型,但是对象和基本数据类型不可以强行转换

2) 简而言之,编译检查就是检查变量'声明的类型'是否存在我们想要的操作,如果有,就会编译通过.

MKPerson *p1 = [MKStudent new];

[p1 sayHi];

[(MKStudent *)p1 study];

#pragma mark - Day06_09_运行检查

1. 运行检查.

程序在运行的时候.当要调用1个指针指向的对象的方法的时候.

仍然会做1个检查,会去检查,这个指针指向的对象中是否真的有这个方法,如果真的有,就指向,如果没有,就运行报错.

这就是运行检查.

[p1 sayHi];   调用p1指针指向的对象的sayHi方法.

所以:

1). 编译检查只是检查指针的类型.

2). 运行检查才会真正的去检查对象中是否有这个方法.

#pragma mark - Day06_10_NSObject万能指针

1. NSObject指针.

根据我们的里氏替换原则,NSObject类是所有OC类的祖宗类.

NSObject指针可以指向任意的OC对象.

注意的是: C中的数据类型不是OC对象.

当NSObject指针指向子类对象的时候, 如果要调用子类对象的独有成员.就必须做类型转换.

所以:

1). NSObject指针是1个万能指针.

2). 当指针的类型是NSObject*类型的时候.编译器要做编译检查.

#pragma mark - Day06_11_id指针

.id指针.

1). id类型是1个typedef类型的,在typedef的时候,已经加*了.

所以 声明id类型的指针不需要再加*

2). id指针是1个万能指针.它可以指向任意的OC对象.

3). 和NSObject指针的区别在于:

a. 当指针的类型是NSObject类型的时候. 编译器在编译的时候,会做编译检查

b. 当指针的类型是id类型的时候,不会做编译检查,直接通过

当编译器在编译的时候,如果发现指针的类型是id类型的.

这个时候,直接通过.不会做任何的编译检查.

4). id指针的局限性: 只能使用中括弧调用方法.不能使用点语法.

"补充

属性的名称可以叫id 不会出现问题

#pragma mark - Day06_12_id指针总结

id指针 总结

1). NSObject指针和id指针都是叫做万能指针.

他们都可以指向任意的OC对象.

2). 通过指针去调用方法的时候.

a. 如果指针的类型是NSObject类型的时候,编译器在编译的时候,会做编译检查.

如果就是要调用. 必须要做类型转换,

b. 如果指针的类型是id类型的时候 编译器在编译的时候,会直接通过. 不用做类型转换.

3). 只是在编译的时候不会做编译检查.

但是运行的时候,仍然要做运行检查.

#pragma mark - Day06_13_instancetype

1. 在父类中写1个类方法,返回1个对象

+ (MKPerson *)person

{

return [MKPerson new];

}

这么写存在的问题:

子类可以继承这个类方法. 通过子类也可以调用这个person方法.

但是.方法的返回值是1个MKPerson对象.

2. 如何解决上面的问题呢?

解决的方式: 就是把这个方法的返回值写成id.

这样的好处就是: 就算类型也不会做编译检查,不会给警告,因为id不会做编译检查.

+ (id)person

{

return [MKPerson new];

}

存在的新问题:

虽然没有警告了.但是这个类方法无论如何返回的都是Person对象. 因为这个方法的实现就是创建1个Pewrson对象返回.

新的需求: 这个类方法通过那1个类来调用就创建那1个类的对象.

3. 如何解决呢?

在类方法中创建对象的时候,类名不要写死了 而是写self

self代表当前类

这个类方法是通过那1个类来调用的.self就代表那1个类.

+ (id)person

{

return [self new];

}

那个类来调用这个方法,创建的就是那1个类的对象.

还存在的问题:

这个时候,要接收这个类方法返回的对象的地址,实际上用任意类型的指针都是可以接的.

并且编译器连警告都不会有.

因为返回值是id类型的 而id类型是不会做编译检查的. id是1个无类型的指针.

新的需求: 希望这个类方法通过那1个类来调用.返回值就是那1个类的对象.

4. 如何解决呢?

解决方案: 将方法的返回值写成instancetype

代表: 返回值是当前类的对象. 这个方法通过那1个类去调用,就代表返回的是那个类的对象,

返回的地址是有类型的.

+ (instancetype)person

{

return [self new];

}

这个时候,z这个类方法是通过那个类来调用的.

那么这个方法的返回值就是 这个类的对象. 返回值是有类型的地址.

这个方法创建的是 这个类的对象.

5. instancetype和id的区别

1). instancetype只能作为方法的返回值. 别的地方不能使用.代表返回值的类型为当前类的对象.

2), id是1个万能指针,不仅可以作为方法的返回值. 也能声明id类型的指针变量.

#pragma mark - Day06_14_在对象方法中使用self创建当前类的对象

在那个类中调用 self 就代表那个类,所以可以直接使用self创建当前对象

[[self class] new]

#pragma mark - Day06_15_动态类型检测

1. 编译检查和运行检查.

就算通过了编译检查,运行不一定会成功的.

需求: 编译检查只是检查指针的类型.

我们希望检查1下这个指针指向的对象中到底有没有这个方法

如果有 我才去调用. 如果没有就不要调用.

2. 判断指针指向的对象中是否真的有指定的方法

- (BOOL)respondsToSelector:(SEL)aSelector;  ******

使用这个方法就可以避免我们的运行错误.

可以判断对象中有没有这个方法、这个方法有没有实现.

- (BOOL)isKindOfClass:(Class)aClass;

判断指定的对象是否为指定类的对象或者指定类的子类对象.

- (BOOL)isMemberOfClass:(Class)aClass;

判断指定的对象是否为指定类的对象,不包括子类.

只能判断本类对象,不包括子类.

+ (BOOL)isSubclassOfClass:(Class)aClass;

判断类是否为另外1个类的子类.

#pragma mark - Day06_16_构造方法简介

1. 创建1个类的对象,那么就调用这个类的new方法就可以.

new方法是1个类方法

做的事情:创建类的对象,并且初始化这个对象.

返回值: 把这个对象的地址返回.

new方法的内部做的事情.

这个方法的内部什么事情都没有做.

就是是调用了.

alloc方法和init方法.

alloc方法:

首先是1个类方法.这个方法做的事情: 就是创建对象.

init方法

是1个对象方法 这个方法做的事情:初始化这个对象.

初始化:

就是为对象的属性赋默认值

所以,创建对象我们也可以这么创建.

MKPerson *p2 = [[MKPerson alloc] init];

MKPerson *p2 = [MKPerson new];

因为我们刚刚说过. new方法的内部就是先调用的alloc 再调用的init

init方法 我们叫做构造方法.

init方法做的事情: 为对象的属性赋默认值 初始化对象.

init方法做的事情,不仅仅是为对象的属性赋默认值 还要做的别的事情.

创建的对象务必要调用init方法初始化以后才可以使用,否则就有可能会出问题.

#pragma mark - Day06_17_重写init方法

1. init方法就是我们的构造方法.

为什么对象一创建出来,对象的属性的默认值就是:

0 nil NULL.

因为在构造方法中,为这些对象的属性初始化了.

init方法做了很多事情,其中1件事情就是为当前对象的属性赋默认值.

属性的类型是基本数据类型 0

C指针 NULL

OC指针  nil

3. 如果我们希望对象创建出来以后,对象的属性的默认值不是0 nil NULL

而是别的值.

那么我们就可以重写init方法.

重写init的方法的规范.

1). 要先调用父类的init方法.

因为父类的init方法不仅仅是初始化属性的值,还做了别的事情.

这些事情是要做的,所以要调用1下父类的init方法

2). init方法有1个返回值.返回的是当前对象.

调用init方法的时候,有可能会执行失败.

如果执行失败返回的就是nil.

调用父类的init方法. 将这个方法的返回值赋值给self

3). 判断父类的init方法有没有执行成功.

如果成功,再去初始化子类的属性. 按照自己的方式.

4). 返回当前对象. self

- (instancetype)init

{

self = [super init];

if(self != nil)

{

初始化子类属性 .

}

return self;

}

总结:

1). 什么时候需要重写init方法?

a. 对象一创建出来,不希望对象的属性的默认值是 0 nil NULL 而是我指定的值.

b. 如果你有1段代码,想在对象创建的同时执行,那么就可以将这个代码写在init方法中

2), 重写init方法的规范:

必须要先调用父类的init方法.并且赋值给self.然后判断成功.

- (instancetype)init

{

if(self = [super init]) //说明父类的init方法执行成功

{

初始化子类自己的属性.

}

return self;

}

#pragma mark - Day06_18_自定义构造方法

重写init方法以后.这个时候创建出来的所有的对象的属性的值都是一模一样的.

需求: 对象的属性的值 是由用户传进来的.

自定义构造方法.

自定义构造方法的步骤

1). 肯定是1个对象方法.

2). 返回值写 instancetype

3). 方法名必须以initWith开头.

自定义构造方法必须要以initWith开头.

因为self只能在构造方法中赋值.

编译器只认为 init 或者以initWith开头的方法 才叫做构造方法.