OC基础-day05

时间:2023-03-09 03:19:40
OC基础-day05

#pragma mark - Day05_01_NSObject类

NSObject类

1). NSObject是Foundation框架中的1个类.

在这个类中有1个类方法,叫做new

这个方法的作用:是来创建1个对象,并初始化这个对象.将这个对象的地址返回

如果我们的类想要创建对象.就必须要调用这个类方法new才可以创建对象

而这个new方法是定义在NSObject类中的.

所以,我们自定义的类,如果想要创建对象的话.就必须要拥有这个new方法.

如果我们的类没有这个方法,那么我们写的这个类就没有任何的意义.因为它创建不了对象,没有new方法.

为了让我们的类具备创建对象的能力.我们的类就必须直接或者间接的从NSObject类继承,

因为这样我们的类才有new方法,有了new方法才可以创建对象.

2). NSObject类是所有类的祖宗类.

因为,OC中所有的类都直接的或者间接的从NSObject继承.

3). 如果我们在写类的时候.你不确定这个类的父类是谁,就为这个类指定NSObject为其父类

4). 在NSObject类中其实还有1个属性叫做isa, 而所有的OC类都是从NSObject类继承的,所以,每1个OC对象中都有1个叫做isa的属性.

#pragma mark - Day05_02_super关键字

1. 子类中不能定义和父类中同名的属性,

因为这样就重复定义了.

子类可以继承父类的成员.就相当于子类自己有这个成员,你再定义1个同名的 那自然就重复了.

2. super关键字.

1). 使用在对象方法或者类方法中.

2). 在对象方法中使用super,可以显示的调用当前子类对象从父类中继承过来的对象方法

a. 要在子类的对象方法中调用从父类继承过来的对象方法.可以使用self来调用, 因为父类的就是子类的.

b. 如果我们想要在子类的对象方法中调用从父类继承过来的对象方法,也可以使用super.

c. 虽然.这个时候,使用self和super效果一样,但是我们建议.

如果你调用的方法真的是从父类继承过来的,建议你使用super

因为这样的代码的可读性很高.

3). 在类方法中也可以使用super, 使用super显示的调用从父类继承过来的类方法.要调用类方法其实有很多种方式.

a. 使用父类名来调用.

b. 使用子类名来调用

c. 使用self也可以掉.

d. 使用super也可以调用.

4). super关键字

在对象方法中显示的调用从父类继承过来的对象方法.

在类方法中显示的调用从父类继承过来的类方法,

注意. super不能访问属性.

#pragma mark - Day05_03_属性的访问修饰符

属性的访问修饰符.

1). 作用: 可以限定属性可以被访问的访问的范围.

2). 访问修饰符的种类

@private: 被@private修饰的属性,叫做私有属性. 私有属性只能在本类中访问,除了这个类的内部,其他的地方都是无权访问的。父类的私有成员,子类也是可以继承的.只不过在子类中无法访问从父类继承过来的私有成员.

@protected: 受保护的.被@protected修饰的属性,只能在本类和本类的子类中访问.

@paqudmqishaonianqiongdazhiruoyudazhhiruyuckage: 被@package修饰的属性,只能在当前这个target中访问.

@public: 公共的.被@public修饰的属性,叫做公共属性

在任意的地方都可以访问这些属性.

3). 如果没有为属性写任意的访问修饰符,那么默认的就是@protected.

4). 访问修饰符的作用域.

从这个访问修饰符开始.往下走,直到遇到另外1个访问修饰符,或者遇到大括弧结束

5). 使用建议.

a. 无论什么情况下,都不要使用@public.

如果你希望这个属性要被外界访问.不是为它写@public

而是为它封装getter和setter

b. 如果父类有属性不希望给子类访问.那么就使用@private.

c. 如果父类有属性想给子类访问.但是别的地方不行,就使用@protected

建议:使用默认的@protected就可以了.

#pragma mark - Day05_04_私有属性

私有属性.

1). 被@private修饰的属性,叫做私有属性.

这样的私有属性,我们认为私有的不够彻底.

因为,这个时候,Xcode仍然会提示这个对象有这个属性,只不过无权访问.

2). 我们想要的是 真私有属性.

私有化到: Xcode提示都不会提示.

3). 类的属性还可以定义在@implementation中.

写在这里的属性,任何访问修饰符无效,都是私有的.

定义在@implementation中的属性 和 定义在@interface中的私有属性的区别

唯一的区别: 就是Xcode不提示了.其他都是一样的.

"补充

私有属性不加{}也可以

#pragma mark - Day05_05_私有属性和私有方法的应用

1.私有方法.

1). 访问修饰符只能修饰属性,不能修饰方法.

2). 方法只写实现,不写声明. 那么这个方法就是1个私有方法,只能在类的内部调用.

2. 当外部不需要调用类中的属性,并且我们只是在内部使用的时候,我们就可以把这个属性定义成私有属性

3.  当外部不需要调用类中的方法,并且我们只是在内部调用的时候,我们就可以把这个属性定义成私有方法

"补充

私有方法也可以被继承

"练习

创建一个人类

属性:姓名,年龄(只读),银行存款(deposit,真私有)

对象方法

1.吃饭

2.睡觉

类方法

1.快速创建方法

2.快速创建方法并设置属性

职员类(Employee) 继承人类

属性:工作

对象方法

1.工作(working)

2.借给别人钱(LendMoneyToGambler有返回值,参数是赌鬼对象)

私有方法

3.去银行取钱

类方法

1.快速创建方法

2.快速创建方法并设置属性

赌鬼类(Gambler) 继承人类

对象方法

1.请别人吃饭(eatWithEmployee,给他一个职员对象)

2.假装工作(pretendWorkWithMoney参数需要钱,调用赌博方法)

私有方法

2.赌博(gambling)

类方法

1.快速创建方法

2.快速创建方法并设置属性

需求,

创建一个 赌鬼对象(p1) 在创建一个(p2)

p1 先请 p2吃饭

然后 p1 假装工作,去赌钱

然后输光了

p1 找 p2 借钱(p2 借钱给p1)

然后 p1 假装工作 又把钱输光了

#pragma mark - Day05_06_里氏替换原则

1. 里氏替换原则,子类对象可以替换父类对象的位置,并且程序的功能不受影响.

1). 指针是1个父类类型,但是我们确给了指针1个子类对象的地址.

这样做当然是可以的,因为你要1个父类对象,我给了你个子类对象.

子类就是1个父类嘛.

2). 因为父类中的成员子类都有. 只会多不会少, 所以,程序的功能不受影响.

2. 里氏替换原则的表现形式:

当1个父类指针,指向1个子类对象的时候.这里就是里氏替换原则.

3. LSP的好处是什么?

1). 1个指针不仅仅可以存储本类对象的地址,还可以存储子类对象的地址.

2). 如果1个指针的类型是NSObject类型的,那么这个指针中就可以存储任意的OC对象的地址,所以,NSObject类型的指针,我们叫做万能指针.

3). 如果1个数组的元素的类型是1个父类指针类型.那么这个数组中既可以存储本类对象 也可以存储子类对象.

4). 如果1个数组的元素的类型是NSObject*类型的,那么就意味 这个数组可以存储任意的OC对象.

5). 如果你发现1个方法的参数是1个父类对象 那么实参可以是1个父类对象,也可以是1个子类对象.

4. LSP的局限性.

当1个父类指针指向1个子类对象的时候

只能通过这个父类指针去访问这个子类对象中的父类成员. 子类独有的成员无法访问.

#pragma mark - Day05_07_方法重写

1. 子类虽然拥有父类的行为.

但是子类的行为的实现,和父类是不一样的.

这个时候怎么办? 那么就让子类重新实现就可以了. 这个就叫做方法的重写    那就让子类重写这个方法就可以了.

2. 重写

1) 什么时候子类要重写父类的方法?

子类有这个方法,但是子类的这个方法的实现和父类不一样.

2) 如何重写?

子类在类实现中 重新按照自己的方式重新实现这个方法就可以了.

3)创建子类对象,调用子类对象的方法如果方法被重写了,调用的就是子类重写的方法.

---特别注意----

当1个父类指针指向1个子类对象的时候,如果通过这个父类指针调用的方法被子类重写了,那么调用的就是子类重写的方法.

重写父类的方法的另外一种场景.

子类认为父类的方法做的事情还不够想要在父类方法的基础之上,再多做一些事情.那么这个时候,也可以使用方法重写.使用super先调用1次父类的方法. 然后再加上自己的代码.

#pragma mark - Day05_08_多态

多态

同一种行为,对于不同的事物而言,具有完全不同的表现形式

代码表现形式:

父类指针指向子类对象,同样的行为在子类中表现形式不同

作用:

1.降低代码之间的耦合度

2.增强代码之间的扩展性

#pragma mark - Day05_09_description方法

1.  NSLog一个对象的时候

可以使用%p

也可以使用%@

%p: 打印的是指针变量的值,也就是对象的地址.

%@: 打印的是指针指向的对象.

2. 根据我们的观察.我们使用%@打印1个对象的时候. 输出的格式.

<对象所属的类名:对象的地址>

3. %@的原理.

1). 先调用传入的对象的description方法.

这个方法有1个返回值,是NSStrring字符串.

2). 拿到这个方法返回的字符串数据,将这个字符串输出在控制台.

4. description方法 是定义在NSObject类中的1个方法.

所以这个方法所有的对象都有.

这个方法在NSObject类中的实现是:

返回这么1个格式的字符串:

@"<对象所属的类名:对象地址>"

5. 如果你想要自定义打印对象的格式,那么就重写description方法.

#pragma mark - Day05_10_继承的本质

1. 我们创建1个子类对象.子类对象中到底有什么东西?

子类对象中有子类以及它的所有的父类的所有的属性.

2. 访问子类那么肯定就要访问父类.

所以,将子类加载到代码区,肯定也会将父类加载到代码区.

每1个类,都有1个isa指针指向它的父类.

3. 搜索方法的步骤.

1). 通过对象名调用1个方法的时候.

[p1 sayHi];

2). 先通过p1指针找到对象,通过对象的isa指针找到本身这个类.

搜索这个类中是否有这个方法.如果有就执行, 如果没有就通过这个类的isa指针找父类,直到找到NSObject,如果都没有的话 就报错

#pragma mark - Day05_11_类是以Class对象的形式存储在代码段中的

1. 内存中的五大区域

栈: 局部变量

堆: OC对象.自己申请的空间. calloc malloc realloc

BSS段: 未初始化的全局变量、静态变量

数据段: 已经初始化的全局变量、静态变量、常量数据

代码段: 存储代码

2. 类加载.

当类第1次被访问的时候,就会将这个类加载带代码段中.

@interace HMDog : NSObject

{

MKQuanQuan *_qq;

}

3. 关于类加载.

1). 当类第1次被访问的时候.就去加载类

2). 类是以类对象形式存储到代码区

3).当程序结束的时候,类就从代码段中回收

4. 类存储到代码段的形式

1). 任何存储在内存中的数据都有1个类型.

内存中的空间都有类型.

int num = 12;

MKPerson *p1 = [MKPerson new];

2). 将类加载到代码段中存储 肯定也是需要一块空间来存储的.

那么在代码段中存储类的这块空间是什么类型的呢?

3). 加载类到代码段中的过程

a. 先在代码段中创建1个Class类型的对象.

Class是1个类.由系统定义的. 所以我们就可以创建这个类的对象

这个Class对象的作用:用来存储1个类的信息.

b. 将要加载的类的信息存储到这个Class对象中.

类的信息:

类名:

属性s;

方法s;

所以.类是以Class对象的形式存储在代码段中. 这个Class对象我们叫做类对象.

所以,类是以类对象的形式存储在代码段中.

5. 如何取到存储类的Class对象.

1).要声明Class指针不需要加*了.

因为Class类型是1个typedef类型,在定义的时候就已经加*了.

2). 调用对象的class方法 就可以得到  存储 这个对象所属的类 的Class对象

MKPerson *p1 = [MKPerson new];

Class c1 =  [p1 class];

c1就指向存储MKPerson类的Class对象.

3). 调用类的类方法class. 就可以得到存储这个类的Class对象.

Class c2 = [MKPerson class];

c2指向存储MKPerson类的Class对象.

6. 如何使用类对象.

1). 类对象中存储的是 类的信息.

Class c1 = [MKPerson class];

c1对象中存储的是MKPerson类的信息.

所以 我们可以这么认为, c1对象就代表MKPerson类

c1对象和MKPerson类完全等价.

2). 所以,我们就可以使用这个Class对象来调用存储在Class对象中的类的类方法.

3). 也可以使用这个Class对象来创建 存储在 这个Class对象中的 类的对象

Class c1 = [MKPerson class];

MKPerson *p1 =  [c1 new];

[p1 sayHi];

#pragma mark - Day05_12_SEL数据

1.  调用1个对象的对象方法有两种方式.

1). 通过对象名来调用.

MKPerson *p1 = [MKPerson new];

[p1 sayHi];

2). 手动的为对象发送SEL消息.

MKPerson *p1 = [MKPerson new];

[p1 performSelector:@selector(sayHi)];

2. SEL全称: selector

3. SEL实际上是1个类. 这个类的对象是用来存储方法的.

为什么?

因为要存储1个方法,1个方法的信息有很多.

方法名

参数

返回值

方法体

所以,Class对象存储类信息的方式如下.

1). Class对象中有1个属性是专门来存储类的方法的.

2). 这个属性的类型是SEL指针类型.

3). 创建1个SEL对象.将方法的信息存储在这个对象中.

4). 将这个SEL对象的地址赋值给Class对象的SEL属性

4. 如何拿到存储方法的SEL对象?

1). 因为SEL是1个typedef定义类型.定义的时候就已经有*了.

所以在声明SEL指针的时候,就不需要再加*了.

2). 拿到存储方法的SEL对象的格式

SEL s1 = @selector(方法名);

强调 带了参数的方法的方法名是带了冒号的.

如果给定的方法名不存在.不会报错.只不过拿到1个无效的SEL地址.

3). SEL中,存储的不仅仅是方法的地址 还有一些别的信息.

比如这个方法是属于哪1个类的......

5. 调用方法的本质.

MKPerson *p1 = [MKPerson new];

[p1 sayHi];

1). 先拿到存储sayHi方法的SEL对象的地址. 我们加做SEL消息. SEL数据.

2). 将这个SEL消息发送给p1对象.

3). 当对象收到发送过来的SEL消息的时候.对象就知道你一定是要调用我的方法.

4). 对象再根据isa指针找到存储类的Class对象.

在Class对中搜索是否有匹配的SEL数据 ,如果有 就执行,如果没有 就搜索父类直到NSObject

5. 手动的为对象发送SEL消息

1). 先得到存储方法的SEL消息.

SEL s1 = @selector(sayHi);

2). 将这个SEL消息发送给对象.

[p1 performSelector:s1];

3). 问题.

如果带了参数怎么解决.

SEL消息中不仅仅是地址,还有别的可以区分方法的数据.