├── PTBPerformanceCenter.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcuserdata │ │ └── perterbin.xcuserdatad │ │ └── UserInterfaceState.xcuserstate └── xcuserdata │ └── perterbin.xcuserdatad │ └── xcschemes │ └── xcschememanagement.plist ├── PTBPerformanceCenter ├── AppDelegate.h ├── AppDelegate.m ├── Assets.xcassets │ └── AppIcon.appiconset │ │ └── Contents.json ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard ├── Info.plist ├── PTBPerformanceCenter │ ├── LeakFinder │ │ ├── MLeakedObjectProxy.h │ │ ├── MLeakedObjectProxy.m │ │ ├── MLeaksFinder.h │ │ ├── MLeaksMessenger.h │ │ ├── MLeaksMessenger.m │ │ ├── NSObject+MemoryLeak.h │ │ ├── NSObject+MemoryLeak.m │ │ ├── UIApplication+MemoryLeak.h │ │ ├── UIApplication+MemoryLeak.m │ │ ├── UINavigationController+MemoryLeak.h │ │ ├── UINavigationController+MemoryLeak.m │ │ ├── UIPageViewController+MemoryLeak.h │ │ ├── UIPageViewController+MemoryLeak.m │ │ ├── UISplitViewController+MemoryLeak.h │ │ ├── UISplitViewController+MemoryLeak.m │ │ ├── UITabBarController+MemoryLeak.h │ │ ├── UITabBarController+MemoryLeak.m │ │ ├── UITouch+MemoryLeak.h │ │ ├── UITouch+MemoryLeak.m │ │ ├── UIView+MemoryLeak.h │ │ ├── UIView+MemoryLeak.m │ │ ├── UIViewController+MemoryLeak.h │ │ └── UIViewController+MemoryLeak.m │ ├── Monitor │ │ ├── CPU │ │ │ ├── PTBCPUMonitor.h │ │ │ └── PTBCPUMonitor.m │ │ ├── FPS │ │ │ ├── PTBFPSMonitor.h │ │ │ └── PTBFPSMonitor.m │ │ ├── Memory │ │ │ ├── PTBMemoryMonitor.h │ │ │ └── PTBMemoryMonitor.m │ │ ├── PTBBasePerformanceMonitor.h │ │ └── PTBBasePerformanceMonitor.m │ ├── PTBPerformanceCenter.h │ ├── PTBPerformanceCenter.m │ ├── PTBPerformanceViewController.h │ ├── PTBPerformanceViewController.m │ ├── PTBPerformanceWindow.h │ └── PTBPerformanceWindow.m ├── ViewController.h ├── ViewController.m └── main.m ├── PTBPerformanceCenterTests ├── Info.plist └── PTBPerformanceCenterTests.m ├── PTBPerformanceCenterUITests ├── Info.plist └── PTBPerformanceCenterUITests.m └── README.md /PTBPerformanceCenter.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 48; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 2DBFD2A420C3CD7C001B7F23 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 2DBFD2A320C3CD7C001B7F23 /* AppDelegate.m */; }; 11 | 2DBFD2A720C3CD7C001B7F23 /* ViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 2DBFD2A620C3CD7C001B7F23 /* ViewController.m */; }; 12 | 2DBFD2AA20C3CD7C001B7F23 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 2DBFD2A820C3CD7C001B7F23 /* Main.storyboard */; }; 13 | 2DBFD2AC20C3CD7C001B7F23 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 2DBFD2AB20C3CD7C001B7F23 /* Assets.xcassets */; }; 14 | 2DBFD2AF20C3CD7C001B7F23 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 2DBFD2AD20C3CD7C001B7F23 /* LaunchScreen.storyboard */; }; 15 | 2DBFD2B220C3CD7C001B7F23 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 2DBFD2B120C3CD7C001B7F23 /* main.m */; }; 16 | 2DBFD2BC20C3CD7D001B7F23 /* PTBPerformanceCenterTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 2DBFD2BB20C3CD7D001B7F23 /* PTBPerformanceCenterTests.m */; }; 17 | 2DBFD2C720C3CD7D001B7F23 /* PTBPerformanceCenterUITests.m in Sources */ = {isa = PBXBuildFile; fileRef = 2DBFD2C620C3CD7D001B7F23 /* PTBPerformanceCenterUITests.m */; }; 18 | 2DBFD31220C3CDEC001B7F23 /* UINavigationController+MemoryLeak.m in Sources */ = {isa = PBXBuildFile; fileRef = 2DBFD2E720C3CDEC001B7F23 /* UINavigationController+MemoryLeak.m */; }; 19 | 2DBFD31320C3CDEC001B7F23 /* MLeakedObjectProxy.m in Sources */ = {isa = PBXBuildFile; fileRef = 2DBFD2EB20C3CDEC001B7F23 /* MLeakedObjectProxy.m */; }; 20 | 2DBFD31420C3CDEC001B7F23 /* UIView+MemoryLeak.m in Sources */ = {isa = PBXBuildFile; fileRef = 2DBFD2ED20C3CDEC001B7F23 /* UIView+MemoryLeak.m */; }; 21 | 2DBFD31520C3CDEC001B7F23 /* UITouch+MemoryLeak.m in Sources */ = {isa = PBXBuildFile; fileRef = 2DBFD2F020C3CDEC001B7F23 /* UITouch+MemoryLeak.m */; }; 22 | 2DBFD31620C3CDEC001B7F23 /* UISplitViewController+MemoryLeak.m in Sources */ = {isa = PBXBuildFile; fileRef = 2DBFD2F120C3CDEC001B7F23 /* UISplitViewController+MemoryLeak.m */; }; 23 | 2DBFD31720C3CDEC001B7F23 /* UIPageViewController+MemoryLeak.m in Sources */ = {isa = PBXBuildFile; fileRef = 2DBFD2F320C3CDEC001B7F23 /* UIPageViewController+MemoryLeak.m */; }; 24 | 2DBFD31820C3CDEC001B7F23 /* UITabBarController+MemoryLeak.m in Sources */ = {isa = PBXBuildFile; fileRef = 2DBFD2F520C3CDEC001B7F23 /* UITabBarController+MemoryLeak.m */; }; 25 | 2DBFD31920C3CDEC001B7F23 /* MLeaksMessenger.m in Sources */ = {isa = PBXBuildFile; fileRef = 2DBFD2F620C3CDEC001B7F23 /* MLeaksMessenger.m */; }; 26 | 2DBFD31A20C3CDEC001B7F23 /* UIViewController+MemoryLeak.m in Sources */ = {isa = PBXBuildFile; fileRef = 2DBFD2F820C3CDEC001B7F23 /* UIViewController+MemoryLeak.m */; }; 27 | 2DBFD31B20C3CDEC001B7F23 /* UIApplication+MemoryLeak.m in Sources */ = {isa = PBXBuildFile; fileRef = 2DBFD2FB20C3CDEC001B7F23 /* UIApplication+MemoryLeak.m */; }; 28 | 2DBFD31C20C3CDEC001B7F23 /* NSObject+MemoryLeak.m in Sources */ = {isa = PBXBuildFile; fileRef = 2DBFD30920C3CDEC001B7F23 /* NSObject+MemoryLeak.m */; }; 29 | 2DBFD32020C3CE4B001B7F23 /* PTBPerformanceCenter.m in Sources */ = {isa = PBXBuildFile; fileRef = 2DBFD31F20C3CE4B001B7F23 /* PTBPerformanceCenter.m */; }; 30 | 2DBFD32620C3CEB9001B7F23 /* PTBPerformanceViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 2DBFD32520C3CEB9001B7F23 /* PTBPerformanceViewController.m */; }; 31 | 2DBFD32920C3CEFC001B7F23 /* PTBPerformanceWindow.m in Sources */ = {isa = PBXBuildFile; fileRef = 2DBFD32820C3CEFC001B7F23 /* PTBPerformanceWindow.m */; }; 32 | 2DBFD32C20C3CF56001B7F23 /* PTBBasePerformanceMonitor.m in Sources */ = {isa = PBXBuildFile; fileRef = 2DBFD32B20C3CF56001B7F23 /* PTBBasePerformanceMonitor.m */; }; 33 | 2DBFD32F20C3CF8C001B7F23 /* PTBMemoryMonitor.m in Sources */ = {isa = PBXBuildFile; fileRef = 2DBFD32E20C3CF8C001B7F23 /* PTBMemoryMonitor.m */; }; 34 | 2DBFD33220C3CFC8001B7F23 /* PTBCPUMonitor.m in Sources */ = {isa = PBXBuildFile; fileRef = 2DBFD33120C3CFC8001B7F23 /* PTBCPUMonitor.m */; }; 35 | 2DBFD33520C3CFF5001B7F23 /* PTBFPSMonitor.m in Sources */ = {isa = PBXBuildFile; fileRef = 2DBFD33420C3CFF5001B7F23 /* PTBFPSMonitor.m */; }; 36 | /* End PBXBuildFile section */ 37 | 38 | /* Begin PBXContainerItemProxy section */ 39 | 2DBFD2B820C3CD7D001B7F23 /* PBXContainerItemProxy */ = { 40 | isa = PBXContainerItemProxy; 41 | containerPortal = 2DBFD29720C3CD7C001B7F23 /* Project object */; 42 | proxyType = 1; 43 | remoteGlobalIDString = 2DBFD29E20C3CD7C001B7F23; 44 | remoteInfo = PTBPerformanceCenter; 45 | }; 46 | 2DBFD2C320C3CD7D001B7F23 /* PBXContainerItemProxy */ = { 47 | isa = PBXContainerItemProxy; 48 | containerPortal = 2DBFD29720C3CD7C001B7F23 /* Project object */; 49 | proxyType = 1; 50 | remoteGlobalIDString = 2DBFD29E20C3CD7C001B7F23; 51 | remoteInfo = PTBPerformanceCenter; 52 | }; 53 | /* End PBXContainerItemProxy section */ 54 | 55 | /* Begin PBXFileReference section */ 56 | 2DBFD29F20C3CD7C001B7F23 /* PTBPerformanceCenter.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = PTBPerformanceCenter.app; sourceTree = BUILT_PRODUCTS_DIR; }; 57 | 2DBFD2A220C3CD7C001B7F23 /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; 58 | 2DBFD2A320C3CD7C001B7F23 /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; 59 | 2DBFD2A520C3CD7C001B7F23 /* ViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ViewController.h; sourceTree = ""; }; 60 | 2DBFD2A620C3CD7C001B7F23 /* ViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ViewController.m; sourceTree = ""; }; 61 | 2DBFD2A920C3CD7C001B7F23 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 62 | 2DBFD2AB20C3CD7C001B7F23 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 63 | 2DBFD2AE20C3CD7C001B7F23 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 64 | 2DBFD2B020C3CD7C001B7F23 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 65 | 2DBFD2B120C3CD7C001B7F23 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 66 | 2DBFD2B720C3CD7D001B7F23 /* PTBPerformanceCenterTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = PTBPerformanceCenterTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 67 | 2DBFD2BB20C3CD7D001B7F23 /* PTBPerformanceCenterTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = PTBPerformanceCenterTests.m; sourceTree = ""; }; 68 | 2DBFD2BD20C3CD7D001B7F23 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 69 | 2DBFD2C220C3CD7D001B7F23 /* PTBPerformanceCenterUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = PTBPerformanceCenterUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 70 | 2DBFD2C620C3CD7D001B7F23 /* PTBPerformanceCenterUITests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = PTBPerformanceCenterUITests.m; sourceTree = ""; }; 71 | 2DBFD2C820C3CD7D001B7F23 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 72 | 2DBFD2E720C3CDEC001B7F23 /* UINavigationController+MemoryLeak.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UINavigationController+MemoryLeak.m"; sourceTree = ""; }; 73 | 2DBFD2E820C3CDEC001B7F23 /* MLeaksMessenger.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MLeaksMessenger.h; sourceTree = ""; }; 74 | 2DBFD2E920C3CDEC001B7F23 /* UITabBarController+MemoryLeak.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UITabBarController+MemoryLeak.h"; sourceTree = ""; }; 75 | 2DBFD2EA20C3CDEC001B7F23 /* UIPageViewController+MemoryLeak.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIPageViewController+MemoryLeak.h"; sourceTree = ""; }; 76 | 2DBFD2EB20C3CDEC001B7F23 /* MLeakedObjectProxy.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MLeakedObjectProxy.m; sourceTree = ""; }; 77 | 2DBFD2EC20C3CDEC001B7F23 /* UIViewController+MemoryLeak.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIViewController+MemoryLeak.h"; sourceTree = ""; }; 78 | 2DBFD2ED20C3CDEC001B7F23 /* UIView+MemoryLeak.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIView+MemoryLeak.m"; sourceTree = ""; }; 79 | 2DBFD2EE20C3CDEC001B7F23 /* NSObject+MemoryLeak.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSObject+MemoryLeak.h"; sourceTree = ""; }; 80 | 2DBFD2EF20C3CDEC001B7F23 /* UIApplication+MemoryLeak.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIApplication+MemoryLeak.h"; sourceTree = ""; }; 81 | 2DBFD2F020C3CDEC001B7F23 /* UITouch+MemoryLeak.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UITouch+MemoryLeak.m"; sourceTree = ""; }; 82 | 2DBFD2F120C3CDEC001B7F23 /* UISplitViewController+MemoryLeak.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UISplitViewController+MemoryLeak.m"; sourceTree = ""; }; 83 | 2DBFD2F220C3CDEC001B7F23 /* MLeaksFinder.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MLeaksFinder.h; sourceTree = ""; }; 84 | 2DBFD2F320C3CDEC001B7F23 /* UIPageViewController+MemoryLeak.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIPageViewController+MemoryLeak.m"; sourceTree = ""; }; 85 | 2DBFD2F420C3CDEC001B7F23 /* MLeakedObjectProxy.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MLeakedObjectProxy.h; sourceTree = ""; }; 86 | 2DBFD2F520C3CDEC001B7F23 /* UITabBarController+MemoryLeak.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UITabBarController+MemoryLeak.m"; sourceTree = ""; }; 87 | 2DBFD2F620C3CDEC001B7F23 /* MLeaksMessenger.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MLeaksMessenger.m; sourceTree = ""; }; 88 | 2DBFD2F720C3CDEC001B7F23 /* UINavigationController+MemoryLeak.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UINavigationController+MemoryLeak.h"; sourceTree = ""; }; 89 | 2DBFD2F820C3CDEC001B7F23 /* UIViewController+MemoryLeak.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIViewController+MemoryLeak.m"; sourceTree = ""; }; 90 | 2DBFD2F920C3CDEC001B7F23 /* UISplitViewController+MemoryLeak.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UISplitViewController+MemoryLeak.h"; sourceTree = ""; }; 91 | 2DBFD2FA20C3CDEC001B7F23 /* UITouch+MemoryLeak.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UITouch+MemoryLeak.h"; sourceTree = ""; }; 92 | 2DBFD2FB20C3CDEC001B7F23 /* UIApplication+MemoryLeak.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIApplication+MemoryLeak.m"; sourceTree = ""; }; 93 | 2DBFD30920C3CDEC001B7F23 /* NSObject+MemoryLeak.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSObject+MemoryLeak.m"; sourceTree = ""; }; 94 | 2DBFD30A20C3CDEC001B7F23 /* UIView+MemoryLeak.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIView+MemoryLeak.h"; sourceTree = ""; }; 95 | 2DBFD31E20C3CE4B001B7F23 /* PTBPerformanceCenter.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = PTBPerformanceCenter.h; sourceTree = ""; }; 96 | 2DBFD31F20C3CE4B001B7F23 /* PTBPerformanceCenter.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = PTBPerformanceCenter.m; sourceTree = ""; }; 97 | 2DBFD32420C3CEB9001B7F23 /* PTBPerformanceViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = PTBPerformanceViewController.h; sourceTree = ""; }; 98 | 2DBFD32520C3CEB9001B7F23 /* PTBPerformanceViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = PTBPerformanceViewController.m; sourceTree = ""; }; 99 | 2DBFD32720C3CEFC001B7F23 /* PTBPerformanceWindow.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = PTBPerformanceWindow.h; sourceTree = ""; }; 100 | 2DBFD32820C3CEFC001B7F23 /* PTBPerformanceWindow.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = PTBPerformanceWindow.m; sourceTree = ""; }; 101 | 2DBFD32A20C3CF56001B7F23 /* PTBBasePerformanceMonitor.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = PTBBasePerformanceMonitor.h; sourceTree = ""; }; 102 | 2DBFD32B20C3CF56001B7F23 /* PTBBasePerformanceMonitor.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = PTBBasePerformanceMonitor.m; sourceTree = ""; }; 103 | 2DBFD32D20C3CF8C001B7F23 /* PTBMemoryMonitor.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = PTBMemoryMonitor.h; sourceTree = ""; }; 104 | 2DBFD32E20C3CF8C001B7F23 /* PTBMemoryMonitor.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = PTBMemoryMonitor.m; sourceTree = ""; }; 105 | 2DBFD33020C3CFC8001B7F23 /* PTBCPUMonitor.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = PTBCPUMonitor.h; sourceTree = ""; }; 106 | 2DBFD33120C3CFC8001B7F23 /* PTBCPUMonitor.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = PTBCPUMonitor.m; sourceTree = ""; }; 107 | 2DBFD33320C3CFF5001B7F23 /* PTBFPSMonitor.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = PTBFPSMonitor.h; sourceTree = ""; }; 108 | 2DBFD33420C3CFF5001B7F23 /* PTBFPSMonitor.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = PTBFPSMonitor.m; sourceTree = ""; }; 109 | /* End PBXFileReference section */ 110 | 111 | /* Begin PBXFrameworksBuildPhase section */ 112 | 2DBFD29C20C3CD7C001B7F23 /* Frameworks */ = { 113 | isa = PBXFrameworksBuildPhase; 114 | buildActionMask = 2147483647; 115 | files = ( 116 | ); 117 | runOnlyForDeploymentPostprocessing = 0; 118 | }; 119 | 2DBFD2B420C3CD7D001B7F23 /* Frameworks */ = { 120 | isa = PBXFrameworksBuildPhase; 121 | buildActionMask = 2147483647; 122 | files = ( 123 | ); 124 | runOnlyForDeploymentPostprocessing = 0; 125 | }; 126 | 2DBFD2BF20C3CD7D001B7F23 /* Frameworks */ = { 127 | isa = PBXFrameworksBuildPhase; 128 | buildActionMask = 2147483647; 129 | files = ( 130 | ); 131 | runOnlyForDeploymentPostprocessing = 0; 132 | }; 133 | /* End PBXFrameworksBuildPhase section */ 134 | 135 | /* Begin PBXGroup section */ 136 | 2DBFD29620C3CD7C001B7F23 = { 137 | isa = PBXGroup; 138 | children = ( 139 | 2DBFD2A120C3CD7C001B7F23 /* PTBPerformanceCenter */, 140 | 2DBFD2BA20C3CD7D001B7F23 /* PTBPerformanceCenterTests */, 141 | 2DBFD2C520C3CD7D001B7F23 /* PTBPerformanceCenterUITests */, 142 | 2DBFD2A020C3CD7C001B7F23 /* Products */, 143 | ); 144 | sourceTree = ""; 145 | }; 146 | 2DBFD2A020C3CD7C001B7F23 /* Products */ = { 147 | isa = PBXGroup; 148 | children = ( 149 | 2DBFD29F20C3CD7C001B7F23 /* PTBPerformanceCenter.app */, 150 | 2DBFD2B720C3CD7D001B7F23 /* PTBPerformanceCenterTests.xctest */, 151 | 2DBFD2C220C3CD7D001B7F23 /* PTBPerformanceCenterUITests.xctest */, 152 | ); 153 | name = Products; 154 | sourceTree = ""; 155 | }; 156 | 2DBFD2A120C3CD7C001B7F23 /* PTBPerformanceCenter */ = { 157 | isa = PBXGroup; 158 | children = ( 159 | 2DBFD2D420C3CDEC001B7F23 /* PTBPerformanceCenter */, 160 | 2DBFD2A220C3CD7C001B7F23 /* AppDelegate.h */, 161 | 2DBFD2A320C3CD7C001B7F23 /* AppDelegate.m */, 162 | 2DBFD2A520C3CD7C001B7F23 /* ViewController.h */, 163 | 2DBFD2A620C3CD7C001B7F23 /* ViewController.m */, 164 | 2DBFD2A820C3CD7C001B7F23 /* Main.storyboard */, 165 | 2DBFD2AB20C3CD7C001B7F23 /* Assets.xcassets */, 166 | 2DBFD2AD20C3CD7C001B7F23 /* LaunchScreen.storyboard */, 167 | 2DBFD2B020C3CD7C001B7F23 /* Info.plist */, 168 | 2DBFD2B120C3CD7C001B7F23 /* main.m */, 169 | ); 170 | path = PTBPerformanceCenter; 171 | sourceTree = ""; 172 | }; 173 | 2DBFD2BA20C3CD7D001B7F23 /* PTBPerformanceCenterTests */ = { 174 | isa = PBXGroup; 175 | children = ( 176 | 2DBFD2BB20C3CD7D001B7F23 /* PTBPerformanceCenterTests.m */, 177 | 2DBFD2BD20C3CD7D001B7F23 /* Info.plist */, 178 | ); 179 | path = PTBPerformanceCenterTests; 180 | sourceTree = ""; 181 | }; 182 | 2DBFD2C520C3CD7D001B7F23 /* PTBPerformanceCenterUITests */ = { 183 | isa = PBXGroup; 184 | children = ( 185 | 2DBFD2C620C3CD7D001B7F23 /* PTBPerformanceCenterUITests.m */, 186 | 2DBFD2C820C3CD7D001B7F23 /* Info.plist */, 187 | ); 188 | path = PTBPerformanceCenterUITests; 189 | sourceTree = ""; 190 | }; 191 | 2DBFD2D420C3CDEC001B7F23 /* PTBPerformanceCenter */ = { 192 | isa = PBXGroup; 193 | children = ( 194 | 2DBFD2D620C3CDEC001B7F23 /* Monitor */, 195 | 2DBFD2E620C3CDEC001B7F23 /* LeakFinder */, 196 | 2DBFD31E20C3CE4B001B7F23 /* PTBPerformanceCenter.h */, 197 | 2DBFD31F20C3CE4B001B7F23 /* PTBPerformanceCenter.m */, 198 | 2DBFD32420C3CEB9001B7F23 /* PTBPerformanceViewController.h */, 199 | 2DBFD32520C3CEB9001B7F23 /* PTBPerformanceViewController.m */, 200 | 2DBFD32720C3CEFC001B7F23 /* PTBPerformanceWindow.h */, 201 | 2DBFD32820C3CEFC001B7F23 /* PTBPerformanceWindow.m */, 202 | ); 203 | path = PTBPerformanceCenter; 204 | sourceTree = ""; 205 | }; 206 | 2DBFD2D620C3CDEC001B7F23 /* Monitor */ = { 207 | isa = PBXGroup; 208 | children = ( 209 | 2DBFD2D720C3CDEC001B7F23 /* Memory */, 210 | 2DBFD2DA20C3CDEC001B7F23 /* CPU */, 211 | 2DBFD2DE20C3CDEC001B7F23 /* FPS */, 212 | 2DBFD32A20C3CF56001B7F23 /* PTBBasePerformanceMonitor.h */, 213 | 2DBFD32B20C3CF56001B7F23 /* PTBBasePerformanceMonitor.m */, 214 | ); 215 | path = Monitor; 216 | sourceTree = ""; 217 | }; 218 | 2DBFD2D720C3CDEC001B7F23 /* Memory */ = { 219 | isa = PBXGroup; 220 | children = ( 221 | 2DBFD32D20C3CF8C001B7F23 /* PTBMemoryMonitor.h */, 222 | 2DBFD32E20C3CF8C001B7F23 /* PTBMemoryMonitor.m */, 223 | ); 224 | path = Memory; 225 | sourceTree = ""; 226 | }; 227 | 2DBFD2DA20C3CDEC001B7F23 /* CPU */ = { 228 | isa = PBXGroup; 229 | children = ( 230 | 2DBFD33020C3CFC8001B7F23 /* PTBCPUMonitor.h */, 231 | 2DBFD33120C3CFC8001B7F23 /* PTBCPUMonitor.m */, 232 | ); 233 | path = CPU; 234 | sourceTree = ""; 235 | }; 236 | 2DBFD2DE20C3CDEC001B7F23 /* FPS */ = { 237 | isa = PBXGroup; 238 | children = ( 239 | 2DBFD33320C3CFF5001B7F23 /* PTBFPSMonitor.h */, 240 | 2DBFD33420C3CFF5001B7F23 /* PTBFPSMonitor.m */, 241 | ); 242 | path = FPS; 243 | sourceTree = ""; 244 | }; 245 | 2DBFD2E620C3CDEC001B7F23 /* LeakFinder */ = { 246 | isa = PBXGroup; 247 | children = ( 248 | 2DBFD2F220C3CDEC001B7F23 /* MLeaksFinder.h */, 249 | 2DBFD2F420C3CDEC001B7F23 /* MLeakedObjectProxy.h */, 250 | 2DBFD2EB20C3CDEC001B7F23 /* MLeakedObjectProxy.m */, 251 | 2DBFD2E820C3CDEC001B7F23 /* MLeaksMessenger.h */, 252 | 2DBFD2F620C3CDEC001B7F23 /* MLeaksMessenger.m */, 253 | 2DBFD2EE20C3CDEC001B7F23 /* NSObject+MemoryLeak.h */, 254 | 2DBFD30920C3CDEC001B7F23 /* NSObject+MemoryLeak.m */, 255 | 2DBFD30A20C3CDEC001B7F23 /* UIView+MemoryLeak.h */, 256 | 2DBFD2ED20C3CDEC001B7F23 /* UIView+MemoryLeak.m */, 257 | 2DBFD2FA20C3CDEC001B7F23 /* UITouch+MemoryLeak.h */, 258 | 2DBFD2F020C3CDEC001B7F23 /* UITouch+MemoryLeak.m */, 259 | 2DBFD2EC20C3CDEC001B7F23 /* UIViewController+MemoryLeak.h */, 260 | 2DBFD2F820C3CDEC001B7F23 /* UIViewController+MemoryLeak.m */, 261 | 2DBFD2F720C3CDEC001B7F23 /* UINavigationController+MemoryLeak.h */, 262 | 2DBFD2E720C3CDEC001B7F23 /* UINavigationController+MemoryLeak.m */, 263 | 2DBFD2E920C3CDEC001B7F23 /* UITabBarController+MemoryLeak.h */, 264 | 2DBFD2F520C3CDEC001B7F23 /* UITabBarController+MemoryLeak.m */, 265 | 2DBFD2EA20C3CDEC001B7F23 /* UIPageViewController+MemoryLeak.h */, 266 | 2DBFD2F320C3CDEC001B7F23 /* UIPageViewController+MemoryLeak.m */, 267 | 2DBFD2EF20C3CDEC001B7F23 /* UIApplication+MemoryLeak.h */, 268 | 2DBFD2FB20C3CDEC001B7F23 /* UIApplication+MemoryLeak.m */, 269 | 2DBFD2F920C3CDEC001B7F23 /* UISplitViewController+MemoryLeak.h */, 270 | 2DBFD2F120C3CDEC001B7F23 /* UISplitViewController+MemoryLeak.m */, 271 | ); 272 | path = LeakFinder; 273 | sourceTree = ""; 274 | }; 275 | /* End PBXGroup section */ 276 | 277 | /* Begin PBXNativeTarget section */ 278 | 2DBFD29E20C3CD7C001B7F23 /* PTBPerformanceCenter */ = { 279 | isa = PBXNativeTarget; 280 | buildConfigurationList = 2DBFD2CB20C3CD7D001B7F23 /* Build configuration list for PBXNativeTarget "PTBPerformanceCenter" */; 281 | buildPhases = ( 282 | 2DBFD29B20C3CD7C001B7F23 /* Sources */, 283 | 2DBFD29C20C3CD7C001B7F23 /* Frameworks */, 284 | 2DBFD29D20C3CD7C001B7F23 /* Resources */, 285 | ); 286 | buildRules = ( 287 | ); 288 | dependencies = ( 289 | ); 290 | name = PTBPerformanceCenter; 291 | productName = PTBPerformanceCenter; 292 | productReference = 2DBFD29F20C3CD7C001B7F23 /* PTBPerformanceCenter.app */; 293 | productType = "com.apple.product-type.application"; 294 | }; 295 | 2DBFD2B620C3CD7D001B7F23 /* PTBPerformanceCenterTests */ = { 296 | isa = PBXNativeTarget; 297 | buildConfigurationList = 2DBFD2CE20C3CD7D001B7F23 /* Build configuration list for PBXNativeTarget "PTBPerformanceCenterTests" */; 298 | buildPhases = ( 299 | 2DBFD2B320C3CD7D001B7F23 /* Sources */, 300 | 2DBFD2B420C3CD7D001B7F23 /* Frameworks */, 301 | 2DBFD2B520C3CD7D001B7F23 /* Resources */, 302 | ); 303 | buildRules = ( 304 | ); 305 | dependencies = ( 306 | 2DBFD2B920C3CD7D001B7F23 /* PBXTargetDependency */, 307 | ); 308 | name = PTBPerformanceCenterTests; 309 | productName = PTBPerformanceCenterTests; 310 | productReference = 2DBFD2B720C3CD7D001B7F23 /* PTBPerformanceCenterTests.xctest */; 311 | productType = "com.apple.product-type.bundle.unit-test"; 312 | }; 313 | 2DBFD2C120C3CD7D001B7F23 /* PTBPerformanceCenterUITests */ = { 314 | isa = PBXNativeTarget; 315 | buildConfigurationList = 2DBFD2D120C3CD7D001B7F23 /* Build configuration list for PBXNativeTarget "PTBPerformanceCenterUITests" */; 316 | buildPhases = ( 317 | 2DBFD2BE20C3CD7D001B7F23 /* Sources */, 318 | 2DBFD2BF20C3CD7D001B7F23 /* Frameworks */, 319 | 2DBFD2C020C3CD7D001B7F23 /* Resources */, 320 | ); 321 | buildRules = ( 322 | ); 323 | dependencies = ( 324 | 2DBFD2C420C3CD7D001B7F23 /* PBXTargetDependency */, 325 | ); 326 | name = PTBPerformanceCenterUITests; 327 | productName = PTBPerformanceCenterUITests; 328 | productReference = 2DBFD2C220C3CD7D001B7F23 /* PTBPerformanceCenterUITests.xctest */; 329 | productType = "com.apple.product-type.bundle.ui-testing"; 330 | }; 331 | /* End PBXNativeTarget section */ 332 | 333 | /* Begin PBXProject section */ 334 | 2DBFD29720C3CD7C001B7F23 /* Project object */ = { 335 | isa = PBXProject; 336 | attributes = { 337 | LastUpgradeCheck = 0910; 338 | ORGANIZATIONNAME = PerTerbin; 339 | TargetAttributes = { 340 | 2DBFD29E20C3CD7C001B7F23 = { 341 | CreatedOnToolsVersion = 9.1; 342 | ProvisioningStyle = Automatic; 343 | }; 344 | 2DBFD2B620C3CD7D001B7F23 = { 345 | CreatedOnToolsVersion = 9.1; 346 | ProvisioningStyle = Automatic; 347 | TestTargetID = 2DBFD29E20C3CD7C001B7F23; 348 | }; 349 | 2DBFD2C120C3CD7D001B7F23 = { 350 | CreatedOnToolsVersion = 9.1; 351 | ProvisioningStyle = Automatic; 352 | TestTargetID = 2DBFD29E20C3CD7C001B7F23; 353 | }; 354 | }; 355 | }; 356 | buildConfigurationList = 2DBFD29A20C3CD7C001B7F23 /* Build configuration list for PBXProject "PTBPerformanceCenter" */; 357 | compatibilityVersion = "Xcode 8.0"; 358 | developmentRegion = en; 359 | hasScannedForEncodings = 0; 360 | knownRegions = ( 361 | en, 362 | Base, 363 | ); 364 | mainGroup = 2DBFD29620C3CD7C001B7F23; 365 | productRefGroup = 2DBFD2A020C3CD7C001B7F23 /* Products */; 366 | projectDirPath = ""; 367 | projectRoot = ""; 368 | targets = ( 369 | 2DBFD29E20C3CD7C001B7F23 /* PTBPerformanceCenter */, 370 | 2DBFD2B620C3CD7D001B7F23 /* PTBPerformanceCenterTests */, 371 | 2DBFD2C120C3CD7D001B7F23 /* PTBPerformanceCenterUITests */, 372 | ); 373 | }; 374 | /* End PBXProject section */ 375 | 376 | /* Begin PBXResourcesBuildPhase section */ 377 | 2DBFD29D20C3CD7C001B7F23 /* Resources */ = { 378 | isa = PBXResourcesBuildPhase; 379 | buildActionMask = 2147483647; 380 | files = ( 381 | 2DBFD2AF20C3CD7C001B7F23 /* LaunchScreen.storyboard in Resources */, 382 | 2DBFD2AC20C3CD7C001B7F23 /* Assets.xcassets in Resources */, 383 | 2DBFD2AA20C3CD7C001B7F23 /* Main.storyboard in Resources */, 384 | ); 385 | runOnlyForDeploymentPostprocessing = 0; 386 | }; 387 | 2DBFD2B520C3CD7D001B7F23 /* Resources */ = { 388 | isa = PBXResourcesBuildPhase; 389 | buildActionMask = 2147483647; 390 | files = ( 391 | ); 392 | runOnlyForDeploymentPostprocessing = 0; 393 | }; 394 | 2DBFD2C020C3CD7D001B7F23 /* Resources */ = { 395 | isa = PBXResourcesBuildPhase; 396 | buildActionMask = 2147483647; 397 | files = ( 398 | ); 399 | runOnlyForDeploymentPostprocessing = 0; 400 | }; 401 | /* End PBXResourcesBuildPhase section */ 402 | 403 | /* Begin PBXSourcesBuildPhase section */ 404 | 2DBFD29B20C3CD7C001B7F23 /* Sources */ = { 405 | isa = PBXSourcesBuildPhase; 406 | buildActionMask = 2147483647; 407 | files = ( 408 | 2DBFD32020C3CE4B001B7F23 /* PTBPerformanceCenter.m in Sources */, 409 | 2DBFD32920C3CEFC001B7F23 /* PTBPerformanceWindow.m in Sources */, 410 | 2DBFD31420C3CDEC001B7F23 /* UIView+MemoryLeak.m in Sources */, 411 | 2DBFD32C20C3CF56001B7F23 /* PTBBasePerformanceMonitor.m in Sources */, 412 | 2DBFD32620C3CEB9001B7F23 /* PTBPerformanceViewController.m in Sources */, 413 | 2DBFD31920C3CDEC001B7F23 /* MLeaksMessenger.m in Sources */, 414 | 2DBFD32F20C3CF8C001B7F23 /* PTBMemoryMonitor.m in Sources */, 415 | 2DBFD31220C3CDEC001B7F23 /* UINavigationController+MemoryLeak.m in Sources */, 416 | 2DBFD33220C3CFC8001B7F23 /* PTBCPUMonitor.m in Sources */, 417 | 2DBFD2A720C3CD7C001B7F23 /* ViewController.m in Sources */, 418 | 2DBFD31320C3CDEC001B7F23 /* MLeakedObjectProxy.m in Sources */, 419 | 2DBFD31B20C3CDEC001B7F23 /* UIApplication+MemoryLeak.m in Sources */, 420 | 2DBFD31620C3CDEC001B7F23 /* UISplitViewController+MemoryLeak.m in Sources */, 421 | 2DBFD31820C3CDEC001B7F23 /* UITabBarController+MemoryLeak.m in Sources */, 422 | 2DBFD33520C3CFF5001B7F23 /* PTBFPSMonitor.m in Sources */, 423 | 2DBFD31520C3CDEC001B7F23 /* UITouch+MemoryLeak.m in Sources */, 424 | 2DBFD31C20C3CDEC001B7F23 /* NSObject+MemoryLeak.m in Sources */, 425 | 2DBFD2B220C3CD7C001B7F23 /* main.m in Sources */, 426 | 2DBFD31A20C3CDEC001B7F23 /* UIViewController+MemoryLeak.m in Sources */, 427 | 2DBFD2A420C3CD7C001B7F23 /* AppDelegate.m in Sources */, 428 | 2DBFD31720C3CDEC001B7F23 /* UIPageViewController+MemoryLeak.m in Sources */, 429 | ); 430 | runOnlyForDeploymentPostprocessing = 0; 431 | }; 432 | 2DBFD2B320C3CD7D001B7F23 /* Sources */ = { 433 | isa = PBXSourcesBuildPhase; 434 | buildActionMask = 2147483647; 435 | files = ( 436 | 2DBFD2BC20C3CD7D001B7F23 /* PTBPerformanceCenterTests.m in Sources */, 437 | ); 438 | runOnlyForDeploymentPostprocessing = 0; 439 | }; 440 | 2DBFD2BE20C3CD7D001B7F23 /* Sources */ = { 441 | isa = PBXSourcesBuildPhase; 442 | buildActionMask = 2147483647; 443 | files = ( 444 | 2DBFD2C720C3CD7D001B7F23 /* PTBPerformanceCenterUITests.m in Sources */, 445 | ); 446 | runOnlyForDeploymentPostprocessing = 0; 447 | }; 448 | /* End PBXSourcesBuildPhase section */ 449 | 450 | /* Begin PBXTargetDependency section */ 451 | 2DBFD2B920C3CD7D001B7F23 /* PBXTargetDependency */ = { 452 | isa = PBXTargetDependency; 453 | target = 2DBFD29E20C3CD7C001B7F23 /* PTBPerformanceCenter */; 454 | targetProxy = 2DBFD2B820C3CD7D001B7F23 /* PBXContainerItemProxy */; 455 | }; 456 | 2DBFD2C420C3CD7D001B7F23 /* PBXTargetDependency */ = { 457 | isa = PBXTargetDependency; 458 | target = 2DBFD29E20C3CD7C001B7F23 /* PTBPerformanceCenter */; 459 | targetProxy = 2DBFD2C320C3CD7D001B7F23 /* PBXContainerItemProxy */; 460 | }; 461 | /* End PBXTargetDependency section */ 462 | 463 | /* Begin PBXVariantGroup section */ 464 | 2DBFD2A820C3CD7C001B7F23 /* Main.storyboard */ = { 465 | isa = PBXVariantGroup; 466 | children = ( 467 | 2DBFD2A920C3CD7C001B7F23 /* Base */, 468 | ); 469 | name = Main.storyboard; 470 | sourceTree = ""; 471 | }; 472 | 2DBFD2AD20C3CD7C001B7F23 /* LaunchScreen.storyboard */ = { 473 | isa = PBXVariantGroup; 474 | children = ( 475 | 2DBFD2AE20C3CD7C001B7F23 /* Base */, 476 | ); 477 | name = LaunchScreen.storyboard; 478 | sourceTree = ""; 479 | }; 480 | /* End PBXVariantGroup section */ 481 | 482 | /* Begin XCBuildConfiguration section */ 483 | 2DBFD2C920C3CD7D001B7F23 /* Debug */ = { 484 | isa = XCBuildConfiguration; 485 | buildSettings = { 486 | ALWAYS_SEARCH_USER_PATHS = NO; 487 | CLANG_ANALYZER_NONNULL = YES; 488 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 489 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 490 | CLANG_CXX_LIBRARY = "libc++"; 491 | CLANG_ENABLE_MODULES = YES; 492 | CLANG_ENABLE_OBJC_ARC = YES; 493 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 494 | CLANG_WARN_BOOL_CONVERSION = YES; 495 | CLANG_WARN_COMMA = YES; 496 | CLANG_WARN_CONSTANT_CONVERSION = YES; 497 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 498 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 499 | CLANG_WARN_EMPTY_BODY = YES; 500 | CLANG_WARN_ENUM_CONVERSION = YES; 501 | CLANG_WARN_INFINITE_RECURSION = YES; 502 | CLANG_WARN_INT_CONVERSION = YES; 503 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 504 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 505 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 506 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 507 | CLANG_WARN_STRICT_PROTOTYPES = YES; 508 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 509 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 510 | CLANG_WARN_UNREACHABLE_CODE = YES; 511 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 512 | CODE_SIGN_IDENTITY = "iPhone Developer"; 513 | COPY_PHASE_STRIP = NO; 514 | DEBUG_INFORMATION_FORMAT = dwarf; 515 | ENABLE_STRICT_OBJC_MSGSEND = YES; 516 | ENABLE_TESTABILITY = YES; 517 | GCC_C_LANGUAGE_STANDARD = gnu11; 518 | GCC_DYNAMIC_NO_PIC = NO; 519 | GCC_NO_COMMON_BLOCKS = YES; 520 | GCC_OPTIMIZATION_LEVEL = 0; 521 | GCC_PREPROCESSOR_DEFINITIONS = ( 522 | "DEBUG=1", 523 | "$(inherited)", 524 | ); 525 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 526 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 527 | GCC_WARN_UNDECLARED_SELECTOR = YES; 528 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 529 | GCC_WARN_UNUSED_FUNCTION = YES; 530 | GCC_WARN_UNUSED_VARIABLE = YES; 531 | IPHONEOS_DEPLOYMENT_TARGET = 11.1; 532 | MTL_ENABLE_DEBUG_INFO = YES; 533 | ONLY_ACTIVE_ARCH = YES; 534 | SDKROOT = iphoneos; 535 | }; 536 | name = Debug; 537 | }; 538 | 2DBFD2CA20C3CD7D001B7F23 /* Release */ = { 539 | isa = XCBuildConfiguration; 540 | buildSettings = { 541 | ALWAYS_SEARCH_USER_PATHS = NO; 542 | CLANG_ANALYZER_NONNULL = YES; 543 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 544 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 545 | CLANG_CXX_LIBRARY = "libc++"; 546 | CLANG_ENABLE_MODULES = YES; 547 | CLANG_ENABLE_OBJC_ARC = YES; 548 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 549 | CLANG_WARN_BOOL_CONVERSION = YES; 550 | CLANG_WARN_COMMA = YES; 551 | CLANG_WARN_CONSTANT_CONVERSION = YES; 552 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 553 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 554 | CLANG_WARN_EMPTY_BODY = YES; 555 | CLANG_WARN_ENUM_CONVERSION = YES; 556 | CLANG_WARN_INFINITE_RECURSION = YES; 557 | CLANG_WARN_INT_CONVERSION = YES; 558 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 559 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 560 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 561 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 562 | CLANG_WARN_STRICT_PROTOTYPES = YES; 563 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 564 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 565 | CLANG_WARN_UNREACHABLE_CODE = YES; 566 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 567 | CODE_SIGN_IDENTITY = "iPhone Developer"; 568 | COPY_PHASE_STRIP = NO; 569 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 570 | ENABLE_NS_ASSERTIONS = NO; 571 | ENABLE_STRICT_OBJC_MSGSEND = YES; 572 | GCC_C_LANGUAGE_STANDARD = gnu11; 573 | GCC_NO_COMMON_BLOCKS = YES; 574 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 575 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 576 | GCC_WARN_UNDECLARED_SELECTOR = YES; 577 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 578 | GCC_WARN_UNUSED_FUNCTION = YES; 579 | GCC_WARN_UNUSED_VARIABLE = YES; 580 | IPHONEOS_DEPLOYMENT_TARGET = 11.1; 581 | MTL_ENABLE_DEBUG_INFO = NO; 582 | SDKROOT = iphoneos; 583 | VALIDATE_PRODUCT = YES; 584 | }; 585 | name = Release; 586 | }; 587 | 2DBFD2CC20C3CD7D001B7F23 /* Debug */ = { 588 | isa = XCBuildConfiguration; 589 | buildSettings = { 590 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 591 | CODE_SIGN_STYLE = Automatic; 592 | INFOPLIST_FILE = PTBPerformanceCenter/Info.plist; 593 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 594 | PRODUCT_BUNDLE_IDENTIFIER = PerTerbin.PTBPerformanceCenter; 595 | PRODUCT_NAME = "$(TARGET_NAME)"; 596 | TARGETED_DEVICE_FAMILY = "1,2"; 597 | }; 598 | name = Debug; 599 | }; 600 | 2DBFD2CD20C3CD7D001B7F23 /* Release */ = { 601 | isa = XCBuildConfiguration; 602 | buildSettings = { 603 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 604 | CODE_SIGN_STYLE = Automatic; 605 | INFOPLIST_FILE = PTBPerformanceCenter/Info.plist; 606 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 607 | PRODUCT_BUNDLE_IDENTIFIER = PerTerbin.PTBPerformanceCenter; 608 | PRODUCT_NAME = "$(TARGET_NAME)"; 609 | TARGETED_DEVICE_FAMILY = "1,2"; 610 | }; 611 | name = Release; 612 | }; 613 | 2DBFD2CF20C3CD7D001B7F23 /* Debug */ = { 614 | isa = XCBuildConfiguration; 615 | buildSettings = { 616 | BUNDLE_LOADER = "$(TEST_HOST)"; 617 | CODE_SIGN_STYLE = Automatic; 618 | INFOPLIST_FILE = PTBPerformanceCenterTests/Info.plist; 619 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 620 | PRODUCT_BUNDLE_IDENTIFIER = PerTerbin.PTBPerformanceCenterTests; 621 | PRODUCT_NAME = "$(TARGET_NAME)"; 622 | TARGETED_DEVICE_FAMILY = "1,2"; 623 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/PTBPerformanceCenter.app/PTBPerformanceCenter"; 624 | }; 625 | name = Debug; 626 | }; 627 | 2DBFD2D020C3CD7D001B7F23 /* Release */ = { 628 | isa = XCBuildConfiguration; 629 | buildSettings = { 630 | BUNDLE_LOADER = "$(TEST_HOST)"; 631 | CODE_SIGN_STYLE = Automatic; 632 | INFOPLIST_FILE = PTBPerformanceCenterTests/Info.plist; 633 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 634 | PRODUCT_BUNDLE_IDENTIFIER = PerTerbin.PTBPerformanceCenterTests; 635 | PRODUCT_NAME = "$(TARGET_NAME)"; 636 | TARGETED_DEVICE_FAMILY = "1,2"; 637 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/PTBPerformanceCenter.app/PTBPerformanceCenter"; 638 | }; 639 | name = Release; 640 | }; 641 | 2DBFD2D220C3CD7D001B7F23 /* Debug */ = { 642 | isa = XCBuildConfiguration; 643 | buildSettings = { 644 | CODE_SIGN_STYLE = Automatic; 645 | INFOPLIST_FILE = PTBPerformanceCenterUITests/Info.plist; 646 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 647 | PRODUCT_BUNDLE_IDENTIFIER = PerTerbin.PTBPerformanceCenterUITests; 648 | PRODUCT_NAME = "$(TARGET_NAME)"; 649 | TARGETED_DEVICE_FAMILY = "1,2"; 650 | TEST_TARGET_NAME = PTBPerformanceCenter; 651 | }; 652 | name = Debug; 653 | }; 654 | 2DBFD2D320C3CD7D001B7F23 /* Release */ = { 655 | isa = XCBuildConfiguration; 656 | buildSettings = { 657 | CODE_SIGN_STYLE = Automatic; 658 | INFOPLIST_FILE = PTBPerformanceCenterUITests/Info.plist; 659 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 660 | PRODUCT_BUNDLE_IDENTIFIER = PerTerbin.PTBPerformanceCenterUITests; 661 | PRODUCT_NAME = "$(TARGET_NAME)"; 662 | TARGETED_DEVICE_FAMILY = "1,2"; 663 | TEST_TARGET_NAME = PTBPerformanceCenter; 664 | }; 665 | name = Release; 666 | }; 667 | /* End XCBuildConfiguration section */ 668 | 669 | /* Begin XCConfigurationList section */ 670 | 2DBFD29A20C3CD7C001B7F23 /* Build configuration list for PBXProject "PTBPerformanceCenter" */ = { 671 | isa = XCConfigurationList; 672 | buildConfigurations = ( 673 | 2DBFD2C920C3CD7D001B7F23 /* Debug */, 674 | 2DBFD2CA20C3CD7D001B7F23 /* Release */, 675 | ); 676 | defaultConfigurationIsVisible = 0; 677 | defaultConfigurationName = Release; 678 | }; 679 | 2DBFD2CB20C3CD7D001B7F23 /* Build configuration list for PBXNativeTarget "PTBPerformanceCenter" */ = { 680 | isa = XCConfigurationList; 681 | buildConfigurations = ( 682 | 2DBFD2CC20C3CD7D001B7F23 /* Debug */, 683 | 2DBFD2CD20C3CD7D001B7F23 /* Release */, 684 | ); 685 | defaultConfigurationIsVisible = 0; 686 | defaultConfigurationName = Release; 687 | }; 688 | 2DBFD2CE20C3CD7D001B7F23 /* Build configuration list for PBXNativeTarget "PTBPerformanceCenterTests" */ = { 689 | isa = XCConfigurationList; 690 | buildConfigurations = ( 691 | 2DBFD2CF20C3CD7D001B7F23 /* Debug */, 692 | 2DBFD2D020C3CD7D001B7F23 /* Release */, 693 | ); 694 | defaultConfigurationIsVisible = 0; 695 | defaultConfigurationName = Release; 696 | }; 697 | 2DBFD2D120C3CD7D001B7F23 /* Build configuration list for PBXNativeTarget "PTBPerformanceCenterUITests" */ = { 698 | isa = XCConfigurationList; 699 | buildConfigurations = ( 700 | 2DBFD2D220C3CD7D001B7F23 /* Debug */, 701 | 2DBFD2D320C3CD7D001B7F23 /* Release */, 702 | ); 703 | defaultConfigurationIsVisible = 0; 704 | defaultConfigurationName = Release; 705 | }; 706 | /* End XCConfigurationList section */ 707 | }; 708 | rootObject = 2DBFD29720C3CD7C001B7F23 /* Project object */; 709 | } 710 | -------------------------------------------------------------------------------- /PTBPerformanceCenter.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /PTBPerformanceCenter.xcodeproj/project.xcworkspace/xcuserdata/perterbin.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PerTerbin/PTBPerformanceCenter/2d39535347d5e0a00590306411479843dcf77a98/PTBPerformanceCenter.xcodeproj/project.xcworkspace/xcuserdata/perterbin.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /PTBPerformanceCenter.xcodeproj/xcuserdata/perterbin.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | PTBPerformanceCenter.xcscheme 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /PTBPerformanceCenter/AppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.h 3 | // PTBPerformanceCenter 4 | // 5 | // Created by PerTerbin on 2018/6/3. 6 | // Copyright © 2018年 PerTerbin. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface AppDelegate : UIResponder 12 | 13 | @property (strong, nonatomic) UIWindow *window; 14 | 15 | 16 | @end 17 | 18 | -------------------------------------------------------------------------------- /PTBPerformanceCenter/AppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.m 3 | // PTBPerformanceCenter 4 | // 5 | // Created by PerTerbin on 2018/6/3. 6 | // Copyright © 2018年 PerTerbin. All rights reserved. 7 | // 8 | 9 | #import "AppDelegate.h" 10 | #import "PTBPerformanceCenter.h" 11 | 12 | @interface AppDelegate () 13 | 14 | @end 15 | 16 | @implementation AppDelegate 17 | 18 | 19 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { 20 | // Override point for customization after application launch. 21 | 22 | [[PTBPerformanceCenter defaultCenter] enable]; 23 | 24 | return YES; 25 | } 26 | 27 | 28 | - (void)applicationWillResignActive:(UIApplication *)application { 29 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 30 | // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game. 31 | } 32 | 33 | 34 | - (void)applicationDidEnterBackground:(UIApplication *)application { 35 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 36 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 37 | } 38 | 39 | 40 | - (void)applicationWillEnterForeground:(UIApplication *)application { 41 | // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background. 42 | } 43 | 44 | 45 | - (void)applicationDidBecomeActive:(UIApplication *)application { 46 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 47 | } 48 | 49 | 50 | - (void)applicationWillTerminate:(UIApplication *)application { 51 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 52 | } 53 | 54 | 55 | @end 56 | -------------------------------------------------------------------------------- /PTBPerformanceCenter/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "20x20", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "20x20", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "29x29", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "29x29", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "40x40", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "40x40", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "size" : "60x60", 36 | "scale" : "2x" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "size" : "60x60", 41 | "scale" : "3x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "20x20", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "20x20", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "29x29", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "29x29", 61 | "scale" : "2x" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "size" : "40x40", 66 | "scale" : "1x" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "size" : "40x40", 71 | "scale" : "2x" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "size" : "76x76", 76 | "scale" : "1x" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "size" : "76x76", 81 | "scale" : "2x" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "size" : "83.5x83.5", 86 | "scale" : "2x" 87 | } 88 | ], 89 | "info" : { 90 | "version" : 1, 91 | "author" : "xcode" 92 | } 93 | } -------------------------------------------------------------------------------- /PTBPerformanceCenter/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 | -------------------------------------------------------------------------------- /PTBPerformanceCenter/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 | -------------------------------------------------------------------------------- /PTBPerformanceCenter/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 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | UILaunchStoryboardName 24 | LaunchScreen 25 | UIMainStoryboardFile 26 | Main 27 | UIRequiredDeviceCapabilities 28 | 29 | armv7 30 | 31 | UISupportedInterfaceOrientations 32 | 33 | UIInterfaceOrientationPortrait 34 | UIInterfaceOrientationLandscapeLeft 35 | UIInterfaceOrientationLandscapeRight 36 | 37 | UISupportedInterfaceOrientations~ipad 38 | 39 | UIInterfaceOrientationPortrait 40 | UIInterfaceOrientationPortraitUpsideDown 41 | UIInterfaceOrientationLandscapeLeft 42 | UIInterfaceOrientationLandscapeRight 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /PTBPerformanceCenter/PTBPerformanceCenter/LeakFinder/MLeakedObjectProxy.h: -------------------------------------------------------------------------------- 1 | // 2 | // MLeakedObjectProxy.h 3 | // MLeaksFinder 4 | // 5 | // Created by 佘泽坡 on 7/15/16. 6 | // Copyright © 2016 zeposhe. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface MLeakedObjectProxy : NSObject 12 | 13 | + (BOOL)isAnyObjectLeakedAtPtrs:(NSSet *)ptrs; 14 | + (void)addLeakedObject:(id)object; 15 | 16 | @end 17 | -------------------------------------------------------------------------------- /PTBPerformanceCenter/PTBPerformanceCenter/LeakFinder/MLeakedObjectProxy.m: -------------------------------------------------------------------------------- 1 | // 2 | // MLeakedObjectProxy.m 3 | // MLeaksFinder 4 | // 5 | // Created by 佘泽坡 on 7/15/16. 6 | // Copyright © 2016 zeposhe. All rights reserved. 7 | // 8 | 9 | #import "MLeakedObjectProxy.h" 10 | #import "MLeaksFinder.h" 11 | #import "MLeaksMessenger.h" 12 | #import "NSObject+MemoryLeak.h" 13 | #import 14 | #import 15 | #import "PTBPerformanceCenter.h" 16 | 17 | #if _INTERNAL_MLF_RC_ENABLED 18 | #import 19 | #endif 20 | 21 | static NSMutableSet *leakedObjectPtrs; 22 | 23 | @interface MLeakedObjectProxy () 24 | @property (nonatomic, weak) id object; 25 | @property (nonatomic, strong) NSNumber *objectPtr; 26 | @property (nonatomic, strong) NSArray *viewStack; 27 | @end 28 | 29 | @implementation MLeakedObjectProxy 30 | 31 | + (BOOL)isAnyObjectLeakedAtPtrs:(NSSet *)ptrs { 32 | NSAssert([NSThread isMainThread], @"Must be in main thread."); 33 | 34 | static dispatch_once_t onceToken; 35 | dispatch_once(&onceToken, ^{ 36 | leakedObjectPtrs = [[NSMutableSet alloc] init]; 37 | }); 38 | 39 | if (!ptrs.count) { 40 | return NO; 41 | } 42 | if ([leakedObjectPtrs intersectsSet:ptrs]) { 43 | return YES; 44 | } else { 45 | return NO; 46 | } 47 | } 48 | 49 | + (void)addLeakedObject:(id)object { 50 | NSAssert([NSThread isMainThread], @"Must be in main thread."); 51 | 52 | MLeakedObjectProxy *proxy = [[MLeakedObjectProxy alloc] init]; 53 | proxy.object = object; 54 | proxy.objectPtr = @((uintptr_t)object); 55 | proxy.viewStack = [object viewStack]; 56 | static const void *const kLeakedObjectProxyKey = &kLeakedObjectProxyKey; 57 | objc_setAssociatedObject(object, kLeakedObjectProxyKey, proxy, OBJC_ASSOCIATION_RETAIN); 58 | 59 | [leakedObjectPtrs addObject:proxy.objectPtr]; 60 | 61 | #if _INTERNAL_MLF_RC_ENABLED 62 | __block NSArray *retainCycleArray = @[@"Fail to find a retain cycle"]; 63 | dispatch_async(dispatch_get_global_queue(0, 0), ^{ 64 | // FBRetainCycleDetector *detector = [FBRetainCycleDetector new]; 65 | // [detector addCandidate:proxy.object]; 66 | // NSSet *retainCycles = [detector findRetainCyclesWithMaxCycleLength:20]; 67 | // 68 | // BOOL hasFound = NO; 69 | // for (NSArray *retainCycle in retainCycles) { 70 | // NSInteger index = 0; 71 | // for (FBObjectiveCGraphElement *element in retainCycle) { 72 | // if (element.object == object) { 73 | // retainCycleArray = [proxy shiftArray:retainCycle toIndex:index]; 74 | // hasFound = YES; 75 | // break; 76 | // } 77 | // ++index; 78 | // } 79 | // if (hasFound) { 80 | // break; 81 | // } 82 | // } 83 | 84 | dispatch_async(dispatch_get_main_queue(), ^{ 85 | [[PTBPerformanceCenter defaultCenter].viewController findMenoryLeakWithViewStack:proxy.viewStack retainCycle:retainCycleArray]; 86 | }); 87 | }); 88 | #else 89 | [MLeaksMessenger alertWithTitle:@"Memory Leak" 90 | message:[NSString stringWithFormat:@"%@", proxy.viewStack]]; 91 | #endif 92 | } 93 | 94 | - (void)dealloc { 95 | NSNumber *objectPtr = _objectPtr; 96 | NSArray *viewStack = _viewStack; 97 | dispatch_async(dispatch_get_main_queue(), ^{ 98 | [leakedObjectPtrs removeObject:objectPtr]; 99 | [MLeaksMessenger alertWithTitle:@"Object Deallocated" 100 | message:[NSString stringWithFormat:@"%@", viewStack]]; 101 | }); 102 | } 103 | 104 | #pragma mark - UIAlertViewDelegate 105 | 106 | - (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex { 107 | if (!buttonIndex) { 108 | return; 109 | } 110 | 111 | id object = self.object; 112 | if (!object) { 113 | return; 114 | } 115 | 116 | #if _INTERNAL_MLF_RC_ENABLED 117 | dispatch_async(dispatch_get_global_queue(0, 0), ^{ 118 | FBRetainCycleDetector *detector = [FBRetainCycleDetector new]; 119 | [detector addCandidate:self.object]; 120 | NSSet *retainCycles = [detector findRetainCyclesWithMaxCycleLength:20]; 121 | 122 | BOOL hasFound = NO; 123 | for (NSArray *retainCycle in retainCycles) { 124 | NSInteger index = 0; 125 | for (FBObjectiveCGraphElement *element in retainCycle) { 126 | if (element.object == object) { 127 | NSArray *shiftedRetainCycle = [self shiftArray:retainCycle toIndex:index]; 128 | 129 | dispatch_async(dispatch_get_main_queue(), ^{ 130 | [MLeaksMessenger alertWithTitle:@"Retain Cycle" 131 | message:[NSString stringWithFormat:@"%@", shiftedRetainCycle]]; 132 | }); 133 | hasFound = YES; 134 | break; 135 | } 136 | 137 | ++index; 138 | } 139 | if (hasFound) { 140 | break; 141 | } 142 | } 143 | if (!hasFound) { 144 | dispatch_async(dispatch_get_main_queue(), ^{ 145 | [MLeaksMessenger alertWithTitle:@"Retain Cycle" 146 | message:@"Fail to find a retain cycle"]; 147 | }); 148 | } 149 | }); 150 | #endif 151 | } 152 | 153 | - (NSArray *)shiftArray:(NSArray *)array toIndex:(NSInteger)index { 154 | if (index == 0) { 155 | return array; 156 | } 157 | 158 | NSRange range = NSMakeRange(index, array.count - index); 159 | NSMutableArray *result = [[array subarrayWithRange:range] mutableCopy]; 160 | [result addObjectsFromArray:[array subarrayWithRange:NSMakeRange(0, index)]]; 161 | return result; 162 | } 163 | 164 | @end 165 | -------------------------------------------------------------------------------- /PTBPerformanceCenter/PTBPerformanceCenter/LeakFinder/MLeaksFinder.h: -------------------------------------------------------------------------------- 1 | // 2 | // MLeaksFinder.h 3 | // MLeaksFinder 4 | // 5 | // Created by zeposhe on 12/12/15. 6 | // Copyright © 2015 zeposhe. All rights reserved. 7 | // 8 | 9 | #import "NSObject+MemoryLeak.h" 10 | 11 | //#define MEMORY_LEAKS_FINDER_ENABLED 0 12 | 13 | #ifdef MEMORY_LEAKS_FINDER_ENABLED 14 | #define _INTERNAL_MLF_ENABLED MEMORY_LEAKS_FINDER_ENABLED 15 | #else 16 | #define _INTERNAL_MLF_ENABLED (env_t == 2) 17 | #endif 18 | 19 | //#define MEMORY_LEAKS_FINDER_RETAIN_CYCLE_ENABLED 1 20 | 21 | #ifdef MEMORY_LEAKS_FINDER_RETAIN_CYCLE_ENABLED 22 | #define _INTERNAL_MLF_RC_ENABLED MEMORY_LEAKS_FINDER_RETAIN_CYCLE_ENABLED 23 | #elif COCOAPODS 24 | #define _INTERNAL_MLF_RC_ENABLED COCOAPODS 25 | #endif 26 | -------------------------------------------------------------------------------- /PTBPerformanceCenter/PTBPerformanceCenter/LeakFinder/MLeaksMessenger.h: -------------------------------------------------------------------------------- 1 | // 2 | // MLeaksMessenger.h 3 | // MLeaksFinder 4 | // 5 | // Created by 佘泽坡 on 7/17/16. 6 | // Copyright © 2016 zeposhe. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | 12 | @interface MLeaksMessenger : NSObject 13 | 14 | + (void)alertWithTitle:(NSString *)title message:(NSString *)message; 15 | + (void)alertWithTitle:(NSString *)title 16 | message:(NSString *)message 17 | delegate:(id)delegate 18 | additionalButtonTitle:(NSString *)additionalButtonTitle; 19 | 20 | @end 21 | -------------------------------------------------------------------------------- /PTBPerformanceCenter/PTBPerformanceCenter/LeakFinder/MLeaksMessenger.m: -------------------------------------------------------------------------------- 1 | // 2 | // MLeaksMessenger.m 3 | // MLeaksFinder 4 | // 5 | // Created by 佘泽坡 on 7/17/16. 6 | // Copyright © 2016 zeposhe. All rights reserved. 7 | // 8 | 9 | #import "MLeaksMessenger.h" 10 | 11 | static __weak UIAlertView *alertView; 12 | 13 | @implementation MLeaksMessenger 14 | 15 | + (void)alertWithTitle:(NSString *)title message:(NSString *)message { 16 | [self alertWithTitle:title message:message delegate:nil additionalButtonTitle:nil]; 17 | } 18 | 19 | + (void)alertWithTitle:(NSString *)title 20 | message:(NSString *)message 21 | delegate:(id)delegate 22 | additionalButtonTitle:(NSString *)additionalButtonTitle { 23 | [alertView dismissWithClickedButtonIndex:0 animated:NO]; 24 | UIAlertView *alertViewTemp = [[UIAlertView alloc] initWithTitle:title 25 | message:message 26 | delegate:delegate 27 | cancelButtonTitle:@"OK" 28 | otherButtonTitles:additionalButtonTitle, nil]; 29 | [alertViewTemp show]; 30 | alertView = alertViewTemp; 31 | 32 | NSLog(@"%@: %@", title, message); 33 | } 34 | 35 | @end 36 | -------------------------------------------------------------------------------- /PTBPerformanceCenter/PTBPerformanceCenter/LeakFinder/NSObject+MemoryLeak.h: -------------------------------------------------------------------------------- 1 | // 2 | // NSObject+MemoryLeak.h 3 | // MLeaksFinder 4 | // 5 | // Created by zeposhe on 12/12/15. 6 | // Copyright © 2015 zeposhe. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | #define MLCheck(TARGET) [self willReleaseObject:(TARGET) relationship:@#TARGET]; 12 | 13 | @interface NSObject (MemoryLeak) 14 | 15 | - (BOOL)willDealloc; 16 | - (void)willReleaseObject:(id)object relationship:(NSString *)relationship; 17 | 18 | - (void)willReleaseChild:(id)child; 19 | - (void)willReleaseChildren:(NSArray *)children; 20 | 21 | - (NSArray *)viewStack; 22 | 23 | + (void)addClassNamesToWhitelist:(NSArray *)classNames; 24 | 25 | + (void)swizzleSEL:(SEL)originalSEL withSEL:(SEL)swizzledSEL; 26 | 27 | @end 28 | -------------------------------------------------------------------------------- /PTBPerformanceCenter/PTBPerformanceCenter/LeakFinder/NSObject+MemoryLeak.m: -------------------------------------------------------------------------------- 1 | // 2 | // NSObject+MemoryLeak.m 3 | // MLeaksFinder 4 | // 5 | // Created by zeposhe on 12/12/15. 6 | // Copyright © 2015 zeposhe. All rights reserved. 7 | // 8 | 9 | #import "NSObject+MemoryLeak.h" 10 | #import "MLeakedObjectProxy.h" 11 | #import "MLeaksFinder.h" 12 | #import 13 | #import 14 | 15 | #if _INTERNAL_MLF_RC_ENABLED 16 | #import 17 | #endif 18 | 19 | static const void *const kViewStackKey = &kViewStackKey; 20 | static const void *const kParentPtrsKey = &kParentPtrsKey; 21 | const void *const kLatestSenderKey = &kLatestSenderKey; 22 | 23 | @implementation NSObject (MemoryLeak) 24 | 25 | - (BOOL)willDealloc { 26 | NSString *className = NSStringFromClass([self class]); 27 | if ([[NSObject classNamesWhitelist] containsObject:className]) 28 | return NO; 29 | 30 | NSNumber *senderPtr = objc_getAssociatedObject([UIApplication sharedApplication], kLatestSenderKey); 31 | if ([senderPtr isEqualToNumber:@((uintptr_t)self)]) 32 | return NO; 33 | 34 | __weak id weakSelf = self; 35 | dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ 36 | __strong id strongSelf = weakSelf; 37 | [strongSelf assertNotDealloc]; 38 | }); 39 | 40 | return YES; 41 | } 42 | 43 | - (void)assertNotDealloc { 44 | if ([MLeakedObjectProxy isAnyObjectLeakedAtPtrs:[self parentPtrs]]) { 45 | return; 46 | } 47 | [MLeakedObjectProxy addLeakedObject:self]; 48 | 49 | NSString *className = NSStringFromClass([self class]); 50 | NSLog(@"Possibly Memory Leak.\nIn case that %@ should not be dealloced, override -willDealloc in %@ by returning NO.\nView-ViewController stack: %@", className, className, [self viewStack]); 51 | } 52 | 53 | - (void)willReleaseObject:(id)object relationship:(NSString *)relationship { 54 | if ([relationship hasPrefix:@"self"]) { 55 | relationship = [relationship stringByReplacingCharactersInRange:NSMakeRange(0, 4) withString:@""]; 56 | } 57 | NSString *className = NSStringFromClass([object class]); 58 | className = [NSString stringWithFormat:@"%@(%@), ", relationship, className]; 59 | 60 | [object setViewStack:[[self viewStack] arrayByAddingObject:className]]; 61 | [object setParentPtrs:[[self parentPtrs] setByAddingObject:@((uintptr_t)object)]]; 62 | [object willDealloc]; 63 | } 64 | 65 | - (void)willReleaseChild:(id)child { 66 | if (!child) { 67 | return; 68 | } 69 | 70 | [self willReleaseChildren:@[ child ]]; 71 | } 72 | 73 | - (void)willReleaseChildren:(NSArray *)children { 74 | NSArray *viewStack = [self viewStack]; 75 | NSSet *parentPtrs = [self parentPtrs]; 76 | for (id child in children) { 77 | NSString *className = NSStringFromClass([child class]); 78 | [child setViewStack:[viewStack arrayByAddingObject:className]]; 79 | [child setParentPtrs:[parentPtrs setByAddingObject:@((uintptr_t)child)]]; 80 | [child willDealloc]; 81 | } 82 | } 83 | 84 | - (NSArray *)viewStack { 85 | NSArray *viewStack = objc_getAssociatedObject(self, kViewStackKey); 86 | if (viewStack) { 87 | return viewStack; 88 | } 89 | 90 | NSString *className = NSStringFromClass([self class]); 91 | return @[ className ]; 92 | } 93 | 94 | - (void)setViewStack:(NSArray *)viewStack { 95 | objc_setAssociatedObject(self, kViewStackKey, viewStack, OBJC_ASSOCIATION_RETAIN); 96 | } 97 | 98 | - (NSSet *)parentPtrs { 99 | NSSet *parentPtrs = objc_getAssociatedObject(self, kParentPtrsKey); 100 | if (!parentPtrs) { 101 | parentPtrs = [[NSSet alloc] initWithObjects:@((uintptr_t)self), nil]; 102 | } 103 | return parentPtrs; 104 | } 105 | 106 | - (void)setParentPtrs:(NSSet *)parentPtrs { 107 | objc_setAssociatedObject(self, kParentPtrsKey, parentPtrs, OBJC_ASSOCIATION_RETAIN); 108 | } 109 | 110 | + (NSMutableSet *)classNamesWhitelist { 111 | static NSMutableSet *whitelist = nil; 112 | static dispatch_once_t onceToken; 113 | dispatch_once(&onceToken, ^{ 114 | whitelist = [NSMutableSet setWithObjects: 115 | @"UIFieldEditor", // UIAlertControllerTextField 116 | @"UINavigationBar", 117 | @"_UIAlertControllerActionView", 118 | @"_UIVisualEffectBackdropView", 119 | nil]; 120 | 121 | // System's bug since iOS 10 and not fixed yet up to this ci. 122 | NSString *systemVersion = [UIDevice currentDevice].systemVersion; 123 | if ([systemVersion compare:@"10.0" options:NSNumericSearch] != NSOrderedAscending) { 124 | [whitelist addObject:@"UISwitch"]; 125 | } 126 | }); 127 | return whitelist; 128 | } 129 | 130 | + (void)addClassNamesToWhitelist:(NSArray *)classNames { 131 | [[self classNamesWhitelist] addObjectsFromArray:classNames]; 132 | } 133 | 134 | + (void)swizzleSEL:(SEL)originalSEL withSEL:(SEL)swizzledSEL { 135 | #if _INTERNAL_MLF_ENABLED 136 | 137 | #if _INTERNAL_MLF_RC_ENABLED 138 | // Just find a place to set up FBRetainCycleDetector. 139 | static dispatch_once_t onceToken; 140 | dispatch_once(&onceToken, ^{ 141 | dispatch_async(dispatch_get_main_queue(), ^{ 142 | [FBAssociationManager hook]; 143 | }); 144 | }); 145 | #endif 146 | 147 | Class class = [self class]; 148 | 149 | Method originalMethod = class_getInstanceMethod(class, originalSEL); 150 | Method swizzledMethod = class_getInstanceMethod(class, swizzledSEL); 151 | 152 | BOOL didAddMethod = 153 | class_addMethod(class, 154 | originalSEL, 155 | method_getImplementation(swizzledMethod), 156 | method_getTypeEncoding(swizzledMethod)); 157 | 158 | if (didAddMethod) { 159 | class_replaceMethod(class, 160 | swizzledSEL, 161 | method_getImplementation(originalMethod), 162 | method_getTypeEncoding(originalMethod)); 163 | } else { 164 | method_exchangeImplementations(originalMethod, swizzledMethod); 165 | } 166 | #endif 167 | } 168 | 169 | @end 170 | -------------------------------------------------------------------------------- /PTBPerformanceCenter/PTBPerformanceCenter/LeakFinder/UIApplication+MemoryLeak.h: -------------------------------------------------------------------------------- 1 | // 2 | // UIApplication+MemoryLeak.h 3 | // MLeaksFinder 4 | // 5 | // Created by 佘泽坡 on 5/11/16. 6 | // Copyright © 2016 zeposhe. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "MLeaksFinder.h" 11 | 12 | #if _INTERNAL_MLF_ENABLED 13 | 14 | @interface UIApplication (MemoryLeak) 15 | 16 | @end 17 | 18 | #endif 19 | -------------------------------------------------------------------------------- /PTBPerformanceCenter/PTBPerformanceCenter/LeakFinder/UIApplication+MemoryLeak.m: -------------------------------------------------------------------------------- 1 | // 2 | // UIApplication+MemoryLeak.m 3 | // MLeaksFinder 4 | // 5 | // Created by 佘泽坡 on 5/11/16. 6 | // Copyright © 2016 zeposhe. All rights reserved. 7 | // 8 | 9 | #import "UIApplication+MemoryLeak.h" 10 | #import "NSObject+MemoryLeak.h" 11 | #import 12 | 13 | #if _INTERNAL_MLF_ENABLED 14 | 15 | extern const void *const kLatestSenderKey; 16 | 17 | @implementation UIApplication (MemoryLeak) 18 | 19 | + (void)load { 20 | static dispatch_once_t onceToken; 21 | dispatch_once(&onceToken, ^{ 22 | [self swizzleSEL:@selector(sendAction:to:from:forEvent:) withSEL:@selector(swizzled_sendAction:to:from:forEvent:)]; 23 | }); 24 | } 25 | 26 | - (BOOL)swizzled_sendAction:(SEL)action to:(id)target from:(id)sender forEvent:(UIEvent *)event { 27 | objc_setAssociatedObject(self, kLatestSenderKey, @((uintptr_t)sender), OBJC_ASSOCIATION_RETAIN); 28 | 29 | return [self swizzled_sendAction:action to:target from:sender forEvent:event]; 30 | } 31 | 32 | @end 33 | 34 | #endif 35 | -------------------------------------------------------------------------------- /PTBPerformanceCenter/PTBPerformanceCenter/LeakFinder/UINavigationController+MemoryLeak.h: -------------------------------------------------------------------------------- 1 | // 2 | // UINavigationController+MemoryLeak.h 3 | // MLeaksFinder 4 | // 5 | // Created by zeposhe on 12/12/15. 6 | // Copyright © 2015 zeposhe. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "MLeaksFinder.h" 11 | 12 | #if _INTERNAL_MLF_ENABLED 13 | 14 | @interface UINavigationController (MemoryLeak) 15 | 16 | @end 17 | 18 | #endif 19 | -------------------------------------------------------------------------------- /PTBPerformanceCenter/PTBPerformanceCenter/LeakFinder/UINavigationController+MemoryLeak.m: -------------------------------------------------------------------------------- 1 | // 2 | // UINavigationController+MemoryLeak.m 3 | // MLeaksFinder 4 | // 5 | // Created by zeposhe on 12/12/15. 6 | // Copyright © 2015 zeposhe. All rights reserved. 7 | // 8 | 9 | #import "UINavigationController+MemoryLeak.h" 10 | #import "NSObject+MemoryLeak.h" 11 | #import 12 | 13 | #if _INTERNAL_MLF_ENABLED 14 | 15 | static const void *const kPoppedDetailVCKey = &kPoppedDetailVCKey; 16 | 17 | @implementation UINavigationController (MemoryLeak) 18 | 19 | + (void)load { 20 | static dispatch_once_t onceToken; 21 | dispatch_once(&onceToken, ^{ 22 | [self swizzleSEL:@selector(pushViewController:animated:) withSEL:@selector(swizzled_pushViewController:animated:)]; 23 | [self swizzleSEL:@selector(popViewControllerAnimated:) withSEL:@selector(swizzled_popViewControllerAnimated:)]; 24 | [self swizzleSEL:@selector(popToViewController:animated:) withSEL:@selector(swizzled_popToViewController:animated:)]; 25 | [self swizzleSEL:@selector(popToRootViewControllerAnimated:) withSEL:@selector(swizzled_popToRootViewControllerAnimated:)]; 26 | }); 27 | } 28 | 29 | - (void)swizzled_pushViewController:(UIViewController *)viewController animated:(BOOL)animated { 30 | if (self.splitViewController) { 31 | id detailViewController = objc_getAssociatedObject(self, kPoppedDetailVCKey); 32 | if ([detailViewController isKindOfClass:[UIViewController class]]) { 33 | [detailViewController willDealloc]; 34 | objc_setAssociatedObject(self, kPoppedDetailVCKey, nil, OBJC_ASSOCIATION_RETAIN); 35 | } 36 | } 37 | 38 | [self swizzled_pushViewController:viewController animated:animated]; 39 | } 40 | 41 | - (UIViewController *)swizzled_popViewControllerAnimated:(BOOL)animated { 42 | UIViewController *poppedViewController = [self swizzled_popViewControllerAnimated:animated]; 43 | 44 | if (!poppedViewController) { 45 | return nil; 46 | } 47 | 48 | // Detail VC in UISplitViewController is not dealloced until another detail VC is shown 49 | if (self.splitViewController && 50 | self.splitViewController.viewControllers.firstObject == self && 51 | self.splitViewController == poppedViewController.splitViewController) { 52 | objc_setAssociatedObject(self, kPoppedDetailVCKey, poppedViewController, OBJC_ASSOCIATION_RETAIN); 53 | return poppedViewController; 54 | } 55 | 56 | // VC is not dealloced until disappear when popped using a left-edge swipe gesture 57 | extern const void *const kHasBeenPoppedKey; 58 | objc_setAssociatedObject(poppedViewController, kHasBeenPoppedKey, @(YES), OBJC_ASSOCIATION_RETAIN); 59 | 60 | return poppedViewController; 61 | } 62 | 63 | - (NSArray *)swizzled_popToViewController:(UIViewController *)viewController animated:(BOOL)animated { 64 | NSArray *poppedViewControllers = [self swizzled_popToViewController:viewController animated:animated]; 65 | 66 | for (UIViewController *viewController in poppedViewControllers) { 67 | [viewController willDealloc]; 68 | } 69 | 70 | return poppedViewControllers; 71 | } 72 | 73 | - (NSArray *)swizzled_popToRootViewControllerAnimated:(BOOL)animated { 74 | NSArray *poppedViewControllers = [self swizzled_popToRootViewControllerAnimated:animated]; 75 | 76 | for (UIViewController *viewController in poppedViewControllers) { 77 | [viewController willDealloc]; 78 | } 79 | 80 | return poppedViewControllers; 81 | } 82 | 83 | - (BOOL)willDealloc { 84 | if (![super willDealloc]) { 85 | return NO; 86 | } 87 | 88 | [self willReleaseChildren:self.viewControllers]; 89 | 90 | return YES; 91 | } 92 | 93 | @end 94 | 95 | #endif 96 | -------------------------------------------------------------------------------- /PTBPerformanceCenter/PTBPerformanceCenter/LeakFinder/UIPageViewController+MemoryLeak.h: -------------------------------------------------------------------------------- 1 | // 2 | // UIPageViewController+MemoryLeak.h 3 | // MLeaksFinder 4 | // 5 | // Created by zeposhe on 12/12/15. 6 | // Copyright © 2015 zeposhe. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "MLeaksFinder.h" 11 | 12 | #if _INTERNAL_MLF_ENABLED 13 | 14 | @interface UIPageViewController (MemoryLeak) 15 | 16 | @end 17 | 18 | #endif 19 | -------------------------------------------------------------------------------- /PTBPerformanceCenter/PTBPerformanceCenter/LeakFinder/UIPageViewController+MemoryLeak.m: -------------------------------------------------------------------------------- 1 | // 2 | // UIPageViewController+MemoryLeak.m 3 | // MLeaksFinder 4 | // 5 | // Created by zeposhe on 12/12/15. 6 | // Copyright © 2015 zeposhe. All rights reserved. 7 | // 8 | 9 | #import "UIPageViewController+MemoryLeak.h" 10 | #import "NSObject+MemoryLeak.h" 11 | 12 | #if _INTERNAL_MLF_ENABLED 13 | 14 | @implementation UIPageViewController (MemoryLeak) 15 | 16 | - (BOOL)willDealloc { 17 | if (![super willDealloc]) { 18 | return NO; 19 | } 20 | 21 | [self willReleaseChildren:self.viewControllers]; 22 | 23 | return YES; 24 | } 25 | 26 | @end 27 | 28 | #endif 29 | -------------------------------------------------------------------------------- /PTBPerformanceCenter/PTBPerformanceCenter/LeakFinder/UISplitViewController+MemoryLeak.h: -------------------------------------------------------------------------------- 1 | // 2 | // UISplitViewController+MemoryLeak.h 3 | // MLeaksFinder 4 | // 5 | // Created by zeposhe on 12/12/15. 6 | // Copyright © 2015 zeposhe. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "MLeaksFinder.h" 11 | 12 | #if _INTERNAL_MLF_ENABLED 13 | 14 | @interface UISplitViewController (MemoryLeak) 15 | 16 | @end 17 | 18 | #endif 19 | -------------------------------------------------------------------------------- /PTBPerformanceCenter/PTBPerformanceCenter/LeakFinder/UISplitViewController+MemoryLeak.m: -------------------------------------------------------------------------------- 1 | // 2 | // UISplitViewController+MemoryLeak.m 3 | // MLeaksFinder 4 | // 5 | // Created by zeposhe on 12/12/15. 6 | // Copyright © 2015 zeposhe. All rights reserved. 7 | // 8 | 9 | #import "UISplitViewController+MemoryLeak.h" 10 | #import "NSObject+MemoryLeak.h" 11 | 12 | #if _INTERNAL_MLF_ENABLED 13 | 14 | @implementation UISplitViewController (MemoryLeak) 15 | 16 | - (BOOL)willDealloc { 17 | if (![super willDealloc]) { 18 | return NO; 19 | } 20 | 21 | [self willReleaseChildren:self.viewControllers]; 22 | 23 | return YES; 24 | } 25 | 26 | @end 27 | 28 | #endif 29 | -------------------------------------------------------------------------------- /PTBPerformanceCenter/PTBPerformanceCenter/LeakFinder/UITabBarController+MemoryLeak.h: -------------------------------------------------------------------------------- 1 | // 2 | // UITabBarController+MemoryLeak.h 3 | // MLeaksFinder 4 | // 5 | // Created by zeposhe on 12/12/15. 6 | // Copyright © 2015 zeposhe. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "MLeaksFinder.h" 11 | 12 | #if _INTERNAL_MLF_ENABLED 13 | 14 | @interface UITabBarController (MemoryLeak) 15 | 16 | @end 17 | 18 | #endif 19 | -------------------------------------------------------------------------------- /PTBPerformanceCenter/PTBPerformanceCenter/LeakFinder/UITabBarController+MemoryLeak.m: -------------------------------------------------------------------------------- 1 | // 2 | // UITabBarController+MemoryLeak.m 3 | // MLeaksFinder 4 | // 5 | // Created by zeposhe on 12/12/15. 6 | // Copyright © 2015 zeposhe. All rights reserved. 7 | // 8 | 9 | #import "UITabBarController+MemoryLeak.h" 10 | #import "NSObject+MemoryLeak.h" 11 | 12 | #if _INTERNAL_MLF_ENABLED 13 | 14 | @implementation UITabBarController (MemoryLeak) 15 | 16 | - (BOOL)willDealloc { 17 | if (![super willDealloc]) { 18 | return NO; 19 | } 20 | 21 | [self willReleaseChildren:self.viewControllers]; 22 | 23 | return YES; 24 | } 25 | 26 | @end 27 | 28 | #endif 29 | -------------------------------------------------------------------------------- /PTBPerformanceCenter/PTBPerformanceCenter/LeakFinder/UITouch+MemoryLeak.h: -------------------------------------------------------------------------------- 1 | // 2 | // UITouch+MemoryLeak.h 3 | // MLeaksFinder 4 | // 5 | // Created by 佘泽坡 on 8/31/16. 6 | // Copyright © 2016 zeposhe. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "MLeaksFinder.h" 11 | 12 | #if _INTERNAL_MLF_ENABLED 13 | 14 | @interface UITouch (MemoryLeak) 15 | 16 | @end 17 | 18 | #endif 19 | -------------------------------------------------------------------------------- /PTBPerformanceCenter/PTBPerformanceCenter/LeakFinder/UITouch+MemoryLeak.m: -------------------------------------------------------------------------------- 1 | // 2 | // UITouch+MemoryLeak.m 3 | // MLeaksFinder 4 | // 5 | // Created by 佘泽坡 on 8/31/16. 6 | // Copyright © 2016 zeposhe. All rights reserved. 7 | // 8 | 9 | #import "UITouch+MemoryLeak.h" 10 | #import 11 | 12 | #if _INTERNAL_MLF_ENABLED 13 | 14 | extern const void *const kLatestSenderKey; 15 | 16 | @implementation UITouch (MemoryLeak) 17 | 18 | + (void)load { 19 | static dispatch_once_t onceToken; 20 | dispatch_once(&onceToken, ^{ 21 | [self swizzleSEL:@selector(setView:) withSEL:@selector(swizzled_setView:)]; 22 | }); 23 | } 24 | 25 | - (void)swizzled_setView:(UIView *)view { 26 | [self swizzled_setView:view]; 27 | 28 | if (view) { 29 | objc_setAssociatedObject([UIApplication sharedApplication], 30 | kLatestSenderKey, 31 | @((uintptr_t)view), 32 | OBJC_ASSOCIATION_RETAIN); 33 | } 34 | } 35 | 36 | @end 37 | 38 | #endif 39 | -------------------------------------------------------------------------------- /PTBPerformanceCenter/PTBPerformanceCenter/LeakFinder/UIView+MemoryLeak.h: -------------------------------------------------------------------------------- 1 | // 2 | // UIView+MemoryLeak.h 3 | // MLeaksFinder 4 | // 5 | // Created by zeposhe on 12/12/15. 6 | // Copyright © 2015 zeposhe. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "MLeaksFinder.h" 11 | 12 | #if _INTERNAL_MLF_ENABLED 13 | 14 | @interface UIView (MemoryLeak) 15 | 16 | @end 17 | 18 | #endif 19 | -------------------------------------------------------------------------------- /PTBPerformanceCenter/PTBPerformanceCenter/LeakFinder/UIView+MemoryLeak.m: -------------------------------------------------------------------------------- 1 | // 2 | // UIView+MemoryLeak.m 3 | // MLeaksFinder 4 | // 5 | // Created by zeposhe on 12/12/15. 6 | // Copyright © 2015 zeposhe. All rights reserved. 7 | // 8 | 9 | #import "UIView+MemoryLeak.h" 10 | #import "NSObject+MemoryLeak.h" 11 | 12 | #if _INTERNAL_MLF_ENABLED 13 | 14 | @implementation UIView (MemoryLeak) 15 | 16 | - (BOOL)willDealloc { 17 | if (![super willDealloc]) { 18 | return NO; 19 | } 20 | 21 | [self willReleaseChildren:self.subviews]; 22 | 23 | return YES; 24 | } 25 | 26 | @end 27 | 28 | #endif 29 | -------------------------------------------------------------------------------- /PTBPerformanceCenter/PTBPerformanceCenter/LeakFinder/UIViewController+MemoryLeak.h: -------------------------------------------------------------------------------- 1 | // 2 | // UIViewController+MemoryLeak.h 3 | // MLeaksFinder 4 | // 5 | // Created by zeposhe on 12/12/15. 6 | // Copyright © 2015 zeposhe. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "MLeaksFinder.h" 11 | 12 | #if _INTERNAL_MLF_ENABLED 13 | 14 | @interface UIViewController (MemoryLeak) 15 | 16 | @end 17 | 18 | #endif 19 | -------------------------------------------------------------------------------- /PTBPerformanceCenter/PTBPerformanceCenter/LeakFinder/UIViewController+MemoryLeak.m: -------------------------------------------------------------------------------- 1 | // 2 | // UIViewController+MemoryLeak.m 3 | // MLeaksFinder 4 | // 5 | // Created by zeposhe on 12/12/15. 6 | // Copyright © 2015 zeposhe. All rights reserved. 7 | // 8 | 9 | #import "UIViewController+MemoryLeak.h" 10 | #import "NSObject+MemoryLeak.h" 11 | #import 12 | 13 | #if _INTERNAL_MLF_ENABLED 14 | 15 | const void *const kHasBeenPoppedKey = &kHasBeenPoppedKey; 16 | 17 | @implementation UIViewController (MemoryLeak) 18 | 19 | + (void)load { 20 | static dispatch_once_t onceToken; 21 | dispatch_once(&onceToken, ^{ 22 | [self swizzleSEL:@selector(viewDidDisappear:) withSEL:@selector(swizzled_viewDidDisappear:)]; 23 | [self swizzleSEL:@selector(viewWillAppear:) withSEL:@selector(swizzled_viewWillAppear:)]; 24 | [self swizzleSEL:@selector(dismissViewControllerAnimated:completion:) withSEL:@selector(swizzled_dismissViewControllerAnimated:completion:)]; 25 | }); 26 | } 27 | 28 | - (void)swizzled_viewDidDisappear:(BOOL)animated { 29 | [self swizzled_viewDidDisappear:animated]; 30 | 31 | if ([objc_getAssociatedObject(self, kHasBeenPoppedKey) boolValue]) { 32 | [self willDealloc]; 33 | } 34 | } 35 | 36 | - (void)swizzled_viewWillAppear:(BOOL)animated { 37 | [self swizzled_viewWillAppear:animated]; 38 | 39 | objc_setAssociatedObject(self, kHasBeenPoppedKey, @(NO), OBJC_ASSOCIATION_RETAIN); 40 | } 41 | 42 | - (void)swizzled_dismissViewControllerAnimated:(BOOL)flag completion:(void (^)(void))completion { 43 | [self swizzled_dismissViewControllerAnimated:flag completion:completion]; 44 | 45 | UIViewController *dismissedViewController = self.presentedViewController; 46 | if (!dismissedViewController && self.presentingViewController) { 47 | dismissedViewController = self; 48 | } 49 | 50 | if (!dismissedViewController) return; 51 | 52 | [dismissedViewController willDealloc]; 53 | } 54 | 55 | - (BOOL)willDealloc { 56 | if (![super willDealloc]) { 57 | return NO; 58 | } 59 | 60 | [self willReleaseChildren:self.childViewControllers]; 61 | [self willReleaseChild:self.presentedViewController]; 62 | 63 | if (self.isViewLoaded) { 64 | [self willReleaseChild:self.view]; 65 | } 66 | 67 | return YES; 68 | } 69 | 70 | @end 71 | 72 | #endif 73 | -------------------------------------------------------------------------------- /PTBPerformanceCenter/PTBPerformanceCenter/Monitor/CPU/PTBCPUMonitor.h: -------------------------------------------------------------------------------- 1 | // 2 | // PTBCPUMonitor.h 3 | // PTBPerformanceCenter 4 | // 5 | // Created by PerTerbin on 2018/6/3. 6 | // Copyright © 2018年 PerTerbin. All rights reserved. 7 | // 8 | 9 | #import "PTBBasePerformanceMonitor.h" 10 | 11 | @interface PTBCPUMonitor : PTBBasePerformanceMonitor 12 | 13 | + (instancetype)sharedInstance; 14 | 15 | @end 16 | -------------------------------------------------------------------------------- /PTBPerformanceCenter/PTBPerformanceCenter/Monitor/CPU/PTBCPUMonitor.m: -------------------------------------------------------------------------------- 1 | // 2 | // PTBCPUMonitor.m 3 | // PTBPerformanceCenter 4 | // 5 | // Created by PerTerbin on 2018/6/3. 6 | // Copyright © 2018年 PerTerbin. All rights reserved. 7 | // 8 | 9 | #import "PTBCPUMonitor.h" 10 | #import "mach/mach.h" 11 | 12 | @interface PTBCPUMonitor() 13 | 14 | @property (nonatomic, strong) NSTimer *timer; 15 | 16 | @end 17 | 18 | @implementation PTBCPUMonitor 19 | 20 | + (instancetype)sharedInstance { 21 | static PTBCPUMonitor *monitor; 22 | static dispatch_once_t onceToken; 23 | dispatch_once(&onceToken, ^{ 24 | monitor = [[PTBCPUMonitor alloc] init]; 25 | }); 26 | 27 | return monitor; 28 | } 29 | 30 | - (void)startMonitoringWithNoticeBlock:(void(^)(CGFloat value))noticeBlock { 31 | self.noticeBlock = noticeBlock; 32 | 33 | _timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(noticeCPUValue) userInfo:nil repeats:YES]; 34 | } 35 | 36 | - (void)stopMonitoring { 37 | [_timer invalidate]; 38 | _timer = nil; 39 | } 40 | 41 | - (void)noticeCPUValue { 42 | if (self.noticeBlock) { 43 | self.noticeBlock([self usedCpu]); 44 | } 45 | } 46 | 47 | - (CGFloat)usedCpu { 48 | kern_return_t kr = { 0 }; 49 | task_info_data_t tinfo = { 0 }; 50 | mach_msg_type_number_t task_info_count = TASK_INFO_MAX; 51 | 52 | kr = task_info(mach_task_self(), TASK_BASIC_INFO, (task_info_t)tinfo, &task_info_count); 53 | if (kr != KERN_SUCCESS) { 54 | return 0.0f; 55 | } 56 | 57 | task_basic_info_t basic_info = { 0 }; 58 | thread_array_t thread_list = { 0 }; 59 | mach_msg_type_number_t thread_count = { 0 }; 60 | 61 | thread_info_data_t thinfo = { 0 }; 62 | thread_basic_info_t basic_info_th = { 0 }; 63 | 64 | basic_info = (task_basic_info_t)tinfo; 65 | 66 | // get threads in the task 67 | kr = task_threads(mach_task_self(), &thread_list, &thread_count); 68 | if (kr != KERN_SUCCESS) { 69 | return 0.0f; 70 | } 71 | 72 | long tot_sec = 0; 73 | long tot_usec = 0; 74 | float tot_cpu = 0; 75 | 76 | for (int i = 0; i < thread_count; i++) { 77 | mach_msg_type_number_t thread_info_count = THREAD_INFO_MAX; 78 | 79 | kr = thread_info(thread_list[i], THREAD_BASIC_INFO, (thread_info_t)thinfo, &thread_info_count); 80 | if (kr != KERN_SUCCESS) { 81 | return 0.0f; 82 | } 83 | 84 | basic_info_th = (thread_basic_info_t)thinfo; 85 | if ((basic_info_th->flags & TH_FLAGS_IDLE) == 0) { 86 | tot_sec = tot_sec + basic_info_th->user_time.seconds + basic_info_th->system_time.seconds; 87 | tot_usec = tot_usec + basic_info_th->system_time.microseconds + basic_info_th->system_time.microseconds; 88 | tot_cpu = tot_cpu + basic_info_th->cpu_usage / (float)TH_USAGE_SCALE; 89 | } 90 | } 91 | 92 | kr = vm_deallocate( mach_task_self(), (vm_offset_t)thread_list, thread_count * sizeof(thread_t) ); 93 | if (kr != KERN_SUCCESS) { 94 | return 0.0f; 95 | } 96 | 97 | return (CGFloat)tot_cpu * 100; 98 | } 99 | 100 | @end 101 | -------------------------------------------------------------------------------- /PTBPerformanceCenter/PTBPerformanceCenter/Monitor/FPS/PTBFPSMonitor.h: -------------------------------------------------------------------------------- 1 | // 2 | // PTBFPSMonitor.h 3 | // PTBPerformanceCenter 4 | // 5 | // Created by PerTerbin on 2018/6/3. 6 | // Copyright © 2018年 PerTerbin. All rights reserved. 7 | // 8 | 9 | #import "PTBBasePerformanceMonitor.h" 10 | 11 | @interface PTBFPSMonitor : PTBBasePerformanceMonitor 12 | 13 | + (instancetype)sharedInstance; 14 | 15 | @end 16 | -------------------------------------------------------------------------------- /PTBPerformanceCenter/PTBPerformanceCenter/Monitor/FPS/PTBFPSMonitor.m: -------------------------------------------------------------------------------- 1 | // 2 | // PTBFPSMonitor.m 3 | // PTBPerformanceCenter 4 | // 5 | // Created by PerTerbin on 2018/6/3. 6 | // Copyright © 2018年 PerTerbin. All rights reserved. 7 | // 8 | 9 | #import "PTBFPSMonitor.h" 10 | 11 | @interface PTBFPSMonitor () 12 | 13 | @property (nonatomic, strong) CADisplayLink *displayLink; 14 | @property (nonatomic, strong) NSMutableArray *timestampArray; 15 | 16 | @end 17 | 18 | @implementation PTBFPSMonitor 19 | 20 | + (instancetype)sharedInstance { 21 | static PTBFPSMonitor *monitor; 22 | static dispatch_once_t onceToken; 23 | dispatch_once(&onceToken, ^{ 24 | monitor = [[PTBFPSMonitor alloc] init]; 25 | }); 26 | 27 | return monitor; 28 | } 29 | 30 | - (void)startMonitoringWithNoticeBlock:(void(^)(CGFloat value))noticeBlock { 31 | self.noticeBlock = noticeBlock; 32 | 33 | _displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(envokeDisplayLink:)]; 34 | _displayLink.paused = NO; 35 | [_displayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes]; 36 | } 37 | 38 | - (void)stopMonitoring { 39 | _displayLink.paused = YES; 40 | _displayLink = nil; 41 | } 42 | 43 | - (void)envokeDisplayLink:(CADisplayLink *)displayLink { 44 | if (!_timestampArray) { 45 | _timestampArray = [NSMutableArray arrayWithCapacity:60]; 46 | } 47 | 48 | if (_timestampArray.count == 60) { 49 | [_timestampArray removeObject:_timestampArray.firstObject]; 50 | } 51 | 52 | [_timestampArray addObject:@(displayLink.timestamp)]; 53 | 54 | __block NSInteger fps = 0; 55 | [_timestampArray enumerateObjectsWithOptions:NSEnumerationReverse usingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { 56 | if (displayLink.timestamp - [obj doubleValue] < 1) { 57 | fps++; 58 | } else { 59 | *stop = YES; 60 | } 61 | }]; 62 | 63 | if (self.noticeBlock) { 64 | self.noticeBlock((CGFloat)fps); 65 | } 66 | } 67 | 68 | @end 69 | 70 | -------------------------------------------------------------------------------- /PTBPerformanceCenter/PTBPerformanceCenter/Monitor/Memory/PTBMemoryMonitor.h: -------------------------------------------------------------------------------- 1 | // 2 | // PTBMemoryMonitor.h 3 | // PTBPerformanceCenter 4 | // 5 | // Created by PerTerbin on 2018/6/3. 6 | // Copyright © 2018年 PerTerbin. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "PTBBasePerformanceMonitor.h" 11 | 12 | @interface PTBMemoryMonitor : PTBBasePerformanceMonitor 13 | 14 | + (instancetype)sharedInstance; 15 | 16 | /// 当前设备已使用的内存(MB) 17 | + (CGFloat)deviceUsedMemory; 18 | /// 当前设备可用内存(MB) 19 | + (CGFloat)deviceAvailableMemory; 20 | 21 | @end 22 | -------------------------------------------------------------------------------- /PTBPerformanceCenter/PTBPerformanceCenter/Monitor/Memory/PTBMemoryMonitor.m: -------------------------------------------------------------------------------- 1 | // 2 | // PTBMemoryMonitor.m 3 | // PTBPerformanceCenter 4 | // 5 | // Created by PerTerbin on 2018/6/3. 6 | // Copyright © 2018年 PerTerbin. All rights reserved. 7 | // 8 | 9 | #import "PTBMemoryMonitor.h" 10 | #import 11 | #import 12 | 13 | @interface PTBMemoryMonitor() 14 | 15 | @property (nonatomic, strong) NSTimer *timer; 16 | 17 | @end 18 | 19 | @implementation PTBMemoryMonitor 20 | 21 | + (instancetype)sharedInstance { 22 | static PTBMemoryMonitor *monitor; 23 | static dispatch_once_t onceToken; 24 | dispatch_once(&onceToken, ^{ 25 | monitor = [[PTBMemoryMonitor alloc] init]; 26 | }); 27 | 28 | return monitor; 29 | } 30 | 31 | - (void)startMonitoringWithNoticeBlock:(void(^)(CGFloat value))noticeBlock { 32 | self.noticeBlock = noticeBlock; 33 | 34 | _timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(noticeMemoryValue) userInfo:nil repeats:YES]; 35 | } 36 | 37 | - (void)stopMonitoring { 38 | [_timer invalidate]; 39 | _timer = nil; 40 | } 41 | 42 | - (void)noticeMemoryValue { 43 | if (self.noticeBlock) { 44 | self.noticeBlock([self usedMemory]); 45 | } 46 | } 47 | 48 | - (CGFloat)usedMemory { 49 | task_vm_info_data_t vmInfo; 50 | mach_msg_type_number_t count = TASK_VM_INFO_COUNT; 51 | kern_return_t kernReturn = task_info(mach_task_self(), TASK_VM_INFO, (task_info_t)&vmInfo,&count); 52 | 53 | if (kernReturn != KERN_SUCCESS) { 54 | return NSNotFound; 55 | } 56 | 57 | return (CGFloat)(vmInfo.phys_footprint / 1024.0 / 1024.0); 58 | } 59 | 60 | + (CGFloat)deviceUsedMemory { 61 | size_t length = 0; 62 | int mib[6] = {0}; 63 | 64 | int pagesize = 0; 65 | mib[0] = CTL_HW; 66 | mib[1] = HW_PAGESIZE; 67 | length = sizeof(pagesize); 68 | if (sysctl(mib, 2, &pagesize, &length, NULL, 0) < 0) { 69 | return 0; 70 | } 71 | 72 | mach_msg_type_number_t count = HOST_VM_INFO_COUNT; 73 | 74 | vm_statistics_data_t vmstat; 75 | 76 | if (host_statistics(mach_host_self(), HOST_VM_INFO, (host_info_t)&vmstat, &count) != KERN_SUCCESS) { 77 | return 0; 78 | } 79 | 80 | int wireMem = vmstat.wire_count * pagesize; 81 | int activeMem = vmstat.active_count * pagesize; 82 | 83 | return (CGFloat)(wireMem + activeMem) / 1024.0 / 1024.0; 84 | } 85 | 86 | + (CGFloat)deviceAvailableMemory { 87 | vm_statistics64_data_t vmStats; 88 | mach_msg_type_number_t infoCount = HOST_VM_INFO_COUNT; 89 | kern_return_t kernReturn = host_statistics(mach_host_self(), 90 | HOST_VM_INFO, 91 | (host_info_t)&vmStats, 92 | &infoCount); 93 | 94 | if (kernReturn != KERN_SUCCESS) { 95 | return NSNotFound; 96 | } 97 | 98 | return (CGFloat)(vm_page_size * (vmStats.free_count + vmStats.inactive_count) / 1024.0 / 1024.0); 99 | } 100 | 101 | @end 102 | -------------------------------------------------------------------------------- /PTBPerformanceCenter/PTBPerformanceCenter/Monitor/PTBBasePerformanceMonitor.h: -------------------------------------------------------------------------------- 1 | // 2 | // PTBBasePerformanceMonitor.h 3 | // PTBPerformanceCenter 4 | // 5 | // Created by PerTerbin on 2018/6/3. 6 | // Copyright © 2018年 PerTerbin. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface PTBBasePerformanceMonitor : NSObject 12 | 13 | @property (nonatomic, copy) void(^noticeBlock)(CGFloat value); 14 | 15 | - (void)startMonitoringWithNoticeBlock:(void(^)(CGFloat value))noticeBlock; 16 | - (void)stopMonitoring; 17 | 18 | @end 19 | -------------------------------------------------------------------------------- /PTBPerformanceCenter/PTBPerformanceCenter/Monitor/PTBBasePerformanceMonitor.m: -------------------------------------------------------------------------------- 1 | // 2 | // PTBBasePerformanceMonitor.m 3 | // PTBPerformanceCenter 4 | // 5 | // Created by PerTerbin on 2018/6/3. 6 | // Copyright © 2018年 PerTerbin. All rights reserved. 7 | // 8 | 9 | #import "PTBBasePerformanceMonitor.h" 10 | 11 | @implementation PTBBasePerformanceMonitor 12 | 13 | - (void)startMonitoringWithNoticeBlock:(void(^)(CGFloat value))noticeBlock { 14 | // do something in subclass 15 | } 16 | 17 | - (void)stopMonitoring { 18 | // do something in subclass 19 | } 20 | 21 | @end 22 | -------------------------------------------------------------------------------- /PTBPerformanceCenter/PTBPerformanceCenter/PTBPerformanceCenter.h: -------------------------------------------------------------------------------- 1 | // 2 | // PTBPerformanceCenter.h 3 | // PTBPerformanceCenter 4 | // 5 | // Created by PerTerbin on 2018/6/3. 6 | // Copyright © 2018年 PerTerbin. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "PTBPerformanceViewController.h" 11 | 12 | @interface PTBPerformanceCenter : NSObject 13 | 14 | @property (nonatomic, strong, readonly) PTBPerformanceViewController *viewController; 15 | 16 | + (instancetype)defaultCenter; 17 | 18 | - (void)enable; 19 | - (void)disable; 20 | 21 | @end 22 | -------------------------------------------------------------------------------- /PTBPerformanceCenter/PTBPerformanceCenter/PTBPerformanceCenter.m: -------------------------------------------------------------------------------- 1 | // 2 | // PTBPerformanceCenter.m 3 | // PTBPerformanceCenter 4 | // 5 | // Created by PerTerbin on 2018/6/3. 6 | // Copyright © 2018年 PerTerbin. All rights reserved. 7 | // 8 | 9 | #import "PTBPerformanceCenter.h" 10 | #import "PTBPerformanceWindow.h" 11 | #import "PTBPerformanceViewController.h" 12 | #import "PTBFPSMonitor.h" 13 | #import "PTBCPUMonitor.h" 14 | #import "PTBMemoryMonitor.h" 15 | 16 | @interface PTBPerformanceCenter () 17 | 18 | @property (nonatomic, strong) PTBPerformanceWindow *window; 19 | @property (nonatomic, strong) PTBPerformanceViewController *viewController; 20 | 21 | @end 22 | 23 | @implementation PTBPerformanceCenter 24 | 25 | + (instancetype)defaultCenter { 26 | static PTBPerformanceCenter *center; 27 | static dispatch_once_t onceToken; 28 | dispatch_once(&onceToken, ^{ 29 | center = [[PTBPerformanceCenter alloc] init]; 30 | }); 31 | 32 | return center; 33 | } 34 | 35 | - (instancetype)init { 36 | self = [super init]; 37 | if (self) { 38 | _viewController = [[PTBPerformanceViewController alloc] init]; 39 | 40 | _window = [[PTBPerformanceWindow alloc] initWithFrame:[UIScreen mainScreen].bounds]; 41 | _window.rootViewController = _viewController; 42 | _window.windowLevel = UIWindowLevelAlert + 1000; 43 | _window.delegate = _viewController; 44 | _window.hidden = YES; 45 | } 46 | 47 | return self; 48 | } 49 | 50 | - (void)enable { 51 | if (_window.hidden == NO) { 52 | return; 53 | } 54 | 55 | _window.hidden = NO; 56 | 57 | __weak typeof(self) weakSelf = self; 58 | // FPS 59 | [[PTBFPSMonitor sharedInstance] startMonitoringWithNoticeBlock:^(CGFloat value) { 60 | [weakSelf.viewController setFPSValue:value]; 61 | }]; 62 | 63 | // CPU 64 | [[PTBCPUMonitor sharedInstance] startMonitoringWithNoticeBlock:^(CGFloat value) { 65 | [weakSelf.viewController setCPUValue:value]; 66 | }]; 67 | 68 | // Memory 69 | [[PTBMemoryMonitor sharedInstance] startMonitoringWithNoticeBlock:^(CGFloat value) { 70 | [weakSelf.viewController setMemoryValue:value]; 71 | }]; 72 | } 73 | 74 | - (void)disable { 75 | if (_window.hidden == YES) { 76 | return; 77 | } 78 | 79 | _window.hidden = YES; 80 | 81 | [[PTBFPSMonitor sharedInstance] stopMonitoring]; 82 | [[PTBCPUMonitor sharedInstance] stopMonitoring]; 83 | [[PTBMemoryMonitor sharedInstance] stopMonitoring]; 84 | } 85 | 86 | @end 87 | -------------------------------------------------------------------------------- /PTBPerformanceCenter/PTBPerformanceCenter/PTBPerformanceViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // PTBPerformanceViewController.h 3 | // PTBPerformanceCenter 4 | // 5 | // Created by PerTerbin on 2018/6/3. 6 | // Copyright © 2018年 PerTerbin. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "PTBPerformanceWindow.h" 11 | 12 | @interface PTBPerformanceViewController : UIViewController 13 | 14 | - (void)setFPSValue:(CGFloat)fpsValue; 15 | - (void)setCPUValue:(CGFloat)cpuValue; 16 | - (void)setMemoryValue:(CGFloat)memoryValue; 17 | - (void)findMenoryLeakWithViewStack:(NSArray *)viewStack retainCycle:(NSArray *)retainCycle; 18 | 19 | @end 20 | -------------------------------------------------------------------------------- /PTBPerformanceCenter/PTBPerformanceCenter/PTBPerformanceViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // PTBPerformanceViewController.m 3 | // PTBPerformanceCenter 4 | // 5 | // Created by PerTerbin on 2018/6/3. 6 | // Copyright © 2018年 PerTerbin. All rights reserved. 7 | // 8 | 9 | #import "PTBPerformanceViewController.h" 10 | #import "PTBPerformanceCenter.h" 11 | #import "PTBMemoryMonitor.h" 12 | 13 | #define kGoodColor [UIColor colorWithRed:135.0 / 255.0 green:183.0 / 255.0 blue:74.0 / 255.0 alpha:1] 14 | #define kAverageColor [UIColor colorWithRed:214.0 / 255.0 green:161.0 / 255.0 blue:69.0 / 255.0 alpha:1] 15 | #define kPoorColor [UIColor colorWithRed:187.0 / 255.0 green:63.0 / 255.0 blue:55.0 / 255.0 alpha:1] 16 | #define kDefaultColor [UIColor colorWithRed:54.0 / 255.0 green:57.0 / 255.0 blue:64.0 / 255.0 alpha:1] 17 | #define kCpuIndicatorColor [UIColor colorWithRed:71.0 / 255.0 green:74.0 / 255.0 blue:81.0 / 255.0 alpha:1] 18 | #define kMonitorValueColor [UIColor colorWithRed:188.0 / 255.0 green:188.0 / 255.0 blue:188.0 / 255.0 alpha:1] 19 | #define kMonitorTitleColor [UIColor colorWithRed:20.0 / 255.0 green:20.0 / 255.0 blue:20.0 / 255.0 alpha:1] 20 | #define kMonitorBackgroundColor [UIColor colorWithRed:39.0 / 255.0 green:42.0 / 255.0 blue:49.0 / 255.0 alpha:1] 21 | 22 | #define isiPhoneX (CGSizeEqualToSize(CGSizeMake(375.f, 812.f), [UIScreen mainScreen].bounds.size) || CGSizeEqualToSize(CGSizeMake(812.f, 375.f), [UIScreen mainScreen].bounds.size)) 23 | 24 | 25 | static CGFloat const kPerformanceViewWidth = 150; 26 | static CGFloat const kPerformanceViewHeight = 90; 27 | static CGFloat const kStackViewHeight = 180; 28 | 29 | @interface PTBPerformanceViewController () 30 | 31 | @property (nonatomic, strong) UIView *backView; 32 | @property (nonatomic, strong) UIView *monitorBackView; 33 | @property (nonatomic, strong) CAShapeLayer *cpuArcLayer; 34 | @property (nonatomic, strong) CAShapeLayer *fpsArcLayer; 35 | @property (nonatomic, strong) CAShapeLayer *memoryArcLayer; 36 | @property (nonatomic, strong) CATextLayer *cpuTextLayer; 37 | @property (nonatomic, strong) CATextLayer *fpsTextLayer; 38 | @property (nonatomic, strong) CATextLayer *memoryTextLayer; 39 | @property (nonatomic, strong) CAShapeLayer *cpuIndicatorLayer; 40 | @property (nonatomic, strong) UITextView *leakStackView; 41 | 42 | @property (nonatomic, assign) CGFloat totalMemory; 43 | 44 | @end 45 | 46 | @implementation PTBPerformanceViewController 47 | 48 | - (void)viewDidLoad { 49 | [super viewDidLoad]; 50 | // Do any additional setup after loading the view. 51 | 52 | [self setupUI]; 53 | } 54 | 55 | - (void)didReceiveMemoryWarning { 56 | [super didReceiveMemoryWarning]; 57 | // Dispose of any resources that can be recreated. 58 | } 59 | 60 | - (void)setupUI { 61 | _backView = [[UIView alloc] initWithFrame:CGRectMake(0, isiPhoneX ? 30 : 0, kPerformanceViewWidth, kPerformanceViewHeight)]; 62 | _backView.backgroundColor = kMonitorBackgroundColor; 63 | _backView.layer.cornerRadius = 25; 64 | [_backView addGestureRecognizer:[[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(backViewTapped:)]]; 65 | [_backView addGestureRecognizer:[[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(backViewLongPressed:)]]; 66 | [_backView addGestureRecognizer:[[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(backViewPaned:)]]; 67 | [self.view addSubview:_backView]; 68 | 69 | _monitorBackView = [[UIView alloc] initWithFrame:_backView.bounds]; 70 | _monitorBackView.backgroundColor = [UIColor clearColor]; 71 | [_backView addSubview:_monitorBackView]; 72 | 73 | // FPS 74 | CATextLayer *fpsTitleLayer = [self createTitleTextLayerWithSuperLayer:_monitorBackView.layer]; 75 | fpsTitleLayer.frame = CGRectMake((_backView.bounds.size.width - 80) / 2, kPerformanceViewHeight * 0.08, 80, 15); 76 | fpsTitleLayer.string = @"FPS"; 77 | 78 | CGFloat startAngle = 6.0 / 5.0 * M_PI; 79 | CAShapeLayer *fpsBackLayer = [CAShapeLayer layer]; 80 | fpsBackLayer.lineWidth = 18; 81 | fpsBackLayer.strokeColor = kDefaultColor.CGColor; 82 | fpsBackLayer.fillColor = [UIColor clearColor].CGColor; 83 | _fpsArcLayer = [CAShapeLayer layer]; 84 | _fpsArcLayer.lineWidth = fpsBackLayer.lineWidth; 85 | _fpsArcLayer.strokeColor = kPoorColor.CGColor; 86 | _fpsArcLayer.fillColor = [UIColor clearColor].CGColor; 87 | 88 | CGPoint fpsArcCenter = CGPointMake(kPerformanceViewWidth * 0.5, kPerformanceViewHeight * 0.65); 89 | CGFloat fpsArcRadius = kPerformanceViewHeight * 0.45; 90 | CGFloat endAngle = startAngle + 3.0 / 5.0 * M_PI; 91 | UIBezierPath *fpsFullPath = [UIBezierPath bezierPathWithArcCenter:fpsArcCenter radius:fpsArcRadius - _fpsArcLayer.lineWidth / 2 startAngle:startAngle endAngle:endAngle clockwise:true]; 92 | fpsBackLayer.path = fpsFullPath.CGPath; 93 | _fpsArcLayer.path = fpsFullPath.CGPath; 94 | [_monitorBackView.layer addSublayer:fpsBackLayer]; 95 | [_monitorBackView.layer addSublayer:_fpsArcLayer]; 96 | 97 | CGFloat indicatorWidth = 8; 98 | CGFloat indicatorHeight = fpsArcCenter.y - kPerformanceViewHeight * 0.15; 99 | _cpuIndicatorLayer = [CAShapeLayer layer]; 100 | _cpuIndicatorLayer.frame = CGRectMake(fpsArcCenter.x, kPerformanceViewHeight * 0.15 + indicatorHeight / 2, indicatorWidth, indicatorHeight); 101 | _cpuIndicatorLayer.backgroundColor = [UIColor clearColor].CGColor; 102 | _cpuIndicatorLayer.anchorPoint = CGPointMake(1.0, 1.0); 103 | _cpuIndicatorLayer.transform = CATransform3DRotate(_cpuIndicatorLayer.transform, M_PI * -0.3 + indicatorWidth / 2 / fpsArcRadius, 0, 0, 1); 104 | _cpuIndicatorLayer.fillColor = [UIColor colorWithRed:71.0 / 255.0 green:74.0 / 255.0 blue:81.0 / 255.0 alpha:1].CGColor; 105 | 106 | UIBezierPath *fpsIndicatorPath = [UIBezierPath bezierPath]; 107 | [fpsIndicatorPath moveToPoint:CGPointMake(indicatorWidth / 2, 0)]; 108 | [fpsIndicatorPath addQuadCurveToPoint:CGPointMake(indicatorWidth / 2, indicatorHeight) controlPoint:CGPointMake(0, indicatorHeight + 4)]; 109 | [fpsIndicatorPath addQuadCurveToPoint:CGPointMake(indicatorWidth / 2, 0) controlPoint:CGPointMake(indicatorWidth, indicatorHeight + 4)]; 110 | _cpuIndicatorLayer.path = fpsIndicatorPath.CGPath; 111 | 112 | [_monitorBackView.layer addSublayer:_cpuIndicatorLayer]; 113 | 114 | _fpsTextLayer = [self createValueTextLayerWithSuperLayer:_monitorBackView.layer]; 115 | _fpsTextLayer.frame = CGRectMake((_backView.bounds.size.width - 80) / 2, kPerformanceViewHeight * 0.25, 80, 15); 116 | 117 | // CPU 118 | CATextLayer *cpuTitleLayer = [self createTitleTextLayerWithSuperLayer:_monitorBackView.layer]; 119 | cpuTitleLayer.frame = CGRectMake(0, kPerformanceViewHeight * 0.35, kPerformanceViewHeight * 0.55, 15); 120 | cpuTitleLayer.string = @"CPU"; 121 | 122 | CAShapeLayer *cpuBackLayer = [CAShapeLayer layer]; 123 | cpuBackLayer.lineWidth = 2; 124 | cpuBackLayer.strokeColor = kDefaultColor.CGColor; 125 | cpuBackLayer.fillColor = [UIColor clearColor].CGColor; 126 | _cpuArcLayer = [CAShapeLayer layer]; 127 | _cpuArcLayer.lineWidth = cpuBackLayer.lineWidth; 128 | _cpuArcLayer.strokeColor = kPoorColor.CGColor; 129 | _cpuArcLayer.fillColor = [UIColor clearColor].CGColor; 130 | _cpuArcLayer.lineCap = kCALineJoinRound; 131 | 132 | UIBezierPath *cpuFullPath = [UIBezierPath bezierPathWithArcCenter:CGPointMake(kPerformanceViewHeight * 0.55 / 2, kPerformanceViewHeight * 0.72) radius:kPerformanceViewHeight * 0.45 / 2 - _cpuArcLayer.lineWidth / 2 startAngle:3.0 / 2.0 * M_PI endAngle:3.0 / 2.0 * M_PI + 2.0 * M_PI clockwise:true]; 133 | cpuBackLayer.path = cpuFullPath.CGPath; 134 | _cpuArcLayer.path = cpuFullPath.CGPath; 135 | [_monitorBackView.layer addSublayer:cpuBackLayer]; 136 | [_monitorBackView.layer addSublayer:_cpuArcLayer]; 137 | 138 | _cpuTextLayer = [self createValueTextLayerWithSuperLayer:_monitorBackView.layer]; 139 | _cpuTextLayer.frame = CGRectMake(0, kPerformanceViewHeight * 0.65, kPerformanceViewHeight * 0.55, 10); 140 | 141 | // Memory 142 | CATextLayer *memoryTitleLayer = [self createTitleTextLayerWithSuperLayer:_monitorBackView.layer]; 143 | memoryTitleLayer.frame = CGRectMake(kPerformanceViewWidth - kPerformanceViewHeight * 0.55, kPerformanceViewHeight * 0.35, kPerformanceViewHeight * 0.55, 15); 144 | memoryTitleLayer.string = @"Memory"; 145 | 146 | CAShapeLayer *memoryBackLayer = [CAShapeLayer layer]; 147 | memoryBackLayer.lineWidth = 2; 148 | memoryBackLayer.strokeColor = kDefaultColor.CGColor; 149 | memoryBackLayer.fillColor = [UIColor clearColor].CGColor; 150 | _memoryArcLayer = [CAShapeLayer layer]; 151 | _memoryArcLayer.lineWidth = memoryBackLayer.lineWidth; 152 | _memoryArcLayer.strokeColor = kPoorColor.CGColor; 153 | _memoryArcLayer.fillColor = [UIColor clearColor].CGColor; 154 | _memoryArcLayer.lineCap = kCALineJoinRound; 155 | 156 | UIBezierPath *memoryFullPath = [UIBezierPath bezierPathWithArcCenter:CGPointMake(kPerformanceViewWidth - kPerformanceViewHeight * 0.55 / 2, kPerformanceViewHeight * 0.72) radius:kPerformanceViewHeight * 0.45 / 2 - _cpuArcLayer.lineWidth / 2 startAngle:3.0 / 2.0 * M_PI endAngle:3.0 / 2.0 * M_PI + 2.0 * M_PI clockwise:true]; 157 | memoryBackLayer.path = memoryFullPath.CGPath; 158 | _memoryArcLayer.path = memoryFullPath.CGPath; 159 | [_monitorBackView.layer addSublayer:memoryBackLayer]; 160 | [_monitorBackView.layer addSublayer:_memoryArcLayer]; 161 | 162 | _memoryTextLayer = [self createValueTextLayerWithSuperLayer:_monitorBackView.layer]; 163 | _memoryTextLayer.fontSize = 7; 164 | _memoryTextLayer.frame = CGRectMake(kPerformanceViewWidth - kPerformanceViewHeight * 0.55, kPerformanceViewHeight * 0.59, kPerformanceViewHeight * 0.55, 25); 165 | 166 | // Leak 167 | _leakStackView = [[UITextView alloc] initWithFrame:_backView.bounds]; 168 | _leakStackView.backgroundColor = [UIColor redColor]; 169 | _leakStackView.layer.cornerRadius = _backView.layer.cornerRadius; 170 | _leakStackView.textColor = [UIColor whiteColor]; 171 | _leakStackView.textAlignment = NSTextAlignmentCenter; 172 | _leakStackView.text = @"Possibly Memory Leak."; 173 | _leakStackView.hidden = YES; 174 | _leakStackView.editable = NO; 175 | _leakStackView.scrollEnabled = NO; 176 | _leakStackView.selectable = NO; 177 | [_leakStackView addGestureRecognizer:[[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(leakStackViewTapped:)]]; 178 | [_leakStackView addGestureRecognizer:[[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(leakStackViewLongPressed:)]]; 179 | [_backView addSubview:_leakStackView]; 180 | } 181 | 182 | - (CATextLayer *)createTitleTextLayerWithSuperLayer:(CALayer *)superLayer { 183 | CATextLayer *titleLayer = [CATextLayer layer]; 184 | titleLayer.foregroundColor = kMonitorValueColor.CGColor; 185 | titleLayer.fontSize = 7; 186 | titleLayer.alignmentMode = kCAAlignmentCenter; 187 | titleLayer.contentsScale = [UIScreen mainScreen].scale; 188 | [superLayer addSublayer:titleLayer]; 189 | 190 | return titleLayer; 191 | } 192 | 193 | - (CATextLayer *)createValueTextLayerWithSuperLayer:(CALayer *)superLayer { 194 | CATextLayer *valueLayer = [CATextLayer layer]; 195 | valueLayer.foregroundColor = kMonitorValueColor.CGColor; 196 | valueLayer.fontSize = 8; 197 | valueLayer.alignmentMode = kCAAlignmentCenter; 198 | valueLayer.contentsScale = [UIScreen mainScreen].scale; 199 | [superLayer addSublayer:valueLayer]; 200 | 201 | return valueLayer; 202 | } 203 | 204 | - (void)showFindMenoryLeakAnimation { 205 | _monitorBackView.hidden = YES; 206 | _leakStackView.hidden = NO; 207 | _leakStackView.frame = _backView.bounds; 208 | 209 | CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"opacity"]; 210 | animation.fromValue = @(1.0); 211 | animation.toValue = @(0.0); 212 | animation.duration = 0.15; 213 | animation.repeatCount = MAXFLOAT; 214 | animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseIn]; 215 | [_leakStackView.layer addAnimation:animation forKey:@"FindMenoryLeakAnimation"]; 216 | } 217 | 218 | - (void)stopFindMenoryLeakAnimation { 219 | [_leakStackView.layer removeAllAnimations]; 220 | } 221 | 222 | #pragma mark - Public 223 | - (void)setFPSValue:(CGFloat)fpsValue { 224 | CGFloat startAngle = 6.0 / 5.0 * M_PI; 225 | CGFloat endAngle = startAngle + 3.0 / 5.0 * M_PI * fpsValue / 60.0; 226 | if (fpsValue >= 57) { 227 | _fpsArcLayer.strokeColor = kGoodColor.CGColor; 228 | } else if(fpsValue >= 50) { 229 | _fpsArcLayer.strokeColor = kAverageColor.CGColor; 230 | } else { 231 | _fpsArcLayer.strokeColor = kPoorColor.CGColor; 232 | } 233 | 234 | [CATransaction begin]; 235 | [CATransaction setDisableActions:YES]; 236 | _fpsArcLayer.path = [UIBezierPath bezierPathWithArcCenter:CGPointMake(_backView.bounds.size.width / 2, kPerformanceViewHeight * 0.65) radius:kPerformanceViewHeight * 0.45 - _fpsArcLayer.lineWidth / 2 startAngle:startAngle endAngle:endAngle clockwise:true].CGPath; 237 | _cpuIndicatorLayer.transform = CATransform3DMakeRotation(endAngle + M_PI / 2 + 4 / kPerformanceViewHeight * 0.6, 0, 0, 1); 238 | [CATransaction commit]; 239 | _fpsTextLayer.string = [NSString stringWithFormat:@"%.0f", fpsValue]; 240 | } 241 | 242 | - (void)setCPUValue:(CGFloat)cpuValue { 243 | CGFloat startAngle = 3.0 / 2.0 * M_PI; 244 | CGFloat endAngle = startAngle + 2.0 * M_PI * cpuValue / 100.0; 245 | if (cpuValue <= 20) { 246 | _cpuArcLayer.strokeColor = kGoodColor.CGColor; 247 | } else if(cpuValue <= 80) { 248 | _cpuArcLayer.strokeColor = kAverageColor.CGColor; 249 | } else { 250 | _cpuArcLayer.strokeColor = kAverageColor.CGColor; 251 | } 252 | 253 | _cpuTextLayer.string = [NSString stringWithFormat:@"%.2f%%", cpuValue]; 254 | 255 | _cpuArcLayer.path = [UIBezierPath bezierPathWithArcCenter:CGPointMake(kPerformanceViewHeight * 0.55 / 2, kPerformanceViewHeight * 0.72) radius:kPerformanceViewHeight * 0.45 / 2 - _cpuArcLayer.lineWidth / 2 startAngle:startAngle endAngle:endAngle clockwise:true].CGPath; 256 | } 257 | 258 | - (void)setMemoryValue:(CGFloat)memoryValue { 259 | if (_totalMemory == 0) { 260 | _totalMemory = [PTBMemoryMonitor deviceUsedMemory] + [PTBMemoryMonitor deviceAvailableMemory]; 261 | } 262 | 263 | CGFloat startAngle = 3.0 / 2.0 * M_PI; 264 | CGFloat endAngle = startAngle + 2.0 * M_PI * memoryValue / _totalMemory; 265 | if (memoryValue <= 200) { 266 | _memoryArcLayer.strokeColor = kGoodColor.CGColor; 267 | } else if(memoryValue <= 280) { 268 | _memoryArcLayer.strokeColor = kAverageColor.CGColor; 269 | } else { 270 | _memoryArcLayer.strokeColor = kAverageColor.CGColor; 271 | } 272 | 273 | _memoryArcLayer.path = [UIBezierPath bezierPathWithArcCenter:CGPointMake(kPerformanceViewWidth - kPerformanceViewHeight * 0.55 / 2, kPerformanceViewHeight * 0.72) radius:kPerformanceViewHeight * 0.45 / 2 - _cpuArcLayer.lineWidth / 2 startAngle:startAngle endAngle:endAngle clockwise:true].CGPath; 274 | _memoryTextLayer.string = [NSString stringWithFormat:@"%.2f\nMB\n%.2f%%", memoryValue, memoryValue / _totalMemory * 100.0]; 275 | } 276 | 277 | - (void)findMenoryLeakWithViewStack:(NSArray *)viewStack retainCycle:(NSArray *)retainCycle { 278 | [self showFindMenoryLeakAnimation]; 279 | 280 | NSString *title = @"\n\n\nPossibly Memory Leak.\n\n\n\n"; 281 | NSString *viewStackTitle = @"View Stack:"; 282 | NSString *retainCycleTitle = @"\n\nReatin Cycle:"; 283 | 284 | NSMutableAttributedString *attributedString = [[NSMutableAttributedString alloc] initWithString:title attributes:@{NSForegroundColorAttributeName : [UIColor whiteColor]}]; 285 | [attributedString appendAttributedString:[[NSAttributedString alloc] initWithString:viewStackTitle attributes:@{NSForegroundColorAttributeName : [UIColor yellowColor]}]]; 286 | 287 | __block NSString *viewStackString = @""; 288 | [viewStack enumerateObjectsWithOptions:NSEnumerationReverse usingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { 289 | viewStackString = [NSString stringWithFormat:@"%@\n\n%@", viewStackString, obj]; 290 | }]; 291 | [attributedString appendAttributedString:[[NSAttributedString alloc] initWithString:viewStackString attributes:@{NSForegroundColorAttributeName : [UIColor whiteColor]}]]; 292 | 293 | [attributedString appendAttributedString:[[NSAttributedString alloc] initWithString:retainCycleTitle attributes:@{NSForegroundColorAttributeName : [UIColor yellowColor]}]]; 294 | 295 | __block NSString *retainCycleString = @""; 296 | [retainCycle enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { 297 | retainCycleString = [NSString stringWithFormat:@"%@\n\n%@", retainCycleString, obj]; 298 | }]; 299 | [attributedString appendAttributedString:[[NSAttributedString alloc] initWithString:retainCycleString attributes:@{NSForegroundColorAttributeName : [UIColor whiteColor]}]]; 300 | 301 | NSMutableParagraphStyle *style = [[NSMutableParagraphStyle alloc] init]; 302 | style.alignment = NSTextAlignmentCenter; 303 | [attributedString addAttribute:NSParagraphStyleAttributeName value:style range:NSMakeRange(0, attributedString.length)]; 304 | [attributedString addAttribute:NSFontAttributeName value:[UIFont systemFontOfSize:10] range:NSMakeRange(0, attributedString.length)]; 305 | 306 | _leakStackView.attributedText = attributedString; 307 | } 308 | 309 | #pragma mark Gesture 310 | - (void)backViewTapped:(UIGestureRecognizer *)recognizer { 311 | // nothing to do 312 | } 313 | 314 | - (void)backViewLongPressed:(UIGestureRecognizer *)recognizer { 315 | [[PTBPerformanceCenter defaultCenter] disable]; 316 | } 317 | 318 | - (void)leakStackViewTapped:(UIGestureRecognizer *)recognizer { 319 | [self stopFindMenoryLeakAnimation]; 320 | 321 | CGRect stackViewframe = _leakStackView.frame; 322 | CGRect backViewFrame = _backView.frame; 323 | if (stackViewframe.size.height == kPerformanceViewHeight) { 324 | stackViewframe.size.height = kStackViewHeight; 325 | backViewFrame.size.height = kStackViewHeight; 326 | _leakStackView.scrollEnabled = YES; 327 | } else { 328 | stackViewframe.size.height = kPerformanceViewHeight; 329 | backViewFrame.size.height = kPerformanceViewHeight; 330 | _leakStackView.scrollEnabled = NO; 331 | } 332 | 333 | [UIView animateWithDuration:0.2 animations:^{ 334 | _leakStackView.frame = stackViewframe; 335 | _backView.frame = backViewFrame; 336 | }]; 337 | } 338 | 339 | - (void)backViewPaned:(UIPanGestureRecognizer *)recognizer { 340 | CGPoint point = [recognizer translationInView:self.view]; 341 | CGPoint center = CGPointMake(recognizer.view.center.x + point.x, recognizer.view.center.y + point.y); 342 | 343 | if (center.x - recognizer.view.bounds.size.width / 2 < 0) { 344 | center.x = recognizer.view.bounds.size.width / 2; 345 | } 346 | if (center.x + recognizer.view.bounds.size.width / 2 > self.view.bounds.size.width) { 347 | center.x = self.view.bounds.size.width - recognizer.view.bounds.size.width / 2; 348 | } 349 | if (center.y - recognizer.view.bounds.size.height / 2 < 0) { 350 | center.y = recognizer.view.bounds.size.height / 2; 351 | } 352 | if (center.y + recognizer.view.bounds.size.height / 2 > self.view.bounds.size.height) { 353 | center.y = self.view.bounds.size.height - recognizer.view.bounds.size.height / 2; 354 | } 355 | 356 | if (recognizer.state == UIGestureRecognizerStateEnded || recognizer.state == UIGestureRecognizerStateCancelled) { 357 | if (center.y - recognizer.view.bounds.size.height / 2 <= 50) { 358 | center.y = recognizer.view.bounds.size.height / 2; 359 | } else if (self.view.bounds.size.height - (center.y + recognizer.view.bounds.size.height / 2) <= 50) { 360 | center.y = self.view.bounds.size.height - recognizer.view.bounds.size.height / 2; 361 | } else if (center.x >= self.view.bounds.size.width / 2) { 362 | center.x = self.view.bounds.size.width - recognizer.view.bounds.size.width / 2; 363 | } else { 364 | center.x = recognizer.view.bounds.size.width / 2; 365 | } 366 | [UIView animateWithDuration:0.15 animations:^{ 367 | recognizer.view.center = center; 368 | }]; 369 | } else { 370 | recognizer.view.center = center; 371 | [recognizer setTranslation:CGPointMake(0, 0) inView:self.view]; 372 | } 373 | } 374 | 375 | - (void)leakStackViewLongPressed:(UIGestureRecognizer *)recognizer { 376 | _monitorBackView.hidden = NO; 377 | _leakStackView.hidden = YES; 378 | 379 | CGRect stackViewframe = _leakStackView.frame; 380 | CGRect backViewFrame = _backView.frame; 381 | stackViewframe.size.height = kPerformanceViewHeight; 382 | backViewFrame.size.height = kPerformanceViewHeight; 383 | _backView.frame = backViewFrame; 384 | _leakStackView.frame = stackViewframe; 385 | } 386 | 387 | #pragma mark - PTBPerformanceWindowDelegate 388 | - (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event { 389 | if (CGRectContainsPoint(_backView.bounds, [_backView convertPoint:point fromView:self.view])) { 390 | return YES; 391 | } 392 | return NO; 393 | } 394 | 395 | @end 396 | -------------------------------------------------------------------------------- /PTBPerformanceCenter/PTBPerformanceCenter/PTBPerformanceWindow.h: -------------------------------------------------------------------------------- 1 | // 2 | // PTBPerformanceWindow.h 3 | // PTBPerformanceCenter 4 | // 5 | // Created by PerTerbin on 2018/6/3. 6 | // Copyright © 2018年 PerTerbin. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @protocol PTBPerformanceWindowDelegate 12 | 13 | - (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event; 14 | 15 | @end 16 | 17 | @interface PTBPerformanceWindow : UIWindow 18 | 19 | @property (nonatomic, weak) id delegate; 20 | 21 | @end 22 | -------------------------------------------------------------------------------- /PTBPerformanceCenter/PTBPerformanceCenter/PTBPerformanceWindow.m: -------------------------------------------------------------------------------- 1 | // 2 | // PTBPerformanceWindow.m 3 | // PTBPerformanceCenter 4 | // 5 | // Created by PerTerbin on 2018/6/3. 6 | // Copyright © 2018年 PerTerbin. All rights reserved. 7 | // 8 | 9 | #import "PTBPerformanceWindow.h" 10 | 11 | @implementation PTBPerformanceWindow 12 | 13 | - (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event { 14 | BOOL able = [super pointInside:point withEvent:event]; 15 | if ([_delegate respondsToSelector:@selector(pointInside:withEvent:)]) { 16 | able = [_delegate pointInside:point withEvent:event]; 17 | } 18 | 19 | return able; 20 | } 21 | 22 | @end 23 | -------------------------------------------------------------------------------- /PTBPerformanceCenter/ViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.h 3 | // PTBPerformanceCenter 4 | // 5 | // Created by PerTerbin on 2018/6/3. 6 | // Copyright © 2018年 PerTerbin. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface ViewController : UIViewController 12 | 13 | 14 | @end 15 | 16 | -------------------------------------------------------------------------------- /PTBPerformanceCenter/ViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.m 3 | // PTBPerformanceCenter 4 | // 5 | // Created by PerTerbin on 2018/6/3. 6 | // Copyright © 2018年 PerTerbin. All rights reserved. 7 | // 8 | 9 | #import "ViewController.h" 10 | 11 | @interface ViewController () 12 | 13 | @end 14 | 15 | @implementation ViewController 16 | 17 | - (void)viewDidLoad { 18 | [super viewDidLoad]; 19 | // Do any additional setup after loading the view, typically from a nib. 20 | 21 | } 22 | 23 | 24 | - (void)didReceiveMemoryWarning { 25 | [super didReceiveMemoryWarning]; 26 | // Dispose of any resources that can be recreated. 27 | } 28 | 29 | 30 | @end 31 | -------------------------------------------------------------------------------- /PTBPerformanceCenter/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // PTBPerformanceCenter 4 | // 5 | // Created by PerTerbin on 2018/6/3. 6 | // Copyright © 2018年 PerTerbin. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "AppDelegate.h" 11 | 12 | int main(int argc, char * argv[]) { 13 | @autoreleasepool { 14 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /PTBPerformanceCenterTests/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 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /PTBPerformanceCenterTests/PTBPerformanceCenterTests.m: -------------------------------------------------------------------------------- 1 | // 2 | // PTBPerformanceCenterTests.m 3 | // PTBPerformanceCenterTests 4 | // 5 | // Created by PerTerbin on 2018/6/3. 6 | // Copyright © 2018年 PerTerbin. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface PTBPerformanceCenterTests : XCTestCase 12 | 13 | @end 14 | 15 | @implementation PTBPerformanceCenterTests 16 | 17 | - (void)setUp { 18 | [super setUp]; 19 | // Put setup code here. This method is called before the invocation of each test method in the class. 20 | } 21 | 22 | - (void)tearDown { 23 | // Put teardown code here. This method is called after the invocation of each test method in the class. 24 | [super tearDown]; 25 | } 26 | 27 | - (void)testExample { 28 | // This is an example of a functional test case. 29 | // Use XCTAssert and related functions to verify your tests produce the correct results. 30 | } 31 | 32 | - (void)testPerformanceExample { 33 | // This is an example of a performance test case. 34 | [self measureBlock:^{ 35 | // Put the code you want to measure the time of here. 36 | }]; 37 | } 38 | 39 | @end 40 | -------------------------------------------------------------------------------- /PTBPerformanceCenterUITests/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 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /PTBPerformanceCenterUITests/PTBPerformanceCenterUITests.m: -------------------------------------------------------------------------------- 1 | // 2 | // PTBPerformanceCenterUITests.m 3 | // PTBPerformanceCenterUITests 4 | // 5 | // Created by PerTerbin on 2018/6/3. 6 | // Copyright © 2018年 PerTerbin. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface PTBPerformanceCenterUITests : XCTestCase 12 | 13 | @end 14 | 15 | @implementation PTBPerformanceCenterUITests 16 | 17 | - (void)setUp { 18 | [super setUp]; 19 | 20 | // Put setup code here. This method is called before the invocation of each test method in the class. 21 | 22 | // In UI tests it is usually best to stop immediately when a failure occurs. 23 | self.continueAfterFailure = NO; 24 | // UI tests must launch the application that they test. Doing this in setup will make sure it happens for each test method. 25 | [[[XCUIApplication alloc] init] launch]; 26 | 27 | // In UI tests it’s important to set the initial state - such as interface orientation - required for your tests before they run. The setUp method is a good place to do this. 28 | } 29 | 30 | - (void)tearDown { 31 | // Put teardown code here. This method is called after the invocation of each test method in the class. 32 | [super tearDown]; 33 | } 34 | 35 | - (void)testExample { 36 | // Use recording to get started writing UI tests. 37 | // Use XCTAssert and related functions to verify your tests produce the correct results. 38 | } 39 | 40 | @end 41 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 前言 2 | 3 | 众所周知,如今的用户变得越来越关心app的体验,开发者必须关注应用性能所带来的用户流失问题。目前危害较大的性能问题主要有:闪退、卡顿、发热、耗电快、网络劫持等,但是做过iOS开发的人都知道,在开发过程中我们没有一个很直观的工具可以实时的知道开发者写出来的代码会不会造成性能问题,虽然Xcode里提供了耗电量检测、内存泄漏检测等工具,但是这些工具使用效果并不理想(如Leak无法发现循环引用造成的内存泄漏)。所以这篇文章主要是介绍一款实时监控app各项性能指标的工具,包括**CPU占用率、内存使用量、内存泄漏、FPS、卡顿检测**,并且会分析造成这些性能问题的原因。 4 | 5 | ![](https://upload-images.jianshu.io/upload_images/6691810-8f00fe25c492fcfc.gif?imageMogr2/auto-orient/strip) 6 | 7 | # CPU 8 | 9 | CPU 是移动设备最重要的组成部分,如果开发者写的代码有问题导致CPU负载过高,会导致app使用过程中发生卡顿,同时也可能导致手机发热发烫,耗电过快,严重影响用户体验。 10 | 如果想避免CPU负载过高可以通过检测app的CPU使用率,然后可以发现导致CPU过高的代码,并根据具体情况优化。那该如何检测CPU使用率呢?大学期间学过计算机的应该都上过操作系统这门课,学过的都知道线程CPU是调度和分配的基本单位,而应用作为进程运行时,包含了多个不同的线程,这样如果我们能知道app里所有线程占用 CPU 的情况,也就能知道整个app的 CPU 占用率。幸运的是我们在**Mach** 层中 *thread_basic_info* 结构体发现了我们想要的东西,*thread_basic_info* 结构体定义如下: 11 | ``` 12 | struct thread_basic_info { 13 | time_value_t user_time; /* user run time */ 14 | time_value_t system_time; /* system run time */ 15 | integer_t cpu_usage; /* scaled cpu usage percentage */ 16 | policy_t policy; /* scheduling policy in effect */ 17 | integer_t run_state; /* run state (see below) */ 18 | integer_t flags; /* various flags (see below) */ 19 | integer_t suspend_count; /* suspend count for thread */ 20 | integer_t sleep_time; /* number of seconds that thread 21 | has been sleeping */ 22 | }; 23 | ``` 24 | 其中*cpu_usage*即为该线程的CPU使用率,接下来我们需要获取app的所有线程,iOS内核提供了 *thread_info* API 调用获取指定 task 的线程列表,然后可以通过 *thread_info* API 调用来查询指定线程的信息,*thread_info* API 在 thread_act.h 中定义。 25 | ``` 26 | kern_return_t task_threads 27 | ( 28 | task_t target_task, 29 | thread_act_array_t *act_list, 30 | mach_msg_type_number_t *act_listCnt 31 | ); 32 | ``` 33 | *task_threads* 将 *target_task* 任务中的所有线程保存在 *act_list* 数组中。 34 | 现在我们能够取得app的所有线程,并且能够取得每个线程的CPU使用率,这样获取app的CPU使用率的代码就呼之欲出,直接上代码: 35 | ``` 36 | - (CGFloat)usedCpu { 37 | kern_return_t kr = { 0 }; 38 | task_info_data_t tinfo = { 0 }; 39 | mach_msg_type_number_t task_info_count = TASK_INFO_MAX; 40 | 41 | kr = task_info(mach_task_self(), TASK_BASIC_INFO, (task_info_t)tinfo, &task_info_count); 42 | if (kr != KERN_SUCCESS) { 43 | return 0.0f; 44 | } 45 | 46 | task_basic_info_t basic_info = { 0 }; 47 | thread_array_t thread_list = { 0 }; 48 | mach_msg_type_number_t thread_count = { 0 }; 49 | 50 | thread_info_data_t thinfo = { 0 }; 51 | thread_basic_info_t basic_info_th = { 0 }; 52 | 53 | basic_info = (task_basic_info_t)tinfo; 54 | 55 | // get threads in the task 56 | kr = task_threads(mach_task_self(), &thread_list, &thread_count); 57 | if (kr != KERN_SUCCESS) { 58 | return 0.0f; 59 | } 60 | 61 | long tot_sec = 0; 62 | long tot_usec = 0; 63 | float tot_cpu = 0; 64 | 65 | for (int i = 0; i < thread_count; i++) { 66 | mach_msg_type_number_t thread_info_count = THREAD_INFO_MAX; 67 | 68 | kr = thread_info(thread_list[i], THREAD_BASIC_INFO, (thread_info_t)thinfo, &thread_info_count); 69 | if (kr != KERN_SUCCESS) { 70 | return 0.0f; 71 | } 72 | 73 | basic_info_th = (thread_basic_info_t)thinfo; 74 | if ((basic_info_th->flags & TH_FLAGS_IDLE) == 0) { 75 | tot_sec = tot_sec + basic_info_th->user_time.seconds + basic_info_th->system_time.seconds; 76 | tot_usec = tot_usec + basic_info_th->system_time.microseconds + basic_info_th->system_time.microseconds; 77 | tot_cpu = tot_cpu + basic_info_th->cpu_usage / (float)TH_USAGE_SCALE; 78 | } 79 | } 80 | 81 | kr = vm_deallocate( mach_task_self(), (vm_offset_t)thread_list, thread_count * sizeof(thread_t) ); 82 | if (kr != KERN_SUCCESS) { 83 | return 0.0f; 84 | } 85 | 86 | return (CGFloat)tot_cpu * 100; 87 | } 88 | ``` 89 | 有了获取CPU使用率的方法后我们只要再加个定时器去实时查询,并将得到的结果显示在界面上即可: 90 | ``` 91 | - (void)startMonitoringWithNoticeBlock:(void(^)(CGFloat value))noticeBlock { 92 | self.noticeBlock = noticeBlock; 93 | 94 | _timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(noticeCPUValue) userInfo:nil repeats:YES]; 95 | } 96 | 97 | - (void)noticeCPUValue { 98 | if (self.noticeBlock) { 99 | self.noticeBlock([self usedCpu]); 100 | } 101 | } 102 | ``` 103 | 104 | # Memory 105 | 106 | 物理内存(RAM)与 CPU 一样都是系统中最稀少的资源,也是最有可能产生竞争的资源,应用内存与性能直接相关 - 通常是以牺牲别的应用为代价。 不像 PC 端,iOS 没有交换空间作为备选资源,这就使得内存资源尤为重要。 107 | ## App占用的内存 108 | 109 | 获取app内存的API同样可以在**Mach**层找到,*mach_task_basic_info* 结构体存储了 Mach task 的内存使用信息,其中 *resident_size* 就是应用使用的物理内存大小,*virtual_size* 是虚拟内存大小。 110 | ``` 111 | #define MACH_TASK_BASIC_INFO 20 /* always 64-bit basic info */ 112 | struct mach_task_basic_info { 113 | mach_vm_size_t virtual_size; /* virtual memory size (bytes) */ 114 | mach_vm_size_t resident_size; /* resident memory size (bytes) */ 115 | mach_vm_size_t resident_size_max; /* maximum resident memory size (bytes) */ 116 | time_value_t user_time; /* total user run time for 117 | terminated threads */ 118 | time_value_t system_time; /* total system run time for 119 | terminated threads */ 120 | policy_t policy; /* default policy for new threads */ 121 | integer_t suspend_count; /* suspend count for task */ 122 | }; 123 | ``` 124 | 最后得到获取当前 App Memory 的使用情况: 125 | ``` 126 | - (CGFloat)usedMemory { 127 | task_basic_info_data_t taskInfo; 128 | mach_msg_type_number_t infoCount = TASK_BASIC_INFO_COUNT; 129 | kern_return_t kernReturn = task_info(mach_task_self(), TASK_BASIC_INFO, (task_info_t)&taskInfo, &infoCount); 130 | 131 | if (kernReturn != KERN_SUCCESS) { 132 | return NSNotFound; 133 | } 134 | 135 | CGFloat value = (CGFloat)(taskInfo.resident_size / 1024.0 / 1024.0); 136 | 137 | return value; 138 | } 139 | ``` 140 | ## 设备已使用的内存 141 | 142 | ``` 143 | + (CGFloat)deviceUsedMemory { 144 | size_t length = 0; 145 | int mib[6] = {0}; 146 | 147 | int pagesize = 0; 148 | mib[0] = CTL_HW; 149 | mib[1] = HW_PAGESIZE; 150 | length = sizeof(pagesize); 151 | if (sysctl(mib, 2, &pagesize, &length, NULL, 0) < 0) { 152 | return 0; 153 | } 154 | 155 | mach_msg_type_number_t count = HOST_VM_INFO_COUNT; 156 | 157 | vm_statistics_data_t vmstat; 158 | 159 | if (host_statistics(mach_host_self(), HOST_VM_INFO, (host_info_t)&vmstat, &count) != KERN_SUCCESS) { 160 | return 0; 161 | } 162 | 163 | int wireMem = vmstat.wire_count * pagesize; 164 | int activeMem = vmstat.active_count * pagesize; 165 | 166 | return (CGFloat)(wireMem + activeMem) / 1024.0 / 1024.0; 167 | } 168 | ``` 169 | ##设备可用的内存 170 | ``` 171 | + (CGFloat)deviceAvailableMemory { 172 | vm_statistics64_data_t vmStats; 173 | mach_msg_type_number_t infoCount = HOST_VM_INFO_COUNT; 174 | kern_return_t kernReturn = host_statistics(mach_host_self(), 175 | HOST_VM_INFO, 176 | (host_info_t)&vmStats, 177 | &infoCount); 178 | 179 | if (kernReturn != KERN_SUCCESS) { 180 | return NSNotFound; 181 | } 182 | 183 | return (CGFloat)(vm_page_size * (vmStats.free_count + vmStats.inactive_count) / 1024.0 / 1024.0); 184 | } 185 | ``` 186 | # FPS 187 | 188 | FPS即屏幕每秒的刷新率,范围在0-60之间,60最佳。FPS是测量用于保存、显示动态视频的信息数量,每秒钟帧数愈多,所显示的动作就会愈流畅,优秀的app都要保证FPS 在 55-60 之间,这样才会给用户流畅的感觉,反之,用户则会感觉到卡顿。 189 | 对于FPS的计算网上争议颇多,这边使用的和 *YYKit* 中的 *YYFPSLabel* 原理一样,系统提供了 *CADisplayLink* 这个 API,该API在屏幕每次绘制的时候都会回调,通过接收 *CADisplayLink* 的回调,计算每秒钟收到的回调次数得到屏幕每秒的刷新次数,从而得到 FPS,具体代码如下: 190 | ``` 191 | - (void)startMonitoringWithNoticeBlock:(void(^)(CGFloat value))noticeBlock { 192 | self.noticeBlock = noticeBlock; 193 | 194 | _displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(envokeDisplayLink:)]; 195 | _displayLink.paused = NO; 196 | [_displayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes]; 197 | } 198 | 199 | - (void)envokeDisplayLink:(CADisplayLink *)displayLink { 200 | if (!_timestampArray) { 201 | _timestampArray = [NSMutableArray arrayWithCapacity:60]; 202 | } 203 | 204 | if (_timestampArray.count == 60) { 205 | [_timestampArray removeObject:_timestampArray.firstObject]; 206 | } 207 | 208 | [_timestampArray addObject:@(displayLink.timestamp)]; 209 | 210 | __block NSInteger fps = 0; 211 | [_timestampArray enumerateObjectsWithOptions:NSEnumerationReverse usingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { 212 | if (displayLink.timestamp - [obj doubleValue] < 1) { 213 | fps++; 214 | } else { 215 | *stop = YES; 216 | } 217 | }]; 218 | 219 | if (self.noticeBlock) { 220 | self.noticeBlock((CGFloat)fps); 221 | } 222 | } 223 | ``` 224 | 值得注意的是基于 *CADisplayLink* 实现的 FPS 在生产场景中只有指导意义,不能代表真实的 FPS,因为基于 *CADisplayLink* 实现的 FPS 无法完全检测出当前 Core Animation 的性能情况,它只能检测出当前 RunLoop 的帧率。 225 | 226 | # Freezing 227 | 228 | ## 为什么会出现卡顿 229 | 230 | 从一个像素到最后真正显示在屏幕上,iPhone 究竟在这个过程中做了些什么?想要了解背后的运作流程,首先需要了解屏幕显示的原理。iOS 上完成图形的显示实际上是 CPU、GPU 和显示器协同工作的结果,具体来说,CPU 负责计算显示内容,包括视图的创建、布局计算、图片解码、文本绘制等,CPU 完成计算后会将计算内容提交给 GPU,GPU 进行变换、合成、渲染后将渲染结果提交到帧缓冲区,当下一次垂直同步信号(简称 V-Sync)到来时,最后显示到屏幕上。下面是显示流程的示意图: 231 | 232 | ![显示流程示意图](https://upload-images.jianshu.io/upload_images/6691810-896ca635826bd1a4.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 233 | 234 | 上文中提到 V-Sync 是什么,以及为什么要在 iPhone 的显示流程引入它呢?在 iPhone 中使用的是双缓冲机制,即上图中的 FrameBuffer 有两个缓冲区,双缓冲区的引入是为了提升显示效率,但是与此同时,他引入了一个新的问题,当视频控制器还未读取完成时,比如屏幕内容刚显示一半时,GPU 将新的一帧内容提交到帧缓冲区并把两个缓冲区进行交换后,视频控制器就会把新的一帧数据的下半段显示到屏幕上,造成画面撕裂现象,V-Sync 就是为了解决画面撕裂问题,开启 V-Sync 后,GPU 会在显示器发出 V-Sync 信号后,去进行新帧的渲染和缓冲区的更新。 235 | 236 | 搞清楚了 iPhone 的屏幕显示原理后,下面来看看在 iPhone 上为什么会出现卡顿现象,上文已经提及在图像真正在屏幕显示之前,CPU 和 GPU 需要完成自身的任务,而如果他们完成的时间错过了下一次 V-Sync 的到来(通常是1000/60=16.67ms),这样就会出现显示屏还是之前帧的内容,这就是界面卡顿的原因(离屏渲染就是典型的卡顿问题)。不难发现,无论是 CPU 还是 GPU 引起错过 V-Sync 信号,都会造成界面卡顿。 237 | 238 | ![卡顿原因示意图](https://upload-images.jianshu.io/upload_images/6691810-80960f97a81d8aaa.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 239 | 240 | 那如何检测卡顿呢?比较常见的思路是:开辟一条单独的子线程,让这条子线程去实时检测主线程的 RunLoop 情况,实时计算 kCFRunLoopBeforeSources 和 kCFRunLoopAfterWaiting 两个状态之间的耗时是否超过某个阀值,如果超过阈值即认定主线程发生了卡顿。下面是代码实现: 241 | ``` 242 | static void runLoopObserverCallBack(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info) 243 | { 244 | MyClass *object = (__bridge MyClass*)info; 245 | 246 | // 记录状态值 247 | object->activity = activity; 248 | 249 | // 发送信号 250 | dispatch_semaphore_t semaphore = moniotr->semaphore; 251 | dispatch_semaphore_signal(semaphore); 252 | } 253 | 254 | - (void)registerObserver 255 | { 256 | CFRunLoopObserverContext context = {0,(__bridge void*)self,NULL,NULL}; 257 | CFRunLoopObserverRef observer = CFRunLoopObserverCreate(kCFAllocatorDefault, 258 | kCFRunLoopAllActivities, 259 | YES, 260 | 0, 261 | &runLoopObserverCallBack, 262 | &context); 263 | CFRunLoopAddObserver(CFRunLoopGetMain(), observer, kCFRunLoopCommonModes); 264 | 265 | // 创建信号 266 | semaphore = dispatch_semaphore_create(0); 267 | 268 | // 在子线程监控时长 269 | dispatch_async(dispatch_get_global_queue(0, 0), ^{ 270 | while (YES) 271 | { 272 | // 假定连续5次超时50ms认为卡顿(当然也包含了单次超时250ms) 273 | long st = dispatch_semaphore_wait(semaphore, dispatch_time(DISPATCH_TIME_NOW, 50*NSEC_PER_MSEC)); 274 | if (st != 0) 275 | { 276 | if (activity==kCFRunLoopBeforeSources || activity==kCFRunLoopAfterWaiting) 277 | { 278 | if (++timeoutCount < 5) 279 | continue; 280 | // 检测到卡顿,进行卡顿上报 281 | } 282 | } 283 | timeoutCount = 0; 284 | } 285 | }); 286 | } 287 | ``` 288 | 当检测到卡顿后可以进一步收集卡顿现场,如堆栈信息等,关于收集堆栈信息这里就不细说,很多第三方库都有实现,我之前是使用了项目中已经集成的收集崩溃信息的三方库,通过这个库在收集堆栈信息。 289 | 290 | # MemoryLeak 291 | 292 | 内存泄漏也是造成app内存过高的主要原因,如果iPhone手机的性能都很强,如果一个app会因为内存过高被系统强制杀掉,大部分都是存在内存泄漏。内存泄漏对于开发和测试而言表现得并不明显,如果它不泄漏到一定程度是用户是无法察觉的,但是这也是开发者必须杜绝的一大问题。 293 | ## 查找内存泄漏 294 | 295 | 对于内存泄漏Xcode提供了Leak工具,但是使用过的人都知道Leak无法查出很多泄漏(如循环引用),在这里检测内存泄漏使用的是微信读书团队 Mr.佘 提供的工具 [MLeakFinder](https://github.com/Tencent/MLeaksFinder)。 296 | 这里大致讲一下实现原理,当一个VC(或者View)被pop或者被dismiss 2 秒后还没有被销毁则认定该VC(或View)发生了泄漏。那如何知道 2 秒后该对象有没有被释放呢, 297 | ``` 298 | + (void)load { 299 | static dispatch_once_t onceToken; 300 | dispatch_once(&onceToken, ^{ 301 | [self swizzleSEL:@selector(popViewControllerAnimated:) withSEL:@selector(swizzled_popViewControllerAnimated:)]; 302 | }); 303 | } 304 | ``` 305 | 通过方法交换将系统的pop方法换掉,然后注入自己的代码实现,当调用pop时会调用 *willDealloc* 方法,该方法实现如下: 306 | ``` 307 | - (BOOL)willDealloc { 308 | __weak id weakSelf = self; 309 | dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ 310 | __strong id strongSelf = weakSelf; 311 | [strongSelf assertNotDealloc]; 312 | }); 313 | 314 | return YES; 315 | } 316 | ``` 317 | 通过弱引用持有自身,并在 2 秒后调用 *assertNotDealloc*, 如果 2 秒内该对象已释放这里的 *weakSelf* 为nil,也就什么都不会发生,反之则认为发生了内存泄漏,进行下一步操作,如弹出警告等。 318 | 这里只是大致讲一下 *MLeakFinder* 的原理,详细介绍可以去 [他的博客 ](http://wereadteam.github.io/2016/07/20/MLeaksFinder2/)详细了解。 319 | ## 查找循环引用 320 | 321 | 查找循环引用使用的是 Facebook 开源库 *FBRetainCycleDetector* ,具体也可以去网上查找相关资料,这里就不详细说。 322 | 323 | --------------------------------------------------------------------------------