面试题答案
一键面试内存管理策略调整
- 自动释放池优化:
- 在频繁创建对象的代码块中,手动创建自动释放池。例如:
@autoreleasepool { for (int i = 0; i < 1000; i++) { NSString *str = [[NSString alloc] initWithFormat:@"%d", i]; // 对str进行操作 } }
- 这样可以及时释放临时对象,减少内存峰值。
- 对象复用:
- 对于频繁创建和销毁的对象,使用对象池进行复用。比如,在网络请求中频繁创建的
NSURLRequest
对象,可以建立一个对象池。 - 创建一个类来管理对象池,例如:
@interface RequestPool : NSObject - (NSURLRequest *)getRequest; - (void)returnRequest:(NSURLRequest *)request; @end @implementation RequestPool { NSMutableArray<NSURLRequest *> *pool; } - (instancetype)init { self = [super init]; if (self) { pool = [NSMutableArray array]; } return self; } - (NSURLRequest *)getRequest { if (pool.count > 0) { return [pool lastObject]; } return [[NSURLRequest alloc] init]; } - (void)returnRequest:(NSURLRequest *)request { [pool addObject:request]; } @end
- 对于频繁创建和销毁的对象,使用对象池进行复用。比如,在网络请求中频繁创建的
使用更高效的数据结构
- 线程安全的集合类:
- 当多线程访问集合类时,使用
NSMutableDictionary
的线程安全替代品,如NSHashTable
(适用于对象存储)或NSMapTable
(适用于键值对存储)。这些类在多线程环境下性能更好。 - 例如,在多线程环境中存储用户信息:
NSMapTable *userInfoTable = [NSMapTable strongToStrongObjectsMapTable]; // 线程1 [userInfoTable setObject:user1 forKey:@"user1"]; // 线程2 id user = [userInfoTable objectForKey:@"user1"];
- 当多线程访问集合类时,使用
- 队列结构:
- 对于任务队列,使用
dispatch_queue_t
(GCD队列)。如果需要顺序执行任务,可以使用串行队列;如果可以并发执行,则使用并发队列。 - 例如,创建一个串行队列处理特定任务:
dispatch_queue_t serialQueue = dispatch_queue_create("com.example.serialQueue", DISPATCH_QUEUE_SERIAL); dispatch_async(serialQueue, ^{ // 执行任务 });
- 对于任务队列,使用
优化锁机制
- 读写锁:
- 如果线程之间主要是读多写少的操作,可以使用读写锁。在Objective - C中,可以使用
pthread_rwlock_t
。 - 示例代码:
pthread_rwlock_t rwlock; pthread_rwlock_init(&rwlock, NULL); // 读操作 pthread_rwlock_rdlock(&rwlock); // 执行读操作 pthread_rwlock_unlock(&rwlock); // 写操作 pthread_rwlock_wrlock(&rwlock); // 执行写操作 pthread_rwlock_unlock(&rwlock); pthread_rwlock_destroy(&rwlock);
- 如果线程之间主要是读多写少的操作,可以使用读写锁。在Objective - C中,可以使用
- 信号量:
- 使用信号量(
dispatch_semaphore_t
)来控制对共享资源的访问。例如,控制同时访问某个资源的线程数量:
dispatch_semaphore_t semaphore = dispatch_semaphore_create(3); // 允许最多3个线程同时访问 dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER); // 访问共享资源 dispatch_semaphore_signal(semaphore); });
- 使用信号量(
方案的可扩展性和潜在风险分析
- 自动释放池优化:
- 可扩展性:在对象创建频繁的场景下,这种方式可扩展性较好,因为可以在不同层次的代码块创建自动释放池,灵活控制内存释放时机。随着项目规模扩大,只要合理安排自动释放池的位置,对性能提升依然有效。
- 潜在风险:如果自动释放池嵌套过深,可能会导致部分对象释放延迟,影响性能。另外,如果在自动释放池中执行大量耗时操作,可能会导致其他线程等待,影响整体并发性能。
- 对象复用:
- 可扩展性:对于复用对象类型固定且复用场景明确的项目,可扩展性良好。随着对象创建频率增加,复用带来的性能提升更显著。但如果项目需求变化,对象类型或复用规则改变,可能需要较大的代码调整。
- 潜在风险:对象复用可能引入对象状态残留问题,如果在复用对象前没有正确重置对象状态,可能导致逻辑错误。另外,对象池的大小需要合理设置,过大可能浪费内存,过小则达不到复用效果。
- 线程安全的集合类:
- 可扩展性:这些类设计上就考虑了多线程场景,在多线程并发访问频繁的情况下可扩展性强。随着线程数量增加,相比普通集合类,性能优势更明显。
- 潜在风险:虽然线程安全,但不同的线程安全集合类有不同的适用场景,如果选择不当,可能达不到预期的性能优化效果。例如,
NSHashTable
和NSMapTable
在存储对象关系上有不同的特点,选错可能导致数据结构混乱。
- 队列结构:
- 可扩展性:GCD队列具有很好的可扩展性,无论是单核还是多核设备,都能根据系统资源动态调整任务执行。随着项目中任务数量和复杂度增加,通过合理划分队列类型(串行/并发),可以有效管理任务执行顺序和并发度。
- 潜在风险:如果对队列的优先级设置不合理,可能导致重要任务被延迟执行。另外,如果在串行队列中执行耗时过长的任务,会阻塞后续任务的执行,影响整体性能。
- 读写锁:
- 可扩展性:适用于读多写少的场景,在这种场景下可扩展性较好。随着读操作并发量增加,读写锁能有效提高性能。但如果写操作频率增加,读写锁的性能优势会逐渐降低。
- 潜在风险:如果写操作时间过长,会导致读操作长时间等待,影响系统响应性。而且读写锁的使用需要精确控制读和写操作的边界,否则可能出现数据不一致问题。
- 信号量:
- 可扩展性:信号量在控制资源访问数量方面具有较好的可扩展性。可以根据资源的实际承载能力动态调整信号量的值,适应不同的并发场景。
- 潜在风险:如果信号量的值设置不合理,可能导致资源浪费或过度竞争。例如,信号量值设置过大,可能使资源过载;设置过小,会限制并发性能。同时,信号量的使用也需要小心处理等待和信号发送的逻辑,否则可能出现死锁问题。