(SIGABRT Попытка использовать неизвестное имя проекта класса) Ошибка EXC_BAD_ACCESS для переменной со строгой ссылкой в ​​Xcode 11, Swift 5, iOS 13 - PullRequest
2 голосов
/ 27 февраля 2020

TL; DR

У меня есть класс без инициализаторов или экземпляров publi c, который передает свой экземпляр замыканию в другом классе. Это делается через зеркало другого класса. Когда я go получаю доступ к этому экземпляру из замыкания, я получаю ошибку EXC_BAD_ACCESS, но другие параметры, передаваемые замыканию, четко доступны и не приводят к ошибке неверного доступа. Понятия не имею почему. См. Приведенный ниже код для репликации в новом проекте или на игровой площадке.

Подробное объяснение

Я пытался найти способ реализовать управление доступом, определяемое классом c, где несколько Speci c классы имеют подошва доступ к другому классу, содержащему переменные и функции для совместного использования между ними. Все остальные классы не имели бы такого доступа. Вроде как класс stati c или шаблон Singleton, но с указанным c, именованным контролем доступа класса.

Я думал, что у меня есть кое-что, что на самом деле будет работать в чистом быстром, (что хорошо для меня, так как я не знаю Objective- C, и только начал быстро около 16 месяцев go.) Это сделано почти анти-быстрым способом, так что просто потерпите меня - моя цель - начать с чем-то функциональным и двигайте это к элегантности и красоте оттуда.

Несмотря на то, что я достаточно уверен, что все должно работать, я сталкиваюсь с ошибкой EXC_BAD_ACCESS в очень неожиданном месте.

Класс "class-speci c private" что вам не разрешен доступ к экземпляру, если вы не в его «нормальном» списке, мы можем вызвать класс Restricted.

Класс (ы), которому разрешен доступ к классу Restricted, который мы может вызывать класс (ы) Accessor.

Программист должен сообщить классу Restricted, чтобы он вызывал функцию из Accessor, и "поместил" экземпляр класса Restricted, передав его в качестве параметра этой функции. , Это делается путем передачи имени вызываемой функции, экземпляра класса Accessor, для которого вызывается указанная функция, и любых параметров, которые потребуются функции в дополнение к экземпляру класса Restricted.

Я мог бы сделать огромный переход в классе Restricted, в каждом случае которого правильно вызывается каждая функция, указанная в каждом из классов Accessor ... но чтобы обойти эту чрезмерную нагрузку / настройку, у меня есть имя вызываемой функции классы Accessor передаются в виде строки и доступны через зеркало. Поскольку зеркала отражают только свойства, а не функции, функция должна быть свойством с назначенным замыканием, а не традиционной функцией.

Мы можем вызывать эти замыкания DropClosures, поскольку их целью является наличие общего класса Restricted упал в них. Фактически мы могли бы назвать весь этот шаблон «Шаблон DropClosure». (Или, может быть, анти-паттерн, я знаю, что это как-то ужасно, как есть.)

Свойства «общего» экземпляра класса Restricted хранятся внутри как частные данные c dict (как json, в основном). Чтобы сгенерировать фактический экземпляр самого себя, класс Restricted использует закрытый инициализатор, который принимает этот dict в качестве параметра. После того как DropClosure запускается с указанным инициализированным экземпляром, класс Restricted использует Mirror этого экземпляра для сохранения любых изменений в «разделяемом» файле, и экземпляр будет go вне области, если на него не будет сделана ссылка. Таким образом, после того, как каждый DropClosure завершает свой запуск, экземпляр, переданный ему, более или менее бесполезен в качестве представления «общего» аспекта класса, намеренно так.

Я делаю это только потому, что нет способа требовать, чтобы все ссылки на определенную слабую ссылку также были слабыми. Я не хочу, чтобы класс с доступом к слабой ссылке назначал сильную ссылку тому же экземпляру и сохранял ее в памяти, что могло бы нанести ущерб цели управления доступом, позволяя обмениваться экземпляром за пределами его области доступа. Поскольку я не могу заставить экземпляр истечь после того, как закрытие завершено, следующая лучшая вещь - это удалить мотивацию для этого, сделав объект больше не подключенным к общему источнику после завершения закрытия.

Это все теоретически работает, и скомпилируется, и не вызовет быстрых исключений при запуске.

Accessor (или любой класс, имеющий экземпляр Accessor) вызывает RestrictedClass.run (), код выполнения проверяется экземпляр Accessor находит DropClosure в этом экземпляре и передает экземпляр класса Restricted этому закрытию.

Однако всякий раз, когда я пытаюсь получить доступ к этому экземпляру из DropClosure, он выдает мне вышеупомянутую ошибку неверного доступа, по-видимому, на уровне C или Objective- C.

Как Насколько я могу судить, экземпляр должен быть доступен в этот момент, и ни одна из используемых переменных еще не должна выпадать из области видимости.

На данный момент я полностью плевался - возможно ли, что там что-то в фоновом режиме препятствует прохождению класса без инициализаторов publi c через зеркало? Связано ли это с передачей в замыкание из этого зеркала? Есть ли какая-то скрытая слабая ссылка, которая заставляет экземпляр получить AR C '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)
}

Обновления

Независимо от того, включены ли объекты zomb ie, я получаю одно и то же сообщение об ошибке из XCode: Thread 1: EXC_BAD_ACCESS (code=1, address=0x1a1ebac696e) Запуск анализа с помощью Command + Shift + B не выдает предупреждений.

Запуск с все включенные опции mallo c показывают следующую ошибку: Thread 1: signal SIGABRT, objc[somenumber]: Attempt to use unknown class 0xSomevalue

Это просто странно ...

По-видимому, "неизвестный класс" проэкт. Я выяснил это, выбрав (i) всплывающую подсказку в инспекторе встроенных объектов для экземпляра Restricted, который вызывал cra sh. Это дает мне следующее сообщение:

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)
                                                                  ^~~~~~~~~~~

screenshot of object description

Я думал, что это может произойти для других классов, поэтому я проверил, и он может доступ к другим классам уровня проекта просто отлично. Только для этого указанного c экземпляра "пространство имен" проекта не определено.

1 Ответ

2 голосов
/ 27 февраля 2020

Ниже приведены требуемые модификации (не так много) ... Протестировано с 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) ... и все - build & run & output

demo

Примечание: я бы рекомендовал избегать принудительного развертывания и использовать следующее

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