面试题答案
一键面试__weak 和 __strong 在多线程环境下的语义影响
- __strong:
- 在多线程环境中,__strong 修饰符会保持对象的强引用。这意味着只要有 __strong 引用指向对象,对象就不会被释放。然而,多个线程同时对一个对象进行 __strong 操作(如赋值、访问等)时,如果没有适当的同步机制,可能会导致数据竞争问题。例如,一个线程正在释放对象(使其引用计数降为 0),而另一个线程可能同时尝试通过 __strong 引用访问该对象,这会导致野指针访问,进而程序崩溃。
- __weak:
- __weak 修饰符创建的是弱引用,不会增加对象的引用计数。在多线程环境下,由于对象的释放可能在任意时刻发生(当强引用计数降为 0 时),__weak 指针可能会在某个线程中突然变为 nil。如果多个线程同时操作 __weak 指针,可能会出现一个线程刚检查 __weak 指针不为 nil,准备使用它时,另一个线程已经使对象释放,__weak 指针变为 nil,从而导致程序出现逻辑错误。
避免因不当使用导致内存问题和程序崩溃的方法
- 使用同步机制:
- 可以使用锁(如
@synchronized
块、NSLock
、dispatch_semaphore
等)来保护对使用 __weak 和 __strong 修饰的对象的访问。例如,当多个线程可能同时访问和修改一个由 __strong 修饰的对象时,使用@synchronized
块来确保同一时间只有一个线程可以操作该对象。 - 对于 __weak 指针,在使用前同样要进行同步检查,确保在检查和使用之间对象不会被释放。
- 可以使用锁(如
- 原子操作:
- 使用原子属性(默认情况下,对象属性是原子的)。虽然原子属性不能完全解决多线程问题,但它可以保证属性的访问是原子性的,减少数据竞争的可能性。例如,对于一个由 __strong 修饰的属性,原子操作可以确保在多线程环境下对该属性的赋值和读取是完整的操作,不会被其他线程打断。
多线程环境下正确使用的示例代码结构
以下是使用 GCD(Grand Central Dispatch)和 __weak、__strong 修饰符的示例代码:
#import <UIKit/UIKit.h>
@interface MyObject : NSObject
@property (nonatomic, strong) NSString *data;
@end
@implementation MyObject
@end
@interface ViewController : UIViewController
@property (nonatomic, strong) MyObject *myObject;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.myObject = [[MyObject alloc] init];
self.myObject.data = @"Initial data";
__weak typeof(self) weakSelf = self;
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(queue, ^{
__strong typeof(self) strongSelf = weakSelf;
if (strongSelf) {
// 使用 strongSelf 来确保在块执行期间 self 不会被释放
MyObject *object = strongSelf.myObject;
@synchronized (object) {
// 对 object 的操作进行同步
NSLog(@"Data from background thread: %@", object.data);
object.data = @"Data updated in background";
}
}
});
}
@end
在上述代码中:
- 首先通过
__weak typeof(self) weakSelf = self;
创建了一个弱引用,以避免在 block 中出现循环引用。 - 在 block 内部,使用
__strong typeof(self) strongSelf = weakSelf;
获取一个强引用,确保在 block 执行期间self
不会被释放。 - 对于访问和修改
myObject
,使用@synchronized
块来同步操作,避免多线程环境下的数据竞争问题。