OC课程-009: 访问修饰符,特有语法

self关键字:

在对象方法中,父类的成员对于子类来说,也是属于子类的。所以父类的成员在子类的方法中也可以使用self关键字来访问。

多态的前提:有方法的重写。


89. 子类在内存中的存储和方法调用过程。

一、创建一个对象,对象在内存中是如何分配的。

1). 子类对象中有自己的属性,同时拥有所有父类的属性。
2). 代码段中的每1个类,都有1个叫isa的指针,这个指针指向它的父类。
一直指到NSObject。
[p1 sayHi];
在调用方法的时候,先根据p1指针找到p1指向的对象,然后根据对象的isa指针找到Person类。
搜索Person类中是否有这个sayHiffa ,如果有就执行;
如果没有,就根据类的isa指针找父类.....
NSObject 类都没有,就报错。

内存关系图

90. 结构体与类的区别

结构体与对象的相同点

都可以将多个数据封装为1个整体。

结构体与对象的不同点

a. 结构体只能封装数据,类不仅可以封装数据 还可以封装行为。
b. 结构体变量分配在栈空间(局部变量的情况下);
而对象分配在堆空间。
----栈的特点:空间相对较小,但是存储在栈中的数据,访问的效率更高一些。
----堆的特点:空间相对较大,但是存储在堆中的数据,访问的效率相对低一些。
----栈中存储的数据访问效率高;堆中存储的数据访问效率低。
c. 赋值:
结构体:Student
类:Person

Student s1 = {"jack", 19, GenderMale};
Student s2 = s1; // 值赋值。copy赋值

Person *p1 = [Person new];
Person *p2 = p1; // 对象的地址赋值。

应用场景。

a. 如果表示的这个实体,不仅由多个数据组成,还有行为,只能使用类。
b. 如果表示的实体没有行为,光有属性。
-- 如果属性较少,只有结果。那么这个时候就定义为结构体,分配在栈,提高效率。
-- 如果属性较多,不要定义为结构体。因为这样结构体变量会在栈中占据很大的1块空间,反而会影响效率。

typedef struct {
    int year;
    int month;
    int day;
} Date; // 日期结构体

@interface Book: NSObject {
    NSString *_name;
        Auther *_auther; // 作者类
    Date _publishDate; // 结构体:不需要加星号 *
}

Book *b1 = [Book new];
[b1 setName:@"钢铁侠"];
[b1 setPublishDate:{2020, 11, 22}];
Author *a1 = [Author new];
[a1 setName:@"奥斯特洛夫斯基"];
[b1 setAuthor: a1];

91. 类的本质:类是以class对象,存储在代码段

类加载:当类第1次被访问的时候,这个类就会被夹在到代码段存储起来。

  1. 类什么时候,加载到代码段?

类第1次被访问的时候,这个类就会被夹在到代码段存储起来。类加载。

  1. 类以什么样的形式,存储在代码段?

  2. 类一旦被加载到代码段之后,什么时候回收?

是不会被回收的,除非程序结束。

类以什么样的形式存储在代码段的?
  • 任何存储在内存中的数据,都有1个数据类型。
  • 在代码段存储类的那一块空间,是什么类型的?
    在代码段中,存储类的步骤。
    ---a. 先在代码段中,创建一个Class对象,Class是Foundation框架中的1个类。
    这个Class对象,就是用来存储类信息的。
    ---b. 将类的信息,存储在这个Class对象之中。
    这个Class对象,至少有三个属性:
    ------类名:存储的这个类的名称;
    ------属性们:存储的这个类,具有那些属性。
    ------方法们:存储的这个类,具有那些方法。

所以。类是以Class对象的形式,存储在代码段的。
存储类的这个Class对象,也叫做类对象,用来存储类的1个对象。
所以,存储类的类对象,也有一个叫isa指针的属性,这个指针指向存储其父类的类对象

image.png

92. 类对象Class的使用

  1. 如何拿到存储在代码段中的类对象。
    a. 调用类的类方法 class,就可以得到存储类的类对象的地址。
   Class c1 = [Person class]; // Class类型本身带*
   NSLog(@"c1 = %p", c1);

b. 调用对象的对象方法class,就可以得到存储这个对象所属的类的Class对象的地址。

 Person *p1 = [Person new];
 Class c2 = [p1 class];
 NSLog(@"c2 = %p", c2);

c. 对象中的isa指针的值,其实就是代码段中存储类的类对象的地址。

注意:
声明Class指针的时候,不需要加号,因为在typedef的时候已经加了号了。

  1. 如何使用类对象----Class
    a. 拿到存储类的类对象后。

Class c1 = [Person class];
c1对象就是Person类。
c1 完全等价于 Person

b。使用类对象来调用类的类方法。
因为类对象,就代表存储在这个类对象中的类。c1就是Person类。所以在使用Person的地方,完全可以使用c1代替。
[Person sayHi]; 等价与 [c1 sayHi];

c. 可以使用类对象,来调用new方法,来创建存储在类对象中的 类的对象。
Person *p1 = [Person new];
Class c1 = [Person class];
Person *p2 = [c1 new];

d. 注意:

使用类对象,只能调用类的方法,因为类对象就等价于存在其中的类。
Class c1 = [Person class];
c1就是Person。

e:总结:
------ 类是以Class对象的形式,存储在代码段之中的。
------ 如何拿到存储类的类对象。
------ 作用:可以使用类的对象,调用类的类方法。
Class c1 = [Person class]; 要调用Person的类方法,可以使用Person去调用,也可以使用c1去调用。


93. 方法的本质:SEL对象。

  1. SEL:全称是selector选择器。

SEL是1个数据类型,所以需要在内存中申请 空间存储数据。
SEL其实是1个类, SEL对象是用来存储1个方法的。

  1. 类是以Class对象的形式,存储在代码段之中的。

类名:存储这个类的类名。NSString.
还要将方法存储在类对象之中,如何将方法存储在类对象之中。

  • 先创建1个SEL对象。
  • 将方法的信息,存储在这个SEL对象之中。
  • 再将这个SEL对象,作为类对象的属性。
  1. 拿到存储方法的SEL对象。
  • 因为SEL是1个typedef类型的,在自定义的时候已经加了。
    所以在声明SEL的时候,不需要加
  • 取到存储方法的SEL对象。
    SEL s1 = @selector(sayHi);
    NSLog(@"s1 = %p", s1);
  1. 调用方法的本质:[p1 sayHi]; 先对象发送SEL消息。

a. 先拿到存储sayHi方法的SEL对象,也就是拿到存储sayHi方法的SEL数据。SEL消息。
b. 将这个SEL消息,发送给p1对象。
c. 这个时候,p1对象接收到SEL消息后,就知道要调用方法。
d. 根据对象的isa指针,找到存储类的类对象。
e. 找到这个类对象之后,在这个类对象中去搜索是否有和传入的SEL数据相匹配的。
如果有,就执行。
如果没有,再找父类,直到找到NSObject.

image.png

OC最重要的1个机制:消息机制。
调用方法的本质,其实就是为对象发送SEL消息。
[p1 sayHi]; 为p1对象,发送1条sayHi消息。


94. 手动向对象发送SEL消息。

5. 重点概念:
  • 方法是以SEL对象的形式存储起来的。
  • 如何拿到存储方法的SEL对象。


    内存结构图--SEL

使用1个SEL对象存储1个方法方法。

6. 手动的为对象发送SEL消息。
  • 先得到方法的SEL数据.
  • 将这个SEL消息发送给p1对象。
  • 调用1个对象的方法有两种形式:
    ----- [对象名 方法名];
    ----- 手动的为对象发送SEL消息。
Person *p1 = [Person new];
SEL s1 = @selector(sayHi); // 拿到sayHi方法的SEL对象。
[p1 performSelector:s1]; // 为p1发送一条s1消息(调用sayHi方法)。等价于`[p1 sayHi];`
7. 注意事项:
  • 如果方法有参数,那么方法名是带了冒号的。
  • 如果方法有参数,如何传递参数?(调用带withObject的方法)
Person *p1 = [Person new];
SEL s1 = @selector(eatWithFood:); // 拿到sayHi方法的SEL对象。
[p1 performSelector:s1  withObject:@"红烧肉"]; // 传递参数。
  • 如果有多个参数,把参数封装在1个对象里面,
8.总结:
  • 类是以Class对象的形式存储在代码段。
  • 如何取到存储类的类对象。
  • 如何使用类对象,调用类的类方法。
  • 方法是以SEL数据的形式存储的。
  • 调用方法的两种形式。

95. 点语法

Java、C# 对象可以使用点语法,来访问对象的成员。
OC中也有点语法。OC中也可以使用点语法来访问独享的睡醒。
使用OC的点语法和Java、C# 是完全不同的。
OC对象想要为属性赋值或取值,就要调用对应的getter或setter方法。

1. 使用点语法来访问对象的属性。

语法:对象名.去掉下划线的属性名

Person *p1 = [Person new];
p1.name = @"Jack";   // 给属性赋值
NSString *name = p1.name;  // 取p1对象name属性中的值
2. 点语法的原理

p1.age = 18;
这句话的本质,并不是把18直接赋值给p1对象的_age属性。

点语法在编译器编译的时候。其实会将点语法转换成调用setter,getter的代码。

当使用点语法赋值的时候,这个时候编译器会将点语法,转换为调用setter方法的代码。

对象名.去掉下划线的属性名 = 数据;
转换为: [对象名 set去掉下划线的属性名,首字母大写:数据];
p1.age =10; <====== > [p1 setAge:10];

当使用点语法取值的时候,这个时候编译器会将点语法,转换为调用getter方法的代码。

对象名.去掉下划线的属性名;
转换为: [对象名 去掉下划线的属性名];
int age = p1.age <====== > int age = [p1 age];

3. 注意:
  • 在setter和getter里面,慎用点语法,容易造成死循环(无限递归调用)。

  • 点语法。最终在编译期编译的时候,会转换为setter,getter方法的代码。
    如果setter方法,getter方法名不符合规范,那么点语法就会出问题。
    p1.name = @"jack";
    [p1 setName:@"jack"];
    NSString *name = p1.name;
    NSString *name = [p1 name];

  • 想要使用点语法,必须为属性创建getter,setter方法。
    如果属性没有封装getter,setter方法,是无法使用点语法的。
    因为点语法的本质,是调用getter,setter方法。


96. @property关键字:【编译器自动的,生成getter,setter方法】

写一个类:

  • 先写一个类,声明属性。
  • 再声明属性的getter,setter。
  • 再实现getter,setter方法。

编译器自动的,生成getter,setter方法。-------@property

  1. 作用:自动生成getter、setter方法的声明。
    因为是生成方法的声明,所以应该写在@interface类的声明当中。
  2. 语法:
    @property 数据类型 名称;
    @property int age;
  3. 原理:自动生成getter,getter方法的声明
    编译器在编译的时候,会根据@property 生成getter和getter方法的实现。
    @property 数据类型 名称;
    生成:
  - (void)set首字母大写的名称:(数据类型)名称;
  - (数据类型)名称
@interface Person: NSObject {
    NSString *_name;
    int _age;
}
@property int name;
/*
  -(void)setName:(NSString *)name;
  -(NSString *)name;
*/
@property int age;
/*
  -(void)setAge:(int)age;
  -(int)age;
*/
@end

使用@property的注意:

    1. @property的类型要和属性的类型,要一致。
      @property的名称与属性名称要一致(去掉下划线),不能乱写。
    1. @property的名称决定了生成getter和setter方法的名称。
      所以,@property的名称,要和属性的名称一致(去掉下划线),否则生成的方法名,就是不符合规范的。
      @property的数据类型,决定了生成setter方法的参数类型,getter方法的返回值类型。
    1. @property只是生成getter和setter方法的声明,实现还要自己来,属性还要自己定义。

97. @synthesize关键字。【自动生成getter,setter方法的实现】

@property只能生成getter和setter的声明,方法的实现还是急需要自己来。

  • 作用:自动生成getter,setter方法的实现。所以,应该写在类的实现当中。
  • 语法:@synthesize @property 名称;
@interface Person: NSObject{
    int _age;
}
@property int age;
@end
-----------------------
@implementation Person
@synthesize age; // 自动生成age  setter,getter的实现。
@end
  • @synthesize做的事情:
    a. 生成1个真私有的属性。属性的类型和@synthesize对应的@property类型一致。
    属性的名字和@synthesize对应的@property名字一致。
    b. 自动生成setter方法的实现,
    实现的方式:将参数直接赋值给自动生成的那个私有属性。
    并且没有做任何的逻辑验证。
// @synthesize age  ====> 自动生成的代码。
@implementation Person {
  int age
}
- (void) setAge:(int)age {
    self->age = age;
}
- (int) age {
    return age;
}

c. 自动生成getter方法的实现。
实现的方式:将生成的私有属性的值返回。

3.自动生成的私有属性,有点多此一举。期望@synthesize不要自动生成私有属性。

getter,setter的实现,操作我们自己写好的属性即可。方法的实现,都操作自己写好的属性。
语法:@synthesize @property 名称= 已经存在的属性名;

@synthesize @property age = _age;

  1. 不会再去生成私有属性,直接生成getter,setter的实现。
    setter的实现:把参数的值,直接赋值给指定的属性。
    getter的实现:直接返回指定属性的值。
    2。方法的实现:
/*
- (void)setAge:(int)age {
    _age = age;
}
- (int)age {
    return _age;
}
*/
4. 注意
  1. 如果直接写一个@synthesize, @synthesize name;
    自动生成私有属性,方法的实现操作的是自动生成的私有属性。
  2. 如果指定操作的属性: @synthesize name = _name;
    不会生成私有属性,getter,setter里面操作的是指定的属性。
  3. 生成的 setter方法实现中,是没有任何逻辑判断的,直接赋值。
    生成的 getter方法的实现中,是直接返回属性的值。
  4. 如果setter,getter有自己的逻辑验证,自己在类的实现当中重写就可以了。
- (void)setAge:(int)age{
    if(age>=0 && age<=20) {
        _age = age;
    } else {
        _age = 18;
    }
}
5. 批量声明
  1. 如果多个@property的 类型一致, 可以批量声明。
    @property float height, weight;
  2. @synthesize 也可以批量写。类型不一致,也可以写。
    @synthesize name=_name, age=_age, weight=_weight, height=_height; // 一行抵好几行。

98. @property 增强 *****

@property 只是自动生成getter,setter的声明;
@synthesize 是生成getter,setter的实现。

从Xcode4.4以后, Xcode编译器对@property做了增强。
只需要写1个@property ,编译器就会自动:
a. 生成私有属性;
b. 生成getter,setter的声明;
a. 生成getter,setter的实现。

@interface Student : NSObject
@property NSString *name;
@end

做的事情:

  • 自动的生成1个私有属性,属性的类型与@property类型一致,属性的名称和@property的名称一致,
    属性的名称自动的加1个下划线。_name
  • 自动的生成这个属性的getter,setter方法的声明。
  • 自动的生成这个属性的getter,setter方法的实现。
    setter的实现:直接将参数的值,赋值给自动生成的私有属性。
    getter的实现:直接返回生成的私有属性的值。
3. 使用注意:
  • @property的类型和名字,决定了生成的属性的类型和名字。
    名称要和属性的名称一致,只是去掉下划线

  • @property 也可以批量声明系统类型的属性,批量声明的时候,会自动生成n个属性。

  • @property 生成的方法实现,没有做任何逻辑验证。
    setter,直接赋值; getter,直接返回。可以重写属性对应的setter或getter方法,来实现自定义的功能。
    可以重写setter来自定义验证逻辑。

  • 如果同时重写getter,setter那么就不会生成私有属性,需要自己在{} 中声明。

父类的@property一样可以被子类继承, @property 生成的属性是私有的,在子类中无法直接访问生成的私有属性。
但是可以通过setter,getter来访问。


99. 静态类型和动态类型

OC是一门弱语言,编译器在编译的时候,检查的时候没有那么严格。不管怎么写都可以。
int num = 12.12;
强类型的语言:java, C#

2. 静态类型

指的是1个指针指向的对象是1个本类对象
Pig *p1 = [Pig new];
动态类型:
指的是1个指针指向的对象不是1个本类对象
Animal *a1 = [Pig new];

3. 编译检查(检查指针的类型)

编译器在编译的时候,能不能通过1个指针去调用 指针指向的对象的方法。
判断原则:看指针所属的类型之中是否这个方法。
---有,就可以调用,编译通过;
---没有,那么编译报错。
在编译的时候, 能不能调用对象的方法,主要看指针的类型。跟对象是什么对象,没关系。

可以将指针的类型做转换,来达到骗过编译器的效果。
Animal *a1 = [Pig new];
[(Pig *)a1 eat];

4. 运行检查(检查对象的类型)

编译检查,只是骗过了编译器。但是方法究竟能不能执行?

所以,在运行的时候,还是会再次进行检查对象中是否有这个方法。没对应的方法,会报错。
Pig *p1 = [Animal new];
[p1 run]; // 正常执行
[p1 eat]; // 报错。

5. LSP 父类指针指向子类对象。

实际上,任意的指针可以指向任意的对象。编译器是不会报错的。
当1个子类指针,指向父类对象,编译器运行通过子类指针,去调用子类独有的方法。
但是运行的时候,会出问题。因为父类对象中,根本没有子类成员。


100. NSObject指针 和 id指针。

1. NSObject 指针。

是OC中所有类的基类。根据LSP,NSObject指针,可以指向任意的OC对象。
所以:NSObject指针,是1个万能指针。可以指向任意的OC对象。
NSObject *obj1 = [Person new];

缺点:如果要调用指向的子类对象的独有的方法,就必须要做类型转换。
NSObject *obj3 = @"Jack";
[(NSString *) obj3 length];

2. id指针(万能指针,可以指向任意的OC对象)

id是一个typedef类型,在定义的时候已经带了。所以,声明id指针不要再带
id指针是1个万能指针,任意的OC对象都可以指。

id id1 = [Person new];

3. NSObject, id的异同:

相同点:万能的指针,都可以指向任意的OC对象。
不同点:
通过NSObject指针,去调用对象的方法的时候。编译器会做编译检查。
通过id类型的指针,去调用对象的方法的时候,编译器直接通过,不做编译检查。
id不要做类型转换。声明万能指针,建议用id。

NSObject *obj = [Person new];
// [obj  sayHi];   // 编译报错
id id1 = [Person new];
[id1  length]; // 正常编译通过。执行的时候,会报错。因为对应的对象里面,没有length方法。
[id1  sayHi];  // 编译正常,执行也正常。
4. 注意点:

id指针只能调用方法,不能使用点语法;如果使用点语法,直接报编译错误。
如果要使用setter方法,调用对应的setter方法实现。
如果要声明1个万能指针,不要使用NSObject,而要用id,因为id方便。



最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,254评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,875评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,682评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,896评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,015评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,152评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,208评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,962评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,388评论 1 304
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,700评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,867评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,551评论 4 335
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,186评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,901评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,142评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,689评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,757评论 2 351

推荐阅读更多精彩内容

  • 今天,又开始学习行书书法了! 2023-05-06的下午,是我们的第35、36节课的学习实践,学习的是“...
    房老师391215阅读 47评论 0 1
  • 时间:2023年05月05-06日 姓名:万小平 诵读道德经: 今日诵读上08遍,本月累计24遍! 徒步20000...
    万小平惟道是从_0e6f阅读 81评论 0 0
  • 中原焦点网络中35期 刘燕 坚持分享第366天 2023/05/06(星期六) 要现实看待你的十几岁孩子的...
    墨香叶子阅读 55评论 0 0
  • 【日精进打卡第1835天】 【知~学习】 《六项精进》 《大学》 【读书】 1、《董明珠传》OK 2、《活法》二遍...
    丛培国阅读 51评论 0 0
  • 中原焦点网初网中第30期孙慧阁坚持分享第574天,2023—05—06(星期六,阴) 今天周六继续上课,对于我这种...
    7f9a69fe388c阅读 53评论 0 0