Swift类继承与循环依赖下的存储属性初始化问题
现有代码实现
1. 可点击对象基类 ObjectNode
class ObjectNode: SKSpriteNode { let onTap: () -> () let objectManager: ObjectManager init(onTap: @escaping () -> (), pos: CGPoint, objectManager: ObjectManager) { self.onTap = onTap self.objectManager = objectManager let texture = SKTexture(imageNamed: "objectImage") super.init(texture: texture, color: SKColor.clear, size: texture.size()) isUserInteractionEnabled = true self.position = pos } override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) { if objectManager.objectTouched { return } onTap() } }
2. 可点击对象容器类 Object
class Object: SKNode { let object1: ObjectNode let objectMenu: ObjectMenu let pos: CGPoint init(size: CGSize, objectTapped: @escaping () -> (), objectManager: ObjectManager, objectMenu: ObjectMenu) { self.objectMenu = objectMenu pos = CGPoint(x: size.width / 2, y: size.height / 2) object1 = ObjectNode(onTap: objectTapped, pos: pos, objectManager: objectManager) addChild(object1) super.init() } }
3. 菜单类 ObjectMenu
class ObjectMenu: SKNode { let menuImage: SKSpriteNode let menuNode: ObjectNode let objectManager: ObjectManager init(size: CGSize, objectManager: ObjectManager) { self.objectManager = objectManager let texture = SKTexture(imageNamed: "menuImage") menuImage = SKSpriteNode(texture: texture, color: SKColor.clear, size: texture.size()) menuImage.position = CGPoint(x: size.width / 2, y: size.height / 2) menuNode = ObjectNode(onTap: objectManager.objectAction, pos: menuImage.position, objectManager: objectManager) super.init() } func objectTapped() { addChild(menuImage) objectManager.objectTouched = true } }
4. 对象管理类 ObjectManager
class ObjectManager: Object { var objectTouched = false func objectAction() { let object2 = ObjectNode(onTap: objectMenu.objectTapped, pos: pos, objectManager: objectMenu.objectManager) addChild(object2) } }
遇到的问题
当前类之间存在循环强依赖:ObjectManager 继承自 Object,Object 需要 ObjectMenu,ObjectMenu 又依赖 ObjectManager,导致初始化顺序(ObjectManager → ObjectMenu → Object)无法满足所有类的初始化要求,总会出现某个类依赖的属性要在自身初始化完成后才创建的情况。
考虑过将属性设为可选类型,但存在两个疑问:
- 如何将无返回值闭包作为可选参数传递
- 如何安全解包这些可选属性
解决方案
一、打破循环强依赖:用弱引用重构类关系
循环强依赖的核心是类互相持有强引用,导致初始化死锁,同时 ObjectManager 继承自 Object 的设计混淆了“管理”和“容器”的职责,先重构这部分:
- 修改
ObjectMenu,将objectManager改为弱引用可选类型:
class ObjectMenu: SKNode { let menuImage: SKSpriteNode let menuNode: ObjectNode weak var objectManager: ObjectManager? // 弱引用避免循环持有 init(size: CGSize, objectManager: ObjectManager?) { self.objectManager = objectManager let texture = SKTexture(imageNamed: "menuImage") menuImage = SKSpriteNode(texture: texture, color: SKColor.clear, size: texture.size()) menuImage.position = CGPoint(x: size.width / 2, y: size.height / 2) // 安全获取管理器的action,无依赖时用空闭包兜底 let tapAction = objectManager?.objectAction ?? {} menuNode = ObjectNode(onTap: tapAction, pos: menuImage.position, objectManager: objectManager!) super.init() } func objectTapped() { addChild(menuImage) objectManager?.objectTouched = true // 可选链调用,避免解包崩溃 } }
- 重构
ObjectManager为独立类,不再继承Object,用弱引用关联ObjectMenu:
class ObjectManager: SKNode { var objectTouched = false weak var objectMenu: ObjectMenu? var pos: CGPoint? // 存储容器对象的位置信息 func objectAction() { guard let pos = pos, let objectMenu = objectMenu else { return } let object2 = ObjectNode(onTap: objectMenu.objectTapped, pos: pos, objectManager: self) addChild(object2) } }
- 修改
Object类,调整初始化逻辑,传递必要的依赖:
class Object: SKNode { let object1: ObjectNode let objectMenu: ObjectMenu let pos: CGPoint init(size: CGSize, objectManager: ObjectManager, objectMenu: ObjectMenu) { self.objectMenu = objectMenu pos = CGPoint(x: size.width / 2, y: size.height / 2) // 给管理器传递位置和菜单引用 objectManager.pos = pos objectManager.objectMenu = objectMenu // 直接传递菜单的tap方法作为闭包 object1 = ObjectNode(onTap: objectMenu.objectTapped, pos: pos, objectManager: objectManager) addChild(object1) super.init() } }
二、可选闭包的传递与安全使用
如果必须使用可选闭包,语法和使用方式如下:
- 声明可选闭包:
var onTap: (() -> ())? - 传递闭包:可以直接传已有的闭包或
nil - 安全调用:用可选链或可选绑定
// 方式1:可选链调用,无闭包时直接跳过 onTap?() // 方式2:可选绑定+默认处理 if let tapAction = onTap { tapAction() } else { // 无闭包时的默认逻辑 print("No tap action set") }
三、调整初始化顺序
在 GameScene 中按以下顺序初始化,确保每个类的依赖都已创建:
class GameScene: SKScene { override init(size: CGSize) { super.init(size: size) // 1. 先创建管理器 let objectManager = ObjectManager() // 2. 创建菜单,传入管理器 let objectMenu = ObjectMenu(size: size, objectManager: objectManager) // 3. 创建容器对象,传入管理器和菜单 let object = Object(size: size, objectManager: objectManager, objectMenu: objectMenu) addChild(object) addChild(objectMenu) } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } }
内容的提问来源于stack exchange,提问作者quickj
相关产品推荐
相关产品推荐

