[iOS]常用修饰符详解

一、内存管理修饰符

这些修饰符主要用于管理对象的内存,包括引用计数和生命周期。

1.strong

strong是一个引用计数修饰符,用于对象的属性,这是默认的属性修饰符。当你将一个对象赋值给一个strong属性时,该对象的引用计数会增加 1,这意味着你拥有了这个对象的所有权,只要你保持对它的引用,它就不会被系统释放。

这对于你需要长期持有的对象非常有用,例如代表某种状态的属性,或者子视图和代理等。但是如果使用不当,就可能导致循环引用,从而造成内存泄露。例如,如果两个对象互相持有对方的strong引用,那么这两个对象就都不会被释放,因为它们的引用计数永远不会降到 0。

示例:

Swift 复制代码
@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,这可以防止野指针(指向已经释放的内存的指针)的出现。

示例:

Swift 复制代码
@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" 的原因。

示例:

Swift 复制代码
@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 这样的不可变对象,确保对象的值语义,避免对象本身改变导致的不可预知的结果。

示例:

Swift 复制代码
@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,这可以防止野指针(指向已经释放的内存的指针)的出现。

示例:

Swift 复制代码
@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 引用过早地被释放。

Swift 复制代码
@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。这可能导致野指针(指向已经释放的内存的指针)的出现,从而导致程序崩溃。

示例:

Swift 复制代码
@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 的生命周期内安全地使用该对象。

示例:

Swift 复制代码
- (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 对象,你应该使用 strongweak 修饰符,而不是 assign

示例:

Swift 复制代码
@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 而不是 retainstrong 的语义与 retain 相同,但是在 ARC 管理下,编译器会自动添加必要的 retain 和 release 调用。

示例:

Swift 复制代码
// 注意:这个例子使用了 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 的主要用途是创建一个只能从类的内部修改的属性,从而封装类的内部实现,防止类的使用者直接访问和修改类的内部状态。

示例:

Swift 复制代码
// 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 方法。你可以使用这两个方法来读取或修改该属性的值。

示例:

Swift 复制代码
// 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 修饰符。例如,上面的代码可以简化为:

Swift 复制代码
// MyObject.h
@interface MyObject : NSObject

@property (nonatomic) NSString *name;

@end

3.getter

getter 是一个属性的修饰符,用于指定获取该属性值的方法的名称。这在改变布尔值的 getter 方法名时特别有用,例如,将 isFinished 的 getter 方法名从 finished 改为 isFinished。

示例:

Swift 复制代码
// 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 修饰符,你可以改变这个名称。

示例:

Swift 复制代码
// 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 属性)。如果你没有提供这些方法,程序在运行时尝试访问这些方法时会崩溃。

示例:

Swift 复制代码
#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 指令,编译器将自动为你的属性添加它。

示例:

Swift 复制代码
#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 来确保线程安全,或者自己实现同步机制。

示例:

Swift 复制代码
// 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 修饰符来提高性能。

示例:

Swift 复制代码
@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。我们可能会写出以下的代码:

Swift 复制代码
myObject.count = myObject.count + 1;

看起来这段代码没有问题,但实际上,它并不是线程安全的。因为这段代码实际上执行了两个操作:一个 getter 方法获取 count 的值,和一个 setter 方法设置 count 的新值。虽然这两个方法各自都是原子的,但两者的组合并不是原子的。如果在这两个操作之间,另一个线程也修改了 count 的值,那么我们的代码就会出现问题。

要解决这个问题,我们需要自己实现同步机制,以确保整个操作的原子性。在 Objective-C 中,我们可以使用锁来实现这一点。以下是一个使用 NSLock 的例子:

Swift 复制代码
// 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 泛型的类型安全性。

示例:

Swift 复制代码
// 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 修饰符通常用于方法的返回值和参数,以及属性。

示例:

Swift 复制代码
// 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 值。

示例:

Swift 复制代码
// 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,并且在使用这个值之前进行检查。

示例:

Swift 复制代码
// 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 来恢复默认值。

示例:

Swift 复制代码
// 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 内部就可以被修改。

示例:

Swift 复制代码
@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 是一个变量修饰符,用于告诉编译器一个特定的变量可能不会被使用。这主要用于防止编译器发出未使用变量的警告。这在某些情况下是很有用的,例如你有一个函数或方法的参数,这个参数在某些实现中可能不会被使用,但在其他实现中可能会被使用。

示例:

Swift 复制代码
@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。

示例:

Swift 复制代码
@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 中:

Swift 复制代码
#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 中:

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 标记的元素被使用时,编译器将发出一个警告,提示开发者这个元素已经过时。

这是一个重要的特性,因为它可以帮助开发者及时发现并替换过时的代码,避免在未来的版本中出现问题。

示例:

Swift 复制代码
@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() 来提供一个具体的消息,说明应该使用什么替代的方法或函数。例如:

Swift 复制代码
- (void)oldMethod __deprecated_msg("Use newMethod instead");

这样,当 oldMethod 被调用时,编译器将发出一个警告,提示开发者应该使用 newMethod 替代 oldMethod。

相关推荐
DisonTangor6 小时前
苹果发布iOS 18.2首个公测版:Siri接入ChatGPT、iPhone 16拍照按钮有用了
ios·chatgpt·iphone
- 羊羊不超越 -6 小时前
App渠道来源追踪方案全面分析(iOS/Android/鸿蒙)
android·ios·harmonyos
2401_865854881 天前
iOS应用想要下载到手机上只能苹果签名吗?
后端·ios·iphone
HackerTom1 天前
iOS用rime且导入自制输入方案
ios·iphone·rime
良技漫谈1 天前
Rust移动开发:Rust在iOS端集成使用介绍
后端·程序人生·ios·rust·objective-c·swift
2401_852403551 天前
高效管理iPhone存储:苹果手机怎么删除相似照片
ios·智能手机·iphone
星际码仔2 天前
【动画图解】是怎样的方法,能被称作是 Flutter Widget 系统的核心?
android·flutter·ios
emperinter2 天前
WordCloudStudio:AI生成模版为您的文字云创意赋能 !
图像处理·人工智能·macos·ios·信息可视化·iphone
关键帧Keyframe2 天前
音视频面试题集锦第 8 期
ios·音视频开发·客户端
pb82 天前
引入最新fluwx2.5.4的时候报错
flutter·ios