├── Loggie ├── Assets │ └── .gitkeep └── Classes │ ├── Core │ ├── LogDetails │ │ ├── Cells │ │ │ ├── LogDetailsTextTableViewCell.swift │ │ │ ├── LogDetailsImageTableViewCell.swift │ │ │ └── LogDetailsTableViewCell.swift │ │ ├── LogDetailsSection.swift │ │ ├── Log+RequestDetails.swift │ │ ├── Log+OverviewDetails.swift │ │ ├── Log+ResponseDetails.swift │ │ └── LogDetailsViewController.swift │ ├── Utility │ │ ├── UITableViewExtensions.swift │ │ ├── UIStoryboardExtensions.swift │ │ ├── Data+FormattedJsonString.swift │ │ ├── BundleExtensions.swift │ │ └── URLRequest+Data.swift │ ├── LogList │ │ ├── LogListTableView.swift │ │ ├── LogListTableViewCell.swift │ │ ├── LogListTableViewController.storyboard │ │ └── LogListTableViewController.swift │ ├── DataDecoder.swift │ ├── LoggieManager.swift │ └── Log.swift │ ├── Alamofire │ └── EventMonitor.swift │ └── URLSession │ └── LoggieURLProtocol.swift ├── Examples ├── SwiftPM │ ├── LoggieExample │ │ ├── Assets.xcassets │ │ │ ├── Contents.json │ │ │ ├── AccentColor.colorset │ │ │ │ └── Contents.json │ │ │ └── AppIcon.appiconset │ │ │ │ └── Contents.json │ │ ├── SceneDelegate.swift │ │ ├── AppDelegate.swift │ │ ├── BaseExampleViewController.swift │ │ ├── AlamofireExampleViewController.swift │ │ ├── URLSessionExampleViewController.swift │ │ ├── Base.lproj │ │ │ └── LaunchScreen.storyboard │ │ └── Info.plist │ └── Loggie.xcodeproj │ │ ├── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ └── IDEWorkspaceChecks.plist │ │ └── xcshareddata │ │ └── xcschemes │ │ └── iOSExample.xcscheme └── Cocoapods │ ├── Pods │ ├── Target Support Files │ │ ├── Loggie │ │ │ ├── Loggie.modulemap │ │ │ ├── Loggie-dummy.m │ │ │ ├── Loggie-prefix.pch │ │ │ ├── Loggie-umbrella.h │ │ │ ├── Loggie.xcconfig │ │ │ ├── ResourceBundle-LoggieResources-Loggie-Info.plist │ │ │ ├── Info.plist │ │ │ ├── Loggie-Info.plist │ │ │ ├── Loggie.debug.xcconfig │ │ │ └── Loggie.release.xcconfig │ │ ├── Alamofire │ │ │ ├── Alamofire.modulemap │ │ │ ├── Alamofire-dummy.m │ │ │ ├── Alamofire-prefix.pch │ │ │ ├── Alamofire-umbrella.h │ │ │ ├── Alamofire.debug.xcconfig │ │ │ ├── Alamofire.release.xcconfig │ │ │ └── Alamofire-Info.plist │ │ ├── Pods-Loggie_Tests │ │ │ ├── Pods-Loggie_Tests.modulemap │ │ │ ├── Pods-Loggie_Tests-dummy.m │ │ │ ├── Pods-Loggie_Tests-umbrella.h │ │ │ ├── Info.plist │ │ │ ├── Pods-Loggie_Tests-Info.plist │ │ │ ├── Pods-Loggie_Tests.debug.xcconfig │ │ │ ├── Pods-Loggie_Tests.release.xcconfig │ │ │ ├── Pods-Loggie_Tests-acknowledgements.markdown │ │ │ ├── Pods-Loggie_Tests-acknowledgements.plist │ │ │ └── Pods-Loggie_Tests-resources.sh │ │ └── Pods-Loggie_Example │ │ │ ├── Pods-Loggie_Example.modulemap │ │ │ ├── Pods-Loggie_Example-dummy.m │ │ │ ├── Pods-Loggie_Example-umbrella.h │ │ │ ├── Info.plist │ │ │ ├── Pods-Loggie_Example-Info.plist │ │ │ ├── Pods-Loggie_Example.debug.xcconfig │ │ │ ├── Pods-Loggie_Example.release.xcconfig │ │ │ ├── Pods-Loggie_Example-acknowledgements.markdown │ │ │ ├── Pods-Loggie_Example-acknowledgements.plist │ │ │ └── Pods-Loggie_Example-resources.sh │ ├── Pods.xcodeproj │ │ └── project.xcworkspace │ │ │ ├── contents.xcworkspacedata │ │ │ └── xcshareddata │ │ │ └── IDEWorkspaceChecks.plist │ ├── Manifest.lock │ ├── Alamofire │ │ ├── LICENSE │ │ └── Source │ │ │ ├── DispatchQueue+Alamofire.swift │ │ │ ├── URLRequest+Alamofire.swift │ │ │ ├── Alamofire.swift │ │ │ ├── URLSessionConfiguration+Alamofire.swift │ │ │ ├── StringEncoding+Alamofire.swift │ │ │ ├── AlamofireExtended.swift │ │ │ ├── HTTPMethod.swift │ │ │ ├── OperationQueue+Alamofire.swift │ │ │ ├── MultipartUpload.swift │ │ │ ├── URLConvertible+URLRequestConvertible.swift │ │ │ ├── Result+Alamofire.swift │ │ │ ├── CachedResponseHandler.swift │ │ │ ├── RedirectHandler.swift │ │ │ ├── Protected.swift │ │ │ ├── Notifications.swift │ │ │ └── RequestTaskMap.swift │ └── Local Podspecs │ │ └── Loggie.podspec.json │ ├── Loggie.xcodeproj │ └── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist │ ├── Loggie.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ ├── IDEWorkspaceChecks.plist │ │ └── xcschemes │ │ └── Loggie-Example.xcscheme │ ├── Podfile │ ├── Tests │ ├── Tests.swift │ └── Info.plist │ ├── Podfile.lock │ └── Loggie │ ├── Images.xcassets │ └── AppIcon.appiconset │ │ └── Contents.json │ ├── AppDelegate.swift │ ├── ViewController.swift │ ├── Info.plist │ ├── SwiftUIView.swift │ └── Base.lproj │ ├── Main.storyboard │ └── LaunchScreen.xib ├── Package.swift ├── .gitignore ├── LICENSE ├── Loggie.podspec ├── Resources └── icon.svg ├── .github └── workflows │ └── ci.yml └── README.md /Loggie/Assets/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /Examples/SwiftPM/LoggieExample/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Examples/Cocoapods/Pods/Target Support Files/Loggie/Loggie.modulemap: -------------------------------------------------------------------------------- 1 | framework module Loggie { 2 | umbrella header "Loggie-umbrella.h" 3 | 4 | export * 5 | module * { export * } 6 | } 7 | -------------------------------------------------------------------------------- /Examples/Cocoapods/Pods/Target Support Files/Loggie/Loggie-dummy.m: -------------------------------------------------------------------------------- 1 | #import 2 | @interface PodsDummy_Loggie : NSObject 3 | @end 4 | @implementation PodsDummy_Loggie 5 | @end 6 | -------------------------------------------------------------------------------- /Examples/Cocoapods/Pods/Target Support Files/Alamofire/Alamofire.modulemap: -------------------------------------------------------------------------------- 1 | framework module Alamofire { 2 | umbrella header "Alamofire-umbrella.h" 3 | 4 | export * 5 | module * { export * } 6 | } 7 | -------------------------------------------------------------------------------- /Examples/Cocoapods/Pods/Target Support Files/Alamofire/Alamofire-dummy.m: -------------------------------------------------------------------------------- 1 | #import 2 | @interface PodsDummy_Alamofire : NSObject 3 | @end 4 | @implementation PodsDummy_Alamofire 5 | @end 6 | -------------------------------------------------------------------------------- /Examples/Cocoapods/Pods/Target Support Files/Pods-Loggie_Tests/Pods-Loggie_Tests.modulemap: -------------------------------------------------------------------------------- 1 | framework module Pods_Loggie_Tests { 2 | umbrella header "Pods-Loggie_Tests-umbrella.h" 3 | 4 | export * 5 | module * { export * } 6 | } 7 | -------------------------------------------------------------------------------- /Examples/SwiftPM/Loggie.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Examples/Cocoapods/Pods/Pods.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Examples/Cocoapods/Pods/Target Support Files/Pods-Loggie_Example/Pods-Loggie_Example.modulemap: -------------------------------------------------------------------------------- 1 | framework module Pods_Loggie_Example { 2 | umbrella header "Pods-Loggie_Example-umbrella.h" 3 | 4 | export * 5 | module * { export * } 6 | } 7 | -------------------------------------------------------------------------------- /Examples/Cocoapods/Pods/Target Support Files/Pods-Loggie_Tests/Pods-Loggie_Tests-dummy.m: -------------------------------------------------------------------------------- 1 | #import 2 | @interface PodsDummy_Pods_Loggie_Tests : NSObject 3 | @end 4 | @implementation PodsDummy_Pods_Loggie_Tests 5 | @end 6 | -------------------------------------------------------------------------------- /Examples/Cocoapods/Pods/Target Support Files/Pods-Loggie_Example/Pods-Loggie_Example-dummy.m: -------------------------------------------------------------------------------- 1 | #import 2 | @interface PodsDummy_Pods_Loggie_Example : NSObject 3 | @end 4 | @implementation PodsDummy_Pods_Loggie_Example 5 | @end 6 | -------------------------------------------------------------------------------- /Examples/Cocoapods/Loggie.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Examples/SwiftPM/LoggieExample/Assets.xcassets/AccentColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "idiom" : "universal" 5 | } 6 | ], 7 | "info" : { 8 | "author" : "xcode", 9 | "version" : 1 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Examples/Cocoapods/Pods/Target Support Files/Loggie/Loggie-prefix.pch: -------------------------------------------------------------------------------- 1 | #ifdef __OBJC__ 2 | #import 3 | #else 4 | #ifndef FOUNDATION_EXPORT 5 | #if defined(__cplusplus) 6 | #define FOUNDATION_EXPORT extern "C" 7 | #else 8 | #define FOUNDATION_EXPORT extern 9 | #endif 10 | #endif 11 | #endif 12 | 13 | -------------------------------------------------------------------------------- /Examples/Cocoapods/Pods/Target Support Files/Alamofire/Alamofire-prefix.pch: -------------------------------------------------------------------------------- 1 | #ifdef __OBJC__ 2 | #import 3 | #else 4 | #ifndef FOUNDATION_EXPORT 5 | #if defined(__cplusplus) 6 | #define FOUNDATION_EXPORT extern "C" 7 | #else 8 | #define FOUNDATION_EXPORT extern 9 | #endif 10 | #endif 11 | #endif 12 | 13 | -------------------------------------------------------------------------------- /Examples/Cocoapods/Loggie.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /Examples/Cocoapods/Podfile: -------------------------------------------------------------------------------- 1 | platform :ios, '11.0' 2 | use_frameworks! 3 | 4 | target 'Loggie_Example' do 5 | pod 'Loggie', :path => '../../' 6 | pod 'Loggie/Alamofire', :path => '../../' 7 | end 8 | 9 | target 'Loggie_Tests' do 10 | pod 'Loggie', :path => '../../' 11 | pod 'Loggie/Alamofire', :path => '../../' 12 | end -------------------------------------------------------------------------------- /Examples/Cocoapods/Loggie.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Examples/Cocoapods/Loggie.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Examples/SwiftPM/Loggie.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Loggie/Classes/Core/LogDetails/Cells/LogDetailsTextTableViewCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LogDetailsTextTableViewCell.swift 3 | // Pods 4 | // 5 | // Created by Filip Bec on 31/03/2017. 6 | // 7 | // 8 | 9 | import UIKit 10 | 11 | class LogDetailsTextTableViewCell: UITableViewCell { 12 | 13 | @IBOutlet weak var textView: UITextView! 14 | 15 | } 16 | -------------------------------------------------------------------------------- /Examples/Cocoapods/Pods/Pods.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Loggie/Classes/Core/LogDetails/Cells/LogDetailsImageTableViewCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LogDetailsImageTableViewCell.swift 3 | // Pods 4 | // 5 | // Created by Filip Bec on 16/03/2017. 6 | // 7 | // 8 | 9 | import UIKit 10 | 11 | class LogDetailsImageTableViewCell: UITableViewCell { 12 | 13 | @IBOutlet weak var customImageView: UIImageView! 14 | 15 | } 16 | -------------------------------------------------------------------------------- /Loggie/Classes/Core/LogDetails/Cells/LogDetailsTableViewCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LogDetailsTableViewCell.swift 3 | // Pods 4 | // 5 | // Created by Filip Bec on 15/03/2017. 6 | // 7 | // 8 | 9 | import UIKit 10 | 11 | class LogDetailsTableViewCell: UITableViewCell { 12 | 13 | @IBOutlet weak var titleLabel: UILabel! 14 | @IBOutlet weak var subtitleLabel: UILabel! 15 | 16 | } 17 | -------------------------------------------------------------------------------- /Examples/Cocoapods/Pods/Target Support Files/Loggie/Loggie-umbrella.h: -------------------------------------------------------------------------------- 1 | #ifdef __OBJC__ 2 | #import 3 | #else 4 | #ifndef FOUNDATION_EXPORT 5 | #if defined(__cplusplus) 6 | #define FOUNDATION_EXPORT extern "C" 7 | #else 8 | #define FOUNDATION_EXPORT extern 9 | #endif 10 | #endif 11 | #endif 12 | 13 | 14 | FOUNDATION_EXPORT double LoggieVersionNumber; 15 | FOUNDATION_EXPORT const unsigned char LoggieVersionString[]; 16 | 17 | -------------------------------------------------------------------------------- /Examples/Cocoapods/Pods/Target Support Files/Alamofire/Alamofire-umbrella.h: -------------------------------------------------------------------------------- 1 | #ifdef __OBJC__ 2 | #import 3 | #else 4 | #ifndef FOUNDATION_EXPORT 5 | #if defined(__cplusplus) 6 | #define FOUNDATION_EXPORT extern "C" 7 | #else 8 | #define FOUNDATION_EXPORT extern 9 | #endif 10 | #endif 11 | #endif 12 | 13 | 14 | FOUNDATION_EXPORT double AlamofireVersionNumber; 15 | FOUNDATION_EXPORT const unsigned char AlamofireVersionString[]; 16 | 17 | -------------------------------------------------------------------------------- /Examples/Cocoapods/Pods/Target Support Files/Pods-Loggie_Tests/Pods-Loggie_Tests-umbrella.h: -------------------------------------------------------------------------------- 1 | #ifdef __OBJC__ 2 | #import 3 | #else 4 | #ifndef FOUNDATION_EXPORT 5 | #if defined(__cplusplus) 6 | #define FOUNDATION_EXPORT extern "C" 7 | #else 8 | #define FOUNDATION_EXPORT extern 9 | #endif 10 | #endif 11 | #endif 12 | 13 | 14 | FOUNDATION_EXPORT double Pods_Loggie_TestsVersionNumber; 15 | FOUNDATION_EXPORT const unsigned char Pods_Loggie_TestsVersionString[]; 16 | 17 | -------------------------------------------------------------------------------- /Examples/Cocoapods/Pods/Target Support Files/Pods-Loggie_Example/Pods-Loggie_Example-umbrella.h: -------------------------------------------------------------------------------- 1 | #ifdef __OBJC__ 2 | #import 3 | #else 4 | #ifndef FOUNDATION_EXPORT 5 | #if defined(__cplusplus) 6 | #define FOUNDATION_EXPORT extern "C" 7 | #else 8 | #define FOUNDATION_EXPORT extern 9 | #endif 10 | #endif 11 | #endif 12 | 13 | 14 | FOUNDATION_EXPORT double Pods_Loggie_ExampleVersionNumber; 15 | FOUNDATION_EXPORT const unsigned char Pods_Loggie_ExampleVersionString[]; 16 | 17 | -------------------------------------------------------------------------------- /Loggie/Classes/Core/Utility/UITableViewExtensions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UITableViewExtensions.swift 3 | // Loggie 4 | // 5 | // Created by Filip Stojanovski on 27.10.22. 6 | // 7 | 8 | import UIKit 9 | 10 | extension UITableView { 11 | func registerCell(type: UITableViewCell.Type, identifier: String? = nil) { 12 | let cellId = String(describing: type) 13 | register(UINib(nibName: cellId, bundle: .loggie), forCellReuseIdentifier: identifier ?? cellId) 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Loggie/Classes/Core/Utility/UIStoryboardExtensions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIStoryboardExtensions.swift 3 | // Loggie 4 | // 5 | // Created by Filip Stojanovski on 27.10.22. 6 | // 7 | 8 | import UIKit 9 | 10 | extension UIStoryboard { 11 | 12 | enum LoggieIdentifier: String { 13 | case logDetails = "LogDetails" 14 | } 15 | 16 | func storyboard(for loggieIdentifier: LoggieIdentifier) -> UIStoryboard { 17 | UIStoryboard(name: loggieIdentifier.rawValue, bundle: .loggie) 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Examples/SwiftPM/LoggieExample/SceneDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SceneDelegate.swift 3 | // iOSExample 4 | // 5 | // Created by Mario Galijot on 23/10/2020. 6 | // 7 | 8 | import UIKit 9 | 10 | class SceneDelegate: UIResponder, UIWindowSceneDelegate { 11 | 12 | var window: UIWindow? 13 | 14 | func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { 15 | guard let _ = (scene as? UIWindowScene) else { return } 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /Examples/Cocoapods/Pods/Target Support Files/Loggie/Loggie.xcconfig: -------------------------------------------------------------------------------- 1 | CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/Loggie 2 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 3 | OTHER_LDFLAGS = $(inherited) -framework "Security" -framework "UIKit" 4 | OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS 5 | PODS_BUILD_DIR = ${BUILD_DIR} 6 | PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) 7 | PODS_ROOT = ${SRCROOT} 8 | PODS_TARGET_SRCROOT = ${PODS_ROOT}/../.. 9 | PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier} 10 | SKIP_INSTALL = YES 11 | USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES 12 | -------------------------------------------------------------------------------- /Examples/Cocoapods/Tests/Tests.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import XCTest 3 | import Loggie 4 | 5 | class Tests: XCTestCase { 6 | 7 | override func setUp() { 8 | super.setUp() 9 | // Put setup code here. This method is called before the invocation of each test method in the class. 10 | } 11 | 12 | override func tearDown() { 13 | // Put teardown code here. This method is called after the invocation of each test method in the class. 14 | super.tearDown() 15 | } 16 | 17 | func testExample() { 18 | // This is an example of a functional test case. 19 | XCTAssert(true, "Pass") 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /Loggie/Classes/Core/LogDetails/LogDetailsSection.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LogDetailsSection.swift 3 | // Pods 4 | // 5 | // Created by Filip Bec on 15/03/2017. 6 | // 7 | // 8 | 9 | import UIKit 10 | 11 | enum LogDetailsItem { 12 | 13 | case subtitle(String?, String?) 14 | case text(String?) 15 | case image(UIImage?) 16 | } 17 | 18 | class LogDetailsSection: NSObject { 19 | 20 | var headerTitle: String? 21 | var footerTitle: String? 22 | 23 | var items: [LogDetailsItem] = [] 24 | 25 | init(headerTitle: String? = nil, footerTitle: String? = nil) { 26 | self.headerTitle = headerTitle 27 | self.footerTitle = footerTitle 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Loggie/Classes/Core/Utility/Data+FormattedJsonString.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Data+FormattedJsonString.swift 3 | // Pods 4 | // 5 | // Created by Filip Beć on 05/05/2017. 6 | // 7 | // 8 | 9 | import UIKit 10 | 11 | extension Data { 12 | 13 | public var formattedJsonString: String? { 14 | guard let jsonObject = try? JSONSerialization.jsonObject(with: self, options: []) else { 15 | return nil 16 | } 17 | guard let formattedBody = try? JSONSerialization.data(withJSONObject: jsonObject, options: .prettyPrinted) else { 18 | return nil 19 | } 20 | return String(data: formattedBody, encoding: .utf8) 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /Examples/Cocoapods/Podfile.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - Alamofire (5.6.4) 3 | - Loggie (2.4.3): 4 | - Loggie/URLSession (= 2.4.3) 5 | - Loggie/Alamofire (2.4.3): 6 | - Alamofire (~> 5.2) 7 | - Loggie/Core 8 | - Loggie/Core (2.4.3) 9 | - Loggie/URLSession (2.4.3): 10 | - Loggie/Core 11 | 12 | DEPENDENCIES: 13 | - Loggie (from `../../`) 14 | - Loggie/Alamofire (from `../../`) 15 | 16 | SPEC REPOS: 17 | trunk: 18 | - Alamofire 19 | 20 | EXTERNAL SOURCES: 21 | Loggie: 22 | :path: "../../" 23 | 24 | SPEC CHECKSUMS: 25 | Alamofire: 4e95d97098eacb88856099c4fc79b526a299e48c 26 | Loggie: 25e7c532ec422174eebe1c9e707731d2b82a785f 27 | 28 | PODFILE CHECKSUM: 0a4acde98567376b8dc74f5701777de99bfaf511 29 | 30 | COCOAPODS: 1.12.1 31 | -------------------------------------------------------------------------------- /Examples/Cocoapods/Pods/Manifest.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - Alamofire (5.6.4) 3 | - Loggie (2.4.3): 4 | - Loggie/URLSession (= 2.4.3) 5 | - Loggie/Alamofire (2.4.3): 6 | - Alamofire (~> 5.2) 7 | - Loggie/Core 8 | - Loggie/Core (2.4.3) 9 | - Loggie/URLSession (2.4.3): 10 | - Loggie/Core 11 | 12 | DEPENDENCIES: 13 | - Loggie (from `../../`) 14 | - Loggie/Alamofire (from `../../`) 15 | 16 | SPEC REPOS: 17 | trunk: 18 | - Alamofire 19 | 20 | EXTERNAL SOURCES: 21 | Loggie: 22 | :path: "../../" 23 | 24 | SPEC CHECKSUMS: 25 | Alamofire: 4e95d97098eacb88856099c4fc79b526a299e48c 26 | Loggie: 25e7c532ec422174eebe1c9e707731d2b82a785f 27 | 28 | PODFILE CHECKSUM: 0a4acde98567376b8dc74f5701777de99bfaf511 29 | 30 | COCOAPODS: 1.12.1 31 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.5 2 | // The swift-tools-version declares the minimum version of Swift required to build this package. 3 | 4 | import PackageDescription 5 | 6 | let package = Package( 7 | name: "Loggie", 8 | defaultLocalization: "en", 9 | platforms: [ 10 | .iOS(.v11) 11 | ], 12 | products: [ 13 | .library( 14 | name: "Loggie", 15 | targets: ["Loggie"]), 16 | ], 17 | dependencies: [ 18 | .package(url: "https://github.com/Alamofire/Alamofire.git", .upToNextMajor(from: "5.2.0")) 19 | ], 20 | targets: [ 21 | .target( 22 | name: "Loggie", 23 | dependencies: ["Alamofire"], 24 | path: "Loggie/Classes" 25 | ), 26 | ] 27 | ) 28 | -------------------------------------------------------------------------------- /Loggie/Classes/Core/LogList/LogListTableView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LogListTableView.swift 3 | // Loggie 4 | // 5 | // Created by Damjan Dimovski on 14.10.22. 6 | // 7 | 8 | import SwiftUI 9 | 10 | @available(iOS 13.0, *) 11 | public struct LogListTableView: UIViewControllerRepresentable { 12 | 13 | let filter: ((Log) -> Bool)? 14 | 15 | public init(filter: ((Log) -> Bool)? = nil) { 16 | self.filter = filter 17 | } 18 | 19 | public typealias UIViewControllerType = UINavigationController 20 | 21 | public func makeUIViewController(context: Context) -> UINavigationController { 22 | return LoggieManager.shared.showLogs(filter: filter) 23 | } 24 | 25 | public func updateUIViewController(_ uiViewController: UINavigationController, context: Context) {} 26 | } 27 | -------------------------------------------------------------------------------- /Examples/SwiftPM/LoggieExample/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // iOSExample 4 | // 5 | // Created by Mario Galijot on 23/10/2020. 6 | // 7 | 8 | import UIKit 9 | 10 | @main 11 | class AppDelegate: UIResponder, UIApplicationDelegate { 12 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 13 | return true 14 | } 15 | 16 | // MARK: UISceneSession Lifecycle 17 | 18 | func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration { 19 | return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role) 20 | } 21 | } 22 | 23 | -------------------------------------------------------------------------------- /Examples/SwiftPM/LoggieExample/BaseExampleViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BaseExampleViewController.swift 3 | // iOSExample 4 | // 5 | // Created by Mario Galijot on 27/10/2020. 6 | // 7 | 8 | import UIKit 9 | import Loggie 10 | 11 | class BaseExampleViewController: UIViewController { 12 | 13 | override func viewDidLoad() { 14 | super.viewDidLoad() 15 | 16 | navigationItem.rightBarButtonItem = UIBarButtonItem( 17 | title: "Show Loggie", 18 | style: .plain, 19 | target: self, 20 | action: #selector(showLoggieBarButtonItemAction(_:)) 21 | ) 22 | } 23 | 24 | @objc 25 | private func showLoggieBarButtonItemAction(_ sender: UIBarButtonItem) { 26 | LoggieManager.shared.showLogs(from: self) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Examples/Cocoapods/Loggie/Images.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "29x29", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "29x29", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "40x40", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "40x40", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "60x60", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "60x60", 31 | "scale" : "3x" 32 | } 33 | ], 34 | "info" : { 35 | "version" : 1, 36 | "author" : "xcode" 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Loggie/Classes/Core/Utility/BundleExtensions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BundleExtensions.swift 3 | // Loggie 4 | // 5 | // Created by Filip Gulan on 25/10/2019. 6 | // 7 | 8 | import Foundation 9 | 10 | extension Bundle { 11 | 12 | private static var _bundlePath: String? { 13 | return Bundle.main.path(forResource: "LoggieResources", ofType: "bundle") 14 | } 15 | 16 | static var loggie: Bundle { 17 | #if SWIFT_PACKAGE 18 | return .module 19 | #endif 20 | 21 | // If use_frameworks! is not used then resources will be embedded 22 | // inside separate bundle, just for resources 23 | if let path = _bundlePath, let bundle = Bundle(path: path) { 24 | return bundle 25 | } 26 | return Bundle(for: LoggieManager.self) 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # OS X 2 | .DS_Store 3 | 4 | # Xcode 5 | .build/ 6 | build/ 7 | *.pbxuser 8 | !default.pbxuser 9 | *.mode1v3 10 | !default.mode1v3 11 | *.mode2v3 12 | !default.mode2v3 13 | *.perspectivev3 14 | !default.perspectivev3 15 | xcuserdata/ 16 | *.xccheckout 17 | profile 18 | *.moved-aside 19 | DerivedData 20 | *.hmap 21 | *.ipa 22 | 23 | # Bundler 24 | .bundle 25 | 26 | Carthage 27 | # We recommend against adding the Pods directory to your .gitignore. However 28 | # you should judge for yourself, the pros and cons are mentioned at: 29 | # http://guides.cocoapods.org/using/using-cocoapods.html#should-i-ignore-the-pods-directory-in-source-control 30 | # 31 | # Note: if you ignore the Pods directory, make sure to uncomment 32 | # `pod install` in .travis.yml 33 | # 34 | # Pods/ 35 | 36 | # Swift Package Manager 37 | .swiftpm/xcode 38 | Package.resolved -------------------------------------------------------------------------------- /Examples/Cocoapods/Tests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | 24 | 25 | -------------------------------------------------------------------------------- /Examples/Cocoapods/Loggie/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // Loggie 4 | // 5 | // Created by Filip Beć on 03/12/2017. 6 | // Copyright (c) 2017 Filip Beć. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import SwiftUI 11 | 12 | @UIApplicationMain 13 | class AppDelegate: UIResponder, UIApplicationDelegate { 14 | 15 | var window: UIWindow? 16 | 17 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 18 | 19 | // Uncomment this if you want to see the SwiftUI implementation 20 | // if #available(iOS 13.0.0, *) { 21 | // let vc = UIHostingController(rootView: SwiftUIView()) 22 | // window?.rootViewController = vc 23 | // window?.makeKeyAndVisible() 24 | // } 25 | 26 | return true 27 | } 28 | 29 | } 30 | 31 | -------------------------------------------------------------------------------- /Loggie/Classes/Core/Utility/URLRequest+Data.swift: -------------------------------------------------------------------------------- 1 | // 2 | // URLRequest+HTTPBody.swift 3 | // Pods 4 | // 5 | // Created by Filip Bec on 16/03/2017. 6 | // 7 | // 8 | 9 | import UIKit 10 | 11 | extension URLRequest { 12 | 13 | var data: Data? { 14 | if let body = httpBody { 15 | return body 16 | } else if let stream = httpBodyStream { 17 | let body = NSMutableData() 18 | var buffer = [UInt8](repeating: 0, count: 4096) 19 | stream.open() 20 | while stream.hasBytesAvailable { 21 | let length = stream.read(&buffer, maxLength: 4096) 22 | if length == 0 { 23 | break 24 | } else { 25 | body.append(&buffer, length: length) 26 | } 27 | } 28 | return body as Data 29 | } 30 | return nil 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /Examples/Cocoapods/Pods/Target Support Files/Loggie/ResourceBundle-LoggieResources-Loggie-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | ${PODS_DEVELOPMENT_LANGUAGE} 7 | CFBundleIdentifier 8 | ${PRODUCT_BUNDLE_IDENTIFIER} 9 | CFBundleInfoDictionaryVersion 10 | 6.0 11 | CFBundleName 12 | ${PRODUCT_NAME} 13 | CFBundlePackageType 14 | BNDL 15 | CFBundleShortVersionString 16 | 2.4.3 17 | CFBundleSignature 18 | ???? 19 | CFBundleVersion 20 | 1 21 | NSPrincipalClass 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /Examples/Cocoapods/Pods/Target Support Files/Alamofire/Alamofire.debug.xcconfig: -------------------------------------------------------------------------------- 1 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO 2 | CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/Alamofire 3 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 4 | LIBRARY_SEARCH_PATHS = $(inherited) "${DT_TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}" /usr/lib/swift 5 | OTHER_LDFLAGS = $(inherited) -framework "CFNetwork" 6 | OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS 7 | PODS_BUILD_DIR = ${BUILD_DIR} 8 | PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) 9 | PODS_DEVELOPMENT_LANGUAGE = ${DEVELOPMENT_LANGUAGE} 10 | PODS_ROOT = ${SRCROOT} 11 | PODS_TARGET_SRCROOT = ${PODS_ROOT}/Alamofire 12 | PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates 13 | PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier} 14 | SKIP_INSTALL = YES 15 | USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES 16 | -------------------------------------------------------------------------------- /Examples/Cocoapods/Pods/Target Support Files/Alamofire/Alamofire.release.xcconfig: -------------------------------------------------------------------------------- 1 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO 2 | CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/Alamofire 3 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 4 | LIBRARY_SEARCH_PATHS = $(inherited) "${DT_TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}" /usr/lib/swift 5 | OTHER_LDFLAGS = $(inherited) -framework "CFNetwork" 6 | OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS 7 | PODS_BUILD_DIR = ${BUILD_DIR} 8 | PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) 9 | PODS_DEVELOPMENT_LANGUAGE = ${DEVELOPMENT_LANGUAGE} 10 | PODS_ROOT = ${SRCROOT} 11 | PODS_TARGET_SRCROOT = ${PODS_ROOT}/Alamofire 12 | PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates 13 | PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier} 14 | SKIP_INSTALL = YES 15 | USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES 16 | -------------------------------------------------------------------------------- /Examples/Cocoapods/Pods/Target Support Files/Loggie/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | ${EXECUTABLE_NAME} 9 | CFBundleIdentifier 10 | ${PRODUCT_BUNDLE_IDENTIFIER} 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | ${PRODUCT_NAME} 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 0.2.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | ${CURRENT_PROJECT_VERSION} 23 | NSPrincipalClass 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /Examples/Cocoapods/Pods/Target Support Files/Pods-Loggie_Tests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | ${EXECUTABLE_NAME} 9 | CFBundleIdentifier 10 | ${PRODUCT_BUNDLE_IDENTIFIER} 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | ${PRODUCT_NAME} 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | ${CURRENT_PROJECT_VERSION} 23 | NSPrincipalClass 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /Examples/Cocoapods/Pods/Target Support Files/Pods-Loggie_Example/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | ${EXECUTABLE_NAME} 9 | CFBundleIdentifier 10 | ${PRODUCT_BUNDLE_IDENTIFIER} 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | ${PRODUCT_NAME} 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | ${CURRENT_PROJECT_VERSION} 23 | NSPrincipalClass 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /Examples/Cocoapods/Pods/Target Support Files/Loggie/Loggie-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | ${PODS_DEVELOPMENT_LANGUAGE} 7 | CFBundleExecutable 8 | ${EXECUTABLE_NAME} 9 | CFBundleIdentifier 10 | ${PRODUCT_BUNDLE_IDENTIFIER} 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | ${PRODUCT_NAME} 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 2.4.3 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | ${CURRENT_PROJECT_VERSION} 23 | NSPrincipalClass 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /Examples/Cocoapods/Pods/Target Support Files/Alamofire/Alamofire-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | ${PODS_DEVELOPMENT_LANGUAGE} 7 | CFBundleExecutable 8 | ${EXECUTABLE_NAME} 9 | CFBundleIdentifier 10 | ${PRODUCT_BUNDLE_IDENTIFIER} 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | ${PRODUCT_NAME} 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 5.6.4 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | ${CURRENT_PROJECT_VERSION} 23 | NSPrincipalClass 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /Examples/Cocoapods/Pods/Target Support Files/Pods-Loggie_Tests/Pods-Loggie_Tests-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | ${PODS_DEVELOPMENT_LANGUAGE} 7 | CFBundleExecutable 8 | ${EXECUTABLE_NAME} 9 | CFBundleIdentifier 10 | ${PRODUCT_BUNDLE_IDENTIFIER} 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | ${PRODUCT_NAME} 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | ${CURRENT_PROJECT_VERSION} 23 | NSPrincipalClass 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /Examples/Cocoapods/Pods/Target Support Files/Pods-Loggie_Example/Pods-Loggie_Example-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | ${PODS_DEVELOPMENT_LANGUAGE} 7 | CFBundleExecutable 8 | ${EXECUTABLE_NAME} 9 | CFBundleIdentifier 10 | ${PRODUCT_BUNDLE_IDENTIFIER} 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | ${PRODUCT_NAME} 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | ${CURRENT_PROJECT_VERSION} 23 | NSPrincipalClass 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /Examples/Cocoapods/Loggie/ViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // Loggie 4 | // 5 | // Created by Filip Beć on 03/12/2017. 6 | // Copyright (c) 2017 Filip Beć. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import Loggie 11 | 12 | class ViewController: UIViewController { 13 | 14 | @IBOutlet weak var webView: UIWebView! 15 | 16 | override func viewDidLoad() { 17 | super.viewDidLoad() 18 | URLProtocol.registerClass(LoggieURLProtocol.self) 19 | 20 | webView.delegate = self 21 | 22 | let url = URL(string: "https://jsonplaceholder.typicode.com/users/1")! 23 | let request = URLRequest(url: url) 24 | webView.loadRequest(request) 25 | } 26 | } 27 | 28 | extension ViewController: UIWebViewDelegate { 29 | 30 | func webViewDidFinishLoad(_ webView: UIWebView) { 31 | showLogs() 32 | } 33 | 34 | func webView(_ webView: UIWebView, didFailLoadWithError error: Error) { 35 | showLogs() 36 | } 37 | 38 | private func showLogs() { 39 | LoggieManager.shared.showLogs(from: self) 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /Examples/Cocoapods/Pods/Target Support Files/Loggie/Loggie.debug.xcconfig: -------------------------------------------------------------------------------- 1 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO 2 | CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/Loggie 3 | FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/Alamofire" 4 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 5 | LIBRARY_SEARCH_PATHS = $(inherited) "${DT_TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}" /usr/lib/swift 6 | OTHER_LDFLAGS = $(inherited) -framework "Alamofire" -framework "CFNetwork" -framework "Security" -framework "UIKit" 7 | OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS 8 | PODS_BUILD_DIR = ${BUILD_DIR} 9 | PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) 10 | PODS_DEVELOPMENT_LANGUAGE = ${DEVELOPMENT_LANGUAGE} 11 | PODS_ROOT = ${SRCROOT} 12 | PODS_TARGET_SRCROOT = ${PODS_ROOT}/../../.. 13 | PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates 14 | PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier} 15 | SKIP_INSTALL = YES 16 | USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES 17 | -------------------------------------------------------------------------------- /Examples/Cocoapods/Pods/Target Support Files/Loggie/Loggie.release.xcconfig: -------------------------------------------------------------------------------- 1 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO 2 | CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/Loggie 3 | FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/Alamofire" 4 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 5 | LIBRARY_SEARCH_PATHS = $(inherited) "${DT_TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}" /usr/lib/swift 6 | OTHER_LDFLAGS = $(inherited) -framework "Alamofire" -framework "CFNetwork" -framework "Security" -framework "UIKit" 7 | OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS 8 | PODS_BUILD_DIR = ${BUILD_DIR} 9 | PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) 10 | PODS_DEVELOPMENT_LANGUAGE = ${DEVELOPMENT_LANGUAGE} 11 | PODS_ROOT = ${SRCROOT} 12 | PODS_TARGET_SRCROOT = ${PODS_ROOT}/../../.. 13 | PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates 14 | PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier} 15 | SKIP_INSTALL = YES 16 | USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES 17 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2017 Filip Beć 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /Examples/Cocoapods/Pods/Alamofire/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014-2022 Alamofire Software Foundation (http://alamofire.org/) 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /Examples/Cocoapods/Pods/Target Support Files/Pods-Loggie_Example/Pods-Loggie_Example.debug.xcconfig: -------------------------------------------------------------------------------- 1 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES 2 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO 3 | FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/Alamofire" "${PODS_CONFIGURATION_BUILD_DIR}/Loggie" 4 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 5 | HEADER_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/Alamofire/Alamofire.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/Loggie/Loggie.framework/Headers" 6 | LD_RUNPATH_SEARCH_PATHS = $(inherited) /usr/lib/swift '@executable_path/Frameworks' '@loader_path/Frameworks' 7 | LIBRARY_SEARCH_PATHS = $(inherited) "${DT_TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}" /usr/lib/swift 8 | OTHER_LDFLAGS = $(inherited) -framework "Alamofire" -framework "CFNetwork" -framework "Loggie" -framework "Security" -framework "UIKit" 9 | OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS 10 | PODS_BUILD_DIR = ${BUILD_DIR} 11 | PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) 12 | PODS_PODFILE_DIR_PATH = ${SRCROOT}/. 13 | PODS_ROOT = ${SRCROOT}/Pods 14 | PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates 15 | USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES 16 | -------------------------------------------------------------------------------- /Examples/Cocoapods/Pods/Target Support Files/Pods-Loggie_Example/Pods-Loggie_Example.release.xcconfig: -------------------------------------------------------------------------------- 1 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES 2 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO 3 | FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/Alamofire" "${PODS_CONFIGURATION_BUILD_DIR}/Loggie" 4 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 5 | HEADER_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/Alamofire/Alamofire.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/Loggie/Loggie.framework/Headers" 6 | LD_RUNPATH_SEARCH_PATHS = $(inherited) /usr/lib/swift '@executable_path/Frameworks' '@loader_path/Frameworks' 7 | LIBRARY_SEARCH_PATHS = $(inherited) "${DT_TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}" /usr/lib/swift 8 | OTHER_LDFLAGS = $(inherited) -framework "Alamofire" -framework "CFNetwork" -framework "Loggie" -framework "Security" -framework "UIKit" 9 | OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS 10 | PODS_BUILD_DIR = ${BUILD_DIR} 11 | PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) 12 | PODS_PODFILE_DIR_PATH = ${SRCROOT}/. 13 | PODS_ROOT = ${SRCROOT}/Pods 14 | PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates 15 | USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES 16 | -------------------------------------------------------------------------------- /Examples/Cocoapods/Pods/Target Support Files/Pods-Loggie_Tests/Pods-Loggie_Tests.debug.xcconfig: -------------------------------------------------------------------------------- 1 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES 2 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO 3 | FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/Alamofire" "${PODS_CONFIGURATION_BUILD_DIR}/Loggie" 4 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 5 | HEADER_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/Alamofire/Alamofire.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/Loggie/Loggie.framework/Headers" 6 | LD_RUNPATH_SEARCH_PATHS = $(inherited) /usr/lib/swift "$(PLATFORM_DIR)/Developer/Library/Frameworks" '@executable_path/Frameworks' '@loader_path/Frameworks' 7 | LIBRARY_SEARCH_PATHS = $(inherited) "${DT_TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}" /usr/lib/swift 8 | OTHER_LDFLAGS = $(inherited) -framework "Alamofire" -framework "CFNetwork" -framework "Loggie" -framework "Security" -framework "UIKit" 9 | OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS 10 | PODS_BUILD_DIR = ${BUILD_DIR} 11 | PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) 12 | PODS_PODFILE_DIR_PATH = ${SRCROOT}/. 13 | PODS_ROOT = ${SRCROOT}/Pods 14 | PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates 15 | USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES 16 | -------------------------------------------------------------------------------- /Examples/Cocoapods/Pods/Target Support Files/Pods-Loggie_Tests/Pods-Loggie_Tests.release.xcconfig: -------------------------------------------------------------------------------- 1 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES 2 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO 3 | FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/Alamofire" "${PODS_CONFIGURATION_BUILD_DIR}/Loggie" 4 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 5 | HEADER_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/Alamofire/Alamofire.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/Loggie/Loggie.framework/Headers" 6 | LD_RUNPATH_SEARCH_PATHS = $(inherited) /usr/lib/swift "$(PLATFORM_DIR)/Developer/Library/Frameworks" '@executable_path/Frameworks' '@loader_path/Frameworks' 7 | LIBRARY_SEARCH_PATHS = $(inherited) "${DT_TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}" /usr/lib/swift 8 | OTHER_LDFLAGS = $(inherited) -framework "Alamofire" -framework "CFNetwork" -framework "Loggie" -framework "Security" -framework "UIKit" 9 | OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS 10 | PODS_BUILD_DIR = ${BUILD_DIR} 11 | PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) 12 | PODS_PODFILE_DIR_PATH = ${SRCROOT}/. 13 | PODS_ROOT = ${SRCROOT}/Pods 14 | PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates 15 | USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES 16 | -------------------------------------------------------------------------------- /Examples/SwiftPM/LoggieExample/AlamofireExampleViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AlamofireExampleViewController.swift 3 | // iOSExample 4 | // 5 | // Created by Mario Galijot on 27/10/2020. 6 | // 7 | 8 | import UIKit 9 | import Alamofire 10 | import Loggie 11 | 12 | final class AlamofireExampleViewController: BaseExampleViewController { 13 | 14 | private let session: Alamofire.Session = { 15 | return .init(eventMonitors: [EventMonitor()]) 16 | }() 17 | 18 | } 19 | 20 | // MARK: - Actions 21 | 22 | private extension AlamofireExampleViewController { 23 | 24 | @IBAction func performGetRequestAction(_ sender: UIButton) { 25 | session 26 | .request("https://jsonplaceholder.typicode.com/posts/1", method: .get) 27 | .response { (response: AFDataResponse) in } 28 | } 29 | 30 | @IBAction func performPostRequestAction(_ sender: UIButton) { 31 | let params: [String: String] = [ 32 | "title": "foo", 33 | "body": "bar", 34 | "userId": "1" 35 | ] 36 | 37 | session 38 | .request( 39 | "https://jsonplaceholder.typicode.com/posts", 40 | method: .post, 41 | parameters: params, 42 | encoder: JSONParameterEncoder.default 43 | ) 44 | .response { (response: AFDataResponse) in } 45 | } 46 | 47 | } 48 | -------------------------------------------------------------------------------- /Loggie.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = 'Loggie' 3 | s.version = '2.4.3' 4 | s.summary = 'In-app network logging library.' 5 | s.homepage = 'https://github.com/infinum/ios-loggie.git' 6 | s.license = { :type => 'MIT', :file => 'LICENSE' } 7 | s.author = { 'Filip Beć' => 'filip.bec@gmail.com' } 8 | s.source = { :git => 'https://github.com/infinum/ios-loggie.git', :tag => s.version.to_s } 9 | s.social_media_url = 'https://twitter.com/FilipBec' 10 | s.swift_version = '5.0' 11 | 12 | s.ios.deployment_target = '11.0' 13 | s.default_subspec = 'URLSession' 14 | 15 | s.subspec 'Core' do |sp| 16 | sp.source_files = 'Loggie/Classes/Core/**/*.{swift}' 17 | sp.resource_bundles = {'LoggieResources' => ['Loggie/Classes/Core/**/*.{xib,storyboard}']} 18 | sp.resources = ['Loggie/Classes/Core/**/*.{xib,storyboard}'] 19 | sp.frameworks = 'UIKit', 'Security' 20 | sp.ios.deployment_target = '11.0' 21 | end 22 | 23 | s.subspec 'Alamofire' do |sp| 24 | sp.source_files = 'Loggie/Classes/Alamofire/**/*.{swift}' 25 | sp.dependency 'Loggie/Core' 26 | sp.dependency 'Alamofire', '~> 5.2' 27 | sp.ios.deployment_target = '11.0' 28 | end 29 | 30 | s.subspec 'URLSession' do |sp| 31 | sp.source_files = 'Loggie/Classes/URLSession/**/*.{swift}' 32 | sp.dependency 'Loggie/Core' 33 | sp.ios.deployment_target = '11.0' 34 | end 35 | 36 | end 37 | -------------------------------------------------------------------------------- /Examples/Cocoapods/Loggie/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | LSRequiresIPhoneOS 24 | 25 | NSAppTransportSecurity 26 | 27 | NSAllowsArbitraryLoadsInWebContent 28 | 29 | 30 | UILaunchStoryboardName 31 | LaunchScreen 32 | UIMainStoryboardFile 33 | Main 34 | UIRequiredDeviceCapabilities 35 | 36 | armv7 37 | 38 | UISupportedInterfaceOrientations 39 | 40 | UIInterfaceOrientationPortrait 41 | UIInterfaceOrientationLandscapeLeft 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /Examples/Cocoapods/Loggie/SwiftUIView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SwiftUIView.swift 3 | // Loggie_Example 4 | // 5 | // Created by Damjan Dimovski on 13.10.22. 6 | // Copyright © 2022 CocoaPods. All rights reserved. 7 | // 8 | 9 | import SwiftUI 10 | import Loggie 11 | import WebKit 12 | 13 | @available(iOS 13.0.0, *) 14 | struct SwiftUIView: View { 15 | 16 | @State private var showingLogs = false 17 | 18 | var body: some View { 19 | VStack { 20 | SwiftUIWebView() 21 | Button("Show logs") { 22 | showingLogs = true 23 | } 24 | } 25 | .sheet(isPresented: $showingLogs) { 26 | LogListTableView() 27 | } 28 | } 29 | } 30 | 31 | @available(iOS 13.0.0, *) 32 | struct SwiftUIView_Previews: PreviewProvider { 33 | static var previews: some View { 34 | SwiftUIView() 35 | } 36 | } 37 | 38 | @available(iOS 13.0.0, *) 39 | struct SwiftUIWebView: UIViewRepresentable { 40 | typealias UIViewType = UIWebView 41 | 42 | let webView: UIWebView 43 | 44 | init() { 45 | URLProtocol.registerClass(LoggieURLProtocol.self) 46 | webView = UIWebView(frame: .zero) 47 | webView.loadRequest(URLRequest(url: URL(string: "https://jsonplaceholder.typicode.com/users/1")!)) 48 | } 49 | 50 | func makeUIView(context: Context) -> UIWebView { 51 | webView 52 | } 53 | func updateUIView(_ uiView: UIWebView, context: Context) { 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /Loggie/Classes/Core/LogDetails/Log+RequestDetails.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Log+RequestDetails.swift 3 | // Pods 4 | // 5 | // Created by Filip Bec on 15/03/2017. 6 | // 7 | // 8 | 9 | import UIKit 10 | 11 | extension Log { 12 | 13 | var requestDataSource: [LogDetailsSection] { 14 | var sections: [LogDetailsSection] = [] 15 | 16 | if let headers = request.allHTTPHeaderFields { 17 | let headersSection = LogDetailsSection(headerTitle: "Headers") 18 | sections.append(headersSection) 19 | 20 | headersSection.items = headers.map({ (key, value) -> LogDetailsItem in 21 | return LogDetailsItem.subtitle(key, value) 22 | }) 23 | } 24 | 25 | if let url = request.url, let queryItems = URLComponents(url: url, resolvingAgainstBaseURL: false)?.queryItems { 26 | let queryParamsSection = LogDetailsSection(headerTitle: "Query params") 27 | sections.append(queryParamsSection) 28 | 29 | queryParamsSection.items = queryItems.map({ queryItem -> LogDetailsItem in 30 | return LogDetailsItem.subtitle(queryItem.name, queryItem.value) 31 | }) 32 | } 33 | 34 | if let body = request.data { 35 | let contentType = request.value(forHTTPHeaderField: "Content-Type") 36 | let item = logDetailsItem(with: body, contentType: contentType) 37 | if let item = item { 38 | sections.append(bodySection(with: item)) 39 | } 40 | } 41 | 42 | return sections 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /Loggie/Classes/Alamofire/EventMonitor.swift: -------------------------------------------------------------------------------- 1 | // 2 | // EventMonitor.swift 3 | // LoggieAlamofire 4 | // 5 | // Created by Mario Galijot on 23/10/2020. 6 | // 7 | 8 | import Alamofire 9 | import Foundation 10 | 11 | /// An event monitor through which Loggie is able to track requests being performed. 12 | public final class EventMonitor: Alamofire.EventMonitor { 13 | 14 | public var queue: DispatchQueue { return .init(label: "com.infinum.loggie-event-monitor-queue") } 15 | 16 | public init() {} 17 | 18 | public func request(_ request: Request, didGatherMetrics metrics: URLSessionTaskMetrics) { 19 | /// we're handling only `DataRequest`s for now 20 | guard 21 | let dataRequest = request as? DataRequest, 22 | let urlRequest = dataRequest.request, 23 | let metric = metrics.transactionMetrics.last, 24 | let startTime = metric.requestStartDate, 25 | let endTime = metric.responseEndDate 26 | else { return } 27 | 28 | let log = Log(request: urlRequest) 29 | log.startTime = startTime 30 | log.endTime = endTime 31 | log.data = dataRequest.data 32 | log.error = dataRequest.error 33 | log.response = metric.response as? HTTPURLResponse 34 | // For some reason, the request that is collected only has a handful of headers available. To make this a bit more verbose, 35 | // we're attaching headers from the metric which has a lot more information about the request. 36 | log.request.headers = metric.request.headers 37 | 38 | LoggieManager.shared.add(log) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /Examples/SwiftPM/LoggieExample/URLSessionExampleViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // URLSessionExampleViewController.swift 3 | // iOSExample 4 | // 5 | // Created by Mario Galijot on 27/10/2020. 6 | // 7 | 8 | import UIKit 9 | 10 | final class URLSessionExampleViewController: BaseExampleViewController { 11 | 12 | private let session: URLSession = .init(configuration: .loggie) 13 | } 14 | 15 | // MARK: - Actions 16 | 17 | private extension URLSessionExampleViewController { 18 | 19 | @IBAction func performGetRequestAction(_ sender: UIButton) { 20 | let request = URLRequest(url: URL(string: "https://jsonplaceholder.typicode.com/posts/1")!) 21 | session 22 | .dataTask(with: request) 23 | .resume() 24 | } 25 | 26 | @IBAction func performPostRequestAction(_ sender: UIButton) { 27 | let params: [String: Any] = [ 28 | "title": "foo", 29 | "body": "bar", 30 | "userId": 1 31 | ] 32 | 33 | do { 34 | let data = try JSONSerialization.data(withJSONObject: params, options: []) 35 | 36 | var request = URLRequest(url: URL(string: "https://jsonplaceholder.typicode.com/posts/")!) 37 | request.httpMethod = "POST" 38 | request.httpBody = data 39 | request.addValue("application/json", forHTTPHeaderField: "Content-Type") 40 | request.addValue("application/json", forHTTPHeaderField: "Accept") 41 | 42 | session 43 | .dataTask(with: request) 44 | .resume() 45 | 46 | } catch { 47 | print(error) 48 | } 49 | } 50 | 51 | } 52 | -------------------------------------------------------------------------------- /Examples/SwiftPM/LoggieExample/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /Examples/Cocoapods/Pods/Alamofire/Source/DispatchQueue+Alamofire.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DispatchQueue+Alamofire.swift 3 | // 4 | // Copyright (c) 2014-2018 Alamofire Software Foundation (http://alamofire.org/) 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to deal 8 | // in the Software without restriction, including without limitation the rights 9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | // copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in 14 | // all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | // THE SOFTWARE. 23 | // 24 | 25 | import Dispatch 26 | import Foundation 27 | 28 | extension DispatchQueue { 29 | /// Execute the provided closure after a `TimeInterval`. 30 | /// 31 | /// - Parameters: 32 | /// - delay: `TimeInterval` to delay execution. 33 | /// - closure: Closure to execute. 34 | func after(_ delay: TimeInterval, execute closure: @escaping () -> Void) { 35 | asyncAfter(deadline: .now() + delay, execute: closure) 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /Examples/Cocoapods/Pods/Local Podspecs/Loggie.podspec.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Loggie", 3 | "version": "2.4.3", 4 | "summary": "In-app network logging library.", 5 | "homepage": "https://github.com/infinum/ios-loggie.git", 6 | "license": { 7 | "type": "MIT", 8 | "file": "LICENSE" 9 | }, 10 | "authors": { 11 | "Filip Beć": "filip.bec@gmail.com" 12 | }, 13 | "source": { 14 | "git": "https://github.com/infinum/ios-loggie.git", 15 | "tag": "2.4.3" 16 | }, 17 | "social_media_url": "https://twitter.com/FilipBec", 18 | "swift_versions": "5.0", 19 | "platforms": { 20 | "ios": "11.0" 21 | }, 22 | "default_subspecs": "URLSession", 23 | "subspecs": [ 24 | { 25 | "name": "Core", 26 | "source_files": "Loggie/Classes/Core/**/*.{swift}", 27 | "resource_bundles": { 28 | "LoggieResources": [ 29 | "Loggie/Classes/Core/**/*.{xib,storyboard}" 30 | ] 31 | }, 32 | "resources": [ 33 | "Loggie/Classes/Core/**/*.{xib,storyboard}" 34 | ], 35 | "frameworks": [ 36 | "UIKit", 37 | "Security" 38 | ], 39 | "platforms": { 40 | "ios": "11.0" 41 | } 42 | }, 43 | { 44 | "name": "Alamofire", 45 | "source_files": "Loggie/Classes/Alamofire/**/*.{swift}", 46 | "dependencies": { 47 | "Loggie/Core": [ 48 | 49 | ], 50 | "Alamofire": [ 51 | "~> 5.2" 52 | ] 53 | }, 54 | "platforms": { 55 | "ios": "11.0" 56 | } 57 | }, 58 | { 59 | "name": "URLSession", 60 | "source_files": "Loggie/Classes/URLSession/**/*.{swift}", 61 | "dependencies": { 62 | "Loggie/Core": [ 63 | 64 | ] 65 | }, 66 | "platforms": { 67 | "ios": "11.0" 68 | } 69 | } 70 | ], 71 | "swift_version": "5.0" 72 | } 73 | -------------------------------------------------------------------------------- /Examples/Cocoapods/Pods/Alamofire/Source/URLRequest+Alamofire.swift: -------------------------------------------------------------------------------- 1 | // 2 | // URLRequest+Alamofire.swift 3 | // 4 | // Copyright (c) 2019 Alamofire Software Foundation (http://alamofire.org/) 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to deal 8 | // in the Software without restriction, including without limitation the rights 9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | // copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in 14 | // all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | // THE SOFTWARE. 23 | // 24 | 25 | import Foundation 26 | 27 | extension URLRequest { 28 | /// Returns the `httpMethod` as Alamofire's `HTTPMethod` type. 29 | public var method: HTTPMethod? { 30 | get { httpMethod.flatMap(HTTPMethod.init) } 31 | set { httpMethod = newValue?.rawValue } 32 | } 33 | 34 | public func validate() throws { 35 | if method == .get, let bodyData = httpBody { 36 | throw AFError.urlRequestValidationFailed(reason: .bodyDataInGETRequest(bodyData)) 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /Resources/icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 8 | 9 | 10 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /Examples/Cocoapods/Pods/Alamofire/Source/Alamofire.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Alamofire.swift 3 | // 4 | // Copyright (c) 2014-2021 Alamofire Software Foundation (http://alamofire.org/) 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to deal 8 | // in the Software without restriction, including without limitation the rights 9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | // copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in 14 | // all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | // THE SOFTWARE. 23 | // 24 | 25 | import Dispatch 26 | import Foundation 27 | #if canImport(FoundationNetworking) 28 | @_exported import FoundationNetworking 29 | #endif 30 | 31 | // Enforce minimum Swift version for all platforms and build systems. 32 | #if swift(<5.3) 33 | #error("Alamofire doesn't support Swift versions below 5.3.") 34 | #endif 35 | 36 | /// Reference to `Session.default` for quick bootstrapping and examples. 37 | public let AF = Session.default 38 | 39 | /// Current Alamofire version. Necessary since SPM doesn't use dynamic libraries. Plus this will be more accurate. 40 | let version = "5.6.4" 41 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | branches: 9 | - "*" 10 | 11 | jobs: 12 | build: 13 | runs-on: macos-latest 14 | 15 | steps: 16 | - name: Checkout code 17 | id: checkout_code 18 | uses: actions/checkout@v3 19 | 20 | - name: Set up destination 21 | id: set_up_destination 22 | run: | 23 | device=$(xcrun simctl list devices | grep -oE 'iPhone.*?[^\(]+' | tail -1 | awk '{$1=$1;print}' | sed -e "s/ Simulator$//") 24 | echo "DESTINATION_NAME=$device" >> $GITHUB_ENV 25 | echo "DESTINATION_PLATFORM=iOS Simulator" >> $GITHUB_ENV 26 | 27 | - name: Build package 28 | id: build_package 29 | run: | 30 | xcodebuild clean build \ 31 | -scheme "Loggie" \ 32 | -sdk iphoneos \ 33 | -destination "platform=${{ env.DESTINATION_PLATFORM }},name=${{ env.DESTINATION_NAME }}" \ 34 | | xcpretty --color && exit ${PIPESTATUS[0]} 35 | 36 | - name: Build Cocoapods example 37 | id: build_cocoapods_example 38 | run: | 39 | xcodebuild clean build \ 40 | -workspace "Examples/Cocoapods/Loggie.xcworkspace" \ 41 | -scheme "Loggie-Example" \ 42 | -sdk iphoneos \ 43 | -destination "platform=${{ env.DESTINATION_PLATFORM }},name=${{ env.DESTINATION_NAME }}" \ 44 | | xcpretty --color && exit ${PIPESTATUS[0]} 45 | 46 | - name: Build SPM example 47 | id: build_spm_example 48 | run: | 49 | xcodebuild clean build \ 50 | -project "Examples/SwiftPM/Loggie.xcodeproj" \ 51 | -scheme "iOSExample" \ 52 | -sdk iphoneos \ 53 | -destination "platform=${{ env.DESTINATION_PLATFORM }},name=${{ env.DESTINATION_NAME }}" \ 54 | | xcpretty --color && exit ${PIPESTATUS[0]} 55 | -------------------------------------------------------------------------------- /Loggie/Classes/Core/LogDetails/Log+OverviewDetails.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Log+Details.swift 3 | // Pods 4 | // 5 | // Created by Filip Bec on 15/03/2017. 6 | // 7 | // 8 | 9 | import UIKit 10 | 11 | extension Log { 12 | 13 | var overviewDataSource: [LogDetailsSection] { 14 | var sections: [LogDetailsSection] = [] 15 | 16 | let section = LogDetailsSection() 17 | section.items.append(.subtitle("URL", request.url?.absoluteString)) 18 | sections.append(section) 19 | 20 | if let method = request.httpMethod { 21 | section.items.append(.subtitle("Method", method)) 22 | } 23 | if let responseStatus = response?.statusCode { 24 | section.items.append(.subtitle("Response status", String(responseStatus))) 25 | } 26 | if let startTime = startTime { 27 | let formatedDate = DateFormatter.localizedString(from: startTime, dateStyle: .medium, timeStyle: .medium) 28 | section.items.append(.subtitle("Request time", formatedDate)) 29 | } 30 | if let endTime = endTime { 31 | let formatedDate = DateFormatter.localizedString(from: endTime, dateStyle: .medium, timeStyle: .medium) 32 | section.items.append(.subtitle("Response time", formatedDate)) 33 | } 34 | if let durationString = durationString { 35 | section.items.append(.subtitle("Duration", durationString)) 36 | } 37 | if let requestData = request.data { 38 | let sizeString = ByteCountFormatter.string(fromByteCount: Int64(requestData.count), countStyle: .memory) 39 | section.items.append(.subtitle("Request size", sizeString)) 40 | } 41 | if let responseData = data { 42 | let sizeString = ByteCountFormatter.string(fromByteCount: Int64(responseData.count), countStyle: .memory) 43 | section.items.append(.subtitle("Response size", sizeString)) 44 | } 45 | return sections 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /Examples/SwiftPM/LoggieExample/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "scale" : "2x", 6 | "size" : "20x20" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "scale" : "3x", 11 | "size" : "20x20" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "scale" : "2x", 16 | "size" : "29x29" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "scale" : "3x", 21 | "size" : "29x29" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "scale" : "2x", 26 | "size" : "40x40" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "scale" : "3x", 31 | "size" : "40x40" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "scale" : "2x", 36 | "size" : "60x60" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "scale" : "3x", 41 | "size" : "60x60" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "scale" : "1x", 46 | "size" : "20x20" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "scale" : "2x", 51 | "size" : "20x20" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "scale" : "1x", 56 | "size" : "29x29" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "scale" : "2x", 61 | "size" : "29x29" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "scale" : "1x", 66 | "size" : "40x40" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "scale" : "2x", 71 | "size" : "40x40" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "scale" : "1x", 76 | "size" : "76x76" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "scale" : "2x", 81 | "size" : "76x76" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "scale" : "2x", 86 | "size" : "83.5x83.5" 87 | }, 88 | { 89 | "idiom" : "ios-marketing", 90 | "scale" : "1x", 91 | "size" : "1024x1024" 92 | } 93 | ], 94 | "info" : { 95 | "author" : "xcode", 96 | "version" : 1 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /Loggie/Classes/Core/LogList/LogListTableViewCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LogListTableViewCell.swift 3 | // Pods 4 | // 5 | // Created by Filip Bec on 13/03/2017. 6 | // 7 | // 8 | 9 | import UIKit 10 | 11 | class LogListTableViewCell: UITableViewCell { 12 | 13 | private static let successStatusCodes = Array(200..<300) 14 | 15 | @IBOutlet weak private var statusCodeLabel: UILabel! 16 | @IBOutlet weak private var httpMethodLabel: UILabel! 17 | @IBOutlet weak private var pathLabel: UILabel! 18 | @IBOutlet weak private var hostLabel: UILabel! 19 | @IBOutlet weak private var timeLabel: UILabel! 20 | @IBOutlet weak private var durationLabel: UILabel! 21 | 22 | private var allLabels: [UILabel] { 23 | return [statusCodeLabel, httpMethodLabel, pathLabel, hostLabel, timeLabel, durationLabel] 24 | } 25 | 26 | override func awakeFromNib() { 27 | super.awakeFromNib() 28 | clearData() 29 | } 30 | 31 | override func prepareForReuse() { 32 | super.prepareForReuse() 33 | clearData() 34 | } 35 | 36 | private func clearData() { 37 | allLabels.forEach { $0.text = nil } 38 | } 39 | 40 | func configure(with log: Log) { 41 | httpMethodLabel.text = log.request.httpMethod 42 | pathLabel.text = log.request.url?.path 43 | hostLabel.text = log.request.url?.host 44 | 45 | if let startTime = log.startTime { 46 | timeLabel.text = DateFormatter.localizedString(from: startTime, dateStyle: .none, timeStyle: .medium) 47 | } 48 | 49 | durationLabel.text = log.durationString 50 | 51 | if let statusCode = log.response?.statusCode { 52 | statusCodeLabel.text = String(statusCode) 53 | if LogListTableViewCell.successStatusCodes.contains(statusCode) { 54 | statusCodeLabel.textColor = UIColor(red: 55.0/255.0, green: 188.0/255.0, blue: 155.0/255.0, alpha: 1.0) 55 | } else { 56 | statusCodeLabel.textColor = UIColor(red: 218.0/255.0, green: 68.0/255.0, blue: 83.0/255.0, alpha: 1.0) 57 | } 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /Loggie/Classes/Core/DataDecoder.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ResponseDecoder.swift 3 | // Pods 4 | // 5 | // Created by Filip Bec on 16/03/2017. 6 | // 7 | // 8 | 9 | import UIKit 10 | 11 | protocol DataDecoder: NSObjectProtocol { 12 | func canDecode(_ data: Data, contentType: String?) -> Bool 13 | func decode(_ data: Data, contentType: String?) -> LogDetailsItem 14 | } 15 | 16 | class JSONDataDecoder: NSObject, DataDecoder { 17 | 18 | func canDecode(_ data: Data, contentType: String?) -> Bool { 19 | if contentType == "application/json" { 20 | return true 21 | } 22 | return data.formattedJsonString != nil 23 | } 24 | 25 | func decode(_ data: Data, contentType: String?) -> LogDetailsItem { 26 | return .text(data.formattedJsonString) 27 | } 28 | } 29 | 30 | class PlainTextDataDecoder: NSObject, DataDecoder { 31 | 32 | func canDecode(_ data: Data, contentType: String?) -> Bool { 33 | if let _contentType = contentType, _contentType.hasPrefix("text") { 34 | return true 35 | } 36 | return false 37 | } 38 | 39 | func decode(_ data: Data, contentType: String?) -> LogDetailsItem { 40 | let text = String(data: data, encoding: .utf8) 41 | return .text(text) 42 | } 43 | } 44 | 45 | class ImageDataDecoder: NSObject, DataDecoder { 46 | 47 | func canDecode(_ data: Data, contentType: String?) -> Bool { 48 | if let _contentType = contentType, _contentType.hasPrefix("image") { 49 | return true 50 | } 51 | if UIImage(data: data) != nil { 52 | return true 53 | } 54 | return false 55 | } 56 | 57 | func decode(_ data: Data, contentType: String?) -> LogDetailsItem { 58 | let image = UIImage(data: data) 59 | return .image(image) 60 | } 61 | } 62 | 63 | class DefaultDecoder: NSObject, DataDecoder { 64 | 65 | func canDecode(_ data: Data, contentType: String?) -> Bool { 66 | return true 67 | } 68 | 69 | func decode(_ data: Data, contentType: String?) -> LogDetailsItem { 70 | return .text(String(data: data, encoding: .utf8)) 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /Examples/Cocoapods/Pods/Alamofire/Source/URLSessionConfiguration+Alamofire.swift: -------------------------------------------------------------------------------- 1 | // 2 | // URLSessionConfiguration+Alamofire.swift 3 | // 4 | // Copyright (c) 2014-2018 Alamofire Software Foundation (http://alamofire.org/) 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to deal 8 | // in the Software without restriction, including without limitation the rights 9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | // copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in 14 | // all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | // THE SOFTWARE. 23 | // 24 | 25 | import Foundation 26 | 27 | extension URLSessionConfiguration: AlamofireExtended {} 28 | extension AlamofireExtension where ExtendedType: URLSessionConfiguration { 29 | /// Alamofire's default configuration. Same as `URLSessionConfiguration.default` but adds Alamofire default 30 | /// `Accept-Language`, `Accept-Encoding`, and `User-Agent` headers. 31 | public static var `default`: URLSessionConfiguration { 32 | let configuration = URLSessionConfiguration.default 33 | configuration.headers = .default 34 | 35 | return configuration 36 | } 37 | 38 | /// `.ephemeral` configuration with Alamofire's default `Accept-Language`, `Accept-Encoding`, and `User-Agent` 39 | /// headers. 40 | public static var ephemeral: URLSessionConfiguration { 41 | let configuration = URLSessionConfiguration.ephemeral 42 | configuration.headers = .default 43 | 44 | return configuration 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /Examples/SwiftPM/LoggieExample/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | UIApplicationSceneManifest 24 | 25 | UIApplicationSupportsMultipleScenes 26 | 27 | UISceneConfigurations 28 | 29 | UIWindowSceneSessionRoleApplication 30 | 31 | 32 | UISceneConfigurationName 33 | Default Configuration 34 | UISceneDelegateClassName 35 | $(PRODUCT_MODULE_NAME).SceneDelegate 36 | UISceneStoryboardFile 37 | Main 38 | 39 | 40 | 41 | 42 | UIApplicationSupportsIndirectInputEvents 43 | 44 | UILaunchStoryboardName 45 | LaunchScreen 46 | UIMainStoryboardFile 47 | Main 48 | UIRequiredDeviceCapabilities 49 | 50 | armv7 51 | 52 | UISupportedInterfaceOrientations 53 | 54 | UIInterfaceOrientationPortrait 55 | UIInterfaceOrientationLandscapeLeft 56 | UIInterfaceOrientationLandscapeRight 57 | 58 | UISupportedInterfaceOrientations~ipad 59 | 60 | UIInterfaceOrientationPortrait 61 | UIInterfaceOrientationPortraitUpsideDown 62 | UIInterfaceOrientationLandscapeLeft 63 | UIInterfaceOrientationLandscapeRight 64 | 65 | 66 | 67 | -------------------------------------------------------------------------------- /Examples/Cocoapods/Pods/Alamofire/Source/StringEncoding+Alamofire.swift: -------------------------------------------------------------------------------- 1 | // 2 | // StringEncoding+Alamofire.swift 3 | // 4 | // Copyright (c) 2020 Alamofire Software Foundation (http://alamofire.org/) 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to deal 8 | // in the Software without restriction, including without limitation the rights 9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | // copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in 14 | // all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | // THE SOFTWARE. 23 | // 24 | 25 | import Foundation 26 | 27 | extension String.Encoding { 28 | /// Creates an encoding from the IANA charset name. 29 | /// 30 | /// - Notes: These mappings match those [provided by CoreFoundation](https://opensource.apple.com/source/CF/CF-476.18/CFStringUtilities.c.auto.html) 31 | /// 32 | /// - Parameter name: IANA charset name. 33 | init?(ianaCharsetName name: String) { 34 | switch name.lowercased() { 35 | case "utf-8": 36 | self = .utf8 37 | case "iso-8859-1": 38 | self = .isoLatin1 39 | case "unicode-1-1", "iso-10646-ucs-2", "utf-16": 40 | self = .utf16 41 | case "utf-16be": 42 | self = .utf16BigEndian 43 | case "utf-16le": 44 | self = .utf16LittleEndian 45 | case "utf-32": 46 | self = .utf32 47 | case "utf-32be": 48 | self = .utf32BigEndian 49 | case "utf-32le": 50 | self = .utf32LittleEndian 51 | default: 52 | return nil 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /Loggie/Classes/Core/LogDetails/Log+ResponseDetails.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Log+ResponseDetails.swift 3 | // Pods 4 | // 5 | // Created by Filip Bec on 15/03/2017. 6 | // 7 | // 8 | 9 | import UIKit 10 | 11 | extension Log { 12 | 13 | var responseDataSource: [LogDetailsSection] { 14 | var sections: [LogDetailsSection] = [] 15 | if let error = error as NSError? { 16 | let errorSection = LogDetailsSection(headerTitle: "Error") 17 | sections.append(errorSection) 18 | 19 | errorSection.items = [ 20 | LogDetailsItem.subtitle("Code", String(error.code)), 21 | LogDetailsItem.subtitle("Description", error.localizedDescription), 22 | LogDetailsItem.subtitle("Failure reason", error.localizedFailureReason), 23 | LogDetailsItem.subtitle("Recovery options", error.localizedRecoveryOptions?.joined(separator: ", ")), 24 | LogDetailsItem.subtitle("Failure", error.localizedRecoverySuggestion) 25 | ] 26 | 27 | let userInfo = LogDetailsSection(headerTitle: "User info") 28 | sections.append(userInfo) 29 | 30 | userInfo.items = error.userInfo.compactMap({ (arg) -> LogDetailsItem? in 31 | #if swift(>=4.0) 32 | let key = arg.key 33 | #else 34 | guard let key = arg.key as? String else { return nil } 35 | #endif 36 | return LogDetailsItem.subtitle(key, String(describing: arg.value)) 37 | }) 38 | 39 | } else { 40 | if let headers = response?.allHeaderFields as? [String: String] { 41 | let headersSection = LogDetailsSection(headerTitle: "Headers") 42 | sections.append(headersSection) 43 | 44 | headersSection.items = headers.map({ (key, value) -> LogDetailsItem in 45 | return LogDetailsItem.subtitle(key, value) 46 | }) 47 | } 48 | 49 | if let body = data { 50 | let contentType = response?.allHeaderFields["Content-Type"] as? String 51 | let item = logDetailsItem(with: body, contentType: contentType) 52 | if let item = item { 53 | sections.append(bodySection(with: item)) 54 | } 55 | } 56 | } 57 | 58 | return sections 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /Examples/Cocoapods/Pods/Target Support Files/Pods-Loggie_Tests/Pods-Loggie_Tests-acknowledgements.markdown: -------------------------------------------------------------------------------- 1 | # Acknowledgements 2 | This application makes use of the following third party libraries: 3 | 4 | ## Alamofire 5 | 6 | Copyright (c) 2014-2022 Alamofire Software Foundation (http://alamofire.org/) 7 | 8 | Permission is hereby granted, free of charge, to any person obtaining a copy 9 | of this software and associated documentation files (the "Software"), to deal 10 | in the Software without restriction, including without limitation the rights 11 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | copies of the Software, and to permit persons to whom the Software is 13 | furnished to do so, subject to the following conditions: 14 | 15 | The above copyright notice and this permission notice shall be included in 16 | all copies or substantial portions of the Software. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | THE SOFTWARE. 25 | 26 | 27 | ## Loggie 28 | 29 | Copyright (c) 2017 Filip Beć 30 | 31 | Permission is hereby granted, free of charge, to any person obtaining a copy 32 | of this software and associated documentation files (the "Software"), to deal 33 | in the Software without restriction, including without limitation the rights 34 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 35 | copies of the Software, and to permit persons to whom the Software is 36 | furnished to do so, subject to the following conditions: 37 | 38 | The above copyright notice and this permission notice shall be included in 39 | all copies or substantial portions of the Software. 40 | 41 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 42 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 43 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 44 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 45 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 46 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 47 | THE SOFTWARE. 48 | 49 | Generated by CocoaPods - https://cocoapods.org 50 | -------------------------------------------------------------------------------- /Examples/Cocoapods/Pods/Target Support Files/Pods-Loggie_Example/Pods-Loggie_Example-acknowledgements.markdown: -------------------------------------------------------------------------------- 1 | # Acknowledgements 2 | This application makes use of the following third party libraries: 3 | 4 | ## Alamofire 5 | 6 | Copyright (c) 2014-2022 Alamofire Software Foundation (http://alamofire.org/) 7 | 8 | Permission is hereby granted, free of charge, to any person obtaining a copy 9 | of this software and associated documentation files (the "Software"), to deal 10 | in the Software without restriction, including without limitation the rights 11 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | copies of the Software, and to permit persons to whom the Software is 13 | furnished to do so, subject to the following conditions: 14 | 15 | The above copyright notice and this permission notice shall be included in 16 | all copies or substantial portions of the Software. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | THE SOFTWARE. 25 | 26 | 27 | ## Loggie 28 | 29 | Copyright (c) 2017 Filip Beć 30 | 31 | Permission is hereby granted, free of charge, to any person obtaining a copy 32 | of this software and associated documentation files (the "Software"), to deal 33 | in the Software without restriction, including without limitation the rights 34 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 35 | copies of the Software, and to permit persons to whom the Software is 36 | furnished to do so, subject to the following conditions: 37 | 38 | The above copyright notice and this permission notice shall be included in 39 | all copies or substantial portions of the Software. 40 | 41 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 42 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 43 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 44 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 45 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 46 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 47 | THE SOFTWARE. 48 | 49 | Generated by CocoaPods - https://cocoapods.org 50 | -------------------------------------------------------------------------------- /Examples/Cocoapods/Pods/Alamofire/Source/AlamofireExtended.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AlamofireExtended.swift 3 | // 4 | // Copyright (c) 2019 Alamofire Software Foundation (http://alamofire.org/) 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to deal 8 | // in the Software without restriction, including without limitation the rights 9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | // copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in 14 | // all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | // THE SOFTWARE. 23 | // 24 | 25 | /// Type that acts as a generic extension point for all `AlamofireExtended` types. 26 | public struct AlamofireExtension { 27 | /// Stores the type or meta-type of any extended type. 28 | public private(set) var type: ExtendedType 29 | 30 | /// Create an instance from the provided value. 31 | /// 32 | /// - Parameter type: Instance being extended. 33 | public init(_ type: ExtendedType) { 34 | self.type = type 35 | } 36 | } 37 | 38 | /// Protocol describing the `af` extension points for Alamofire extended types. 39 | public protocol AlamofireExtended { 40 | /// Type being extended. 41 | associatedtype ExtendedType 42 | 43 | /// Static Alamofire extension point. 44 | static var af: AlamofireExtension.Type { get set } 45 | /// Instance Alamofire extension point. 46 | var af: AlamofireExtension { get set } 47 | } 48 | 49 | extension AlamofireExtended { 50 | /// Static Alamofire extension point. 51 | public static var af: AlamofireExtension.Type { 52 | get { AlamofireExtension.self } 53 | set {} 54 | } 55 | 56 | /// Instance Alamofire extension point. 57 | public var af: AlamofireExtension { 58 | get { AlamofireExtension(self) } 59 | set {} 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /Examples/Cocoapods/Pods/Alamofire/Source/HTTPMethod.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HTTPMethod.swift 3 | // 4 | // Copyright (c) 2014-2018 Alamofire Software Foundation (http://alamofire.org/) 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to deal 8 | // in the Software without restriction, including without limitation the rights 9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | // copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in 14 | // all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | // THE SOFTWARE. 23 | // 24 | 25 | /// Type representing HTTP methods. Raw `String` value is stored and compared case-sensitively, so 26 | /// `HTTPMethod.get != HTTPMethod(rawValue: "get")`. 27 | /// 28 | /// See https://tools.ietf.org/html/rfc7231#section-4.3 29 | public struct HTTPMethod: RawRepresentable, Equatable, Hashable { 30 | /// `CONNECT` method. 31 | public static let connect = HTTPMethod(rawValue: "CONNECT") 32 | /// `DELETE` method. 33 | public static let delete = HTTPMethod(rawValue: "DELETE") 34 | /// `GET` method. 35 | public static let get = HTTPMethod(rawValue: "GET") 36 | /// `HEAD` method. 37 | public static let head = HTTPMethod(rawValue: "HEAD") 38 | /// `OPTIONS` method. 39 | public static let options = HTTPMethod(rawValue: "OPTIONS") 40 | /// `PATCH` method. 41 | public static let patch = HTTPMethod(rawValue: "PATCH") 42 | /// `POST` method. 43 | public static let post = HTTPMethod(rawValue: "POST") 44 | /// `PUT` method. 45 | public static let put = HTTPMethod(rawValue: "PUT") 46 | /// `QUERY` method. 47 | public static let query = HTTPMethod(rawValue: "QUERY") 48 | /// `TRACE` method. 49 | public static let trace = HTTPMethod(rawValue: "TRACE") 50 | 51 | public let rawValue: String 52 | 53 | public init(rawValue: String) { 54 | self.rawValue = rawValue 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /Examples/Cocoapods/Pods/Alamofire/Source/OperationQueue+Alamofire.swift: -------------------------------------------------------------------------------- 1 | // 2 | // OperationQueue+Alamofire.swift 3 | // 4 | // Copyright (c) 2014-2018 Alamofire Software Foundation (http://alamofire.org/) 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to deal 8 | // in the Software without restriction, including without limitation the rights 9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | // copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in 14 | // all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | // THE SOFTWARE. 23 | // 24 | 25 | import Foundation 26 | 27 | extension OperationQueue { 28 | /// Creates an instance using the provided parameters. 29 | /// 30 | /// - Parameters: 31 | /// - qualityOfService: `QualityOfService` to be applied to the queue. `.default` by default. 32 | /// - maxConcurrentOperationCount: Maximum concurrent operations. 33 | /// `OperationQueue.defaultMaxConcurrentOperationCount` by default. 34 | /// - underlyingQueue: Underlying `DispatchQueue`. `nil` by default. 35 | /// - name: Name for the queue. `nil` by default. 36 | /// - startSuspended: Whether the queue starts suspended. `false` by default. 37 | convenience init(qualityOfService: QualityOfService = .default, 38 | maxConcurrentOperationCount: Int = OperationQueue.defaultMaxConcurrentOperationCount, 39 | underlyingQueue: DispatchQueue? = nil, 40 | name: String? = nil, 41 | startSuspended: Bool = false) { 42 | self.init() 43 | self.qualityOfService = qualityOfService 44 | self.maxConcurrentOperationCount = maxConcurrentOperationCount 45 | self.underlyingQueue = underlyingQueue 46 | self.name = name 47 | isSuspended = startSuspended 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /Examples/SwiftPM/Loggie.xcodeproj/xcshareddata/xcschemes/iOSExample.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 43 | 45 | 51 | 52 | 53 | 54 | 60 | 62 | 68 | 69 | 70 | 71 | 73 | 74 | 77 | 78 | 79 | -------------------------------------------------------------------------------- /Examples/Cocoapods/Loggie/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /Examples/Cocoapods/Pods/Alamofire/Source/MultipartUpload.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MultipartUpload.swift 3 | // 4 | // Copyright (c) 2014-2018 Alamofire Software Foundation (http://alamofire.org/) 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to deal 8 | // in the Software without restriction, including without limitation the rights 9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | // copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in 14 | // all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | // THE SOFTWARE. 23 | // 24 | 25 | import Foundation 26 | 27 | /// Internal type which encapsulates a `MultipartFormData` upload. 28 | final class MultipartUpload { 29 | lazy var result = Result { try build() } 30 | 31 | @Protected 32 | private(set) var multipartFormData: MultipartFormData 33 | let encodingMemoryThreshold: UInt64 34 | let request: URLRequestConvertible 35 | let fileManager: FileManager 36 | 37 | init(encodingMemoryThreshold: UInt64, 38 | request: URLRequestConvertible, 39 | multipartFormData: MultipartFormData) { 40 | self.encodingMemoryThreshold = encodingMemoryThreshold 41 | self.request = request 42 | fileManager = multipartFormData.fileManager 43 | self.multipartFormData = multipartFormData 44 | } 45 | 46 | func build() throws -> UploadRequest.Uploadable { 47 | let uploadable: UploadRequest.Uploadable 48 | if $multipartFormData.contentLength < encodingMemoryThreshold { 49 | let data = try $multipartFormData.read { try $0.encode() } 50 | 51 | uploadable = .data(data) 52 | } else { 53 | let tempDirectoryURL = fileManager.temporaryDirectory 54 | let directoryURL = tempDirectoryURL.appendingPathComponent("org.alamofire.manager/multipart.form.data") 55 | let fileName = UUID().uuidString 56 | let fileURL = directoryURL.appendingPathComponent(fileName) 57 | 58 | try fileManager.createDirectory(at: directoryURL, withIntermediateDirectories: true, attributes: nil) 59 | 60 | do { 61 | try $multipartFormData.read { try $0.writeEncodedData(to: fileURL) } 62 | } catch { 63 | // Cleanup after attempted write if it fails. 64 | try? fileManager.removeItem(at: fileURL) 65 | throw error 66 | } 67 | 68 | uploadable = .file(fileURL, shouldRemove: true) 69 | } 70 | 71 | return uploadable 72 | } 73 | } 74 | 75 | extension MultipartUpload: UploadConvertible { 76 | func asURLRequest() throws -> URLRequest { 77 | var urlRequest = try request.asURLRequest() 78 | 79 | $multipartFormData.read { multipartFormData in 80 | urlRequest.headers.add(.contentType(multipartFormData.contentType)) 81 | } 82 | 83 | return urlRequest 84 | } 85 | 86 | func createUploadable() throws -> UploadRequest.Uploadable { 87 | try result.get() 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /Examples/Cocoapods/Pods/Target Support Files/Pods-Loggie_Tests/Pods-Loggie_Tests-acknowledgements.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreferenceSpecifiers 6 | 7 | 8 | FooterText 9 | This application makes use of the following third party libraries: 10 | Title 11 | Acknowledgements 12 | Type 13 | PSGroupSpecifier 14 | 15 | 16 | FooterText 17 | Copyright (c) 2014-2022 Alamofire Software Foundation (http://alamofire.org/) 18 | 19 | Permission is hereby granted, free of charge, to any person obtaining a copy 20 | of this software and associated documentation files (the "Software"), to deal 21 | in the Software without restriction, including without limitation the rights 22 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 23 | copies of the Software, and to permit persons to whom the Software is 24 | furnished to do so, subject to the following conditions: 25 | 26 | The above copyright notice and this permission notice shall be included in 27 | all copies or substantial portions of the Software. 28 | 29 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 30 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 31 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 32 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 33 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 34 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 35 | THE SOFTWARE. 36 | 37 | License 38 | MIT 39 | Title 40 | Alamofire 41 | Type 42 | PSGroupSpecifier 43 | 44 | 45 | FooterText 46 | Copyright (c) 2017 Filip Beć <filip.bec@gmail.com> 47 | 48 | Permission is hereby granted, free of charge, to any person obtaining a copy 49 | of this software and associated documentation files (the "Software"), to deal 50 | in the Software without restriction, including without limitation the rights 51 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 52 | copies of the Software, and to permit persons to whom the Software is 53 | furnished to do so, subject to the following conditions: 54 | 55 | The above copyright notice and this permission notice shall be included in 56 | all copies or substantial portions of the Software. 57 | 58 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 59 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 60 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 61 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 62 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 63 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 64 | THE SOFTWARE. 65 | 66 | License 67 | MIT 68 | Title 69 | Loggie 70 | Type 71 | PSGroupSpecifier 72 | 73 | 74 | FooterText 75 | Generated by CocoaPods - https://cocoapods.org 76 | Title 77 | 78 | Type 79 | PSGroupSpecifier 80 | 81 | 82 | StringsTable 83 | Acknowledgements 84 | Title 85 | Acknowledgements 86 | 87 | 88 | -------------------------------------------------------------------------------- /Examples/Cocoapods/Loggie/Base.lproj/LaunchScreen.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 20 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /Examples/Cocoapods/Pods/Target Support Files/Pods-Loggie_Example/Pods-Loggie_Example-acknowledgements.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreferenceSpecifiers 6 | 7 | 8 | FooterText 9 | This application makes use of the following third party libraries: 10 | Title 11 | Acknowledgements 12 | Type 13 | PSGroupSpecifier 14 | 15 | 16 | FooterText 17 | Copyright (c) 2014-2022 Alamofire Software Foundation (http://alamofire.org/) 18 | 19 | Permission is hereby granted, free of charge, to any person obtaining a copy 20 | of this software and associated documentation files (the "Software"), to deal 21 | in the Software without restriction, including without limitation the rights 22 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 23 | copies of the Software, and to permit persons to whom the Software is 24 | furnished to do so, subject to the following conditions: 25 | 26 | The above copyright notice and this permission notice shall be included in 27 | all copies or substantial portions of the Software. 28 | 29 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 30 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 31 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 32 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 33 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 34 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 35 | THE SOFTWARE. 36 | 37 | License 38 | MIT 39 | Title 40 | Alamofire 41 | Type 42 | PSGroupSpecifier 43 | 44 | 45 | FooterText 46 | Copyright (c) 2017 Filip Beć <filip.bec@gmail.com> 47 | 48 | Permission is hereby granted, free of charge, to any person obtaining a copy 49 | of this software and associated documentation files (the "Software"), to deal 50 | in the Software without restriction, including without limitation the rights 51 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 52 | copies of the Software, and to permit persons to whom the Software is 53 | furnished to do so, subject to the following conditions: 54 | 55 | The above copyright notice and this permission notice shall be included in 56 | all copies or substantial portions of the Software. 57 | 58 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 59 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 60 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 61 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 62 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 63 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 64 | THE SOFTWARE. 65 | 66 | License 67 | MIT 68 | Title 69 | Loggie 70 | Type 71 | PSGroupSpecifier 72 | 73 | 74 | FooterText 75 | Generated by CocoaPods - https://cocoapods.org 76 | Title 77 | 78 | Type 79 | PSGroupSpecifier 80 | 81 | 82 | StringsTable 83 | Acknowledgements 84 | Title 85 | Acknowledgements 86 | 87 | 88 | -------------------------------------------------------------------------------- /Loggie/Classes/URLSession/LoggieURLProtocol.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LoggieURLProtocol.swift 3 | // Pods 4 | // 5 | // Created by Filip Bec on 12/03/2017. 6 | // 7 | // 8 | 9 | import UIKit 10 | 11 | public class LoggieURLProtocol: URLProtocol { 12 | 13 | // MARK: - Properties 14 | 15 | private static let HeaderKey = "Loggie" 16 | 17 | private var dataTask: URLSessionDataTask? 18 | 19 | // MARK: - URLProtocol Overrides 20 | 21 | public override class func canInit(with request: URLRequest) -> Bool { 22 | return property(forKey: LoggieURLProtocol.HeaderKey, in: request) == nil 23 | } 24 | 25 | public override class func canonicalRequest(for request: URLRequest) -> URLRequest { 26 | return request 27 | } 28 | 29 | public override class func requestIsCacheEquivalent(_ a: URLRequest, to b: URLRequest) -> Bool { 30 | return super.requestIsCacheEquivalent(a, to: b) 31 | } 32 | 33 | public override func startLoading() { 34 | var request = LoggieURLProtocol.canonicalRequest(for: self.request) 35 | 36 | if let mutableRequest = (request as NSURLRequest).mutableCopy() as? NSMutableURLRequest { 37 | LoggieURLProtocol.setProperty(true, forKey: LoggieURLProtocol.HeaderKey, in: mutableRequest) 38 | request = mutableRequest as URLRequest 39 | } 40 | 41 | let log = Log(request: request) 42 | log.startTime = Date() 43 | 44 | let session: URLSession = LoggieManager.shared.urlSessionFor(urlRequest: request) 45 | 46 | dataTask = session.dataTask(with: request, completionHandler: { [weak self] (data, response, error) in 47 | guard let `self` = self else { return } 48 | 49 | log.endTime = Date() 50 | log.error = error 51 | log.data = data 52 | log.response = response as? HTTPURLResponse 53 | LoggieManager.shared.add(log) 54 | 55 | guard let client = self.client else { return } 56 | 57 | if let error = error { 58 | client.urlProtocol(self, didFailWithError: error) 59 | } else if let response = response { 60 | client.urlProtocol(self, didReceive: response, cacheStoragePolicy: .notAllowed) 61 | } 62 | 63 | if let data = data { 64 | client.urlProtocol(self, didLoad: data) 65 | } 66 | 67 | client.urlProtocolDidFinishLoading(self) 68 | }) 69 | dataTask?.resume() 70 | } 71 | 72 | public override func stopLoading() { 73 | dataTask?.cancel() 74 | dataTask = nil 75 | } 76 | } 77 | 78 | private extension LoggieManager { 79 | 80 | func urlSessionFor(urlRequest: URLRequest) -> URLSession { 81 | return delegate?.loggie(self, urlSessionForURLRequest: urlRequest) ?? defaultURLSession 82 | } 83 | } 84 | 85 | // MARK: - Loggie + URLSessionConfiguration 86 | 87 | public extension URLSessionConfiguration { 88 | 89 | /// `URLSessionConfiguration.default`, modified in a way that its `protocolClasses` 90 | /// contains `LoggieURLProtocol` at the first index. 91 | @objc(loggieSessionConfiguration) 92 | static var loggie: URLSessionConfiguration { 93 | return loggie(combinedWith: .default) 94 | } 95 | 96 | /// Returns the modified session configuration provided in the method, modified in a way that its `protocolClasses` 97 | /// contains `LoggieURLProtocol` at the first index. 98 | /// 99 | /// If you just want to use simple form of configuration, use the provided `loggie` configuration. 100 | @objc(loggieSessionConfigurationCombinedWith:) 101 | static func loggie(combinedWith configuration: URLSessionConfiguration) -> URLSessionConfiguration { 102 | if configuration.protocolClasses == nil { 103 | configuration.protocolClasses = [LoggieURLProtocol.self] 104 | } else { 105 | configuration.protocolClasses?.insert(LoggieURLProtocol.self, at: 0) 106 | } 107 | return configuration 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Loggie 2 | 3 | > __IMPORTANT NOTE:__ 4 | > 5 | > Loggie is __deprecated__ and __no longer maintained__. We suggest migrating to [Pulse](https://github.com/kean/pulse) as an alternative. Pulse is more powerful and flexible then Loggie. 6 | 7 | [![Build Status](https://github.com/infinum/ios-loggie/actions/workflows/ci.yml/badge.svg)](https://github.com/infinum/ios-loggie/actions/workflows/ci.yml) 8 | [![Version](https://img.shields.io/cocoapods/v/Loggie.svg?style=flat)](http://cocoapods.org/pods/Loggie) 9 | [![License](https://img.shields.io/cocoapods/l/Loggie.svg?style=flat)](http://cocoapods.org/pods/Loggie) 10 | [![Swift Package Manager](https://img.shields.io/badge/swift%20package%20manager-compatible-brightgreen.svg)](https://github.com/apple/swift-package-manager) 11 | [![Platform](https://img.shields.io/cocoapods/p/Loggie.svg?style=flat)](http://cocoapods.org/pods/Loggie) 12 | 13 |

14 | Loggie 15 |

16 | 17 | ## About 18 | 19 | **Loggie** is a simple in-app network logging library that gives the developers and the QA testers a possibility to log network. The library is handy for easily accessible list of network trafic. 20 | 21 | ## Requirements 22 | 23 | - Xcode 10 24 | - iOS 11 25 | 26 | ## Installation 27 | 28 | ### CocoaPods 29 | Loggie is available through [CocoaPods](https://cocoapods.org). To install 30 | it, simply add the following line to your Podfile: 31 | 32 | ```ruby 33 | pod 'Loggie' 34 | ``` 35 | 36 | By default, network debugging will be available for the `URLSession`. If you want to use Loggie with Alamofire, please use: 37 | 38 | ```ruby 39 | pod 'Loggie/Alamofire' 40 | ``` 41 | 42 | ### Swift Package Manager 43 | If you are using SPM for your dependency manager, add this to the dependencies in your `Package.swift` file: 44 | ```swift 45 | dependencies: [ 46 | .package(url: "https://github.com/infinum/ios-loggie.git") 47 | ] 48 | ``` 49 | 50 | ## Example 51 | 52 | To run the example project, clone the repo, and run `pod install` from the Examples/Cocoapods directory first. 53 | 54 | ## Usage 55 | 56 | **1. Register custom `LoggieURLProtocol` in the `application:didFinishLaunchingWithOptions` method:** 57 | 58 | ```swift 59 | // Swift 60 | URLProtocol.registerClass(LoggieURLProtocol.self) 61 | ``` 62 | 63 | ```objective-c 64 | // Objective-C 65 | [NSURLProtocol registerClass:[LoggieURLProtocol class]]; 66 | ``` 67 | 68 | **2. If you use `NSURLSession` (or AFNetworking/Alamofire) make sure that you use `loggieSessionConfiguration`:** 69 | 70 | ```swift 71 | // Swift 72 | URLSession(configuration: URLSessionConfiguration.loggie) 73 | ``` 74 | 75 | ```objective-c 76 | // Objective-C 77 | [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration loggieSessionConfiguration]]; 78 | ``` 79 | 80 | **3. At the point where you want to display network logs, you can just put the following line:** 81 | 82 | ```swift 83 | // Swift 84 | LoggieManager.shared.showLogs(from: viewController) 85 | ``` 86 | 87 | ```objective-c 88 | // Objective-C 89 | [[LoggieManager sharedManager] showLogsFromViewController:viewController filter:nil]; 90 | ``` 91 | 92 | **You can create custom output or UI to show network logs. To get an array of all network logs just call:** 93 | 94 | ```swift 95 | // Swift 96 | let logs = LoggieManager.shared.logs 97 | ``` 98 | 99 | ```objective-c 100 | // Objective-C 101 | NSArray *array = [[LoggieManager sharedManager] logs]; 102 | ``` 103 | 104 | If you would like to receive notifications when new logs are added to the list, your app can observe `LoggieDidUpdateLogs` notification. 105 | 106 | ## Important 107 | 108 | Please make sure that `LogieURLProtocol` and `loggieSessionConfiguration` are not used in production builds. 109 | 110 | ## Author 111 | 112 | Filip Beć, filip.bec@gmail.com 113 | 114 | ## Credits 115 | 116 | Maintained and sponsored by [Infinum](http://www.infinum.co). 117 | 118 | ![Infinum logo](https://cloud.githubusercontent.com/assets/1422973/24369980/9c36b0a6-12da-11e7-898a-b711ed7ca52f.png) 119 | 120 | ## License 121 | 122 | Loggie is available under the MIT license. See the `LICENSE` file for more information. 123 | -------------------------------------------------------------------------------- /Examples/Cocoapods/Pods/Alamofire/Source/URLConvertible+URLRequestConvertible.swift: -------------------------------------------------------------------------------- 1 | // 2 | // URLConvertible+URLRequestConvertible.swift 3 | // 4 | // Copyright (c) 2014-2018 Alamofire Software Foundation (http://alamofire.org/) 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to deal 8 | // in the Software without restriction, including without limitation the rights 9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | // copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in 14 | // all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | // THE SOFTWARE. 23 | // 24 | 25 | import Foundation 26 | 27 | /// Types adopting the `URLConvertible` protocol can be used to construct `URL`s, which can then be used to construct 28 | /// `URLRequests`. 29 | public protocol URLConvertible { 30 | /// Returns a `URL` from the conforming instance or throws. 31 | /// 32 | /// - Returns: The `URL` created from the instance. 33 | /// - Throws: Any error thrown while creating the `URL`. 34 | func asURL() throws -> URL 35 | } 36 | 37 | extension String: URLConvertible { 38 | /// Returns a `URL` if `self` can be used to initialize a `URL` instance, otherwise throws. 39 | /// 40 | /// - Returns: The `URL` initialized with `self`. 41 | /// - Throws: An `AFError.invalidURL` instance. 42 | public func asURL() throws -> URL { 43 | guard let url = URL(string: self) else { throw AFError.invalidURL(url: self) } 44 | 45 | return url 46 | } 47 | } 48 | 49 | extension URL: URLConvertible { 50 | /// Returns `self`. 51 | public func asURL() throws -> URL { self } 52 | } 53 | 54 | extension URLComponents: URLConvertible { 55 | /// Returns a `URL` if the `self`'s `url` is not nil, otherwise throws. 56 | /// 57 | /// - Returns: The `URL` from the `url` property. 58 | /// - Throws: An `AFError.invalidURL` instance. 59 | public func asURL() throws -> URL { 60 | guard let url = url else { throw AFError.invalidURL(url: self) } 61 | 62 | return url 63 | } 64 | } 65 | 66 | // MARK: - 67 | 68 | /// Types adopting the `URLRequestConvertible` protocol can be used to safely construct `URLRequest`s. 69 | public protocol URLRequestConvertible { 70 | /// Returns a `URLRequest` or throws if an `Error` was encountered. 71 | /// 72 | /// - Returns: A `URLRequest`. 73 | /// - Throws: Any error thrown while constructing the `URLRequest`. 74 | func asURLRequest() throws -> URLRequest 75 | } 76 | 77 | extension URLRequestConvertible { 78 | /// The `URLRequest` returned by discarding any `Error` encountered. 79 | public var urlRequest: URLRequest? { try? asURLRequest() } 80 | } 81 | 82 | extension URLRequest: URLRequestConvertible { 83 | /// Returns `self`. 84 | public func asURLRequest() throws -> URLRequest { self } 85 | } 86 | 87 | // MARK: - 88 | 89 | extension URLRequest { 90 | /// Creates an instance with the specified `url`, `method`, and `headers`. 91 | /// 92 | /// - Parameters: 93 | /// - url: The `URLConvertible` value. 94 | /// - method: The `HTTPMethod`. 95 | /// - headers: The `HTTPHeaders`, `nil` by default. 96 | /// - Throws: Any error thrown while converting the `URLConvertible` to a `URL`. 97 | public init(url: URLConvertible, method: HTTPMethod, headers: HTTPHeaders? = nil) throws { 98 | let url = try url.asURL() 99 | 100 | self.init(url: url) 101 | 102 | httpMethod = method.rawValue 103 | allHTTPHeaderFields = headers?.dictionary 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /Examples/Cocoapods/Loggie.xcworkspace/xcshareddata/xcschemes/Loggie-Example.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 29 | 35 | 36 | 37 | 38 | 39 | 44 | 45 | 51 | 52 | 53 | 54 | 56 | 62 | 63 | 64 | 65 | 66 | 76 | 78 | 84 | 85 | 86 | 87 | 93 | 95 | 101 | 102 | 103 | 104 | 106 | 107 | 110 | 111 | 112 | -------------------------------------------------------------------------------- /Loggie/Classes/Core/LogList/LogListTableViewController.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | -------------------------------------------------------------------------------- /Loggie/Classes/Core/LoggieManager.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LoggieManager.swift 3 | // Pods 4 | // 5 | // Created by Filip Bec on 12/03/2017. 6 | // 7 | // 8 | 9 | import UIKit 10 | import SwiftUI 11 | 12 | extension Notification.Name { 13 | static let LoggieDidUpdateLogs = Notification.Name("com.infinum.loggie-did-update-logs") 14 | } 15 | 16 | @objc 17 | public protocol LoggieDelegate { 18 | 19 | /// Asks a delegate to provide a `URLRequest`-specific `URLSession`, 20 | /// using which a `Loggie` will be able to perform a `URLSessionTask`. 21 | /// 22 | /// Most of the apps tipically manage networking logic in one class, such as `Alamofire`'s `SessionManager`, 23 | /// which is ideal place for implementing this protocol. 24 | /// If you don't conform to this protocol & implement this method, `Loggie` will create (& retain) it's own `URLSession`, 25 | /// instantiated with a `URLSessionConfiguration.default`. 26 | /// 27 | /// - Parameters: 28 | /// - loggie: An instance of a `LoggieManager` that is asking for a `URLSession` object. 29 | /// - urlRequest: `URLRequest` for which which a `Loggie` needs a `URLSession` which will execute it. 30 | func loggie(_ loggie: LoggieManager, urlSessionForURLRequest urlRequest: URLRequest) -> URLSession 31 | } 32 | 33 | internal protocol LogsDataSourceDelegate: AnyObject { 34 | func clearLogs() 35 | } 36 | 37 | @objcMembers 38 | public final class LoggieManager: NSObject, LogsDataSourceDelegate { 39 | 40 | // MARK: - Properties 41 | 42 | /// A **serial** queue, on which Loggie is appending new logs & posting a notification that `logs` have changed. 43 | private let logsHandlingQueue: DispatchQueue = .init(label: "com.infinum.loggie-logs-handling-queue") 44 | 45 | /// An instance of `URLSession` which Loggie is using if there's no delegate to provide his own session. 46 | /// 47 | /// Used only for `LoggieURLProtocol`. 48 | lazy var defaultURLSession: URLSession = .init(configuration: .default) 49 | 50 | /// A delegate used to provide a `URLSession` instance, using which Loggie will perform data requests. 51 | /// 52 | /// This delegate is used **only** when you're working with `LoggieURLProtocol`, through `Loggie/URLSession` pod. 53 | /// Assigning a value to this property when working with `EventMonitor` has no impact & isn't used at all, 54 | /// but won't affect the usage anyhow. 55 | /// 56 | /// You should assign this delegate as soon as the app starts, so Loggie could use your, instead of Loggie's default, `URLSession` instance. 57 | public weak var delegate: LoggieDelegate? 58 | 59 | /// An array containing all `Log`s either being performed by Loggie through `LoggieURLProtocol`, 60 | /// or observer by loggie through `EventMonitor`. 61 | private var _logs: [Log] = [] 62 | 63 | public var logs: [Log] { 64 | logsHandlingQueue.sync { _logs } 65 | } 66 | 67 | @objc(sharedManager) 68 | public static let shared = LoggieManager() 69 | 70 | @discardableResult 71 | @objc(showLogsFromViewController:filter:) 72 | public func showLogs(from viewController: UIViewController, filter: ((Log) -> Bool)? = nil) -> UINavigationController { 73 | let navigationController = loggieNavigationController(filter: filter) 74 | viewController.present(navigationController, animated: true, completion: nil) 75 | return navigationController 76 | } 77 | 78 | public func showLogs(filter: ((Log) -> Bool)? = nil) -> UINavigationController { 79 | let navigationController = loggieNavigationController(filter: filter) 80 | return navigationController 81 | } 82 | 83 | func loggieNavigationController(filter: ((Log) -> Bool)?) -> UINavigationController { 84 | guard let vc: LogListTableViewController = UIStoryboard(name: "LogListTableViewController", bundle: .loggie) 85 | .instantiateViewController(withIdentifier: "LogListTableViewController") 86 | as? LogListTableViewController else { return UINavigationController() } 87 | vc.filter = filter 88 | vc.logsDataSourceDelegate = self 89 | 90 | let navigationController = UINavigationController(rootViewController: vc) 91 | navigationController.navigationBar.isTranslucent = false 92 | navigationController.navigationBar.backgroundColor = .white 93 | 94 | return navigationController 95 | } 96 | 97 | public func add(_ log: Log) { 98 | // Avoid changing logs array from multiple threads (race condition) 99 | logsHandlingQueue.async { [weak self] in 100 | guard let self = self else { return } 101 | self._logs.append(log) 102 | NotificationCenter.default.post(name: .LoggieDidUpdateLogs, object: nil) 103 | } 104 | } 105 | 106 | func clearLogs() { 107 | // Avoid changing logs array from multiple threads (race condition) 108 | logsHandlingQueue.async { [weak self] in 109 | guard let self = self else { return } 110 | self._logs = [] 111 | } 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /Examples/Cocoapods/Pods/Alamofire/Source/Result+Alamofire.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Result+Alamofire.swift 3 | // 4 | // Copyright (c) 2019 Alamofire Software Foundation (http://alamofire.org/) 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to deal 8 | // in the Software without restriction, including without limitation the rights 9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | // copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in 14 | // all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | // THE SOFTWARE. 23 | // 24 | 25 | import Foundation 26 | 27 | /// Default type of `Result` returned by Alamofire, with an `AFError` `Failure` type. 28 | public typealias AFResult = Result 29 | 30 | // MARK: - Internal APIs 31 | 32 | extension Result { 33 | /// Returns whether the instance is `.success`. 34 | var isSuccess: Bool { 35 | guard case .success = self else { return false } 36 | return true 37 | } 38 | 39 | /// Returns whether the instance is `.failure`. 40 | var isFailure: Bool { 41 | !isSuccess 42 | } 43 | 44 | /// Returns the associated value if the result is a success, `nil` otherwise. 45 | var success: Success? { 46 | guard case let .success(value) = self else { return nil } 47 | return value 48 | } 49 | 50 | /// Returns the associated error value if the result is a failure, `nil` otherwise. 51 | var failure: Failure? { 52 | guard case let .failure(error) = self else { return nil } 53 | return error 54 | } 55 | 56 | /// Initializes a `Result` from value or error. Returns `.failure` if the error is non-nil, `.success` otherwise. 57 | /// 58 | /// - Parameters: 59 | /// - value: A value. 60 | /// - error: An `Error`. 61 | init(value: Success, error: Failure?) { 62 | if let error = error { 63 | self = .failure(error) 64 | } else { 65 | self = .success(value) 66 | } 67 | } 68 | 69 | /// Evaluates the specified closure when the `Result` is a success, passing the unwrapped value as a parameter. 70 | /// 71 | /// Use the `tryMap` method with a closure that may throw an error. For example: 72 | /// 73 | /// let possibleData: Result = .success(Data(...)) 74 | /// let possibleObject = possibleData.tryMap { 75 | /// try JSONSerialization.jsonObject(with: $0) 76 | /// } 77 | /// 78 | /// - parameter transform: A closure that takes the success value of the instance. 79 | /// 80 | /// - returns: A `Result` containing the result of the given closure. If this instance is a failure, returns the 81 | /// same failure. 82 | func tryMap(_ transform: (Success) throws -> NewSuccess) -> Result { 83 | switch self { 84 | case let .success(value): 85 | do { 86 | return try .success(transform(value)) 87 | } catch { 88 | return .failure(error) 89 | } 90 | case let .failure(error): 91 | return .failure(error) 92 | } 93 | } 94 | 95 | /// Evaluates the specified closure when the `Result` is a failure, passing the unwrapped error as a parameter. 96 | /// 97 | /// Use the `tryMapError` function with a closure that may throw an error. For example: 98 | /// 99 | /// let possibleData: Result = .success(Data(...)) 100 | /// let possibleObject = possibleData.tryMapError { 101 | /// try someFailableFunction(taking: $0) 102 | /// } 103 | /// 104 | /// - Parameter transform: A throwing closure that takes the error of the instance. 105 | /// 106 | /// - Returns: A `Result` instance containing the result of the transform. If this instance is a success, returns 107 | /// the same success. 108 | func tryMapError(_ transform: (Failure) throws -> NewFailure) -> Result { 109 | switch self { 110 | case let .failure(error): 111 | do { 112 | return try .failure(transform(error)) 113 | } catch { 114 | return .failure(error) 115 | } 116 | case let .success(value): 117 | return .success(value) 118 | } 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /Loggie/Classes/Core/LogList/LogListTableViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LogListTableViewController.swift 3 | // Pods 4 | // 5 | // Created by Filip Bec on 12/03/2017. 6 | // 7 | // 8 | 9 | import UIKit 10 | 11 | public class LogListTableViewController: UITableViewController { 12 | 13 | private static let cellReuseIdentifier = "cell" 14 | internal var logsDataSourceDelegate: LogsDataSourceDelegate! 15 | 16 | @IBOutlet private var searchBar: UISearchBar! 17 | private var logs = [Log]() { 18 | didSet { 19 | tableView.reloadData() 20 | } 21 | } 22 | public var filter: ((Log) -> Bool)? = nil { 23 | didSet { 24 | updateLogs() 25 | } 26 | } 27 | 28 | // MARK: - Lifecycle 29 | 30 | override public func viewDidLoad() { 31 | super.viewDidLoad() 32 | title = "Logs" 33 | setupTableView() 34 | navigationItem.leftBarButtonItem = UIBarButtonItem( 35 | barButtonSystemItem: .stop, 36 | target: self, 37 | action: #selector(closeButtonActionHandler) 38 | ) 39 | 40 | NotificationCenter.default.addObserver( 41 | self, 42 | selector: #selector(loggieDidUpdateLogs), 43 | name: .LoggieDidUpdateLogs, 44 | object: nil 45 | ) 46 | updateLogs() 47 | } 48 | 49 | private func setupTableView() { 50 | tableView.rowHeight = 70 51 | tableView.tableFooterView = UIView(frame: CGRect.zero) 52 | tableView.registerCell( 53 | type: LogListTableViewCell.self, 54 | identifier: LogListTableViewController.cellReuseIdentifier 55 | ) 56 | } 57 | 58 | @IBAction func clearAction() { 59 | logsDataSourceDelegate.clearLogs() 60 | logs = [] 61 | } 62 | } 63 | 64 | // MARK: - Table view data source 65 | 66 | extension LogListTableViewController { 67 | 68 | override public func numberOfSections(in tableView: UITableView) -> Int { 69 | return 1 70 | } 71 | 72 | override public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 73 | return logs.count 74 | } 75 | 76 | override public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 77 | let cell = tableView.dequeueReusableCell( 78 | withIdentifier: LogListTableViewController.cellReuseIdentifier, 79 | for: indexPath 80 | ) as! LogListTableViewCell 81 | 82 | cell.configure(with: logs[indexPath.row]) 83 | return cell 84 | } 85 | } 86 | 87 | // MARK: - Table view delegate 88 | 89 | extension LogListTableViewController { 90 | 91 | override public func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { 92 | showLogDetails(with: logs[indexPath.row]) 93 | } 94 | 95 | public override func scrollViewDidScroll(_ scrollView: UIScrollView) { 96 | if searchBar.isFirstResponder { 97 | searchBar.resignFirstResponder() 98 | } 99 | } 100 | } 101 | 102 | // MARK: - Search bar delegate 103 | 104 | extension LogListTableViewController: UISearchBarDelegate { 105 | 106 | public func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) { 107 | updateLogs() 108 | } 109 | 110 | public func searchBarSearchButtonClicked(_ searchBar: UISearchBar) { 111 | searchBar.resignFirstResponder() 112 | } 113 | } 114 | 115 | // MARK: - Private 116 | 117 | extension LogListTableViewController { 118 | 119 | private func updateLogs() { 120 | var _logs: [Log] = LoggieManager.shared.logs.reversed() 121 | 122 | if let filter = filter { 123 | _logs = _logs.filter(filter) 124 | } 125 | 126 | if let text = searchBar?.text, text.isEmpty == false { 127 | _logs = _logs.filter(filter(by: text)) 128 | } 129 | 130 | logs = _logs 131 | } 132 | 133 | private func filter(by searchText: String) -> (Log) -> Bool { 134 | return { (log) in 135 | log.searchKeywords.contains(where: { 136 | $0.range(of: searchText, options: .caseInsensitive) != nil 137 | }) 138 | } 139 | } 140 | 141 | private func showLogDetails(with log: Log) { 142 | let storyboard = UIStoryboard().storyboard(for: UIStoryboard.LoggieIdentifier.logDetails) 143 | guard let viewController = storyboard.instantiateInitialViewController() as? LogDetailsViewController else { return } 144 | viewController.log = log 145 | navigationController?.pushViewController(viewController, animated: true) 146 | } 147 | 148 | @objc private func loggieDidUpdateLogs() { 149 | // This function is not called from the main thread. 150 | // because of that we need to Dispatch here to update the UI on the main thread. 151 | DispatchQueue.main.async { [weak self] in 152 | guard let self = self else { return } 153 | self.updateLogs() 154 | } 155 | } 156 | 157 | @objc private func closeButtonActionHandler() { 158 | dismiss(animated: true, completion: nil) 159 | } 160 | } 161 | -------------------------------------------------------------------------------- /Examples/Cocoapods/Pods/Alamofire/Source/CachedResponseHandler.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CachedResponseHandler.swift 3 | // 4 | // Copyright (c) 2019 Alamofire Software Foundation (http://alamofire.org/) 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to deal 8 | // in the Software without restriction, including without limitation the rights 9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | // copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in 14 | // all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | // THE SOFTWARE. 23 | // 24 | 25 | import Foundation 26 | 27 | /// A type that handles whether the data task should store the HTTP response in the cache. 28 | public protocol CachedResponseHandler { 29 | /// Determines whether the HTTP response should be stored in the cache. 30 | /// 31 | /// The `completion` closure should be passed one of three possible options: 32 | /// 33 | /// 1. The cached response provided by the server (this is the most common use case). 34 | /// 2. A modified version of the cached response (you may want to modify it in some way before caching). 35 | /// 3. A `nil` value to prevent the cached response from being stored in the cache. 36 | /// 37 | /// - Parameters: 38 | /// - task: The data task whose request resulted in the cached response. 39 | /// - response: The cached response to potentially store in the cache. 40 | /// - completion: The closure to execute containing cached response, a modified response, or `nil`. 41 | func dataTask(_ task: URLSessionDataTask, 42 | willCacheResponse response: CachedURLResponse, 43 | completion: @escaping (CachedURLResponse?) -> Void) 44 | } 45 | 46 | // MARK: - 47 | 48 | /// `ResponseCacher` is a convenience `CachedResponseHandler` making it easy to cache, not cache, or modify a cached 49 | /// response. 50 | public struct ResponseCacher { 51 | /// Defines the behavior of the `ResponseCacher` type. 52 | public enum Behavior { 53 | /// Stores the cached response in the cache. 54 | case cache 55 | /// Prevents the cached response from being stored in the cache. 56 | case doNotCache 57 | /// Modifies the cached response before storing it in the cache. 58 | case modify((URLSessionDataTask, CachedURLResponse) -> CachedURLResponse?) 59 | } 60 | 61 | /// Returns a `ResponseCacher` with a `.cache` `Behavior`. 62 | public static let cache = ResponseCacher(behavior: .cache) 63 | /// Returns a `ResponseCacher` with a `.doNotCache` `Behavior`. 64 | public static let doNotCache = ResponseCacher(behavior: .doNotCache) 65 | 66 | /// The `Behavior` of the `ResponseCacher`. 67 | public let behavior: Behavior 68 | 69 | /// Creates a `ResponseCacher` instance from the `Behavior`. 70 | /// 71 | /// - Parameter behavior: The `Behavior`. 72 | public init(behavior: Behavior) { 73 | self.behavior = behavior 74 | } 75 | } 76 | 77 | extension ResponseCacher: CachedResponseHandler { 78 | public func dataTask(_ task: URLSessionDataTask, 79 | willCacheResponse response: CachedURLResponse, 80 | completion: @escaping (CachedURLResponse?) -> Void) { 81 | switch behavior { 82 | case .cache: 83 | completion(response) 84 | case .doNotCache: 85 | completion(nil) 86 | case let .modify(closure): 87 | let response = closure(task, response) 88 | completion(response) 89 | } 90 | } 91 | } 92 | 93 | #if swift(>=5.5) 94 | extension CachedResponseHandler where Self == ResponseCacher { 95 | /// Provides a `ResponseCacher` which caches the response, if allowed. Equivalent to `ResponseCacher.cache`. 96 | public static var cache: ResponseCacher { .cache } 97 | 98 | /// Provides a `ResponseCacher` which does not cache the response. Equivalent to `ResponseCacher.doNotCache`. 99 | public static var doNotCache: ResponseCacher { .doNotCache } 100 | 101 | /// Creates a `ResponseCacher` which modifies the proposed `CachedURLResponse` using the provided closure. 102 | /// 103 | /// - Parameter closure: Closure used to modify the `CachedURLResponse`. 104 | /// - Returns: The `ResponseCacher`. 105 | public static func modify(using closure: @escaping ((URLSessionDataTask, CachedURLResponse) -> CachedURLResponse?)) -> ResponseCacher { 106 | ResponseCacher(behavior: .modify(closure)) 107 | } 108 | } 109 | #endif 110 | -------------------------------------------------------------------------------- /Examples/Cocoapods/Pods/Alamofire/Source/RedirectHandler.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RedirectHandler.swift 3 | // 4 | // Copyright (c) 2014-2018 Alamofire Software Foundation (http://alamofire.org/) 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to deal 8 | // in the Software without restriction, including without limitation the rights 9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | // copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in 14 | // all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | // THE SOFTWARE. 23 | // 24 | 25 | import Foundation 26 | 27 | /// A type that handles how an HTTP redirect response from a remote server should be redirected to the new request. 28 | public protocol RedirectHandler { 29 | /// Determines how the HTTP redirect response should be redirected to the new request. 30 | /// 31 | /// The `completion` closure should be passed one of three possible options: 32 | /// 33 | /// 1. The new request specified by the redirect (this is the most common use case). 34 | /// 2. A modified version of the new request (you may want to route it somewhere else). 35 | /// 3. A `nil` value to deny the redirect request and return the body of the redirect response. 36 | /// 37 | /// - Parameters: 38 | /// - task: The `URLSessionTask` whose request resulted in a redirect. 39 | /// - request: The `URLRequest` to the new location specified by the redirect response. 40 | /// - response: The `HTTPURLResponse` containing the server's response to the original request. 41 | /// - completion: The closure to execute containing the new `URLRequest`, a modified `URLRequest`, or `nil`. 42 | func task(_ task: URLSessionTask, 43 | willBeRedirectedTo request: URLRequest, 44 | for response: HTTPURLResponse, 45 | completion: @escaping (URLRequest?) -> Void) 46 | } 47 | 48 | // MARK: - 49 | 50 | /// `Redirector` is a convenience `RedirectHandler` making it easy to follow, not follow, or modify a redirect. 51 | public struct Redirector { 52 | /// Defines the behavior of the `Redirector` type. 53 | public enum Behavior { 54 | /// Follow the redirect as defined in the response. 55 | case follow 56 | /// Do not follow the redirect defined in the response. 57 | case doNotFollow 58 | /// Modify the redirect request defined in the response. 59 | case modify((URLSessionTask, URLRequest, HTTPURLResponse) -> URLRequest?) 60 | } 61 | 62 | /// Returns a `Redirector` with a `.follow` `Behavior`. 63 | public static let follow = Redirector(behavior: .follow) 64 | /// Returns a `Redirector` with a `.doNotFollow` `Behavior`. 65 | public static let doNotFollow = Redirector(behavior: .doNotFollow) 66 | 67 | /// The `Behavior` of the `Redirector`. 68 | public let behavior: Behavior 69 | 70 | /// Creates a `Redirector` instance from the `Behavior`. 71 | /// 72 | /// - Parameter behavior: The `Behavior`. 73 | public init(behavior: Behavior) { 74 | self.behavior = behavior 75 | } 76 | } 77 | 78 | // MARK: - 79 | 80 | extension Redirector: RedirectHandler { 81 | public func task(_ task: URLSessionTask, 82 | willBeRedirectedTo request: URLRequest, 83 | for response: HTTPURLResponse, 84 | completion: @escaping (URLRequest?) -> Void) { 85 | switch behavior { 86 | case .follow: 87 | completion(request) 88 | case .doNotFollow: 89 | completion(nil) 90 | case let .modify(closure): 91 | let request = closure(task, request, response) 92 | completion(request) 93 | } 94 | } 95 | } 96 | 97 | #if swift(>=5.5) 98 | extension RedirectHandler where Self == Redirector { 99 | /// Provides a `Redirector` which follows redirects. Equivalent to `Redirector.follow`. 100 | public static var follow: Redirector { .follow } 101 | 102 | /// Provides a `Redirector` which does not follow redirects. Equivalent to `Redirector.doNotFollow`. 103 | public static var doNotFollow: Redirector { .doNotFollow } 104 | 105 | /// Creates a `Redirector` which modifies the redirected `URLRequest` using the provided closure. 106 | /// 107 | /// - Parameter closure: Closure used to modify the redirect. 108 | /// - Returns: The `Redirector`. 109 | public static func modify(using closure: @escaping (URLSessionTask, URLRequest, HTTPURLResponse) -> URLRequest?) -> Redirector { 110 | Redirector(behavior: .modify(closure)) 111 | } 112 | } 113 | #endif 114 | -------------------------------------------------------------------------------- /Examples/Cocoapods/Pods/Alamofire/Source/Protected.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Protected.swift 3 | // 4 | // Copyright (c) 2014-2020 Alamofire Software Foundation (http://alamofire.org/) 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to deal 8 | // in the Software without restriction, including without limitation the rights 9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | // copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in 14 | // all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | // THE SOFTWARE. 23 | // 24 | 25 | import Foundation 26 | 27 | private protocol Lock { 28 | func lock() 29 | func unlock() 30 | } 31 | 32 | extension Lock { 33 | /// Executes a closure returning a value while acquiring the lock. 34 | /// 35 | /// - Parameter closure: The closure to run. 36 | /// 37 | /// - Returns: The value the closure generated. 38 | func around(_ closure: () throws -> T) rethrows -> T { 39 | lock(); defer { unlock() } 40 | return try closure() 41 | } 42 | 43 | /// Execute a closure while acquiring the lock. 44 | /// 45 | /// - Parameter closure: The closure to run. 46 | func around(_ closure: () throws -> Void) rethrows { 47 | lock(); defer { unlock() } 48 | try closure() 49 | } 50 | } 51 | 52 | #if os(Linux) || os(Windows) 53 | 54 | extension NSLock: Lock {} 55 | 56 | #endif 57 | 58 | #if os(macOS) || os(iOS) || os(watchOS) || os(tvOS) 59 | /// An `os_unfair_lock` wrapper. 60 | final class UnfairLock: Lock { 61 | private let unfairLock: os_unfair_lock_t 62 | 63 | init() { 64 | unfairLock = .allocate(capacity: 1) 65 | unfairLock.initialize(to: os_unfair_lock()) 66 | } 67 | 68 | deinit { 69 | unfairLock.deinitialize(count: 1) 70 | unfairLock.deallocate() 71 | } 72 | 73 | fileprivate func lock() { 74 | os_unfair_lock_lock(unfairLock) 75 | } 76 | 77 | fileprivate func unlock() { 78 | os_unfair_lock_unlock(unfairLock) 79 | } 80 | } 81 | #endif 82 | 83 | /// A thread-safe wrapper around a value. 84 | @propertyWrapper 85 | @dynamicMemberLookup 86 | final class Protected { 87 | #if os(macOS) || os(iOS) || os(watchOS) || os(tvOS) 88 | private let lock = UnfairLock() 89 | #elseif os(Linux) || os(Windows) 90 | private let lock = NSLock() 91 | #endif 92 | private var value: T 93 | 94 | init(_ value: T) { 95 | self.value = value 96 | } 97 | 98 | /// The contained value. Unsafe for anything more than direct read or write. 99 | var wrappedValue: T { 100 | get { lock.around { value } } 101 | set { lock.around { value = newValue } } 102 | } 103 | 104 | var projectedValue: Protected { self } 105 | 106 | init(wrappedValue: T) { 107 | value = wrappedValue 108 | } 109 | 110 | /// Synchronously read or transform the contained value. 111 | /// 112 | /// - Parameter closure: The closure to execute. 113 | /// 114 | /// - Returns: The return value of the closure passed. 115 | func read(_ closure: (T) throws -> U) rethrows -> U { 116 | try lock.around { try closure(self.value) } 117 | } 118 | 119 | /// Synchronously modify the protected value. 120 | /// 121 | /// - Parameter closure: The closure to execute. 122 | /// 123 | /// - Returns: The modified value. 124 | @discardableResult 125 | func write(_ closure: (inout T) throws -> U) rethrows -> U { 126 | try lock.around { try closure(&self.value) } 127 | } 128 | 129 | subscript(dynamicMember keyPath: WritableKeyPath) -> Property { 130 | get { lock.around { value[keyPath: keyPath] } } 131 | set { lock.around { value[keyPath: keyPath] = newValue } } 132 | } 133 | 134 | subscript(dynamicMember keyPath: KeyPath) -> Property { 135 | lock.around { value[keyPath: keyPath] } 136 | } 137 | } 138 | 139 | extension Protected where T == Request.MutableState { 140 | /// Attempts to transition to the passed `State`. 141 | /// 142 | /// - Parameter state: The `State` to attempt transition to. 143 | /// 144 | /// - Returns: Whether the transition occurred. 145 | func attemptToTransitionTo(_ state: Request.State) -> Bool { 146 | lock.around { 147 | guard value.state.canTransitionTo(state) else { return false } 148 | 149 | value.state = state 150 | 151 | return true 152 | } 153 | } 154 | 155 | /// Perform a closure while locked with the provided `Request.State`. 156 | /// 157 | /// - Parameter perform: The closure to perform while locked. 158 | func withState(perform: (Request.State) -> Void) { 159 | lock.around { perform(value.state) } 160 | } 161 | } 162 | -------------------------------------------------------------------------------- /Loggie/Classes/Core/Log.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LoggieRequest.swift 3 | // Pods 4 | // 5 | // Created by Filip Bec on 12/03/2017. 6 | // 7 | // 8 | 9 | import UIKit 10 | 11 | public class Log: NSObject { 12 | 13 | static let dataDecoders: [DataDecoder] = [ 14 | JSONDataDecoder(), 15 | PlainTextDataDecoder(), 16 | ImageDataDecoder(), 17 | DefaultDecoder() 18 | ] 19 | 20 | public var request: URLRequest 21 | public var response: HTTPURLResponse? 22 | public var data: Data? 23 | public var error: Error? 24 | 25 | public var startTime: Date? 26 | public var endTime: Date? 27 | 28 | public var duration: TimeInterval? { 29 | guard let start = startTime, let end = endTime else { 30 | return nil 31 | } 32 | return end.timeIntervalSince(start) 33 | } 34 | 35 | public var durationString: String? { 36 | guard let _duration = duration else { 37 | return nil 38 | } 39 | return String(format: "%dms", Int(_duration * 1000.0)) 40 | } 41 | 42 | public init(request: URLRequest) { 43 | self.request = request 44 | } 45 | 46 | func logDetailsItem(with data: Data, contentType: String?) -> LogDetailsItem? { 47 | guard let dataDecoder = Log.dataDecoders.filter({ $0.canDecode(data, contentType: contentType) }).first else { return nil } 48 | return dataDecoder.decode(data, contentType: contentType) 49 | } 50 | 51 | func bodySection(with item: LogDetailsItem) -> LogDetailsSection { 52 | let section = LogDetailsSection(headerTitle: "Body") 53 | section.items.append(item) 54 | return section 55 | } 56 | 57 | var searchKeywords: [String] { 58 | return [ 59 | request.url?.absoluteString, 60 | response.flatMap { String($0.statusCode) } 61 | ].compactMap { $0 } 62 | } 63 | } 64 | 65 | extension Log { 66 | 67 | public var shareRepresentation: String { 68 | var output: String = "" 69 | 70 | // MARK: - Overview - 71 | 72 | var overviewItems = [(String, String)]() 73 | 74 | if let url = request.url { 75 | overviewItems.append(("URL", url.absoluteString)) 76 | } 77 | if let method = request.httpMethod { 78 | overviewItems.append(("Method", method)) 79 | } 80 | if let responseStatus = response?.statusCode { 81 | overviewItems.append(("Response status", String(responseStatus))) 82 | } 83 | if let startTime = startTime { 84 | let formatedDate = DateFormatter.localizedString(from: startTime, dateStyle: .medium, timeStyle: .medium) 85 | overviewItems.append(("Request time", formatedDate)) 86 | } 87 | if let endTime = endTime { 88 | let formatedDate = DateFormatter.localizedString(from: endTime, dateStyle: .medium, timeStyle: .medium) 89 | overviewItems.append(("Response time", formatedDate)) 90 | } 91 | if let durationString = durationString { 92 | overviewItems.append(("Duration", durationString)) 93 | } 94 | if let requestData = request.data { 95 | let sizeString = ByteCountFormatter.string(fromByteCount: Int64(requestData.count), countStyle: .memory) 96 | overviewItems.append(("Request size", sizeString)) 97 | } 98 | if let responseData = data { 99 | let sizeString = ByteCountFormatter.string(fromByteCount: Int64(responseData.count), countStyle: .memory) 100 | overviewItems.append(("Response size", sizeString)) 101 | } 102 | 103 | let overviewItemsString = overviewItems.map { String(format: "%@: %@", $0.0, $0.1) }.joined(separator: "\n") 104 | output += _string(for: "Overview", value: overviewItemsString) 105 | 106 | // MARK: - REQUEST - 107 | 108 | output += _formattedSectionTitle("Request") 109 | 110 | if let headers = request.allHTTPHeaderFields { 111 | output += _string(for: "Headers", value: headers.shareRepresentation) 112 | } 113 | 114 | if let url = request.url, let queryItems = URLComponents(url: url, resolvingAgainstBaseURL: false)?.queryItems { 115 | let queryParamsString: String = queryItems 116 | .map({ queryItem -> String in 117 | return String(format: "%@: %@", queryItem.name, queryItem.value ?? "") 118 | }) 119 | .joined(separator: "\n") 120 | 121 | output += _string(for: "Query params", value: queryParamsString) 122 | } 123 | 124 | if let body = request.data, let jsonString = body.formattedJsonString { 125 | output += _string(for: "BODY", value: jsonString) 126 | } 127 | 128 | // MARK: - RESPONSE - 129 | 130 | output += _formattedSectionTitle("Response") 131 | 132 | if let headers = response?.allHeaderFields as? [String: String] { 133 | output += _string(for: "Headers", value: headers.shareRepresentation) 134 | } 135 | 136 | if let body = data, let jsonString = body.formattedJsonString { 137 | output += _string(for: "Body", value: jsonString) 138 | } 139 | 140 | return output 141 | } 142 | 143 | private func _string(for title: String, value: String) -> String { 144 | return String(format: "\t%@:\n-----------------------\n%@\n\n", title, value) 145 | } 146 | 147 | private func _formattedSectionTitle(_ title: String) -> String { 148 | let line = "--------------------------------" 149 | return [line, title, line].joined(separator: "\n") + "\n\n" 150 | } 151 | } 152 | 153 | 154 | fileprivate extension Dictionary where Key == String, Value == String { 155 | 156 | var shareRepresentation: String { 157 | return self.map { String(format: "%@: %@", $0.key, $0.value) }.joined(separator: "\n") 158 | } 159 | } 160 | -------------------------------------------------------------------------------- /Examples/Cocoapods/Pods/Alamofire/Source/Notifications.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Notifications.swift 3 | // 4 | // Copyright (c) 2014-2018 Alamofire Software Foundation (http://alamofire.org/) 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to deal 8 | // in the Software without restriction, including without limitation the rights 9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | // copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in 14 | // all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | // THE SOFTWARE. 23 | // 24 | 25 | import Foundation 26 | 27 | extension Request { 28 | /// Posted when a `Request` is resumed. The `Notification` contains the resumed `Request`. 29 | public static let didResumeNotification = Notification.Name(rawValue: "org.alamofire.notification.name.request.didResume") 30 | /// Posted when a `Request` is suspended. The `Notification` contains the suspended `Request`. 31 | public static let didSuspendNotification = Notification.Name(rawValue: "org.alamofire.notification.name.request.didSuspend") 32 | /// Posted when a `Request` is cancelled. The `Notification` contains the cancelled `Request`. 33 | public static let didCancelNotification = Notification.Name(rawValue: "org.alamofire.notification.name.request.didCancel") 34 | /// Posted when a `Request` is finished. The `Notification` contains the completed `Request`. 35 | public static let didFinishNotification = Notification.Name(rawValue: "org.alamofire.notification.name.request.didFinish") 36 | 37 | /// Posted when a `URLSessionTask` is resumed. The `Notification` contains the `Request` associated with the `URLSessionTask`. 38 | public static let didResumeTaskNotification = Notification.Name(rawValue: "org.alamofire.notification.name.request.didResumeTask") 39 | /// Posted when a `URLSessionTask` is suspended. The `Notification` contains the `Request` associated with the `URLSessionTask`. 40 | public static let didSuspendTaskNotification = Notification.Name(rawValue: "org.alamofire.notification.name.request.didSuspendTask") 41 | /// Posted when a `URLSessionTask` is cancelled. The `Notification` contains the `Request` associated with the `URLSessionTask`. 42 | public static let didCancelTaskNotification = Notification.Name(rawValue: "org.alamofire.notification.name.request.didCancelTask") 43 | /// Posted when a `URLSessionTask` is completed. The `Notification` contains the `Request` associated with the `URLSessionTask`. 44 | public static let didCompleteTaskNotification = Notification.Name(rawValue: "org.alamofire.notification.name.request.didCompleteTask") 45 | } 46 | 47 | // MARK: - 48 | 49 | extension Notification { 50 | /// The `Request` contained by the instance's `userInfo`, `nil` otherwise. 51 | public var request: Request? { 52 | userInfo?[String.requestKey] as? Request 53 | } 54 | 55 | /// Convenience initializer for a `Notification` containing a `Request` payload. 56 | /// 57 | /// - Parameters: 58 | /// - name: The name of the notification. 59 | /// - request: The `Request` payload. 60 | init(name: Notification.Name, request: Request) { 61 | self.init(name: name, object: nil, userInfo: [String.requestKey: request]) 62 | } 63 | } 64 | 65 | extension NotificationCenter { 66 | /// Convenience function for posting notifications with `Request` payloads. 67 | /// 68 | /// - Parameters: 69 | /// - name: The name of the notification. 70 | /// - request: The `Request` payload. 71 | func postNotification(named name: Notification.Name, with request: Request) { 72 | let notification = Notification(name: name, request: request) 73 | post(notification) 74 | } 75 | } 76 | 77 | extension String { 78 | /// User info dictionary key representing the `Request` associated with the notification. 79 | fileprivate static let requestKey = "org.alamofire.notification.key.request" 80 | } 81 | 82 | /// `EventMonitor` that provides Alamofire's notifications. 83 | public final class AlamofireNotifications: EventMonitor { 84 | public func requestDidResume(_ request: Request) { 85 | NotificationCenter.default.postNotification(named: Request.didResumeNotification, with: request) 86 | } 87 | 88 | public func requestDidSuspend(_ request: Request) { 89 | NotificationCenter.default.postNotification(named: Request.didSuspendNotification, with: request) 90 | } 91 | 92 | public func requestDidCancel(_ request: Request) { 93 | NotificationCenter.default.postNotification(named: Request.didCancelNotification, with: request) 94 | } 95 | 96 | public func requestDidFinish(_ request: Request) { 97 | NotificationCenter.default.postNotification(named: Request.didFinishNotification, with: request) 98 | } 99 | 100 | public func request(_ request: Request, didResumeTask task: URLSessionTask) { 101 | NotificationCenter.default.postNotification(named: Request.didResumeTaskNotification, with: request) 102 | } 103 | 104 | public func request(_ request: Request, didSuspendTask task: URLSessionTask) { 105 | NotificationCenter.default.postNotification(named: Request.didSuspendTaskNotification, with: request) 106 | } 107 | 108 | public func request(_ request: Request, didCancelTask task: URLSessionTask) { 109 | NotificationCenter.default.postNotification(named: Request.didCancelTaskNotification, with: request) 110 | } 111 | 112 | public func request(_ request: Request, didCompleteTask task: URLSessionTask, with error: AFError?) { 113 | NotificationCenter.default.postNotification(named: Request.didCompleteTaskNotification, with: request) 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /Examples/Cocoapods/Pods/Alamofire/Source/RequestTaskMap.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RequestTaskMap.swift 3 | // 4 | // Copyright (c) 2014-2018 Alamofire Software Foundation (http://alamofire.org/) 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to deal 8 | // in the Software without restriction, including without limitation the rights 9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | // copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in 14 | // all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | // THE SOFTWARE. 23 | // 24 | 25 | import Foundation 26 | 27 | /// A type that maintains a two way, one to one map of `URLSessionTask`s to `Request`s. 28 | struct RequestTaskMap { 29 | private typealias Events = (completed: Bool, metricsGathered: Bool) 30 | 31 | private var tasksToRequests: [URLSessionTask: Request] 32 | private var requestsToTasks: [Request: URLSessionTask] 33 | private var taskEvents: [URLSessionTask: Events] 34 | 35 | var requests: [Request] { 36 | Array(tasksToRequests.values) 37 | } 38 | 39 | init(tasksToRequests: [URLSessionTask: Request] = [:], 40 | requestsToTasks: [Request: URLSessionTask] = [:], 41 | taskEvents: [URLSessionTask: (completed: Bool, metricsGathered: Bool)] = [:]) { 42 | self.tasksToRequests = tasksToRequests 43 | self.requestsToTasks = requestsToTasks 44 | self.taskEvents = taskEvents 45 | } 46 | 47 | subscript(_ request: Request) -> URLSessionTask? { 48 | get { requestsToTasks[request] } 49 | set { 50 | guard let newValue = newValue else { 51 | guard let task = requestsToTasks[request] else { 52 | fatalError("RequestTaskMap consistency error: no task corresponding to request found.") 53 | } 54 | 55 | requestsToTasks.removeValue(forKey: request) 56 | tasksToRequests.removeValue(forKey: task) 57 | taskEvents.removeValue(forKey: task) 58 | 59 | return 60 | } 61 | 62 | requestsToTasks[request] = newValue 63 | tasksToRequests[newValue] = request 64 | taskEvents[newValue] = (completed: false, metricsGathered: false) 65 | } 66 | } 67 | 68 | subscript(_ task: URLSessionTask) -> Request? { 69 | get { tasksToRequests[task] } 70 | set { 71 | guard let newValue = newValue else { 72 | guard let request = tasksToRequests[task] else { 73 | fatalError("RequestTaskMap consistency error: no request corresponding to task found.") 74 | } 75 | 76 | tasksToRequests.removeValue(forKey: task) 77 | requestsToTasks.removeValue(forKey: request) 78 | taskEvents.removeValue(forKey: task) 79 | 80 | return 81 | } 82 | 83 | tasksToRequests[task] = newValue 84 | requestsToTasks[newValue] = task 85 | taskEvents[task] = (completed: false, metricsGathered: false) 86 | } 87 | } 88 | 89 | var count: Int { 90 | precondition(tasksToRequests.count == requestsToTasks.count, 91 | "RequestTaskMap.count invalid, requests.count: \(tasksToRequests.count) != tasks.count: \(requestsToTasks.count)") 92 | 93 | return tasksToRequests.count 94 | } 95 | 96 | var eventCount: Int { 97 | precondition(taskEvents.count == count, "RequestTaskMap.eventCount invalid, count: \(count) != taskEvents.count: \(taskEvents.count)") 98 | 99 | return taskEvents.count 100 | } 101 | 102 | var isEmpty: Bool { 103 | precondition(tasksToRequests.isEmpty == requestsToTasks.isEmpty, 104 | "RequestTaskMap.isEmpty invalid, requests.isEmpty: \(tasksToRequests.isEmpty) != tasks.isEmpty: \(requestsToTasks.isEmpty)") 105 | 106 | return tasksToRequests.isEmpty 107 | } 108 | 109 | var isEventsEmpty: Bool { 110 | precondition(taskEvents.isEmpty == isEmpty, "RequestTaskMap.isEventsEmpty invalid, isEmpty: \(isEmpty) != taskEvents.isEmpty: \(taskEvents.isEmpty)") 111 | 112 | return taskEvents.isEmpty 113 | } 114 | 115 | mutating func disassociateIfNecessaryAfterGatheringMetricsForTask(_ task: URLSessionTask) -> Bool { 116 | guard let events = taskEvents[task] else { 117 | fatalError("RequestTaskMap consistency error: no events corresponding to task found.") 118 | } 119 | 120 | switch (events.completed, events.metricsGathered) { 121 | case (_, true): fatalError("RequestTaskMap consistency error: duplicate metricsGatheredForTask call.") 122 | case (false, false): taskEvents[task] = (completed: false, metricsGathered: true); return false 123 | case (true, false): self[task] = nil; return true 124 | } 125 | } 126 | 127 | mutating func disassociateIfNecessaryAfterCompletingTask(_ task: URLSessionTask) -> Bool { 128 | guard let events = taskEvents[task] else { 129 | fatalError("RequestTaskMap consistency error: no events corresponding to task found.") 130 | } 131 | 132 | switch (events.completed, events.metricsGathered) { 133 | case (true, _): fatalError("RequestTaskMap consistency error: duplicate completionReceivedForTask call.") 134 | #if os(Linux) // Linux doesn't gather metrics, so unconditionally remove the reference and return true. 135 | default: self[task] = nil; return true 136 | #else 137 | case (false, false): 138 | if #available(macOS 10.12, iOS 10, watchOS 7, tvOS 10, *) { 139 | taskEvents[task] = (completed: true, metricsGathered: false); return false 140 | } else { 141 | // watchOS < 7 doesn't gather metrics, so unconditionally remove the reference and return true. 142 | self[task] = nil; return true 143 | } 144 | case (false, true): 145 | self[task] = nil; return true 146 | #endif 147 | } 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /Examples/Cocoapods/Pods/Target Support Files/Pods-Loggie_Tests/Pods-Loggie_Tests-resources.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | set -u 4 | set -o pipefail 5 | 6 | function on_error { 7 | echo "$(realpath -mq "${0}"):$1: error: Unexpected failure" 8 | } 9 | trap 'on_error $LINENO' ERR 10 | 11 | if [ -z ${UNLOCALIZED_RESOURCES_FOLDER_PATH+x} ]; then 12 | # If UNLOCALIZED_RESOURCES_FOLDER_PATH is not set, then there's nowhere for us to copy 13 | # resources to, so exit 0 (signalling the script phase was successful). 14 | exit 0 15 | fi 16 | 17 | mkdir -p "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" 18 | 19 | RESOURCES_TO_COPY=${PODS_ROOT}/resources-to-copy-${TARGETNAME}.txt 20 | > "$RESOURCES_TO_COPY" 21 | 22 | XCASSET_FILES=() 23 | 24 | # This protects against multiple targets copying the same framework dependency at the same time. The solution 25 | # was originally proposed here: https://lists.samba.org/archive/rsync/2008-February/020158.html 26 | RSYNC_PROTECT_TMP_FILES=(--filter "P .*.??????") 27 | 28 | case "${TARGETED_DEVICE_FAMILY:-}" in 29 | 1,2) 30 | TARGET_DEVICE_ARGS="--target-device ipad --target-device iphone" 31 | ;; 32 | 1) 33 | TARGET_DEVICE_ARGS="--target-device iphone" 34 | ;; 35 | 2) 36 | TARGET_DEVICE_ARGS="--target-device ipad" 37 | ;; 38 | 3) 39 | TARGET_DEVICE_ARGS="--target-device tv" 40 | ;; 41 | 4) 42 | TARGET_DEVICE_ARGS="--target-device watch" 43 | ;; 44 | *) 45 | TARGET_DEVICE_ARGS="--target-device mac" 46 | ;; 47 | esac 48 | 49 | install_resource() 50 | { 51 | if [[ "$1" = /* ]] ; then 52 | RESOURCE_PATH="$1" 53 | else 54 | RESOURCE_PATH="${PODS_ROOT}/$1" 55 | fi 56 | if [[ ! -e "$RESOURCE_PATH" ]] ; then 57 | cat << EOM 58 | error: Resource "$RESOURCE_PATH" not found. Run 'pod install' to update the copy resources script. 59 | EOM 60 | exit 1 61 | fi 62 | case $RESOURCE_PATH in 63 | *.storyboard) 64 | echo "ibtool --reference-external-strings-file --errors --warnings --notices --minimum-deployment-target ${!DEPLOYMENT_TARGET_SETTING_NAME} --output-format human-readable-text --compile ${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$RESOURCE_PATH\" .storyboard`.storyboardc $RESOURCE_PATH --sdk ${SDKROOT} ${TARGET_DEVICE_ARGS}" || true 65 | ibtool --reference-external-strings-file --errors --warnings --notices --minimum-deployment-target ${!DEPLOYMENT_TARGET_SETTING_NAME} --output-format human-readable-text --compile "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$RESOURCE_PATH\" .storyboard`.storyboardc" "$RESOURCE_PATH" --sdk "${SDKROOT}" ${TARGET_DEVICE_ARGS} 66 | ;; 67 | *.xib) 68 | echo "ibtool --reference-external-strings-file --errors --warnings --notices --minimum-deployment-target ${!DEPLOYMENT_TARGET_SETTING_NAME} --output-format human-readable-text --compile ${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$RESOURCE_PATH\" .xib`.nib $RESOURCE_PATH --sdk ${SDKROOT} ${TARGET_DEVICE_ARGS}" || true 69 | ibtool --reference-external-strings-file --errors --warnings --notices --minimum-deployment-target ${!DEPLOYMENT_TARGET_SETTING_NAME} --output-format human-readable-text --compile "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$RESOURCE_PATH\" .xib`.nib" "$RESOURCE_PATH" --sdk "${SDKROOT}" ${TARGET_DEVICE_ARGS} 70 | ;; 71 | *.framework) 72 | echo "mkdir -p ${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" || true 73 | mkdir -p "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" 74 | echo "rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" $RESOURCE_PATH ${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" || true 75 | rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" "$RESOURCE_PATH" "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" 76 | ;; 77 | *.xcdatamodel) 78 | echo "xcrun momc \"$RESOURCE_PATH\" \"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH"`.mom\"" || true 79 | xcrun momc "$RESOURCE_PATH" "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcdatamodel`.mom" 80 | ;; 81 | *.xcdatamodeld) 82 | echo "xcrun momc \"$RESOURCE_PATH\" \"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcdatamodeld`.momd\"" || true 83 | xcrun momc "$RESOURCE_PATH" "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcdatamodeld`.momd" 84 | ;; 85 | *.xcmappingmodel) 86 | echo "xcrun mapc \"$RESOURCE_PATH\" \"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcmappingmodel`.cdm\"" || true 87 | xcrun mapc "$RESOURCE_PATH" "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcmappingmodel`.cdm" 88 | ;; 89 | *.xcassets) 90 | ABSOLUTE_XCASSET_FILE="$RESOURCE_PATH" 91 | XCASSET_FILES+=("$ABSOLUTE_XCASSET_FILE") 92 | ;; 93 | *) 94 | echo "$RESOURCE_PATH" || true 95 | echo "$RESOURCE_PATH" >> "$RESOURCES_TO_COPY" 96 | ;; 97 | esac 98 | } 99 | if [[ "$CONFIGURATION" == "Debug" ]]; then 100 | install_resource "${PODS_ROOT}/../../Loggie/Classes/LogDetails/LogDetails.storyboard" 101 | install_resource "${PODS_ROOT}/../../Loggie/Classes/LogList/LogListTableViewCell.xib" 102 | install_resource "${PODS_CONFIGURATION_BUILD_DIR}/Loggie/LoggieResources.bundle" 103 | fi 104 | if [[ "$CONFIGURATION" == "Release" ]]; then 105 | install_resource "${PODS_ROOT}/../../Loggie/Classes/LogDetails/LogDetails.storyboard" 106 | install_resource "${PODS_ROOT}/../../Loggie/Classes/LogList/LogListTableViewCell.xib" 107 | install_resource "${PODS_CONFIGURATION_BUILD_DIR}/Loggie/LoggieResources.bundle" 108 | fi 109 | 110 | mkdir -p "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" 111 | rsync -avr --copy-links --no-relative --exclude '*/.svn/*' --files-from="$RESOURCES_TO_COPY" / "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" 112 | if [[ "${ACTION}" == "install" ]] && [[ "${SKIP_INSTALL}" == "NO" ]]; then 113 | mkdir -p "${INSTALL_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" 114 | rsync -avr --copy-links --no-relative --exclude '*/.svn/*' --files-from="$RESOURCES_TO_COPY" / "${INSTALL_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" 115 | fi 116 | rm -f "$RESOURCES_TO_COPY" 117 | 118 | if [[ -n "${WRAPPER_EXTENSION}" ]] && [ "`xcrun --find actool`" ] && [ -n "${XCASSET_FILES:-}" ] 119 | then 120 | # Find all other xcassets (this unfortunately includes those of path pods and other targets). 121 | OTHER_XCASSETS=$(find "$PWD" -iname "*.xcassets" -type d) 122 | while read line; do 123 | if [[ $line != "${PODS_ROOT}*" ]]; then 124 | XCASSET_FILES+=("$line") 125 | fi 126 | done <<<"$OTHER_XCASSETS" 127 | 128 | if [ -z ${ASSETCATALOG_COMPILER_APPICON_NAME+x} ]; then 129 | printf "%s\0" "${XCASSET_FILES[@]}" | xargs -0 xcrun actool --output-format human-readable-text --notices --warnings --platform "${PLATFORM_NAME}" --minimum-deployment-target "${!DEPLOYMENT_TARGET_SETTING_NAME}" ${TARGET_DEVICE_ARGS} --compress-pngs --compile "${BUILT_PRODUCTS_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" 130 | else 131 | printf "%s\0" "${XCASSET_FILES[@]}" | xargs -0 xcrun actool --output-format human-readable-text --notices --warnings --platform "${PLATFORM_NAME}" --minimum-deployment-target "${!DEPLOYMENT_TARGET_SETTING_NAME}" ${TARGET_DEVICE_ARGS} --compress-pngs --compile "${BUILT_PRODUCTS_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" --app-icon "${ASSETCATALOG_COMPILER_APPICON_NAME}" --output-partial-info-plist "${TARGET_TEMP_DIR}/assetcatalog_generated_info_cocoapods.plist" 132 | fi 133 | fi 134 | -------------------------------------------------------------------------------- /Examples/Cocoapods/Pods/Target Support Files/Pods-Loggie_Example/Pods-Loggie_Example-resources.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | set -u 4 | set -o pipefail 5 | 6 | function on_error { 7 | echo "$(realpath -mq "${0}"):$1: error: Unexpected failure" 8 | } 9 | trap 'on_error $LINENO' ERR 10 | 11 | if [ -z ${UNLOCALIZED_RESOURCES_FOLDER_PATH+x} ]; then 12 | # If UNLOCALIZED_RESOURCES_FOLDER_PATH is not set, then there's nowhere for us to copy 13 | # resources to, so exit 0 (signalling the script phase was successful). 14 | exit 0 15 | fi 16 | 17 | mkdir -p "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" 18 | 19 | RESOURCES_TO_COPY=${PODS_ROOT}/resources-to-copy-${TARGETNAME}.txt 20 | > "$RESOURCES_TO_COPY" 21 | 22 | XCASSET_FILES=() 23 | 24 | # This protects against multiple targets copying the same framework dependency at the same time. The solution 25 | # was originally proposed here: https://lists.samba.org/archive/rsync/2008-February/020158.html 26 | RSYNC_PROTECT_TMP_FILES=(--filter "P .*.??????") 27 | 28 | case "${TARGETED_DEVICE_FAMILY:-}" in 29 | 1,2) 30 | TARGET_DEVICE_ARGS="--target-device ipad --target-device iphone" 31 | ;; 32 | 1) 33 | TARGET_DEVICE_ARGS="--target-device iphone" 34 | ;; 35 | 2) 36 | TARGET_DEVICE_ARGS="--target-device ipad" 37 | ;; 38 | 3) 39 | TARGET_DEVICE_ARGS="--target-device tv" 40 | ;; 41 | 4) 42 | TARGET_DEVICE_ARGS="--target-device watch" 43 | ;; 44 | *) 45 | TARGET_DEVICE_ARGS="--target-device mac" 46 | ;; 47 | esac 48 | 49 | install_resource() 50 | { 51 | if [[ "$1" = /* ]] ; then 52 | RESOURCE_PATH="$1" 53 | else 54 | RESOURCE_PATH="${PODS_ROOT}/$1" 55 | fi 56 | if [[ ! -e "$RESOURCE_PATH" ]] ; then 57 | cat << EOM 58 | error: Resource "$RESOURCE_PATH" not found. Run 'pod install' to update the copy resources script. 59 | EOM 60 | exit 1 61 | fi 62 | case $RESOURCE_PATH in 63 | *.storyboard) 64 | echo "ibtool --reference-external-strings-file --errors --warnings --notices --minimum-deployment-target ${!DEPLOYMENT_TARGET_SETTING_NAME} --output-format human-readable-text --compile ${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$RESOURCE_PATH\" .storyboard`.storyboardc $RESOURCE_PATH --sdk ${SDKROOT} ${TARGET_DEVICE_ARGS}" || true 65 | ibtool --reference-external-strings-file --errors --warnings --notices --minimum-deployment-target ${!DEPLOYMENT_TARGET_SETTING_NAME} --output-format human-readable-text --compile "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$RESOURCE_PATH\" .storyboard`.storyboardc" "$RESOURCE_PATH" --sdk "${SDKROOT}" ${TARGET_DEVICE_ARGS} 66 | ;; 67 | *.xib) 68 | echo "ibtool --reference-external-strings-file --errors --warnings --notices --minimum-deployment-target ${!DEPLOYMENT_TARGET_SETTING_NAME} --output-format human-readable-text --compile ${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$RESOURCE_PATH\" .xib`.nib $RESOURCE_PATH --sdk ${SDKROOT} ${TARGET_DEVICE_ARGS}" || true 69 | ibtool --reference-external-strings-file --errors --warnings --notices --minimum-deployment-target ${!DEPLOYMENT_TARGET_SETTING_NAME} --output-format human-readable-text --compile "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$RESOURCE_PATH\" .xib`.nib" "$RESOURCE_PATH" --sdk "${SDKROOT}" ${TARGET_DEVICE_ARGS} 70 | ;; 71 | *.framework) 72 | echo "mkdir -p ${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" || true 73 | mkdir -p "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" 74 | echo "rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" $RESOURCE_PATH ${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" || true 75 | rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" "$RESOURCE_PATH" "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" 76 | ;; 77 | *.xcdatamodel) 78 | echo "xcrun momc \"$RESOURCE_PATH\" \"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH"`.mom\"" || true 79 | xcrun momc "$RESOURCE_PATH" "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcdatamodel`.mom" 80 | ;; 81 | *.xcdatamodeld) 82 | echo "xcrun momc \"$RESOURCE_PATH\" \"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcdatamodeld`.momd\"" || true 83 | xcrun momc "$RESOURCE_PATH" "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcdatamodeld`.momd" 84 | ;; 85 | *.xcmappingmodel) 86 | echo "xcrun mapc \"$RESOURCE_PATH\" \"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcmappingmodel`.cdm\"" || true 87 | xcrun mapc "$RESOURCE_PATH" "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcmappingmodel`.cdm" 88 | ;; 89 | *.xcassets) 90 | ABSOLUTE_XCASSET_FILE="$RESOURCE_PATH" 91 | XCASSET_FILES+=("$ABSOLUTE_XCASSET_FILE") 92 | ;; 93 | *) 94 | echo "$RESOURCE_PATH" || true 95 | echo "$RESOURCE_PATH" >> "$RESOURCES_TO_COPY" 96 | ;; 97 | esac 98 | } 99 | if [[ "$CONFIGURATION" == "Debug" ]]; then 100 | install_resource "${PODS_ROOT}/../../Loggie/Classes/LogDetails/LogDetails.storyboard" 101 | install_resource "${PODS_ROOT}/../../Loggie/Classes/LogList/LogListTableViewCell.xib" 102 | install_resource "${PODS_CONFIGURATION_BUILD_DIR}/Loggie/LoggieResources.bundle" 103 | fi 104 | if [[ "$CONFIGURATION" == "Release" ]]; then 105 | install_resource "${PODS_ROOT}/../../Loggie/Classes/LogDetails/LogDetails.storyboard" 106 | install_resource "${PODS_ROOT}/../../Loggie/Classes/LogList/LogListTableViewCell.xib" 107 | install_resource "${PODS_CONFIGURATION_BUILD_DIR}/Loggie/LoggieResources.bundle" 108 | fi 109 | 110 | mkdir -p "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" 111 | rsync -avr --copy-links --no-relative --exclude '*/.svn/*' --files-from="$RESOURCES_TO_COPY" / "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" 112 | if [[ "${ACTION}" == "install" ]] && [[ "${SKIP_INSTALL}" == "NO" ]]; then 113 | mkdir -p "${INSTALL_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" 114 | rsync -avr --copy-links --no-relative --exclude '*/.svn/*' --files-from="$RESOURCES_TO_COPY" / "${INSTALL_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" 115 | fi 116 | rm -f "$RESOURCES_TO_COPY" 117 | 118 | if [[ -n "${WRAPPER_EXTENSION}" ]] && [ "`xcrun --find actool`" ] && [ -n "${XCASSET_FILES:-}" ] 119 | then 120 | # Find all other xcassets (this unfortunately includes those of path pods and other targets). 121 | OTHER_XCASSETS=$(find "$PWD" -iname "*.xcassets" -type d) 122 | while read line; do 123 | if [[ $line != "${PODS_ROOT}*" ]]; then 124 | XCASSET_FILES+=("$line") 125 | fi 126 | done <<<"$OTHER_XCASSETS" 127 | 128 | if [ -z ${ASSETCATALOG_COMPILER_APPICON_NAME+x} ]; then 129 | printf "%s\0" "${XCASSET_FILES[@]}" | xargs -0 xcrun actool --output-format human-readable-text --notices --warnings --platform "${PLATFORM_NAME}" --minimum-deployment-target "${!DEPLOYMENT_TARGET_SETTING_NAME}" ${TARGET_DEVICE_ARGS} --compress-pngs --compile "${BUILT_PRODUCTS_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" 130 | else 131 | printf "%s\0" "${XCASSET_FILES[@]}" | xargs -0 xcrun actool --output-format human-readable-text --notices --warnings --platform "${PLATFORM_NAME}" --minimum-deployment-target "${!DEPLOYMENT_TARGET_SETTING_NAME}" ${TARGET_DEVICE_ARGS} --compress-pngs --compile "${BUILT_PRODUCTS_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" --app-icon "${ASSETCATALOG_COMPILER_APPICON_NAME}" --output-partial-info-plist "${TARGET_TEMP_DIR}/assetcatalog_generated_info_cocoapods.plist" 132 | fi 133 | fi 134 | -------------------------------------------------------------------------------- /Loggie/Classes/Core/LogDetails/LogDetailsViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LogDetailsViewController.swift 3 | // Pods 4 | // 5 | // Created by Filip Bec on 14/03/2017. 6 | // 7 | // 8 | 9 | import UIKit 10 | 11 | class LogDetailsViewController: UIViewController { 12 | 13 | private enum Kind: Int { 14 | case overview = 0 15 | case request = 1 16 | case response = 2 17 | } 18 | 19 | private var kind: Kind = .overview { 20 | didSet { 21 | switch kind { 22 | case .overview: 23 | sections = log.overviewDataSource 24 | case .request: 25 | sections = log.requestDataSource 26 | case .response: 27 | sections = log.responseDataSource 28 | } 29 | tableView.reloadData() 30 | } 31 | } 32 | 33 | fileprivate var sections = [LogDetailsSection]() 34 | 35 | var log: Log! 36 | 37 | @IBOutlet weak var optionsBar: UINavigationBar! 38 | @IBOutlet weak private var tableView: UITableView! 39 | 40 | override func viewDidLoad() { 41 | super.viewDidLoad() 42 | kind = .overview 43 | 44 | setupOptionsBar() 45 | setupShareButton() 46 | setupTableView() 47 | 48 | let titleComponents = [log.request.httpMethod, log.request.url?.path] 49 | title = titleComponents 50 | .compactMap { $0 } 51 | .joined(separator: " ") 52 | } 53 | 54 | // MARK: - Private 55 | 56 | private func setupOptionsBar() { 57 | optionsBar.tintColor = navigationController?.navigationBar.tintColor 58 | optionsBar.barTintColor = navigationController?.navigationBar.barTintColor 59 | } 60 | 61 | private func setupShareButton() { 62 | let shareButton = UIBarButtonItem( 63 | barButtonSystemItem: .action, 64 | target: self, 65 | action: #selector(shareButtonActionHandler(_:)) 66 | ) 67 | navigationItem.rightBarButtonItem = shareButton 68 | } 69 | 70 | private func setupTableView() { 71 | tableView.rowHeight = UITableView.automaticDimension 72 | tableView.estimatedRowHeight = 40.0 73 | } 74 | 75 | // MARK: - Actions 76 | 77 | @objc private func shareButtonActionHandler(_ sender: UIBarButtonItem) { 78 | let activityItems: [Any] = [log.shareRepresentation] 79 | let activityController = UIActivityViewController( 80 | activityItems: activityItems, 81 | applicationActivities: nil 82 | ) 83 | activityController.popoverPresentationController?.barButtonItem = sender 84 | present(activityController, animated: true, completion: nil) 85 | } 86 | 87 | @IBAction func segmentedControlActionHandler(_ sender: UISegmentedControl) { 88 | guard let _kind = Kind(rawValue: sender.selectedSegmentIndex) else { return } 89 | kind = _kind 90 | } 91 | 92 | } 93 | 94 | extension LogDetailsViewController: UITableViewDataSource { 95 | 96 | func numberOfSections(in tableView: UITableView) -> Int { 97 | return sections.count 98 | } 99 | 100 | func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 101 | return sections[section].items.count 102 | } 103 | 104 | func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? { 105 | return sections[section].headerTitle 106 | } 107 | 108 | func tableView(_ tableView: UITableView, titleForFooterInSection section: Int) -> String? { 109 | return sections[section].footerTitle 110 | } 111 | 112 | func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 113 | let item = sections[indexPath.section].items[indexPath.row] 114 | 115 | switch item { 116 | case .subtitle(let title, let subtitle): 117 | let cell = tableView.dequeueReusableCell(withIdentifier: "subtitleCell", for: indexPath) as! LogDetailsTableViewCell 118 | cell.titleLabel.text = title 119 | cell.subtitleLabel.text = subtitle 120 | return cell 121 | 122 | case .text(let text): 123 | let cell = tableView.dequeueReusableCell(withIdentifier: "textCell", for: indexPath) as! LogDetailsTextTableViewCell 124 | cell.textView.text = text 125 | return cell 126 | 127 | case .image(let image): 128 | let cell = tableView.dequeueReusableCell(withIdentifier: "imageCell", for: indexPath) as! LogDetailsImageTableViewCell 129 | cell.customImageView.image = image 130 | return cell 131 | } 132 | } 133 | } 134 | 135 | extension LogDetailsViewController: UITableViewDelegate { 136 | 137 | func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { 138 | let actionSheet = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet) 139 | actionSheet.addAction(copyAction(for: indexPath)) 140 | actionSheet.addAction(shareAction(for: indexPath)) 141 | actionSheet.addAction(cancelAction(for: indexPath)) 142 | 143 | setSourceView( 144 | popover: actionSheet.popoverPresentationController, 145 | sourceView: tableView, 146 | sourceRect: tableView.cellForRow(at: indexPath)?.frame 147 | ) 148 | 149 | present(actionSheet, animated: true) 150 | } 151 | } 152 | 153 | private extension LogDetailsViewController { 154 | 155 | func copyAction(for indexPath: IndexPath) -> UIAlertAction { 156 | return UIAlertAction(title: "Copy value", style: .default) { [weak self] (action) in 157 | guard let self = self else { return } 158 | 159 | let item = self.sections[indexPath.section].items[indexPath.row] 160 | let pasteboard = UIPasteboard.general 161 | 162 | switch item { 163 | case .subtitle(_, let subtitle): 164 | pasteboard.string = subtitle 165 | case .text(let text): 166 | pasteboard.string = text 167 | case .image(let image): 168 | pasteboard.image = image 169 | } 170 | 171 | self.tableView.deselectRow(at: indexPath, animated: true) 172 | } 173 | } 174 | 175 | func shareAction(for indexPath: IndexPath) -> UIAlertAction { 176 | return UIAlertAction(title: "Share value", style: .default) { [weak self] (action) in 177 | guard let self = self else { return } 178 | 179 | let item = self.sections[indexPath.section].items[indexPath.row] 180 | let shareItem: Any? 181 | 182 | switch item { 183 | case .subtitle(_, let subtitle): 184 | shareItem = subtitle 185 | case .text(let text): 186 | shareItem = text 187 | case .image(let image): 188 | shareItem = image 189 | } 190 | 191 | if let shareItem = shareItem { 192 | let shareController = UIActivityViewController(activityItems: [shareItem], applicationActivities: nil) 193 | self.setSourceView( 194 | popover: shareController.popoverPresentationController, 195 | sourceView: self.tableView, 196 | sourceRect: self.tableView.cellForRow(at: indexPath)?.frame 197 | ) 198 | self.present(shareController, animated: true) 199 | } 200 | 201 | self.tableView.deselectRow(at: indexPath, animated: true) 202 | } 203 | } 204 | 205 | func cancelAction(for indexPath: IndexPath) -> UIAlertAction { 206 | return UIAlertAction(title: "Cancel", style: .cancel, handler: { [weak self] (action) in 207 | self?.tableView.deselectRow(at: indexPath, animated: true) 208 | }) 209 | } 210 | 211 | func setSourceView(popover: UIPopoverPresentationController?, sourceView: UIView, sourceRect: CGRect?) { 212 | popover.flatMap { 213 | $0.sourceView = sourceView 214 | if let sourceRect = sourceRect { 215 | $0.sourceRect = sourceRect 216 | $0.permittedArrowDirections = [.up] 217 | } else { 218 | $0.sourceRect = .init(origin: sourceView.center, size: .zero) 219 | $0.permittedArrowDirections = [] 220 | } 221 | } 222 | } 223 | } 224 | --------------------------------------------------------------------------------