diff --git a/README.md b/README.md index ea78df8..ff9bf59 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,7 @@ Sample iOS app written the way I write iOS apps because I cannot share the app I * Adding custom reactive properties * Basic Dark mode support * Custom operator for simple UI code -* Generated code to safely access assets +* Structured logging * Xcode build plugins ## Getting started diff --git a/Sources/iOSSampleApp.xcodeproj/project.pbxproj b/Sources/iOSSampleApp.xcodeproj/project.pbxproj index 03b3b60..3d61614 100644 --- a/Sources/iOSSampleApp.xcodeproj/project.pbxproj +++ b/Sources/iOSSampleApp.xcodeproj/project.pbxproj @@ -19,7 +19,7 @@ F33474F4244F64D80034B1C2 /* Reusable in Frameworks */ = {isa = PBXBuildFile; productRef = F33474F3244F64D80034B1C2 /* Reusable */; }; F33474F7244F65050034B1C2 /* FeedKit in Frameworks */ = {isa = PBXBuildFile; productRef = F33474F6244F65050034B1C2 /* FeedKit */; }; F33474FA244F67270034B1C2 /* RxSwiftExt in Frameworks */ = {isa = PBXBuildFile; productRef = F33474F9244F67270034B1C2 /* RxSwiftExt */; }; - F33A1F4124C9866F008ED2BD /* Log.swift in Sources */ = {isa = PBXBuildFile; fileRef = F33A1F4024C9866F008ED2BD /* Log.swift */; }; + F33A1F4124C9866F008ED2BD /* Logger+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = F33A1F4024C9866F008ED2BD /* Logger+Extensions.swift */; }; F33EB3FE1FBDD81F0050560D /* UIViewController+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = F33EB3FD1FBDD81F0050560D /* UIViewController+Extensions.swift */; }; F35BD6392065111F000AE4E8 /* AppUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F35BD6382065111F000AE4E8 /* AppUITests.swift */; }; F3651D22203C1B0D0082A73A /* DataService.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3651D21203C1B0D0082A73A /* DataService.swift */; }; @@ -82,7 +82,6 @@ F3C8DB41214EA7F200C1A654 /* FeedViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3C8DB40214EA7F200C1A654 /* FeedViewModelTests.swift */; }; F3CBB19621CCFFA00023EE18 /* RxSwift+WKWebView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3CBB19521CCFFA00023EE18 /* RxSwift+WKWebView.swift */; }; F3D6865C1F9B761E00879154 /* TestExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3D6865B1F9B761E00879154 /* TestExtensions.swift */; }; - F3D7D14B2441C599001695B9 /* DebugUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3D7D14A2441C599001695B9 /* DebugUtils.swift */; }; F3EEA696234A3BE800A2FCB5 /* SnapshotHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3EEA695234A3BE800A2FCB5 /* SnapshotHelper.swift */; }; /* End PBXBuildFile section */ @@ -109,7 +108,7 @@ F3208A7E1F84E48100B57B0E /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; F3208A881F84E83900B57B0E /* DataServiceTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataServiceTests.swift; sourceTree = ""; }; F3208A911F84EAF400B57B0E /* CustomSourceViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomSourceViewModelTests.swift; sourceTree = ""; }; - F33A1F4024C9866F008ED2BD /* Log.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Log.swift; sourceTree = ""; }; + F33A1F4024C9866F008ED2BD /* Logger+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Logger+Extensions.swift"; sourceTree = ""; }; F33EB3FD1FBDD81F0050560D /* UIViewController+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIViewController+Extensions.swift"; sourceTree = ""; }; F35BD6362065111F000AE4E8 /* iOSSampleAppUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = iOSSampleAppUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; F35BD6382065111F000AE4E8 /* AppUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppUITests.swift; sourceTree = ""; }; @@ -172,7 +171,6 @@ F3C8DB40214EA7F200C1A654 /* FeedViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedViewModelTests.swift; sourceTree = ""; }; F3CBB19521CCFFA00023EE18 /* RxSwift+WKWebView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "RxSwift+WKWebView.swift"; sourceTree = ""; }; F3D6865B1F9B761E00879154 /* TestExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestExtensions.swift; sourceTree = ""; }; - F3D7D14A2441C599001695B9 /* DebugUtils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DebugUtils.swift; sourceTree = ""; }; F3EEA695234A3BE800A2FCB5 /* SnapshotHelper.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = SnapshotHelper.swift; path = ../../fastlane/SnapshotHelper.swift; sourceTree = ""; }; /* End PBXFileReference section */ @@ -512,8 +510,6 @@ F3A812DD1F83761E00A09AAB /* Extensions */, F3A812D61F83758000A09AAB /* Coordinators */, F3A812D41F83754100A09AAB /* AppDelegate+Setup.swift */, - F3D7D14A2441C599001695B9 /* DebugUtils.swift */, - F33A1F4024C9866F008ED2BD /* Log.swift */, F3C5F3512961BB1200257080 /* Operators.swift */, ); path = "Supporting Files"; @@ -532,16 +528,17 @@ children = ( F3ABC2B92285F6D00010BAF0 /* Array+Extensions.swift */, F38CD1BF1F8384490050056C /* Bundle+Extensions.swift */, + F33A1F4024C9866F008ED2BD /* Logger+Extensions.swift */, F37781142085E14C00146DBE /* Optional+Extensions.swift */, F38CD2341F83AB9E0050056C /* Reactive+Extensions.swift */, F3CBB19521CCFFA00023EE18 /* RxSwift+WKWebView.swift */, F3A813121F837ECF00A09AAB /* String+Extensions.swift */, + F3C5F35D2961EADD00257080 /* UIEdgeInsets+Extensions.swift */, F3AAA6251F86113A009653B7 /* UINavigationController+Extensions.swift */, F38CD2361F83AEC70050056C /* UIScrollView+Extensions.swift */, F3C5F3532961BD8300257080 /* UIView+Layout.swift */, F33EB3FD1FBDD81F0050560D /* UIViewController+Extensions.swift */, F3C7CFDC2423AF4C003A961E /* URL+Extensions.swift */, - F3C5F35D2961EADD00257080 /* UIEdgeInsets+Extensions.swift */, ); path = Extensions; sourceTree = ""; @@ -798,7 +795,7 @@ files = ( F3C5F3572961C60A00257080 /* LibraryCell.swift in Sources */, F3C7CFDD2423AF4C003A961E /* URL+Extensions.swift in Sources */, - F33A1F4124C9866F008ED2BD /* Log.swift in Sources */, + F33A1F4124C9866F008ED2BD /* Logger+Extensions.swift in Sources */, F38CD1BD1F8382950050056C /* RssSourceViewModel.swift in Sources */, F3A813021F837A8200A09AAB /* SourceSelectionViewController.swift in Sources */, F38CD1C01F8384490050056C /* Bundle+Extensions.swift in Sources */, @@ -837,7 +834,6 @@ F3A812DC1F8375DF00A09AAB /* AppCoordinator.swift in Sources */, F3A8130B1F837B0E00A09AAB /* SetupCoordinator.swift in Sources */, F3A812D81F8375A100A09AAB /* Coordinator.swift in Sources */, - F3D7D14B2441C599001695B9 /* DebugUtils.swift in Sources */, F3A8130F1F837C0800A09AAB /* FeedCoordinator.swift in Sources */, F33EB3FE1FBDD81F0050560D /* UIViewController+Extensions.swift in Sources */, F3C5F35C2961E45000257080 /* AboutCell.swift in Sources */, diff --git a/Sources/iOSSampleApp/Scenarios/About/ViewModels/LibrariesViewModel.swift b/Sources/iOSSampleApp/Scenarios/About/ViewModels/LibrariesViewModel.swift index 0489cbe..76674ec 100644 --- a/Sources/iOSSampleApp/Scenarios/About/ViewModels/LibrariesViewModel.swift +++ b/Sources/iOSSampleApp/Scenarios/About/ViewModels/LibrariesViewModel.swift @@ -17,7 +17,7 @@ final class LibrariesViewModel { init() { guard let path = Bundle.main.path(forResource: "Licenses", ofType: "plist"), let array = NSArray(contentsOfFile: path) as? [[String: Any]] else { - fail("Invalid bundle linceses file") + fatalError("Invalid bundle linceses file") } libraries = Observable.just(array.map { diff --git a/Sources/iOSSampleApp/Scenarios/Common/Coordinators/AppCoordinator.swift b/Sources/iOSSampleApp/Scenarios/Common/Coordinators/AppCoordinator.swift index 82fdcca..d10e8eb 100644 --- a/Sources/iOSSampleApp/Scenarios/Common/Coordinators/AppCoordinator.swift +++ b/Sources/iOSSampleApp/Scenarios/Common/Coordinators/AppCoordinator.swift @@ -7,6 +7,7 @@ // import Foundation +import OSLog import Swinject import UIKit @@ -55,10 +56,10 @@ final class AppCoordinator: Coordinator { */ func start() { if settingsService.selectedSource.isSome { - Log.debug("Setup complete, starting dashboard") + Logger.appFlow.debug("Setup complete, starting dashboard") showFeed() } else { - Log.debug("Starting setup") + Logger.appFlow.debug("Starting setup") showSetup() } } diff --git a/Sources/iOSSampleApp/Scenarios/Common/Services/UserDefaultsSettingsService.swift b/Sources/iOSSampleApp/Scenarios/Common/Services/UserDefaultsSettingsService.swift index 8a8664d..7c9f69b 100644 --- a/Sources/iOSSampleApp/Scenarios/Common/Services/UserDefaultsSettingsService.swift +++ b/Sources/iOSSampleApp/Scenarios/Common/Services/UserDefaultsSettingsService.swift @@ -32,7 +32,7 @@ final class UserDefaultsSettingsService: SettingsService { let coder = JSONEncoder() guard let data = try? coder.encode(value) else { - fail("Encoding RssSource should never fail") + fatalError("Encoding RssSource should never fail") } UserDefaults.standard.set(data, forKey: "source") diff --git a/Sources/iOSSampleApp/Scenarios/Feed/Services/RssDataService.swift b/Sources/iOSSampleApp/Scenarios/Feed/Services/RssDataService.swift index 967c352..50ed9fa 100644 --- a/Sources/iOSSampleApp/Scenarios/Feed/Services/RssDataService.swift +++ b/Sources/iOSSampleApp/Scenarios/Feed/Services/RssDataService.swift @@ -8,6 +8,7 @@ import FeedKit import Foundation +import OSLog import UIKit final class RssDataService: DataService { @@ -19,7 +20,7 @@ final class RssDataService: DataService { let parser = FeedParser(URL: feedURL) - Log.debug("Loading \(feedURL.absoluteString)") + Logger.data.debug("Loading \(feedURL.absoluteString)") parser.parseAsync(queue: DispatchQueue.global(qos: .userInitiated)) { result in switch result { @@ -31,7 +32,7 @@ final class RssDataService: DataService { onCompletion(.success(items.map({ RssItem(title: $0.title, description: $0.description, link: $0.link, pubDate: $0.pubDate) }))) case let .failure(error): - Log.error("Loading data failed with \(error)") + Logger.data.error("Loading data failed with \(error)") onCompletion(.failure(error)) } } diff --git a/Sources/iOSSampleApp/Scenarios/Feed/ViewControllers/DetailViewController.swift b/Sources/iOSSampleApp/Scenarios/Feed/ViewControllers/DetailViewController.swift index deb05e6..a1a79ee 100644 --- a/Sources/iOSSampleApp/Scenarios/Feed/ViewControllers/DetailViewController.swift +++ b/Sources/iOSSampleApp/Scenarios/Feed/ViewControllers/DetailViewController.swift @@ -62,8 +62,8 @@ final class DetailViewController: UIViewController { } @available(*, unavailable) - required init?(coder aDecoder: NSCoder) { - notImplemented() + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") } override func loadView() { diff --git a/Sources/iOSSampleApp/Scenarios/Feed/ViewModels/FeedViewModel.swift b/Sources/iOSSampleApp/Scenarios/Feed/ViewModels/FeedViewModel.swift index cdcc25c..791b717 100644 --- a/Sources/iOSSampleApp/Scenarios/Feed/ViewModels/FeedViewModel.swift +++ b/Sources/iOSSampleApp/Scenarios/Feed/ViewModels/FeedViewModel.swift @@ -40,7 +40,7 @@ final class FeedViewModel { init(dataService: DataService, settingsService: SettingsService) { guard let source = settingsService.selectedSource else { - fail("Source not selected, nothing to show in feed") + fatalError("Source not selected, nothing to show in feed") } // converting callback based data service call to an observable diff --git a/Sources/iOSSampleApp/Scenarios/Setup/ViewModels/SourceSelectionViewModel.swift b/Sources/iOSSampleApp/Scenarios/Setup/ViewModels/SourceSelectionViewModel.swift index 906d3d6..05727f1 100644 --- a/Sources/iOSSampleApp/Scenarios/Setup/ViewModels/SourceSelectionViewModel.swift +++ b/Sources/iOSSampleApp/Scenarios/Setup/ViewModels/SourceSelectionViewModel.swift @@ -7,6 +7,7 @@ // import Foundation +import OSLog import RxCocoa import RxSwift @@ -27,7 +28,7 @@ final class SourceSelectionViewModel { init(settingsService: SettingsService) { self.settingsService = settingsService - Log.debug("Loading bundled sources") + Logger.data.debug("Loading bundled sources") let jsonData = Bundle.main.loadFile(filename: "sources.json")! @@ -78,7 +79,7 @@ final class SourceSelectionViewModel { func saveSelectedSource() -> Bool { guard let selected = allSources.value.first(where: { $0.isSelected.value }) else { - Log.error("Cannot save, no source selected") + Logger.data.error("Cannot save, no source selected") return false } diff --git a/Sources/iOSSampleApp/Supporting Files/AppDelegate+Setup.swift b/Sources/iOSSampleApp/Supporting Files/AppDelegate+Setup.swift index b2bc077..87ee563 100644 --- a/Sources/iOSSampleApp/Supporting Files/AppDelegate+Setup.swift +++ b/Sources/iOSSampleApp/Supporting Files/AppDelegate+Setup.swift @@ -7,6 +7,7 @@ // import Foundation +import OSLog import Swinject import SwinjectAutoregistration @@ -15,7 +16,7 @@ extension AppDelegate { Set up the depedency graph in the DI container */ internal func setupDependencies() { - Log.debug("Registering dependencies") + Logger.appFlow.debug("Registering dependencies") // services container.autoregister(SettingsService.self, initializer: UserDefaultsSettingsService.init).inObjectScope(ObjectScope.container) @@ -47,7 +48,7 @@ extension AppDelegate { #if DEBUG if ProcessInfo().arguments.contains("testMode") { - Log.debug("Running in UI tests, deleting selected source to start clean") + Logger.appFlow.debug("Running in UI tests, deleting selected source to start clean") let settingsService = container.resolve(SettingsService.self)! settingsService.selectedSource = nil } diff --git a/Sources/iOSSampleApp/Supporting Files/DebugUtils.swift b/Sources/iOSSampleApp/Supporting Files/DebugUtils.swift deleted file mode 100644 index 555ecff..0000000 --- a/Sources/iOSSampleApp/Supporting Files/DebugUtils.swift +++ /dev/null @@ -1,39 +0,0 @@ -// -// DebugUtils.swift -// iOSSampleApp -// -// Created by Igor Kulman on 11/04/2020. -// Copyright © 2020 Igor Kulman. All rights reserved. -// - -import Foundation - -func failDebug(_ logMessage: String, file: StaticString = #file, function: StaticString = #function, line: UInt = #line) { - let formattedMessage = formatLogMessage(logMessage, file: file, function: function, line: line) - Log.debug(formattedMessage) - assertionFailure(formattedMessage, file: file, line: line) -} - -func fail(_ logMessage: String, file: StaticString = #file, function: StaticString = #function, line: UInt = #line) -> Never { - Log.error(formatLogMessage(logMessage, file: file, function: function, line: line)) - fatalError(logMessage, file: file, line: line) -} - -func notImplemented(file: StaticString = #file, function: StaticString = #function, line: UInt = #line) -> Never { - fail("Method not implemented.", file: file, function: function, line: line) -} - -func formatLogMessage(_ logString: String, file: StaticString = #file, function: StaticString = #function, line: UInt = #line) -> String { - let filename = (file.withUTF8Buffer { - String(decoding: $0, as: UTF8.self) - } as NSString).lastPathComponent - return "[\(filename):\(line) \(function)]: \(logString)" -} - -func assertDebug(_ condition: @autoclosure () -> Bool, _ logMessage: String, file: StaticString = #file, function: StaticString = #function, line: UInt = #line) { - #if DEBUG - if !condition() { - failDebug(logMessage, file: file, function: function, line: line) - } - #endif -} diff --git a/Sources/iOSSampleApp/Supporting Files/Extensions/Logger+Extensions.swift b/Sources/iOSSampleApp/Supporting Files/Extensions/Logger+Extensions.swift new file mode 100644 index 0000000..eebc26a --- /dev/null +++ b/Sources/iOSSampleApp/Supporting Files/Extensions/Logger+Extensions.swift @@ -0,0 +1,17 @@ +// +// Log.swift +// iOSSampleApp +// +// Created by Igor Kulman on 23/07/2020. +// Copyright © 2020 Igor Kulman. All rights reserved. +// + +import Foundation +import OSLog + +extension Logger { + private static var subsystem = Bundle.main.bundleIdentifier! + + static let data = Logger(subsystem: subsystem, category: "data") + static let appFlow = Logger(subsystem: subsystem, category: "appFlow") +} diff --git a/Sources/iOSSampleApp/Supporting Files/Log.swift b/Sources/iOSSampleApp/Supporting Files/Log.swift deleted file mode 100644 index 375bbc8..0000000 --- a/Sources/iOSSampleApp/Supporting Files/Log.swift +++ /dev/null @@ -1,20 +0,0 @@ -// -// Log.swift -// iOSSampleApp -// -// Created by Igor Kulman on 23/07/2020. -// Copyright © 2020 Igor Kulman. All rights reserved. -// - -import Foundation -import os.log - -final class Log { - static func error(_ message: String) { - os_log("%@", log: .default, type: .error, message) - } - - static func debug(_ message: String) { - os_log("%@", log: .default, type: .debug, message) - } -}