├── .gitignore ├── LICENSE ├── README.md ├── SwiftChecker.xcodeproj └── project.pbxproj ├── SwiftChecker ├── AppDelegate.swift ├── Credits.rtf ├── Future.swift ├── Info.plist ├── ProcessInfo.swift ├── SwiftChecker-Bridging-Header.h ├── SwiftChecker.xib ├── Timing.swift └── main.swift └── SwiftCheckerTests ├── Info.plist └── SwiftCheckerTests.swift /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | .DS_Store 3 | build/ 4 | *.pbxuser 5 | !default.pbxuser 6 | *.mode1v3 7 | !default.mode1v3 8 | *.mode2v3 9 | !default.mode2v3 10 | *.perspectivev3 11 | !default.perspectivev3 12 | *.xcworkspace 13 | !default.xcworkspace 14 | xcuserdata 15 | profile 16 | *.moved-aside 17 | DerivedData 18 | .idea/ 19 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Parts of The World’s Most Powerful Meta-Disclaimer may — or may not — apply. 2 | The latest version of TWMPMD is at http://brockerhoff.net/disclaimer.html . 3 | Caveat Lector. 4 | 5 | The MIT License (MIT) (modified) 6 | 7 | Copyright (c) 2014-2015 Rainer Brockerhoff 8 | 9 | Permission is hereby granted, free of charge, to any person obtaining a copy 10 | of this software, associated source code documentation files (the "Software"), 11 | to deal in the Software without restriction, including without limitation the 12 | rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 13 | sell copies of the Software, and to permit persons to whom the Software is 14 | furnished to do so, subject to the following conditions: 15 | 16 | The above copyright notice and this permission notice shall be included in all 17 | copies or substantial portions of the Software, including classes and functions 18 | taken from the source code. 19 | 20 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 21 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 22 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 23 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 24 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 25 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 26 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | SwiftChecker 2 | ============ 3 | 4 | This is a simple real-world OS X application I wrote for getting up to speed in Swift; I hope it's useful. My main intentions were to learn something about having parts in Swift and parts in ObjC; also to translate some of my experience with asynchronous execution to Swift. Since GCD and blocks/closures are already a part of system services and the C language (contrary to some people who claim they're ObjC/Cocoa APIs), I found that it's easy to call them from Swift either directly or with some small convenience wrappers. After some learning, I was able to delete the ObjC parts in this latest version. 5 | 6 | The application displays a single window containing a table of running applications/processes (user space only). 7 | 8 | For each process, it displays the icon, name and containing folder, as well as sandbox status (if present) and summaries for signing certificates (if present). 9 | 10 | Updating the table might potentially take some time if the system is very busy, since code signatures and icons will probably have to be loaded from disk. To speed this up, a simple "Future" class is implemented and used to perform these accesses asynchronously. In my initial timing tests, this accelerated table refresh by just under 4x — quite fair on a 4-core machine. In subsequent versions, the various filter/map/sort operations are evaluated lazily, and only the changes are processed; so the impact on table refresh is almost imperceptible. Still, I left the Futures in for evaluation. 11 | 12 | The Timing.swift file contains timing and benchmarking utilities which might come handy in other projects. The classes and functions in Future.swift can also be used elsewhere. 13 | 14 | The project should build and run with no errors, warnings or crashes on the latest versions of OS X 10.11 and Xcode 7.2. In theory if you set the target version to 10.9 it should work there (but I haven't tried); note that current Xcode 7.x ships only with the 10.11 SDK. 15 | 16 | There are copious comments that, hopefully, explain some of the design decisions and workarounds where necessary. I'm trying out various comment styles and placements; along with the new whitespace conventions, this will hopefully converge to a new and consistent coding style for my Swift source code. 17 | 18 | The Xcode project itself is largely unchanged from the default setup and options. Exceptions: 19 | - The test target and source file are there, but I didn't do anything with them. 20 | - Some build options have been set, most importantly "treat warnings as errors", and "-D DEBUG" in "Other Swift Flags". 21 | 22 | Known issues: 23 | - The BenchmarkParallel() functions take way too long; I'm still testing the benchmarks. 24 | - The Future and FutureThrows classes should have similar APIs. 25 | 26 | Recent changes: 27 | - Fix for Xcode 7.2 - removed withUnsafeMutablePointer() calls which are no longer necessary in my case. 28 | - Fix for Xcode 7.1 beta 3 - standard library changes for print() and other functions. Serialize print() to main thread. 29 | - Fix for Xcode 7 beta 4 - substitute UnsafeMutablePointer for Unmanaged arguments to the Security framework 30 | - Fix for Xcode 7 beta 3 - the change dictionary parameter to observeValueForKeyPath is now more specific 31 | - Fixes for Xcode 7 beta 2 - autoconverted to Swift 2, throwing futures, guards, new syntax elsewhere, QuickHelp comments 32 | - Fixes for Xcode 6.3 beta 3 - UInt is gone almost everywhere 33 | - Fixes for Xcode 6.3 beta 2 - more restricted @autoclosure, Future(expression) deleted, more optionals changed 34 | - Fixes for Xcode 6.1GM - less casts needed, app delegate must be public, changes to optionals 35 | - Fixed a rare crash when the last app in the table quit. 36 | - Futures now use a custom queue to avoid the 64-thread limit on the main queues. 37 | - Still messing around with the benchmarking functions. 38 | - Fixed a bug when applications were automatically quit. 39 | - Small cosmetic fixes. 40 | - __conversion() taken out. 41 | - Some more refactoring for less code. 42 | - For Debug builds various timing information is printed to the console. 43 | - General source and comments reorganization, hopefully for better readability. 44 | - The ProcessInfo class has been split off into its own source file. 45 | - A new FutureDebug class is available. 46 | - A new Timing.swift file contains various timing and benchmarking utilities. 47 | - The table is now updated automatically and the refresh button has been removed. 48 | - The ObjCStuff.m and .h files have been removed since I solved the porting problems. 49 | 50 | --- 51 | Check out more Swift stuff [on my blog](http://brockerhoff.net/blog/tag/swift). 52 | Please email comments to . This is intended as sample code, not as a collaborative open source project. Read the license for details. 53 | -------------------------------------------------------------------------------- /SwiftChecker.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 744A95751990074000883027 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74C2748A19411122006C9A3B /* AppDelegate.swift */; }; 11 | 744A95761990074200883027 /* Future.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74C274A819492CD9006C9A3B /* Future.swift */; }; 12 | 744A95771990074500883027 /* ProcessInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7481175D1978A20800D93AA5 /* ProcessInfo.swift */; }; 13 | 744A95781990074700883027 /* Timing.swift in Sources */ = {isa = PBXBuildFile; fileRef = 741C73ED1976F45E004AD6AA /* Timing.swift */; }; 14 | 747A454E194A3888005359A3 /* SwiftChecker.xib in Resources */ = {isa = PBXBuildFile; fileRef = 747A454D194A3888005359A3 /* SwiftChecker.xib */; }; 15 | 74BA4469196091E600F94DE7 /* Credits.rtf in Resources */ = {isa = PBXBuildFile; fileRef = 74BA4467196090C800F94DE7 /* Credits.rtf */; }; 16 | 74C2748719411122006C9A3B /* main.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74C2748619411122006C9A3B /* main.swift */; }; 17 | 74C2749C19411122006C9A3B /* SwiftCheckerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74C2749B19411122006C9A3B /* SwiftCheckerTests.swift */; }; 18 | /* End PBXBuildFile section */ 19 | 20 | /* Begin PBXContainerItemProxy section */ 21 | 74C2749619411122006C9A3B /* PBXContainerItemProxy */ = { 22 | isa = PBXContainerItemProxy; 23 | containerPortal = 74C2747919411122006C9A3B /* Project object */; 24 | proxyType = 1; 25 | remoteGlobalIDString = 74C2748019411122006C9A3B; 26 | remoteInfo = SwiftChecker; 27 | }; 28 | /* End PBXContainerItemProxy section */ 29 | 30 | /* Begin PBXFileReference section */ 31 | 741C73ED1976F45E004AD6AA /* Timing.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Timing.swift; sourceTree = ""; }; 32 | 745B96C019492EB300962E61 /* SwiftChecker-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "SwiftChecker-Bridging-Header.h"; sourceTree = ""; }; 33 | 747A454D194A3888005359A3 /* SwiftChecker.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = SwiftChecker.xib; sourceTree = ""; }; 34 | 7481175D1978A20800D93AA5 /* ProcessInfo.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ProcessInfo.swift; sourceTree = ""; }; 35 | 74BA4467196090C800F94DE7 /* Credits.rtf */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.rtf; path = Credits.rtf; sourceTree = ""; }; 36 | 74C2748119411122006C9A3B /* SwiftChecker.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = SwiftChecker.app; sourceTree = BUILT_PRODUCTS_DIR; }; 37 | 74C2748519411122006C9A3B /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 38 | 74C2748619411122006C9A3B /* main.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = main.swift; sourceTree = ""; }; 39 | 74C2748A19411122006C9A3B /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 40 | 74C2749519411122006C9A3B /* SwiftCheckerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = SwiftCheckerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 41 | 74C2749A19411122006C9A3B /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 42 | 74C2749B19411122006C9A3B /* SwiftCheckerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwiftCheckerTests.swift; sourceTree = ""; }; 43 | 74C274A5194115C1006C9A3B /* .gitignore */ = {isa = PBXFileReference; lastKnownFileType = text; path = .gitignore; sourceTree = SOURCE_ROOT; }; 44 | 74C274A6194115C1006C9A3B /* LICENSE */ = {isa = PBXFileReference; lastKnownFileType = text; path = LICENSE; sourceTree = SOURCE_ROOT; }; 45 | 74C274A7194115C1006C9A3B /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = SOURCE_ROOT; }; 46 | 74C274A819492CD9006C9A3B /* Future.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Future.swift; sourceTree = ""; }; 47 | /* End PBXFileReference section */ 48 | 49 | /* Begin PBXFrameworksBuildPhase section */ 50 | 74C2747E19411122006C9A3B /* Frameworks */ = { 51 | isa = PBXFrameworksBuildPhase; 52 | buildActionMask = 2147483647; 53 | files = ( 54 | ); 55 | runOnlyForDeploymentPostprocessing = 0; 56 | }; 57 | 74C2749219411122006C9A3B /* Frameworks */ = { 58 | isa = PBXFrameworksBuildPhase; 59 | buildActionMask = 2147483647; 60 | files = ( 61 | ); 62 | runOnlyForDeploymentPostprocessing = 0; 63 | }; 64 | /* End PBXFrameworksBuildPhase section */ 65 | 66 | /* Begin PBXGroup section */ 67 | 74C2747819411122006C9A3B = { 68 | isa = PBXGroup; 69 | children = ( 70 | 74C2748319411122006C9A3B /* SwiftChecker */, 71 | 74C2749819411122006C9A3B /* SwiftCheckerTests */, 72 | 74C2748219411122006C9A3B /* Products */, 73 | ); 74 | sourceTree = ""; 75 | }; 76 | 74C2748219411122006C9A3B /* Products */ = { 77 | isa = PBXGroup; 78 | children = ( 79 | 74C2748119411122006C9A3B /* SwiftChecker.app */, 80 | 74C2749519411122006C9A3B /* SwiftCheckerTests.xctest */, 81 | ); 82 | name = Products; 83 | sourceTree = ""; 84 | }; 85 | 74C2748319411122006C9A3B /* SwiftChecker */ = { 86 | isa = PBXGroup; 87 | children = ( 88 | 74C274A5194115C1006C9A3B /* .gitignore */, 89 | 74BA4467196090C800F94DE7 /* Credits.rtf */, 90 | 74C274A6194115C1006C9A3B /* LICENSE */, 91 | 74C274A7194115C1006C9A3B /* README.md */, 92 | 74C2748A19411122006C9A3B /* AppDelegate.swift */, 93 | 74C274A819492CD9006C9A3B /* Future.swift */, 94 | 7481175D1978A20800D93AA5 /* ProcessInfo.swift */, 95 | 741C73ED1976F45E004AD6AA /* Timing.swift */, 96 | 74C2748419411122006C9A3B /* Supporting Files */, 97 | 745B96C019492EB300962E61 /* SwiftChecker-Bridging-Header.h */, 98 | ); 99 | path = SwiftChecker; 100 | sourceTree = ""; 101 | }; 102 | 74C2748419411122006C9A3B /* Supporting Files */ = { 103 | isa = PBXGroup; 104 | children = ( 105 | 74C2748519411122006C9A3B /* Info.plist */, 106 | 747A454D194A3888005359A3 /* SwiftChecker.xib */, 107 | 74C2748619411122006C9A3B /* main.swift */, 108 | ); 109 | name = "Supporting Files"; 110 | sourceTree = ""; 111 | }; 112 | 74C2749819411122006C9A3B /* SwiftCheckerTests */ = { 113 | isa = PBXGroup; 114 | children = ( 115 | 74C2749B19411122006C9A3B /* SwiftCheckerTests.swift */, 116 | 74C2749919411122006C9A3B /* Supporting Files */, 117 | ); 118 | path = SwiftCheckerTests; 119 | sourceTree = ""; 120 | }; 121 | 74C2749919411122006C9A3B /* Supporting Files */ = { 122 | isa = PBXGroup; 123 | children = ( 124 | 74C2749A19411122006C9A3B /* Info.plist */, 125 | ); 126 | name = "Supporting Files"; 127 | sourceTree = ""; 128 | }; 129 | /* End PBXGroup section */ 130 | 131 | /* Begin PBXNativeTarget section */ 132 | 74C2748019411122006C9A3B /* SwiftChecker */ = { 133 | isa = PBXNativeTarget; 134 | buildConfigurationList = 74C2749F19411122006C9A3B /* Build configuration list for PBXNativeTarget "SwiftChecker" */; 135 | buildPhases = ( 136 | 74C2747D19411122006C9A3B /* Sources */, 137 | 74C2747E19411122006C9A3B /* Frameworks */, 138 | 74C2747F19411122006C9A3B /* Resources */, 139 | ); 140 | buildRules = ( 141 | ); 142 | dependencies = ( 143 | ); 144 | name = SwiftChecker; 145 | productName = SwiftChecker; 146 | productReference = 74C2748119411122006C9A3B /* SwiftChecker.app */; 147 | productType = "com.apple.product-type.application"; 148 | }; 149 | 74C2749419411122006C9A3B /* SwiftCheckerTests */ = { 150 | isa = PBXNativeTarget; 151 | buildConfigurationList = 74C274A219411122006C9A3B /* Build configuration list for PBXNativeTarget "SwiftCheckerTests" */; 152 | buildPhases = ( 153 | 74C2749119411122006C9A3B /* Sources */, 154 | 74C2749219411122006C9A3B /* Frameworks */, 155 | 74C2749319411122006C9A3B /* Resources */, 156 | ); 157 | buildRules = ( 158 | ); 159 | dependencies = ( 160 | 74C2749719411122006C9A3B /* PBXTargetDependency */, 161 | ); 162 | name = SwiftCheckerTests; 163 | productName = SwiftCheckerTests; 164 | productReference = 74C2749519411122006C9A3B /* SwiftCheckerTests.xctest */; 165 | productType = "com.apple.product-type.bundle.unit-test"; 166 | }; 167 | /* End PBXNativeTarget section */ 168 | 169 | /* Begin PBXProject section */ 170 | 74C2747919411122006C9A3B /* Project object */ = { 171 | isa = PBXProject; 172 | attributes = { 173 | LastSwiftUpdateCheck = 0700; 174 | LastUpgradeCheck = 0600; 175 | ORGANIZATIONNAME = "Rainer Brockerhoff"; 176 | TargetAttributes = { 177 | 74C2748019411122006C9A3B = { 178 | CreatedOnToolsVersion = 6.0; 179 | }; 180 | 74C2749419411122006C9A3B = { 181 | CreatedOnToolsVersion = 6.0; 182 | TestTargetID = 74C2748019411122006C9A3B; 183 | }; 184 | }; 185 | }; 186 | buildConfigurationList = 74C2747C19411122006C9A3B /* Build configuration list for PBXProject "SwiftChecker" */; 187 | compatibilityVersion = "Xcode 3.2"; 188 | developmentRegion = English; 189 | hasScannedForEncodings = 0; 190 | knownRegions = ( 191 | en, 192 | Base, 193 | ); 194 | mainGroup = 74C2747819411122006C9A3B; 195 | productRefGroup = 74C2748219411122006C9A3B /* Products */; 196 | projectDirPath = ""; 197 | projectRoot = ""; 198 | targets = ( 199 | 74C2748019411122006C9A3B /* SwiftChecker */, 200 | 74C2749419411122006C9A3B /* SwiftCheckerTests */, 201 | ); 202 | }; 203 | /* End PBXProject section */ 204 | 205 | /* Begin PBXResourcesBuildPhase section */ 206 | 74C2747F19411122006C9A3B /* Resources */ = { 207 | isa = PBXResourcesBuildPhase; 208 | buildActionMask = 2147483647; 209 | files = ( 210 | 747A454E194A3888005359A3 /* SwiftChecker.xib in Resources */, 211 | 74BA4469196091E600F94DE7 /* Credits.rtf in Resources */, 212 | ); 213 | runOnlyForDeploymentPostprocessing = 0; 214 | }; 215 | 74C2749319411122006C9A3B /* Resources */ = { 216 | isa = PBXResourcesBuildPhase; 217 | buildActionMask = 2147483647; 218 | files = ( 219 | ); 220 | runOnlyForDeploymentPostprocessing = 0; 221 | }; 222 | /* End PBXResourcesBuildPhase section */ 223 | 224 | /* Begin PBXSourcesBuildPhase section */ 225 | 74C2747D19411122006C9A3B /* Sources */ = { 226 | isa = PBXSourcesBuildPhase; 227 | buildActionMask = 2147483647; 228 | files = ( 229 | 744A95761990074200883027 /* Future.swift in Sources */, 230 | 744A95781990074700883027 /* Timing.swift in Sources */, 231 | 744A95751990074000883027 /* AppDelegate.swift in Sources */, 232 | 74C2748719411122006C9A3B /* main.swift in Sources */, 233 | 744A95771990074500883027 /* ProcessInfo.swift in Sources */, 234 | ); 235 | runOnlyForDeploymentPostprocessing = 0; 236 | }; 237 | 74C2749119411122006C9A3B /* Sources */ = { 238 | isa = PBXSourcesBuildPhase; 239 | buildActionMask = 2147483647; 240 | files = ( 241 | 74C2749C19411122006C9A3B /* SwiftCheckerTests.swift in Sources */, 242 | ); 243 | runOnlyForDeploymentPostprocessing = 0; 244 | }; 245 | /* End PBXSourcesBuildPhase section */ 246 | 247 | /* Begin PBXTargetDependency section */ 248 | 74C2749719411122006C9A3B /* PBXTargetDependency */ = { 249 | isa = PBXTargetDependency; 250 | target = 74C2748019411122006C9A3B /* SwiftChecker */; 251 | targetProxy = 74C2749619411122006C9A3B /* PBXContainerItemProxy */; 252 | }; 253 | /* End PBXTargetDependency section */ 254 | 255 | /* Begin XCBuildConfiguration section */ 256 | 74C2749D19411122006C9A3B /* Debug */ = { 257 | isa = XCBuildConfiguration; 258 | buildSettings = { 259 | ALWAYS_SEARCH_USER_PATHS = NO; 260 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 261 | CLANG_CXX_LIBRARY = "libc++"; 262 | CLANG_ENABLE_MODULES = YES; 263 | CLANG_ENABLE_OBJC_ARC = YES; 264 | CLANG_WARN_BOOL_CONVERSION = YES; 265 | CLANG_WARN_CONSTANT_CONVERSION = YES; 266 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 267 | CLANG_WARN_EMPTY_BODY = YES; 268 | CLANG_WARN_ENUM_CONVERSION = YES; 269 | CLANG_WARN_INT_CONVERSION = YES; 270 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 271 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 272 | CLANG_WARN_UNREACHABLE_CODE = YES; 273 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 274 | CODE_SIGN_IDENTITY = "-"; 275 | COPY_PHASE_STRIP = NO; 276 | ENABLE_STRICT_OBJC_MSGSEND = YES; 277 | GCC_C_LANGUAGE_STANDARD = gnu99; 278 | GCC_DYNAMIC_NO_PIC = NO; 279 | GCC_OPTIMIZATION_LEVEL = 0; 280 | GCC_PREPROCESSOR_DEFINITIONS = ( 281 | "DEBUG=1", 282 | "$(inherited)", 283 | ); 284 | GCC_SYMBOLS_PRIVATE_EXTERN = NO; 285 | GCC_TREAT_WARNINGS_AS_ERRORS = YES; 286 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 287 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 288 | GCC_WARN_SHADOW = YES; 289 | GCC_WARN_UNDECLARED_SELECTOR = YES; 290 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 291 | GCC_WARN_UNUSED_FUNCTION = YES; 292 | GCC_WARN_UNUSED_VARIABLE = YES; 293 | MACOSX_DEPLOYMENT_TARGET = 10.10; 294 | METAL_ENABLE_DEBUG_INFO = YES; 295 | ONLY_ACTIVE_ARCH = YES; 296 | OTHER_SWIFT_FLAGS = "-D DEBUG"; 297 | SDKROOT = macosx; 298 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 299 | }; 300 | name = Debug; 301 | }; 302 | 74C2749E19411122006C9A3B /* Release */ = { 303 | isa = XCBuildConfiguration; 304 | buildSettings = { 305 | ALWAYS_SEARCH_USER_PATHS = NO; 306 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 307 | CLANG_CXX_LIBRARY = "libc++"; 308 | CLANG_ENABLE_MODULES = YES; 309 | CLANG_ENABLE_OBJC_ARC = YES; 310 | CLANG_WARN_BOOL_CONVERSION = YES; 311 | CLANG_WARN_CONSTANT_CONVERSION = YES; 312 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 313 | CLANG_WARN_EMPTY_BODY = YES; 314 | CLANG_WARN_ENUM_CONVERSION = YES; 315 | CLANG_WARN_INT_CONVERSION = YES; 316 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 317 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 318 | CLANG_WARN_UNREACHABLE_CODE = YES; 319 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 320 | CODE_SIGN_IDENTITY = "-"; 321 | COPY_PHASE_STRIP = YES; 322 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 323 | ENABLE_NS_ASSERTIONS = NO; 324 | ENABLE_STRICT_OBJC_MSGSEND = YES; 325 | GCC_C_LANGUAGE_STANDARD = gnu99; 326 | GCC_TREAT_WARNINGS_AS_ERRORS = YES; 327 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 328 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 329 | GCC_WARN_SHADOW = YES; 330 | GCC_WARN_UNDECLARED_SELECTOR = YES; 331 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 332 | GCC_WARN_UNUSED_FUNCTION = YES; 333 | GCC_WARN_UNUSED_VARIABLE = YES; 334 | MACOSX_DEPLOYMENT_TARGET = 10.10; 335 | METAL_ENABLE_DEBUG_INFO = NO; 336 | SDKROOT = macosx; 337 | }; 338 | name = Release; 339 | }; 340 | 74C274A019411122006C9A3B /* Debug */ = { 341 | isa = XCBuildConfiguration; 342 | buildSettings = { 343 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 344 | CLANG_ENABLE_MODULES = YES; 345 | CLANG_ENABLE_OBJC_ARC = NO; 346 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 347 | COMBINE_HIDPI_IMAGES = YES; 348 | GCC_TREAT_WARNINGS_AS_ERRORS = YES; 349 | INFOPLIST_FILE = SwiftChecker/Info.plist; 350 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks"; 351 | PRODUCT_NAME = "$(TARGET_NAME)"; 352 | SDKROOT = macosx; 353 | SWIFT_OBJC_BRIDGING_HEADER = "SwiftChecker/SwiftChecker-Bridging-Header.h"; 354 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 355 | SWIFT_WHOLE_MODULE_OPTIMIZATION = NO; 356 | }; 357 | name = Debug; 358 | }; 359 | 74C274A119411122006C9A3B /* Release */ = { 360 | isa = XCBuildConfiguration; 361 | buildSettings = { 362 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 363 | CLANG_ENABLE_MODULES = YES; 364 | CLANG_ENABLE_OBJC_ARC = NO; 365 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 366 | COMBINE_HIDPI_IMAGES = YES; 367 | GCC_TREAT_WARNINGS_AS_ERRORS = YES; 368 | INFOPLIST_FILE = SwiftChecker/Info.plist; 369 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks"; 370 | PRODUCT_NAME = "$(TARGET_NAME)"; 371 | SDKROOT = macosx; 372 | SWIFT_OBJC_BRIDGING_HEADER = "SwiftChecker/SwiftChecker-Bridging-Header.h"; 373 | SWIFT_WHOLE_MODULE_OPTIMIZATION = YES; 374 | }; 375 | name = Release; 376 | }; 377 | 74C274A319411122006C9A3B /* Debug */ = { 378 | isa = XCBuildConfiguration; 379 | buildSettings = { 380 | BUNDLE_LOADER = "$(BUILT_PRODUCTS_DIR)/SwiftChecker.app/Contents/MacOS/SwiftChecker"; 381 | COMBINE_HIDPI_IMAGES = YES; 382 | FRAMEWORK_SEARCH_PATHS = ( 383 | "$(DEVELOPER_FRAMEWORKS_DIR)", 384 | "$(inherited)", 385 | ); 386 | GCC_PREPROCESSOR_DEFINITIONS = ( 387 | "DEBUG=1", 388 | "$(inherited)", 389 | ); 390 | INFOPLIST_FILE = SwiftCheckerTests/Info.plist; 391 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; 392 | METAL_ENABLE_DEBUG_INFO = YES; 393 | PRODUCT_NAME = "$(TARGET_NAME)"; 394 | TEST_HOST = "$(BUNDLE_LOADER)"; 395 | }; 396 | name = Debug; 397 | }; 398 | 74C274A419411122006C9A3B /* Release */ = { 399 | isa = XCBuildConfiguration; 400 | buildSettings = { 401 | BUNDLE_LOADER = "$(BUILT_PRODUCTS_DIR)/SwiftChecker.app/Contents/MacOS/SwiftChecker"; 402 | COMBINE_HIDPI_IMAGES = YES; 403 | FRAMEWORK_SEARCH_PATHS = ( 404 | "$(DEVELOPER_FRAMEWORKS_DIR)", 405 | "$(inherited)", 406 | ); 407 | INFOPLIST_FILE = SwiftCheckerTests/Info.plist; 408 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; 409 | METAL_ENABLE_DEBUG_INFO = NO; 410 | PRODUCT_NAME = "$(TARGET_NAME)"; 411 | TEST_HOST = "$(BUNDLE_LOADER)"; 412 | }; 413 | name = Release; 414 | }; 415 | /* End XCBuildConfiguration section */ 416 | 417 | /* Begin XCConfigurationList section */ 418 | 74C2747C19411122006C9A3B /* Build configuration list for PBXProject "SwiftChecker" */ = { 419 | isa = XCConfigurationList; 420 | buildConfigurations = ( 421 | 74C2749D19411122006C9A3B /* Debug */, 422 | 74C2749E19411122006C9A3B /* Release */, 423 | ); 424 | defaultConfigurationIsVisible = 0; 425 | defaultConfigurationName = Release; 426 | }; 427 | 74C2749F19411122006C9A3B /* Build configuration list for PBXNativeTarget "SwiftChecker" */ = { 428 | isa = XCConfigurationList; 429 | buildConfigurations = ( 430 | 74C274A019411122006C9A3B /* Debug */, 431 | 74C274A119411122006C9A3B /* Release */, 432 | ); 433 | defaultConfigurationIsVisible = 0; 434 | defaultConfigurationName = Release; 435 | }; 436 | 74C274A219411122006C9A3B /* Build configuration list for PBXNativeTarget "SwiftCheckerTests" */ = { 437 | isa = XCConfigurationList; 438 | buildConfigurations = ( 439 | 74C274A319411122006C9A3B /* Debug */, 440 | 74C274A419411122006C9A3B /* Release */, 441 | ); 442 | defaultConfigurationIsVisible = 0; 443 | defaultConfigurationName = Release; 444 | }; 445 | /* End XCConfigurationList section */ 446 | }; 447 | rootObject = 74C2747919411122006C9A3B /* Project object */; 448 | } 449 | -------------------------------------------------------------------------------- /SwiftChecker/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // SwiftChecker 4 | // 5 | // Created by Rainer Brockerhoff on 5/6/14. 6 | // Copyright (c) 2014-2015 Rainer Brockerhoff. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | 11 | //MARK: private class AppDelegate 12 | // ================================================================================ 13 | /** 14 | # AppDelegate 15 | This class is the Application delegate and also drives the table view. It's easier to make a single class for 16 | such a simple app. 17 | 18 | Since Xcode 6.1 the application delegate class (and I suppose others loaded from .xibs) must be public. 19 | */ 20 | public class AppDelegate: NSObject, NSApplicationDelegate, NSTableViewDataSource, NSTableViewDelegate { 21 | 22 | // -------------------------------------------------------------------------------- 23 | //MARK: properties and outlets 24 | 25 | /** 26 | This is a dictionary of the currently running processes — the user-visible ones, that is. 27 | 28 | The key is a NSRunningApplication and the value is a ProcessInfo, which is actually built from that key — 29 | it contains data generated and cached for display. 30 | 31 | I start out with an empty dict and update it dynamically inside _update(). 32 | 33 | In previous versions this was keyed by NSRunningApplication.processIdentifier (pid) but it 34 | turns out that applications can be quit automatically (losing their pid) and then restart and be assigned 35 | a new one, messing up the display. 36 | 37 | Using NSRunningApplication as the dictionary key leverages the docs' recommendation: 38 | "Do not rely on pid for comparing processes, instead compare NSRunningApplication instances using isEqual:". 39 | */ 40 | private var procdict: [ NSRunningApplication : ProcessInfo ] = [ : ] 41 | 42 | /** 43 | This Array contains cached data for the current list of processes in the GUI. 44 | It is rebuilt from the procdict Dictionary inside _update(). 45 | 46 | I start out with an empty array because AppDelegate.numberOfRowsInTableView() is generally 47 | called several times before the array is filled. 48 | */ 49 | private var processes: [ ProcessInfo ] = [ ] 50 | 51 | // These are normal outlets for UI elements. 52 | @IBOutlet var theWindow: NSWindow! 53 | @IBOutlet var theTable: NSTableView! 54 | 55 | // -------------------------------------------------------------------------------- 56 | //MARK: NSTableViewDataSource and NSTableViewDelegate methods 57 | 58 | /// This NSTableViewDataSource method returns the number of processes: one per row. 59 | public func numberOfRowsInTableView(tableView: NSTableView) -> Int { 60 | return processes.count 61 | } 62 | 63 | /// This NSTableViewDelegate method gets a NSTableCellView from the xib and 64 | /// populates it with the process's icon or text. 65 | public func tableView(tableView: NSTableView, viewForTableColumn tableColumn: NSTableColumn?, row: Int) -> NSView? { 66 | guard row < processes.count else { // new guard syntax prevents a rare crash when the last app in the table quits 67 | return nil 68 | } 69 | let info = processes[row] 70 | if let identifier = tableColumn?.identifier, 71 | view = tableView.makeViewWithIdentifier(identifier, owner: self) as? NSTableCellView { 72 | // Note that in the xib "1" and "2" are identifiers for both NSTableColumns and NSTableCellViews. 73 | switch identifier { 74 | case "1": 75 | view.imageView!.image = info.icon // blocks until the icon is ready 76 | case "2": 77 | view.textField!.attributedStringValue = info.text // blocks until the text is ready 78 | default: 79 | break 80 | } 81 | return view 82 | } 83 | return nil 84 | } 85 | 86 | /// This NSTableViewDelegate method just prevents any row from being selected. 87 | public func tableView(tableView: NSTableView, shouldSelectRow row: Int) -> Bool { 88 | return false 89 | } 90 | 91 | // -------------------------------------------------------------------------------- 92 | //MARK: observers 93 | 94 | /// This KVO observer is called whenever the list of running applications changes. 95 | public override func observeValueForKeyPath(keyPath: String?, ofObject object: AnyObject?, change: [String : AnyObject]?, context: UnsafeMutablePointer) { 96 | var apps: NSArray? = nil 97 | 98 | // This uses the new guard statement to return early if there's no change dictionary. 99 | guard let change = change else { 100 | return 101 | } 102 | 103 | if let rv = change[NSKeyValueChangeKindKey] as? UInt, 104 | kind = NSKeyValueChange(rawValue: rv) { 105 | switch kind { 106 | case .Insertion: 107 | // Get the inserted apps (usually only one, but you never know) 108 | apps = change[NSKeyValueChangeNewKey] as? NSArray 109 | case .Removal: 110 | // Get the removed apps (usually only one, but you never know) 111 | apps = change[NSKeyValueChangeOldKey] as? NSArray 112 | default: 113 | return // nothing to refresh; should never happen, but... 114 | } 115 | } 116 | 117 | // update the table with the changes (if any) 118 | _update(apps) 119 | } 120 | 121 | // -------------------------------------------------------------------------------- 122 | //MARK: NSApplicationDelegate methods 123 | 124 | /// This NSApplicationDelegate method is called as soon as the app's icon begins 125 | /// bouncing in the Dock. 126 | public func applicationWillFinishLaunching(aNotification: NSNotification) { 127 | 128 | // This is one of several prints of timing information that works only on Debug builds. 129 | // Note that startup is defined/initialized inside main.swift! 130 | Print("willFinish; \(startup.age) after startup") 131 | 132 | let workspace = NSWorkspace.sharedWorkspace() 133 | 134 | // This adds the app delegate as observer of NSWorkspace's list of running applications. 135 | // (note that this list only includes processes running inside user space) 136 | workspace.addObserver(self, forKeyPath: "runningApplications", options: [.Old, .New], context: &KVOContext) 137 | 138 | // Update the table with the list of running applications 139 | _update(workspace.runningApplications) 140 | } 141 | 142 | /** This NSApplicationDelegate method is called when all is ready and the app's icon 143 | stops bouncing in the Dock. 144 | */ 145 | public func applicationDidFinishLaunching(Notification: NSNotification) { 146 | Print("didFinish; \(startup.age) after startup") 147 | // Yep, it does nothing else. Early on I had some debugging code in here. 148 | } 149 | 150 | /// This NSApplicationDelegate method quits the app when the window is closed. 151 | public func applicationShouldTerminateAfterLastWindowClosed(theApplication: NSApplication) -> Bool { 152 | return true 153 | } 154 | 155 | /// This NSApplicationDelegate method is called just before termination. 156 | public func applicationWillTerminate(aNotification: NSNotification) { 157 | Print("willTerminate; \(startup.age) after startup") 158 | 159 | // remove the observer we added in applicationWillFinishLaunching 160 | NSWorkspace.sharedWorkspace().removeObserver(self, forKeyPath: "runningApplications") 161 | } 162 | 163 | // -------------------------------------------------------------------------------- 164 | //MARK: private functions 165 | 166 | /** 167 | This function updates the GUI based on an Array of changed NSRunningApplications. 168 | */ 169 | private func _update(apps: NSArray?) { 170 | 171 | /// Proceed if we really have an Array of applications - may be nil. 172 | if let apps = apps as? Array { 173 | var time = TimeStamp("Table refresh") 174 | 175 | /// Use one of the Dictionary extensions to merge the changes into procdict. 176 | procdict.merge(apps) { (app) in 177 | let remove = app.terminated // insert or remove? 178 | let msg = remove ? "Removed " : "Inserted " 179 | Print(msg + app.localizedName!) 180 | return (app, remove ? nil : ProcessInfo(app)) 181 | } 182 | 183 | /// Produce a sorted Array of ProcessInfo as input for the NSTableView. 184 | /// ProcessInfo conforms to Equatable and Comparable, so no predicate is needed. 185 | processes = procdict.values.sort() 186 | 187 | /* All is ready now to reload the table. That is better done on the main thread and 188 | this function will be called before the run loop is started, so I call PerformOnMain() 189 | which is my new way of doing performSelectorOnMainThread: 190 | */ 191 | PerformOnMain { 192 | self.theTable.reloadData() 193 | } 194 | 195 | Print(time.freeze()) 196 | } 197 | } 198 | 199 | } // end of AppDelegate 200 | 201 | //MARK: globals 202 | /// global variable used as unique context for KVO 203 | private var KVOContext: Int = 0 204 | 205 | //MARK: extensions 206 | // -------------------------------------------------------------------------------- 207 | /** 208 | Extensions to Dictionary. SwiftChecker uses only one of those, but they 209 | may be useful elsewhere. 210 | 211 | The idea is to modify the Dictionary, either from an input Sequence of (key,value) tuples, 212 | or from an input Sequence of any type which is then processed by a filter function/closure 213 | to produce the tuples. 214 | 215 | In the latter case, the filter function can return nil to filter out that item 216 | from the input Sequence, return a (key,value) tuple to insert or change an item, or 217 | a (key,nil) tuple to remove an item. See AppDelegate._update() above for an example. 218 | */ 219 | 220 | extension Dictionary { 221 | 222 | /** 223 | Merges a sequence of (key,value) tuples into a Dictionary. 224 | */ 225 | mutating func merge (seq: S) { 226 | var gen = seq.generate() 227 | while let (key, value): (Key, Value) = gen.next() { 228 | self[key] = value 229 | } 230 | } 231 | 232 | /** 233 | Merges a sequence of values into a Dictionary by specifying a filter function. 234 | 235 | The filter function can return nil to filter out that item from the input Sequence, or return a (key,value) 236 | tuple to insert or change an item. In that case, value can be nil to remove the item for that key. 237 | */ 238 | mutating func merge (seq: S, filter: (T) -> (Key, Value?)?) { 239 | var gen = seq.generate() 240 | while let t: T = gen.next() { 241 | if let (key, value): (Key, Value?) = filter(t) { 242 | self[key] = value 243 | } 244 | } 245 | } 246 | 247 | } // end of Dictionary extension 248 | 249 | -------------------------------------------------------------------------------- /SwiftChecker/Credits.rtf: -------------------------------------------------------------------------------- 1 | {\rtf1\ansi\ansicpg1252\cocoartf1324\cocoasubrtf110 2 | {\fonttbl\f0\fnil\fcharset0 HelveticaNeue;} 3 | {\colortbl;\red255\green255\blue255;} 4 | \paperw11900\paperh16840\margl1440\margr1440\vieww10800\viewh8400\viewkind0 5 | \pard\tx566\tx1133\tx1700\tx2267\tx2834\tx3401\tx3968\tx4535\tx5102\tx5669\tx6236\tx6803\pardirnatural\qc 6 | 7 | \f0\fs24 \cf0 Application and source code\ 8 | by {\field{\*\fldinst{HYPERLINK "http://brockerhoff.net/"}}{\fldrslt Rainer Brockerhoff}}\ 9 | \ 10 | \pard\tx529\pardeftab529\pardirnatural\qc 11 | 12 | \fs22 \cf0 \CocoaLigature0 Parts of\ 13 | {\field{\*\fldinst{HYPERLINK "http://brockerhoff.net/disclaimer.html"}}{\fldrslt The World\'92s Most Powerful Meta-Disclaimer}}\ 14 | may \'97 or may not \'97 apply.\ 15 | Caveat Lector.} -------------------------------------------------------------------------------- /SwiftChecker/Future.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Future.swift 3 | // SwiftChecker 4 | // 5 | // Created by Rainer Brockerhoff on 11/6/14. 6 | // Copyright (c) 2014-2015 Rainer Brockerhoff. All rights reserved. 7 | 8 | import Foundation 9 | import Darwin 10 | 11 | /** 12 | Functions and some Future classes to do easy asynchronous stuff. 13 | 14 | A Future is basically a way to reference a result that may take some time to 15 | obtain; while the final value is not 'resolved', you can add it to collections 16 | and do other things with the reference (other than accessing the value). 17 | 18 | In ObjC this is usually implemented as a proxy object which handles value 19 | access transparently, but Swift doesn't have proxy objects and can't subclass 20 | `NSProxy` either - all ObjC objects passed to Swift have to subclass `NSObject`. 21 | */ 22 | 23 | //MARK: public class Future 24 | // ================================================================================ 25 | /** 26 | # Future 27 | This generic class implements a simple Future. 28 | 29 | Use it like this: 30 | ```swift 31 | var aFuture: Future = Future { 32 | ...closure returning someType... 33 | } 34 | ``` 35 | where the closure is supposed to take some time (over 1 ms), therefore worthwhile to be executed 36 | asynchronously. 37 | Type inference works, so the left-hand side on the examples above could usually just be written as 38 | ```swift 39 | var aFuture = Future... 40 | ``` 41 | You can add `aFuture` to collections or pass it around. When you need the result, use 42 | ```swift 43 | aFuture.value 44 | ``` 45 | this will block if the `Future` hasn't resolved (happened) yet. 46 | 47 | You can also test if the `Future` has resolved with `aFuture.resolved`; this will not block. 48 | 49 | Notice that `someType` can be an optional, and then the closure/value may return nil to signal an error or timeout. 50 | */ 51 | public class Future { 52 | 53 | // -------------------------------------------------------------------------------- 54 | //MARK: initializers 55 | 56 | /// This initializer creates and starts a `Future` using the argument closure. 57 | public init(_ work: () -> T) { 58 | _run(work) 59 | } 60 | 61 | // -------------------------------------------------------------------------------- 62 | //MARK: public properties 63 | 64 | /** 65 | This computed property returns the actual `Future` value, and blocks while it is being resolved. 66 | */ 67 | public var value: T { 68 | _lock.lock() 69 | while _result.isEmpty { 70 | _lock.wait() 71 | } 72 | let r = _result[0] 73 | _lock.unlock() 74 | return r 75 | } 76 | 77 | /** 78 | This computed property tests if the `Future` has been resolved. 79 | */ 80 | public var resolved: Bool { 81 | _lock.lock() 82 | let resolved = !_result.isEmpty 83 | _lock.unlock() 84 | return resolved 85 | } 86 | 87 | // -------------------------------------------------------------------------------- 88 | //MARK: private properties 89 | 90 | /** 91 | This property uses a combination mutex/lock to guarantee asynchronous access to the `_result` property. 92 | */ 93 | private let _lock = NSCondition() 94 | 95 | /** 96 | This property contains either an empty array or an array with a single member (the result) when the Future has resolved. 97 | 98 | My first impulse was to implement this as 99 | ```swift 100 | var _result: T 101 | ``` 102 | but this demands a value to be assigned in `init()`, before the `PerformAsync()` call, and I could find no 103 | easy way to generate an empty generic `T`. 104 | 105 | Implementing it as an Optional 106 | ```swift 107 | var _result: T? = nil 108 | ``` 109 | caused a fatal compiler error "LLVM ERROR: unimplemented IRGen feature! non-fixed class layout" 110 | and, even if it worked, might be somewhat ambiguous/tricky to handle if `T` is itself Optional. 111 | *NOTE: this has been fixed in Xcode 6.0b3.* 112 | 113 | So, implementing it as an `array` of `T` obviates the necessity of having an additional `Bool` to signal whether the `Future` has resolved. 114 | */ 115 | var _result: [ T ] = [ ] 116 | 117 | // -------------------------------------------------------------------------------- 118 | //MARK: private functions and methods 119 | 120 | /** 121 | This function is called by the initializer, making subclassing easier. 122 | 123 | The argument closure is performed asynchronously and its value is captured. Access to `_result` is guarded 124 | by the mutex and other threads waiting are unblocked by the `broadcast()` call. 125 | */ 126 | private func _run(work: () -> T) { 127 | PerformAsync { 128 | let value = work() 129 | self._lock.lock() 130 | self._result = [ value ] // note that value is wrapped inside an array! 131 | self._lock.broadcast() 132 | self._lock.unlock() 133 | } 134 | } 135 | 136 | } // end of Future 137 | 138 | // MARK: public class FutureDebug 139 | // ================================================================================ 140 | /** 141 | # FutureDebug 142 | This `Future` subclass is useful for debugging and benchmarking. 143 | 144 | It allows you to measure the resolution time for the `Future` and, if necessary, print it out for debugging. 145 | 146 | Use it like this: 147 | ```swift 148 | var aFuture: FutureDebug = 149 | FutureDebug("label") { 150 | ...closure returning someType... 151 | } 152 | ``` 153 | where the `label` string can also be `nil`. 154 | */ 155 | public class FutureDebug : Future { 156 | 157 | // -------------------------------------------------------------------------------- 158 | //MARK: initializers 159 | 160 | /// This initializer creates and starts a `Future` using the last argument closure. 161 | public init(_ str: String?, _ work: () -> T) { 162 | _time = TimeStamp(str) 163 | super.init(work) 164 | _lock.name = str; 165 | } 166 | 167 | // -------------------------------------------------------------------------------- 168 | //MARK: public properties 169 | 170 | /** 171 | This computed property will return the optional label. 172 | */ 173 | public var label: String? { 174 | return _time.label 175 | } 176 | 177 | /** 178 | This computed property will return the resolution time in seconds, blocking until the `Future` has resolved. 179 | */ 180 | public var seconds: Double { 181 | _lock.lock() 182 | while _result.isEmpty { 183 | _lock.wait() 184 | } 185 | let e = _time.seconds 186 | _lock.unlock() 187 | return e 188 | } 189 | 190 | // -------------------------------------------------------------------------------- 191 | //MARK: private properties 192 | 193 | /** 194 | This internal property is used to measure the resolutiontime and contain the optional label. 195 | */ 196 | private var _time: TimeStamp 197 | 198 | // -------------------------------------------------------------------------------- 199 | //MARK: private functions and methods 200 | 201 | /** 202 | This overrides the actual `Future` execution to measure the resolution time. 203 | 204 | As a convenience, it will print it out if a label has been assigned. 205 | */ 206 | private override func _run(work: () -> T) { 207 | PerformAsync { 208 | let value = work() 209 | self._lock.lock() 210 | 211 | // Freeze the TimeStamp to get the execution time and print it out if 212 | // its label is present. 213 | let str = self._time.freeze() 214 | if self.label != nil { 215 | Print(str) 216 | } 217 | 218 | self._result = [ value ] 219 | self._lock.broadcast() 220 | self._lock.unlock() 221 | } 222 | } 223 | 224 | } // end of FutureDebug 225 | 226 | /// This private `enum` represents either a valid result value or a thrown error. 227 | private enum Result { 228 | case resolved(T) 229 | case error(ErrorType) 230 | } 231 | 232 | //MARK: public class FutureThrows 233 | // ================================================================================ 234 | /** 235 | # FutureThrows 236 | This generic class implements a `Future` that can `throw` an `ErrorType` *(new to Swift 2)*. 237 | 238 | Use it like this: 239 | ```swift 240 | var aFuture: FutureThrows = 241 | FutureThrows { 242 | ...closure returning someType or throw... 243 | } 244 | ``` 245 | where the closure is supposed to take some time (over 1 ms), therefore worthwhile to be executed asynchronously. 246 | Type inference works, so the left-hand side on the example above could usually just be written as 247 | ```swift 248 | var aFuture = FutureThrows... 249 | ``` 250 | You can add `aFuture` to collections or pass it around. When you need the result, use 251 | ```swift 252 | try let afv = aFuture.value() 253 | ``` 254 | followed by `catch`(es), or inside a `rethrows`; this will block if the `Future` hasn't resolved (happened) yet. 255 | 256 | You can also test if the `Future` has resolved with `aFuture.resolved`; this will not block. 257 | 258 | Notice that `someType` can be an optional, but returning `nil` should not be an error. 259 | */ 260 | 261 | public class FutureThrows { 262 | 263 | // -------------------------------------------------------------------------------- 264 | //MARK: initializers 265 | 266 | /// This initializer creates and starts a `Future` using the argument closure. 267 | public init(_ work: () throws -> T) { 268 | _run(work) 269 | } 270 | 271 | // -------------------------------------------------------------------------------- 272 | //MARK: public functions 273 | 274 | /** 275 | This function returns the actual `Future` value, and blocks while it is being resolved. 276 | 277 | In Xcode 7b1 computed properties can't `throw` (yet), so this is a function for now. 278 | */ 279 | public func value() throws -> T { 280 | _lock.lock() 281 | defer { // the new defer syntax is handy since we have different return points 282 | _lock.unlock() 283 | } 284 | while (true) { 285 | guard let _result = _result else { // use new guard statement to loop 286 | _lock.wait() 287 | continue 288 | } 289 | switch _result { 290 | case let .resolved(value): 291 | return value 292 | case let .error(error): 293 | throw error 294 | } 295 | } 296 | } 297 | 298 | // -------------------------------------------------------------------------------- 299 | //MARK: public properties 300 | 301 | /** 302 | This computed property tests if the `Future` has been resolved. 303 | */ 304 | public var resolved: Bool { 305 | _lock.lock() 306 | defer { // again, the new defer syntax makes this shorter 307 | _lock.unlock() 308 | } 309 | return _result != nil ? true : false 310 | } 311 | 312 | // -------------------------------------------------------------------------------- 313 | //MARK: private properties 314 | 315 | /** 316 | This property uses a combination mutex/lock to guarantee asynchronous access to the `_result` property. 317 | */ 318 | private let _lock = NSCondition() 319 | 320 | /** 321 | This Optional property is either `nil`, while the `Future` is resolving, or a 322 | `Result`, which itself can represent a valid result value or a thrown error. 323 | */ 324 | private var _result: Result? = nil; 325 | 326 | // -------------------------------------------------------------------------------- 327 | //MARK: private functions and methods 328 | 329 | /** 330 | This function is called by the initializer, making subclassing easier. 331 | 332 | The argument closure is performed asynchronously and its value is captured. 333 | Access to `_result` is guarded by the mutex and other threads waiting are unblocked by the `broadcast()` call. 334 | */ 335 | private func _run(work: () throws -> T) { 336 | PerformAsync { 337 | let value: Result 338 | do { 339 | try value = Result.resolved(work()) 340 | } catch { 341 | value = Result.error(error) 342 | } 343 | self._lock.lock() 344 | self._result = value 345 | self._lock.broadcast() 346 | self._lock.unlock() 347 | } 348 | } 349 | 350 | } // end of FutureThrows 351 | 352 | // MARK: public class FutureThrowsDebug 353 | // ================================================================================ 354 | /** 355 | # FutureThrowsDebug 356 | This `FutureThrows` subclass is useful for debugging and benchmarking. 357 | 358 | It allows you to measure the resolution time for the `Future` and, if necessary, 359 | print it out for debugging. 360 | 361 | Use it like this: 362 | ```swift 363 | var aFuture: FutureThrowsDebug = 364 | FutureThrowsDebug("label") { 365 | ...closure returning someType or throw... 366 | } 367 | ``` 368 | where the `label` string can also be nil. 369 | */ 370 | public class FutureThrowsDebug : FutureThrows { 371 | 372 | // -------------------------------------------------------------------------------- 373 | //MARK: initializers 374 | 375 | /// This initializer creates and starts a `Future` using the last argument closure. 376 | public init(_ str: String?, _ work: () throws -> T) { 377 | _time = TimeStamp(str) 378 | super.init(work) 379 | _lock.name = str; 380 | } 381 | 382 | // -------------------------------------------------------------------------------- 383 | //MARK: public properties 384 | 385 | /** 386 | This computed property will return the optional label. 387 | */ 388 | public var label: String? { 389 | return _time.label 390 | } 391 | 392 | /** 393 | This computed property will return the resolution time in seconds. Note that it will block until the `Future` has resolved. 394 | */ 395 | public var seconds: Double { 396 | _lock.lock() 397 | while _result == nil { 398 | _lock.wait() 399 | } 400 | let e = _time.seconds 401 | _lock.unlock() 402 | return e 403 | } 404 | 405 | // -------------------------------------------------------------------------------- 406 | //MARK: private properties 407 | 408 | /** 409 | This internal property is used to measure the resolution time and contain the optional label. 410 | */ 411 | private var _time: TimeStamp 412 | 413 | // -------------------------------------------------------------------------------- 414 | //MARK: private functions and methods 415 | 416 | /** 417 | This overrides the actual `Future` execution to measure the resolution time. 418 | 419 | As a convenience, it will print it out if a label has been assigned. 420 | */ 421 | private override func _run(work: () throws -> T) { 422 | PerformAsync { 423 | let value: Result 424 | do { 425 | try value = Result.resolved(work()) 426 | } catch { 427 | value = Result.error(error) 428 | } 429 | self._lock.lock() 430 | 431 | // Freeze the TimeStamp to get the execution time and print it out if 432 | // its label is present. 433 | let str = self._time.freeze() 434 | if self.label != nil { 435 | Print(str) 436 | } 437 | 438 | self._result = value 439 | self._lock.broadcast() 440 | self._lock.unlock() 441 | } 442 | } 443 | 444 | } // end of FutureThrowsDebug 445 | 446 | // -------------------------------------------------------------------------------- 447 | //MARK: convenience debugging function 448 | /** 449 | The following convenience function is for debugging only. For this to work, be sure to 450 | set "-D DEBUG" in "Other Swift Flags" for the Debug build in the Xcode project! 451 | 452 | This used to wrap and serialize the `print()` function to preserve 453 | sanity when invoking it from asynchronous tasks, but apparently that's no 454 | longer necessary with Swift 2.0 and up. 455 | 456 | It does nothing in non-Debug builds, so no need for #if DEBUG lines elsewhere. 457 | */ 458 | 459 | /* Starting with Xcode 7.0/Swift 2.0 there is but a single print function that accepts an 460 | argument list. Currently it seems to be impossible to pass this list down to other functions, 461 | so we have to duplicate what the new print() function does internally. 462 | 463 | Specifically, String() needs to be called on every item in the list before concatenating. 464 | Before realizing this, I had some very entertaining crashes _after_ joinWithSeparator() 465 | silently corrupted memory. 466 | 467 | */ 468 | public func Print(items: Any..., separator: String = " ", terminator: String = "\n") { 469 | #if DEBUG 470 | print(items.map{String($0)}.joinWithSeparator(separator), terminator:terminator) 471 | #endif 472 | } 473 | 474 | // -------------------------------------------------------------------------------- 475 | //MARK: convenience asynchronous functions 476 | /** 477 | Accepts a closure to be performed on the next iteration of the main run loop; 478 | basically an equivalent of `performSelectorOnMainThread:` but with no `object` and 479 | `waitUntilDone:NO`. 480 | 481 | We might do `dispatch_async(dispatch_get_main_queue(), work)` here, but 482 | that may cut in front of other events waiting to be handled in the run loop. 483 | (Thanks to Kyle Sluder for the explanation.) 484 | 485 | `CFRunLoopPerformBlock()` enqueues the block but doesn't wake the runloop up, 486 | so we call `CFRunLoopWakeUp()` to make sure it will run as soon as possible. 487 | */ 488 | public func PerformOnMain(work: () -> Void) { 489 | let loop = NSRunLoop.mainRunLoop().getCFRunLoop() 490 | CFRunLoopPerformBlock(loop, kCFRunLoopCommonModes, work) 491 | CFRunLoopWakeUp(loop) 492 | } 493 | 494 | /** 495 | private concurrent dispatch queue 496 | */ 497 | private let _asyncq = { // global concurrent dispatch queue for PerformAsync 498 | dispatch_queue_create("asyncq", DISPATCH_QUEUE_CONCURRENT) 499 | }() 500 | 501 | /** 502 | Accepts a closure to be performed asynchronously. 503 | */ 504 | public func PerformAsync(work: () -> Void) { 505 | // Comment out the following line and substitute work() to check how the app would run 506 | // without Futures/GCD. 507 | dispatch_async(_asyncq, work) 508 | } 509 | 510 | -------------------------------------------------------------------------------- /SwiftChecker/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | ${EXECUTABLE_NAME} 9 | CFBundleIconFile 10 | 11 | CFBundleIdentifier 12 | net.brockerhoff.${PRODUCT_NAME:rfc1034identifier} 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | ${PRODUCT_NAME} 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | 1.2 21 | CFBundleSignature 22 | ???? 23 | LSMinimumSystemVersion 24 | ${MACOSX_DEPLOYMENT_TARGET} 25 | NSHumanReadableCopyright 26 | Copyright © 2014, 2015 Rainer Brockerhoff 27 | NSPrincipalClass 28 | NSApplication 29 | NSMainNibFile 30 | SwiftChecker 31 | 32 | 33 | -------------------------------------------------------------------------------- /SwiftChecker/ProcessInfo.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ProcessInfo.swift 3 | // SwiftChecker 4 | // 5 | // Created by Rainer Brockerhoff on 17/7/14. 6 | // Copyright (c) 2014-2015 Rainer Brockerhoff. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | 11 | //MARK: public class ProcessInfo 12 | // ================================================================================ 13 | /** 14 | # ProcessInfo 15 | This class represents a running process and corresponds to one row in the `NSTableView`. 16 | 17 | It obtains and caches data displayed by the table. 18 | */ 19 | public class ProcessInfo: Comparable { // Comparable implies Equatable 20 | 21 | // -------------------------------------------------------------------------------- 22 | //MARK: initializers 23 | 24 | /** 25 | This single & only initializer does all the heavy lifting by getting data 26 | from the `NSRunningApplication` parameter. 27 | 28 | It uses Futures to get the bundle icon and to setup the displayed text, which will 29 | also contain the certificate summaries from the code signature. 30 | */ 31 | public init(_ app: NSRunningApplication) { 32 | 33 | // Fetch some values I'll need later on. 34 | let name = app.localizedName! 35 | let url = app.bundleURL! 36 | let fpath = url.URLByDeletingLastPathComponent!.path! 37 | 38 | bundleName = name 39 | 40 | // The icon may be read from disk, so making this a Future may save time. Still, the usual 41 | // time to resolve is under 1 ms in my benchmarks. 42 | _icon = FutureDebug("\tIcon for \(name)") { 43 | let image = app.icon! 44 | image.size = NSSize(width: 64, height: 64) // hardcoded to match the table column size 45 | return image 46 | } 47 | 48 | /* The text is built up in sections and, if a signature is present, this will get the sandboxed 49 | attribute and the signing certificates from the signature and append the summaries. 50 | Reading signatures from disk means a Future is useful, here, too; the usual time to resolve is 51 | between 100 and 400 ms in my benchmarks. 52 | */ 53 | _text = FutureDebug("\tText for \(name)") { 54 | 55 | // Start off with the localized bundle name in bold 56 | var result = NSMutableAttributedString(string: name, attributes: styleBOLD12) 57 | 58 | // Add the architecture as a bonus value 59 | switch app.executableArchitecture { 60 | case NSBundleExecutableArchitectureI386: 61 | result += (" (32-bit)", styleRED) // red text: most apps should be 64 by now 62 | case NSBundleExecutableArchitectureX86_64: 63 | result += " (64-bit)" 64 | default: 65 | break 66 | } 67 | 68 | // Add the containing folder path — path components should be localized, perhaps? 69 | // Check down below for the += operator for NSMutableAttributedStrings. 70 | result += (" in “\(fpath)”\n...", styleNORM12) 71 | 72 | // GetCodeSignatureForURL() may return nil, an empty dictionary, or a dictionary with parts missing. 73 | if let signature = GetCodeSignatureForURL(url) { 74 | 75 | // The entitlements dictionary may also be missing. 76 | if let entitlements = signature["entitlements-dict"] as? NSDictionary, 77 | sandbox = entitlements["com.apple.security.app-sandbox"] as? NSNumber { 78 | 79 | // Even if the sandbox entitlement is present it may be 0 or NO 80 | if sandbox.boolValue { 81 | result += ("sandboxed, ", styleBLUE) // blue text to stand out 82 | } 83 | } 84 | 85 | result += "signed " 86 | 87 | // The certificates array may be empty or missing entirely. Finally it's possible to cast 88 | // directly to Array instead of going over CFTypeRef. 89 | let certificates = signature["certificates"] as? Array 90 | 91 | // Using optional chaining here checks for both empty or missing. 92 | if certificates?.count > 0 { 93 | 94 | // This gets the summaries for all certificates. 95 | let summaries = certificates!.map { (cert) -> String in 96 | return SecCertificateCopySubjectSummary(cert) as String 97 | } 98 | 99 | // Concatenating with commas is easy now 100 | result += "by " + summaries.joinWithSeparator(", ") 101 | 102 | } else { // signed but no certificates 103 | result += "without certificates" 104 | } 105 | 106 | } else { // code signature missing 107 | result += ("unsigned", styleRED) // red text to stand out; most processes should be signed 108 | } 109 | 110 | return result 111 | } 112 | } 113 | 114 | // -------------------------------------------------------------------------------- 115 | //MARK: public properties 116 | 117 | /** 118 | This read-only property contains the localized bundle name (without extension). 119 | */ 120 | public let bundleName: String 121 | 122 | /** 123 | This is a computed property (so it must be var). It will get the future 124 | value from the `_icon` Future, meaning it will block while the icon is obtained. 125 | */ 126 | var icon: NSImage { 127 | return _icon.value 128 | } 129 | 130 | /** 131 | This is a computed property (so it must be var). It will get the future 132 | value from the `_text` Future, meaning it will block while the text is obtained. 133 | */ 134 | var text: NSAttributedString { 135 | return _text.value 136 | } 137 | 138 | // -------------------------------------------------------------------------------- 139 | //MARK: private properties 140 | 141 | /** 142 | This is the backing property for the (computed) icon property. 143 | */ 144 | private let _icon: Future 145 | 146 | /** 147 | This is the backing property for the (computed) text property. 148 | */ 149 | private let _text: Future 150 | 151 | } // end of ProcessInfo 152 | 153 | // ================================================================================ 154 | /** 155 | The following operators, globals and functions are here because they're used or 156 | required by the `ProcessInfo` class. 157 | */ 158 | 159 | // -------------------------------------------------------------------------------- 160 | //MARK: public operators for comparing `ProcessInfo`s 161 | /** 162 | must define < and == to conform to the `Comparable` and `Equatable` protocols. 163 | 164 | Here I use the `bundleName` in Finder order, convenient for sorting. 165 | */ 166 | public func < (lhs: ProcessInfo, rhs: ProcessInfo) -> Bool { // required by Comparable 167 | return lhs.bundleName.localizedStandardCompare(rhs.bundleName) == NSComparisonResult.OrderedAscending 168 | } 169 | 170 | public func == (lhs: ProcessInfo, rhs: ProcessInfo) -> Bool { // required by Equatable and Comparable 171 | return lhs.bundleName.localizedStandardCompare(rhs.bundleName) == NSComparisonResult.OrderedSame 172 | } 173 | 174 | // -------------------------------------------------------------------------------- 175 | //MARK: public operators for appending a string to a NSMutableAttributedString. 176 | /* 177 | var someString = NSMutableAttributedString() 178 | someString += "text" // will append "text" to the string 179 | someString += ("text", [NSForegroundColorAttributeName : NSColor.redColor()]) // will append red "text" 180 | 181 | += is already used for appending to a mutable string, so this is a useful shortcut. 182 | 183 | Notice a useful feature in the second case: passing a tuple to an operator. 184 | */ 185 | 186 | /// The right-hand String is appended to the left-hand NSMutableString. 187 | public func += (inout left: NSMutableAttributedString, right: String) { 188 | left.appendAttributedString(NSAttributedString(string: right, attributes: [ : ])) 189 | } 190 | 191 | /// The right-hand tuple contains a String with an attribute NSDictionary to append 192 | /// to the left-hand NSMutableString. 193 | 194 | public func += (inout left: NSMutableAttributedString, right: (str: String, att: [String : AnyObject])) { 195 | left.appendAttributedString(NSAttributedString(string: right.str, attributes: right.att)) 196 | } 197 | 198 | // Some preset style attributes for that last function. 199 | 200 | public let styleRED: [String : AnyObject] = [NSForegroundColorAttributeName : NSColor.redColor()] 201 | public let styleBLUE: [String : AnyObject] = [NSForegroundColorAttributeName : NSColor.blueColor()] 202 | public let styleBOLD12: [String : AnyObject] = [NSFontAttributeName : NSFont.boldSystemFontOfSize(12)] 203 | public let styleNORM12: [String : AnyObject] = [NSFontAttributeName : NSFont.systemFontOfSize(12)] 204 | 205 | 206 | // -------------------------------------------------------------------------------- 207 | //MARK: functions that get data from the Security framework 208 | 209 | /** 210 | This function returns an Optional NSDictionary containing code signature data for the 211 | argument file URL. 212 | */ 213 | private func GetCodeSignatureForURL(url: NSURL?) -> NSDictionary? { 214 | var result: NSDictionary? = nil 215 | if let url = url { // immediate unwrap if not nil, reuse the name 216 | 217 | var code: SecStaticCode? = nil 218 | 219 | let err: OSStatus = SecStaticCodeCreateWithPath(url, SecCSFlags.DefaultFlags, &code) 220 | if err == OSStatus(noErr) && code != nil { 221 | 222 | var dict: CFDictionary? = nil 223 | 224 | let err = SecCodeCopySigningInformation(code!, SecCSFlags(rawValue: kSecCSSigningInformation), &dict) 225 | result = err == OSStatus(noErr) ? dict as NSDictionary? : nil 226 | } 227 | } 228 | return result // if anything untoward happens, this will be nil. 229 | } 230 | 231 | 232 | 233 | -------------------------------------------------------------------------------- /SwiftChecker/SwiftChecker-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | // 2 | // Use this file to import your target's public headers that you would like to expose to Swift. 3 | // 4 | 5 | // currently: none :-) -------------------------------------------------------------------------------- /SwiftChecker/SwiftChecker.xib: -------------------------------------------------------------------------------- 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 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | 294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | 309 | 310 | 311 | 312 | 313 | 314 | 315 | 316 | 317 | 318 | 319 | 320 | 321 | 322 | 323 | 324 | 325 | 326 | 327 | 328 | 329 | 330 | 331 | 332 | 333 | 334 | 335 | 336 | 337 | 338 | 339 | 340 | 341 | 342 | 343 | 344 | 345 | 346 | 347 | 348 | 349 | 350 | 351 | 352 | 353 | 354 | 355 | 356 | 357 | 358 | 359 | 360 | 361 | 362 | 363 | 364 | 365 | 366 | 367 | 368 | 369 | 370 | 371 | 372 | 373 | 374 | 375 | 376 | 377 | 378 | 379 | 380 | 381 | 382 | 383 | 384 | 385 | 386 | 387 | 388 | 389 | 390 | 391 | 392 | 393 | 394 | 395 | 396 | 397 | 398 | 399 | 400 | 401 | 402 | 403 | 404 | 405 | 406 | 407 | 408 | 409 | 410 | 411 | 412 | 413 | 414 | 415 | 416 | 417 | 418 | 419 | 420 | 421 | 422 | 423 | 424 | 425 | 426 | 427 | 428 | 429 | 430 | 431 | 432 | 433 | 434 | 435 | 436 | 437 | 438 | 439 | 440 | 441 | 442 | 443 | 444 | 445 | 446 | 447 | 448 | 449 | 450 | 451 | 452 | 453 | 454 | 455 | 456 | 457 | 458 | 459 | 460 | 461 | 462 | 463 | 464 | 465 | 466 | 467 | 468 | 469 | 473 | 477 | 478 | 479 | 480 | 481 | 482 | 483 | 484 | 485 | 486 | 487 | 488 | 489 | 490 | -------------------------------------------------------------------------------- /SwiftChecker/Timing.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Timing.swift 3 | // 4 | // Created by Rainer Brockerhoff on 7/7/14. 5 | // Copyright (c) 2014-2015 Rainer Brockerhoff. All rights reserved. 6 | // 7 | 8 | import Foundation 9 | 10 | /* A struct and functions to do timing and benchmarking. 11 | 12 | The benchmarking functions aren't currently used in SwiftChecker but were 13 | useful during testing. 14 | */ 15 | 16 | //MARK: public struct TimeStamp 17 | // ================================================================================ 18 | /** 19 | # TimeStamp 20 | This generic struct allows simple timing and timestamping. 21 | 22 | A `TimeStamp` can represent either a time interval (relative timestamp) 23 | or a time interval since the last reboot (absolute timestamp), both with good 24 | precision — nanoseconds on most modern Macs. 25 | 26 | The internal representation is based on the 64-bit value returned by the 27 | `mach_absolute_time()` function in `.` Unlike that value, you can 28 | also construct negative values with the - operator. 29 | 30 | It can also contain an optional label. 31 | 32 | You can construct a `TimeStamp` in any of these ways: 33 | ```swift 34 | let t1 = TimeStamp() // current time, no label 35 | let t2 = TimeStamp("Label") // current time with label 36 | let t3 = TimeStamp(someInt64, optionalLabel) // any values 37 | ``` 38 | or by using the + and - operators, for instance: 39 | ```swift 40 | let delta = TimeStamp() - t2 // time since t2 was generated; label will be nil 41 | delta.label = "Difference" // but you can set the label afterwards 42 | ``` 43 | */ 44 | public struct TimeStamp : CustomStringConvertible { 45 | 46 | // -------------------------------------------------------------------------------- 47 | //MARK: initializers 48 | 49 | /// Simplest initializer: current time, no label; absolute timestamp. 50 | public init() { 51 | absolute = true 52 | time = Int64(mach_absolute_time()) 53 | label = nil 54 | } 55 | 56 | /// Initializer: current time, with label; absolute timestamp. 57 | public init(_ str: String?) { 58 | absolute = true 59 | time = Int64(mach_absolute_time()) 60 | label = str 61 | } 62 | 63 | /// Initializer: time difference in seconds, label; relative timestamp. 64 | public init(_ start: Double, _ str: String?) { 65 | absolute = false 66 | time = Int64(start / TimeStamp._factor) 67 | label = str 68 | } 69 | 70 | /// Private initializer: flag, arbitrary time and label. You shouldn't need to use 71 | /// this directly. 72 | private init(_ absol: Bool, _ start: Int64, _ str: String?) { 73 | absolute = absol 74 | time = start 75 | label = str 76 | } 77 | 78 | // -------------------------------------------------------------------------------- 79 | //MARK: public properties and methods 80 | 81 | /** 82 | Returns the elapsed time in `mach_absolute_time()` units if absolute, `nil` if 83 | relative. You normally won't need to use that; either use the `seconds` 84 | property, or the description or age properties. 85 | */ 86 | public var elapsed: Int64? { 87 | return absolute ? Int64(mach_absolute_time()) - time : nil 88 | } 89 | 90 | /** 91 | Returns the `TimeStamp `value in seconds for relative `TimeStamp`s. For absolute `TimeStamp`s, returns the age instead. 92 | */ 93 | public var seconds: Double { 94 | let delta = absolute ? Int64(mach_absolute_time()) - time : time 95 | return Double(delta) * TimeStamp._factor 96 | } 97 | 98 | /** 99 | This is an optional label for the `TimeStamp`. Used only by the description and age properties. 100 | */ 101 | public var label: String? 102 | 103 | /** 104 | This `Bool` indicates whether the `TimeStamp` is absolute or relative. 105 | */ 106 | public let absolute: Bool 107 | 108 | /** 109 | Returns a printable description of the `TimeStamp`, useful for debugging. 110 | */ 111 | public var description: String { 112 | var prt = TimeStamp._format(absolute, time) 113 | if label != nil { 114 | prt = "\(label!): " + prt 115 | } 116 | return prt 117 | } 118 | 119 | /** 120 | Returns a printable description of the current age of the `TimeStamp` (returns description if it's relative). 121 | */ 122 | public var age: String { 123 | if let delta = elapsed { 124 | var prt = "\(TimeStamp._format(false, delta)) elapsed" 125 | if label != nil { 126 | prt = "\(label!): " + prt 127 | } 128 | return prt 129 | } 130 | return description 131 | } 132 | 133 | /** 134 | Call this to convert an absolute TimeStamp into a relative one, containing 135 | the elapsed time value. It also returns the new description as a convenience. 136 | */ 137 | public mutating func freeze() -> String { 138 | if (absolute) { 139 | self = TimeStamp(false, Int64(mach_absolute_time()) - time, label) 140 | } 141 | return description 142 | } 143 | 144 | // -------------------------------------------------------------------------------- 145 | //MARK: private properties 146 | 147 | /** 148 | This is the internal representation, same units as `mach_absolute_time()`. 149 | You normally won't need to access it; either use the seconds property, or 150 | the description or age properties. 151 | */ 152 | private let time: Int64 153 | 154 | // -------------------------------------------------------------------------------- 155 | //MARK: private functions and static values 156 | 157 | /** 158 | This static value is used to convert from internal units to seconds. 159 | */ 160 | private static let _factor: Double = { 161 | var tinfo = mach_timebase_info_data_t(numer:1, denom:1) 162 | mach_timebase_info(&tinfo) 163 | return Double(tinfo.numer) / (1e9 * Double(tinfo.denom)) 164 | }() 165 | 166 | /** 167 | This function is used to generate a formatted string from a `TimeStamp`. 168 | */ 169 | private static func _format(absol: Bool, _ timeval: Int64) -> String { 170 | if absol { 171 | let delta = Double(timeval - Int64(mach_absolute_time())) * TimeStamp._factor 172 | let date = NSDate(timeIntervalSinceNow: delta) 173 | let style = NSDateFormatterStyle.LongStyle 174 | return NSDateFormatter.localizedStringFromDate(date, dateStyle: style, timeStyle: style) 175 | } 176 | return SecsToStr(Double(timeval) * TimeStamp._factor) 177 | } 178 | 179 | } // end of TimeStamp 180 | 181 | // -------------------------------------------------------------------------------- 182 | //MARK: public operators on TimeStamps 183 | 184 | /** 185 | This operator produces a new `TimeStamp` containing the difference between the 186 | two argument `TimeStamps`. This makes no sense if the left side is relative and the 187 | right absolute. 188 | */ 189 | public func - (left: TimeStamp, right: TimeStamp) -> TimeStamp! { 190 | if !left.absolute && right.absolute { 191 | return nil // wrong combination, boom! 192 | } 193 | return TimeStamp(left.absolute != right.absolute, left.time - right.time, nil) 194 | } 195 | 196 | /** 197 | This operator produces a new `TimeStamp` containing the sum of the two argument 198 | `TimeStamp`s. This makes no sense if both sides are absolute. 199 | */ 200 | public func + (left: TimeStamp, right: TimeStamp) -> TimeStamp! { 201 | if left.absolute && right.absolute { 202 | return nil // wrong combination, boom! 203 | } 204 | return TimeStamp(left.absolute != right.absolute, left.time + right.time, nil) 205 | } 206 | 207 | /** 208 | This operator produces a new `TimeStamp`, subtracting the right value (in seconds) 209 | from the left `TimeStamp`. The `TimeStamp` type is conserved. 210 | */ 211 | public func - (left: TimeStamp, right: Double) -> TimeStamp { 212 | return TimeStamp(left.absolute, left.time - Int64(right / TimeStamp._factor), nil) 213 | } 214 | 215 | /** 216 | This operator produces a new `TimeStamp`, adding the right value (in seconds) 217 | to the left `TimeStamp`. The `TimeStamp` type is conserved. 218 | */ 219 | func + (left: TimeStamp, right: Double) -> TimeStamp { 220 | return TimeStamp(left.absolute, left.time + Int64(right / TimeStamp._factor), nil) 221 | } 222 | 223 | // ================================================================================ 224 | //MARK: useful functions for benchmarking a closure 225 | 226 | public typealias BenchClosure = () -> Any? 227 | 228 | /** 229 | This function accepts an optional label, a repetition count (which should be at 230 | least 1000 to be useful), and a closure to be benchmarked in serial; it produces a 231 | relative `TimeStamp` containing the average execution time for the closure. 232 | 233 | This makes no sense if the repetition count is < 1. 234 | */ 235 | public func BenchmarkSerial(comment: String?, times: Int, work: BenchClosure) -> TimeStamp! { 236 | if (times < 1) { 237 | return nil // zero repetitions, boom! 238 | } 239 | let total = _BenchmarkSerial(times, work: work) 240 | return TimeStamp(false, total, comment) 241 | } 242 | 243 | /** 244 | This function accepts a repetition count (which should be at least 1000 to be useful), 245 | and a closure to be benchmarked in serial; it produces a relative `TimeStamp` containing 246 | the average execution time for the closure. 247 | 248 | This makes no sense if the repetition count is < 1. 249 | */ 250 | public func BenchmarkSerial(times: Int, work: BenchClosure) -> TimeStamp! { 251 | return BenchmarkSerial(nil, times: times, work: work) 252 | } 253 | 254 | /** 255 | This function accepts an optional label, a repetition count (which should be a multiple of 10, 256 | and at least 100 to be useful), and a closure to be benchmarked in parallel; it produces a 257 | relative `TimeStamp` containing the average execution time for the closure. 258 | 259 | This makes no sense if the repetition count is < 1. 260 | */ 261 | public func BenchmarkParallel(comment: String?, times: Int, work: BenchClosure) -> TimeStamp! { 262 | if (times < 1) { 263 | return nil // zero, boom! 264 | } 265 | let total = _BenchmarkParallel(times, work: work) 266 | return TimeStamp(false, total, comment) 267 | } 268 | 269 | /** 270 | This function accepts a repetition count (which should be a multiple of 10, and at least 100 to be useful), 271 | and a closure to be benchmarked in parallel; it produces a relative `TimeStamp` containing 272 | the average execution time for the closure. 273 | 274 | This makes no sense if the repetition count is < 1. 275 | */ 276 | public func BenchmarkParallel(times: Int, work: BenchClosure) -> TimeStamp! { 277 | return BenchmarkParallel(nil, times: times, work: work) 278 | } 279 | 280 | // -------------------------------------------------------------------------------- 281 | //MARK: various functions for benchmarking. 282 | 283 | /** 284 | This utility function converts a time in seconds to an easier-to-read `String`. 285 | */ 286 | public func SecsToStr(seconds: Double) -> String { 287 | let magn = abs(seconds) 288 | if magn < 1e-6 { 289 | return "\(1e9 * seconds) ns" 290 | } 291 | if magn < 1e-3 { 292 | return "\(1e6 * seconds) µs" 293 | } 294 | if magn < 1 { 295 | return "\(1e3 * seconds) ms" 296 | } 297 | return "\(seconds) s" 298 | } 299 | 300 | /** 301 | This utility function reports internal overhead values. 302 | */ 303 | public func ReportTimingData() { 304 | Print("Timing quantum = \(TimeStamp._format(false,1))") 305 | let overhead = BenchmarkSerial(1000) { 306 | return TimeStamp() 307 | } 308 | Print("TimeStamp overhead = \(overhead)") 309 | } 310 | 311 | // -------------------------------------------------------------------------------- 312 | //MARK: private functions and values for benchmarking 313 | 314 | /** 315 | This private function does the actual serial measurement. 316 | */ 317 | private func _BenchmarkSerial(times: Int, work: BenchClosure) -> Int64 { 318 | var total: Int64 = 0 319 | for _ in 1...times { 320 | let before = Int64(mach_absolute_time()) 321 | _ = work() 322 | total += Int64(mach_absolute_time()) - before 323 | } 324 | return total / Int64(times) 325 | } 326 | 327 | /** 328 | private concurrent dispatch queue for benchmarking 329 | */ 330 | private let _benchq = { // global concurrent dispatch queue for BenchmarkParallel 331 | dispatch_queue_create("benchq", DISPATCH_QUEUE_CONCURRENT) 332 | }() 333 | 334 | /** 335 | the stride count multiplier 336 | */ 337 | private let _repeat: Int = 10 338 | 339 | /** 340 | This private function does the actual parallel measurement. 341 | */ 342 | private func _BenchmarkParallel(times: Int, work: BenchClosure) -> Int64 { 343 | let lock = NSCondition() 344 | var total: Int64 = 0 345 | let nt = min(1, times/_repeat) 346 | dispatch_apply(nt, _benchq) { (_) in 347 | let before = Int64(mach_absolute_time()) 348 | for _ in 1..._repeat { 349 | _ = work() 350 | } 351 | let elapsed = Int64(mach_absolute_time()) - before 352 | lock.lock() 353 | total += elapsed 354 | lock.unlock() 355 | } 356 | return total / Int64(nt * _repeat) 357 | } 358 | 359 | -------------------------------------------------------------------------------- /SwiftChecker/main.swift: -------------------------------------------------------------------------------- 1 | // 2 | // main.swift 3 | // SwiftChecker 4 | // 5 | // Created by Rainer Brockerhoff on 5/6/14. 6 | // Copyright (c) 2014-2015 Rainer Brockerhoff. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | 11 | // This TimeStamp is used in AppDelegate to log startup and quit times. 12 | let startup = TimeStamp() 13 | Print("Started at \(startup)") 14 | 15 | NSApplicationMain(Process.argc, Process.unsafeArgv) 16 | 17 | -------------------------------------------------------------------------------- /SwiftCheckerTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | ${EXECUTABLE_NAME} 9 | CFBundleIdentifier 10 | net.brockerhoff.${PRODUCT_NAME:rfc1034identifier} 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 | -------------------------------------------------------------------------------- /SwiftCheckerTests/SwiftCheckerTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SwiftCheckerTests.swift 3 | // SwiftCheckerTests 4 | // 5 | // Created by Rainer Brockerhoff on 5/6/14. 6 | // Copyright (c) 2014 Rainer Brockerhoff. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | 11 | class SwiftCheckerTests: XCTestCase { 12 | 13 | override func setUp() { 14 | super.setUp() 15 | // Put setup code here. This method is called before the invocation of each test method in the class. 16 | } 17 | 18 | override func tearDown() { 19 | // Put teardown code here. This method is called after the invocation of each test method in the class. 20 | super.tearDown() 21 | } 22 | 23 | func testExample() { 24 | // This is an example of a functional test case. 25 | XCTAssert(true, "Pass") 26 | } 27 | 28 | func testPerformanceExample() { 29 | // This is an example of a performance test case. 30 | self.measureBlock() { 31 | // Put the code you want to measure the time of here. 32 | } 33 | } 34 | 35 | } 36 | --------------------------------------------------------------------------------