A. *_*ine 5 exc-bad-access ios swift swift5 ios13
我有一个没有公共初始化程序或实例的类,它将自身的实例传递给另一个类中的闭包。它通过其他类的镜像来做到这一点。当我从闭包内访问该实例时,出现错误EXC_BAD_ACCESS,但传递给闭包的其他参数显然可以访问,并且不会导致错误的访问错误。我不知道为什么。请参阅下面的代码以在新项目或游乐场中进行复制。
我一直在试图找出一种方法来实现特定于类的访问控制,其中多个特定类可以单独访问另一个包含要在它们之间共享的变量和函数的类。所有其他类都没有这样的访问权限。有点像静态类或单例模式,但具有特定的、以类命名的访问控制。
我以为我有一些东西可以在纯 swift 中实际工作(这对我来说很好,因为我不了解 Objective-C,并且大约 16 个月前才开始使用 swift。)它是以一种几乎反 swift 的方式完成的,所以请耐心听我说——我的目标是从功能性的东西开始,然后将其推向优雅和美丽。
尽管我相当有信心它应该一切正常,但我EXC_BAD_ACCESS在一个非常意想不到的地方遇到了错误。
除非您在其“可以”列表中,否则不允许您访问“特定于类的私有”类的实例,我们可以将其称为“受限制”类。
允许访问 Restricted 类的类我们可以称为访问器类。
程序员必须告诉 Restricted 类从访问器调用函数,并通过将 Restricted 类的实例作为参数传递给该函数来“放入”该实例。您可以通过传入要调用的函数的名称、要调用该函数的 Accessor 类的实例以及该函数除了 Restricted 类实例之外还需要的任何参数来完成此操作。
我可以在 Restricted 类中进行巨大的切换,每种情况都正确调用每个 Accessor 类上指示的每个函数...但是为了避免过多的开销/设置,我有要调用的函数的名称Accessor 类作为字符串传入,并通过镜像进行访问。由于镜像只反映属性而不反映函数,因此函数必须是带有指定闭包的属性,而不是传统的函数。
我们可以将这些闭包称为 DropClosures,因为它们的目的是将共享的 Restricted 类放入其中。事实上,我们可以将整个模式称为“DropClosure 模式”。(或者也许是反模式,我知道这有点可怕。)
Restricted 类的“共享”实例的属性在内部存储为私有静态字典(基本上为 json)。为了生成自身的实际实例,Restricted 类使用接受该字典作为参数的私有初始值设定项。在 DropClosure 使用所述初始化实例运行后,Restricted 类使用该实例的 Mirror 将任何更改存储回“共享”字典中,并且该实例将超出范围,除非对其进行引用。因此,在每个 DropClosure 完成其运行之后,传递给它的实例作为类的“共享”方面的表示或多或少是无用的,这是故意的。
我这样做只是因为没有办法要求所有对某个弱引用的引用也都是弱的。我不希望有权访问弱引用的类为同一实例分配强引用并将其保留在内存中,这会允许实例在其访问范围之外共享,从而破坏访问控制目标。由于我无法在闭包完成后强制实例过期,因此下一个最好的办法是通过在闭包完成后使对象不再连接到共享源来消除这样做的动机。
这一切理论上都是有效的,并且会编译,并且在运行时不会抛出任何快速异常。
Accessor(或任何具有 Accessor 实例的类)调用 RestrictedClass.run(),运行代码验证 Accessor 实例,在该实例中查找 DropClosure,并将 Restricted 类的实例传递给该闭包。
然而,每当我尝试从 DropClosure 中访问该实例时,它都会给我带来前面提到的错误访问错误,似乎是在 C 或 Objective-C 级别上。
据我所知,此时该实例应该是可以访问的,并且正在使用的任何变量都不应该超出范围。
此时我完全在吐痰 - 是否有可能后台有某些东西阻止没有公共初始化器的类通过镜像传递?这是否与将其传递到从该镜像调用的闭包中有关?是否存在某种隐藏的弱引用使实例获得 ARC?
请注意,我尝试丢弃“弱”包装器对象,只将 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
}
}
Run Code Online (Sandbox Code Playgroud)
我刚刚把它放在一个新项目的AppDelegate.application(didFinishLaunching). 您可以将上面和下面的所有代码按顺序放在游乐场中,它会在同一个位置中断,但不那么清楚。
let accessor = Accessor()
do {
try accessor.runRestrictedClassPassable()
}
catch {
print(error.localizedDescription)
}
Run Code Online (Sandbox Code Playgroud)
更新
无论僵尸对象是打开还是关闭,我都会从 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
这简直太奇怪了……
显然,“未知类”就是该项目。我通过在导致崩溃的受限实例的内联对象检查器上选择 (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)
^~~~~~~~~~~
Run Code Online (Sandbox Code Playgroud)
我认为其他类可能也会发生这种情况,所以我进行了测试,它能够很好地访问其他项目级类。仅对于此特定实例,项目“命名空间”未定义。
请在下面找到所需的修改(不多)...使用 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?
Run Code Online (Sandbox Code Playgroud)
2)修复使用地点(两处相同)
var interface = WeaklyConnectedInterface(selfInstance) // made var
dropClosure(&interface, parameters) // << copy closure args here was a reason of crash
Run Code Online (Sandbox Code Playgroud)
3) ...就是这样 - 构建&运行&输出

注意:我建议避免强制解开并使用以下内容
if let dropClosure = child.value as? DropClosureVoid<RestrictedAccessClass> {
dropClosure(&interface, parameters)
}
Run Code Online (Sandbox Code Playgroud)