【OC】关键字@property浅析
写在前面
在开始我们对@property的解释之前我们需要认识一些东西。
访问控制符
访问控制符有四个类型,分别为:@private
,@package
,@protected
,@public
下面以访问权限由低到高来进行依次介绍
@public
@public
声明的成员在任何地方都可以被访问。- 如果将一个成员声明为
@public
,则表示这个成员可以在任何类的任何地方被访问和修改,包括类的外部代码。 - 这种访问控制方式不太安全,因为它允许任何地方对成员进行直接访问,可能导致数据的不稳定性和安全性问题。
@package
@package
声明的成员可以被同一编译单元(即同一源文件)中的其他类访问。- 如果将一个成员声明为
@package
,则表示这个成员可以被同一编译单元中的其他类访问和修改,而在不同编译单元中是不可见的。 - 这种访问控制方式提供了一种介于
@protected
和@private
之间的封装性和安全性,可以确保成员只在当前编译单元内可见。
注:源文件是指包含 Objective-C 代码的文件,通常以
.m
扩展名结尾。这里的编译单元是指在编译时被编译器处理的文件集合,通常是一个源文件及其相关的头文件。因此,同一编译单元中的其他类指的是和当前类属于同一个源文件的其他类。
@protected
@protected
声明的成员只能在当前类及其子类中被访问。- 如果将一个成员声明为
@protected
,则表示这个成员只能在当前类及其子类的方法中被访问和修改,而在类的外部是不可见的。 - 这种访问控制方式提供了一定的封装性和安全性,可以防止外部类直接访问和修改受保护的成员。
编译器在声明成员变量的时候,默认使用的就是@protected权限
@private
@private
声明的成员只能在当前类中被访问。- 如果将一个成员声明为
@private
,则表示这个成员只能在当前类的方法中被访问和修改,而在类的子类和外部代码中是不可见的。 - 这种访问控制方式提供了最高级别的封装性和安全性,可以确保成员只在声明的类内部可见,不会被外部代码访问。
setter方法和getter方法
当我们在以某个类为模板创建对象的时候,我们需要为对象中的成员变量进行赋值,根据我们刚刚所了解的内容可以知道,最简单的方式就是将成员变量的控制权限设置为@public
,然后对其直接进行访问修改。
// MyClass.h
#import <Foundation/Foundation.h>
@interface MyClass : NSObject {
@public
int _num;
}
@end
-----------------------------------------------------------------------------------------
// MyClass.m
#import "MyClass.h"
@implementation MyClass
@end
-----------------------------------------------------------------------------------------
// main.m
#import <Foundation/Foundation.h>
#import "MyClass.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
MyClass *JCClass = [[MyClass alloc] init];
JCClass->_num = 10; // 直接对公共成员变量进行赋值
NSLog(@"公共成员变量的值为: %d", JCClass->_num);
}
return 0;
}
当是如果使用@public
来定义变量,那么在程序的所有地方都可以对这个变量内容进行修改,可能会导致数据出现不稳定性和安全性相关的问题。
为了安全性的考虑,我们需要对对象中的成员变量进行封装。那么如果不用@public
函数那我们又该如何去获取以及修改成员变量的值呢?
那就需要引入我们接下来介绍的setter
和getter
的方法了。
setter方法
Setter 方法用于设置属性的值。它的命名规则通常是属性名去掉下划线前缀并加上首字母大写的 “set” 前缀,然后后接一个冒号 “:”。如果属性名为 num
,那么其对应的 setter 方法一般命名为 setNum:
。
Setter 方法的作用是将传入的参数值赋给属性,并且可以在方法中进行一些逻辑处理,如数据验证、通知其他对象等。
//set方法的模板
- (void)setNum:(type)value {
// 设置属性值
_num = value;
}
一次性赋值多个变量的setter
方法
- (void)setProperty1:(type1)value1 andProperty2:(type2)value2 {
self._num1 = value1;
self._num2 = value2;
}
getter方法
Getter 方法用于获取属性的值。它的命名规则通常是属性名称去掉其前面的下划线_
。如果属性名为 num
,那么其对应的 getter 方法一般命名为 num
。
Getter 方法的作用是返回属性的值,可以在方法中进行一些逻辑处理,如数据转换、计算等。
//getter方法的模板
- (type)num {
// 返回属性值
return _num;
}
总结一下
getter
和 setter
方法提供了对属性的访问接口,在面向对象的封装特性中,他们的存在有着巨大的作用:
- 封装性: 通过 getter 和 setter 方法,我们可以控制属性的读写权限,使得属性的内部实现对外部隐藏。这样可以确保属性的数据安全性和完整性,防止外部直接访问和修改属性,从而实现了类的封装性。
- 数据验证: 在 setter 方法中,我们可以对传入的参数进行验证和处理,确保设置的值符合预期。例如,可以对数值型属性进行范围检查,对字符串属性进行长度限制等,从而避免了不合法数据的设置。
- 副作用处理: 在 setter 方法中,我们可以添加一些额外的逻辑处理或副作用。例如,在设置属性值时,可能需要发送通知给其他对象,或者触发一些与属性相关的操作,这些都可以在 setter 方法中实现。
- 数据存取控制: 通过 getter 和 setter 方法,我们可以控制属性的存取方式。例如,在 getter 方法中,可以进行数据转换或计算,返回一个经过处理的值;在 setter 方法中,可以控制属性的赋值方式,例如进行深拷贝或浅拷贝等。
- 后期扩展: 使用 getter 和 setter 方法可以提高代码的灵活性和可维护性。如果后期需要修改属性的实现方式或增加一些额外的逻辑,只需修改 getter 和 setter 方法的实现,而不需要修改所有使用该属性的地方。
@property
我们通过上面的相关知识了解到了,当我们在创建一个类的时候,需要在方法的实现中为其变量设置其对相应的setter
和getter
方法,那么但一个类中的成员变量特别多的时候呢,为这个类的所有成员写其对应的所有setter
和getter
方法就显得十分繁琐。为了解决这一问题,OC中引入的了@property
这个关键字来实现合成存取方法
在 Objective-C 中,@property
是用来声明类的属性的一个关键字。它提供了对属性的声明和自动生成 getter 和 setter 方法的便利方式。下面详细解释一下 @property
的用法以及与其相关的内容:
@property
的基本语法:
@property (attributes) type name;
attributes
是可选的,用于指定属性的一些特性,比如nonatomic
、readwrite
、readonly
、strong
、weak
等。type
是属性的数据类型,可以是 Objective-C 对象类型,也可以是基本数据类型。name
是属性的名称。
attributes的相关内容
当使用 @property
声明属性时,可以通过 attributes
指定一些属性的特性,以控制属性的行为。下面详细解释一下常见的属性特性:
- 多线程相关:
nonatomic
vsatomic
:nonatomic
:表示属性的访问是非原子性的。在多线程环境下,多个线程同时访问这个属性时,不会进行加锁操作,可能会导致数据竞争和线程安全问题,但性能更高。atomic
:表示属性的访问是原子性的。在多线程环境下,对这个属性的访问会进行加锁操作,确保同时只有一个线程可以访问,从而保证了线程安全,但性能可能会有所下降。
- 读取权限:
readwrite
vsreadonly
:readwrite
:表示属性是可读可写的。编译器会自动生成 getter 和 setter 方法,允许对属性进行读取和写入。readonly
:表示属性是只读的。编译器只会自动生成 getter 方法,不会生成 setter 方法,属性值只能在初始化时被赋值,之后不可修改。
- 强弱参数:
strong
vsweak
:strong
:表示强引用。当使用strong
修饰对象类型属性时,会增加对象的引用计数,确保对象不会在使用过程中被释放,直到所有强引用都失效。weak
:表示弱引用。当使用weak
修饰对象类型属性时,不会增加对象的引用计数,当对象被释放后,弱引用会自动被设置为 nil,避免了悬空引用和内存泄漏的问题。
- **
assign
**assign
:表示直接赋值。用于非 Objective-C 对象类型的属性,如基本数据类型和 C 结构体。不会改变对象的引用计数,也不会在对象释放后将指针置为 nil,因此存在悬空指针的风险。
- **
copy
**copy
:表示属性需要进行深拷贝。当使用copy
修饰 NSString、NSArray、NSDictionary 等不可变对象时,会在赋值时生成一个新的副本,防止原始对象被修改。
@synthesize的作用
如果使用 @property
声明了属性,编译器会自动合成属性的存取方法,无需手动实现。我们就可以用
但有时候,我们可能希望手动实现属性的存取方法,以便添加额外的逻辑或自定义属性的行为。这时就可以使用 @synthesize
指令手动合成属性的存取方法。它的基本语法是:
@synthesize propertyName = instanceVariableName;
propertyName
是属性的名称,表示要合成存取方法的属性。instanceVariableName
是实例变量的名称,表示要与属性关联的实例变量。如果不指定实例变量的名称,则会自动生成一个与属性同名的实例变量。
// MyClass.m
#import "MyClass.h"
@implementation MyClass
@synthesize name = _name; // 合成 name 属性的存取方法,并将其与 _name 实例变量关联
@end
根据以上这样的操作,我们就可以在类的其他方法中直接通过 _name
实例变量来访问和修改 name
属性的值了。
@property的完整使用样例
// Person.h
#import <Foundation/Foundation.h>
@interface Person : NSObject
@property (nonatomic, strong) NSString *name;
@property (nonatomic, assign) int age;
@end
-----------------------------------------------------------------------------------------
// Person.m
#import "Person.h"
@implementation Person
@synthesize name = _name;// 合成 name 属性的存取方法,并将其与 _name 实例变量关联
@synthesize age = _age;// 合成 age 属性的存取方法,并将其与 _age 实例变量关联
@end
-----------------------------------------------------------------------------------------
#import <Foundation/Foundation.h>
#import "Person.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
// 创建 Person 对象
Person *person = [[Person alloc] init];
// 设置属性值
[person setName:@"John"];
[person setAge:30];
// 获取属性值并输出
NSLog(@"Name: %@", [person name]);
NSLog(@"Age: %d", [person age]);
}
return 0;
}
点语法
在我们每次调用或设置类中的属性时,使用默认的getter
以及setter
方法,在实际操作中其实也略显繁琐,那么OC中提供了一种特殊的语法特性——点语法。它用于简化调用对象的方法和访问对象的属性。通过点语法,可以将方法调用和属性访问的语法结合起来,使得代码更加清晰易读。
我们以上文的例子进行点语法的尝试:
#import <Foundation/Foundation.h>
#import "Person.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
// 创建 Person 对象
Person *person = [[Person alloc] init];
// 设置属性值
person.name = @"John";//相当于 [person setName:@"John"];
person.age = 30;//相当于[person setAge:30];
// 获取属性值并输出
NSLog(@"Name: %@", person.name);// 等价于[person name]
NSLog(@"Age: %d", person.age);// 等价于[person age]
}
return 0;
}
本文总结
@property 关键字是 Objective-C 中用于声明类的属性并自动生成对应的 getter 和 setter 方法的重要语法特性。本文以我详细介绍了 @property 的基本语法和常见 attributes 特性,以及与其相关的内容。
访问控制符(@public、@package、@protected 和 @private)用于控制属性的访问权限,实现了类的封装性和安全性。setter 和 getter 方法提供了对属性的访问接口,实现了属性的封装和数据验证功能。@synthesize 指令用于手动合成属性的存取方法,灵活控制属性的存取方式。点语法简化了调用对象方法和访问对象属性的语法,提高了代码的可读性。
- 使用
@property
可以简化类的声明,提高代码的可读性和可维护性。 - 自动生成的 getter 和 setter 方法可以直接通过点语法来访问和修改属性的值,使得代码更加简洁易读。
- 通过属性的特性可以控制属性的行为,如线程安全性、内存管理方式等。
- 属性的合成方法(
@synthesize
)可以手动控制属性的存取方法的实现,提高了灵活性。
总的来说,@property
提供了一种简洁明了的声明属性的方式,同时提供了一些额外的功能,如自动生成getter
和 setter
方法、内存管理、线程安全等,使得代码编写更加高效和可靠。