[iOS]常用修饰符详解
一、内存管理修饰符
这些修饰符主要用于管理对象的内存,包括引用计数和生命周期。
1.strong
strong是一个引用计数修饰符,用于对象的属性,这是默认的属性修饰符。当你将一个对象赋值给一个strong属性时,该对象的引用计数会增加 1,这意味着你拥有了这个对象的所有权,只要你保持对它的引用,它就不会被系统释放。
这对于你需要长期持有的对象非常有用,例如代表某种状态的属性,或者子视图和代理等。但是如果使用不当,就可能导致循环引用,从而造成内存泄露。例如,如果两个对象互相持有对方的strong引用,那么这两个对象就都不会被释放,因为它们的引用计数永远不会降到 0。
示例:
@interface MyObject : NSObject
@property (nonatomic, strong) NSString *name;
@end
@implementation MyObject
- (void)someMethod {
self.name = @"Hello"; // 当这一行代码执行时,@"Hello" 这个字符串的引用计数会增加 1,因为我们使用了 strong 修饰符
}
- (void)dealloc {
// 当 MyObject 被释放时,self.name 也会被释放,因为我们不再持有它的引用
// @"Hello" 这个字符串的引用计数会减少 1,如果它的引用计数变为 0,那么它也会被释放
}
@end
在这个例子中,name 属性由 strong 修饰,因此当你将一个字符串赋值给 name 属性时,该字符串的引用计数会增加 1,反过来,当 MyObject 实例被释放时,name 属性也会被释放,这会导致赋给 name 属性的字符串的引用计数减少 1。如果该字符串的引用计数变为 0,那么它也会被系统释放。
2.weak
weak 是一个引用计数修饰符。当你将一个对象赋值给一个 weak 属性时,不会影响该对象的引用计数。换句话说,你并未获得该对象的所有权,所以无法阻止该对象被释放。
weak 的主要用途是防止循环引用。例如,当你有两个对象相互引用时,你可以将其中一个对象对另一个对象的引用设为 weak,这样就不会形成循环引用,因为 weak 引用不会增加对象的引用计数。
当 weak 引用的对象被释放后,所有指向该对象的 weak 引用会自动被设置为 nil,这可以防止野指针(指向已经释放的内存的指针)的出现。
示例:
@interface MyParentObject : NSObject
@property (nonatomic, strong) NSMutableArray *children;
@end
@implementation MyParentObject
- (void)addChild:(MyChildObject *)child {
if (!self.children) {
self.children = [NSMutableArray array];
}
[self.children addObject:child];
child.parent = self;
}
@end
@interface MyChildObject : NSObject
@property (nonatomic, weak) MyParentObject *parent;
@end
在这个例子中,MyParentObject 类有一个 children 属性,用于存储它的所有子对象。每个 MyChildObject 实例都有一个 parent 属性,用于引用它的父对象。parent 属性是 weak 的,所以即使子对象引用了父对象,也不会增加父对象的引用计数。这可以防止父对象和子对象之间形成循环引用。
当 MyParentObject 实例被释放时,它的 children 属性也会被释放,这会导致所有子对象的引用计数减少 1。如果某个子对象的引用计数变为 0,那么它会被释放,parent 属性会自动被设置为 nil。
3.unsafe_unretained
unsafe_unretained是一个引用计数修饰符。与 weak 修饰符类似,unsafe_unretained 修饰的属性或变量在赋值时不会增加所引用对象的引用计数,也就是说,它并未获得该对象的所有权,所以无法阻止该对象被释放。
然而,与 weak 不同的是,当 unsafe_unretained 引用的对象被释放后,引用该对象的 unsafe_unretained 变量的值并不会自动置为 nil,而是会继续指向被释放的内存空间。如果你在此时访问该变量,就会造成未定义的行为,通常是导致程序崩溃。这就是它被称为 "unsafe" 的原因。
示例:
@interface MyObject : NSObject
@property (nonatomic, unsafe_unretained) NSObject *object;
@end
@implementation MyObject
- (void)someMethod {
NSObject *localObject = [[NSObject alloc] init];
self.object = localObject; // self.object 现在引用了 localObject,但并没有增加它的引用计数
} // 当这个方法返回时,localObject 被释放,但 self.object 仍然指向已经被释放的内存空间
@end
在这个例子中,如果你在 someMethod 方法执行后访问 self.object 属性,就会导致程序崩溃,因为 self.object 指向的内存空间已经被释放。
因此,除非你完全确定所引用的对象的生命周期,否则应该尽量避免使用 unsafe_unretained 修饰符,而应该优先使用 weak 或其他修饰符。
4.copy
copy 是一个属性修饰符,它具有创建一个新的对象副本的功能。当你将一个对象赋值给一个 copy 属性时,实际上赋值的是这个对象的一个新的副本,而不是原对象。这对于一些可变类型的对象(比如 NSMutableString、NSMutableArray 等)非常有用,可以防止原对象的改变影响到拷贝对象。
copy 属性通常用于 NSString、NSArray 和 NSDictionary 这样的不可变对象,确保对象的值语义,避免对象本身改变导致的不可预知的结果。
示例:
@interface MyClass : NSObject
@property (nonatomic, copy) NSString *name;
@end
@implementation MyClass
- (void)setName:(NSString *)name {
_name = [name copy]; // 创建了一个新的字符串副本
}
@end
@implementation AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
MyClass *myObject = [[MyClass alloc] init];
NSMutableString *name = [NSMutableString stringWithString:@"Hello"];
myObject.name = name;
[name appendString:@" World"]; // 改变原字符串,但不会影响到 myObject.name
NSLog(@"%@", myObject.name); // 输出 "Hello",而不是 "Hello World"
return YES;
}
@end
在这个例子中,MyClass 的 name 属性被 copy 修饰。当你将 NSMutableString 对象 name 赋值给 myObject.name 时,实际上赋值的是 name 的一个新的副本。所以当你改变 name 的值时,myObject.name 的值并不会改变。
5.__weak
与 weak 修饰符类似,但是用在局部变量或者实例变量上。
__weak 是一个引用计数修饰符,它在 ARC (Automatic Reference Counting) 环境中起作用。当你将一个对象赋值给一个 __weak 变量时,不会增加该对象的引用计数。这意味着你并没有获得该对象的所有权,所以无法阻止该对象被释放。
__weak 的主要用途是防止循环引用。例如,当你有两个对象相互引用时,你可以将其中一个对象对另一个对象的引用设为 __weak,这样就不会形成循环引用,因为 __weak 引用不会增加对象的引用计数。
另一个重要特性是,当 __weak 引用的对象被释放后,所有指向该对象的 __weak 引用会自动被设置为 nil,这可以防止野指针(指向已经释放的内存的指针)的出现。
示例:
@interface MyParentObject : NSObject
@property (nonatomic, strong) NSMutableArray *children;
@end
@implementation MyParentObject
- (void)addChild:(MyChildObject *)child {
if (!self.children) {
self.children = [NSMutableArray array];
}
[self.children addObject:child];
__weak MyParentObject *weakParent = self;
child.parent = weakParent;
}
@end
@interface MyChildObject : NSObject
@property (nonatomic, weak) MyParentObject *parent;
@end
在这个例子中,MyParentObject 类有一个 children 属性,用于存储它的所有子对象。每个 MyChildObject 实例都有一个 parent 属性,用于引用它的父对象。parent 属性是 weak 的,所以即使子对象引用了父对象,也不会增加父对象的引用计数。这可以防止父对象和子对象之间形成循环引用。
当 MyParentObject 实例被释放时,它的 children 属性也会被释放,这会导致所有子对象的引用计数减少 1。如果某个子对象的引用计数变为 0,那么它会被释放,parent 属性会自动被设置为 nil。
6.__strong
与 strong 修饰符类似,但是用在局部变量或者实例变量上。
__strong 是默认的所有权修饰符,在 ARC(Automatic Reference Counting)环境中,你不需要显式地写出它。当你将一个对象赋值给一个 __strong 变量或属性时,会增加该对象的引用计数。这意味着你获得了该对象的所有权,可以防止该对象被释放。
虽然你可以显式地使用 __strong 修饰符,但在大多数情况下,你并不需要这么做。因为在 ARC 环境中,如果你没有指定其他所有权修饰符(如 __weak、__unsafe_unretained、__autoreleasing),那么变量或属性就会默认被 __strong 修饰。
示例:
在 Objective-C 中,我们有时在块(block)中使用 __strong 来防止一个 __weak 引用过早地被释放。
@interface MyClass : NSObject
@property (nonatomic, strong) NSString *name;
@end
@implementation MyClass
- (void)method {
__weak MyClass *weakSelf = self;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
__strong MyClass *strongSelf = weakSelf;
strongSelf.name = @"Hello";
NSLog(@"Name: %@", strongSelf.name);
});
}
@end
@implementation AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
MyClass *myObject = [[MyClass alloc] init];
[myObject method];
return YES;
}
@end
在这个例子中,我们首先创建了一个 __weak 引用 weakSelf 指向 self。然后我们在一个异步块中创建了一个 __strong 引用 strongSelf,并将 weakSelf 的值赋给 strongSelf。由于 strongSelf 是 __strong 的,所以只要块在执行,self 就不会被释放。然后我们可以安全地使用 strongSelf 来访问和修改 self 的属性。
注意,这个模式通常用于防止块创建循环引用,同时防止 self 在块执行期间被释放。如果你不使用 __strong 引用,那么 self 可能在块执行期间被释放,这可能导致未定义的行为或程序崩溃。
7.__unsafe_unretained
与 unsafe_unretained 修饰符类似,但是用在局部变量或者实例变量上。
__unsafe_unretained 是一个所有权修饰符,它在 ARC (Automatic Reference Counting) 环境中起作用。当你将一个对象赋值给一个 __unsafe_unretained 变量时,不会增加该对象的引用计数。这意味着你并没有获得该对象的所有权,所以无法阻止该对象被释放。
与 __weak 修饰符不同,当 __unsafe_unretained 引用的对象被释放后,所有指向该对象的 __unsafe_unretained 引用不会自动被设置为 nil。这可能导致野指针(指向已经释放的内存的指针)的出现,从而导致程序崩溃。
示例:
@interface MyObject : NSObject
@property (nonatomic, unsafe_unretained) NSString *name;
@end
@implementation AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
MyObject *myObject = [[MyObject alloc] init];
@autoreleasepool {
NSString *string = [NSString stringWithFormat:@"Hello"];
myObject.name = string;
}
NSLog(@"%@", myObject.name); // 这可能会导致程序崩溃
return YES;
}
@end
在这个例子中,MyObject 的 name 属性被 unsafe_unretained 修饰。在 @autoreleasepool 块中,我们创建了一个 NSString 对象,并将其赋值给 myObject.name。然而,当 @autoreleasepool 块结束时,NSString 对象会被释放,因此 myObject.name 成为了一个野指针。在随后的 NSLog 语句中,我们试图访问这个已经被释放的对象,这可能会导致程序崩溃。
通常,我们应当尽量避免使用 __unsafe_unretained 修饰符,除非我们确定对象在整个生命周期内都会一直存在。在大多数情况下,我们应该使用 __weak 或 __strong 修饰符。
8.__autoreleasing
__autoreleasing 是一个所有权修饰符,主要用于处理方法或函数的错误输出参数。当你将一个对象赋值给一个 __autoreleasing 变量时,不会增加该对象的引用计数,但该对象会在当前的 autorelease pool 空出时被释放。这意味着你并没有获得该对象的所有权,但你可以在当前的 autorelease pool 的生命周期内安全地使用该对象。
示例:
- (BOOL)trySomethingWithError:(NSError * __autoreleasing *)error {
if (/* some condition */) {
if (error) {
*error = [NSError errorWithDomain:NSURLErrorDomain
code:400
userInfo:nil];
}
return NO;
}
return YES;
}
- (void)method {
NSError *error = nil;
if (![self trySomethingWithError:&error]) {
NSLog(@"Error: %@", error);
}
}
在这个例子中,trySomethingWithError: 方法接受一个指向 NSError 对象的指针作为参数。该指针被 __autoreleasing 修饰,意味着我们可以将一个新的 NSError 对象赋值给它,而不增加该 NSError 对象的引用计数。然后在 method 方法中,我们创建了一个 NSError 对象并将其地址传递给 trySomethingWithError:。如果 trySomethingWithError: 返回 NO,那么我们就可以打印出错误信息。
这种模式最常见的使用场景是处理 Cocoa 和 Cocoa Touch 的错误传递。在这些环境中,如果一个方法可能会发生错误,那么它通常会有一个额外的参数,这个参数是一个指向 NSError 对象的指针的指针,如上面的例子所示。
9.assign
assign
是一个属性修饰符,它用于生成 setter 方法,用于设置基础数据类型(例如 int,float,double,NSInteger 等)或者 C 数据类型(例如 struct、enum 等)。当你使用 assign
修饰符时,setter 方法将执行一个简单的赋值操作。
需要注意的是,对于 Objective-C 对象,使用 assign
修饰符可能会导致一些问题。如果你使用 assign
修饰一个 Objective-C 对象,并且这个对象在被引用的时候被其他地方释放了,那么当你再次访问这个属性时,程序就会崩溃,因为你正在访问一个已经被释放的对象。这就是所谓的“悬垂指针”。因此,对于 Objective-C 对象,你应该使用 strong
或 weak
修饰符,而不是 assign
。
示例:
@interface MyClass : NSObject
@property (assign, nonatomic) NSInteger myInteger;
@end
@implementation MyClass
- (void)someMethod {
self.myInteger = 10;
NSLog(@"myInteger: %ld", (long)self.myInteger);
}
@end
int main() {
MyClass *myObject = [[MyClass alloc] init];
[myObject someMethod]; // 输出: myInteger: 10
return 0;
}
在这个例子中,myInteger
属性使用 assign
修饰符。在 someMethod
方法中,我们简单地为 myInteger
赋值,并打印其值。
10.retain
retain
是一个属性修饰符,主要用于内存管理。在 MRC(手动引用计数)环境下,当你使用 retain
来修饰一个属性时,setter 方法将保留(增加引用计数)新的对象,同时释放旧对象。
在 ARC(自动引用计数)环境下,你应该使用 strong
而不是 retain
。strong
的语义与 retain
相同,但是在 ARC 管理下,编译器会自动添加必要的 retain 和 release 调用。
示例:
// 注意:这个例子使用了 MRC,所以需要在编译时禁用 ARC
@interface MyClass : NSObject
@property (retain, nonatomic) NSString *myString;
@end
@implementation MyClass
- (void)dealloc {
[_myString release];
[super dealloc];
}
- (void)someMethod {
self.myString = [[[NSString alloc] initWithFormat:@"Hello, World!"] autorelease];
NSLog(@"myString: %@", self.myString);
}
@end
int main() {
MyClass *myObject = [[MyClass alloc] init];
[myObject someMethod]; // 输出: myString: Hello, World!
[myObject release];
return 0;
}
在这个例子中,myString
属性使用 retain
修饰符。在 someMethod
方法中,我们为 myString
赋值,并打印其值。还要注意的是,在 dealloc
方法中,我们需要手动释放 _myString
,以防止内存泄漏。在 main
函数中,我们创建了一个 MyClass
对象,调用了 someMethod
,并在结束时释放了这个对象。
这个例子展示了 retain
的基本用法,但现代的 Objective-C 开发通常会使用 ARC,所以在实践中,你可能不会经常看到 retain
。
二、属性访问修饰符
这些修饰符用于控制属性的访问方式,包括访问权限和访问方法的名称。
1.readonly
表示这个属性只有 getter 方法,没有 setter 方法。
readonly 是一个属性的修饰符。当你将一个属性声明为 readonly 时,你只能在类的实现文件(.m 文件)的初始化方法或者类的 init 方法中设置该属性的值。在其他地方,你只能读取该属性的值,不能修改它。
readonly 的主要用途是创建一个只能从类的内部修改的属性,从而封装类的内部实现,防止类的使用者直接访问和修改类的内部状态。
示例:
// MyObject.h
@interface MyObject : NSObject
@property (nonatomic, readonly) NSString *name;
- (instancetype)initWithName:(NSString *)name;
@end
// MyObject.m
@interface MyObject()
@property (nonatomic, readwrite) NSString *name;
@end
@implementation MyObject
- (instancetype)initWithName:(NSString *)name {
self = [super init];
if (self) {
_name = [name copy];
}
return self;
}
- (void)changeName:(NSString *)newName {
_name = [newName copy];
}
@end
// 使用 MyObject 的例子
@implementation AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
MyObject *myObject = [[MyObject alloc] initWithName:@"Hello"];
NSLog(@"%@", myObject.name); // 输出 "Hello"
[myObject changeName:@"World"];
NSLog(@"%@", myObject.name); // 输出 "World"
// myObject.name = @"World"; // 编译错误:Cannot assign to property: 'name' is a read-only property
return YES;
}
@end
在这个例子中,我们在 MyObject.m 文件中添加了一个类扩展(class extension),并在类扩展中重新声明 name 属性为 readwrite。这样,我们就可以在类的实现中修改 name 属性的值了。然后我们定义了一个 changeName: 方法,用于修改 name 属性的值。在类的使用者看来,name 属性仍然是 readonly 的,他们不能直接修改 name 属性的值,只能通过 changeName: 方法来修改 name 的值。
2.readwrite
readwrite 是一个属性的修饰符,它是属性修饰符的默认值。当你将一个属性声明为 readwrite 时,编译器会为该属性生成一个公开的 getter 方法和一个公开的 setter 方法。你可以使用这两个方法来读取或修改该属性的值。
示例:
// MyObject.h
@interface MyObject : NSObject
@property (nonatomic, readwrite) NSString *name;
@end
// MyObject.m
@implementation MyObject
@end
// 使用 MyObject 的例子
@implementation AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
MyObject *myObject = [[MyObject alloc] init];
myObject.name = @"Hello";
NSLog(@"%@", myObject.name); // 输出 "Hello"
myObject.name = @"World";
NSLog(@"%@", myObject.name); // 输出 "World"
return YES;
}
@end
在这个例子中,MyObject 的 name 属性被声明为 readwrite,所以我们可以自由地读取和修改 name 属性的值。
注意,readwrite 是属性修饰符的默认值,所以在实际编程中,如果一个属性是 readwrite 的,我们通常会省略 readwrite 修饰符。例如,上面的代码可以简化为:
// MyObject.h
@interface MyObject : NSObject
@property (nonatomic) NSString *name;
@end
3.getter
getter 是一个属性的修饰符,用于指定获取该属性值的方法的名称。这在改变布尔值的 getter 方法名时特别有用,例如,将 isFinished 的 getter 方法名从 finished 改为 isFinished。
示例:
// MyObject.h
@interface MyObject : NSObject
@property (nonatomic, getter=isFinished) BOOL finished;
@end
// MyObject.m
@implementation MyObject
@end
// 使用 MyObject 的例子
@implementation AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
MyObject *myObject = [[MyObject alloc] init];
myObject.finished = YES;
if ([myObject isFinished]) {
NSLog(@"My object is finished.");
} else {
NSLog(@"My object is not finished.");
}
return YES;
}
@end
在这个例子中,MyObject 的 finished 属性被声明为 getter=isFinished,所以我们使用 isFinished 方法来获取 finished 属性的值,而不是使用 finished 方法。
注意,getter 修饰符只改变了 getter 方法的名称,不改变 setter 方法的名称。在上面的例子中,我们仍然使用 finished 属性的 setter 方法来设置 finished 属性的值,而不是使用 isFinished 方法。
4.setter
setter 是一个属性 (property) 的修饰符,它允许你自定义属性的 setter 方法的名称。默认情况下,一个属性 name 的 setter 方法的名称是 setName:,但如果你使用 setter 修饰符,你可以改变这个名称。
示例:
// MyObject.h
@interface MyObject : NSObject
@property (nonatomic, setter=setMyName:) NSString *name;
@end
// MyObject.m
@implementation MyObject
@end
// 使用 MyObject 的例子
@implementation AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
MyObject *myObject = [[MyObject alloc] init];
[myObject setMyName:@"Hello"];
NSLog(@"%@", myObject.name); // 输出 "Hello"
[myObject setMyName:@"World"];
NSLog(@"%@", myObject.name); // 输出 "World"
return YES;
}
@end
在这个例子中,MyObject 的 name 属性被声明为 setter=setMyName:,所以我们使用 setMyName: 方法来设置 name 属性的值,而不是使用 setName: 方法。
注意,setter 修饰符只改变了 setter 方法的名称,不改变 getter 方法的名称。在上面的例子中,我们仍然使用 name 属性的 getter 方法来获取 name 属性的值,而不是使用 getMyName 方法。
5.dynamic
dynamic
是一个属性修饰符,它告诉编译器,属性的 getter 和 setter 方法将在运行时动态提供,而不是在编译时生成。通常,这与 Objective-C 的动态特性和 @synthesize
一起使用。
当你将一个属性声明为 dynamic
,你必须提供该属性的 getter 和 setter 方法(对于 readwrite 属性),或者仅提供 getter 方法(对于 readonly 属性)。如果你没有提供这些方法,程序在运行时尝试访问这些方法时会崩溃。
示例:
#import <Foundation/Foundation.h>
@interface MyClass : NSObject
@property (dynamic, nonatomic) NSString *myString;
@end
@implementation MyClass
- (NSString *)myString {
return @"Hello, World!";
}
- (void)setMyString:(NSString *)myString {
NSLog(@"Set myString to: %@", myString);
}
@end
int main() {
MyClass *myObject = [[MyClass alloc] init];
NSLog(@"myString: %@", myObject.myString); // 输出: myString: Hello, World!
myObject.myString = @"New value"; // 输出: Set myString to: New value
return 0;
}
在这个例子中,myString
属性使用 dynamic
修饰符。在 MyClass 的实现中,我们提供了 myString
的 getter 和 setter 方法。在 main
函数中,我们创建了一个 MyClass 对象,获取了 myString
的值,并尝试设置一个新的值。
注意,尽管我们尝试设置 myString
的值,但由于我们的 setter 实现并没有实际改变 myString
的值,所以再次获取 myString
的值时,它仍然是原来的值。
@synthesize指令
@synthesize
不是属性修饰符,而是一个指令,用于告诉编译器自动创建与属性相关的实例变量及其 getter 和 setter 方法。这个指令通常在类的实现文件中使用。在早期的 Objective-C 版本中,如果你声明了一个属性,你需要手动在类的实现中添加 @synthesize
指令。但是从 Objective-C 2.0 开始,如果你没有提供 @synthesize
指令,编译器将自动为你的属性添加它。
示例:
#import <Foundation/Foundation.h>
@interface MyClass : NSObject
@property (nonatomic, strong) NSString *myString;
@end
@implementation MyClass
@synthesize myString = _myString;
- (void)someMethod {
self.myString = @"Hello, World!";
NSLog(@"myString: %@", self.myString);
}
@end
int main() {
MyClass *myObject = [[MyClass alloc] init];
[myObject someMethod]; // 输出: myString: Hello, World!
return 0;
}
在这个例子中,myString
是一个属性,我们在 MyClass 的实现中使用了 @synthesize
指令来自动生成实例变量 _myString
及其 getter 和 setter 方法。在 someMethod
方法中,我们设置了 myString
的值,并打印出来。在 main
函数中,我们创建了一个 MyClass 对象,并调用了 someMethod
方法。
注意,从 Objective-C 2.0 开始,如果你没有提供 @synthesize
指令,编译器将自动为你的属性添加它,所以在现代的 Objective-C 代码中,你可能不会经常看到 @synthesize
指令。
三、线程安全修饰符
这些修饰符用于控制属性的线程安全性。
1.nonatomic
表示这个属性的访问不是线程安全的。这是为了提高性能。
nonatomic 是一个属性的修饰符,它用于指定该属性的访问不需要进行原子(atomic)操作。相对于 atomic(默认值),nonatomic 可以提高属性访问的性能,但它不是线程安全的。如果你的属性可能同时在多个线程中被访问,你应该考虑使用 atomic 来确保线程安全,或者自己实现同步机制。
示例:
// MyObject.h
@interface MyObject : NSObject
@property (nonatomic) NSString *name;
@end
// MyObject.m
@implementation MyObject
@end
// 使用 MyObject 的例子
@implementation AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
MyObject *myObject = [[MyObject alloc] init];
myObject.name = @"Hello";
NSLog(@"%@", myObject.name); // 输出 "Hello"
myObject.name = @"World";
NSLog(@"%@", myObject.name); // 输出 "World"
return YES;
}
@end
在这个例子中,MyObject 的 name 属性被声明为 nonatomic,所以我们可以自由地读取和修改 name 属性的值,而不需要担心性能问题。
注意,在实际编程中,nonatomic 修饰符通常与 strong、weak、assign、copy 等其他修饰符一起使用,例如 @property (nonatomic, strong) NSString *name;。
2.atomic
表示这个属性的访问是线程安全的。
atomic 是一个属性的修饰符,它用于指定该属性的访问需要进行原子(atomic)操作。这意味着 getter 和 setter 方法都会通过锁来确保线程安全,不会在读写操作中被其他线程打断。atomic 是属性修饰符的默认值。
虽然 atomic 提供了线程安全,但是它也有一些性能开销。如果你的属性不可能在多线程环境中被访问,或者你有其他的同步机制,你可以使用 nonatomic 修饰符来提高性能。
示例:
@interface MyObject : NSObject
@property (atomic) NSString *name;
//@property NSString *name; // atomic 是属性修饰符的默认值,可简写为这样
@end
在这个例子中,MyObject 的 name 属性被声明为 atomic,所以我们可以在多线程环境中安全地读取和修改 name 属性的值。
注意:
虽然 atomic 属性提供了某种程度的线程安全性,但它并不绝对。这是因为 atomic 只保证了 getter 和 setter 方法的原子性,但在实际使用中,我们可能需要执行更复杂的操作,而这些操作可能无法通过单个 getter 或 setter 方法完成。
例如,假设我们有一个 atomic 属性 count,我们想要将 count 的值增加 1。我们可能会写出以下的代码:
myObject.count = myObject.count + 1;
看起来这段代码没有问题,但实际上,它并不是线程安全的。因为这段代码实际上执行了两个操作:一个 getter 方法获取 count 的值,和一个 setter 方法设置 count 的新值。虽然这两个方法各自都是原子的,但两者的组合并不是原子的。如果在这两个操作之间,另一个线程也修改了 count 的值,那么我们的代码就会出现问题。
要解决这个问题,我们需要自己实现同步机制,以确保整个操作的原子性。在 Objective-C 中,我们可以使用锁来实现这一点。以下是一个使用 NSLock 的例子:
// MyObject.h
@interface MyObject : NSObject
@property (nonatomic) NSInteger count;
@property (nonatomic, strong) NSLock *lock;
@end
// MyObject.m
@implementation MyObject
- (instancetype)init {
if (self = [super init]) {
_lock = [[NSLock alloc] init];
}
return self;
}
- (void)incrementCount {
[self.lock lock];
_count = _count + 1;
[self.lock unlock];
}
@end
// 使用 MyObject 的例子
@implementation AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
MyObject *myObject = [[MyObject alloc] init];
[myObject incrementCount];
NSLog(@"%ld", (long)myObject.count); // 输出 "1"
[myObject incrementCount];
NSLog(@"%ld", (long)myObject.count); // 输出 "2"
return YES;
}
@end
在这个例子中,我们在 MyObject 中添加了一个 NSLock 对象 lock,并在 incrementCount 方法中使用这个锁来确保 count 的增加操作的原子性。
四、其他属性修饰符
1.__kindof
__kindof 是一个类型修饰符,用于在泛型类型中表示 "某种类型的实例,或者其子类的实例"。__kindof 是在 Xcode 7 和 iOS 9 中引入的,用于改进 Objective-C 泛型的类型安全性。
示例:
// Animal.h
@interface Animal : NSObject
@end
// Dog.h
@interface Dog : Animal
@end
// Kennel.h
@interface Kennel<ObjectType : __kindof Animal *> : NSObject
@property (nonatomic, readonly) ObjectType resident;
- (instancetype)initWithResident:(ObjectType)resident;
@end
// Kennel.m
@implementation Kennel
- (instancetype)initWithResident:(id)resident {
if (self = [super init]) {
_resident = resident;
}
return self;
}
@end
// 使用 Kennel 的例子
@implementation AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
Dog *dog = [[Dog alloc] init];
Kennel *kennel = [[Kennel alloc] initWithResident:dog];
Dog *resident = kennel.resident;
NSLog(@"%@", resident); // 输出 "<Dog: 0x600003c04ff0>"
return YES;
}
@end
在这个例子中,Kennel 是一个泛型类,它的泛型参数 ObjectType 被声明为 __kindof Animal *,所以 ObjectType 可以是 Animal *,也可以是 Animal 的任何子类。在 AppDelegate 的 application:didFinishLaunchingWithOptions: 方法中,我们创建了一个 Dog 对象和一个 Kennel 对象,然后通过 resident 属性获取 Kennel 对象的居民,结果是一个 Dog 对象。
如果我们没有使用 __kindof 修饰符,那么 resident 的类型将被推断为 Animal *,而不是 Dog *。我们可以将 Dog * 对象赋值给 Animal * 变量,但不能将 Animal * 对象赋值给 Dog * 变量,除非进行显式类型转换。__kindof 修饰符消除了这个问题,使得代码更加简洁和类型安全。
五、可空性修饰符
这些修饰符用于表示参数、返回值或属性是否可以为 nil。
1.nullable
nullable 是一个类型修饰符,它表示一个值可以是 nil。nullable 修饰符主要用于提高类型安全性,因为它可以帮助编译器和开发者知道哪些值可能是 nil,从而避免意外地访问 nil 值。nullable 修饰符通常用于方法的返回值和参数,以及属性。
示例:
// MyObject.h
@interface MyObject : NSObject
@property (nonatomic, strong, nullable) NSString *name;
- (nullable NSString *)getName;
- (void)setName:(nullable NSString *)name;
@end
// MyObject.m
@implementation MyObject
- (NSString *)getName {
return _name;
}
- (void)setName:(NSString *)name {
_name = name;
}
@end
// 使用 MyObject 的例子
@implementation AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
MyObject *myObject = [[MyObject alloc] init];
[myObject setName:nil];
NSLog(@"%@", [myObject getName]); // 输出 "(null)"
[myObject setName:@"Hello"];
NSLog(@"%@", [myObject getName]); // 输出 "Hello"
return YES;
}
@end
在这个例子中,MyObject 的 name 属性和 setName: 方法的参数都被声明为 nullable,所以我们可以使用 nil 值。
如果我们没有使用 nullable 修饰符,那么编译器可能会警告我们 nil 值可能会导致问题。nullable 修饰符消除了这个问题,使得我们可以明确地表示某些值可以是 nil。
2.nonnull
nonnull 是一个类型修饰符,用于指定一个值不应该为 nil。nonnull 修饰符主要用于提高类型安全性,因为它允许编译器和开发者知道哪些值不应该为 nil,这样可以避免在运行时出现意外的 nil 值。
示例:
// MyObject.h
@interface MyObject : NSObject
@property (nonatomic, strong, nonnull) NSString *name;
- (nonnull NSString *)getName;
- (void)setName:(nonnull NSString *)name;
@end
// MyObject.m
@implementation MyObject
- (NSString *)getName {
return _name;
}
- (void)setName:(NSString *)name {
_name = name;
}
@end
// 使用 MyObject 的例子
@implementation AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
MyObject *myObject = [[MyObject alloc] init];
[myObject setName:@"Hello"];
NSLog(@"%@", [myObject getName]); // 输出 "Hello"
return YES;
}
@end
在这个例子中,MyObject 的 name 属性和 setName: 方法的参数都被声明为 nonnull,所以我们不能使用 nil 值。
如果我们尝试使用 nil 值,编译器将给出警告,因为 nil 值可能会导致问题。nonnull 修饰符消除了这个问题,使得我们可以明确地表示某些值不应该为 nil。
3.null_unspecified
null_unspecified是一个类型修饰符,用于指定一个值可能是 nil,也可能不是。它被引入是为了帮助 Objective-C 代码与 Swift 代码进行交互,并且它的目的是提供一种过渡机制,使得 Objective-C 代码可以更容易地与 Swift 的强类型系统进行交互。
null_unspecified 修饰符实际上表示 "开发者没有指定这个值是否可以为 nil"。这意味着,如果你看到 null_unspecified 修饰符,你应该假设这个值可能是 nil,并且在使用这个值之前进行检查。
示例:
// MyObject.h
@interface MyObject : NSObject
@property (nonatomic, strong, null_unspecified) NSString *name;
- (null_unspecified NSString *)getName;
- (void)setName:(null_unspecified NSString *)name;
@end
// MyObject.m
@implementation MyObject
- (NSString *)getName {
return _name;
}
- (void)setName:(NSString *)name {
_name = name;
}
@end
// 使用 MyObject 的例子
@implementation AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
MyObject *myObject = [[MyObject alloc] init];
[myObject setName:@"Hello"];
NSLog(@"%@", [myObject getName]); // 输出 "Hello"
[myObject setName:nil];
NSLog(@"%@", [myObject getName]); // 输出 "(null)"
return YES;
}
@end
在这个例子中,MyObject 的 name 属性和 setName: 方法的参数都被声明为 null_unspecified,所以我们可以使用 nil 值。
你应该尽量避免在新的 Objective-C 代码中使用 null_unspecified 修饰符,而是明确指定值是否可以为 nil,使用 nullable 或 nonnull 修饰符。null_unspecified 修饰符主要用于帮助旧的 Objective-C 代码与 Swift 代码进行交互。
4.null_resettable
null_resettable是一个可空性修饰符,它适用于属性或方法的返回值,表示该属性或方法的返回值在正常情况下不应为 nil,但其 setter 方法可以接受 nil 参数。在接受 nil 后,getter 方法会返回一个默认值,而不是 nil。
这在一些特定的情况下非常有用,比如你可能有一个属性,其默认值是非 nil,但你希望允许用户将其设置为 nil 来恢复默认值。
示例:
// MyObject.h
@interface MyObject : NSObject
@property (nonatomic, strong, null_resettable) NSString *name;
- (nonnull NSString *)getName;
- (void)setName:(nullable NSString *)name;
@end
// MyObject.m
@implementation MyObject
- (NSString *)getName {
if (!_name) {
_name = @"Default";
}
return _name;
}
- (void)setName:(NSString *)name {
if (name) {
_name = name;
} else {
_name = @"Default";
}
}
@end
// 使用 MyObject 的例子
@implementation AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
MyObject *myObject = [[MyObject alloc] init];
[myObject setName:@"Hello"];
NSLog(@"%@", [myObject getName]); // 输出 "Hello"
[myObject setName:nil];
NSLog(@"%@", [myObject getName]); // 输出 "Default"
return YES;
}
@end
在这个例子中,MyObject 的 name 属性被声明为 null_resettable,所以我们可以使用 nil 值设置它,但是当我们获取它的值时,如果它的值是 nil,那么将返回一个默认值 "Default"。
null_resettable 修饰符的使用场景并不常见,但在某些特定的情况下,例如你有一个属性,它的默认值不是 nil,并且你希望在设置为 nil 时返回一个默认值,那么 null_resettable 修饰符将会非常有用。
六、变量修饰符
这些修饰符用于修饰局部变量或实例变量。
1.__block
__block 是一个变量修饰符,主要用于在 block 中捕获和修改局部变量的值。默认情况下,block 会捕获并保留它所使用的局部变量的当前状态,这意味着即使在 block 外部改变了变量的值,这个改变也不会反映在 block 内部。然而,如果你使用 __block 修饰符来声明一个变量,那么这个变量在 block 内部就可以被修改。
示例:
@implementation AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
__block int myVariable = 0;
void (^myBlock)(void) = ^{
myVariable = 5;
NSLog(@"Inside block: %d", myVariable); // 输出 "Inside block: 5"
};
myBlock();
NSLog(@"Outside block: %d", myVariable); // 输出 "Outside block: 5"
return YES;
}
@end
在这个例子中,我们声明了一个 __block 变量 myVariable,并在一个 block 中修改了它的值。然后我们在 block 外部打印出 myVariable 的值,可以看到 myVariable 的值已经被 block 修改。
如果我们没有使用 __block 修饰符,那么 myVariable 的值在 block 内部是不能被修改的,尝试这样做将会导致一个编译器错误。
注意,在使用 __block 修饰符时,你需要注意内存管理的问题,因为 __block 变量的生命周期可能会超过它们原来的作用域。在 ARC(Automatic Reference Counting)环境下,__block 变量会在 block 被复制到堆上时被自动保留,并在 block 被销毁时被释放。
2.__unused
用在变量上,表示这个变量可能没有被使用,编译器不应该产生未使用变量的警告。
__unused 是一个变量修饰符,用于告诉编译器一个特定的变量可能不会被使用。这主要用于防止编译器发出未使用变量的警告。这在某些情况下是很有用的,例如你有一个函数或方法的参数,这个参数在某些实现中可能不会被使用,但在其他实现中可能会被使用。
示例:
@implementation AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
int __unused myVariable = 5;
// 其他代码...
return YES;
}
@end
在这个例子中,我们声明了一个变量 myVariable 并使用 __unused 修饰符告诉编译器这个变量可能不会被使用。即使我们没有在后面的代码中使用 myVariable,编译器也不会发出未使用变量的警告。
注意,尽管 __unused 修饰符可以防止未使用变量的警告,但你应该避免在没有必要的情况下使用它。未使用变量的警告通常是有用的,因为它们可能表明代码存在问题,例如有些代码被错误地删除或遗忘。只有当你确定一个变量可能不会被使用,但你仍然需要声明它时,才应该使用 __unused 修饰符。
七、函数和方法修饰符
这些修饰符用于添加函数或方法的特殊属性,包括废弃信息和平台可用性。
1.attribute
attribute是一个用于向编译器提供关于代码行为的额外信息的修饰符。它们可以应用于函数、变量、参数等,以改变它们的行为,或者提供编译时的额外信息。
这里有一些常见的 attribute 修饰符:
-
__attribute__((deprecated)):标记一个函数、方法或类为过时的。当它们被使用时,编译器将发出一个警告。
-
__attribute__((unused)):标记一个变量为可能未使用,防止编译器发出未使用变量的警告。
-
__attribute__((nonnull)):指示函数或方法的某个或全部参数不能为 nil。
-
__attribute__((visibility("default")))
和__attribute__((visibility("hidden")))
:这两个修饰符用于控制符号(例如函数)的可见性,相当于其他编程语言中的 public 和 private。
示例:
@interface MyClass : NSObject
- (void)myMethod __attribute__((deprecated));
@end
@implementation MyClass
- (void)myMethod {
// some deprecated code...
}
@end
@implementation AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
MyClass *myObject = [[MyClass alloc] init];
[myObject myMethod]; // Compiler will issue a warning here
return YES;
}
@end
在这个例子中,MyClass 的 myMethod 方法被标记为过时的,因此在 AppDelegate 的 application:didFinishLaunchingWithOptions: 方法中使用它时,编译器将发出一个警告。
使用 attribute 修饰符可以帮助你更好地控制代码的行为,并提供更多的编译时信息,使得代码更安全,更易于维护。
2.__available
在 Objective-C 和 Swift 中,我们使用 API_AVAILABLE(在 Objective-C 中)或 @available(在 Swift 中)来指定特定 API 在哪个版本的操作系统中是可用的。这对于维护向后兼容性非常有用,因为你可以在代码中检查操作系统的版本,然后决定是否调用某个特定的 API。
通过使用 API_AVAILABLE 或 @available,你可以确保你的应用在旧版本的操作系统上仍然可以正常工作,同时在新版本的操作系统上可以利用新的 API 功能。
示例:
在 Objective-C 中:
#import <UIKit/UIKit.h>
@interface MyViewController : UIViewController
- (void)useNewAPI API_AVAILABLE(ios(11.0));
@end
@implementation MyViewController
- (void)useNewAPI {
if (@available(iOS 11.0, *)) {
// Call the API that's available on iOS 11.0 or newer
} else {
// Call the older API
}
}
@end
在这个例子中,MyViewController 的 useNewAPI 方法中使用了 @available 检查操作系统的版本,然后决定调用哪个 API。
在 Swift 中:
import UIKit
class MyViewController: UIViewController {
func useNewAPI() {
if #available(iOS 11.0, *) {
// Call the API that's available on iOS 11.0 or newer
} else {
// Call the older API
}
}
}
在这个例子中,MyViewController 的 useNewAPI 方法中使用了 #available 检查操作系统的版本,然后决定调用哪个 API。
3.__deprecated
__deprecated 是一个变量、函数或方法修饰符,用于标记它们已经被弃用。当一个被 __deprecated 标记的元素被使用时,编译器将发出一个警告,提示开发者这个元素已经过时。
这是一个重要的特性,因为它可以帮助开发者及时发现并替换过时的代码,避免在未来的版本中出现问题。
示例:
@interface MyClass : NSObject
- (void)oldMethod __deprecated;
@end
@implementation MyClass
- (void)oldMethod {
// 这个方法已经被弃用
}
@end
@implementation AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
MyClass *myObject = [[MyClass alloc] init];
[myObject oldMethod]; // Compiler will issue a warning here
return YES;
}
@end
在这个例子中,MyClass 的 oldMethod 方法被标记为过时的。因此,在 AppDelegate 的 application:didFinishLaunchingWithOptions: 方法中使用它时,编译器将发出一个警告。
你也可以使用 __deprecated_msg() 来提供一个具体的消息,说明应该使用什么替代的方法或函数。例如:
- (void)oldMethod __deprecated_msg("Use newMethod instead");
这样,当 oldMethod 被调用时,编译器将发出一个警告,提示开发者应该使用 newMethod 替代 oldMethod。