<NME> XMLTemplateService.swift
<BEF> //
//  XMLTemplateService.swift
//  TemplateKit
//
//  Created by Matias Cudich on 9/11/16.
//  Copyright © 2016 Matias Cudich. All rights reserved.
//

import Foundation
import CSSParser

class XMLTemplateParser: Parser {
  typealias ParsedType = XMLDocument

  required init() {}

  func parse(data: Data) throws -> XMLDocument {
    return try XMLDocument(data: data)
  }
}

class StyleSheetParser: Parser {
  typealias ParsedType = String

  required init() {}

      return resourceService.cachePolicy
    }
  }
  let resourceService = ResourceService<XMLTemplateParser>()

  private lazy var cache = [URL: Template]()

  public init() {
  }

  public func element(withLocation location: URL, properties: [String: Any] = [:]) throws -> Element {
  private lazy var requestQueue: DispatchQueue = DispatchQueue(label: "XMLTemplateService")
  private lazy var templateResourceService: ResourceService<XMLTemplateParser> = {
    return ResourceService<XMLTemplateParser>(requestQueue: self.requestQueue)
  }()
  private lazy var styleSheetResourceService: ResourceService<StyleSheetParser> = {
    return ResourceService<StyleSheetParser>(requestQueue: self.requestQueue)
  }()

  private let liveReload: Bool
  public lazy var templates = [URL: Template]()
  private lazy var observers = [URL: NSHashTable<AnyObject>]()

  public init(liveReload: Bool = false) {
    self.liveReload = liveReload
  }

          self?.cache[url] = template
          if expectedCount == 0 {
            completion(.success())
          }
        case .failure(_):
          completion(.failure(TemplateKitError.missingTemplate("Template not found at \(url)")))
    for url in urls {
      templateResourceService.load(url) { [weak self] result in
    }
  }

  public func watchTemplates(withURLs urls: [URL], completion: @escaping (Result<Void>) -> Void) {
    let time = DispatchTime.now() + DispatchTimeInterval.seconds(5)
    DispatchQueue.main.asyncAfter(deadline: time) {
      self.fetchTemplates(withURLs: urls) { result in
        completion(result)
        self.watchTemplates(withURLs: urls, completion: completion)
      }
    }
  }
    self.observers[location] = observers
  }

  public func removeObserver(observer: Node, forLocation location: URL) {
    observers[location]?.remove(observer as AnyObject)
  }

  private func resolveStyles(for template: XMLDocument, at relativeURL: URL, completion: @escaping (StyleSheet?) -> Void) {
    var urls = [URL]()
    var sheets = [String](repeating: "", count: template.styleElements.count)
    for (index, styleElement) in template.styleElements.enumerated() {
      if let urlString = styleElement.attributes["url"], let url = URL(string: urlString, relativeTo: relativeURL) {
        urls.append(url)
      } else {
        sheets[index] = styleElement.value ?? ""
      }
    }

    let done = { (fetchedSheets: [String]) in
      completion(StyleSheet(string: fetchedSheets.joined(), inheritedProperties: ["fontName", "fontSize", "color", "textAlignment"]))
    }

    var pendingURLs = Set(urls)
    if pendingURLs.isEmpty {
      return done(sheets)
    }

    for (index, url) in urls.enumerated() {
      styleSheetResourceService.load(url) { result in
        pendingURLs.remove(url)
        switch result {
        case .success(let sheetString):
          sheets[index] = sheetString
          if pendingURLs.isEmpty {
            done(sheets)
          }
        case .failure(_):
          done(sheets)
        }
      }
    }
  }

  private func watchTemplates(withURLs urls: [URL]) {
    let time = DispatchTime.now() + liveReloadInterval
    DispatchQueue.main.asyncAfter(deadline: time) {
      let cachedCopies = self.templates
      self.fetchTemplates(withURLs: urls) { [weak self] result in
        for url in urls {
          if self?.templates[url] != cachedCopies[url], let observers = self?.observers[url] {
            for observer in observers.allObjects {
              (observer as! Node).forceUpdate()
            }
          }
        }
      }
    }
  }
}

extension XMLDocument {
  var hasRemoteStyles: Bool {
    return styleElements.contains { element in
      return element.attributes["url"] != nil
    }
  }

  var styleElements: [XMLElement] {
    return root?.children.filter { candidate in
      return candidate.name == "style"
    } ?? []
  }

  var componentElement: XMLElement? {
    return root?.children.first { candidate in
      return candidate.name != "style"
    }
  }
}

extension XMLElement: ElementProvider {
  func build(with model: Model) -> Element {
    guard let built = maybeBuild(with: model) else {
      fatalError("Templates may not be empty")
    }
    return built
  }

  func equals(_ other: ElementProvider?) -> Bool {
    guard let other = other as? XMLElement else {
      return false
    }
    return self == other
  }

  private func maybeBuild(with model: Model) -> Element? {
    let resolvedProperties = model.resolve(properties: attributes)
    if let ifDirective: Bool = resolvedProperties.cast("if"), !ifDirective {
      return nil
    }
    return NodeRegistry.shared.buildElement(with: name, properties: resolvedProperties, children: children.flatMap { $0.maybeBuild(with: model) })
  }
}

<MSG> Clean up template reloading

<DFF> @@ -27,11 +27,17 @@ public class XMLTemplateService: TemplateService {
       return resourceService.cachePolicy
     }
   }
+
+  public var liveReloadInterval = DispatchTimeInterval.seconds(5)
+
   let resourceService = ResourceService<XMLTemplateParser>()
 
+  private let liveReload: Bool
   private lazy var cache = [URL: Template]()
+  private lazy var observers = [URL: NSHashTable<AnyObject>]()
 
-  public init() {
+  public init(liveReload: Bool = false) {
+    self.liveReload = liveReload
   }
 
   public func element(withLocation location: URL, properties: [String: Any] = [:]) throws -> Element {
@@ -54,6 +60,9 @@ public class XMLTemplateService: TemplateService {
           self?.cache[url] = template
           if expectedCount == 0 {
             completion(.success())
+            if self?.liveReload ?? false {
+              self?.watchTemplates(withURLs: urls)
+            }
           }
         case .failure(_):
           completion(.failure(TemplateKitError.missingTemplate("Template not found at \(url)")))
@@ -62,12 +71,32 @@ public class XMLTemplateService: TemplateService {
     }
   }
 
-  public func watchTemplates(withURLs urls: [URL], completion: @escaping (Result<Void>) -> Void) {
-    let time = DispatchTime.now() + DispatchTimeInterval.seconds(5)
+  public func addObserver(observer: Node, forLocation location: URL) {
+    if !liveReload {
+      return
+    }
+    let observers = self.observers[location] ?? NSHashTable.weakObjects()
+
+    observers.add(observer as AnyObject)
+    self.observers[location] = observers
+  }
+
+  public func removeObserver(observer: Node, forLocation location: URL) {
+    observers[location]?.remove(observer as AnyObject)
+  }
+
+  private func watchTemplates(withURLs urls: [URL]) {
+    let time = DispatchTime.now() + liveReloadInterval
     DispatchQueue.main.asyncAfter(deadline: time) {
-      self.fetchTemplates(withURLs: urls) { result in
-        completion(result)
-        self.watchTemplates(withURLs: urls, completion: completion)
+      let cachedCopies = self.cache
+      self.fetchTemplates(withURLs: urls) { [weak self] result in
+        for url in urls {
+          if self?.cache[url] != cachedCopies[url], let observers = self?.observers[url] {
+            for observer in observers.allObjects {
+              (observer as! Node).forceUpdate()
+            }
+          }
+        }
       }
     }
   }

Clean up template reloading