MST

星途 面试题库

面试题:Objective-C多线程环境下,如何处理循环引用与内存管理的复杂场景?

假设在一个复杂的多线程应用中,存在多个对象之间的循环引用,同时还涉及多线程对这些对象的频繁访问和内存操作。请详细阐述你会如何分析这种场景下可能出现的内存问题,并且给出全面的解决方案,包括但不限于使用弱引用(weak)、块(block)的正确使用方式以及手动打破循环引用的时机等,同时需要给出相应的代码框架来展示你的解决方案。
29.4万 热度难度
编程语言Objective-C

知识考点

AI 面试

面试题答案

一键面试

分析可能出现的内存问题

  1. 内存泄漏:循环引用会导致对象之间相互持有强引用,使得垃圾回收器无法回收这些对象,即使它们在程序逻辑上不再被需要,从而造成内存泄漏。
  2. 数据竞争:多线程频繁访问和操作这些对象时,不同线程可能同时读写同一对象的属性,导致数据不一致,引发未定义行为。

解决方案

  1. 使用弱引用(weak)
    • 弱引用不会增加对象的引用计数,当对象的强引用计数为0时,对象会被释放,而持有该对象的弱引用会自动被设置为nil。
    • 在循环引用的场景中,将其中一个方向的引用改为弱引用,以打破循环。例如,在Objective - C中:
@interface ClassA;
@interface ClassB {
    // 使用弱引用打破循环
    __weak ClassA *a; 
}
@end

@interface ClassA {
    ClassB *b;
}
@end
  1. 块(block)的正确使用方式
    • 在block中使用对象时,要注意避免循环引用。在Objective - C中,可以使用__weak修饰符来解决。例如:
__weak typeof(self) weakSelf = self;
self.block = ^{
    __strong typeof(weakSelf) strongSelf = weakSelf;
    if (strongSelf) {
        // 访问strongSelf的属性和方法
        [strongSelf doSomething];
    }
};

这里先将self__weak修饰,在block内部再用__strong修饰,这样可以保证在block执行期间,self不会被释放,同时又避免了循环引用。 3. 手动打破循环引用的时机

  • 在对象生命周期结束前,手动打破循环引用。例如,在对象的dealloc方法中,将循环引用的对象设置为nil。
@implementation ClassA
- (void)dealloc {
    self.b = nil;
}
@end

@implementation ClassB
- (void)dealloc {
    self.a = nil;
}
@end
  1. 多线程数据竞争问题的解决
    • 使用锁:可以使用互斥锁(pthread_mutex_t在C语言中,@synchronized在Objective - C中)来保证同一时间只有一个线程可以访问共享对象。例如,在Objective - C中:
- (void)accessSharedObject {
    @synchronized(self) {
        // 访问和操作共享对象的代码
        self.sharedObject.property = newValue;
    }
}
  • 使用队列:将对共享对象的操作放在一个串行队列中执行,以避免数据竞争。例如,在Objective - C中使用Grand Central Dispatch(GCD):
dispatch_queue_t queue = dispatch_queue_create("com.example.serialQueue", DISPATCH_QUEUE_SERIAL);
dispatch_async(queue, ^{
    // 访问和操作共享对象的代码
    self.sharedObject.property = newValue;
});

代码框架展示

以下是一个简单的Objective - C代码框架,展示了如何综合应用上述方法:

#import <Foundation/Foundation.h>

@interface ClassB;

@interface ClassA : NSObject
@property (nonatomic, strong) ClassB *b;
- (void)doSomething;
@end

@interface ClassB : NSObject
@property (nonatomic, weak) ClassA *a;
- (void)doAnotherThing;
@end

@implementation ClassA
- (void)doSomething {
    NSLog(@"ClassA doSomething");
}
- (void)dealloc {
    NSLog(@"ClassA dealloc");
    self.b = nil;
}
@end

@implementation ClassB
- (void)doAnotherThing {
    NSLog(@"ClassB doAnotherThing");
}
- (void)dealloc {
    NSLog(@"ClassB dealloc");
    self.a = nil;
}
@end

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        ClassA *a = [[ClassA alloc] init];
        ClassB *b = [[ClassB alloc] init];
        a.b = b;
        b.a = a;

        // 使用块
        __weak typeof(a) weakA = a;
        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
            __strong typeof(weakA) strongA = weakA;
            if (strongA) {
                [strongA doSomething];
            }
        });

        // 模拟多线程访问
        dispatch_queue_t queue = dispatch_queue_create("com.example.serialQueue", DISPATCH_QUEUE_SERIAL);
        dispatch_async(queue, ^{
            @synchronized(a) {
                // 访问和操作a的属性
                // 这里可以添加具体的属性访问和修改代码
            }
        });

        // 释放对象
        a = nil;
        b = nil;
    }
    return 0;
}

在这个代码框架中,通过__weak修饰符打破了ClassAClassB之间的循环引用,使用块时避免了循环引用,通过@synchronized和串行队列处理了多线程访问问题。同时在dealloc方法中手动打破循环引用以确保对象能正确释放。