Подтвердить что ты не робот

Как перебрать свойства структуры в Swift?

Можно ли перебирать свойства структуры в Swift?

Мне нужно зарегистрировать идентификаторы повторного использования ячеек в контроллере представления, который использует множество разных типов ячеек (ячейки организованы в разные файлы nib). Поэтому моя идея заключалась в том, чтобы поместить все идентификаторы повторного использования и соответствующие nib файлы в качестве статических свойств tuple (reuseID, nibName) в структуре. Но как я могу перебирать все из них, чтобы зарегистрировать ячейки с помощью tableView?

Я уже что-то пробовал (см. мой ответ ниже). Но есть ли более простой способ сделать это, например. не помещая каждое свойство внутри массива?

4b9b3361

Ответ 1

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

Вы можете использовать Ссылка на структуру зеркал. Дело в том, что после вызова reflect к некоторому объекту вы получаете его "зеркало", которое довольно экономно, но все же полезно отражать.

Таким образом, мы могли бы легко объявить следующий протокол, где key - это имя свойства, а value - фактическое значение:

protocol PropertyLoopable
{
    func allProperties() throws -> [String: Any]
}

Конечно, мы должны использовать новые расширения протокола для обеспечения реализации по умолчанию для этого протокола:

extension PropertyLoopable
{
    func allProperties() throws -> [String: Any] {

        var result: [String: Any] = [:]

        let mirror = Mirror(reflecting: self)

        guard let style = mirror.displayStyle where style == .Struct || style == .Class else {
            //throw some error
            throw NSError(domain: "hris.to", code: 777, userInfo: nil)
        }

        for (labelMaybe, valueMaybe) in mirror.children {
            guard let label = labelMaybe else {
                continue
            }

            result[label] = valueMaybe
        }

        return result
    }
}

Итак, теперь мы можем перебрать свойства любого class или struct с помощью этого метода. Мы просто должны отметить класс как PropertyLoopable.

Чтобы сохранить вещи статическими (как в примере), я также добавлю одноэлемент:

struct ReuseID: PropertyLoopable {
    static let instance: ReuseID = ReuseID()
}

Независимо от того, используется ли singleton или нет, мы могли бы, наконец, перебрать свойства следующим образом:

do {
    print(try ReuseID.instance.allProperties())
} catch _ {

}

И это с помощью свойств структуры цикла. Наслаждайтесь быстрым;)

Ответ 2

Используя hris.to удивительный ответ, я хотел бы предоставить ответ Swift 3, который больше соответствует точке и не использует синглтоны.

Протокол и расширение:

protocol Loopable {
    func allProperties() throws -> [String: Any]
}

extension Loopable {
    func allProperties() throws -> [String: Any] {

        var result: [String: Any] = [:]

        let mirror = Mirror(reflecting: self)

        // Optional check to make sure we're iterating over a struct or class
        guard let style = mirror.displayStyle, style == .struct || style == .class else {
            throw NSError()
        }

        for (property, value) in mirror.children {
            guard let property = property else {
                continue
            }

            result[property] = value
        }

        return result
    }
}

Пример:

struct Person: Loopable {
    var name: String
    var age: Int
}

var bob = Person(name: "bob", age: 20)

print(try bob.allProperties())

// prints: ["name": "bob", "age": 20]

Ответ 3

Зная, что в Swift 1.2 вы можете использовать reflect(), а после Swift 2 вы можете использовать Mirror, вот дополнение на hris.to ответ для Swift 3 и 4.

protocol Loopable {
    var allProperties: [String: Any] { get }
}
extension Loopable {
    var allProperties: [String: Any] {
        var result = [String: Any]()
        Mirror(reflecting: self).children.forEach { child in
            if let property = child.label {
                result[property] = child.value
            }
        }
        return result
    }
}

Использование любой структуры или класса:

extension NSString: Loopable {}

print("hello".allProperties)
// ["_core": Swift._StringCore(_baseAddress: Optional(0x00000001157ee000), _countAndFlags: 5, _owner: nil)]

Ответ 4

Ниже приведен пример итерации над свойствами структуры (идентификаторы повторного использования UITableViewCells и соответствующие имена NIB) с использованием функции Swifts tuple. Это полезно, если вам нравится организовывать ваши ячейки в файлах nib и иметь UIViewController, который использует много разных типов ячеек.

struct ReuseID {
  static let prepaidRechargeCreditCell = "PrepaidRechargeCreditCell"
  static let threeTitledIconCell = "ThreeTitledIconCell"
  static let usageCell = "UsageCell"
  static let detailsCell = "DetailsCell"
  static let phoneNumberCell = "PhoneNumberCell"

  static let nibNamePrepaidRechargeCreditCell = "PrepaidRechargeCreditCell"
  static let nibNameThreeTitledIconCell = "IconCellWith3Titles"
  static let nibNameUsageCell = "ListElementRingViewCell"
  static let nibNameDetailsCell = "ListElementStandardViewCell"
  static let nibNamePhoneNumberCell = "PhoneNumberCell"

  static let allValuesAndNibNames = [
    (ReuseID.prepaidRechargeCreditCell, ReuseID.nibNamePrepaidRechargeCreditCell),          
    (ReuseID.threeTitledIconCell, ReuseID.nibNameThreeTitledIconCell), 
    (ReuseID.usageCell, ReuseID.nibNameUsageCell), 
    (ReuseID.detailsCell, ReuseID.nibNameDetailsCell), 
    (ReuseID.phoneNumberCell, ReuseID.nibNamePhoneNumberCell)]
}

С помощью этой структуры легко регистрировать все типы ячеек с помощью цикла for:

for (reuseID, nibName) in ReuseID.allValuesAndNibNames {
    if let xibPath = NSBundle.mainBundle().pathForResource(nibName, ofType: "nib") {
        let fileName = xibPath.lastPathComponent.stringByDeletingPathExtension
        self.tableView.registerNib(UINib(nibName: fileName, bundle: nil), forCellReuseIdentifier: reuseID)

    } else {
        assertionFailure("Didn't find prepaidRechargeCreditCell 👎")
    }
}

Ответ 5

Я создал рекурсивную функцию на основе решения @John R Perry, которая углубляется в свойства объектов. Также требуется параметр, чтобы ограничить количество уровней (по умолчанию Int.max), чтобы предотвратить Int.max:

protocol Loopable {
    func allProperties(limit: Int) [String: Any]
}

extension Loopable {
    func allProperties(limit: Int = Int.max) [String: Any] {
        return props(obj: self, count: 0, limit: limit)
    }

    private func props(obj: Any, count: Int, limit: Int) -> [String: Any] {
        let mirror = Mirror(reflecting: obj)
        var result: [String: Any] = [:]
        for (prop, val) in mirror.children {
            guard let prop = prop else { continue }
            if limit == count {
                result[prop] = val
            } else {
                let subResult = props(obj: val, count: count + 1, limit: limit)
                result[prop] = subResult.count == 0 ? val : subResult
            }
        }
        return result
    }
}

Я избавился от проверки, является ли объект class или struct потому что параметр, не являющийся class или struct является базовым случаем рекурсивной функции, и было проще обрабатывать его вручную, чем с ошибками.

Тестирование это:

class C {
    var w = 14
}
class B: Loopable {
    var x = 12
    var y = "BHello"
    var z = C()
    static func test() -> String {
        return "Test"
    }
}
class A: Loopable {
    var a = 1
    var c = 10.0
    var d = "AHello"
    var e = true
    var f = B()
    var g = [1,2,3,4]
    var h: [String: Any] = ["A": 0, "B": "Dictionary"]
    var i: Int?
}
print(A().allProperties())

печатает:

["e": true, "g": [1, 2, 3, 4], "f": ["z": ["w": 14], "x": 12, "y": "BHello"], "h": ["A": 0, "B": "Dictionary"], "c": 10.0, "i": nil, "d": "AHello", "a": 1]

(Словари неупорядочены, поэтому, если вы получаете другой заказ, вот почему)

Ответ 6

Теперь есть гораздо более простой способ сделать это:

1. Создайте расширение кодируемого протокола:

extension Encodable {
    var dictionary: [String: Any]? {
        guard let data = try? JSONEncoder().encode(self) else { return nil }
        return (try? JSONSerialization.jsonObject(with: data, options: .allowFragments)).flatMap { $0 as? [String: Any] }
    }
}

2: приведите структуру/класс в соответствие с кодируемым протоколом

struct MyStruct: Encodable {...}
class MyClass: Encodable {...}

И тогда вы можете в любое время получить словарь, представляющий ваш экземпляр struct/class:

var a: MyStruct
var b: MyClass

print(a.dictionary)
print(b.dictionary)

А затем вы можете переключаться между клавишами:

for (key, value) in a.dictionary { ... }
for (key, value) in b.dictionary { ... }