【问题标题】:(SIGABRT Attempt to use unknown class projectname)EXC_BAD_ACCESS error on a strongly referenced variable in Xcode 11, Swift 5, iOS 13(SIGABRT 尝试使用未知类项目名称)Xcode 11、Swift 5、iOS 13 中强引用变量上的 EXC_BAD_ACCESS 错误
【发布时间】:2020-06-11 18:47:02
【问题描述】:

TL;DR

我有一个没有公共初始化程序或实例的类,它将自身的实例传递给另一个类中的闭包。它通过另一个类的镜像来做到这一点。当我从闭包中访问该实例时,我收到EXC_BAD_ACCESS 错误,但传递给闭包的其他参数显然可以访问,并且不会导致错误的访问错误。我不知道为什么。请参阅下面的代码以在新项目或 Playground 中进行复制。

详细说明

我一直在尝试找出一种方法来实现特定于类的访问控制,其中多个特定类可以唯一访问另一个包含要在它们之间共享的变量和函数的类。所有其他类都没有这种访问权限。有点像静态类或单例模式,但具有特定的、以类命名的访问控制。

我认为我有一些可以在纯 swift 中实际工作的东西,(这对我来说很好,因为我不了解 Objective-C,并且大约 16 个月前才开始使用 swift。)快速的方式,所以请耐心等待 - 我的目标是从实用的东西开始,然后将其推向优雅和美丽。

尽管我有理由相信它应该一切正常,但我在一个非常意想不到的地方遇到了 EXC_BAD_ACCESS 错误。

除非您在其“okay”列表中,否则不允许您访问其实例的“特定于类的私有”类,我们可以调用 Restricted 类。

允许访问受限类的类,我们可以称之为访问器类。

程序员必须告诉 Restricted 类从 Accessor 调用一个函数,并通过将 Restricted 类的实例作为参数传递给该函数来“放入”该实例。为此,您可以传入要调用的函数的名称、要调用该函数的 Accessor 类的实例,以及该函数除了 Restricted 类实例之外还需要的任何参数。

我可以在 Restricted 类中进行巨大的切换,每种情况都会正确调用每个 Accessor 类上指示的每个函数...但是为了避免过多的开销/设置,我有函数的名称在作为字符串传入的访问器类上调用,并通过镜像访问。由于镜像只反映属性而不反映函数,因此函数必须是具有分配闭包的属性,而不是传统函数。

我们可以将这些闭包称为 DropClosures,因为它们的目的是将共享的 Restricted 类放入其中。事实上,我们可以将整个模式称为“DropClosure 模式”。 (或者可能是反模式,我知道这有点可怕。)

Restricted 类的“共享”实例的属性在内部存储为私有静态字典(基本上是 json)。为了生成自身的实际实例,Restricted 类使用一个私有初始化程序,该初始化程序接受该 dict 作为参数。在使用所述初始化实例运行 DropClosure 之后,Restricted 类使用该实例的镜像将任何更改存储回“共享”字典中,除非对其进行引用,否则该实例将超出范围。因此,在每个 DropClosure 完成其运行后,传递给它的实例或多或少地用作类的“共享”方面的表示,这是故意的。

我这样做只是因为没有办法要求对某个弱引用的所有引用也是弱引用。我不希望有权访问弱引用的类将强引用分配给同一个实例并将其保存在内存中,这会通过允许在其访问范围之外共享实例来破坏访问控制目标。因为一旦闭包完成,我就不能强制实例过期,下一个最好的办法是通过在闭包完成后使对象不再连接到共享源来消除这样做的动机。

这一切理论上可行,并且会编译,并且在运行时不会抛出任何快速异常。

Accessor(或任何具有 Accessor 实例的类)调用 RestrictedClass.run(),运行代码验证 Accessor 实例,在该实例中找到 DropClosure,并将 Restricted 类的实例传递给该实例关闭。

但是,每当我尝试从 DropClosure 中访问该实例时,都会出现前面提到的错误访问错误,似乎是在 C 或 Objective-C 级别。

据我所知,此时该实例应该是可访问的,并且所使用的变量都不应超出范围。

在这一点上,我完全吐了 - 是否有可能在后台存在阻止没有公共初始化程序的类通过镜像传递?它是否与将其传递到从该镜像调用的闭包中有关?是否有某种隐藏的弱引用导致实例被 ARC'd?

请注意,我尝试丢弃“弱”包装对象,只将 Restricted 实例传递给闭包,但我得到了同样的错误访问错误。该错误与被弱引用的实例无关。

代码:

import Foundation

typealias DropClosureVoid<T: AnyObject & AccessRestricted> = (_ weaklyConnectedInterface: WeaklyConnectedInterface<T>, _ usingParameters: Any?)->Void
typealias DropClosureAny<T: AnyObject & AccessRestricted> = (_ weaklyConnectedInterface: WeaklyConnectedInterface<T>, _ usingParameters: Any?)->Any?

enum AccessError : Error {
    case InvalidFunction
    case InvalidAccessClass
}
protocol AccessRestricted {
    static func run<T:AnyObject>(_ closureName:String, in classObject: T, with parameters:Any?) throws
    static func runAndReturn<T:AnyObject>(_ closureName:String, in classObject: T, with parameters:Any?) throws -> Any?
}
///This class contains an instance that should be expected to only temporarily represent the original, even if a strong reference is made that keeps the value in scope.
class WeaklyConnectedInterface<T:AnyObject> {
    weak var value:T?
    init(_ value: T) {
        self.value = value
    }
}
class Accessor {
    
    let restrictedClassPassable:DropClosureVoid<RestrictedAccessClass> = { weaklyConnectedInterface, parameters in
        print(weaklyConnectedInterface) // **EXC_BAD_ACCESS error here**
        //note that the error above happens even if I pass in the instance directly, without the WeaklyConnectedInterface wrapper. 
        //It's clearly an issue that occurs when trying to access the instance, whether the instance is wrapped in a the class that makes a weak reference to it or not, which means that it is inaccessible even when strongly referenced.
        if let parameterDict = parameters as? [String:String] {
            print(parameterDict["paramkey"] ?? "nil")
            print(weaklyConnectedInterface)
            weaklyConnectedInterface.value?.restrictedVariable = "I've changed the restricted variable"
        }
    }
    
    let anotherRestrictedClassPassable:DropClosureAny<RestrictedAccessClass> = { weaklyConnectedInterface, parameters in
        if let parameterDict = parameters as? [String:String] {
            print(parameterDict["paramkey"] ?? "nil")
            print(weaklyConnectedInterface.value?.restrictedVariable as Any)
            return weaklyConnectedInterface.value?.restrictedVariable
        }
        return nil
    }
    
    func runRestrictedClassPassable() throws {
        let functionName = "restrictedClassPassable"
        print("trying validateClosureName(functionName)")
        try validateClosureName(functionName)//this is in case you refactor/change the function name and the "constant" above is no longer valid
        print("trying RestrictedAccessClass.run")
        try RestrictedAccessClass.run(functionName, in: self, with: ["paramkey":"paramvalue"])
        let returningFunctionName = "anotherRestrictedClassPassable"
        print("trying validateClosureName(returningFunctionName)")
        try validateClosureName(returningFunctionName)
        print("trying RestrictedAccessClass.runAndReturn")
        let result = (try RestrictedAccessClass.runAndReturn(returningFunctionName, in: self, with: ["paramkey":"ParamValueChanged"]) as! String?) ?? "NIL, something went wrong"
        print("result is \(result)")
    }
    
    func validateClosureName(_ name:String) throws {
        let mirror = Mirror(reflecting: self)
        var functionNameIsPresent = false
        for child in mirror.children {
            if child.label != nil && child.label! == name {
                functionNameIsPresent = true
                break
            }
        }
        guard functionNameIsPresent else {
            print("invalid function")
            throw AccessError.InvalidFunction
        }
    }
}
extension Mirror {
    func getChildrenDict() -> [String:Any]
    {
        var dict = [String:Any]()
        for child in children
        {
            if let name = child.label
            {
                dict[name] = child.value
            }
        }
        return dict
    }
}


class RestrictedAccessClass:AccessRestricted {
    
    private static var shared:[String:Any] = [
        "restrictedVariable" : "You can't access me!"
    ]
    private static func validateType<T>(of classObject:T) throws {
        switch classObject {
        case is Accessor:
            return
        default:
            print("Invalid access class")
            throw AccessError.InvalidAccessClass
        }
    }
    var restrictedVariable:String
    private init() {
        restrictedVariable = "You can't access me!"
    }
    private init(from json:[String:Any]) {
        restrictedVariable = json["restrictedVariable"] as! String
    }
    static func run<T:AnyObject>(_ closureName:String, in classObject: T, with parameters:Any?) throws {
        print("trying validateType(of: classObject) in run")
        try validateType(of: classObject)
        for child in Mirror(reflecting: classObject).children {
            if let childName = child.label {
                if childName == closureName {
                    let dropClosure = child.value as! DropClosureVoid<RestrictedAccessClass>
                    let selfInstance = RestrictedAccessClass(from:shared)
                    let interface = WeaklyConnectedInterface(selfInstance)
                    dropClosure(interface, parameters)
                    runCleanup(on: selfInstance)//parses any data changed by the end of the drop closure back into the dict for use in future instances. This means you mustn't try using the instance in an async closure. The correct way to do this would be to call run inside of an async closure, rather than putting an anync closure inside of the drop closure.
                    _ = interface.value
                    return
                }
            }
        }
    }
    static func runAndReturn<T:AnyObject>(_ closureName:String, in classObject: T, with parameters:Any?) throws -> Any? {
        print("trying validateType(of: classObject) in runAndReturn")
        try validateType(of: classObject)
        for child in Mirror(reflecting: classObject).children {
            if let childName = child.label {
                if childName == closureName {
                    let dropClosure = child.value as! DropClosureAny<RestrictedAccessClass>
                    let selfInstance = RestrictedAccessClass(from:shared)
                    let interface = WeaklyConnectedInterface(selfInstance)
                    let result = dropClosure(interface, parameters)
                    runCleanup(on: selfInstance)//parses any data changed by the end of the drop closure back into the dict for use in future instances. This means you mustn't try using the instance in an async closure. The correct way to do this would be to call run inside of an async closure, rather than putting an anync closure inside of the drop closure.
                    _ = interface.value
                    return result
                }
            }
        }
        return nil
    }
    private static func runCleanup(on instance:RestrictedAccessClass) {
        shared = Mirror(reflecting:instance).getChildrenDict()
        //once this function goes out of scope(or shortly thereafter), the instance passed will become useless as a shared resource
    }
    
    
}

遇到错误的代码:

我只是把它放在一个新项目的AppDelegate.application(didFinishLaunching) 中。您可以将上面和下面的所有代码按顺序放在操场上,它会在同一个位置中断,但不是很清楚。

let accessor = Accessor()
do {
    try accessor.runRestrictedClassPassable()
}
catch {
    print(error.localizedDescription)
}

更新

无论僵尸对象是打开还是关闭,我都会从 Xcode 收到相同的错误消息:Thread 1: EXC_BAD_ACCESS (code=1, address=0x1a1ebac696e) 使用 Command+Shift+B 运行分析不会显示任何警告。

在启用所有 malloc 选项的情况下运行会显示以下错误: Thread 1: signal SIGABRT, objc[somenumber]: Attempt to use unknown class 0xSomevalue

这有点奇怪......

显然,“未知类”是项目。我通过为导致崩溃的 Restricted 实例选择内联对象检查器上的 (i) 气泡来发现这一点。它给了我以下信息:

Printing description of weaklyConnectedInterface:

expression produced error: error: 
/var/folders/zq/_x931v493_vbyhrfc25z1yd80000gn/T/expr52-223aa0..swift:1:65: 
error: use of undeclared type 'TestProject'
Swift._DebuggerSupport.stringForPrintObject(Swift.UnsafePointer<TestProject.RestrictedAccessClass>(bitPattern: 0x103450690)!.pointee)
                                                                  ^~~~~~~~~~~

我认为可能这会发生在其他类上,所以我测试了它,它能够访问其他项目级别的类就好了。仅针对此特定实例未定义项目“命名空间”。

【问题讨论】:

  • 你是否在开启僵尸的情况下运行代码?
  • 不,让我快点试试
  • 嗯,我在这里有点超出我的深度,我在方案中打开了 Zombie Objects 并再次运行它,它因同样的错误而崩溃 - 我应该寻找什么?我什至不确定 Zombie Objects 是做什么的(作为 iOS 开发者,我还是个新手)
  • 等一下,我找到了一篇关于它的文章
  • 它会崩溃,但它应该准确地告诉你什么对象被过度释放了。

标签: ios swift exc-bad-access ios13 swift5


【解决方案1】:

请在下面找到所需的修改(不多)...已使用 Xcode 11.2 / iOS 13.2 测试。

1) 将接口inout 原样传递,否则它不知何故复制了丢失的类型信息

typealias DropClosureVoid<T: AnyObject & AccessRestricted> = 
    (_ weaklyConnectedInterface: inout WeaklyConnectedInterface<T>, _ usingParameters: Any?)->Void
typealias DropClosureAny<T: AnyObject & AccessRestricted> = 
    (_ weaklyConnectedInterface: inout WeaklyConnectedInterface<T>, _ usingParameters: Any?)->Any?

2) 修复使用地点(两处相同)

var interface = WeaklyConnectedInterface(selfInstance) // made var
dropClosure(&interface, parameters) // << copy closure args here was a reason of crash

3) ... 就是这样 - 构建 & 运行 & 输出

注意:我建议避免强制展开并使用以下内容

if let dropClosure = child.value as? DropClosureVoid<RestrictedAccessClass> {
    dropClosure(&interface, parameters)
}

【讨论】:

  • 好吧,它停止了错误。谢谢!我也执行了你的建议。
猜你喜欢
  • 2020-08-16
  • 1970-01-01
  • 2022-01-07
  • 1970-01-01
  • 2012-10-28
  • 1970-01-01
  • 2013-06-26
  • 2021-08-23
  • 2016-10-05
相关资源
最近更新 更多