Swift开发iOS App集成SignalR出现内存泄漏致崩溃问题
问题场景
在Swift开发的iOS应用中使用signalr实现数值实时更新功能,SignalRService类实现代码如下:
public class SignalRService { private var connection: HubConnection private let intIndesiesArgument : String = "argument1,argument2,..." private let commoditiesArgument : String = "argument1,argument2,..." init(url: URL,delegate : SignalRDataUpdateDelegate) { connection = HubConnectionBuilder(url: url) .withLogging(minLogLevel: .info) .withAutoReconnect() .build() connection.delegate = self connection.on(method: Events.PricesUpdated.rawValue, callback: { response in do { let channel = try response.getArgument(type: String.self) if channel == Channels.commodities.rawValue{ if response.hasMoreArgs() { let dataModel : CommoditiesModel = try response.getArgument(type: CommoditiesModel.self) delegate.updateComodities(dataModel: dataModel) } }else{ if response.hasMoreArgs() { let dataModel : CommoditiesModel = try response.getArgument(type: CommoditiesModel.self) delegate.updateIntIndecies(dataModel: dataModel) } } } catch { //print(error) } }) connection.start() } func disconnectSignalR(){ connection.stop() } private func handleMessage(_ message: String, from user: String) { // Do something with the message. print(message) } } extension SignalRService : HubConnectionDelegate{ public func connectionDidOpen(hubConnection: HubConnection) { connection.invoke(method: Events.Subscribe.rawValue, arguments:[Channels.commodities.rawValue,commoditiesArgument]) { error in if let e = error { //self.appendMessage(message: "Error: \(e)") print(e) }else{ print("Succefully subscribe") } } connection.invoke(method: Events.Subscribe.rawValue, arguments:[Channels.intlIndices.rawValue,intIndesiesArgument]) { error in if let e = error { //self.appendMessage(message: "Error: \(e)") print(e) }else{ print("Succefully subscribe") } } print(hubConnection.connectionId ?? "") } public func connectionDidFailToOpen(error: Error) { print(error.localizedDescription) } public func connectionDidClose(error: Error?) { print(error?.localizedDescription ?? "") } }
在ViewController中按如下逻辑使用服务:视图控制器变为活跃状态时建立连接,视图消失时断开连接,实现代码如下:
class MyViewController : BaseViewController , SignalRDataUpdateDelegate { private var connection: SignalRService? @objc func didBecomeActive() { connection = SignalRService(url: URL(string: "https://myURL.com/update")!, delegate: self) } override func viewDidDisappear(_ animated: Bool) { super.viewDidDisappear(animated) connection?.disconnectSignalR() } }
功能可正常运行并更新数值,但存在异常:应用每秒收到新值时内存占用持续攀升,直到超出内存上限后应用崩溃,抛出异常信息如下:
RESOURCE RESOURCE_TYPE_MEMORY (limit=2098 MB, unused=0x0)
问题原因及修复方案
内存持续上涨是循环引用+重复注册回调/重复创建连接导致的,按以下几点逐一修复即可:
- 闭包循环引用导致实例无法释放
connection.on(method:)注册的回调闭包、invoke方法的回调闭包均强引用内部持有的对象,而connection本身是SignalRService的强持有属性,形成循环引用,每次进入页面创建的SignalRService实例、HubConnection实例、所有回调闭包都不会被系统回收,每秒收到消息时还会持续触发闭包持有新的dataModel对象,内存自然线性上涨。
修复方式:所有用到外部对象的闭包都加[weak 引用对象]捕获列表,闭包内使用弱引用调用方法:
// 注册消息接收回调时,弱持有delegate避免循环引用 connection.on(method: Events.PricesUpdated.rawValue, callback: { [weak delegate] response in do { let channel = try response.getArgument(type: String.self) if channel == Channels.commodities.rawValue{ if response.hasMoreArgs() { let dataModel : CommoditiesModel = try response.getArgument(type: CommoditiesModel.self) delegate?.updateComodities(dataModel: dataModel) } }else{ if response.hasMoreArgs() { let dataModel : CommoditiesModel = try response.getArgument(type: CommoditiesModel.self) delegate?.updateIntIndecies(dataModel: dataModel) } } } catch {} }) // invoke订阅方法的回调同理,弱持有self避免循环引用 connection.invoke(method: Events.Subscribe.rawValue, arguments:[Channels.commodities.rawValue,commoditiesArgument]) { [weak self] error in if let e = error { print(e) }else{ print("Successfully subscribe") } }
- 断开连接时未移除注册的消息回调、未清理旧连接引用
调用connection.stop()仅停止连接传输,不会移除之前通过on(method:)注册的回调,也不会释放HubConnection本身的强引用。需要在disconnectSignalR方法中补充清理逻辑:- 移除所有已注册的方法监听
- 将connection的delegate置空
- 在
ViewController中断开连接后将connection属性也置空,确保实例完全释放
// SignalRService中断开方法补充逻辑 func disconnectSignalR(){ connection.stop() // 移除所有注册的回调,方法名根据你使用的SignalR库对应API调整,常见为removeAllHandlers或者off方法 connection.removeAllHandlers() connection.delegate = nil } // ViewController的viewDidDisappear中补充置空逻辑 override func viewDidDisappear(_ animated: Bool) { super.viewDidDisappear(animated) connection?.disconnectSignalR() connection = nil }
- 重复创建连接导致实例叠加
didBecomeActive会在应用每次从后台返回前台、视图每次激活时触发,当前逻辑每次触发都会直接新建SignalRService实例赋值给connection属性,旧实例没有被显式断开和释放就被覆盖,会导致同时存在多个活跃连接、多组回调同时接收消息,内存占用翻倍上涨。
修复方式:创建新连接前先判断是否已存在连接,存在则先断开清理再创建:
@objc func didBecomeActive() { // 先清理旧连接 connection?.disconnectSignalR() connection = nil // 再创建新连接 connection = SignalRService(url: URL(string: "https://myURL.com/update")!, delegate: self) }
- 日志级别过高导致额外内存占用
当前配置的日志级别为.info,每秒接收消息时会输出大量日志,系统日志缓存如果没有及时释放也会占用内存,调试完成后建议将日志级别调整为.error或者直接关闭日志。
修复完成后可以用Xcode Memory Graph Debugger验证,进入退出页面多次后SignalRService和HubConnection实例数不会持续增长,内存占用会稳定在固定区间不会线性上涨。
内容的提问来源于stack exchange,提问作者Wahab Khan Jadon
相关产品推荐
相关产品推荐

