(SIGABRT Attempt to use unknown class projectname)EXC_BAD_ACCESS 在 Xcode 11、Swift 5、iOS 13 中的强引用变量上出错

(SIGABRT Attempt to use unknown class projectname)EXC_BAD_ACCESS error on a strongly referenced variable in Xcode 11, Swift 5, iOS 13

TL;DR

我有一个 class 没有 public 初始化器或实例,它会将自身的实例传递给另一个 class 中的闭包。它通过另一个 class 的镜像来实现。当我从闭包内访问该实例时,出现 EXC_BAD_ACCESS 错误,但传递给闭包的其他参数显然可以访问,不会导致错误的访问错误。我不知道为什么。请参阅下面的代码以在新项目或 playground 中进行复制。

详细说明

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

我认为我有一些东西可以在纯 swift 中实际工作(这对我来说很好,因为我不知道 Objective-C,并且只从 swift 开始16 个月前。)它是以一种几乎反 swift 的方式完成的,所以请耐心等待 - 我的目标是从一些功能性的东西开始,然后从那里走向优雅和美丽。

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

“class-specific private”class 除非你在它的“okay”列表中,否则你不能访问它的实例,我们可以调用 Restricted class.

允许访问受限 class 的 class(es) 我们可以调用访问器 class(es)。

程序员必须告诉 Restricted class 从访问器调用函数,并通过将其作为参数传递给该函数来“插入”Restricted class 的实例。您可以通过传入要调用的函数的名称、调用所述函数的访问器 class 的实例,以及除了 Restricted class 之外该函数还需要的任何参数来执行此操作实例.

我可以在 Restricted class 中做一个巨大的切换,每个案例都会正确调用每个访问器 classes 上指示的每个函数......但是为了避免过度 overhead/setup,我有要在作为字符串传入的访问器 classes 上调用的函数的名称,并通过镜像访问。由于镜像仅反映属性而不反映函数,因此该函数必须是 属性 并分配了闭包,而不是传统函数。

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

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

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

这一切理论上都有效,并且会编译,并且在 运行.

时不会抛出任何 swift 异常

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

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

据我所知,此时应该可以访问该实例,并且正在使用的 none 个变量应该超出范围。

在这一点上,我完全吐了口水 - 是否有可能在后台阻止没有 public 初始值设定项的 class 通过镜像?它是否与将它传递到从该镜像调用的闭包有关?是否存在某种隐藏的弱引用使实例获得 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

这有点奇怪...

显然,“未知 class”是该项目。我通过在导致崩溃的受限实例的内联对象检查器上选择 (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)
                                                                  ^~~~~~~~~~~

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

请在下面找到所需的修改(不多)...使用 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)
}