Skip to content

Commit

Permalink
Merge 52865d9 into 0576288
Browse files Browse the repository at this point in the history
  • Loading branch information
igor-makarov committed May 6, 2018
2 parents 0576288 + 52865d9 commit 966ae0b
Show file tree
Hide file tree
Showing 4 changed files with 73 additions and 0 deletions.
4 changes: 4 additions & 0 deletions Layout.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,7 @@
01FAAD271FBD991E00C26799 /* StackViewTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01FAAD261FBD991E00C26799 /* StackViewTests.swift */; };
3583485A1F3F1EB000CFB6BB /* ReturnCodeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 358348591F3F1EB000CFB6BB /* ReturnCodeTests.swift */; };
3583485B1F3F217200CFB6BB /* main.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0105F79C1F055C260067B313 /* main.swift */; };
789BC7B7209F5175000DF311 /* FileWatcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 789BC7B6209F5174000DF311 /* FileWatcher.swift */; };
ACD6CEF01FD6ABC900E08B8C /* LayoutViewControllerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = ACD6CEEF1FD6ABC900E08B8C /* LayoutViewControllerTests.swift */; };
ACD6CEF51FD6B16F00E08B8C /* LayoutLoading.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0105F7801F0547910067B313 /* LayoutLoading.swift */; };
ACD6CEF71FD6C64A00E08B8C /* LayoutDidLoad_Invalid.xml in Resources */ = {isa = PBXBuildFile; fileRef = ACD6CEF61FD6C64A00E08B8C /* LayoutDidLoad_Invalid.xml */; };
Expand Down Expand Up @@ -465,6 +466,7 @@
358348591F3F1EB000CFB6BB /* ReturnCodeTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReturnCodeTests.swift; sourceTree = "<group>"; };
4118130BF7EC77593CF35CF1 /* Pods_Layout.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Layout.framework; sourceTree = BUILT_PRODUCTS_DIR; };
53F1B12A3E800D0CF1C1C2D3 /* Pods_UIDesigner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_UIDesigner.framework; sourceTree = BUILT_PRODUCTS_DIR; };
789BC7B6209F5174000DF311 /* FileWatcher.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FileWatcher.swift; sourceTree = "<group>"; };
8470F0AD644C109EF8FEA9C4 /* Pods_SampleApp.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_SampleApp.framework; sourceTree = BUILT_PRODUCTS_DIR; };
ACD6CEEF1FD6ABC900E08B8C /* LayoutViewControllerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LayoutViewControllerTests.swift; sourceTree = "<group>"; };
ACD6CEF21FD6AFC400E08B8C /* LayoutDidLoad_Valid.xml */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = LayoutDidLoad_Valid.xml; sourceTree = "<group>"; };
Expand Down Expand Up @@ -798,6 +800,7 @@
01B1316D1F629F0700B426BB /* XMLNode+Layout.swift */,
011D73681FB1D217006406F7 /* StringUtils.swift */,
01146B111F4700AD008A9B24 /* Files.swift */,
789BC7B6209F5174000DF311 /* FileWatcher.swift */,
01052D122075129600482C10 /* Shared.swift */,
);
path = Shared;
Expand Down Expand Up @@ -1562,6 +1565,7 @@
files = (
01052D132075129600482C10 /* Shared.swift in Sources */,
01E7E0511F1C077900B7F9DB /* LayoutError+LayoutNode.swift in Sources */,
789BC7B7209F5175000DF311 /* FileWatcher.swift in Sources */,
0137B81B1FDEF24200E2061F /* ReloadManager.swift in Sources */,
01E7E04F1F1C057700B7F9DB /* Layout+XML.swift in Sources */,
012073261F39EE0000FD092A /* UIScrollView+Layout.swift in Sources */,
Expand Down
4 changes: 4 additions & 0 deletions Layout/LayoutLoader.swift
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,7 @@ class LayoutLoader {
private var _state: Any = ()
private var _constants: [String: Any] = [:]
private var _strings: [String: String]?
private(set) var watcher: FileWatcher?

/// Used for protecting operations that must not be interupted by a reload.
/// Any reload attempts that happen inside the block will be ignored
Expand Down Expand Up @@ -258,6 +259,7 @@ class LayoutLoader {
_originalURL = xmlURL
_xmlURL = xmlURL
_strings = nil
watcher = nil

func processLayoutData(_ data: Data) throws {
assert(Thread.isMainThread) // TODO: can we parse XML in the background instead?
Expand Down Expand Up @@ -290,6 +292,8 @@ class LayoutLoader {
let path = parts.joined(separator: "/")
do {
_xmlURL = try findSourceURL(forRelativePath: path, in: projectDirectory)
// Create file watcher for the source file
watcher = FileWatcher(with: _xmlURL, queue: .main)
} catch {
completion(nil, LayoutError(error))
return
Expand Down
6 changes: 6 additions & 0 deletions Layout/LayoutLoading.swift
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,9 @@ public extension LayoutLoading {
}
completion?(error)
}
loader.watcher?.fileChanged = { [weak self] in
self?.reloadLayout()
}
}

/// Reload the previously loaded xml file
Expand All @@ -79,6 +82,9 @@ public extension LayoutLoading {
}
completion?(error)
}
loader.watcher?.fileChanged = { [weak self] in
self?.reloadLayout()
}
}

// Used by LayoutLoading protocol
Expand Down
59 changes: 59 additions & 0 deletions Layout/Shared/FileWatcher.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
// Copyright © 2018 Schibsted. All rights reserved.

import Foundation

/// A basic wrapper around a VNODE dispatch source
/// Every time the file is written to, a callback gets invoked
class FileWatcher {
private let _url: URL
private let _event: DispatchSource.FileSystemEvent
private let _queue: DispatchQueue

private var _source: DispatchSourceFileSystemObject?

public var fileChanged: (() -> Void)? {
willSet {
_source?.cancel()
_source = nil
}
didSet {
startObservingFileChangesIfPossible()
}
}

init?(with url: URL,
observing event: DispatchSource.FileSystemEvent = .write,
queue: DispatchQueue = .global()) {
_url = url
_event = event
_queue = queue

guard fileExists() else { return nil }
}

deinit {
_source?.cancel()
}

private func fileExists() -> Bool {
return _url.isFileURL && FileManager.default.fileExists(atPath: _url.path)
}

private func startObservingFileChangesIfPossible() {
guard fileExists() else { return }
guard fileChanged != nil else { return }

let descriptor = open(_url.path, O_EVTONLY)

guard descriptor > 0 else { return }

let source = DispatchSource.makeFileSystemObjectSource(fileDescriptor: descriptor, eventMask: _event, queue: _queue)
_source = source

source.setEventHandler { [weak self] in self?.fileChanged?() }

source.setCancelHandler() { close(descriptor) }

source.resume()
}
}

0 comments on commit 966ae0b

Please sign in to comment.