[iOS] Detailed explanation of commonly used modifiers

1. Memory management modifiers

These modifiers are mainly used to manage the object's memory, including reference counting and life cycle.

1.strong

strong is a reference counting modifier for an object's properties, which is the default property modifier. When you assign an object to a strong property, the reference count of the object will be increased by 1, which means that you have ownership of the object and it will not be released by the system as long as you keep a reference to it.

This is useful for objects that you need to hold long-term, such as properties that represent some state, or subviews and delegates. However, if used improperly, it may cause circular references and cause memory leaks. For example, if two objects hold strong references to each other, neither object will be released because their reference counts will never drop to 0.
 

Example:

@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

In this example, the name attribute is modified by strong, so when you assign a string to the name attribute, the reference count of the string is increased by 1. In turn, when the MyObject instance is released, the name attribute is also released. , which causes the reference count of the string assigned to the name attribute to be reduced by one. If the string's reference count becomes 0, it will also be released by the system.

2.weak

weak is a reference counting modifier. When you assign an object to a weak property, it does not affect the object's reference count. In other words, you do not take ownership of the object, so you cannot prevent the object from being released.

The main purpose of weak is to prevent circular references. For example, when you have two objects referencing each other, you can make the reference from one object to the other weak so that no circular reference is formed because a weak reference does not increment the object's reference count.

When the weakly referenced object is released, all weak references pointing to the object will automatically be set to nil, which can prevent the occurrence of wild pointers (pointers pointing to freed memory).
 

Example:

@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

In this example, the MyParentObject class has a children property that stores all of its child objects. Each MyChildObject instance has a parent property that refers to its parent object. The parent property is weak, so even if the child object references the parent object, the parent object's reference count will not be incremented. This prevents circular references from forming between parent and child objects.

When a MyParentObject instance is released, its children property is also released, which causes the reference count of all child objects to be reduced by one. If a child object's reference count becomes 0, it is released and the parent property is automatically set to nil.

3.unsafe_unretained

unsafe_unretained is a reference counting modifier. Similar to the weak modifier, the unsafe_unretained modified property or variable does not increase the reference count of the referenced object when assigned, that is, it does not obtain ownership of the object, so it cannot prevent the object from being released.

However, unlike weak, when the object referenced by unsafe_unretained is released, the value of the unsafe_unretained variable referencing the object will not be automatically set to nil, but will continue to point to the released memory space. If you access the variable at this time, it will cause undefined behavior, usually causing the program to crash. That's why it's called "unsafe".
 

Example:

@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

In this example, if you access the self.object property after the someMethod method is executed, it will cause the program to crash because the memory space pointed to by self.object has been released.

Therefore, unless you are completely sure of the lifetime of the object you are referencing, you should try to avoid using the unsafe_unretained modifier and use weak or other modifiers instead.

4.copy

copy is a property modifier that has the function of creating a new copy of an object. When you assign an object to a copy property, you are actually assigning a new copy of the object, not the original object. This is very useful for some mutable types of objects (such as NSMutableString, NSMutableArray, etc.) to prevent changes in the original object from affecting the copied object.

The copy attribute is usually used for immutable objects such as NSString, NSArray and NSDictionary to ensure the value semantics of the object and avoid unpredictable results caused by changes in the object itself.
 

Example:

@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

In this example, the name property of MyClass is modified with copy. When you assign the NSMutableString object name to myObject.name, what is actually assigned is a new copy of name. So when you change the value of name, the value of myObject.name does not change.

5.__weak

Similar to the weak modifier, but used on local variables or instance variables.

__weak is a reference counting modifier that works in an ARC (Automatic Reference Counting) environment. When you assign an object to a __weak variable, the object's reference count is not incremented. This means that you do not take ownership of the object, so you cannot prevent the object from being released.

The main purpose of __weak is to prevent circular references. For example, when you have two objects referencing each other, you can make the reference from one object to the other object __weak so that no circular reference is created because a __weak reference does not increase the object's reference count.

Another important feature is that when the object referenced by __weak is released, all __weak references pointing to the object are automatically set to nil, which prevents the occurrence of wild pointers (pointers pointing to freed memory).
 

Example:

@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

In this example, the MyParentObject class has a children property that stores all of its child objects. Each MyChildObject instance has a parent property that refers to its parent object. The parent property is weak, so even if the child object references the parent object, the parent object's reference count will not be incremented. This prevents circular references from forming between parent and child objects.

When a MyParentObject instance is released, its children property is also released, which causes the reference count of all child objects to be reduced by one. If a child object's reference count becomes 0, it is released and the parent property is automatically set to nil.

6.__strong

Similar to the strong modifier, but used on local variables or instance variables.

__strong is the default ownership modifier, and in an ARC (Automatic Reference Counting) environment, you don't need to write it out explicitly. When you assign an object to a __strong variable or property, the object's reference count is incremented. This means that you gain ownership of the object and can prevent the object from being released.

Although you can use the __strong modifier explicitly, in most cases you don't need to. Because in the ARC environment, if you do not specify other ownership modifiers (such as __weak, __unsafe_unretained, __autoreleasing), then the variable or attribute will be modified by __strong by default.
 

Example:

In Objective-C, we sometimes use __strong inside a block to prevent a __weak reference from being released prematurely.

@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

In this example, we first create a __weak reference weakSelf pointing to self. Then we create a __strong reference to strongSelf in an async block and assign the value of weakSelf to strongSelf. Since strongSelf is __strong , self will not be released as long as the block is executing. We can then safely use strongSelf to access and modify self's properties.

Note that this pattern is typically used to prevent blocks from creating circular references and to prevent self from being freed during the execution of the block. If you don't use a __strong reference, then self may be freed during the execution of the block, which may cause undefined behavior or program crash.

7.__unsafe_unretained

Similar to the unsafe_unretained modifier, but used on local variables or instance variables.

__unsafe_unretained is an ownership modifier that works in an ARC (Automatic Reference Counting) environment. When you assign an object to an __unsafe_unretained variable, the object's reference count is not incremented. This means that you do not take ownership of the object, so you cannot prevent the object from being released.

Unlike the __weak modifier, when the object referenced by __unsafe_unretained is released, all __unsafe_unretained references pointing to the object are not automatically set to nil. This can lead to the appearance of wild pointers (pointers to memory that have been freed), causing the program to crash.
 

Example:

@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

In this example, the name property of MyObject is modified with unsafe_unretained. In the @autoreleasepool block, we create an NSString object and assign it to myObject.name. However, when the @autoreleasepool block ends, the NSString object is released, so myObject.name becomes a wild pointer. In the subsequent NSLog statement, we try to access this object that has been released, which may cause the program to crash.

In general, we should try to avoid using the __unsafe_unretained modifier unless we are sure that the object will exist throughout its lifetime. In most cases, we should use the __weak or __strong modifier.

8.__autoreleasing

__autoreleasing is an ownership modifier mainly used to handle error output parameters of methods or functions. When you assign an object to an __autoreleasing variable, the object's reference count is not incremented, but the object is released when the current autorelease pool is emptied. This means that you do not gain ownership of the object, but you can safely use the object for the lifetime of the current autorelease pool.
 

Example:

- (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);
    }
}

In this example, the trySomethingWithError: method accepts a pointer to an NSError object as a parameter. The pointer is decorated with __autoreleasing, which means that we can assign a new NSError object to it without increasing the reference count of the NSError object. Then in the method method, we create an NSError object and pass its address to trySomethingWithError:. If trySomethingWithError: returns NO, then we can print out the error message.

The most common use case for this pattern is handling error propagation in Cocoa and Cocoa Touch. In these environments, if a method can error, it will usually have an extra parameter, which is a pointer to a pointer to an NSError object, as in the example above.

9.assign

assign Is a property modifier, which is used to generate setter methods for setting basic data types (such as int, float, double, NSInteger, etc.) or C data types (such as struct, enum, etc.). When you use  assign modifiers, the setter method performs a simple assignment operation.

It should be noted that for Objective-C objects, using  assign modifiers may cause some problems. If you  assign decorate an Objective-C object, and the object is released elsewhere while it is referenced, then when you access the property again, the program will crash because you are accessing an object that has already been released. This is called a "dangling pointer". Therefore, for Objective-C objects, you should use  the strong or  weak modifier instead  assign.

Example:

@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;
}

In this example, myInteger the properties use  assign modifiers. In  someMethod the method, we simply assign  myInteger a value and print its value.

10.retain

retain It is an attribute modifier mainly used for memory management. In an MRC (manual reference counting) environment, when you use  retain to decorate a property, the setter method will retain (increase the reference count) the new object while releasing the old object.

In an ARC (Automatic Reference Counting) environment you should use  strong instead  retain. strong The semantics are  retain the same as , but under ARC management, the compiler automatically adds the necessary retain and release calls.


Example:

// 注意:这个例子使用了 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;
}

In this example, myString the properties use  retain modifiers. In  someMethod the method, we assign  myString a value and print its value. Also note that within  dealloc the method we need to release manually  _myStringto prevent memory leaks. In  main the function, we create an  MyClass object, call it  someMethod, and release the object at the end.

This example shows  retain basic usage, but modern Objective-C development often uses ARC, so in practice you may not see it very often  retain.

2. Attribute access modifiers

These modifiers are used to control how a property is accessed, including access permissions and the name of the access method.

1.readonly

Indicates that this property only has a getter method and no setter method.

readonly is a property modifier. When you declare a property as readonly, you can only set the value of the property in the initialization method of the class's implementation file (.m file) or the init method of the class. Elsewhere, you can only read the property's value, not modify it.

The main purpose of readonly is to create a property that can only be modified from within the class, thereby encapsulating the internal implementation of the class and preventing users of the class from directly accessing and modifying the internal state of the class.
 

Example:

// 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

In this example, we added a class extension to the MyObject.m file and redeclared the name attribute to readwrite in the class extension. In this way, we can modify the value of the name attribute in the implementation of the class. Then we define a changeName: method to modify the value of the name attribute. From the perspective of users of the class, the name attribute is still readonly. They cannot directly modify the value of the name attribute, but can only modify the value of name through the changeName: method.

2.readwrite

readwrite is a property modifier, it is the default value of the property modifier. When you declare a property as readwrite, the compiler generates a public getter method and a public setter method for the property. You can use these two methods to read or modify the value of this property.
 

Example:

// 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

In this example, the name property of MyObject is declared as readwrite, so we can freely read and modify the value of the name property.

Note that readwrite is the default value of the attribute modifier, so in actual programming, if a property is readwrite, we usually omit the readwrite modifier. For example, the code above can be simplified to:

// MyObject.h
@interface MyObject : NSObject

@property (nonatomic) NSString *name;

@end

3.getter

getter is a property modifier used to specify the name of the method to obtain the property value. This is particularly useful when changing the getter method name of a Boolean value, for example, changing the getter method name of isFinished from finished to isFinished.
 

Example:

// 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

In this example, the finished property of MyObject is declared as getter=isFinished, so we use the isFinished method to get the value of the finished property instead of using the finished method.

Note that the getter modifier only changes the name of the getter method, not the setter method. In the above example, we still use the setter method of the finished property to set the value of the finished property instead of using the isFinished method.

4.setter

The setter is a property modifier that allows you to customize the name of the property's setter method. By default, the name of the setter method for a property name is setName:, but you can change this name if you use the setter modifier.
 

Example:

// 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

In this example, the name property of MyObject is declared as setter=setMyName:, so we use the setMyName: method to set the value of the name property instead of using the setName: method.

Note that the setter modifier only changes the name of the setter method, not the getter method. In the above example, we still use the getter method of the name attribute to get the value of the name attribute instead of using the getMyName method.

5.dynamic

dynamic is a property modifier that tells the compiler that the property's getter and setter methods will be provided dynamically at runtime rather than generated at compile time. Typically, this is used in conjunction with the dynamic nature of Objective-C  @synthesize.

When you declare a property as  dynamic, you must provide the property's getter and setter methods (for readwrite properties), or only the getter method (for readonly properties). If you do not provide these methods, the program will crash when trying to access these methods at runtime.


Example:

#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;
}

In this example, myString the properties use  dynamic modifiers. In the implementation of MyClass, we provide  myString getter and setter methods. In  main the function, we create a MyClass object, get  myString the value, and try to set a new value.

Note that even though we try to set  myString the value, since our setter implementation doesn't actually change  myString the value,  myString when we get the value again, it's still the original value.

@synthesize指令

@synthesize Not a property modifier, but a directive that tells the compiler to automatically create property-related instance variables and their getter and setter methods. This directive is usually used in the implementation file of a class. In earlier versions of Objective-C, if you declared a property, you needed to manually add  @synthesize directives to the class's implementation. But starting with Objective-C 2.0, if you don't provide  @synthesize a directive, the compiler will automatically add it to your properties.
 

Example:

#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;
}

In this case, myString it is a property, and we use  @synthesize directives in the implementation of MyClass to automatically generate instance variables  _myString and their getter and setter methods. In  someMethod the method, we set  myString the value and print it. In  main the function, we create a MyClass object and call  someMethod the method.

Note that starting with Objective-C 2.0, if you don't provide  @synthesize a directive, the compiler will automatically add it for your properties, so in modern Objective-C code, you may not see  @synthesize the directive very often.

3. Thread safety modifier

These modifiers are used to control the thread safety of properties.

1.nonatomic

Indicates that access to this property is not thread-safe. This is to improve performance.

nonatomic is a property modifier that specifies that access to the property does not require atomic operations. Relative to atomic (the default value), nonatomic can improve the performance of property access, but it is not thread-safe. If your properties may be accessed from multiple threads at the same time, you should consider using atomic to ensure thread safety, or implement the synchronization mechanism yourself.
 

Example:

// 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

In this example, the name property of MyObject is declared nonatomic, so we can freely read and modify the value of the name property without worrying about performance issues.

Note that in actual programming, the nonatomic modifier is usually used together with other modifiers such as strong, weak, assign, copy, etc., such as @property (nonatomic, strong) NSString *name;.

2.atomic

Indicates that access to this property is thread-safe.

Atomic is a property modifier that specifies that access to the property requires atomic operations. This means that both getter and setter methods use locks to ensure thread safety and will not be interrupted by other threads during read and write operations. atomic is the default value for property modifiers.

Although atomic provides thread safety, it also has some performance overhead. If your properties are unlikely to be accessed in a multi-threaded environment, or you have other synchronization mechanisms in place, you can use the nonatomic modifier to improve performance.
 

Example:

@interface MyObject : NSObject

@property (atomic) NSString *name;
//@property NSString *name; // atomic 是属性修饰符的默认值,可简写为这样

@end

In this example, the name property of MyObject is declared atomic, so we can safely read and modify the value of the name property in a multi-threaded environment.
 

Notice:

Although the atomic attribute provides some degree of thread safety, it is not absolute. This is because atomic only guarantees the atomicity of getter and setter methods, but in actual use, we may need to perform more complex operations, which may not be completed through a single getter or setter method.

For example, suppose we have an atomic property count and we want to increment the value of count by 1. We might write the following code:

myObject.count = myObject.count + 1;

It looks like there is nothing wrong with this code, but in fact, it is not thread-safe. Because this code actually performs two operations: a getter method to obtain the value of count, and a setter method to set the new value of count. While both methods are individually atomic, the combination of the two is not. If between these two operations, another thread also modifies the value of count, then our code will have problems.

To solve this problem, we need to implement the synchronization mechanism ourselves to ensure the atomicity of the entire operation. In Objective-C, we can use locks to achieve this. Here is an example using 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

In this example, we add an NSLock object lock to MyObject and use this lock in the incrementCount method to ensure the atomicity of the count increment operation.

4. Other attribute modifiers

1.__kindof

__kindof is a type modifier used to represent "an instance of a certain type, or an instance of its subclass" in a generic type. __kindof was introduced in Xcode 7 and iOS 9 to improve the type safety of Objective-C generics.
 

Example:

// 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

In this example, Kennel is a generic class, and its generic parameter ObjectType is declared as __kindof Animal *, so ObjectType can be Animal * or any subclass of Animal. In the application:didFinishLaunchingWithOptions: method of AppDelegate, we create a Dog object and a Kennel object, and then obtain the resident of the Kennel object through the resident property, and the result is a Dog object.

If we did not use the __kindof modifier, the type of resident would be inferred as Animal * instead of Dog *. We can assign Dog * objects to Animal * variables, but we cannot assign Animal * objects to Dog * variables unless explicit type conversion is performed. The __kindof modifier eliminates this problem, making the code more concise and type-safe.

5. Nullability modifier

These modifiers are used to indicate whether a parameter, return value, or property can be nil.

1.nullable

nullable is a type modifier that indicates that a value can be nil. The nullable modifier is mainly used to improve type safety, because it helps the compiler and developers know which values ​​may be nil, thus avoiding accidental access to nil values. The nullable modifier is typically used on method return values ​​and parameters, as well as on properties.
 

Example:

// 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

In this example, both the name property of MyObject and the parameters of the setName: method are declared nullable, so we can use nil values.

If we did not use the nullable modifier, the compiler might warn us that nil values ​​might cause problems. The nullable modifier eliminates this problem and allows us to explicitly indicate that certain values ​​can be nil.

2.nonnull

nonnull is a type modifier that specifies that a value should not be nil. The nonnull modifier is mainly used to improve type safety because it allows the compiler and developers to know which values ​​should not be nil, thus avoiding unexpected nil values ​​at runtime.
 

Example:

// 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

In this example, the name property of MyObject and the parameters of the setName: method are declared as nonnull, so we cannot use nil values.

If we try to use a nil value, the compiler will give a warning because nil values ​​may cause problems. The nonnull modifier eliminates this problem and allows us to explicitly indicate that certain values ​​should not be nil.

3.null_unspecified

null_unspecified is a type modifier that specifies that a value may or may not be nil. It was introduced to help Objective-C code interact with Swift code, and its purpose is to provide a transition mechanism so that Objective-C code can more easily interact with Swift's strong type system.

The null_unspecified modifier actually means "the developer did not specify whether this value can be nil". This means that if you see the null_unspecified modifier, you should assume that the value may be nil and check before using the value.
 

Example:

// 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

In this example, the name attribute of MyObject and the parameters of the setName: method are declared as null_unspecified, so we can use nil values.

You should try to avoid using the null_unspecified modifier in new Objective-C code, and instead explicitly specify whether a value can be nil, using the nullable or nonnull modifier. The null_unspecified modifier is mainly used to help old Objective-C code interact with Swift code.

4.null_resettable

null_resettable is a nullability modifier that applies to the return value of a property or method, indicating that the return value of the property or method should not be nil under normal circumstances, but its setter method can accept nil parameters. After accepting nil, the getter method returns a default value instead of nil.

This is useful in certain situations, such as you might have a property whose default value is non-nil, but you want to allow the user to set it to nil to restore the default value.
 

Example:

// 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

In this example, the name property of MyObject is declared as null_resettable, so we can set it with a nil value, but when we get its value, if its value is nil, a default value of "Default" will be returned.

The usage scenarios of the null_resettable modifier are not common, but in some specific cases, for example, if you have a property whose default value is not nil, and you want to return a default value when set to nil, then the null_resettable modifier will will be very useful.

6. Variable modifiers

These modifiers are used to modify local variables or instance variables.

1.__block

__block is a variable modifier, mainly used to capture and modify the value of local variables in a block. By default, a block captures and retains the current state of the local variables it uses, which means that even if the value of a variable is changed outside the block, the change will not be reflected inside the block. However, if you declare a variable using the __block modifier, then the variable can be modified inside the block.
 

Example:

@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

In this example, we declare a __block variable myVariable and modify its value in a block. Then we print out the value of myVariable outside the block, and we can see that the value of myVariable has been modified by the block.

If we do not use the __block modifier, then the value of myVariable cannot be modified inside the block, and trying to do so will result in a compiler error.

Note that when using the __block modifier, you need to pay attention to memory management issues, because the life cycle of __block variables may exceed their original scope. In an ARC (Automatic Reference Counting) environment, the __block variable will be automatically retained when the block is copied to the heap, and will be released when the block is destroyed.

2.__unused

Used on a variable, it indicates that the variable may not be used, and the compiler should not generate a warning about unused variables.

__unused is a variable modifier used to tell the compiler that a specific variable may not be used. This is mainly used to prevent the compiler from issuing warnings about unused variables. This is useful in certain situations, such as if you have a function or method parameter that may not be used in some implementations, but may be used in others.
 

Example:

@implementation AppDelegate

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    int __unused myVariable = 5;
    
    // 其他代码...
    
    return YES;
}

@end

In this example, we declare a variable myVariable and use the __unused modifier to tell the compiler that the variable may not be used. Even if we don't use myVariable in the code behind, the compiler will not issue an unused variable warning.

Note that although the __unused modifier can prevent unused variable warnings, you should avoid using it unnecessarily. Warnings about unused variables are often useful because they may indicate a problem with the code, such as some code being removed by mistake or forgotten. You should use the __unused modifier only when you are sure that a variable may not be used, but you still need to declare it.

7. Function and method modifiers

These modifiers are used to add special properties of a function or method, including deprecation information and platform availability.

1.attribute

attribute is a modifier used to provide the compiler with additional information about the behavior of the code. They can be applied to functions, variables, parameters, etc. to change their behavior or provide additional information at compile time.

Here are some common attribute modifiers:

  • __attribute__((deprecated)): Marks a function, method or class as deprecated. The compiler will issue a warning when they are used.

  • __attribute__((unused)): Mark a variable as potentially unused to prevent the compiler from issuing warnings about unused variables.

  • __attribute__((nonnull)): Indicates that one or all parameters of a function or method cannot be nil.

  • __attribute__((visibility("default"))) and  __attribute__((visibility("hidden"))): These two modifiers are used to control the visibility of symbols (such as functions), equivalent to public and private in other programming languages.

Example:

@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

In this example, MyClass's myMethod method is marked as deprecated, so the compiler will issue a warning when using it in AppDelegate's application:didFinishLaunchingWithOptions: method.

Using attribute modifiers can help you better control the behavior of your code and provide more compile-time information, making your code safer and easier to maintain.

2.__available

In Objective-C and Swift, we use API_AVAILABLE (in Objective-C) or @available (in Swift) to specify in which version of the operating system a specific API is available. This is useful for maintaining backward compatibility because you can check the operating system version in your code and decide whether to call a specific API.

By using API_AVAILABLE or @available, you can ensure that your app still works properly on older versions of the operating system, while taking advantage of new API features on newer versions of the operating system.
 

Example:

In 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

In this example, @available is used in MyViewController's useNewAPI method to check the operating system version and then decide which API to call.
 

In 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
        }
    }

}

In this example, #available is used in MyViewController's useNewAPI method to check the operating system version and then decide which API to call.

3.__deprecated

__deprecated is a variable, function or method modifier used to mark them as deprecated. When an element marked with __deprecated is used, the compiler will issue a warning to remind the developer that the element is deprecated.

This is an important feature because it helps developers find and replace obsolete code in a timely manner to avoid problems in future versions.
 

Example:

@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

In this example, MyClass's oldMethod method is marked as obsolete. Therefore, the compiler will issue a warning when using it in the application:didFinishLaunchingWithOptions: method of AppDelegate.

You can also use __deprecated_msg() to provide a specific message indicating what alternative method or function should be used. For example:

- (void)oldMethod __deprecated_msg("Use newMethod instead");

In this way, when oldMethod is called, the compiler will issue a warning, prompting the developer to use newMethod instead of oldMethod.

Guess you like

Origin blog.csdn.net/u012881779/article/details/134759885