You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Core Data执行Fetch Request时应用偶发崩溃求助(MKMapKit场景)

问题:MapView缩放时Core Data偶发EXC_BAD_ACCESS崩溃

在使用MKMapKit的mapView进行缩放操作时,应用偶发崩溃,触发点是Core Data执行Fetch Request时的EXC_BAD_ACCESS错误,且该错误并非每次都会出现。

相关代码

MapView代理方法

func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
    let userStatus = CoreDataUserLocationManagement().returnUsersLastKnownStatus()
    if (annotation is MKUserLocation) && userStatus != 3 {
        // 后续代码
    }
    // 其他逻辑
}

Core Data Fetch方法

func returnUsersLastKnownStatus() -> Int16 {
    let fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "UsersLocationDetails")
    do {
        if let fetchResults = try managedObjectContext.fetch(fetchRequest) as? [UsersLocationDetails] {
            if fetchResults.count != 0 {
                let managedObject = fetchResults[0]
                return managedObject.lastKnownStatus
            }
        }
    }
    catch {
        print("Catch error retrieving last known status to Core Data")
    }
    return 3
}

崩溃日志信息

Printing description of fetchRequest:
(NSFetchRequest) fetchRequest = 0x0000000283a5be20 {
  baseNSPersistentStoreRequest@0 = {
    baseNSObject@0 = {
      isa = NSFetchRequest
    }
    _affectedStores = 0x0000000000000000
  }
  _groupByProperties = 0x0000000000000000
  _havingPredicate = 0x0000000000000000
  _additionalPrivateIvars = 0x0000000281044100
  _valuesToFetch = 0x0000000000000000
  _entity = 0x0000000282684dc0 {
    baseNSObject@0 = {
      isa = NSEntityDescription
    }
    _cd_rc = 12
    _snapshotClass = 0x000000028208a760
    _versionHashModifier = 0x0000000000000000
    _versionHash = some {
      some = 0x0000000102faea90 {
        baseNSData@0 = {
          baseNSObject@0 = {
            isa = _PFEncodedData
          }
        }
        _aData = some {
          some = 0x00000002811c7e60 {
            baseNSData@0 = {
              baseNSObject@0 = {
                isa = _PFVMData
              }
            }
            _cd_rc = 1732
            _length = 40944
            _payload = 0x0000000102fac000
          }
        }
        _byteCount = 32
        _reserved = 0
      }
    }
    _model = some {
      some = 0x00000002832a9400 {
        baseNSObject@0 = {
          isa = NSManagedObjectModel
        }
        _dataForOptimization = some {
          some = 0x00000002811c7e60 {...}
        }
        _optimizationHints = 0x0000000102fb5850
        _additionalPrivateIvars = 0x00000002811c0120
        _entities = some {
          some = 0x000000028239f9f0 {
            baseNSKnownKeysDictionary1@0 = {
              baseNSKnownKeysDictionary@0 = {
                baseNSMutableDictionary@0 = {
                  NSDictionary = {
                    NSObject = {
                      isa = NSKnownKeysDictionary2
                    }
                  }
                }
              }
              _cd_rc = 0
              _count = 14
              _keySearch = some {
                some = 0x0000000102faeb10 {
                  baseNSKnownKeysMappingStrategy1@0 = {
                    baseNSKnownKeysMappingStrategy@0 = {
                      baseNSObject@0 = {
                        isa = NSKnownKeysMappingStrategy2
                      }
                    }
                    _cd_rc = -2128839072
                    _reserved64 = 2
                    _table = 0x0000000102faeb40
                    _length = 14
                    _reserved1 = 0x0000000000000000
                    _keys = 0x0000000102faec28
                  }
                }
              }
              _values = {}
            }
          }
        }
        _configurations = 0x00000002811c0100 1 key/value pair
        _fetchRequestTemplates = 0x0000000000000000
        _versionIdentifiers = 0x00000002811c0140 1 element
      }
    }
    _classNameForEntity = some {
      some = 0x0000000102fad388 {
        base_PFAbstractString@0 = {
          baseNSString@0 = {
            baseNSObject@0 = {
              isa = _PFEncodedString
            }
          }
        }
        _cd_rc = 0
        _length = 20
        _sourceData = some {
          some = 0x00000002811c7e60 {...}
        }
      }
    }
    _instanceClass = 0x00000002835b9b70
    _name = some {
      some = 0x0000000102fad388 {...}
    }
    _rootentity = 0x0000000282684dc0 {...}
    _superentity = 0x0000000000000000
    _subentities = 0x0000000000000000
    _properties = some {
      some = 0x00000002835b8720 {
        baseNSKnownKeysDictionary1@0 = {
          baseNSKnownKeysDictionary@0 = {
            baseNSMutableDictionary@0 = {
              NSDictionary = {
                NSObject = {
                  isa = NSKnownKeysDictionary2
                }
              }
            }
          }
          _cd_rc = 0
          _count = 9
          _keySearch = some {
            some = 0x0000000102fb0bf0 {
              baseNSKnownKeysMappingStrategy1@0 = {
                baseNSKnownKeysMappingStrategy@0 = {
                  baseNSObject@0 = {
                    isa = NSKnownKeysMappingStrategy2
                  }
                }
                _cd_rc = -2128839072
                _reserved64 = 2
                _table = 0x0000000102fb0c20
                _length = 9
                _reserved1 = 0x0000000000000000
                _keys = 0x0000000102fb0cd0
              }
            }
          }
          _values = {}
        }
      }
    }
    _propertyMapping = some {
      some = 0x0000000102fb0bf0 {...}
    }
    _userInfo = 0x0000000000000000
    _flattenedSubentities = 0x0000000281ff60d0 1 element
    _kvcPropertyAccessors = 0x00000002832a92c0
    _modelsReferenceIDForEntity = 13
  }
  _predicate = 0x0000000000000000
  _sortDescriptors = 0x0000000000000000
  _batchSize = 0
  _fetchLimit = 0
  _allocationSize = 0
  _relationshipKeyPathsForPrefetching = 0x0000000000000000
}

Xcode额外提示

(NSPersistentStoreRequest) CoreData.NSPersistentStoreRequest = <parent failed to evaluate: invalid load address>

问题分析与解决方案

核心原因

  1. 线程安全问题:MKMapKit的代理方法mapView(_:viewFor:)运行在主线程,但如果managedObjectContext是后台上下文,或者在非创建线程执行fetch操作,会导致Core Data对象访问野指针。
  2. 上下文生命周期问题:每次调用CoreDataUserLocationManagement()都会创建新实例,可能导致managedObjectContext被提前释放,后续访问时出现内存错误。
  3. Fetch请求优化不足:未设置fetchLimit,会一次性加载所有数据,增加内存压力和崩溃概率。

修复方案

1. 确保Core Data操作线程安全

如果使用的是私有上下文,将fetch操作放到后台线程执行,获取结果后回到主线程更新UI:

func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
    guard annotation is MKUserLocation else {
        // 处理其他annotation
        return nil
    }
    
    // 异步获取用户状态
    DispatchQueue.global(qos: .background).async {
        let userStatus = CoreDataUserLocationManagement.shared.returnUsersLastKnownStatus()
        DispatchQueue.main.async {
            if userStatus != 3 {
                // 更新用户位置annotation的UI
            }
        }
    }
    
    // 返回默认的annotationView,后续再更新
    return MKAnnotationView(annotation: annotation, reuseIdentifier: "UserLocation")
}

2. 单例管理Core Data操作类

将CoreDataUserLocationManagement改为单例,避免重复创建导致上下文生命周期问题:

class CoreDataUserLocationManagement {
    static let shared = CoreDataUserLocationManagement()
    private let managedObjectContext: NSManagedObjectContext
    
    private init() {
        // 初始化上下文,确保是线程安全的实例
        let persistentContainer = NSPersistentContainer(name: "YourDataModel")
        persistentContainer.loadPersistentStores { _, error in
            if let error = error {
                fatalError("Core Data load failed: \(error)")
            }
        }
        managedObjectContext = persistentContainer.viewContext // 使用主线程上下文,或私有上下文
    }
    
    func returnUsersLastKnownStatus() -> Int16 {
        let fetchRequest = NSFetchRequest<UsersLocationDetails>(entityName: "UsersLocationDetails")
        fetchRequest.fetchLimit = 1 // 只取第一条数据,优化性能
        do {
            let fetchResults = try managedObjectContext.fetch(fetchRequest)
            return fetchResults.first?.lastKnownStatus ?? 3
        } catch {
            print("获取最后已知状态失败: \(error)")
            return 3
        }
    }
}

3. 缓存用户状态

避免在mapView代理方法中频繁调用Core Data,缓存已获取的用户状态,减少数据库操作:

class YourMapViewController: UIViewController {
    private var cachedUserStatus: Int16 = 3
    
    override func viewDidLoad() {
        super.viewDidLoad()
        // 提前获取并缓存用户状态
        cachedUserStatus = CoreDataUserLocationManagement.shared.returnUsersLastKnownStatus()
        // 监听Core Data数据变化,更新缓存
        NotificationCenter.default.addObserver(self, selector: #selector(updateUserStatusCache), name: NSNotification.Name.NSManagedObjectContextObjectsDidChange, object: CoreDataUserLocationManagement.shared.managedObjectContext)
    }
    
    @objc private func updateUserStatusCache() {
        cachedUserStatus = CoreDataUserLocationManagement.shared.returnUsersLastKnownStatus()
    }
    
    func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
        if annotation is MKUserLocation && cachedUserStatus != 3 {
            // 使用缓存的状态处理UI
        }
        // 其他逻辑
    }
}

内容的提问来源于stack exchange,提问作者Chris

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.01 21:20:33