Skip to content

Latest commit

 

History

History
135 lines (107 loc) · 12 KB

get_graph.md

File metadata and controls

135 lines (107 loc) · 12 KB

Получение графа

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

Для получения графа зависимостей у контейнера нужно вызвать функцию makeGraph() которая быстро сконвертирует внутренний формат хранения данных в удобный для использования. Функция возвращает тип DIGraph:

let graph: DIGraph = container.makeGraph()

Граф создается для текущего состояния, и автоматически не меняется в случае регистрации дополнительных зависимостей.

DIGraph

Граф представлен списком смежности и имеет следующую структуру:

typealias AdjacencyList = [[(edge: DIEdge, toIndices: [Int])]]
let vertices: [DIVertex]
let adjacencyList: AdjacencyList

При этом гарантируется, что количество вершин равно количествую элементов списка смежности.

Вершины представлены в виде массива, и к ним можно и нужно обращаться по индексу.

Список смежности представлен как массив, где каждый элемент соответствует вершине. Для каждого элемента в массиве содержится еще один массив - список ребер. При этом видно, что ребра представлены не в типичном виде - есть само ребро, и отдельно массив индексов куда возможен переход. Оно описано как картёж для удобства использования. Да по одному ребру возможен переход в несколько вершин. Это сделано специально ради возможности правильно описать модификатор many.

если вы не используете many и ваш граф корректен, то в массиве всегда будет один элемент.

Если нужно обойти весь граф, начиная с некоторой вершины, то достаточно воспользоваться adjacencyList и использовать toIndices.

Например, если мы хотим с помощью обхода в ширину обойти все достижимые вершины, начиная с некоторой, то можно написать следующую функцию:

let graph: DIGraph = container.makeGraph()

func bfs(from startIndex: Int) {
  var visited: Set<Int> = []
  var stack: [Int] = [startIndex]
  while let fromIndex = stack.first {
    stack.removeFirst()
    visited.insert(fromIndex)
    
    for toIndex in graph.adjacencyList[fromIndex].flatMap({ $0.toIndices }) {
      if !visited.contains(toIndex) {
        stack.append(toIndex)
      }
    }
  }
  
  return visited
}

Функция при этом возвращает все достижимые вершины из заданной.

Но просто бегать по графу не интересно - давайте разбираться какие данные хранятся в нем помимо списка смежности.

DIVertex

Вершина графа. Вершина графа может быть трех видов: компонент, аргумент, неизвестный тип:

enum DIVertex: Hashable {
  case component(DIComponentVertex)
  case argument(DIArgumentVertex)
  case unknown(DIUnknownVertex)
}

Компоненты берутся из container-а и находятся вначале списка вершин. Аргументы и неизвестный тип находятся дальше, но между ними порядка нет.

Аргумент создается каждый раз, когда в компоненте встречается внедрение аргумента. Даже если типы у аргументов совпадают, то это будет две разных вершины.

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

Аргумент и неизвестный тип имеют одинаковую структуру:

struct DIArgumentVertex/DIUnknownVertex: Hashable {
  let type: DIAType
}

И просто хранят в себе обычный Swift тип.

DIComponentVertex

Вершина графа представленная компонентом. Содержит описание регистрации компонента. Уникальность обеспечивается компонентом - на каждую регистрацию в коде приходится один компонент и одна вершина. Их можно скопировать, но они будут равны.

Структура:

let componentInfo: DIComponentInfo
let lifeTime: DILifeTime
let priority: DIComponentPriority
let canInitialize: Bool

let alternativeTypes: [ComponentAlternativeType]

let framework: DIFramework.Type?
let part: DIPart.Type?

По порядку:

  • Описание компонента с точки зрения уникальности и расположения - это регистрируемый тип, файл в котором происходит регистрация и строчка кода.
  • Время жизни - тоже самое что было указано при регистрации. и также приоритет который меняется в случае если при регистрации была вызвана функция default() или test()
  • Флаг, говорящий о том можно ли компонент инициализировать, или можно только внедрять в него зависимости по средствам функции container.inject(into:.... Компонент нельзя инициализировать, если не был передан метод инициализации, то есть было написано так: container.register(Type.self) где самое важное это .self
  • Альтернативные типы. Это типы, которые были записаны с помощью функции as. Так как альтернативные типы позволяют указать не только тип, но и тэг или имя в паре с типом, то тут альтернативные типы являются перечислением: просто тип, тип+тэг, тип+имя.
  • Фреймворк и часть, в которых происходила регистрация. Эта информация очень полезная, если у вас модульное приложение.

На этом описание вершины закончилось, но в дальнейшем возможны изменения/дополнения информации о компоненте.

DIEdge

Информация о ребре графа, или по другому о зависимости. Имеет следующую структуру:

let initial: Bool
let cycle: Bool
let optional: Bool
let many: Bool
let delayed: Bool
let tags: [DITag]
let name: String? 
let type: DIAType

По порядку:

  • initial - говорит о том, что данная зависимость идет из метода инициализации, а не отдельным внедрением.
  • cycle - говорит о том, что есть указание разрыва цикла. Ну или по другому в коде было написано: .injection(cycle: true.... Если initial истина, то cycle точно лож.
  • optional - является ли данная зависимость опциональной. Это возможно если внедряемый тип имеет обычный опционал с точки зрения языка.
  • many - является ли данная зависимость множественным внедрением.
  • delayed - является ли данная зависимость отложенной. То есть используется или Lazy или Provider.
  • tags - массив тэгов, по которым производился поиск компонента. Подробней про тэги.
  • name - использовалось ли указание дополнительного имени. В отличие от тэгов имя может быть только одно, и не рекомендуемо к использованию.
  • type - базовый! тип используемый при поиске куда указывает ребро. Этот тип не содержит информации об опциональности, тэгах и т.п. типах - только база.

DICycle и поиск циклов

Помимо просмотра графа можно также найти все циклы в графе и получить их.

Для этого у созданного графа надо вызвать функцию findCycles(), которая найдет все циклы и вернет их в виде массива содержащего DICycle который имеет следующую структуру:

let vertexIndices: [Int]
let edges: [DIEdge]

Длина массива индексов вершин и длина массива ребер всегда равные. Для каждой вершины есть соответствующее ребро которое хранит информацию о переходе из этого ребра в другое. Схематично это выглядит так: vertexIndices[i] -> edges[i] -> vertexIndices[( i + 1) % count] Что означает, что по i ребру происходит переход из i вершины в i+1 вершину или начало.

Библиотека, в свою очередь, используя всю эту информацию, предлагает такую возможность как проверка графа зависимостей. С этой возможностью я настоятельно рекомендую ознакомиться, так как она может сэкономить уйму времени при разработке, и уменьшить количество ошибок во время исполнения.