文章目录
前言
我们在前面知道了我们通过对象来访问我们的成员变量,但这里我们引出一系列问题:
如果直接对成员变量进行赋值,这并不合理,那额这里就需要我们对类进行良好的封装。
一、理解封装
封装是面向对象的三大特征之一,还有两个是继承与多态。
它是指将成员变量隐藏在对象内部,不许外部程序直接访问,但是可以通过我们提供的方法对内部信息进行操作与访问。
对一个类或对象实现良好的封装需要实现以下目的:
- 隐藏类的实现细节
- 让使用者只能通过预先设定好的方法对数据进行访问,限制对成员变量的不合理访问。
- 将对象的成员变量以及实现细节隐藏起来,不允许外部直接访问。
- 暴露方法,让方法来控制对这些成员变量进行安全的访问与操作。
封装实际上有两方面的含义,把该隐藏的隐藏起来(指类与对象它们的成员变量以及实现细节),把该暴露的暴露出来(指操作与访问成员变量的方法)
我们需要通过使用OC提供的访问控制符来实现这些功能。
二、使用访问控制符
OC中有四种访问控制符:==@private(当前类访问权限),@package(与映像访问权限相同),@protect(子类访问权限),@public(公共访问权限)。
1.private(当前类访问权限)
成员变量只能在当前类的内部被访问,用于彻底隐藏成员变量。在类的实现部分定义的成员变量默认使用这种访问权限。
2.package(与映像访问权限相同)
成员变量可以在当前类以及当前类实现的同一个映像的任意地方进行访问。用于部分隐藏成员变量
3.protected(子类访问权限)
成员变量可以在当前类,当前类的子类的任意地方进行访问。类的接口部分定义的成员变量默认这个访问权限
4.public(公共访问权限)
这个成员变量可以在任意地方进行访问。
访问控制符用于控制成员变量是否能被其他类访问。
5.良好的封装
接下来我们就给出一个实现良好分装的代码:
接口部分:
//
// FKPerson.h
// 隐藏与封装
//
// Created by 夏楠 on 2023/4/13.
//
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface FKPerson : NSObject {
@private
//使用private来限制成员变量
NSString* _name;
int _age;
}
//提供方法来操作name_Field
- (void)setName:(NSString*)name;
//提供方法来获取_name成员变量的值
- (NSString*)name;
//提供方法来设置age成员变量
- (void)setAge:(int)age;
//提供方法来获取成员变量
- (int)age;
@end
NS_ASSUME_NONNULL_END
我们定义了两个成员变量,同时用@private表明这个成员变量只能在当前类中访问,这也是为什么我们要设置一个getter方法来获取成员变量而不直接调用。
接下来程序为_name,_age分别提供了setter与getter方法来设置成员变量值,获取成员变量值。
实现部分:
//
// FKPerson.m
// 隐藏与封装
//
// Created by 夏楠 on 2023/4/13.
//
#import "FKPerson.h"
@implementation FKPerson
//实现方法设置_name成员变量
- (void)setName:(NSString *)name {
if ([name length] > 6 || [name length] < 2) {
NSLog(@"您设置的年龄不符合要求");
return;
}
else {
_name = name;
}
}
//提供方法来获取_name成员变量的值
- (NSString*)name {
return _name;
}
//提供方法来设置age成员变量
- (void)setAge:(int)age {
if (_age != age) {
if (age > 100 || age < 0) {
//执行合理性检验,要求用户年龄必须在0-100之间
NSLog(@"您设置的年龄不合法");
return;
}
else {
_age = age;
}
}
}
- (int)age {
return _age;
}
@end
在FKPerson类之外我们只能通过setter与getter方法来对成员变量进行访问。
运行部分:
在OC中成员变量的setter与getter方法十分重要,例如我们某个类中有个叫_kobe的成员变量,那么它的setter方法的名字就应该是成员变量名的首字母大写前面加上set,写作setKobe。getter方法的名字应该是去除下划线,kobe。
如果OC中的每个成员变量都被private限制,并且每个成员变量都提供了getter与setter的方法。那么该类就是一个符合规范的类.
三、理解@package访问控制符
@package使受他控制的成员变量不仅可以在当前类访问,还可以在相同映像的其他程序中访问。关键何为相同映像?
同一映像的概念:
简单的说,就是编译后生成的同一个框架或同一个执行文件,当我们想开发一个基础框架时,如果用private就限制的太死了,其他函数可能需要直接访问这个成员变量,但是该框架又不希望外部程序访问我们的成员变量,就可以考虑用package了
当编译器最后把@package限制的成员变量所在的类,其他类,函数编译成一个框架库后,这些类、函数就都在一个映像中(注意这里的函数包括我们的主函数)。也就是说当我们使用@package之后我们的主函数也可以调用我们的成员变量,但是当其他程序引用这个框架库时,由于其他程序与这个框架库不在一个映像中,其他程序就无法访问我们的被@package限制的成员变量。
接口部分:
主函数部分与结果:
注意:
四、合成存取方法
1.合成方法
前面我们介绍了成员变量为自己实现setter与getter方法,但是当我们有多个成员变量时重复定义我们的方法就显得冗杂了,所以在OC2.0之后我们可以我们可以通过一些操作自动合成我们的这两个方法。
这需要以下两步
- 在类接口部分使用@property指令定义我们的属性,我们将其放在我们的interface与end的中间
- 在类实现部分使用@synthesize声明该属性即可。
使用上述两个步骤合成存取方法后,不仅会合成成对的方法,还会在类实现部分定义一个与getter方法同名的成员变量。如果为某个类定义了一个成员变量,并提供了相应的getter与setter方法,那么可称定义了一个属性。
怎么使用@property与@synthesize
当然我们之前说过,成员变量最好开头加上下划线,例如:_age,在@synthesize后面赋值即可,开发工具会默认生成 _age 变量而不是 age
当然我们有一点要注意
但是我们的日常学习中我们还是会在实现部分加上这个关键词。
例子:
接口部分:
实现部分:
如果我们需要为我们的方法添加一些特殊的功能,那么我们只能重写这个方法来覆盖原来xcode帮我们自动实现的方法
函数部分:
2.特殊指示符
我们可以在@property与类型之间添加一些特殊指示符,正如我们前面看到的这样。可使用的特殊指示符如下:
1⃣️assign
该指示符指定对属性指示简单的赋值,不更改对所赋的值的引用计数,主要适用于一些基础数据类型。
2⃣️atomic(nonatomic
指定合成的存取方法是否为线程安全。当一个线程进入存取的方法体之后,去他县城就无法进入该方法,**避免了多线程并发破坏对象的数据完整性。**但是我们一般会用nonatomic来提高存取方法的访问性能。
3⃣️copy
在调用setter方法给成员变量赋值时,会将被赋值的对象生成一个副本,然我将该副本赋值给成员变量。copy关键字和 strong类似,copy 多用于修饰有可变类型的不可变对象上 NSString,NSArray,NSDictionary上。
下面用一个例子来讲解copy:
接口部分:
添加copy前:
添加后:
函数部分:
当我们没有添加copy时接口时这样的
添加copy后
从他人的博客中的值我们引用copy时实际上是对原字符串进行了一次深拷贝,生成了一个新对象,并且copy的对象指向这个新对象。
4⃣️getter与setter
我们可以修改这两个方法的名字
接口部分:
函数部分:
解释:
5⃣️retain
6⃣️strong(强引用)与weak(弱引用)
3.深入理解copy
首先在接口部分我定义了带copy与不带copy的方法
然后我在函数部分分别用两种方法调用它们的成员变量的地址。
在函数部分发现它们的地址不同
原因就是当我们使用copy时自动创建了一个对象的副本,让我们变量指向这个副本,因此改变了地址。
4.使用点语法访问属性
使用点语法访问属性就可以简化我们的操作,使用点语法就像我们c语言中对结构体的成员进行操作一样,但其本质都是调用getter与setter方法。
总结
- 访问控制符是为了更好地实现我们的封装,protect允许我们的子类访问我们的成员变量,package允许我们的函数访问我们的成员变量,实现部分定义成员变量默认private,接口部分默认protect。
- copy是让我们的变量指向了我们复制的新副本。
- 点语法可以让我们的代码操作更便捷。