├── .gitignore ├── LICENSE ├── QAPM.podspec ├── QAPM ├── Monitor │ ├── FPSMonitor │ │ ├── QAPFPSMonitor.h │ │ └── QAPFPSMonitor.m │ ├── QAPMonitor.h │ ├── QAPMonitor.m │ ├── Reachability.h │ ├── Reachability.m │ ├── SystemInfo │ │ ├── QAPMSystemInfo.h │ │ └── QAPMSystemInfo.m │ └── URLConnection │ │ ├── NSURLConnection+QunarAPM.h │ │ ├── NSURLConnection+QunarAPM.m │ │ ├── NSURLSession+QunarAPM.h │ │ ├── NSURLSession+QunarAPM.m │ │ ├── QAPMNSURLSessionDelegateAgent.h │ │ ├── QAPMNSURLSessionDelegateAgent.m │ │ ├── QAPMNSURLSessionTaskAgent.h │ │ ├── QAPMNSURLSessionTaskAgent.m │ │ ├── QAPMNetworkEntry.h │ │ ├── QAPMNetworkEntry.m │ │ ├── QAPMURLConnectionDelegateAgent.h │ │ └── QAPMURLConnectionDelegateAgent.m ├── QAPManager.h ├── QAPManager.m └── Storage │ ├── QCacheStorage.h │ └── QCacheStorage.m ├── QAPMApp.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist ├── xcshareddata │ └── xcschemes │ │ └── QAPMApp.xcscheme └── xcuserdata │ └── mdd.xcuserdatad │ └── xcschemes │ └── xcschememanagement.plist ├── QAPMApp ├── AppDelegate.h ├── AppDelegate.m ├── Assets.xcassets │ ├── AppIcon.appiconset │ │ └── Contents.json │ ├── Contents.json │ └── LaunchImage.launchimage │ │ └── Contents.json ├── Info.plist ├── QAPMViewController.h ├── QAPMViewController.m └── main.m └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | # kdiff3 ignore 2 | *.orig 3 | 4 | # maven ignore 5 | target/ 6 | 7 | # eclipse ignore 8 | .settings/ 9 | .project 10 | .classpath 11 | 12 | # idea ignore 13 | .idea/ 14 | *.ipr 15 | *.iml 16 | *.iws 17 | 18 | # temp ignore 19 | *.log 20 | *.cache 21 | *.diff 22 | *.patch 23 | *.tmp 24 | 25 | # system ignore 26 | .DS_Store 27 | Thumbs.db 28 | 29 | # package ignore (optional) 30 | # *.jar 31 | # *.war 32 | # *.zip 33 | # *.tar 34 | # *.tar.gz 35 | 36 | # pods ignore 37 | Pods/ 38 | *.xcuserstate 39 | *.xcbkptlist 40 | UserInterfaceState.xcuserstate 41 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /QAPM.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = "QAPM" 3 | s.version = "0.0.1" 4 | s.summary = "QAPMLib组件库" 5 | s.description = <<-DESC 6 | QAPM 7 | DESC 8 | s.homepage = "http://www.qunar.com" 9 | s.author = { "mobi" => "mobi@qunar.com" } 10 | s.platform = :ios, "6" 11 | s.license = "Copyright 2015 Qunar.com" 12 | 13 | #对于下边的s.source部分,不需要用户进行编辑,发布平台会自动处理 14 | s.source = { :git => "git@github.com:qunarcorp/qapm_ios.git", :tag => s.version.to_s} 15 | #如果包含头文件,请将下边的注释去掉 16 | s.source_files = "QAPM/**/*.{h,m}", "$(PODS_ROOT)/**/*.h" 17 | #s.exclude_files = "Classes/Exclude" 18 | s.public_header_files = "library/**/*.h" 19 | end 20 | -------------------------------------------------------------------------------- /QAPM/Monitor/FPSMonitor/QAPFPSMonitor.h: -------------------------------------------------------------------------------- 1 | // 2 | // QAPFPSMonitor.h 3 | // QAPMApp 4 | // 5 | // Created by mdd on 2019/1/7. 6 | // Copyright © 2019年 mdd. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | #define QAPFPS_DROPPED_FROZEN 42 14 | #define QAPFPS_DROPPED_HIGH 24 15 | #define QAPFPS_DROPPED_MIDDLE 9 16 | #define QAPFPS_DROPPED_NORMAL 3 17 | 18 | typedef struct QAPFPSDroppedInfo { 19 | long long count; 20 | long long sumTime; 21 | NSTimeInterval lastTime; 22 | struct { 23 | long long frozen; 24 | long long high; 25 | long long middle; 26 | long long nomal; 27 | long long best; 28 | } dropSum; 29 | struct { 30 | long long frozen; 31 | long long high; 32 | long long middle; 33 | long long nomal; 34 | long long best; 35 | } dropLevel; 36 | 37 | }QAPFPSDroppedInfo; 38 | 39 | @interface QAPFPSMonitor : NSObject 40 | - (void)startFPSMonitor; 41 | @end 42 | 43 | NS_ASSUME_NONNULL_END 44 | -------------------------------------------------------------------------------- /QAPM/Monitor/FPSMonitor/QAPFPSMonitor.m: -------------------------------------------------------------------------------- 1 | // 2 | // QAPFPSMonitor.m 3 | // QAPMApp 4 | // 5 | // Created by mdd on 2019/1/7. 6 | // Copyright © 2019年 mdd. All rights reserved. 7 | // 8 | 9 | #import "QAPFPSMonitor.h" 10 | #import 11 | #import "QAPManager.h" 12 | #import "QAPMonitor.h" 13 | 14 | /// 失去焦点上报,停止监听,获取焦点开始监听 15 | @interface QAPFPSMonitor () { 16 | CADisplayLink *_link; 17 | BOOL _isMonitor; 18 | QAPFPSDroppedInfo _dropInfo; 19 | NSString *_lastTopVC; 20 | } 21 | @end 22 | 23 | @implementation QAPFPSMonitor 24 | 25 | - (instancetype)init { 26 | if (self = [super init]) { 27 | [[NSNotificationCenter defaultCenter] addObserver:self 28 | selector:@selector(appDidBecomeActive) 29 | name:UIApplicationDidBecomeActiveNotification 30 | object:nil]; 31 | [[NSNotificationCenter defaultCenter] addObserver:self 32 | selector:@selector(appWillResignActive) 33 | name:UIApplicationWillResignActiveNotification 34 | object:nil]; 35 | } 36 | return self; 37 | } 38 | 39 | - (void)appDidBecomeActive { 40 | if (_isMonitor == NO) { 41 | _isMonitor = YES; 42 | _lastTopVC = [QAPManager topVCClassName]; 43 | } 44 | } 45 | 46 | - (void)appWillResignActive { 47 | if (_isMonitor) { 48 | [self recordFPSInfo]; 49 | _isMonitor = NO; 50 | } 51 | } 52 | 53 | - (void)startFPSMonitor { 54 | @synchronized (self) { 55 | if (!_link) { 56 | [self clearDropedInfo]; 57 | _isMonitor = YES; 58 | _lastTopVC = @""; 59 | _link = [CADisplayLink displayLinkWithTarget:self selector:@selector(tick:)]; 60 | [_link addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes]; 61 | } 62 | } 63 | } 64 | 65 | - (void)clearDropedInfo { 66 | _dropInfo.count = 0; 67 | _dropInfo.sumTime = 0; 68 | _dropInfo.lastTime = 0; 69 | 70 | _dropInfo.dropSum.frozen = 0; 71 | _dropInfo.dropSum.high = 0; 72 | _dropInfo.dropSum.middle = 0; 73 | _dropInfo.dropSum.nomal = 0; 74 | _dropInfo.dropSum.best = 0; 75 | 76 | _dropInfo.dropLevel.frozen = 0; 77 | _dropInfo.dropLevel.high = 0; 78 | _dropInfo.dropLevel.middle = 0; 79 | _dropInfo.dropLevel.nomal = 0; 80 | _dropInfo.dropLevel.best = 0; 81 | } 82 | 83 | /// 减少耗时操作 84 | - (void)tick:(CADisplayLink *)link { 85 | if (!_isMonitor) { 86 | return; 87 | } 88 | if (_dropInfo.lastTime == 0) { 89 | _dropInfo.lastTime = link.timestamp; 90 | return; 91 | } 92 | NSString *curTopVC = [QAPManager topVCClassName]; 93 | if (curTopVC.length == 0) { 94 | return; 95 | } 96 | if (_lastTopVC.length == 0) { 97 | _lastTopVC = curTopVC; 98 | return; 99 | } 100 | 101 | // 微妙 102 | static int refreshTime = 1; 103 | if (refreshTime == 1) { 104 | refreshTime = link.duration * 1000000; 105 | } 106 | int period = (link.timestamp - _dropInfo.lastTime) * 1000000; 107 | if ([curTopVC isEqualToString:_lastTopVC]) { 108 | _dropInfo.count++; 109 | _dropInfo.sumTime += period; 110 | int temp = period / refreshTime - 1; 111 | if (temp >= QAPFPS_DROPPED_FROZEN) { 112 | _dropInfo.dropLevel.frozen++; 113 | _dropInfo.dropSum.frozen += temp; 114 | } 115 | else if (temp >= QAPFPS_DROPPED_HIGH) { 116 | _dropInfo.dropLevel.high++; 117 | _dropInfo.dropSum.high += temp; 118 | } 119 | else if (temp >= QAPFPS_DROPPED_MIDDLE) { 120 | _dropInfo.dropLevel.middle++; 121 | _dropInfo.dropSum.middle += temp; 122 | } 123 | else if (temp >= QAPFPS_DROPPED_NORMAL) { 124 | _dropInfo.dropLevel.nomal++; 125 | _dropInfo.dropSum.nomal += temp; 126 | } 127 | else { 128 | _dropInfo.dropLevel.best++; 129 | _dropInfo.dropSum.best += (temp < 0 ? 0 : temp); 130 | } 131 | } 132 | else { 133 | // 发送 134 | [self recordFPSInfo]; 135 | _lastTopVC = curTopVC; 136 | } 137 | _dropInfo.lastTime = link.timestamp; 138 | } 139 | 140 | - (void)recordFPSInfo { 141 | if (_lastTopVC.length == 0 || _dropInfo.count == 0) { 142 | [self clearDropedInfo]; 143 | return; 144 | } 145 | long long fps = (_dropInfo.count * 1000000) / _dropInfo.sumTime; 146 | long long timeIntervalNow = (long long)([[NSDate date] timeIntervalSince1970] * 1000); 147 | NSDictionary *dropDict = @{ 148 | @"action":@"fps", 149 | // 页面名称 150 | @"page":_lastTopVC, 151 | // 页面丢帧占比分布 152 | @"dropLevel":@{ 153 | @"frozen":[@(_dropInfo.dropLevel.frozen) stringValue], 154 | @"high":[@(_dropInfo.dropLevel.high) stringValue], 155 | @"middle":[@(_dropInfo.dropLevel.middle) stringValue], 156 | @"normal":[@(_dropInfo.dropLevel.nomal) stringValue], 157 | @"best":[@(_dropInfo.dropLevel.best) stringValue] 158 | }, 159 | // 页面丢帧占比分布 160 | @"dropSum":@{ 161 | @"frozen":[@(_dropInfo.dropSum.frozen) stringValue], 162 | @"high":[@(_dropInfo.dropSum.high) stringValue], 163 | @"middle":[@(_dropInfo.dropSum.middle) stringValue], 164 | @"normal":[@(_dropInfo.dropSum.nomal) stringValue], 165 | @"best":[@(_dropInfo.dropSum.best) stringValue] 166 | }, 167 | @"fps":[@(fps) stringValue], 168 | @"count":[@(_dropInfo.count) stringValue], 169 | @"sumTime":[@(_dropInfo.sumTime / 1000) stringValue], 170 | @"logTime":[@(timeIntervalNow) stringValue] 171 | }; 172 | [self clearDropedInfo]; 173 | dispatch_async(dispatch_get_global_queue(0, 0), ^{ 174 | [QAPMonitor addFPSMonitor:dropDict]; 175 | }); 176 | } 177 | 178 | - (void)dealloc { 179 | [[NSNotificationCenter defaultCenter] removeObserver:self]; 180 | [_link invalidate]; 181 | _link = nil; 182 | } 183 | 184 | @end 185 | -------------------------------------------------------------------------------- /QAPM/Monitor/QAPMonitor.h: -------------------------------------------------------------------------------- 1 | // 2 | // Monitor.h 3 | // CommonFramework 4 | // 5 | // Created by zhou on 16/1/14. 6 | // Copyright © 2016年 Qunar.com. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "QCacheStorage.h" 11 | 12 | typedef enum : NSUInteger 13 | { 14 | eNetworkTaskMonitor, 15 | eWebNetMonitor, 16 | eUIMonitor, 17 | } eMonitorType; 18 | 19 | NS_ASSUME_NONNULL_BEGIN 20 | 21 | @interface QAPMonitor : NSObject 22 | + (instancetype)getInstance; 23 | @property (nonatomic, strong, readonly) QCacheStorage *gMonitorCache; 24 | @property (nonatomic, copy) NSString *uploadUrl; 25 | // 装载性能监控 26 | + (void)setupMonitorWithPid:(nonnull NSString *)pid 27 | cid:(nullable NSString *)cid 28 | vid:(nullable NSString *)vid 29 | uid:(nullable NSString *)uid; 30 | 31 | // 添加监控数据 32 | + (void)addMonitor:(NSDictionary *)monitorData withType:(eMonitorType)type; 33 | /// 添加UI监控数据 34 | + (void)addUIMonitor:(NSDictionary *)uiMonitorData; 35 | /// 添加net监控数据 36 | + (void)addNetMonitor:(NSDictionary *)netMonitorData; 37 | // 发送监控数据 38 | + (void)sendMonitor; 39 | + (void)addFPSMonitor:(NSDictionary *)fpsMonitorData; 40 | // 最近成功的NetworkTask的URL 41 | + (NSString *)lastSearchURL; 42 | 43 | // 获取vid 44 | + (NSString *)vid; 45 | 46 | // 获取uid 47 | + (NSString *)uid; 48 | 49 | + (NSString *)netType; 50 | 51 | + (NSString *)carrierCode; 52 | + (BOOL)isForeground; 53 | 54 | - (NSDictionary *)commonParam; 55 | @property (nonatomic, strong) NSNumber *userModifyTime; 56 | @end 57 | 58 | NS_ASSUME_NONNULL_END 59 | 60 | __BEGIN_DECLS 61 | long long QAPMGetCurrentTime_millisecond(void); 62 | __END_DECLS 63 | -------------------------------------------------------------------------------- /QAPM/Monitor/QAPMonitor.m: -------------------------------------------------------------------------------- 1 | // 2 | // Monitor.m 3 | // CommonFramework 4 | // 5 | // Created by zhou on 16/1/14. 6 | // Copyright © 2016年 Qunar.com. All rights reserved. 7 | // 8 | 9 | #import "QAPMonitor.h" 10 | #import 11 | #import 12 | #import 13 | #import 14 | #import 15 | #import 16 | #import "NSURLConnection+QunarAPM.h" 17 | #import "NSURLSession+QunarAPM.h" 18 | #import "Reachability.h" 19 | #import "QAPManager.h" 20 | #import "QCacheStorage.h" 21 | #import "QAPFPSMonitor.h" 22 | #import "QAPMSystemInfo.h" 23 | 24 | #define kMonitorStorageKey @"AppMonitorStorageKey" 25 | #define kMinMonitorNumber 10 26 | 27 | @interface QAPMonitor () 28 | /// 新格式,d新格式稳定后 老格式暂时保留:arrayNetwork、arrayUI 29 | @property (atomic, strong) NSMutableArray *arrayMonitor; 30 | @property (nonatomic, strong) NSString *searchURL; // 最近成功的URL 31 | 32 | @property (nonatomic, strong) NSString *vid; // 版本号 33 | @property (nonatomic, strong) NSString *pid; // 产品号 34 | @property (nonatomic, strong) NSString *uid; // 设备号 35 | @property (nonatomic, strong) NSString *cid; // 渠道标识 36 | @property (nonatomic, assign) BOOL isForeground; 37 | @property (nonatomic, strong) QAPFPSMonitor *fpsMonitor; 38 | @property (nonatomic, strong) QCacheStorage *gMonitorCache; 39 | @end 40 | 41 | static QAPMonitor *globalMonitor = nil; 42 | static NSLock *globalMonitorWriteLock = nil; 43 | 44 | @implementation QAPMonitor 45 | 46 | + (instancetype)getInstance { 47 | @synchronized(self) { 48 | // 实例对象只分配一次 49 | if (globalMonitor == nil) { 50 | globalMonitor = [[QAPMonitor alloc] init]; 51 | NSString *defaultDirectory = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)[0]; 52 | NSString *rlt = [defaultDirectory stringByAppendingPathComponent:@"QAPMonitorLog"]; 53 | globalMonitor.gMonitorCache = [QCacheStorage cacheStorageWithDir:rlt]; 54 | globalMonitor.gMonitorCache.maxCacheSize = 5; 55 | [globalMonitor setSearchURL:nil]; 56 | globalMonitorWriteLock = [[NSLock alloc] init]; 57 | 58 | if ([globalMonitor arrayMonitor] == nil) { 59 | [globalMonitor setArrayMonitor:[[NSMutableArray alloc] initWithCapacity:0]]; 60 | } 61 | [globalMonitor registerNotification]; 62 | } 63 | } 64 | return globalMonitor; 65 | } 66 | 67 | + (void)addUIMonitor:(NSDictionary *)uiMonitorData { 68 | [self addMonitorData:uiMonitorData]; 69 | } 70 | 71 | + (void)addNetMonitor:(NSDictionary *)netMonitorData { 72 | [self addMonitorData:netMonitorData]; 73 | } 74 | 75 | + (void)addFPSMonitor:(NSDictionary *)fpsMonitorData { 76 | [self addMonitorData:fpsMonitorData]; 77 | } 78 | 79 | + (void)addMonitorData:(NSDictionary *)monitorData { 80 | [[QAPMonitor getInstance] lock]; 81 | if (monitorData.count > 0) { 82 | [[[QAPMonitor getInstance] arrayMonitor] addObject:monitorData]; 83 | if ([[[QAPMonitor getInstance] arrayMonitor] count] >= 10) { 84 | NSDictionary *cparam = [[QAPMonitor getInstance] commonParam]; 85 | NSDictionary *data = @{@"c":cparam,@"b":[[QAPMonitor getInstance] arrayMonitor]}; 86 | [[QAPMonitor getInstance] setArrayMonitor:@[].mutableCopy]; 87 | [[[self getInstance] gMonitorCache] saveData:data toFile:[QCacheStorage autoIncrementFileName]]; 88 | [[QAPMonitor getInstance] sendMonitorToServer]; 89 | } 90 | } 91 | [[QAPMonitor getInstance] unlock]; 92 | } 93 | 94 | - (void)saveAllMonitorData { 95 | if ([[[QAPMonitor getInstance] arrayMonitor] count] > 0) { 96 | NSDictionary *cparam = [[QAPMonitor getInstance] commonParam]; 97 | NSDictionary *data = @{@"c":cparam?:@{},@"b":[[QAPMonitor getInstance] arrayMonitor]}; 98 | [[QAPMonitor getInstance] setArrayMonitor:@[].mutableCopy]; 99 | [self.gMonitorCache saveData:data toFile:[QCacheStorage autoIncrementFileName]]; 100 | [self.gMonitorCache saveCacheToFile:nil]; 101 | } 102 | } 103 | 104 | + (void)addMonitor:(NSDictionary *)monitorData withType:(eMonitorType)type { 105 | NSMutableDictionary *dictM = nil; 106 | [[QAPMonitor getInstance] lock]; 107 | if ([monitorData count] > 0) { 108 | switch (type) 109 | { 110 | case eNetworkTaskMonitor: 111 | { 112 | NSString *reqUrl = [monitorData objectForKey:@"reqUrl"]; 113 | NSString *code = [monitorData objectForKey:@"httpCode"]; 114 | if (reqUrl != nil && [reqUrl length] > 0 && code != nil && [code length] > 0 && [code isEqualToString:@"200"]) 115 | { 116 | [[QAPMonitor getInstance] setSearchURL:reqUrl]; 117 | } 118 | dictM = monitorData.mutableCopy; 119 | dictM[@"action"] = @"iosNet"; 120 | } 121 | break; 122 | 123 | default: 124 | break; 125 | } 126 | } 127 | [[QAPMonitor getInstance] unlock]; 128 | if (dictM) { 129 | [self addMonitorData:dictM]; 130 | #if (BETA_BUILD == 1) || DEBUG 131 | dispatch_async(dispatch_get_global_queue(0, 0), ^{ 132 | [[QAPMSystemInfo sharedInstance] addArrayMonitor:dictM]; 133 | }); 134 | #endif 135 | } 136 | } 137 | 138 | + (void)setupMonitorWithPid:(nonnull NSString *)pid 139 | cid:(nullable NSString *)cid 140 | vid:(nullable NSString *)vid 141 | uid:(nullable NSString *)uid { 142 | 143 | [[QAPMonitor getInstance] setPid:pid]; 144 | [[QAPMonitor getInstance] setCid:cid]; 145 | [[QAPMonitor getInstance] setVid:vid]; 146 | [[QAPMonitor getInstance] setUid:uid]; 147 | [[QAPMonitor getInstance] setIsForeground:YES]; 148 | [QAPMonitor setupMonitor]; 149 | } 150 | 151 | + (void)setupMonitor { 152 | // 初始化对 NSURLConnection/UIViewController 的监控 153 | static dispatch_once_t once_token; 154 | dispatch_once(&once_token, ^{ 155 | [QAPMonitor getInstance]; 156 | [NSURLConnection QAPMSetupPerformanceMonitoring]; 157 | [NSURLSession QAPMSetupPerformanceMonitoring]; 158 | Class classInstance = NSClassFromString(@"QAPMVCLoadingMonitor"); 159 | SEL sel = NSSelectorFromString(@"startVCLoadingMonitor"); 160 | if ([classInstance respondsToSelector:sel]) { 161 | [classInstance performSelector:sel withObject:nil]; 162 | } 163 | globalMonitor.fpsMonitor = [[QAPFPSMonitor alloc] init]; 164 | [globalMonitor.fpsMonitor startFPSMonitor]; 165 | #if (BETA_BUILD == 1) || DEBUG 166 | [[QAPMSystemInfo sharedInstance] startMonitor]; 167 | #endif 168 | }); 169 | } 170 | 171 | + (void)sendMonitor { 172 | dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ 173 | [[QAPMonitor getInstance] sendMonitor]; 174 | }); 175 | } 176 | 177 | + (NSString *)lastSearchURL { 178 | return [[QAPMonitor getInstance] searchURL]; 179 | } 180 | 181 | + (NSString *)vid { 182 | return [[QAPMonitor getInstance] vid]; 183 | } 184 | 185 | + (NSString *)uid { 186 | return [[QAPMonitor getInstance] uid]; 187 | } 188 | 189 | + (BOOL)isForeground { 190 | return [[self getInstance] isForeground]; 191 | } 192 | 193 | + (void)enterBackground { 194 | [[self getInstance] setIsForeground:NO]; 195 | [[self getInstance] sendMonitor]; 196 | } 197 | 198 | + (void)enterForeground { 199 | [[self getInstance] setIsForeground:YES]; 200 | } 201 | 202 | - (void)registerNotification { 203 | [[NSNotificationCenter defaultCenter] addObserver:[self class] 204 | selector:@selector(sendMonitor) 205 | name:UIApplicationWillTerminateNotification 206 | object:nil]; 207 | [[NSNotificationCenter defaultCenter] addObserver:[self class] 208 | selector:@selector(enterBackground) 209 | name:UIApplicationDidEnterBackgroundNotification 210 | object:nil]; 211 | [[NSNotificationCenter defaultCenter] addObserver:[self class] 212 | selector:@selector(enterForeground) 213 | name:UIApplicationWillEnterForegroundNotification 214 | object:nil]; 215 | [[NSNotificationCenter defaultCenter] addObserver:[QAPMonitor getInstance] 216 | selector:@selector(delaySendMonitor) 217 | name:UIApplicationDidFinishLaunchingNotification 218 | object:nil]; 219 | } 220 | /// 启动延迟三秒发送,避免影响性能 221 | - (void)delaySendMonitor { 222 | dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ 223 | [self sendMonitor]; 224 | }); 225 | } 226 | 227 | - (void)unregisterNotification { 228 | [[NSNotificationCenter defaultCenter] removeObserver:[QAPMonitor getInstance]]; 229 | } 230 | 231 | - (void)lock { 232 | if ([QAPMonitor getInstance] != nil) { 233 | [globalMonitorWriteLock lock]; 234 | } 235 | } 236 | 237 | - (void)unlock { 238 | if ([QAPMonitor getInstance] != nil) { 239 | [globalMonitorWriteLock unlock]; 240 | } 241 | } 242 | 243 | - (void)sendMonitor { 244 | [[QAPMonitor getInstance] lock]; 245 | // 退到后台或者退出程序先保存到本地,t然后启动发送 246 | [self saveAllMonitorData]; 247 | [[QAPMonitor getInstance] sendMonitorToServer]; 248 | [[QAPMonitor getInstance] unlock]; 249 | } 250 | 251 | - (void)sendMonitorToServer { 252 | __weak typeof(self) weakSelf = self; 253 | [self.gMonitorCache earlyFile:^(NSDictionary *data) { 254 | if (data) { 255 | NSString *fileName = data.allKeys.firstObject; 256 | NSDictionary *fileData = data[fileName]; 257 | if ([fileData isKindOfClass:[NSDictionary class]]) { 258 | NSDictionary *dictionary = @{@"monitor":fileName}; 259 | if (weakSelf.uploadUrl) { 260 | [weakSelf uploadLoadData:fileData withFileName:(NSString *)fileName WithURLString:weakSelf.uploadUrl]; 261 | } 262 | else { 263 | [weakSelf qunarSendData:fileData customInfo:dictionary]; 264 | } 265 | } 266 | } 267 | }]; 268 | } 269 | 270 | - (void)uploadLoadData:(NSDictionary *)fileData withFileName:(NSString *)fileName WithURLString:(NSString *)urlString { 271 | urlString = [urlString stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLQueryAllowedCharacterSet]]; 272 | NSURL *url = [NSURL URLWithString:urlString]; 273 | NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url cachePolicy:NSURLRequestReloadIgnoringLocalCacheData timeoutInterval:60]; 274 | request.HTTPMethod = @"POST"; 275 | [request setValue:@"application/json" forHTTPHeaderField:@"Content-Type"]; 276 | if (fileData != nil) { 277 | NSData *data = [NSJSONSerialization dataWithJSONObject:fileData options:NSJSONWritingPrettyPrinted error:nil]; 278 | request.HTTPBody = data; 279 | } 280 | __weak typeof(self) weakSelf = self; 281 | NSURLSessionDataTask *dataTask = [[NSURLSession sharedSession] dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) { 282 | if (data && (error == nil)) { 283 | [[weakSelf gMonitorCache] deleteFile:fileName]; 284 | [weakSelf sendMonitorToServer]; 285 | } else { 286 | [[weakSelf gMonitorCache] sendFileErrorAddFile:fileName]; 287 | } 288 | }]; 289 | // 5.每一个任务默认都是挂起的,需要调用 resume 方法 290 | [dataTask resume]; 291 | } 292 | 293 | #pragma mark - qunar inner 294 | 295 | - (void)qunarSendData:(NSDictionary *)data customInfo:(NSDictionary *)info { 296 | Class classInstance = NSClassFromString(@"QAPMNetworkTask"); 297 | SEL sel = NSSelectorFromString(@"sendData:forInfo:"); 298 | if ([classInstance respondsToSelector:sel]) { 299 | [classInstance performSelector:sel withObject:data withObject:info]; 300 | } 301 | } 302 | 303 | // 获取网络请求回调 304 | + (void)networkCallback:(NSNumber *)status forInfo:(NSDictionary *)customInfo { 305 | BOOL success = status.boolValue; 306 | NSString *fileName = [customInfo objectForKey:@"monitor"]; 307 | if (fileName) { 308 | if (success) { 309 | [[[self getInstance] gMonitorCache] deleteFile:fileName]; 310 | dispatch_async(dispatch_get_global_queue(0, 0), ^{ 311 | [[self getInstance] sendMonitorToServer]; 312 | }); 313 | } 314 | else { 315 | [[[self getInstance] gMonitorCache] sendFileErrorAddFile:fileName]; 316 | } 317 | } 318 | } 319 | 320 | #pragma mark - qunar inner end 321 | 322 | 323 | - (NSDictionary *)commonParam { 324 | NSMutableDictionary *cParam = [[NSMutableDictionary alloc] init]; 325 | 326 | // 版本号 327 | NSString *vid = [[QAPMonitor getInstance] vid]; 328 | if (vid.length > 0) { 329 | [cParam setObject:vid forKey:@"vid"]; 330 | } 331 | 332 | // 程序号 333 | NSString *pid = [[QAPMonitor getInstance] pid]; 334 | if (pid.length > 0) { 335 | [cParam setObject:pid forKey:@"pid"]; 336 | } 337 | 338 | // 渠道号 339 | NSString *cid = [[QAPMonitor getInstance] cid]; 340 | if (cid.length > 0) { 341 | [cParam setObject:cid forKey:@"cid"]; 342 | } 343 | 344 | // 设备标识符 345 | NSString *uid = [[QAPMonitor getInstance] uid]; 346 | if (uid.length > 0) { 347 | [cParam setObject:uid forKey:@"uid"]; 348 | } 349 | CLLocation *location = [QAPManager location]; 350 | NSString *loc = location? [NSString stringWithFormat:@"%f,%f", location.coordinate.longitude, location.coordinate.latitude]:@"Unknown"; 351 | [cParam setObject:loc forKey:@"loc"]; 352 | 353 | NSString *mno = [QAPMonitor carrierCode]; 354 | if (mno) { 355 | [cParam setObject:mno forKey:@"mno"]; 356 | } 357 | else { 358 | [cParam setObject:@"Unknown" forKey:@"mno"]; 359 | } 360 | 361 | // 系统版本 362 | NSString *systemVersion = [[UIDevice currentDevice] systemVersion]; 363 | if(systemVersion != nil) { 364 | [cParam setObject:systemVersion forKey:@"osVersion"]; 365 | } 366 | 367 | // 获取Key 368 | NSDate *curDate = [NSDate date]; 369 | long long timeIntervalNow = (long long)([curDate timeIntervalSince1970] * 1000); 370 | 371 | NSString *curDateText = [NSString stringWithFormat:@"%lld", timeIntervalNow]; 372 | 373 | [cParam setObject:curDateText forKey:@"key"]; 374 | 375 | static NSString *platform = nil; 376 | if (platform == nil) { 377 | // 获取model 378 | size_t size; 379 | sysctlbyname("hw.machine", NULL, &size, NULL, 0); 380 | char *machine = malloc(size); 381 | sysctlbyname("hw.machine", machine, &size, NULL, 0); 382 | platform = [NSString stringWithUTF8String:machine]; 383 | free(machine); 384 | } 385 | if (platform) { 386 | [cParam setObject:platform forKey:@"model"]; 387 | } 388 | else { 389 | [cParam setObject:@"Unknown" forKey:@"model"]; 390 | } 391 | return cParam; 392 | } 393 | 394 | /// 4g/wifi...Unknown(未知网络),无网:(unconnect) 395 | + (NSString *)netType { 396 | Class reachCla = NSClassFromString(@"Reachability"); 397 | if (!reachCla) { 398 | return nil; 399 | } 400 | id curReach = [reachCla reachabilityForInternetConnection]; 401 | 402 | // 获得网络状态 403 | NetworkStatus netStatus = [curReach currentReachabilityStatus]; 404 | switch (netStatus) 405 | { 406 | case NotReachable: 407 | { 408 | return @"unconnect"; 409 | } 410 | break; 411 | 412 | case ReachableViaWWAN: 413 | { 414 | // 判断是否能够取得运营商 415 | Class telephoneNetWorkClass = (NSClassFromString(@"CTTelephonyNetworkInfo")); 416 | if (telephoneNetWorkClass != nil) { 417 | static CTTelephonyNetworkInfo * telephonyNetworkInfo = nil; 418 | if (telephonyNetworkInfo == nil) { 419 | telephonyNetworkInfo = [[CTTelephonyNetworkInfo alloc] init]; 420 | } 421 | 422 | if ([telephonyNetworkInfo respondsToSelector:@selector(currentRadioAccessTechnology)]) { 423 | // 7.0 系统的适配处理。 424 | return [NSString stringWithFormat:@"%@",telephonyNetworkInfo.currentRadioAccessTechnology]; 425 | } 426 | } 427 | 428 | return @"2g/3g"; 429 | } 430 | break; 431 | 432 | case ReachableViaWiFi: 433 | { 434 | return @"wifi"; 435 | } 436 | break; 437 | 438 | default: 439 | break; 440 | } 441 | 442 | return nil; 443 | } 444 | 445 | /// 运营商信息 446 | + (NSString *)carrierCode { 447 | // 判断是否能够取得运营商 448 | Class telephoneNetWorkClass = (NSClassFromString(@"CTTelephonyNetworkInfo")); 449 | if (telephoneNetWorkClass != nil) { 450 | static CTTelephonyNetworkInfo * telephonyNetworkInfo = nil; 451 | if (telephonyNetworkInfo == nil) { 452 | telephonyNetworkInfo = [[CTTelephonyNetworkInfo alloc] init]; 453 | } 454 | 455 | // 获得运营商的信息 456 | Class carrierClass = (NSClassFromString(@"CTCarrier")); 457 | if (carrierClass != nil) { 458 | CTCarrier *carrier = telephonyNetworkInfo.subscriberCellularProvider; 459 | 460 | // 移动运营商的mcc 和 mnc 461 | NSString * mobileCountryCode = [carrier mobileCountryCode]; 462 | NSString * mobileNetworkCode = [carrier mobileNetworkCode]; 463 | 464 | // 统计能够取到信息的运营商 465 | if ((mobileCountryCode != nil) && (mobileNetworkCode != nil)) { 466 | NSString *mobileCode = [[NSString alloc] initWithFormat:@"%@%@", mobileCountryCode, mobileNetworkCode]; 467 | return mobileCode; 468 | } 469 | } 470 | } 471 | 472 | return nil; 473 | } 474 | 475 | @end 476 | 477 | long long QAPMGetCurrentTime_millisecond(void) { 478 | long long time = (long long)([[NSDate date] timeIntervalSince1970] * 1000); 479 | return time; 480 | } 481 | 482 | -------------------------------------------------------------------------------- /QAPM/Monitor/Reachability.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (C) 2016 Apple Inc. All Rights Reserved. 3 | See LICENSE.txt for this sample’s licensing information 4 | 5 | Abstract: 6 | Basic demonstration of how to use the SystemConfiguration Reachablity APIs. 7 | */ 8 | 9 | #import 10 | #import 11 | #import 12 | 13 | 14 | typedef enum : NSInteger { 15 | NotReachable = 0, 16 | ReachableViaWiFi, 17 | ReachableViaWWAN 18 | } NetworkStatus; 19 | 20 | #pragma mark IPv6 Support 21 | //Reachability fully support IPv6. For full details, see ReadMe.md. 22 | 23 | 24 | extern NSString *kReachabilityChangedNotification; 25 | 26 | 27 | @interface Reachability : NSObject 28 | 29 | /*! 30 | * Use to check the reachability of a given host name. 31 | */ 32 | + (instancetype)reachabilityWithHostName:(NSString *)hostName; 33 | 34 | /*! 35 | * Use to check the reachability of a given IP address. 36 | */ 37 | + (instancetype)reachabilityWithAddress:(const struct sockaddr *)hostAddress; 38 | 39 | /*! 40 | * Checks whether the default route is available. Should be used by applications that do not connect to a particular host. 41 | */ 42 | + (instancetype)reachabilityForInternetConnection; 43 | 44 | 45 | #pragma mark reachabilityForLocalWiFi 46 | //reachabilityForLocalWiFi has been removed from the sample. See ReadMe.md for more information. 47 | //+ (instancetype)reachabilityForLocalWiFi; 48 | 49 | /*! 50 | * Start listening for reachability notifications on the current run loop. 51 | */ 52 | - (BOOL)startNotifier; 53 | - (void)stopNotifier; 54 | 55 | - (NetworkStatus)currentReachabilityStatus; 56 | 57 | /*! 58 | * WWAN may be available, but not active until a connection has been established. WiFi may require a connection for VPN on Demand. 59 | */ 60 | - (BOOL)connectionRequired; 61 | 62 | @end 63 | 64 | 65 | -------------------------------------------------------------------------------- /QAPM/Monitor/Reachability.m: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (C) 2016 Apple Inc. All Rights Reserved. 3 | See LICENSE.txt for this sample’s licensing information 4 | 5 | Abstract: 6 | Basic demonstration of how to use the SystemConfiguration Reachablity APIs. 7 | */ 8 | 9 | #import 10 | #import 11 | #import 12 | #import 13 | #import 14 | 15 | #import 16 | 17 | #import "Reachability.h" 18 | 19 | #pragma mark IPv6 Support 20 | //Reachability fully support IPv6. For full details, see ReadMe.md. 21 | 22 | 23 | NSString *kReachabilityChangedNotification = @"kNetworkReachabilityChangedNotification"; 24 | 25 | 26 | #pragma mark - Supporting functions 27 | 28 | #define kShouldPrintReachabilityFlags 1 29 | 30 | static void PrintReachabilityFlags(SCNetworkReachabilityFlags flags, const char* comment) 31 | { 32 | #if kShouldPrintReachabilityFlags 33 | 34 | NSLog(@"Reachability Flag Status: %c%c %c%c%c%c%c%c%c %s\n", 35 | (flags & kSCNetworkReachabilityFlagsIsWWAN) ? 'W' : '-', 36 | (flags & kSCNetworkReachabilityFlagsReachable) ? 'R' : '-', 37 | 38 | (flags & kSCNetworkReachabilityFlagsTransientConnection) ? 't' : '-', 39 | (flags & kSCNetworkReachabilityFlagsConnectionRequired) ? 'c' : '-', 40 | (flags & kSCNetworkReachabilityFlagsConnectionOnTraffic) ? 'C' : '-', 41 | (flags & kSCNetworkReachabilityFlagsInterventionRequired) ? 'i' : '-', 42 | (flags & kSCNetworkReachabilityFlagsConnectionOnDemand) ? 'D' : '-', 43 | (flags & kSCNetworkReachabilityFlagsIsLocalAddress) ? 'l' : '-', 44 | (flags & kSCNetworkReachabilityFlagsIsDirect) ? 'd' : '-', 45 | comment 46 | ); 47 | #endif 48 | } 49 | 50 | 51 | static void ReachabilityCallback(SCNetworkReachabilityRef target, SCNetworkReachabilityFlags flags, void* info) 52 | { 53 | #pragma unused (target, flags) 54 | NSCAssert(info != NULL, @"info was NULL in ReachabilityCallback"); 55 | NSCAssert([(__bridge NSObject*) info isKindOfClass: [Reachability class]], @"info was wrong class in ReachabilityCallback"); 56 | 57 | Reachability* noteObject = (__bridge Reachability *)info; 58 | // Post a notification to notify the client that the network reachability changed. 59 | [[NSNotificationCenter defaultCenter] postNotificationName: kReachabilityChangedNotification object: noteObject]; 60 | } 61 | 62 | 63 | #pragma mark - Reachability implementation 64 | 65 | @implementation Reachability 66 | { 67 | SCNetworkReachabilityRef _reachabilityRef; 68 | } 69 | 70 | + (instancetype)reachabilityWithHostName:(NSString *)hostName 71 | { 72 | Reachability* returnValue = NULL; 73 | SCNetworkReachabilityRef reachability = SCNetworkReachabilityCreateWithName(NULL, [hostName UTF8String]); 74 | if (reachability != NULL) 75 | { 76 | returnValue= [[self alloc] init]; 77 | if (returnValue != NULL) 78 | { 79 | returnValue->_reachabilityRef = reachability; 80 | } 81 | else { 82 | CFRelease(reachability); 83 | } 84 | } 85 | return returnValue; 86 | } 87 | 88 | 89 | + (instancetype)reachabilityWithAddress:(const struct sockaddr *)hostAddress 90 | { 91 | SCNetworkReachabilityRef reachability = SCNetworkReachabilityCreateWithAddress(kCFAllocatorDefault, hostAddress); 92 | 93 | Reachability* returnValue = NULL; 94 | 95 | if (reachability != NULL) 96 | { 97 | returnValue = [[self alloc] init]; 98 | if (returnValue != NULL) 99 | { 100 | returnValue->_reachabilityRef = reachability; 101 | } 102 | else { 103 | CFRelease(reachability); 104 | } 105 | } 106 | return returnValue; 107 | } 108 | 109 | 110 | + (instancetype)reachabilityForInternetConnection 111 | { 112 | struct sockaddr_in zeroAddress; 113 | bzero(&zeroAddress, sizeof(zeroAddress)); 114 | zeroAddress.sin_len = sizeof(zeroAddress); 115 | zeroAddress.sin_family = AF_INET; 116 | 117 | return [self reachabilityWithAddress: (const struct sockaddr *) &zeroAddress]; 118 | } 119 | 120 | #pragma mark reachabilityForLocalWiFi 121 | //reachabilityForLocalWiFi has been removed from the sample. See ReadMe.md for more information. 122 | //+ (instancetype)reachabilityForLocalWiFi 123 | 124 | 125 | 126 | #pragma mark - Start and stop notifier 127 | 128 | - (BOOL)startNotifier 129 | { 130 | BOOL returnValue = NO; 131 | SCNetworkReachabilityContext context = {0, (__bridge void *)(self), NULL, NULL, NULL}; 132 | 133 | if (SCNetworkReachabilitySetCallback(_reachabilityRef, ReachabilityCallback, &context)) 134 | { 135 | if (SCNetworkReachabilityScheduleWithRunLoop(_reachabilityRef, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode)) 136 | { 137 | returnValue = YES; 138 | } 139 | } 140 | 141 | return returnValue; 142 | } 143 | 144 | 145 | - (void)stopNotifier 146 | { 147 | if (_reachabilityRef != NULL) 148 | { 149 | SCNetworkReachabilityUnscheduleFromRunLoop(_reachabilityRef, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode); 150 | } 151 | } 152 | 153 | 154 | - (void)dealloc 155 | { 156 | [self stopNotifier]; 157 | if (_reachabilityRef != NULL) 158 | { 159 | CFRelease(_reachabilityRef); 160 | } 161 | } 162 | 163 | 164 | #pragma mark - Network Flag Handling 165 | 166 | - (NetworkStatus)networkStatusForFlags:(SCNetworkReachabilityFlags)flags 167 | { 168 | PrintReachabilityFlags(flags, "networkStatusForFlags"); 169 | if ((flags & kSCNetworkReachabilityFlagsReachable) == 0) 170 | { 171 | // The target host is not reachable. 172 | return NotReachable; 173 | } 174 | 175 | NetworkStatus returnValue = NotReachable; 176 | 177 | if ((flags & kSCNetworkReachabilityFlagsConnectionRequired) == 0) 178 | { 179 | /* 180 | If the target host is reachable and no connection is required then we'll assume (for now) that you're on Wi-Fi... 181 | */ 182 | returnValue = ReachableViaWiFi; 183 | } 184 | 185 | if ((((flags & kSCNetworkReachabilityFlagsConnectionOnDemand ) != 0) || 186 | (flags & kSCNetworkReachabilityFlagsConnectionOnTraffic) != 0)) 187 | { 188 | /* 189 | ... and the connection is on-demand (or on-traffic) if the calling application is using the CFSocketStream or higher APIs... 190 | */ 191 | 192 | if ((flags & kSCNetworkReachabilityFlagsInterventionRequired) == 0) 193 | { 194 | /* 195 | ... and no [user] intervention is needed... 196 | */ 197 | returnValue = ReachableViaWiFi; 198 | } 199 | } 200 | 201 | if ((flags & kSCNetworkReachabilityFlagsIsWWAN) == kSCNetworkReachabilityFlagsIsWWAN) 202 | { 203 | /* 204 | ... but WWAN connections are OK if the calling application is using the CFNetwork APIs. 205 | */ 206 | returnValue = ReachableViaWWAN; 207 | } 208 | 209 | return returnValue; 210 | } 211 | 212 | 213 | - (BOOL)connectionRequired 214 | { 215 | NSAssert(_reachabilityRef != NULL, @"connectionRequired called with NULL reachabilityRef"); 216 | SCNetworkReachabilityFlags flags; 217 | 218 | if (SCNetworkReachabilityGetFlags(_reachabilityRef, &flags)) 219 | { 220 | return (flags & kSCNetworkReachabilityFlagsConnectionRequired); 221 | } 222 | 223 | return NO; 224 | } 225 | 226 | 227 | - (NetworkStatus)currentReachabilityStatus 228 | { 229 | NSAssert(_reachabilityRef != NULL, @"currentNetworkStatus called with NULL SCNetworkReachabilityRef"); 230 | NetworkStatus returnValue = NotReachable; 231 | SCNetworkReachabilityFlags flags; 232 | 233 | if (SCNetworkReachabilityGetFlags(_reachabilityRef, &flags)) 234 | { 235 | returnValue = [self networkStatusForFlags:flags]; 236 | } 237 | 238 | return returnValue; 239 | } 240 | 241 | 242 | @end 243 | -------------------------------------------------------------------------------- /QAPM/Monitor/SystemInfo/QAPMSystemInfo.h: -------------------------------------------------------------------------------- 1 | // 2 | // QAPMSystemInfo.h 3 | // QAPMApp 4 | // 5 | // Created by mdd on 2019/1/28. 6 | // Copyright © 2019年 mdd. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "QCacheStorage.h" 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | @interface QAPMSystemInfo : NSObject 14 | + (instancetype)sharedInstance; 15 | @property (nonatomic, strong, readonly) QCacheStorage *sysMonitorCache; 16 | - (void)startMonitor; 17 | - (void)addArrayMonitor:(NSDictionary *)dict; 18 | @end 19 | 20 | NS_ASSUME_NONNULL_END 21 | -------------------------------------------------------------------------------- /QAPM/Monitor/SystemInfo/QAPMSystemInfo.m: -------------------------------------------------------------------------------- 1 | // 2 | // QAPMSystemInfo.m 3 | // QAPMApp 4 | // 5 | // Created by mdd on 2019/1/28. 6 | // Copyright © 2019年 mdd. All rights reserved. 7 | // 8 | 9 | #import "QAPMSystemInfo.h" 10 | 11 | #import 12 | #include 13 | #include 14 | 15 | #import "QAPFPSMonitor.h" 16 | #import "QAPManager.h" 17 | #import "QAPMonitor.h" 18 | #import "QCacheStorage.h" 19 | 20 | @interface QAPMSystemInfo () 21 | { 22 | CADisplayLink *_link; 23 | QAPFPSDroppedInfo _dropInfo; 24 | } 25 | @property (nonatomic, strong) NSTimer *timer; 26 | @property (nonatomic, strong) NSMutableArray *arrayMonitor; 27 | @end 28 | 29 | @implementation QAPMSystemInfo 30 | 31 | + (instancetype)sharedInstance { 32 | static QAPMSystemInfo *instance; 33 | static dispatch_once_t onceToken; 34 | dispatch_once(&onceToken, ^{ 35 | instance = [[self alloc] init]; 36 | }); 37 | return instance; 38 | } 39 | 40 | - (void)startMonitor { 41 | static dispatch_once_t onceToken; 42 | dispatch_once(&onceToken, ^{ 43 | self.timer = [[NSTimer alloc] initWithFireDate:[NSDate date] interval:1 target:self selector:@selector(timerTick) userInfo:nil repeats:YES]; 44 | [[NSRunLoop currentRunLoop] addTimer:self.timer forMode:(NSString *)kCFRunLoopCommonModes]; 45 | _link = [CADisplayLink displayLinkWithTarget:self selector:@selector(tick:)]; 46 | [_link addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes]; 47 | NSString *defaultDirectory = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)[0]; 48 | NSString *rlt = [defaultDirectory stringByAppendingPathComponent:@"QAPMSystemInfo"]; 49 | _sysMonitorCache = [QCacheStorage cacheStorageWithDir:rlt]; 50 | _sysMonitorCache.maxCacheSize = 50; 51 | _sysMonitorCache.maxFileSize = 1000; 52 | }); 53 | } 54 | 55 | - (void)tick:(CADisplayLink *)link { 56 | static NSTimeInterval lastSecond = 0; 57 | if (![QAPMonitor isForeground]) { 58 | [self clearDropedInfo]; 59 | return; 60 | } 61 | if (_dropInfo.lastTime == 0 || lastSecond == 0) { 62 | _dropInfo.lastTime = link.timestamp; 63 | lastSecond = link.timestamp; 64 | return; 65 | } 66 | 67 | NSTimeInterval delta = link.timestamp - lastSecond; 68 | 69 | static int refreshTime = 1; 70 | if (refreshTime == 1) { 71 | refreshTime = link.duration * 1000000; 72 | } 73 | int period = (link.timestamp - _dropInfo.lastTime) * 1000000; 74 | if (delta < 1) { 75 | _dropInfo.count++; 76 | _dropInfo.sumTime += period; 77 | int temp = period / refreshTime - 1; 78 | if (temp >= QAPFPS_DROPPED_FROZEN) { 79 | _dropInfo.dropLevel.frozen++; 80 | _dropInfo.dropSum.frozen += temp; 81 | } 82 | else if (temp >= QAPFPS_DROPPED_HIGH) { 83 | _dropInfo.dropLevel.high++; 84 | _dropInfo.dropSum.high += temp; 85 | } 86 | else if (temp >= QAPFPS_DROPPED_MIDDLE) { 87 | _dropInfo.dropLevel.middle++; 88 | _dropInfo.dropSum.middle += temp; 89 | } 90 | else if (temp >= QAPFPS_DROPPED_NORMAL) { 91 | _dropInfo.dropLevel.nomal++; 92 | _dropInfo.dropSum.nomal += temp; 93 | } 94 | else { 95 | _dropInfo.dropLevel.best++; 96 | _dropInfo.dropSum.best += (temp < 0 ? 0 : temp); 97 | } 98 | } 99 | else { 100 | // 发送 101 | [self recordFPSInfo]; 102 | lastSecond = link.timestamp; 103 | } 104 | _dropInfo.lastTime = link.timestamp; 105 | } 106 | 107 | - (void)recordFPSInfo { 108 | if (_dropInfo.sumTime == 0) { 109 | return; 110 | } 111 | long long fps = (_dropInfo.count * 1000000) / _dropInfo.sumTime; 112 | long long timeIntervalNow = (long long)([[NSDate date] timeIntervalSince1970] * 1000); 113 | NSString *lastTopVC = [QAPManager appearVC]; 114 | NSDictionary *dropDict = @{ 115 | @"action":@"fps", 116 | // 页面名称 117 | @"page":lastTopVC?:@"-1", 118 | // 页面丢帧占比分布 119 | @"dropLevel":@{ 120 | @"frozen":[@(_dropInfo.dropLevel.frozen) stringValue], 121 | @"high":[@(_dropInfo.dropLevel.high) stringValue], 122 | @"middle":[@(_dropInfo.dropLevel.middle) stringValue], 123 | @"normal":[@(_dropInfo.dropLevel.nomal) stringValue], 124 | @"best":[@(_dropInfo.dropLevel.best) stringValue] 125 | }, 126 | // 页面丢帧占比分布 127 | @"dropSum":@{ 128 | @"frozen":[@(_dropInfo.dropSum.frozen) stringValue], 129 | @"high":[@(_dropInfo.dropSum.high) stringValue], 130 | @"middle":[@(_dropInfo.dropSum.middle) stringValue], 131 | @"normal":[@(_dropInfo.dropSum.nomal) stringValue], 132 | @"best":[@(_dropInfo.dropSum.best) stringValue] 133 | }, 134 | @"fps":[@(fps) stringValue], 135 | @"count":[@(_dropInfo.count) stringValue], 136 | @"sumTime":[@(_dropInfo.sumTime / 1000) stringValue], 137 | @"logTime":[@(timeIntervalNow) stringValue] 138 | }; 139 | [self clearDropedInfo]; 140 | dispatch_async(dispatch_get_global_queue(0, 0), ^{ 141 | [self addArrayMonitor:dropDict]; 142 | }); 143 | } 144 | 145 | - (void)clearDropedInfo { 146 | _dropInfo.count = 0; 147 | _dropInfo.sumTime = 0; 148 | _dropInfo.lastTime = 0; 149 | 150 | _dropInfo.dropSum.frozen = 0; 151 | _dropInfo.dropSum.high = 0; 152 | _dropInfo.dropSum.middle = 0; 153 | _dropInfo.dropSum.nomal = 0; 154 | _dropInfo.dropSum.best = 0; 155 | 156 | _dropInfo.dropLevel.frozen = 0; 157 | _dropInfo.dropLevel.high = 0; 158 | _dropInfo.dropLevel.middle = 0; 159 | _dropInfo.dropLevel.nomal = 0; 160 | _dropInfo.dropLevel.best = 0; 161 | } 162 | 163 | - (NSMutableArray *)arrayMonitor { 164 | if (!_arrayMonitor) { 165 | _arrayMonitor = @[].mutableCopy; 166 | } 167 | return _arrayMonitor; 168 | } 169 | 170 | - (void)dealloc { 171 | [[NSNotificationCenter defaultCenter] removeObserver:self]; 172 | [_link invalidate]; 173 | _link = nil; 174 | } 175 | 176 | - (void)timerTick { 177 | static int count = 0; 178 | if (![QAPMonitor isForeground]) { 179 | count = 0; 180 | return; 181 | } 182 | NSDictionary *memData = [QAPMSystemInfo memoryMonitorData]; 183 | NSDictionary *cpuData = [QAPMSystemInfo cpuMonitorData]; 184 | NSDictionary *batData = nil; 185 | if (count++ == 60) { 186 | count = 0; 187 | batData = [QAPMSystemInfo batteryMonitorData]; 188 | } 189 | dispatch_async(dispatch_get_global_queue(0, 0), ^{ 190 | if (memData) { 191 | [self addArrayMonitor:memData]; 192 | } 193 | if (cpuData) { 194 | [self addArrayMonitor:cpuData]; 195 | } 196 | if (batData) { 197 | [self addArrayMonitor:batData]; 198 | } 199 | }); 200 | } 201 | 202 | static float cpu_usage(void) 203 | { 204 | kern_return_t kr; 205 | thread_array_t thread_list; 206 | mach_msg_type_number_t thread_count; 207 | thread_info_data_t thinfo; 208 | mach_msg_type_number_t thread_info_count; 209 | thread_basic_info_t basic_info_th; 210 | 211 | kr = task_threads(mach_task_self(), &thread_list, &thread_count); 212 | if (kr != KERN_SUCCESS) { 213 | return -1; 214 | } 215 | float cpu_usage = 0; 216 | 217 | for (int i = 0; i < thread_count; i++) 218 | { 219 | thread_info_count = THREAD_INFO_MAX; 220 | kr = thread_info(thread_list[i], THREAD_BASIC_INFO,(thread_info_t)thinfo, &thread_info_count); 221 | if (kr != KERN_SUCCESS) { 222 | return -1; 223 | } 224 | 225 | basic_info_th = (thread_basic_info_t)thinfo; 226 | 227 | if (!(basic_info_th->flags & TH_FLAGS_IDLE)) 228 | { 229 | cpu_usage += basic_info_th->cpu_usage; 230 | } 231 | } 232 | 233 | cpu_usage = cpu_usage / (float)TH_USAGE_SCALE * 100.0; 234 | 235 | vm_deallocate(mach_task_self(), (vm_offset_t)thread_list, thread_count * sizeof(thread_t)); 236 | 237 | return cpu_usage; 238 | } 239 | 240 | static bool VMStats(vm_statistics64_data_t* const vmStats, vm_size_t* const pageSize) 241 | { 242 | kern_return_t kr; 243 | const mach_port_t hostPort = mach_host_self(); 244 | 245 | if((kr = host_page_size(hostPort, pageSize)) != KERN_SUCCESS) 246 | { 247 | return false; 248 | } 249 | 250 | mach_msg_type_number_t hostSize = sizeof(*vmStats) / sizeof(natural_t); 251 | kr = host_statistics64(hostPort, HOST_VM_INFO64,(host_info64_t)vmStats, &hostSize); 252 | if(kr != KERN_SUCCESS) 253 | { 254 | return false; 255 | } 256 | 257 | return true; 258 | } 259 | 260 | static uint64_t getUsedMemory(void) { 261 | int64_t memoryUsageInByte = 0; 262 | task_vm_info_data_t vmInfo; 263 | mach_msg_type_number_t count = TASK_VM_INFO_COUNT; 264 | kern_return_t kernReturn = task_info(mach_task_self(), TASK_VM_INFO, (task_info_t) &vmInfo, &count); 265 | if (kernReturn != KERN_SUCCESS) { return NSNotFound; } 266 | memoryUsageInByte = (int64_t) vmInfo.phys_footprint; 267 | return memoryUsageInByte; 268 | } 269 | 270 | + (NSDictionary *)memoryMonitorData { 271 | uint64_t usedMem = -1; 272 | uint64_t freeMem = -1; 273 | uint64_t totalMem = -1; 274 | vm_statistics64_data_t vmStats; 275 | vm_size_t pageSize; 276 | if(VMStats(&vmStats, &pageSize)) { 277 | // usedMem = ((uint64_t)pageSize) * (vmStats.active_count + vmStats.wire_count) / 1024; 278 | freeMem = ((uint64_t)pageSize) * (vmStats.free_count + vmStats.inactive_count) >> 10; 279 | totalMem = [NSProcessInfo processInfo].physicalMemory >> 10; 280 | } 281 | usedMem = getUsedMemory() >> 10; 282 | long long timeIntervalNow = (long long)([[NSDate date] timeIntervalSince1970] * 1000); 283 | float usedRate = usedMem * 100.0 / totalMem; 284 | NSDictionary *memInfo = @{ 285 | @"action":@"memory", 286 | @"currentProcessName":@"-1", 287 | @"system":@{ 288 | @"used":[@(usedMem) stringValue], //设备已使用内存 单位K 289 | @"free":[@(freeMem) stringValue], //设备剩余内存 单位K 290 | @"total":[@(totalMem) stringValue] //设备总内存 单位K 291 | }, 292 | @"currentProcess":@{ 293 | @"max":@"-1", //当前应用分配最大的内存 单位K 294 | @"used":[@(usedMem) stringValue], //当前应用占用内存 单位K 295 | @"usedRate":[NSString stringWithFormat:@"%.2f",usedRate] //当前应用占用的内存比 单位% 296 | }, 297 | @"logTime":[@(timeIntervalNow) stringValue],//1970年以来的毫秒值 298 | }; 299 | return memInfo; 300 | } 301 | 302 | + (NSDictionary *)batteryMonitorData { 303 | static dispatch_once_t onceToken; 304 | dispatch_once(&onceToken, ^{ 305 | [UIDevice currentDevice].batteryMonitoringEnabled = YES; 306 | }); 307 | 308 | CGFloat battery = [[UIDevice currentDevice] batteryLevel]; 309 | BOOL isCharging = ([UIDevice currentDevice].batteryState == UIDeviceBatteryStateCharging); 310 | long long timeIntervalNow = (long long)([[NSDate date] timeIntervalSince1970] * 1000); 311 | NSDictionary *batteryData = @{ 312 | @"action":@"battery", 313 | @"currentBatteryRate":[NSString stringWithFormat:@"%.1f",battery], 314 | @"isCharging":[@(isCharging) stringValue], 315 | @"logTime":[@(timeIntervalNow) stringValue], 316 | }; 317 | return batteryData; 318 | } 319 | 320 | + (NSDictionary *)cpuMonitorData { 321 | float usagRate = cpu_usage(); 322 | long long timeIntervalNow = (long long)([[NSDate date] timeIntervalSince1970] * 1000); 323 | NSDictionary *cpuData = @{ 324 | @"action":@"cpu", 325 | @"currentProcessName":@"-1", 326 | @"usagRate":[NSString stringWithFormat:@"%.2f",usagRate], 327 | @"logTime":[@(timeIntervalNow) stringValue], 328 | }; 329 | return cpuData; 330 | } 331 | 332 | - (void)addArrayMonitor:(NSDictionary *)dict { 333 | @synchronized (self) { 334 | if (!dict) { 335 | return; 336 | } 337 | [self.arrayMonitor addObject:dict]; 338 | if (self.arrayMonitor.count >= 10) { 339 | NSDictionary *cparam = [[QAPMonitor getInstance] commonParam]; 340 | NSDictionary *data = @{@"c":cparam,@"b":[self arrayMonitor]}; 341 | self.arrayMonitor = @[].mutableCopy; 342 | [_sysMonitorCache saveData:data toFile:[QCacheStorage autoIncrementFileName]]; 343 | [self sendMonitorToServer]; 344 | 345 | } 346 | } 347 | } 348 | 349 | - (void)sendMonitorToServer { 350 | __weak typeof(self) weakSelf = self; 351 | [_sysMonitorCache earlyFile:^(NSDictionary *data) { 352 | if (data) { 353 | NSString *fileName = data.allKeys.firstObject; 354 | NSDictionary *fileData = data[fileName]; 355 | if ([fileData isKindOfClass:[NSDictionary class]]) { 356 | NSDictionary *dictionary = @{@"monitor":fileName,@"type":@"betaInner"}; 357 | [weakSelf qunarSendData:fileData customInfo:dictionary]; 358 | } 359 | } 360 | }]; 361 | } 362 | 363 | - (void)qunarSendData:(NSDictionary *)data customInfo:(NSDictionary *)info { 364 | Class classInstance = NSClassFromString(@"QAPMNetworkTask"); 365 | SEL sel = NSSelectorFromString(@"sendData:forInfo:"); 366 | if ([classInstance respondsToSelector:sel]) { 367 | [classInstance performSelector:sel withObject:data withObject:info]; 368 | } 369 | } 370 | 371 | // 获取网络请求回调 372 | + (void)networkCallback:(NSNumber *)status forInfo:(NSDictionary *)customInfo { 373 | BOOL success = status.boolValue; 374 | NSString *fileName = [customInfo objectForKey:@"monitor"]; 375 | if (fileName) { 376 | if (success) { 377 | [[[self sharedInstance] sysMonitorCache] deleteFile:fileName]; 378 | dispatch_async(dispatch_get_global_queue(0, 0), ^{ 379 | [[self sharedInstance] sendMonitorToServer]; 380 | }); 381 | } 382 | else { 383 | [[[self sharedInstance] sysMonitorCache] sendFileErrorAddFile:fileName]; 384 | } 385 | } 386 | } 387 | 388 | @end 389 | -------------------------------------------------------------------------------- /QAPM/Monitor/URLConnection/NSURLConnection+QunarAPM.h: -------------------------------------------------------------------------------- 1 | // 2 | // NSURLConnection+QunarAPM.h 3 | // QunarAPM 4 | // 5 | // Created by Quanquan.zhang on 15/9/22. 6 | // Copyright © 2015年 Qunar. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | 12 | @interface NSURLConnection (QunarAPM) 13 | 14 | + (void)QAPMSetupPerformanceMonitoring; 15 | 16 | @end 17 | -------------------------------------------------------------------------------- /QAPM/Monitor/URLConnection/NSURLConnection+QunarAPM.m: -------------------------------------------------------------------------------- 1 | // 2 | // NSURLConnection+QunarAPM.m 3 | // QunarAPM 4 | // 5 | // Created by Quanquan.zhang on 15/9/22. 6 | // Copyright © 2015年 Qunar. All rights reserved. 7 | // 8 | 9 | #import "NSURLConnection+QunarAPM.h" 10 | #import "QAPMURLConnectionDelegateAgent.h" 11 | #import "QAPMNetworkEntry.h" 12 | #import "QAPMonitor.h" 13 | #import 14 | 15 | @implementation NSURLConnection (QunarAPM) 16 | 17 | #pragma mark - Class Methods 18 | 19 | + (void)QAPMSendAsynchronousRequest:(NSURLRequest *) request 20 | queue:(NSOperationQueue *) queue 21 | completionHandler:(void (^)(NSURLResponse * _Nullable response, NSData * _Nullable data, NSError * _Nullable connectionError)) handler 22 | { 23 | request = [QAPMNetworkEntry addCustomHeaderFieldWithURLRequest:request]; 24 | 25 | QAPMNetworkEntry *networkEntry = [[QAPMNetworkEntry alloc] initWithRequest:request]; 26 | [networkEntry recordStartTime]; 27 | 28 | [self QAPMSendAsynchronousRequest:request queue:queue completionHandler:^(NSURLResponse * _Nullable response, NSData * _Nullable data, NSError * _Nullable connectionError) { 29 | 30 | if (response) { 31 | [networkEntry recordResponse:response]; 32 | } 33 | 34 | [networkEntry recordEndTime]; 35 | 36 | if (data) { 37 | networkEntry.responseSize = data.length; 38 | } 39 | 40 | if (connectionError) { 41 | [networkEntry recordError:connectionError]; 42 | } 43 | 44 | [networkEntry debugPrint]; 45 | 46 | NSDictionary *dictEntry = [networkEntry convertToDictionary]; 47 | if (dictEntry) { 48 | [QAPMonitor addMonitor:dictEntry withType:eNetworkTaskMonitor]; 49 | } 50 | 51 | handler(response, data, connectionError); 52 | }]; 53 | } 54 | 55 | + (nullable NSData *)QAPMSendSynchronousRequest:(NSURLRequest *)request 56 | returningResponse:(NSURLResponse * _Nullable * _Nullable)response 57 | error:(NSError **)error 58 | { 59 | NSError *agentError = nil; 60 | NSData *data = nil; 61 | 62 | request = [QAPMNetworkEntry addCustomHeaderFieldWithURLRequest:request]; 63 | 64 | QAPMNetworkEntry *networkEntry = [[QAPMNetworkEntry alloc] initWithRequest:request]; 65 | [networkEntry recordStartTime]; 66 | 67 | if (error) { 68 | data = [self QAPMSendSynchronousRequest:request returningResponse:response error:error]; 69 | } else { 70 | data = [self QAPMSendSynchronousRequest:request returningResponse:response error:&agentError]; 71 | } 72 | 73 | if (response && *response) { 74 | [networkEntry recordResponse:*response]; 75 | } 76 | 77 | [networkEntry recordEndTime]; 78 | 79 | if (data) { 80 | networkEntry.responseSize = data.length; 81 | } 82 | 83 | if (error && *error) { 84 | [networkEntry recordError:*error]; 85 | } 86 | 87 | [networkEntry debugPrint]; 88 | NSDictionary *dictEntry = [networkEntry convertToDictionary]; 89 | if (dictEntry) { 90 | [QAPMonitor addMonitor:dictEntry withType:eNetworkTaskMonitor]; 91 | } 92 | 93 | return data; 94 | } 95 | 96 | + (nullable NSURLConnection*)QAPMConnectionWithRequest:(NSURLRequest *)request delegate:(nullable id)delegate 97 | { 98 | request = [QAPMNetworkEntry addCustomHeaderFieldWithURLRequest:request]; 99 | QAPMURLConnectionDelegateAgent *agent = [QAPMURLConnectionDelegateAgent agentWithTarget:delegate request:request]; 100 | return [self QAPMConnectionWithRequest:request delegate:agent]; 101 | } 102 | 103 | #pragma mark - Instance Methods 104 | 105 | - (nullable instancetype)QAPMInitWithRequest:(NSURLRequest *)request delegate:(nullable id)delegate startImmediately:(BOOL)startImmediately 106 | { 107 | request = [QAPMNetworkEntry addCustomHeaderFieldWithURLRequest:request]; 108 | QAPMURLConnectionDelegateAgent *agent = [QAPMURLConnectionDelegateAgent agentWithTarget:delegate request:request]; 109 | return [self QAPMInitWithRequest:request delegate:agent startImmediately:startImmediately]; 110 | } 111 | 112 | - (nullable instancetype)QAPMInitWithRequest:(NSURLRequest *)request delegate:(nullable id)delegate 113 | { 114 | request = [QAPMNetworkEntry addCustomHeaderFieldWithURLRequest:request]; 115 | QAPMURLConnectionDelegateAgent *agent = [QAPMURLConnectionDelegateAgent agentWithTarget:delegate request:request]; 116 | return [self QAPMInitWithRequest:request delegate:agent]; 117 | } 118 | 119 | + (void)QAPMSetupPerformanceMonitoring 120 | { 121 | // Class methods 122 | [self QAPMSwizzleClassMethod:self original:@selector(sendAsynchronousRequest:queue:completionHandler:) swizzled:@selector(QAPMSendAsynchronousRequest:queue:completionHandler:)]; 123 | [self QAPMSwizzleClassMethod:self original:@selector(sendSynchronousRequest:returningResponse:error:) swizzled:@selector(QAPMSendSynchronousRequest:returningResponse:error:)]; 124 | [self QAPMSwizzleClassMethod:self original:@selector(connectionWithRequest:delegate:) swizzled:@selector(QAPMConnectionWithRequest:delegate:)]; 125 | 126 | // Instance methods 127 | [self QAPMSwizzleInstanceMethod:[self class] original:@selector(initWithRequest:delegate:startImmediately:) swizzled:@selector(QAPMInitWithRequest:delegate:startImmediately:)]; 128 | [self QAPMSwizzleInstanceMethod:[self class] original:@selector(initWithRequest:delegate:) swizzled:@selector(QAPMInitWithRequest:delegate:)]; 129 | } 130 | 131 | //==================================================================================================== 132 | // 133 | #pragma mark - 134 | #pragma mark Method 135 | 136 | + (void)QAPMSwizzleClassMethod:(Class)target original:(SEL)originalSelector swizzled:(SEL)swizzledSelector 137 | { 138 | Class meta = object_getClass((id)target); 139 | 140 | Method originMethod = class_getClassMethod(target, originalSelector); 141 | Method swizzledMethod = class_getClassMethod(target, swizzledSelector); 142 | 143 | if (class_addMethod(meta, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod))) 144 | { 145 | class_replaceMethod(meta, swizzledSelector, method_getImplementation(originMethod), method_getTypeEncoding(originMethod)); 146 | } 147 | else 148 | { 149 | method_exchangeImplementations(originMethod, swizzledMethod); 150 | } 151 | } 152 | 153 | + (void)QAPMSwizzleInstanceMethod:(Class)target original:(SEL)originalSelector swizzled:(SEL)swizzledSelector 154 | { 155 | Method originMethod = class_getInstanceMethod(target, originalSelector); 156 | Method swizzledMethod = class_getInstanceMethod(target, swizzledSelector); 157 | 158 | if (class_addMethod(target, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod))) 159 | { 160 | class_replaceMethod(target, swizzledSelector, method_getImplementation(originMethod), method_getTypeEncoding(originMethod)); 161 | } 162 | else 163 | { 164 | method_exchangeImplementations(originMethod, swizzledMethod); 165 | } 166 | } 167 | 168 | @end 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | -------------------------------------------------------------------------------- /QAPM/Monitor/URLConnection/NSURLSession+QunarAPM.h: -------------------------------------------------------------------------------- 1 | // 2 | // NSURLSession+QunarAPM.h 3 | // QunarAPM 4 | // 5 | // Created by Quanquan.zhang on 15/9/23. 6 | // Copyright © 2015年 Qunar. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | 12 | @interface NSURLSession (QunarAPM) 13 | 14 | + (void)QAPMSetupPerformanceMonitoring; 15 | 16 | @end 17 | -------------------------------------------------------------------------------- /QAPM/Monitor/URLConnection/NSURLSession+QunarAPM.m: -------------------------------------------------------------------------------- 1 | // 2 | // NSURLSession+QunarAPM.m 3 | // QunarAPM 4 | // 5 | // Created by Quanquan.zhang on 15/9/23. 6 | // Copyright © 2015年 Qunar. All rights reserved. 7 | // 8 | 9 | #include 10 | 11 | #import "NSURLSession+QunarAPM.h" 12 | #import "QAPMNSURLSessionDelegateAgent.h" 13 | #import "QAPMNSURLSessionTaskAgent.h" 14 | #import "QAPMNetworkEntry.h" 15 | 16 | 17 | typedef void (^DataTaskCompletionBlock)(NSData*,NSURLResponse*,NSError*); 18 | 19 | NSURLSessionDataTask* (*origin_DataTaskWithRequestAndCompletionHandler)(id, SEL, NSURLRequest*, DataTaskCompletionBlock) = NULL; 20 | void (*origin_NSCFLocalDataTask_resume)(id,SEL) = NULL; 21 | void (*origin_NSURLSessionTask_resume)(id,SEL) = NULL; 22 | 23 | 24 | #pragma mark - NSURLSessionTask 25 | 26 | static void QAPM_NSCFLocalDataTask_Resume(id self, SEL _cmd) 27 | { 28 | QAPMNSURLSessionTaskAgent *agent = [QAPMNSURLSessionTaskAgent agentForTask:self]; 29 | if (agent) { 30 | [agent.networkEntry recordStartTime]; 31 | } 32 | 33 | origin_NSCFLocalDataTask_resume(self, _cmd); 34 | } 35 | 36 | static void QAPM_NSURLSessionTask_Resume(id self, SEL _cmd) 37 | { 38 | QAPMNSURLSessionTaskAgent *agent = [QAPMNSURLSessionTaskAgent agentForTask:self]; 39 | if (agent) { 40 | [agent.networkEntry recordStartTime]; 41 | } 42 | 43 | origin_NSURLSessionTask_resume(self, _cmd); 44 | } 45 | 46 | 47 | #pragma mark - NSURLSession 48 | 49 | static NSURLSessionTask *QAPMN_NSURLSession_DataTaskWithRequestAndCompletionHandler(id self, SEL _cmd, NSURLRequest *request, DataTaskCompletionBlock completionHandler) 50 | { 51 | NSURLSessionDataTask *task = nil; 52 | NSString *aID = [[NSUUID UUID] UUIDString]; 53 | 54 | request = [QAPMNetworkEntry addCustomHeaderFieldWithURLRequest:request]; 55 | 56 | if (completionHandler) { 57 | task = origin_DataTaskWithRequestAndCompletionHandler(self, _cmd, request, ^(NSData *data, 58 | NSURLResponse *response, 59 | NSError *error) { 60 | QAPMNSURLSessionTaskAgent *agent = [QAPMNSURLSessionTaskAgent agentForID:aID]; 61 | if (agent) { 62 | QAPMNetworkEntry *networkEntry = agent.networkEntry; 63 | 64 | if (response) { 65 | [networkEntry recordResponse:response]; 66 | } 67 | 68 | networkEntry.responseSize = data.length; 69 | 70 | if (error) { 71 | [networkEntry recordError:error]; 72 | } 73 | 74 | [agent.networkEntry recordEndTime]; 75 | [agent finish]; 76 | 77 | [QAPMNSURLSessionTaskAgent removeAgentForID:aID]; 78 | } 79 | 80 | completionHandler(data, response, error); 81 | }); 82 | } else { 83 | task = origin_DataTaskWithRequestAndCompletionHandler(self, _cmd, request, completionHandler); 84 | } 85 | 86 | QAPMNSURLSessionTaskAgent *agent = [[QAPMNSURLSessionTaskAgent alloc] init]; 87 | [agent.networkEntry recordRequest:request]; 88 | [QAPMNSURLSessionTaskAgent registerAgent:agent forID:aID]; 89 | [QAPMNSURLSessionTaskAgent registerID:aID forTask:task]; 90 | 91 | return task; 92 | } 93 | 94 | static void QAPMSwizzleClassMethod(Class target, SEL originalSelector, SEL swizzledSelector) 95 | { 96 | Class meta = object_getClass((id)target); 97 | 98 | Method originMethod = class_getClassMethod(target, originalSelector); 99 | Method swizzledMethod = class_getClassMethod(target, swizzledSelector); 100 | 101 | if (class_addMethod(meta, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod))) { 102 | class_replaceMethod(meta, swizzledSelector, method_getImplementation(originMethod), method_getTypeEncoding(originMethod)); 103 | } else { 104 | method_exchangeImplementations(originMethod, swizzledMethod); 105 | } 106 | } 107 | 108 | @implementation NSURLSession (QunarAPM) 109 | 110 | //- sessionWithConfiguration:delegate:delegateQueue: 111 | 112 | #pragma mark - Class methods 113 | 114 | + (NSURLSession *)QAPMSessionWithConfiguration:(NSURLSessionConfiguration *)configuration delegate:(nullable id )delegate delegateQueue:(nullable NSOperationQueue *)queue { 115 | QAPMNSURLSessionDelegateAgent *agent = [QAPMNSURLSessionDelegateAgent agentWithTarget:delegate]; 116 | return [self QAPMSessionWithConfiguration:configuration delegate:(id)agent delegateQueue:queue]; 117 | } 118 | 119 | 120 | #pragma mark - Instance methods 121 | /// urlsession 监控初始化 122 | + (void)QAPMSetupPerformanceMonitoring { 123 | // iOS 6 compatible 124 | if (![NSURLSession class]) { 125 | return; 126 | } 127 | 128 | // Class method 129 | QAPMSwizzleClassMethod(self, 130 | @selector(sessionWithConfiguration:delegate:delegateQueue:), 131 | @selector(QAPMSessionWithConfiguration:delegate:delegateQueue:)); 132 | 133 | Class claObj; 134 | SEL selMethod; 135 | IMP impOverrideMethod; 136 | Method origMethod; 137 | 138 | // NSURLSession dataTaskWithRequest:completionHandler: 139 | claObj = NSClassFromString(@"__NSCFURLSession"); // iOS 7 140 | if (!claObj) { 141 | claObj = NSClassFromString(@"__NSURLSessionLocal"); // iOS 8+ 142 | } 143 | selMethod = @selector(dataTaskWithRequest:completionHandler:); 144 | impOverrideMethod = (IMP)QAPMN_NSURLSession_DataTaskWithRequestAndCompletionHandler; 145 | origMethod = class_getInstanceMethod(claObj, selMethod); 146 | origin_DataTaskWithRequestAndCompletionHandler = (void *)method_getImplementation(origMethod); 147 | if (origin_DataTaskWithRequestAndCompletionHandler) { 148 | method_setImplementation(origMethod, impOverrideMethod); 149 | } 150 | 151 | // NSURLSessionTask resume 152 | claObj = NSClassFromString(@"NSURLSessionTask"); 153 | selMethod = @selector(resume); 154 | impOverrideMethod = (IMP)QAPM_NSURLSessionTask_Resume; 155 | origMethod = class_getInstanceMethod(claObj, selMethod); 156 | origin_NSURLSessionTask_resume = (void *)method_getImplementation(origMethod); 157 | if (origin_NSURLSessionTask_resume) { 158 | method_setImplementation(origMethod, impOverrideMethod); 159 | } 160 | 161 | // __NSCFLocalDataTask resume 162 | claObj = NSClassFromString(@"__NSCFLocalDataTask"); 163 | selMethod = @selector(resume); 164 | impOverrideMethod = (IMP)QAPM_NSCFLocalDataTask_Resume; 165 | origMethod = class_getInstanceMethod(claObj, selMethod); 166 | origin_NSCFLocalDataTask_resume = (void *)method_getImplementation(origMethod); 167 | if (origin_NSCFLocalDataTask_resume) { 168 | method_setImplementation(origMethod, impOverrideMethod); 169 | } 170 | } 171 | 172 | @end 173 | -------------------------------------------------------------------------------- /QAPM/Monitor/URLConnection/QAPMNSURLSessionDelegateAgent.h: -------------------------------------------------------------------------------- 1 | // 2 | // QAPMNSURLSessionDelegateAgent.h 3 | // QunarAPM 4 | // 5 | // Created by Quanquan.zhang on 15/9/23. 6 | // Copyright © 2015年 Qunar. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | 12 | @interface QAPMNSURLSessionDelegateAgent : NSObject 13 | 14 | @property (nonatomic, strong, nullable) id target; 15 | 16 | + (nullable instancetype)agentWithTarget:(nullable id)target; 17 | 18 | @end 19 | -------------------------------------------------------------------------------- /QAPM/Monitor/URLConnection/QAPMNSURLSessionDelegateAgent.m: -------------------------------------------------------------------------------- 1 | // 2 | // QAPMNSURLSessionDelegateAgent.m 3 | // QunarAPM 4 | // 5 | // Created by Quanquan.zhang on 15/9/23. 6 | // Copyright © 2015年 Qunar. All rights reserved. 7 | // 8 | 9 | #import "QAPMNSURLSessionDelegateAgent.h" 10 | #import "QAPMNSURLSessionTaskAgent.h" 11 | #import "QAPMNetworkEntry.h" 12 | #import 13 | 14 | @implementation QAPMNSURLSessionDelegateAgent 15 | 16 | + (nullable instancetype)agentWithTarget:(nullable id)target 17 | { 18 | QAPMNSURLSessionDelegateAgent *agent = [[QAPMNSURLSessionDelegateAgent alloc] init]; 19 | agent.target = target; 20 | return agent; 21 | } 22 | 23 | 24 | #define TARGET_RESPONDS_TO_CMD (_target && [_target respondsToSelector:_cmd]) 25 | 26 | #pragma mark - NSURLSessionDelegate 27 | 28 | - (void)URLSession:(NSURLSession *)session didBecomeInvalidWithError:(nullable NSError *)error; 29 | { 30 | if (TARGET_RESPONDS_TO_CMD) { 31 | [_target URLSession:session didBecomeInvalidWithError:error]; 32 | } 33 | } 34 | 35 | - (void)URLSession:(NSURLSession *)session didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge 36 | completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential * _Nullable credential))completionHandler; 37 | { 38 | if (TARGET_RESPONDS_TO_CMD) { 39 | [_target URLSession:session didReceiveChallenge:challenge completionHandler:completionHandler]; 40 | } else { 41 | // Default handling 42 | completionHandler(NSURLSessionAuthChallengePerformDefaultHandling, NULL); 43 | } 44 | } 45 | 46 | - (void)URLSessionDidFinishEventsForBackgroundURLSession:(NSURLSession *)session NS_AVAILABLE_IOS(7_0); 47 | { 48 | if (TARGET_RESPONDS_TO_CMD) { 49 | [_target URLSessionDidFinishEventsForBackgroundURLSession:session]; 50 | } 51 | } 52 | 53 | 54 | #pragma mark - NSURLSessionTaskDelegate 55 | 56 | - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task 57 | willPerformHTTPRedirection:(NSHTTPURLResponse *)response 58 | newRequest:(NSURLRequest *)request 59 | completionHandler:(void (^)(NSURLRequest * _Nullable))completionHandler 60 | { 61 | if (TARGET_RESPONDS_TO_CMD) { 62 | [_target URLSession:session task:task willPerformHTTPRedirection:response newRequest:request completionHandler:completionHandler]; 63 | } else { 64 | completionHandler(request); 65 | } 66 | } 67 | 68 | - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task 69 | didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge 70 | completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential * _Nullable credential))completionHandler 71 | { 72 | if (TARGET_RESPONDS_TO_CMD) { 73 | [_target URLSession:session task:task didReceiveChallenge:challenge completionHandler:completionHandler]; 74 | } else { 75 | completionHandler(NSURLSessionAuthChallengePerformDefaultHandling, nil); 76 | } 77 | } 78 | 79 | - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task 80 | needNewBodyStream:(void (^)(NSInputStream * _Nullable bodyStream))completionHandler 81 | { 82 | if (TARGET_RESPONDS_TO_CMD) { 83 | [_target URLSession:session task:task needNewBodyStream:completionHandler]; 84 | } else { 85 | NSInputStream* inputStream = nil; 86 | 87 | if (task.originalRequest.HTTPBodyStream && 88 | [task.originalRequest.HTTPBodyStream conformsToProtocol:@protocol(NSCopying)]) 89 | { 90 | inputStream = [task.originalRequest.HTTPBodyStream copy]; 91 | } 92 | 93 | completionHandler(inputStream); 94 | } 95 | } 96 | 97 | - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task 98 | didSendBodyData:(int64_t)bytesSent 99 | totalBytesSent:(int64_t)totalBytesSent 100 | totalBytesExpectedToSend:(int64_t)totalBytesExpectedToSend 101 | { 102 | if (TARGET_RESPONDS_TO_CMD) { 103 | [_target URLSession:session task:task didSendBodyData:bytesSent totalBytesSent:totalBytesSent totalBytesExpectedToSend:totalBytesExpectedToSend]; 104 | } 105 | } 106 | 107 | - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task 108 | didCompleteWithError:(nullable NSError *)error 109 | { 110 | QAPMNSURLSessionTaskAgent *agent = [QAPMNSURLSessionTaskAgent agentForTask:task]; 111 | if (agent) { 112 | QAPMNetworkEntry *entry = agent.networkEntry; 113 | [entry recordError:error]; 114 | [entry recordEndTime]; 115 | 116 | [agent finish]; 117 | [QAPMNSURLSessionTaskAgent removeAgentForTask:task]; 118 | } 119 | 120 | if (TARGET_RESPONDS_TO_CMD) { 121 | [_target URLSession:session task:task didCompleteWithError:error]; 122 | } 123 | } 124 | 125 | 126 | #pragma mark - NSURLSessionDataDelegate 127 | 128 | - (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask 129 | didReceiveResponse:(NSURLResponse *)response 130 | completionHandler:(void (^)(NSURLSessionResponseDisposition disposition))completionHandler 131 | { 132 | if (response) { 133 | QAPMNSURLSessionTaskAgent *agent = [QAPMNSURLSessionTaskAgent agentForTask:dataTask]; 134 | if (agent) { 135 | [agent.networkEntry recordResponse:response]; 136 | } 137 | } 138 | 139 | if (TARGET_RESPONDS_TO_CMD) { 140 | [_target URLSession:session dataTask:dataTask didReceiveResponse:response completionHandler:completionHandler]; 141 | } else { 142 | completionHandler(NSURLSessionResponseAllow); 143 | } 144 | } 145 | 146 | - (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask 147 | didBecomeDownloadTask:(NSURLSessionDownloadTask *)downloadTask 148 | { 149 | [QAPMNSURLSessionTaskAgent removeAgentForTask:dataTask]; 150 | 151 | if (TARGET_RESPONDS_TO_CMD) { 152 | [_target URLSession:session dataTask:dataTask didBecomeDownloadTask:downloadTask]; 153 | } 154 | } 155 | 156 | - (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask 157 | didBecomeStreamTask:(NSURLSessionStreamTask *)streamTask 158 | { 159 | if (TARGET_RESPONDS_TO_CMD) { 160 | [_target URLSession:session dataTask:dataTask didBecomeStreamTask:streamTask]; 161 | } 162 | } 163 | 164 | - (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask 165 | didReceiveData:(NSData *)data 166 | { 167 | QAPMNSURLSessionTaskAgent *agent = [QAPMNSURLSessionTaskAgent agentForTask:dataTask]; 168 | if (agent) { 169 | agent.networkEntry.responseSize = data.length; 170 | } 171 | 172 | if (TARGET_RESPONDS_TO_CMD) { 173 | [_target URLSession:session dataTask:dataTask didReceiveData:data]; 174 | } 175 | } 176 | 177 | - (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask 178 | willCacheResponse:(NSCachedURLResponse *)proposedResponse 179 | completionHandler:(void (^)(NSCachedURLResponse * _Nullable cachedResponse))completionHandler 180 | { 181 | if (TARGET_RESPONDS_TO_CMD) { 182 | [_target URLSession:session dataTask:dataTask willCacheResponse:proposedResponse completionHandler:completionHandler]; 183 | } else { 184 | completionHandler(proposedResponse); 185 | } 186 | } 187 | 188 | 189 | #pragma mark - NSURLSessionDownloadDelegate 190 | 191 | - (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask 192 | didFinishDownloadingToURL:(NSURL *)location 193 | { 194 | if (TARGET_RESPONDS_TO_CMD) { 195 | [_target URLSession:session downloadTask:downloadTask didFinishDownloadingToURL:location]; 196 | } 197 | } 198 | 199 | - (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask 200 | didWriteData:(int64_t)bytesWritten 201 | totalBytesWritten:(int64_t)totalBytesWritten 202 | totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite 203 | { 204 | if (TARGET_RESPONDS_TO_CMD) { 205 | [_target URLSession:session downloadTask:downloadTask didWriteData:bytesWritten totalBytesWritten:totalBytesWritten totalBytesExpectedToWrite:totalBytesExpectedToWrite]; 206 | } 207 | } 208 | 209 | - (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask 210 | didResumeAtOffset:(int64_t)fileOffset 211 | expectedTotalBytes:(int64_t)expectedTotalBytes 212 | { 213 | if (TARGET_RESPONDS_TO_CMD) { 214 | [_target URLSession:session downloadTask:downloadTask didResumeAtOffset:fileOffset expectedTotalBytes:expectedTotalBytes]; 215 | } 216 | } 217 | 218 | 219 | #pragma mark - NSURLSessionStreamDelegate 220 | 221 | - (void)URLSession:(NSURLSession *)session readClosedForStreamTask:(NSURLSessionStreamTask *)streamTask 222 | { 223 | if (TARGET_RESPONDS_TO_CMD) { 224 | [_target URLSession:session readClosedForStreamTask:streamTask]; 225 | } 226 | } 227 | 228 | - (void)URLSession:(NSURLSession *)session writeClosedForStreamTask:(NSURLSessionStreamTask *)streamTask 229 | { 230 | if (TARGET_RESPONDS_TO_CMD) { 231 | [_target URLSession:session writeClosedForStreamTask:streamTask]; 232 | } 233 | } 234 | 235 | - (void)URLSession:(NSURLSession *)session betterRouteDiscoveredForStreamTask:(NSURLSessionStreamTask *)streamTask 236 | { 237 | if (TARGET_RESPONDS_TO_CMD) { 238 | [_target URLSession:session betterRouteDiscoveredForStreamTask:streamTask]; 239 | } 240 | } 241 | 242 | - (void)URLSession:(NSURLSession *)session streamTask:(NSURLSessionStreamTask *)streamTask 243 | didBecomeInputStream:(NSInputStream *)inputStream 244 | outputStream:(NSOutputStream *)outputStream 245 | { 246 | if (TARGET_RESPONDS_TO_CMD) { 247 | [_target URLSession:session streamTask:streamTask didBecomeInputStream:inputStream outputStream:outputStream]; 248 | } 249 | } 250 | /// iOS10 以上才会被调用;另外这种会漏掉 [NSURLSession sharedSession] 的请求 251 | - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didFinishCollectingMetrics:(NSURLSessionTaskMetrics *)metrics { 252 | QAPMNSURLSessionTaskAgent *agent = [QAPMNSURLSessionTaskAgent agentForTask:task]; 253 | if (agent) { 254 | NSURLSessionTaskTransactionMetrics *transactionMetrics = nil; 255 | for (NSURLSessionTaskTransactionMetrics *transMetric in metrics.transactionMetrics) { 256 | 257 | // 只记录通过网络加载的 258 | if (transMetric.resourceFetchType == NSURLSessionTaskMetricsResourceFetchTypeNetworkLoad) { 259 | transactionMetrics = transMetric; 260 | } 261 | } 262 | if (transactionMetrics) { 263 | QAPMMetricsTimingData *timingData = [[QAPMMetricsTimingData alloc] init]; 264 | unsigned int count ,i; 265 | objc_property_t *propertyArray = class_copyPropertyList([timingData class], &count); 266 | for (i = 0; i < count; i++) { 267 | objc_property_t property = propertyArray[i]; 268 | NSString *proKey = [NSString stringWithCString:property_getName(property) encoding:NSUTF8StringEncoding]; 269 | id proValue = [transactionMetrics valueForKey:proKey]; 270 | if (proValue) { 271 | [timingData setValue:proValue forKey:proKey]; 272 | } 273 | } 274 | free(propertyArray); 275 | agent.networkEntry.timingData = timingData; 276 | } 277 | } 278 | if (TARGET_RESPONDS_TO_CMD) { 279 | [_target URLSession:session task:task didFinishCollectingMetrics:metrics]; 280 | } 281 | } 282 | #undef TARGET_RESPONDS_TO_CMD 283 | 284 | @end 285 | -------------------------------------------------------------------------------- /QAPM/Monitor/URLConnection/QAPMNSURLSessionTaskAgent.h: -------------------------------------------------------------------------------- 1 | // 2 | // QAPMNSURLSessionTaskAgent.h 3 | // QunarAPM 4 | // 5 | // Created by Quanquan.zhang on 15/10/26. 6 | // Copyright © 2015年 Qunar. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | 12 | @class QAPMNetworkEntry; 13 | 14 | 15 | @interface QAPMNSURLSessionTaskAgent : NSObject 16 | 17 | @property (nonatomic, strong, nullable) QAPMNetworkEntry *networkEntry; 18 | 19 | /** 20 | * Record the networkEntry to Queue and delete it. 21 | */ 22 | - (void)finish; 23 | 24 | /** 25 | * Register agent for ID 26 | * 27 | * @param agent agent 28 | * @param aID task ID 29 | */ 30 | + (void)registerAgent:(nullable QAPMNSURLSessionTaskAgent *)agent forID:(nullable NSString *)aID; 31 | 32 | /** 33 | * Get agent with the given ID 34 | * 35 | * @param aID task ID 36 | * 37 | * @return agent 38 | */ 39 | + (nullable instancetype)agentForID:(nullable NSString *)aID; 40 | 41 | /** 42 | * Remove agent 43 | * 44 | * @param aID task ID 45 | */ 46 | + (void)removeAgentForID:(nullable NSString *)aID; 47 | 48 | /** 49 | * Register ID for task 50 | * 51 | * @param aID task ID 52 | * @param task session task 53 | */ 54 | + (void)registerID:(nullable NSString *)aID forTask:(nullable NSURLSessionTask *)task; 55 | 56 | /** 57 | * Get the ID of task 58 | * 59 | * @param task data task 60 | * 61 | * @return task ID 62 | */ 63 | + (nullable NSString *)idForTask:(nullable NSURLSessionTask *)task; 64 | 65 | /** 66 | * Get the agent by task 67 | * 68 | * @param task session task 69 | * 70 | * @return agent or nil 71 | */ 72 | + (nullable instancetype)agentForTask:(nullable NSURLSessionTask *)task; 73 | 74 | /** 75 | * Remove agent 76 | * 77 | * @param task session task 78 | */ 79 | + (void)removeAgentForTask:(nullable NSURLSessionTask *)task; 80 | 81 | @end 82 | -------------------------------------------------------------------------------- /QAPM/Monitor/URLConnection/QAPMNSURLSessionTaskAgent.m: -------------------------------------------------------------------------------- 1 | // 2 | // QAPMNSURLSessionTaskAgent.m 3 | // QunarAPM 4 | // 5 | // Created by Quanquan.zhang on 15/10/26. 6 | // Copyright © 2015年 Qunar. All rights reserved. 7 | // 8 | 9 | #import "QAPMNSURLSessionTaskAgent.h" 10 | #import "QAPMNetworkEntry.h" 11 | #import "QAPMonitor.h" 12 | 13 | static NSMutableDictionary *agentTaskMap; 14 | static NSMapTable *taskIDMap; 15 | 16 | // Lock for agentTaskMap and taskIDMap operation 17 | 18 | static NSRecursiveLock *globalLock() 19 | { 20 | static NSRecursiveLock *gLock; 21 | static dispatch_once_t token; 22 | dispatch_once(&token, ^{ 23 | gLock = [[NSRecursiveLock alloc] init]; 24 | }); 25 | 26 | return gLock; 27 | } 28 | 29 | #define LOCK [globalLock() lock]; 30 | #define UNLOCK [globalLock() unlock]; 31 | 32 | 33 | @implementation QAPMNSURLSessionTaskAgent 34 | 35 | - (instancetype)init { 36 | self = [super init]; 37 | if (self) { 38 | self.networkEntry = [[QAPMNetworkEntry alloc] initWithRequest:nil]; 39 | } 40 | 41 | return self; 42 | } 43 | 44 | - (void)finish { 45 | NSDictionary *dictEntry = [_networkEntry convertToDictionary]; 46 | if (dictEntry) { 47 | [QAPMonitor addMonitor:dictEntry withType:eNetworkTaskMonitor]; 48 | } 49 | 50 | self.networkEntry = nil; 51 | } 52 | 53 | + (void)registerAgent:(nullable QAPMNSURLSessionTaskAgent *)agent forID:(nullable NSString *)aID { 54 | static dispatch_once_t token; 55 | dispatch_once(&token, ^{ 56 | agentTaskMap = [NSMutableDictionary dictionary]; 57 | }); 58 | 59 | if (!aID) { 60 | return; 61 | } 62 | 63 | LOCK 64 | 65 | [agentTaskMap setObject:agent forKey:aID]; 66 | 67 | UNLOCK 68 | } 69 | 70 | + (nullable instancetype)agentForID:(nullable NSString *)aID { 71 | if (!aID) { 72 | return nil; 73 | } 74 | 75 | QAPMNSURLSessionTaskAgent *agent; 76 | 77 | LOCK 78 | 79 | agent = [agentTaskMap objectForKey:aID]; 80 | 81 | UNLOCK 82 | 83 | return agent; 84 | } 85 | 86 | + (void)removeAgentForID:(nullable NSString *)aID { 87 | if (!aID) { 88 | return; 89 | } 90 | 91 | LOCK 92 | 93 | [agentTaskMap removeObjectForKey:aID]; 94 | 95 | UNLOCK 96 | } 97 | 98 | + (void)registerID:(nullable NSString *)aID forTask:(nullable NSURLSessionTask *)task { 99 | static dispatch_once_t token; 100 | 101 | dispatch_once(&token, ^{ 102 | // weak key and strong value 103 | taskIDMap = [NSMapTable weakToStrongObjectsMapTable]; 104 | }); 105 | 106 | if (!aID || !task) { 107 | return; 108 | } 109 | 110 | LOCK 111 | 112 | [taskIDMap setObject:aID forKey:task]; 113 | 114 | UNLOCK 115 | } 116 | 117 | + (nullable NSString *)idForTask:(nullable NSURLSessionTask *)task { 118 | if (!task) { 119 | return nil; 120 | } 121 | 122 | NSString *aID; 123 | 124 | LOCK 125 | 126 | aID = [taskIDMap objectForKey:task]; 127 | 128 | UNLOCK 129 | 130 | return aID; 131 | } 132 | 133 | + (nullable instancetype)agentForTask:(nullable NSURLSessionTask *)task { 134 | if (!task) { 135 | return nil; 136 | } 137 | 138 | QAPMNSURLSessionTaskAgent *agent; 139 | 140 | LOCK 141 | 142 | NSString *aID = [taskIDMap objectForKey:task]; 143 | if (aID) { 144 | agent = [self agentForID:aID]; 145 | } 146 | 147 | UNLOCK 148 | 149 | return agent; 150 | } 151 | 152 | + (void)removeAgentForTask:(nullable NSURLSessionTask *)task { 153 | if (!task) { 154 | return; 155 | } 156 | 157 | LOCK 158 | 159 | NSString *aID = [taskIDMap objectForKey:task]; 160 | if (aID) { 161 | [self removeAgentForID:aID]; 162 | } 163 | 164 | UNLOCK 165 | } 166 | 167 | @end 168 | -------------------------------------------------------------------------------- /QAPM/Monitor/URLConnection/QAPMNetworkEntry.h: -------------------------------------------------------------------------------- 1 | // 2 | // NetworkMonitorEntry.h 3 | // CommonFramework 4 | // 5 | // Created by Quanquan.zhang on 16/1/19. 6 | // Copyright © 2016年 Qunar.com. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface QAPMMetricsTimingData : NSObject 12 | 13 | @property (nonatomic, strong) NSDate *fetchStartDate; 14 | @property (nonatomic, strong) NSDate *domainLookupStartDate; 15 | @property (nonatomic, strong) NSDate *domainLookupEndDate; 16 | @property (nonatomic, strong) NSDate *connectStartDate; 17 | @property (nonatomic, strong) NSDate *connectEndDate; 18 | @property (nonatomic, strong) NSDate *secureConnectionStartDate; 19 | @property (nonatomic, strong) NSDate *secureConnectionEndDate; 20 | 21 | @property (nonatomic, strong) NSDate *requestStartDate; 22 | @property (nonatomic, strong) NSDate *requestEndDate; 23 | @property (nonatomic, strong) NSDate *responseStartDate; 24 | @property (nonatomic, strong) NSDate *responseEndDate; 25 | 26 | @end 27 | 28 | 29 | @interface QAPMNetworkEntry : NSObject 30 | @property (nonatomic, strong) QAPMMetricsTimingData *timingData; 31 | 32 | /// The url that is connecting to. 33 | @property (nonatomic, strong, nullable) NSURL *url; 34 | 35 | /** 36 | * HTTP method, GET/POST/HEAD, etc. 37 | * 38 | * @discussion Better in capital letters, defaults to GET. 39 | */ 40 | @property (nonatomic, copy, nullable) NSString *httpMethod; 41 | 42 | /** 43 | * Connection start time. 44 | */ 45 | @property (nonatomic) long long startTime; 46 | 47 | /** 48 | * Response time. 49 | */ 50 | @property (nonatomic) long long connectTime; 51 | 52 | /** 53 | * Time of end or error. 54 | */ 55 | @property (nonatomic) long long endTime; 56 | 57 | @property (nonatomic, assign) uint64_t cpuEndTime; 58 | 59 | @property (nonatomic, assign) uint64_t cpuStartTime; 60 | /** 61 | * HTTP status code, such as 200, 404, etc. 62 | */ 63 | @property (nonatomic, copy, nullable) NSString *httpStatusCode; 64 | 65 | /** 66 | * success:HTTP status code(100~399)的情况 67 | * error:其它情况 68 | */ 69 | @property (nonatomic, copy, nullable) NSString *netStatus; 70 | 71 | /** 72 | * Error message 73 | */ 74 | @property (nonatomic, copy, nullable) NSString *errorMsg; 75 | 76 | /** 77 | * Size of request data. 78 | */ 79 | @property (nonatomic, assign) NSUInteger requestSize; 80 | 81 | /** 82 | * Size of response data. 83 | */ 84 | @property (nonatomic, assign) NSUInteger responseSize; 85 | 86 | /** 87 | * Network type, 2G/3G/4G/Wifi/Cellular/Unknow 88 | */ 89 | @property (nonatomic, copy, nullable) NSString *networkType; 90 | 91 | /** 92 | * HTTP header fields, Pitcher-Url/qrid 93 | */ 94 | @property (nonatomic, strong, nullable) NSDictionary *requestHeaderFields; 95 | 96 | /** 97 | * Record is valid. Default: YES 98 | */ 99 | @property (nonatomic) BOOL isValid; 100 | /** 101 | * Init the instance with url request 102 | * 103 | * @param request url request 104 | * 105 | * @return QAPMNetworkEntry instance 106 | */ 107 | - (nullable instancetype)initWithRequest:(nullable NSURLRequest *)request; 108 | 109 | /** 110 | * Record url request. 111 | * 112 | * @discussion This method will initialize url, httpMethod, startTime, 113 | * requestSize, and networkType properties. 114 | * 115 | * @param request url request 116 | */ 117 | - (void)recordRequest:(nullable NSURLRequest *)request; 118 | 119 | /** 120 | * Record network response. 121 | * 122 | * @param response response 123 | */ 124 | - (void)recordResponse:(nullable NSURLResponse *)response; 125 | 126 | /** 127 | * Record network error. 128 | * 129 | * @param error network error 130 | */ 131 | - (void)recordError:(nullable NSError *)error; 132 | 133 | /** 134 | * Record the connection start time. 135 | */ 136 | - (void)recordStartTime; 137 | 138 | /** 139 | * Record the connection ready time. 140 | */ 141 | - (void)recordConnectTime; 142 | 143 | /** 144 | * Record the connection end time. 145 | */ 146 | - (void)recordEndTime; 147 | 148 | /** 149 | * Show properties in debug console. 150 | */ 151 | - (void)debugPrint; 152 | 153 | /** 154 | * Convert metircs data to dictionary. 155 | * 156 | * @return data in dictionary 157 | */ 158 | - (nullable NSDictionary *)convertToDictionary; 159 | 160 | /** 161 | * 工具方法: 给请求添加自定义的header字段 162 | */ 163 | + (nullable NSURLRequest *)addCustomHeaderFieldWithURLRequest:(nullable NSURLRequest *)urlRequest; 164 | 165 | @end 166 | -------------------------------------------------------------------------------- /QAPM/Monitor/URLConnection/QAPMNetworkEntry.m: -------------------------------------------------------------------------------- 1 | // 2 | // NetworkMonitorEntry.m 3 | // CommonFramework 4 | // 5 | // Created by Quanquan.zhang on 16/1/19. 6 | // Copyright © 2016年 Qunar.com. All rights reserved. 7 | // 8 | 9 | #import "QAPMNetworkEntry.h" 10 | #import 11 | #import "QAPManager.h" 12 | #import "QAPMonitor.h" 13 | #import 14 | #import 15 | 16 | static NSString *const pitcherURLKey = @"Pitcher-Url"; 17 | 18 | @implementation QAPMMetricsTimingData : NSObject 19 | @end 20 | 21 | @implementation QAPMNetworkEntry 22 | 23 | 24 | - (instancetype)initWithRequest:(nullable NSURLRequest *)request 25 | { 26 | self = [super init]; 27 | if (self) { 28 | _httpStatusCode = @"Unknown"; 29 | _netStatus = @"error"; 30 | _httpMethod = @"GET"; 31 | _isValid = YES; 32 | _startTime = _connectTime = _endTime = 0; 33 | if (request) { 34 | [self recordRequest:request]; 35 | } 36 | } 37 | 38 | return self; 39 | } 40 | 41 | - (void)recordRequest:(nullable NSURLRequest *)request { 42 | self.url = request.URL; 43 | 44 | /** 45 | @b 统一逻辑 url 表示原始 url, Pitcher 数据在 request header 中 46 | // Pitcher support 47 | NSString *pitcherURL = [request valueForHTTPHeaderField:pitcherURLKey]; 48 | if (pitcherURL && pitcherURL.length > 0) { 49 | self.url = [NSURL URLWithString:pitcherURL]?:request.URL; 50 | } 51 | */ 52 | self.httpMethod = request.HTTPMethod; 53 | // TODO: HTTPBodyStream 54 | self.requestSize = [request.HTTPBody length]; 55 | self.networkType = [QAPMonitor netType]; 56 | 57 | // HTTPRequestHeaders 58 | NSMutableDictionary *allHTTPHeaderFields = [request.allHTTPHeaderFields mutableCopy]; 59 | // remove some keys 60 | [allHTTPHeaderFields removeObjectsForKeys:@[@"Content-Type", @"X-ClientEncoding", @"Host", @"Connection", @"Accept-Encoding", @"Content-Length", @"Cookie"]]; 61 | self.requestHeaderFields = allHTTPHeaderFields; 62 | } 63 | 64 | - (void)recordResponse:(nullable NSURLResponse *)response { 65 | if ([response isKindOfClass:[NSHTTPURLResponse class]]) { 66 | // HTTP response 67 | long code = [(NSHTTPURLResponse*)response statusCode]; 68 | if (code >= 100 && code < 400) { 69 | _netStatus = @"success"; 70 | } 71 | self.httpStatusCode = [NSString stringWithFormat:@"%ld", code]; 72 | } 73 | 74 | [self recordConnectTime]; 75 | } 76 | 77 | - (void)recordError:(nullable NSError *)error { 78 | _errorMsg = [error localizedDescription]; 79 | _endTime = _endTime?:QAPMGetCurrentTime_millisecond(); 80 | _connectTime = _connectTime?:_endTime; 81 | if (self.cpuEndTime == 0) { 82 | self.cpuEndTime = mach_absolute_time(); 83 | } 84 | if (error) { 85 | _netStatus = @"error"; 86 | // 用户取消请求不记录 87 | switch (error.code) { 88 | case NSURLErrorUnknown: 89 | _errorMsg = @"Unknown"; 90 | break; 91 | case NSURLErrorCancelled: 92 | _isValid = NO; 93 | break; 94 | case NSURLErrorBadURL: 95 | case NSURLErrorUnsupportedURL: 96 | _errorMsg = @"badurl"; 97 | break; 98 | case NSURLErrorTimedOut: 99 | _errorMsg = @"timeout"; 100 | break; 101 | case NSURLErrorNotConnectedToInternet: 102 | _errorMsg = @"unconnect"; 103 | _networkType = @"unconnect"; 104 | break; 105 | case NSURLErrorCannotFindHost: 106 | case NSURLErrorCannotConnectToHost: 107 | case NSURLErrorNetworkConnectionLost: 108 | case NSURLErrorDNSLookupFailed: 109 | case NSURLErrorHTTPTooManyRedirects: 110 | case NSURLErrorRedirectToNonExistentLocation: 111 | _errorMsg = @"hostErr"; 112 | break; 113 | default: 114 | break; 115 | } 116 | } 117 | } 118 | 119 | - (void)recordStartTime { 120 | dispatch_async(dispatch_get_main_queue(), ^{ 121 | if ([[UIApplication sharedApplication] applicationState] == UIApplicationStateBackground) { 122 | self.isValid = NO; 123 | } 124 | }); 125 | self.startTime = QAPMGetCurrentTime_millisecond(); 126 | self.cpuStartTime = mach_absolute_time(); 127 | } 128 | 129 | - (void)recordConnectTime { 130 | self.connectTime = QAPMGetCurrentTime_millisecond(); 131 | } 132 | 133 | - (void)recordEndTime { 134 | self.endTime = QAPMGetCurrentTime_millisecond(); 135 | self.cpuEndTime = mach_absolute_time(); 136 | } 137 | 138 | - (void)debugPrint 139 | { 140 | #ifdef DEBUG 141 | NSLog(@"%@", [self convertToDictionary]); 142 | #endif 143 | } 144 | 145 | - (BOOL)isIgnoreReq { 146 | NSString *reqStr = [NSString stringWithFormat:@"%@%@",_requestHeaderFields[@"Pitcher-Url"],_url.absoluteString]; 147 | NSArray *filterArr = [[QAPManager sharedInstance] domainFilterList]; 148 | for (NSString *filter in filterArr) { 149 | if ([reqStr containsString:filter]) { 150 | return YES; 151 | } 152 | } 153 | return NO; 154 | } 155 | 156 | - (NSDictionary *)convertToDictionary { 157 | 158 | long long enterBackGroundTime = [QAPManager enterBackgroundTime]; 159 | 160 | if (_isValid == NO) { 161 | return nil; 162 | } 163 | 164 | if (_url.absoluteString.length == 0) { 165 | return nil; 166 | } 167 | 168 | if (self.cpuStartTime <= enterBackGroundTime && enterBackGroundTime <= self.cpuEndTime) { 169 | return nil; 170 | } 171 | 172 | // long long reqTime = _endTime - _startTime; 173 | // long long connTime = _connectTime - _startTime; 174 | 175 | // 排除非http请求,只上报http、https开头的协议 176 | if (![_url.absoluteString hasPrefix:@"http"]) { 177 | return nil; 178 | } 179 | if ([self isIgnoreReq]) { 180 | return nil; 181 | } 182 | static dispatch_once_t onceToken; 183 | static double hTime2nsFactor = 1; 184 | dispatch_once(&onceToken, ^{ 185 | mach_timebase_info_data_t info; 186 | if (mach_timebase_info (&info) == KERN_SUCCESS) { 187 | hTime2nsFactor = (double)info.numer / info.denom; 188 | } 189 | }); 190 | uint64_t cpuCost = (self.cpuEndTime - self.cpuStartTime) * hTime2nsFactor / 1000000; 191 | NSString *topVC = [QAPManager appearVC]; 192 | long long endCpuST = _startTime + cpuCost; 193 | NSDictionary *dict = @{ 194 | @"reqUrl": _url.absoluteString?:@"Unknown", 195 | @"startTime": [NSString stringWithFormat:@"%lld", _startTime], 196 | @"endTime": [NSString stringWithFormat:@"%lld", endCpuST], 197 | @"reqSize": [@(_requestSize) stringValue], 198 | @"resSize": [@(_responseSize) stringValue], 199 | @"httpCode": _httpStatusCode?:@"Unknown", 200 | @"hf": _errorMsg?:@"", 201 | @"netType": _networkType?:@"Unknown", 202 | @"header": _requestHeaderFields?_requestHeaderFields:@{}, 203 | @"topPage":topVC?:@"Unknown", 204 | @"netStatus":_netStatus?:@"error", 205 | @"extra":[self appleMetricsTime:self.timingData], 206 | }; 207 | return dict; 208 | } 209 | 210 | - (NSString *)appleMetricsTime:(QAPMMetricsTimingData *)timimgDate { 211 | NSString *timStr = @""; 212 | if (timimgDate.fetchStartDate) { 213 | long long netDua = [timimgDate.responseEndDate timeIntervalSinceDate:timimgDate.fetchStartDate] * 1000; 214 | long long dnsDua = [timimgDate.domainLookupEndDate timeIntervalSinceDate:timimgDate.domainLookupStartDate] * 1000; 215 | long long conDua = [timimgDate.connectEndDate timeIntervalSinceDate:timimgDate.connectStartDate] * 1000; 216 | long long tlsDua = [timimgDate.secureConnectionEndDate timeIntervalSinceDate:timimgDate.secureConnectionStartDate] * 1000; 217 | timStr = [NSString stringWithFormat:@"%lld-%lld-%lld-%lld",netDua,dnsDua,conDua,tlsDua]; 218 | } 219 | return timStr; 220 | } 221 | 222 | + (NSURLRequest *)addCustomHeaderFieldWithURLRequest:(NSURLRequest *)urlRequest { 223 | #ifdef DEBUG 224 | // beta 环境去掉 无用字段“L-Uuid”,待稳定没有问题后,再发布到正式环境 225 | return urlRequest; 226 | #endif 227 | // 给request的Header添加L-Uuid字段 228 | NSMutableURLRequest *mutableURLRequest = [urlRequest mutableCopy]; 229 | NSString *host = urlRequest.URL.host; 230 | // 12306可能根据这个字段风控我们,造成访问不了12306 231 | if ([host containsString:@"12306.cn"]) { 232 | return urlRequest; 233 | } 234 | if (mutableURLRequest) { 235 | NSString *aID = [[NSUUID UUID] UUIDString]; 236 | NSString *uID = [QAPMonitor uid]; 237 | NSString *L_UuidString = [NSString stringWithFormat:@"%@%@", aID, uID]; 238 | NSString *L_UuidMD5String = [QAPMNetworkEntry getStringMD5:L_UuidString]; 239 | if (L_UuidMD5String) { 240 | [mutableURLRequest addValue:L_UuidMD5String forHTTPHeaderField:@"L-Uuid"]; 241 | 242 | return [mutableURLRequest copy]; 243 | } 244 | } 245 | 246 | return urlRequest; 247 | } 248 | 249 | + (NSString *)getStringMD5:(NSString *)inputString { 250 | const char *ptr = [inputString UTF8String]; 251 | unsigned char md5Buffer[CC_MD5_DIGEST_LENGTH]; 252 | 253 | CC_MD5(ptr, (unsigned int)strlen(ptr), md5Buffer); 254 | NSMutableString *output = [NSMutableString stringWithCapacity:CC_MD5_DIGEST_LENGTH * 2]; 255 | for (int i = 0; i < CC_MD5_DIGEST_LENGTH; i++) { 256 | [output appendFormat:@"%02x", md5Buffer[i]]; 257 | } 258 | 259 | return [output copy]; 260 | } 261 | 262 | @end 263 | -------------------------------------------------------------------------------- /QAPM/Monitor/URLConnection/QAPMURLConnectionDelegateAgent.h: -------------------------------------------------------------------------------- 1 | // 2 | // QAPMURLConnectionDelegate.h 3 | // QunarAPM 4 | // 5 | // Created by Quanquan.zhang on 15/9/22. 6 | // Copyright © 2015年 Qunar. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | 12 | @interface QAPMURLConnectionDelegateAgent : NSObject 13 | 14 | @property (nonatomic, strong, nullable) id target; 15 | 16 | - (nullable instancetype)initWithTarget:(nullable id)target request:(nullable NSURLRequest *)request; 17 | + (nullable instancetype)agentWithTarget:(nullable id)target request:(nullable NSURLRequest *)request; 18 | 19 | @end 20 | -------------------------------------------------------------------------------- /QAPM/Monitor/URLConnection/QAPMURLConnectionDelegateAgent.m: -------------------------------------------------------------------------------- 1 | // 2 | // URLConnectionDelegateAgent.m 3 | // CommonFramework 4 | // 5 | // Created by Quanquan.zhang on 16/1/19. 6 | // Copyright © 2016年 Qunar.com. All rights reserved. 7 | // 8 | 9 | #import "QAPMURLConnectionDelegateAgent.h" 10 | #import "QAPMNetworkEntry.h" 11 | #import "QAPMonitor.h" 12 | 13 | 14 | @interface QAPMURLConnectionDelegateAgent () 15 | 16 | @property (nonatomic, strong, nonnull) QAPMNetworkEntry *networkEntry; 17 | @property (nonatomic, assign) NSUInteger dataSize; 18 | 19 | @end 20 | 21 | 22 | @implementation QAPMURLConnectionDelegateAgent 23 | 24 | - (instancetype)initWithTarget:(nullable id)target request:(nullable NSURLRequest *)request 25 | { 26 | self = [super init]; 27 | if (self) { 28 | self.target = target; 29 | self.networkEntry = [[QAPMNetworkEntry alloc] initWithRequest:request]; 30 | [_networkEntry recordStartTime]; 31 | } 32 | 33 | return self; 34 | } 35 | 36 | + (nullable instancetype)agentWithTarget:(nullable id)target request:(nullable NSURLRequest *)request 37 | { 38 | return [[QAPMURLConnectionDelegateAgent alloc] initWithTarget:target request:request]; 39 | } 40 | 41 | - (BOOL)respondsToSelector:(SEL)aSelector 42 | { 43 | /** 44 | * Some methods, such as connectionDidFinishDownloading:destinationURL: and connectionDidFinishLoading:, are conflicted. 45 | * If the agent responds all NSURLConnection delegate methods, the behavior of target may be affected. 46 | */ 47 | 48 | NSString *selectorString = NSStringFromSelector(aSelector); 49 | if ([selectorString isEqualToString:@"connection:didFailWithError:"] 50 | || [selectorString isEqualToString:@"connectionDidFinishLoading:"] 51 | || [selectorString isEqualToString:@"connection:didReceiveResponse:"]) { 52 | return YES; 53 | } 54 | 55 | if ([selectorString hasPrefix:@"connection"]) { 56 | BOOL targetResponses = [_target respondsToSelector:aSelector]; 57 | BOOL selfResponses = [[self class] instancesRespondToSelector:aSelector]; 58 | 59 | if (targetResponses && !selfResponses) { 60 | NSLog(@"Wranning: unresponsed method: %@", selectorString); 61 | } 62 | 63 | return targetResponses && selfResponses; 64 | } 65 | 66 | return [[self class] instancesRespondToSelector:aSelector]; 67 | } 68 | 69 | #define TARGET_RESPONDS_TO_CMD (_target && [_target respondsToSelector:_cmd]) 70 | 71 | #pragma mark - NSURLConnectionDelegate 72 | 73 | - (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error 74 | { 75 | [_networkEntry recordError:error]; 76 | 77 | // TODO: error code 78 | 79 | if (TARGET_RESPONDS_TO_CMD) { 80 | [_target connection:connection didFailWithError:error]; 81 | } 82 | 83 | [_networkEntry debugPrint]; 84 | } 85 | 86 | - (BOOL)connectionShouldUseCredentialStorage:(NSURLConnection *)connection 87 | { 88 | if (TARGET_RESPONDS_TO_CMD) { 89 | return [_target connectionShouldUseCredentialStorage:connection]; 90 | } 91 | 92 | return NO; 93 | } 94 | 95 | - (void)connection:(NSURLConnection *)connection willSendRequestForAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge 96 | { 97 | [_networkEntry recordConnectTime]; 98 | 99 | if (TARGET_RESPONDS_TO_CMD) { 100 | [_target connection:connection willSendRequestForAuthenticationChallenge:challenge]; 101 | } 102 | } 103 | 104 | #pragma GCC diagnostic push 105 | #pragma GCC diagnostic ignored "-Wdeprecated" 106 | 107 | // TODO: Should these deprecated methods be supported? 108 | 109 | - (BOOL)connection:(NSURLConnection *)connection canAuthenticateAgainstProtectionSpace:(NSURLProtectionSpace *)protectionSpace 110 | { 111 | if (TARGET_RESPONDS_TO_CMD) { 112 | return [_target connection:connection canAuthenticateAgainstProtectionSpace:protectionSpace]; 113 | } 114 | 115 | return NO; 116 | } 117 | 118 | - (void)connection:(NSURLConnection *)connection didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge 119 | { 120 | if (TARGET_RESPONDS_TO_CMD) { 121 | [_target connection:connection didReceiveAuthenticationChallenge:challenge]; 122 | } 123 | } 124 | 125 | - (void)connection:(NSURLConnection *)connection didCancelAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge 126 | { 127 | if (TARGET_RESPONDS_TO_CMD) { 128 | [_target connection:connection didCancelAuthenticationChallenge:challenge]; 129 | } 130 | } 131 | 132 | #pragma GCC diagnostic pop 133 | 134 | #pragma mark - NSURLConnectionDataDelegate 135 | 136 | - (nullable NSURLRequest *)connection:(NSURLConnection *)connection willSendRequest:(NSURLRequest *)request redirectResponse:(nullable NSURLResponse *)response 137 | { 138 | if (TARGET_RESPONDS_TO_CMD) { 139 | return [_target connection:connection willSendRequest:request redirectResponse:response]; 140 | } 141 | 142 | return request; 143 | } 144 | 145 | - (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response 146 | { 147 | [_networkEntry recordResponse:response]; 148 | 149 | if (TARGET_RESPONDS_TO_CMD) { 150 | [_target connection:connection didReceiveResponse:response]; 151 | } 152 | } 153 | 154 | - (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data 155 | { 156 | _dataSize += [data length]; 157 | 158 | if (TARGET_RESPONDS_TO_CMD) { 159 | [_target connection:connection didReceiveData:data]; 160 | } 161 | } 162 | 163 | - (nullable NSInputStream *)connection:(NSURLConnection *)connection needNewBodyStream:(NSURLRequest *)request 164 | { 165 | if (TARGET_RESPONDS_TO_CMD) { 166 | return [self.target connection:connection needNewBodyStream:request]; 167 | } 168 | 169 | return nil; 170 | } 171 | 172 | - (void)connection:(NSURLConnection *)connection didSendBodyData:(NSInteger)bytesWritten 173 | totalBytesWritten:(NSInteger)totalBytesWritten 174 | totalBytesExpectedToWrite:(NSInteger)totalBytesExpectedToWrite 175 | 176 | { 177 | if (TARGET_RESPONDS_TO_CMD) { 178 | [self.target connection:connection 179 | didSendBodyData:bytesWritten 180 | totalBytesWritten:totalBytesWritten 181 | totalBytesExpectedToWrite:totalBytesExpectedToWrite]; 182 | } 183 | } 184 | 185 | - (nullable NSCachedURLResponse *)connection:(NSURLConnection *)connection willCacheResponse:(NSCachedURLResponse *)cachedResponse 186 | { 187 | if (TARGET_RESPONDS_TO_CMD) { 188 | return [self.target connection:connection willCacheResponse:cachedResponse]; 189 | } 190 | 191 | return cachedResponse; 192 | } 193 | 194 | - (void)connectionDidFinishLoading:(NSURLConnection *)connection 195 | { 196 | [_networkEntry recordEndTime]; 197 | _networkEntry.responseSize = _dataSize; 198 | 199 | [_networkEntry debugPrint]; 200 | 201 | NSDictionary *dictEntry = [_networkEntry convertToDictionary]; 202 | if (dictEntry) { 203 | [QAPMonitor addMonitor:dictEntry withType:eNetworkTaskMonitor]; 204 | } 205 | 206 | if (TARGET_RESPONDS_TO_CMD) { 207 | [_target connectionDidFinishLoading:connection]; 208 | } 209 | } 210 | 211 | #pragma mark - NSURLConnectionDownloadDelegate 212 | 213 | - (void)connection:(NSURLConnection *)connection didWriteData:(long long)bytesWritten totalBytesWritten:(long long)totalBytesWritten expectedTotalBytes:(long long) expectedTotalBytes 214 | { 215 | _dataSize += bytesWritten; 216 | 217 | if (TARGET_RESPONDS_TO_CMD) { 218 | [_target connection:connection didWriteData:bytesWritten totalBytesWritten:totalBytesWritten expectedTotalBytes:expectedTotalBytes]; 219 | } 220 | } 221 | 222 | - (void)connectionDidResumeDownloading:(NSURLConnection *)connection totalBytesWritten:(long long)totalBytesWritten expectedTotalBytes:(long long) expectedTotalBytes 223 | { 224 | if (TARGET_RESPONDS_TO_CMD) { 225 | [_target connectionDidResumeDownloading:connection totalBytesWritten:totalBytesWritten expectedTotalBytes:expectedTotalBytes]; 226 | } 227 | } 228 | 229 | - (void)connectionDidFinishDownloading:(NSURLConnection *)connection destinationURL:(NSURL *) destinationURL 230 | { 231 | [_networkEntry recordEndTime]; 232 | _networkEntry.responseSize = _dataSize; 233 | 234 | [_networkEntry debugPrint]; 235 | 236 | if (TARGET_RESPONDS_TO_CMD) { 237 | [_target connectionDidFinishDownloading:connection destinationURL:destinationURL]; 238 | } 239 | } 240 | 241 | #undef TARGET_RESPONDS_TO_CMD 242 | 243 | @end 244 | -------------------------------------------------------------------------------- /QAPM/QAPManager.h: -------------------------------------------------------------------------------- 1 | // 2 | // QAPManager.h 3 | // QAPM_a 4 | // 5 | // Created by mdd on 2018/11/21. 6 | // Copyright © 2018年 mdd. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | #import "QCacheStorage.h" 12 | 13 | @protocol QAPMExtendDelegate 14 | 15 | @optional 16 | 17 | /// 获取当前定位信息,,如果没有实现则使用默认实现 18 | + (nullable CLLocation *)location; 19 | 20 | /// 获取当前显示界面,如果用户没有实现则默认检索 21 | + (nullable NSString *)appearVC; 22 | 23 | @end 24 | 25 | @interface QAPManager : NSObject 26 | 27 | + (instancetype)sharedInstance; 28 | /** 29 | 域名过滤,当请求的url.absoluteString包含set的内容时,不上传监控数据。实时生效 30 | 域名比如 苹果网站:www.apple.com , 或者更具体的 www.apple.com/watch ,但是前者已包含后者 31 | */ 32 | @property (nonatomic, strong) NSArray *domainFilterList; 33 | 34 | /** 35 | * Start QAPM with pid, cid and vid 36 | * 37 | * @param pid 产品号 38 | * @param cid 渠道标识 39 | * @param vid 版本号 40 | * @param uid 设备号 41 | */ 42 | + (void)startWithPid:(nonnull NSString *)pid 43 | cid:(nullable NSString *)cid 44 | vid:(nullable NSString *)vid 45 | uid:(nullable NSString *)uid; 46 | 47 | + (void)registExtend:(id)extend; 48 | 49 | /// 添加UI监控数据 50 | + (void)addUIMonitor:(NSDictionary *)uiMonitorData; 51 | /// 添加net监控数据 52 | + (void)addNetMonitor:(NSDictionary *)netMonitorData; 53 | /// 进入Background的cpu时间 54 | + (long long)enterBackgroundTime; 55 | /// 获取当前定位信息 56 | + (nullable CLLocation *)location; 57 | /// 获取当前显示界面 58 | + (nullable NSString *)appearVC; 59 | /// 获取当前显示界面类名 60 | + (nullable NSString *)topVCClassName; 61 | 62 | /**************************************** 63 | 获取当前的日志: 64 | 1. 获取操作文件的实例 65 | 2. 通过实例,调用实例方法获取到存在本地的日志文件名、文件内容。暴露的方法见QCacheStorage.h 66 | ****************************************/ 67 | 68 | /// release环境的日志的实例 69 | + (QCacheStorage *)releaseLogInstance; 70 | /// 获取beta环境 71 | + (QCacheStorage *)betaLogInstance; 72 | 73 | @end 74 | -------------------------------------------------------------------------------- /QAPM/QAPManager.m: -------------------------------------------------------------------------------- 1 | // 2 | // QAPManager.m 3 | // QAPM_a 4 | // 5 | // Created by mdd on 2018/11/21. 6 | // Copyright © 2018年 mdd. All rights reserved. 7 | // 8 | 9 | #import "QAPManager.h" 10 | #import "QAPMonitor.h" 11 | #import 12 | #import 13 | #import 14 | #import "QAPMSystemInfo.h" 15 | 16 | @interface QAPManager () 17 | 18 | @property (nonatomic, strong) Class monitorExtend; 19 | /// 进入Background的cpu时间 20 | @property (nonatomic, assign) long long enterBackgroundTime; 21 | @end 22 | 23 | @implementation QAPManager 24 | 25 | //+ (void)load { 26 | // dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ 27 | // [self startWithPid:@"123" cid:@"234" vid:@"345" uid:@"456"]; 28 | // }); 29 | //} 30 | 31 | + (instancetype)sharedInstance { 32 | static QAPManager *instance; 33 | static dispatch_once_t onceToken; 34 | dispatch_once(&onceToken, ^{ 35 | instance = [[self alloc] init]; 36 | instance.enterBackgroundTime = 0; 37 | [[NSNotificationCenter defaultCenter] addObserver:instance selector:@selector(updateBackgroundTime:) name:UIApplicationDidEnterBackgroundNotification object:nil]; 38 | }); 39 | 40 | return instance; 41 | } 42 | 43 | + (QCacheStorage *)releaseLogInstance { 44 | return [[QAPMonitor getInstance] gMonitorCache]; 45 | } 46 | 47 | + (QCacheStorage *)betaLogInstance { 48 | return [[QAPMSystemInfo sharedInstance] sysMonitorCache]; 49 | } 50 | 51 | + (void)startWithPid:(NSString *)pid cid:(NSString *)cid vid:(NSString *)vid uid:(NSString *)uid { 52 | [QAPManager sharedInstance]; 53 | [QAPMonitor setupMonitorWithPid:pid cid:cid vid:vid uid:uid]; 54 | } 55 | 56 | + (void)registExtend:(id)extend { 57 | QAPManager *manager = [self sharedInstance]; 58 | manager.monitorExtend = [extend class]; 59 | } 60 | 61 | + (long long)enterBackgroundTime { 62 | return [[self sharedInstance] enterBackgroundTime]; 63 | } 64 | 65 | - (void)updateBackgroundTime:(NSNotification*)notification { 66 | self.enterBackgroundTime = mach_absolute_time(); 67 | } 68 | 69 | + (void)addUIMonitor:(NSDictionary *)uiMonitorData { 70 | [QAPMonitor addUIMonitor:uiMonitorData]; 71 | } 72 | 73 | + (void)addNetMonitor:(NSDictionary *)netMonitorData { 74 | [QAPMonitor addNetMonitor:netMonitorData]; 75 | } 76 | 77 | #pragma mark - QAPMExtendDelegate协议 78 | 79 | /// 获取当前定位信息 80 | + (nullable CLLocation *)location { 81 | Class extend = [[QAPManager sharedInstance] monitorExtend]; 82 | if (extend != nil && class_getClassMethod(extend, @selector(location)) != nil) { 83 | return [extend location]; 84 | } 85 | return nil; 86 | } 87 | 88 | /// 获取当前显示界面 89 | + (nullable NSString *)appearVC { 90 | Class extend = [[QAPManager sharedInstance] monitorExtend]; 91 | if (extend != nil && class_getClassMethod(extend, @selector(appearVC)) != nil) { 92 | return [extend appearVC]; 93 | } 94 | // return [self topVCName]; 95 | return nil; 96 | } 97 | 98 | + (nullable NSString *)topVCClassName { 99 | NSString *vcName = nil; 100 | Class cla = NSClassFromString(@"VCController"); 101 | SEL sel = NSSelectorFromString(@"getTopVC"); 102 | if ([cla respondsToSelector:sel]) { 103 | id vc = [cla performSelector:sel]; 104 | if (vc) { 105 | vcName = NSStringFromClass([vc class]); 106 | } 107 | } 108 | return vcName; 109 | } 110 | 111 | //+ (NSString *)topVCName { 112 | // UIViewController *result = nil; 113 | // 114 | // // 获取mainWindow 115 | // UIWindow * window = [[UIApplication sharedApplication] keyWindow]; 116 | // if (window.windowLevel != UIWindowLevelNormal) { 117 | // NSArray *windows = [[UIApplication sharedApplication] windows]; 118 | // for (UIWindow * tmpWin in windows) { 119 | // if (tmpWin.windowLevel == UIWindowLevelNormal) { 120 | // window = tmpWin; 121 | // break; 122 | // } 123 | // } 124 | // } 125 | // 126 | // // 获取window.rootVC,如果不存在,取window的subviews的最上层VC 127 | // UIViewController *rootVC = window.rootViewController; 128 | // if (rootVC == nil) { 129 | // UIView *backView = [[window subviews] objectAtIndex:0]; 130 | // for (UIView* next = backView; next; next = next.superview) { 131 | // UIResponder* nextResponder = [next nextResponder]; 132 | // if ([nextResponder isKindOfClass:[UIViewController class]]) { 133 | // rootVC = (UIViewController*)nextResponder; 134 | // break; 135 | // } 136 | // } 137 | // } 138 | // // 如果存在,则递归获取最上层VC 139 | // result = [self topVCFromViewController:rootVC]; 140 | // 141 | // return NSStringFromClass([result class]); 142 | //} 143 | // 144 | ///// 返回值为顶层VC,vcList为VC栈 145 | //+ (UIViewController *)topVCFromViewController:(UIViewController *)vc { 146 | // if (vc == nil) { 147 | // return nil; 148 | // } 149 | // 150 | // UIViewController *resultVC = vc; 151 | // if (vc.presentedViewController != nil) { 152 | // resultVC = [self topVCFromViewController:vc.presentedViewController]; 153 | // } 154 | // 155 | // if ([vc isKindOfClass:[UINavigationController class]]) { 156 | // UIViewController *topVC = ((UINavigationController *)vc).topViewController; 157 | // 158 | // resultVC = [self topVCFromViewController:topVC]; 159 | // } 160 | // 161 | // if ([vc isKindOfClass:[UITabBarController class]]) { 162 | // UIViewController *selectedVC = ((UITabBarController *)vc).selectedViewController; 163 | // resultVC = [self topVCFromViewController:selectedVC]; 164 | // } 165 | // 166 | // return resultVC; 167 | //} 168 | 169 | @end 170 | -------------------------------------------------------------------------------- /QAPM/Storage/QCacheStorage.h: -------------------------------------------------------------------------------- 1 | // 2 | // QCacheStorage.h 3 | // QAPM_a 4 | // 5 | // Created by mdd on 2018/12/19. 6 | // Copyright © 2018年 mdd. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface QCacheStorage : NSObject 12 | 13 | typedef void (^QGetFileDataCompleteBlock)(NSDictionary *data); 14 | 15 | typedef void (^QGetFileListCompleteBlock)(NSArray *data); 16 | 17 | typedef void (^QSaveFileDataCompleteBlock)(BOOL rlt); 18 | 19 | typedef void (^QGetFileNameCompleteBlock)(NSString *fileName); 20 | 21 | /** 22 | @param dirPath 用户自定义文件夹路径,如果创建时目录为空或创建文件夹失败,则使用默认路径,默认路径为:.../Document/QCacheStorage 23 | */ 24 | + (instancetype)cacheStorageWithDir:(NSString *)dirPath; 25 | - (BOOL)hasValuedFile; 26 | - (void)deleteFile:(NSString *)fileName; 27 | /// 存储文件到本地或者 28 | - (void)saveData:(NSDictionary *)data toFile:(NSString *)fileName; 29 | /// 根据文件名获取文件 30 | - (void)fileDataWithFile:(NSString *)file withCompleteBlock:(QGetFileDataCompleteBlock)block; 31 | /// 当前所有文件名 32 | - (void)fileLists:(QGetFileListCompleteBlock)block; 33 | /// 根据时间排序获取最早的文件,key为文件名 34 | - (void)earlyFile:(QGetFileDataCompleteBlock) block; 35 | - (void)sendFileErrorAddFile:(NSString *)fileName; 36 | /// block 返回成功或失败 37 | - (void)saveCacheToFile:(QSaveFileDataCompleteBlock) block; 38 | /// 文件夹路径。若用户自定义路径创建文件夹成功则返回用户自定义路径,否则返回默认 39 | - (NSString *)dirPath; 40 | @property (nonatomic, assign) NSUInteger maxCacheSize; 41 | /// 允许存储文件的个数,如果maxFileSize为0则无限制。一个文件大概7KB,1万个文件差不多70MB。如果超过了设定限制,会删掉最早的%5 42 | @property (nonatomic, assign) NSUInteger maxFileSize; 43 | /// 生成一个有序的文件名,方便根据文件名排序 44 | + (NSString *)autoIncrementFileName; 45 | 46 | @end 47 | -------------------------------------------------------------------------------- /QAPM/Storage/QCacheStorage.m: -------------------------------------------------------------------------------- 1 | // 2 | // QCacheStorage.m 3 | // QAPM_a 4 | // 5 | // Created by mdd on 2018/12/19. 6 | // Copyright © 2018年 mdd. All rights reserved. 7 | // 8 | 9 | #import "QCacheStorage.h" 10 | 11 | //#define kMaxCacheSize 5 12 | 13 | static NSString *const gStringQCacheDirName = @"/QCacheStorage"; 14 | 15 | @interface QCacheStorage () 16 | /// 当前的所有文件名(后续可以做成最早或者最晚的n个文件) 17 | @property (nonatomic, strong) NSMutableArray *pendingFiles; 18 | 19 | /// 内存缓存中间层,使日志读写更高效,同时减少文件读写次数 20 | @property (nonatomic, strong) NSMutableDictionary *memoryCacher; 21 | /// 串行队列 22 | @property (nonatomic, strong) dispatch_queue_t squeue; 23 | @property (nonatomic, copy) NSString *dirPath; 24 | @end 25 | 26 | @implementation QCacheStorage 27 | /// 避免同一个dirPath重复调用此方法多次!!!待处理 28 | + (instancetype)cacheStorageWithDir:(NSString *)dirPath { 29 | QCacheStorage *cacheStorage = [[QCacheStorage alloc] init]; 30 | cacheStorage.squeue = dispatch_queue_create("QCacheStorageQueue", DISPATCH_QUEUE_SERIAL); 31 | cacheStorage.dirPath = dirPath; 32 | cacheStorage.pendingFiles = [NSMutableArray arrayWithCapacity:1]; 33 | if (![cacheStorage createDirectory:dirPath]) { 34 | cacheStorage.dirPath = nil; 35 | [cacheStorage createDirectory:[self defaultDirectory]]; 36 | } 37 | cacheStorage.pendingFiles = [cacheStorage _existingDataFiles].mutableCopy; 38 | return cacheStorage; 39 | } 40 | 41 | #pragma mark - 对外提供api 42 | 43 | /// 返回最近的{文件名:文件数据} 44 | - (void)latelyFile:(QGetFileDataCompleteBlock) block { 45 | if (!block) { 46 | return; 47 | } 48 | dispatch_async(self.squeue, ^{ 49 | if (self.pendingFiles.count > 0) { 50 | NSString *key = self.pendingFiles.lastObject; 51 | NSDictionary *data = [self _dataFromFile:key]; 52 | if (key && data) { 53 | block(@{key:data}); 54 | } 55 | [self.pendingFiles removeObjectAtIndex:self.pendingFiles.count - 1]; 56 | } 57 | else { 58 | block(nil); 59 | } 60 | }); 61 | } 62 | 63 | - (void)fileLists:(QGetFileListCompleteBlock)block { 64 | if (!block) { 65 | return; 66 | } 67 | dispatch_async(self.squeue, ^{ 68 | block([self.pendingFiles copy]); 69 | }); 70 | } 71 | 72 | - (void)fileDataWithFile:(NSString *)file withCompleteBlock:(QGetFileDataCompleteBlock)block { 73 | if (!block) { 74 | return; 75 | } 76 | if (file) { 77 | dispatch_async(self.squeue, ^{ 78 | NSDictionary *data = [self _dataFromFile:file]; 79 | block(data); 80 | }); 81 | } 82 | else { 83 | block(nil); 84 | } 85 | } 86 | 87 | /// 返回最早的{文件名:文件数据} 88 | - (void)earlyFile:(QGetFileDataCompleteBlock)block { 89 | if (!block) { 90 | return; 91 | } 92 | dispatch_async(self.squeue, ^{ 93 | if (self.pendingFiles.count > 0) { 94 | NSString *key = self.pendingFiles.firstObject; 95 | NSDictionary *data = [self _dataFromFile:key]; 96 | if (key && data) { 97 | block(@{key:data}); 98 | } 99 | else { 100 | block(nil); 101 | } 102 | [self.pendingFiles removeObjectAtIndex:0]; 103 | } 104 | else { 105 | block(nil); 106 | } 107 | }); 108 | } 109 | /// 发送文件失败,将文件再写到pendingFiles 110 | - (void)sendFileErrorAddFile:(NSString *)fileName { 111 | if (!fileName) { 112 | return; 113 | } 114 | dispatch_async(self.squeue, ^{ 115 | [self.pendingFiles insertObject:fileName atIndex:0]; 116 | }); 117 | } 118 | 119 | - (BOOL)hasValuedFile { 120 | return self.pendingFiles.count > 0; 121 | } 122 | 123 | /** 124 | Document/QCacheStorage 125 | */ 126 | + (NSString *)defaultDirectory { 127 | NSString *defaultDirectory = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)[0]; 128 | NSString *rlt = [defaultDirectory stringByAppendingPathComponent:gStringQCacheDirName]; 129 | return rlt; 130 | } 131 | /// 生成一个有序的文件名,下次读取时方便根据文件名排序 132 | + (NSString *)autoIncrementFileName { 133 | // 时间戳可能不唯一,采用时间戳+自增数的逻辑。自增数每次启动时变为0不会有问题,因为前面的时间戳一定是更大的 134 | static long long index = 0; 135 | NSString *curTimeStamp = [NSString stringWithFormat:@"%llu",(unsigned long long)([[NSDate date] timeIntervalSince1970])]; 136 | return [NSString stringWithFormat:@"QCS_%@_%lld",curTimeStamp, ++index]; 137 | } 138 | 139 | - (NSString *)dirPath { 140 | if (!_dirPath) { 141 | return [QCacheStorage defaultDirectory]; 142 | } 143 | return _dirPath; 144 | } 145 | /// 存储文件到本地或者内存 146 | - (void)saveData:(NSDictionary *)data toFile:(NSString *)fileName { 147 | dispatch_async(self.squeue, ^{ 148 | [self.pendingFiles addObject:fileName]; 149 | [self.memoryCacher setObject:data forKey:fileName]; 150 | if ([self.memoryCacher count] >= MAX(self.maxCacheSize, 1) ) { 151 | // 把现有的内存缓存日志存到本地 152 | [self _saveCacheToFile]; 153 | } 154 | if (self.maxFileSize != 0 && self.pendingFiles.count >= self.maxFileSize) { 155 | // 删掉最早的5% 156 | NSUInteger fileCount = self.pendingFiles.count; 157 | NSUInteger removeFileCount = MAX(fileCount * 0.05, 20); 158 | NSArray *removeFiles = [self.pendingFiles subarrayWithRange:NSMakeRange(0, removeFileCount)]; 159 | NSArray *saveFiles = [self.pendingFiles subarrayWithRange:NSMakeRange(removeFileCount, fileCount - removeFileCount)]; 160 | for (NSString *file in removeFiles) { 161 | [self _deleteFile:file]; 162 | } 163 | self.pendingFiles = saveFiles.mutableCopy; 164 | } 165 | }); 166 | } 167 | 168 | /// 从缓存或者本地删掉文件 169 | - (void)deleteFile:(NSString *)fileName { 170 | dispatch_async(self.squeue, ^{ 171 | [self _deleteFile:fileName]; 172 | }); 173 | } 174 | 175 | /// 进入后台时全部存到本地 176 | - (void)saveCacheToFile:(QSaveFileDataCompleteBlock) block{ 177 | dispatch_async(self.squeue, ^{ 178 | BOOL rlt = [self _saveCacheToFile]; 179 | if (block) { 180 | block(rlt); 181 | } 182 | }); 183 | } 184 | /// 取出文件 185 | - (void)dataFromFile:(NSString *)fileName withComplete:(QGetFileDataCompleteBlock) block{ 186 | if (!block) { 187 | return; 188 | } 189 | dispatch_async(self.squeue, ^{ 190 | block([self _dataFromFile:fileName]); 191 | }); 192 | } 193 | 194 | - (void)existingDataFiles:(QGetFileListCompleteBlock)block { 195 | if (!block) { 196 | return; 197 | } 198 | dispatch_async(self.squeue, ^{ 199 | block([self _existingDataFiles]); 200 | }); 201 | } 202 | 203 | #pragma mark - 对外提供api对应的内部方法 204 | 205 | - (void)_deleteFile:(NSString *)fileName { 206 | if (fileName == nil || fileName.length == 0) { return ; } 207 | // 删除本地文件 208 | if ([self.memoryCacher objectForKey:fileName]) { 209 | // 如果在内存缓存中,则直接删除 210 | [self.memoryCacher removeObjectForKey:fileName]; 211 | } else { 212 | NSFileManager *fileManager = [NSFileManager defaultManager]; 213 | [fileManager removeItemAtPath:[self saveFilePathWithFileName:fileName] error:nil]; 214 | } 215 | } 216 | 217 | - (BOOL)_saveCacheToFile { 218 | // 并非把所有缓存写入一个文件,而依然是单独写入各自的文件。之所以这样做是考虑到如果写入一个文件中,那么当需要其中任意一个时都需要把整个文件全量读出。 219 | __block BOOL rlt = YES; 220 | NSMutableArray *deleteArr = @[].mutableCopy; 221 | [self.memoryCacher enumerateKeysAndObjectsUsingBlock:^(NSString * _Nonnull key, NSDictionary * _Nonnull logs, BOOL * _Nonnull stop) { 222 | if ([logs writeToFile:[self saveFilePathWithFileName:key] atomically:YES]) { 223 | [deleteArr addObject:key]; 224 | } else { 225 | rlt = NO; 226 | *stop = YES; 227 | } 228 | }]; 229 | for (NSString *key in deleteArr) { 230 | if (key) { 231 | [self.memoryCacher removeObjectForKey:key]; 232 | } 233 | } 234 | return rlt; 235 | } 236 | 237 | - (NSDictionary *)_dataFromFile:(NSString *)fileName { 238 | NSDictionary *cachedLogs = [self.memoryCacher objectForKey:fileName]; 239 | if (cachedLogs) { 240 | return cachedLogs; 241 | } 242 | // 去本地找 243 | cachedLogs = [NSDictionary dictionaryWithContentsOfFile:[self saveFilePathWithFileName:fileName]]; 244 | return cachedLogs; 245 | } 246 | 247 | - (NSArray *)_existingDataFiles { 248 | // 缓存中的所有key + 本地的所有文件 249 | NSArray *sortedCachedKeys = [self.memoryCacher allKeys]; 250 | NSFileManager *fileManager = [NSFileManager defaultManager]; 251 | // 比较耗时,采用一级目录,避免更大的耗时 252 | NSArray *rlt = [fileManager subpathsAtPath:[self dirPath]]; 253 | sortedCachedKeys = [[sortedCachedKeys arrayByAddingObjectsFromArray:rlt] sortedArrayUsingComparator:^NSComparisonResult(NSString* _Nonnull obj1, NSString* _Nonnull obj2) { 254 | return [obj1 compare:obj2]; 255 | }]; 256 | return sortedCachedKeys; 257 | } 258 | 259 | #pragma mark - 内部方法 260 | 261 | - (NSString *)saveFilePathWithFileName:(NSString *)fileName { 262 | NSString *rlt = [[self dirPath] stringByAppendingPathComponent:[NSString stringWithFormat:@"/%@", fileName]]; 263 | return rlt; 264 | } 265 | 266 | - (NSMutableDictionary *)memoryCacher { 267 | if (_memoryCacher == nil) { 268 | _memoryCacher = [NSMutableDictionary dictionaryWithCapacity:self.maxCacheSize]; 269 | } 270 | return _memoryCacher; 271 | } 272 | 273 | /** 274 | 日志存储文件夹 275 | */ 276 | - (BOOL)createDirectory:(NSString *)dirPath { 277 | if (dirPath == nil) { 278 | return NO; 279 | } 280 | // 文件夹不存在时新建 281 | BOOL isDir = NO; 282 | BOOL rlt = YES; 283 | NSFileManager *fileManager = [NSFileManager defaultManager]; 284 | if (![fileManager fileExistsAtPath:dirPath isDirectory:&isDir]) { 285 | rlt = [fileManager createDirectoryAtPath:dirPath withIntermediateDirectories:YES attributes:nil error:nil]; 286 | } 287 | return rlt; 288 | } 289 | 290 | @end 291 | 292 | -------------------------------------------------------------------------------- /QAPMApp.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 50; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 6008153421FEAEDC00C90D84 /* QAPMSystemInfo.h in Headers */ = {isa = PBXBuildFile; fileRef = 6008153221FEAEDC00C90D84 /* QAPMSystemInfo.h */; }; 11 | 6008153621FEAEDC00C90D84 /* QAPMSystemInfo.m in Sources */ = {isa = PBXBuildFile; fileRef = 6008153321FEAEDC00C90D84 /* QAPMSystemInfo.m */; }; 12 | 601244EC21CA67B800D0D21F /* QCacheStorage.h in Headers */ = {isa = PBXBuildFile; fileRef = 601244EA21CA67B800D0D21F /* QCacheStorage.h */; settings = {ATTRIBUTES = (Public, ); }; }; 13 | 601244ED21CA67B800D0D21F /* QCacheStorage.m in Sources */ = {isa = PBXBuildFile; fileRef = 601244EB21CA67B800D0D21F /* QCacheStorage.m */; }; 14 | 6026AF2C222921C2009879F1 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 6026AF2B222921C2009879F1 /* AppDelegate.m */; }; 15 | 6026AF2F222922DE009879F1 /* QAPMViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 6026AF2E222922DE009879F1 /* QAPMViewController.m */; }; 16 | 6026AF3122292961009879F1 /* README.md in Resources */ = {isa = PBXBuildFile; fileRef = 6026AF3022292961009879F1 /* README.md */; }; 17 | 6026AF36222931EF009879F1 /* SystemConfiguration.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6026AF35222931EF009879F1 /* SystemConfiguration.framework */; }; 18 | 60336C5321E3260900704164 /* QAPFPSMonitor.h in Headers */ = {isa = PBXBuildFile; fileRef = 60336C5121E3260900704164 /* QAPFPSMonitor.h */; }; 19 | 60336C5521E3260900704164 /* QAPFPSMonitor.m in Sources */ = {isa = PBXBuildFile; fileRef = 60336C5221E3260900704164 /* QAPFPSMonitor.m */; }; 20 | 607F62E021AF8A7E00289A78 /* QAPMonitor.m in Sources */ = {isa = PBXBuildFile; fileRef = 607F62DE21AF8A7E00289A78 /* QAPMonitor.m */; }; 21 | 607F62E121AF8A7E00289A78 /* QAPMonitor.h in Headers */ = {isa = PBXBuildFile; fileRef = 607F62DF21AF8A7E00289A78 /* QAPMonitor.h */; }; 22 | 6094049521A53948005ADDF6 /* QAPManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 6094049421A53948005ADDF6 /* QAPManager.m */; }; 23 | 609404B221A54476005ADDF6 /* QAPMNSURLSessionTaskAgent.m in Sources */ = {isa = PBXBuildFile; fileRef = 609404A021A54476005ADDF6 /* QAPMNSURLSessionTaskAgent.m */; }; 24 | 609404B321A54476005ADDF6 /* QAPMNSURLSessionDelegateAgent.m in Sources */ = {isa = PBXBuildFile; fileRef = 609404A221A54476005ADDF6 /* QAPMNSURLSessionDelegateAgent.m */; }; 25 | 609404B421A54476005ADDF6 /* QAPMURLConnectionDelegateAgent.m in Sources */ = {isa = PBXBuildFile; fileRef = 609404A321A54476005ADDF6 /* QAPMURLConnectionDelegateAgent.m */; }; 26 | 609404B521A54476005ADDF6 /* NSURLSession+QunarAPM.m in Sources */ = {isa = PBXBuildFile; fileRef = 609404A621A54476005ADDF6 /* NSURLSession+QunarAPM.m */; }; 27 | 609404B621A54476005ADDF6 /* NSURLConnection+QunarAPM.m in Sources */ = {isa = PBXBuildFile; fileRef = 609404A721A54476005ADDF6 /* NSURLConnection+QunarAPM.m */; }; 28 | 609404B721A54476005ADDF6 /* QAPMNetworkEntry.m in Sources */ = {isa = PBXBuildFile; fileRef = 609404A921A54476005ADDF6 /* QAPMNetworkEntry.m */; }; 29 | 609404C421A58A61005ADDF6 /* QAPManager.h in Headers */ = {isa = PBXBuildFile; fileRef = 6094049321A53948005ADDF6 /* QAPManager.h */; settings = {ATTRIBUTES = (Public, ); }; }; 30 | 60A7F1D721A52D5D00D7D1CC /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 60A7F1D621A52D5D00D7D1CC /* Assets.xcassets */; }; 31 | 60A7F1DD21A52D5E00D7D1CC /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 60A7F1DC21A52D5E00D7D1CC /* main.m */; }; 32 | /* End PBXBuildFile section */ 33 | 34 | /* Begin PBXContainerItemProxy section */ 35 | 6026AF322229312C009879F1 /* PBXContainerItemProxy */ = { 36 | isa = PBXContainerItemProxy; 37 | containerPortal = 60A7F1C221A52D5B00D7D1CC /* Project object */; 38 | proxyType = 1; 39 | remoteGlobalIDString = 6094048721A52E53005ADDF6; 40 | remoteInfo = QAPM_a; 41 | }; 42 | /* End PBXContainerItemProxy section */ 43 | 44 | /* Begin PBXCopyFilesBuildPhase section */ 45 | 6094048621A52E53005ADDF6 /* CopyFiles */ = { 46 | isa = PBXCopyFilesBuildPhase; 47 | buildActionMask = 2147483647; 48 | dstPath = "include/$(PRODUCT_NAME)"; 49 | dstSubfolderSpec = 16; 50 | files = ( 51 | ); 52 | runOnlyForDeploymentPostprocessing = 0; 53 | }; 54 | /* End PBXCopyFilesBuildPhase section */ 55 | 56 | /* Begin PBXFileReference section */ 57 | 6008153221FEAEDC00C90D84 /* QAPMSystemInfo.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = QAPMSystemInfo.h; sourceTree = ""; }; 58 | 6008153321FEAEDC00C90D84 /* QAPMSystemInfo.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = QAPMSystemInfo.m; sourceTree = ""; }; 59 | 601244EA21CA67B800D0D21F /* QCacheStorage.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = QCacheStorage.h; sourceTree = ""; }; 60 | 601244EB21CA67B800D0D21F /* QCacheStorage.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = QCacheStorage.m; sourceTree = ""; }; 61 | 6026AF2A222921C2009879F1 /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; 62 | 6026AF2B222921C2009879F1 /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; 63 | 6026AF2D222922DE009879F1 /* QAPMViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = QAPMViewController.h; sourceTree = ""; }; 64 | 6026AF2E222922DE009879F1 /* QAPMViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = QAPMViewController.m; sourceTree = ""; }; 65 | 6026AF3022292961009879F1 /* README.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = SOURCE_ROOT; }; 66 | 6026AF35222931EF009879F1 /* SystemConfiguration.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SystemConfiguration.framework; path = System/Library/Frameworks/SystemConfiguration.framework; sourceTree = SDKROOT; }; 67 | 60336C5121E3260900704164 /* QAPFPSMonitor.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = QAPFPSMonitor.h; sourceTree = ""; }; 68 | 60336C5221E3260900704164 /* QAPFPSMonitor.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = QAPFPSMonitor.m; sourceTree = ""; }; 69 | 607F62DE21AF8A7E00289A78 /* QAPMonitor.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = QAPMonitor.m; sourceTree = ""; }; 70 | 607F62DF21AF8A7E00289A78 /* QAPMonitor.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = QAPMonitor.h; sourceTree = ""; }; 71 | 6094048821A52E53005ADDF6 /* libQAPM_a.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libQAPM_a.a; sourceTree = BUILT_PRODUCTS_DIR; }; 72 | 6094049321A53948005ADDF6 /* QAPManager.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = QAPManager.h; sourceTree = ""; usesTabs = 0; }; 73 | 6094049421A53948005ADDF6 /* QAPManager.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = QAPManager.m; sourceTree = ""; }; 74 | 6094049F21A54476005ADDF6 /* NSURLSession+QunarAPM.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSURLSession+QunarAPM.h"; sourceTree = ""; }; 75 | 609404A021A54476005ADDF6 /* QAPMNSURLSessionTaskAgent.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = QAPMNSURLSessionTaskAgent.m; sourceTree = ""; }; 76 | 609404A121A54476005ADDF6 /* NSURLConnection+QunarAPM.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSURLConnection+QunarAPM.h"; sourceTree = ""; }; 77 | 609404A221A54476005ADDF6 /* QAPMNSURLSessionDelegateAgent.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = QAPMNSURLSessionDelegateAgent.m; sourceTree = ""; }; 78 | 609404A321A54476005ADDF6 /* QAPMURLConnectionDelegateAgent.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = QAPMURLConnectionDelegateAgent.m; sourceTree = ""; }; 79 | 609404A421A54476005ADDF6 /* QAPMNetworkEntry.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = QAPMNetworkEntry.h; sourceTree = ""; }; 80 | 609404A521A54476005ADDF6 /* QAPMNSURLSessionTaskAgent.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = QAPMNSURLSessionTaskAgent.h; sourceTree = ""; }; 81 | 609404A621A54476005ADDF6 /* NSURLSession+QunarAPM.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSURLSession+QunarAPM.m"; sourceTree = ""; }; 82 | 609404A721A54476005ADDF6 /* NSURLConnection+QunarAPM.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSURLConnection+QunarAPM.m"; sourceTree = ""; }; 83 | 609404A821A54476005ADDF6 /* QAPMNSURLSessionDelegateAgent.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = QAPMNSURLSessionDelegateAgent.h; sourceTree = ""; }; 84 | 609404A921A54476005ADDF6 /* QAPMNetworkEntry.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = QAPMNetworkEntry.m; sourceTree = ""; }; 85 | 609404AA21A54476005ADDF6 /* QAPMURLConnectionDelegateAgent.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = QAPMURLConnectionDelegateAgent.h; sourceTree = ""; }; 86 | 609404C221A581D4005ADDF6 /* Reachability.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Reachability.h; sourceTree = ""; }; 87 | 60A7F1CA21A52D5B00D7D1CC /* QAPMApp.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = QAPMApp.app; sourceTree = BUILT_PRODUCTS_DIR; }; 88 | 60A7F1D621A52D5D00D7D1CC /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 89 | 60A7F1DB21A52D5E00D7D1CC /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 90 | 60A7F1DC21A52D5E00D7D1CC /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 91 | /* End PBXFileReference section */ 92 | 93 | /* Begin PBXFrameworksBuildPhase section */ 94 | 6094048521A52E53005ADDF6 /* Frameworks */ = { 95 | isa = PBXFrameworksBuildPhase; 96 | buildActionMask = 2147483647; 97 | files = ( 98 | ); 99 | runOnlyForDeploymentPostprocessing = 0; 100 | }; 101 | 60A7F1C721A52D5B00D7D1CC /* Frameworks */ = { 102 | isa = PBXFrameworksBuildPhase; 103 | buildActionMask = 2147483647; 104 | files = ( 105 | 6026AF36222931EF009879F1 /* SystemConfiguration.framework in Frameworks */, 106 | ); 107 | runOnlyForDeploymentPostprocessing = 0; 108 | }; 109 | /* End PBXFrameworksBuildPhase section */ 110 | 111 | /* Begin PBXGroup section */ 112 | 6008153121FEAEA900C90D84 /* SystemInfo */ = { 113 | isa = PBXGroup; 114 | children = ( 115 | 6008153221FEAEDC00C90D84 /* QAPMSystemInfo.h */, 116 | 6008153321FEAEDC00C90D84 /* QAPMSystemInfo.m */, 117 | ); 118 | path = SystemInfo; 119 | sourceTree = ""; 120 | }; 121 | 601244E921CA649A00D0D21F /* Storage */ = { 122 | isa = PBXGroup; 123 | children = ( 124 | 601244EA21CA67B800D0D21F /* QCacheStorage.h */, 125 | 601244EB21CA67B800D0D21F /* QCacheStorage.m */, 126 | ); 127 | path = Storage; 128 | sourceTree = ""; 129 | }; 130 | 6026AF34222931EE009879F1 /* Frameworks */ = { 131 | isa = PBXGroup; 132 | children = ( 133 | 6026AF35222931EF009879F1 /* SystemConfiguration.framework */, 134 | ); 135 | name = Frameworks; 136 | sourceTree = ""; 137 | }; 138 | 60336C5021E3244400704164 /* FPSMonitor */ = { 139 | isa = PBXGroup; 140 | children = ( 141 | 60336C5121E3260900704164 /* QAPFPSMonitor.h */, 142 | 60336C5221E3260900704164 /* QAPFPSMonitor.m */, 143 | ); 144 | path = FPSMonitor; 145 | sourceTree = ""; 146 | }; 147 | 6094048921A52E54005ADDF6 /* QAPM */ = { 148 | isa = PBXGroup; 149 | children = ( 150 | 6094049321A53948005ADDF6 /* QAPManager.h */, 151 | 6094049421A53948005ADDF6 /* QAPManager.m */, 152 | 6094049621A54476005ADDF6 /* Monitor */, 153 | 601244E921CA649A00D0D21F /* Storage */, 154 | ); 155 | path = QAPM; 156 | sourceTree = ""; 157 | }; 158 | 6094049621A54476005ADDF6 /* Monitor */ = { 159 | isa = PBXGroup; 160 | children = ( 161 | 607F62DF21AF8A7E00289A78 /* QAPMonitor.h */, 162 | 607F62DE21AF8A7E00289A78 /* QAPMonitor.m */, 163 | 609404C221A581D4005ADDF6 /* Reachability.h */, 164 | 6008153121FEAEA900C90D84 /* SystemInfo */, 165 | 60336C5021E3244400704164 /* FPSMonitor */, 166 | 6094049E21A54476005ADDF6 /* URLConnection */, 167 | ); 168 | path = Monitor; 169 | sourceTree = ""; 170 | }; 171 | 6094049E21A54476005ADDF6 /* URLConnection */ = { 172 | isa = PBXGroup; 173 | children = ( 174 | 609404A421A54476005ADDF6 /* QAPMNetworkEntry.h */, 175 | 609404A921A54476005ADDF6 /* QAPMNetworkEntry.m */, 176 | 6094049F21A54476005ADDF6 /* NSURLSession+QunarAPM.h */, 177 | 609404A621A54476005ADDF6 /* NSURLSession+QunarAPM.m */, 178 | 609404A521A54476005ADDF6 /* QAPMNSURLSessionTaskAgent.h */, 179 | 609404A021A54476005ADDF6 /* QAPMNSURLSessionTaskAgent.m */, 180 | 609404A821A54476005ADDF6 /* QAPMNSURLSessionDelegateAgent.h */, 181 | 609404A221A54476005ADDF6 /* QAPMNSURLSessionDelegateAgent.m */, 182 | 609404A121A54476005ADDF6 /* NSURLConnection+QunarAPM.h */, 183 | 609404A721A54476005ADDF6 /* NSURLConnection+QunarAPM.m */, 184 | 609404AA21A54476005ADDF6 /* QAPMURLConnectionDelegateAgent.h */, 185 | 609404A321A54476005ADDF6 /* QAPMURLConnectionDelegateAgent.m */, 186 | ); 187 | path = URLConnection; 188 | sourceTree = ""; 189 | }; 190 | 60A7F1C121A52D5B00D7D1CC = { 191 | isa = PBXGroup; 192 | children = ( 193 | 6094048921A52E54005ADDF6 /* QAPM */, 194 | 60A7F1CC21A52D5B00D7D1CC /* QAPMApp */, 195 | 60A7F1CB21A52D5B00D7D1CC /* Products */, 196 | 6026AF34222931EE009879F1 /* Frameworks */, 197 | ); 198 | sourceTree = ""; 199 | }; 200 | 60A7F1CB21A52D5B00D7D1CC /* Products */ = { 201 | isa = PBXGroup; 202 | children = ( 203 | 60A7F1CA21A52D5B00D7D1CC /* QAPMApp.app */, 204 | 6094048821A52E53005ADDF6 /* libQAPM_a.a */, 205 | ); 206 | name = Products; 207 | sourceTree = ""; 208 | }; 209 | 60A7F1CC21A52D5B00D7D1CC /* QAPMApp */ = { 210 | isa = PBXGroup; 211 | children = ( 212 | 6026AF2A222921C2009879F1 /* AppDelegate.h */, 213 | 6026AF2B222921C2009879F1 /* AppDelegate.m */, 214 | 6026AF2D222922DE009879F1 /* QAPMViewController.h */, 215 | 6026AF2E222922DE009879F1 /* QAPMViewController.m */, 216 | 60A7F1D621A52D5D00D7D1CC /* Assets.xcassets */, 217 | 60A7F1DB21A52D5E00D7D1CC /* Info.plist */, 218 | 60A7F1DC21A52D5E00D7D1CC /* main.m */, 219 | 6026AF3022292961009879F1 /* README.md */, 220 | ); 221 | path = QAPMApp; 222 | sourceTree = ""; 223 | }; 224 | /* End PBXGroup section */ 225 | 226 | /* Begin PBXHeadersBuildPhase section */ 227 | 609404C321A58A12005ADDF6 /* Headers */ = { 228 | isa = PBXHeadersBuildPhase; 229 | buildActionMask = 2147483647; 230 | files = ( 231 | 607F62E121AF8A7E00289A78 /* QAPMonitor.h in Headers */, 232 | 601244EC21CA67B800D0D21F /* QCacheStorage.h in Headers */, 233 | 609404C421A58A61005ADDF6 /* QAPManager.h in Headers */, 234 | 6008153421FEAEDC00C90D84 /* QAPMSystemInfo.h in Headers */, 235 | 60336C5321E3260900704164 /* QAPFPSMonitor.h in Headers */, 236 | ); 237 | runOnlyForDeploymentPostprocessing = 0; 238 | }; 239 | /* End PBXHeadersBuildPhase section */ 240 | 241 | /* Begin PBXNativeTarget section */ 242 | 6094048721A52E53005ADDF6 /* QAPM_a */ = { 243 | isa = PBXNativeTarget; 244 | buildConfigurationList = 6094048E21A52E54005ADDF6 /* Build configuration list for PBXNativeTarget "QAPM_a" */; 245 | buildPhases = ( 246 | 609404C321A58A12005ADDF6 /* Headers */, 247 | 6094048421A52E53005ADDF6 /* Sources */, 248 | 6094048521A52E53005ADDF6 /* Frameworks */, 249 | 6094048621A52E53005ADDF6 /* CopyFiles */, 250 | ); 251 | buildRules = ( 252 | ); 253 | dependencies = ( 254 | ); 255 | name = QAPM_a; 256 | productName = QAPM_a; 257 | productReference = 6094048821A52E53005ADDF6 /* libQAPM_a.a */; 258 | productType = "com.apple.product-type.library.static"; 259 | }; 260 | 60A7F1C921A52D5B00D7D1CC /* QAPMApp */ = { 261 | isa = PBXNativeTarget; 262 | buildConfigurationList = 60A7F1E021A52D5E00D7D1CC /* Build configuration list for PBXNativeTarget "QAPMApp" */; 263 | buildPhases = ( 264 | 60A7F1C621A52D5B00D7D1CC /* Sources */, 265 | 60A7F1C721A52D5B00D7D1CC /* Frameworks */, 266 | 60A7F1C821A52D5B00D7D1CC /* Resources */, 267 | ); 268 | buildRules = ( 269 | ); 270 | dependencies = ( 271 | 6026AF332229312C009879F1 /* PBXTargetDependency */, 272 | ); 273 | name = QAPMApp; 274 | productName = QAPMApp; 275 | productReference = 60A7F1CA21A52D5B00D7D1CC /* QAPMApp.app */; 276 | productType = "com.apple.product-type.application"; 277 | }; 278 | /* End PBXNativeTarget section */ 279 | 280 | /* Begin PBXProject section */ 281 | 60A7F1C221A52D5B00D7D1CC /* Project object */ = { 282 | isa = PBXProject; 283 | attributes = { 284 | LastUpgradeCheck = 1010; 285 | ORGANIZATIONNAME = mdd; 286 | TargetAttributes = { 287 | 6094048721A52E53005ADDF6 = { 288 | CreatedOnToolsVersion = 10.1; 289 | }; 290 | 60A7F1C921A52D5B00D7D1CC = { 291 | CreatedOnToolsVersion = 10.1; 292 | SystemCapabilities = { 293 | com.apple.BackgroundModes = { 294 | enabled = 1; 295 | }; 296 | }; 297 | }; 298 | }; 299 | }; 300 | buildConfigurationList = 60A7F1C521A52D5B00D7D1CC /* Build configuration list for PBXProject "QAPMApp" */; 301 | compatibilityVersion = "Xcode 9.3"; 302 | developmentRegion = en; 303 | hasScannedForEncodings = 0; 304 | knownRegions = ( 305 | en, 306 | Base, 307 | ); 308 | mainGroup = 60A7F1C121A52D5B00D7D1CC; 309 | productRefGroup = 60A7F1CB21A52D5B00D7D1CC /* Products */; 310 | projectDirPath = ""; 311 | projectRoot = ""; 312 | targets = ( 313 | 60A7F1C921A52D5B00D7D1CC /* QAPMApp */, 314 | 6094048721A52E53005ADDF6 /* QAPM_a */, 315 | ); 316 | }; 317 | /* End PBXProject section */ 318 | 319 | /* Begin PBXResourcesBuildPhase section */ 320 | 60A7F1C821A52D5B00D7D1CC /* Resources */ = { 321 | isa = PBXResourcesBuildPhase; 322 | buildActionMask = 2147483647; 323 | files = ( 324 | 6026AF3122292961009879F1 /* README.md in Resources */, 325 | 60A7F1D721A52D5D00D7D1CC /* Assets.xcassets in Resources */, 326 | ); 327 | runOnlyForDeploymentPostprocessing = 0; 328 | }; 329 | /* End PBXResourcesBuildPhase section */ 330 | 331 | /* Begin PBXSourcesBuildPhase section */ 332 | 6094048421A52E53005ADDF6 /* Sources */ = { 333 | isa = PBXSourcesBuildPhase; 334 | buildActionMask = 2147483647; 335 | files = ( 336 | 607F62E021AF8A7E00289A78 /* QAPMonitor.m in Sources */, 337 | 609404B321A54476005ADDF6 /* QAPMNSURLSessionDelegateAgent.m in Sources */, 338 | 601244ED21CA67B800D0D21F /* QCacheStorage.m in Sources */, 339 | 609404B621A54476005ADDF6 /* NSURLConnection+QunarAPM.m in Sources */, 340 | 609404B521A54476005ADDF6 /* NSURLSession+QunarAPM.m in Sources */, 341 | 6094049521A53948005ADDF6 /* QAPManager.m in Sources */, 342 | 60336C5521E3260900704164 /* QAPFPSMonitor.m in Sources */, 343 | 6008153621FEAEDC00C90D84 /* QAPMSystemInfo.m in Sources */, 344 | 609404B421A54476005ADDF6 /* QAPMURLConnectionDelegateAgent.m in Sources */, 345 | 609404B721A54476005ADDF6 /* QAPMNetworkEntry.m in Sources */, 346 | 609404B221A54476005ADDF6 /* QAPMNSURLSessionTaskAgent.m in Sources */, 347 | ); 348 | runOnlyForDeploymentPostprocessing = 0; 349 | }; 350 | 60A7F1C621A52D5B00D7D1CC /* Sources */ = { 351 | isa = PBXSourcesBuildPhase; 352 | buildActionMask = 2147483647; 353 | files = ( 354 | 6026AF2F222922DE009879F1 /* QAPMViewController.m in Sources */, 355 | 6026AF2C222921C2009879F1 /* AppDelegate.m in Sources */, 356 | 60A7F1DD21A52D5E00D7D1CC /* main.m in Sources */, 357 | ); 358 | runOnlyForDeploymentPostprocessing = 0; 359 | }; 360 | /* End PBXSourcesBuildPhase section */ 361 | 362 | /* Begin PBXTargetDependency section */ 363 | 6026AF332229312C009879F1 /* PBXTargetDependency */ = { 364 | isa = PBXTargetDependency; 365 | target = 6094048721A52E53005ADDF6 /* QAPM_a */; 366 | targetProxy = 6026AF322229312C009879F1 /* PBXContainerItemProxy */; 367 | }; 368 | /* End PBXTargetDependency section */ 369 | 370 | /* Begin XCBuildConfiguration section */ 371 | 6094048F21A52E54005ADDF6 /* Debug */ = { 372 | isa = XCBuildConfiguration; 373 | buildSettings = { 374 | CODE_SIGN_STYLE = Automatic; 375 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 376 | OTHER_LDFLAGS = ( 377 | "$(inherited)", 378 | "-ObjC", 379 | ); 380 | PRODUCT_NAME = "$(TARGET_NAME)"; 381 | SKIP_INSTALL = YES; 382 | TARGETED_DEVICE_FAMILY = "1,2"; 383 | }; 384 | name = Debug; 385 | }; 386 | 6094049021A52E54005ADDF6 /* Release */ = { 387 | isa = XCBuildConfiguration; 388 | buildSettings = { 389 | CODE_SIGN_STYLE = Automatic; 390 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 391 | OTHER_LDFLAGS = ( 392 | "$(inherited)", 393 | "-ObjC", 394 | ); 395 | PRODUCT_NAME = "$(TARGET_NAME)"; 396 | SKIP_INSTALL = YES; 397 | TARGETED_DEVICE_FAMILY = "1,2"; 398 | }; 399 | name = Release; 400 | }; 401 | 60A7F1DE21A52D5E00D7D1CC /* Debug */ = { 402 | isa = XCBuildConfiguration; 403 | buildSettings = { 404 | ALWAYS_SEARCH_USER_PATHS = NO; 405 | CLANG_ANALYZER_NONNULL = YES; 406 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 407 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 408 | CLANG_CXX_LIBRARY = "libc++"; 409 | CLANG_ENABLE_MODULES = YES; 410 | CLANG_ENABLE_OBJC_ARC = YES; 411 | CLANG_ENABLE_OBJC_WEAK = YES; 412 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 413 | CLANG_WARN_BOOL_CONVERSION = YES; 414 | CLANG_WARN_COMMA = YES; 415 | CLANG_WARN_CONSTANT_CONVERSION = YES; 416 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 417 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 418 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 419 | CLANG_WARN_EMPTY_BODY = YES; 420 | CLANG_WARN_ENUM_CONVERSION = YES; 421 | CLANG_WARN_INFINITE_RECURSION = YES; 422 | CLANG_WARN_INT_CONVERSION = YES; 423 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 424 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 425 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 426 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 427 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 428 | CLANG_WARN_STRICT_PROTOTYPES = YES; 429 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 430 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 431 | CLANG_WARN_UNREACHABLE_CODE = YES; 432 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 433 | CODE_SIGN_IDENTITY = "iPhone Developer"; 434 | COPY_PHASE_STRIP = NO; 435 | DEBUG_INFORMATION_FORMAT = dwarf; 436 | ENABLE_STRICT_OBJC_MSGSEND = YES; 437 | ENABLE_TESTABILITY = YES; 438 | GCC_C_LANGUAGE_STANDARD = gnu11; 439 | GCC_DYNAMIC_NO_PIC = NO; 440 | GCC_NO_COMMON_BLOCKS = YES; 441 | GCC_OPTIMIZATION_LEVEL = 0; 442 | GCC_PREPROCESSOR_DEFINITIONS = ( 443 | "DEBUG=1", 444 | "$(inherited)", 445 | ); 446 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 447 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 448 | GCC_WARN_UNDECLARED_SELECTOR = YES; 449 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 450 | GCC_WARN_UNUSED_FUNCTION = YES; 451 | GCC_WARN_UNUSED_VARIABLE = YES; 452 | IPHONEOS_DEPLOYMENT_TARGET = 12.1; 453 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 454 | MTL_FAST_MATH = YES; 455 | ONLY_ACTIVE_ARCH = YES; 456 | OTHER_LDFLAGS = ( 457 | "-all_load", 458 | "-ObjC", 459 | ); 460 | SDKROOT = iphoneos; 461 | }; 462 | name = Debug; 463 | }; 464 | 60A7F1DF21A52D5E00D7D1CC /* Release */ = { 465 | isa = XCBuildConfiguration; 466 | buildSettings = { 467 | ALWAYS_SEARCH_USER_PATHS = NO; 468 | CLANG_ANALYZER_NONNULL = YES; 469 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 470 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 471 | CLANG_CXX_LIBRARY = "libc++"; 472 | CLANG_ENABLE_MODULES = YES; 473 | CLANG_ENABLE_OBJC_ARC = YES; 474 | CLANG_ENABLE_OBJC_WEAK = YES; 475 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 476 | CLANG_WARN_BOOL_CONVERSION = YES; 477 | CLANG_WARN_COMMA = YES; 478 | CLANG_WARN_CONSTANT_CONVERSION = YES; 479 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 480 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 481 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 482 | CLANG_WARN_EMPTY_BODY = YES; 483 | CLANG_WARN_ENUM_CONVERSION = YES; 484 | CLANG_WARN_INFINITE_RECURSION = YES; 485 | CLANG_WARN_INT_CONVERSION = YES; 486 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 487 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 488 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 489 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 490 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 491 | CLANG_WARN_STRICT_PROTOTYPES = YES; 492 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 493 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 494 | CLANG_WARN_UNREACHABLE_CODE = YES; 495 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 496 | CODE_SIGN_IDENTITY = "iPhone Developer"; 497 | COPY_PHASE_STRIP = NO; 498 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 499 | ENABLE_NS_ASSERTIONS = NO; 500 | ENABLE_STRICT_OBJC_MSGSEND = YES; 501 | GCC_C_LANGUAGE_STANDARD = gnu11; 502 | GCC_NO_COMMON_BLOCKS = YES; 503 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 504 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 505 | GCC_WARN_UNDECLARED_SELECTOR = YES; 506 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 507 | GCC_WARN_UNUSED_FUNCTION = YES; 508 | GCC_WARN_UNUSED_VARIABLE = YES; 509 | IPHONEOS_DEPLOYMENT_TARGET = 12.1; 510 | MTL_ENABLE_DEBUG_INFO = NO; 511 | MTL_FAST_MATH = YES; 512 | OTHER_LDFLAGS = ( 513 | "-all_load", 514 | "-ObjC", 515 | ); 516 | SDKROOT = iphoneos; 517 | VALIDATE_PRODUCT = YES; 518 | }; 519 | name = Release; 520 | }; 521 | 60A7F1E121A52D5E00D7D1CC /* Debug */ = { 522 | isa = XCBuildConfiguration; 523 | buildSettings = { 524 | ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; 525 | CODE_SIGN_STYLE = Automatic; 526 | DEVELOPMENT_TEAM = UBEDVG2TLC; 527 | ENABLE_BITCODE = NO; 528 | INFOPLIST_FILE = QAPMApp/Info.plist; 529 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 530 | LD_RUNPATH_SEARCH_PATHS = ( 531 | "$(inherited)", 532 | "@executable_path/Frameworks", 533 | ); 534 | OTHER_LDFLAGS = ( 535 | "$(inherited)", 536 | "-ObjC", 537 | "-l\"QAPM_a\"", 538 | ); 539 | PRODUCT_BUNDLE_IDENTIFIER = Qunar.QAPMApp.mdd; 540 | PRODUCT_NAME = "$(TARGET_NAME)"; 541 | TARGETED_DEVICE_FAMILY = "1,2"; 542 | }; 543 | name = Debug; 544 | }; 545 | 60A7F1E221A52D5E00D7D1CC /* Release */ = { 546 | isa = XCBuildConfiguration; 547 | buildSettings = { 548 | ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; 549 | CODE_SIGN_STYLE = Automatic; 550 | DEVELOPMENT_TEAM = UBEDVG2TLC; 551 | ENABLE_BITCODE = NO; 552 | INFOPLIST_FILE = QAPMApp/Info.plist; 553 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 554 | LD_RUNPATH_SEARCH_PATHS = ( 555 | "$(inherited)", 556 | "@executable_path/Frameworks", 557 | ); 558 | OTHER_LDFLAGS = ( 559 | "$(inherited)", 560 | "-ObjC", 561 | "-l\"QAPM_a\"", 562 | ); 563 | PRODUCT_BUNDLE_IDENTIFIER = Qunar.QAPMApp.mdd; 564 | PRODUCT_NAME = "$(TARGET_NAME)"; 565 | TARGETED_DEVICE_FAMILY = "1,2"; 566 | }; 567 | name = Release; 568 | }; 569 | /* End XCBuildConfiguration section */ 570 | 571 | /* Begin XCConfigurationList section */ 572 | 6094048E21A52E54005ADDF6 /* Build configuration list for PBXNativeTarget "QAPM_a" */ = { 573 | isa = XCConfigurationList; 574 | buildConfigurations = ( 575 | 6094048F21A52E54005ADDF6 /* Debug */, 576 | 6094049021A52E54005ADDF6 /* Release */, 577 | ); 578 | defaultConfigurationIsVisible = 0; 579 | defaultConfigurationName = Release; 580 | }; 581 | 60A7F1C521A52D5B00D7D1CC /* Build configuration list for PBXProject "QAPMApp" */ = { 582 | isa = XCConfigurationList; 583 | buildConfigurations = ( 584 | 60A7F1DE21A52D5E00D7D1CC /* Debug */, 585 | 60A7F1DF21A52D5E00D7D1CC /* Release */, 586 | ); 587 | defaultConfigurationIsVisible = 0; 588 | defaultConfigurationName = Release; 589 | }; 590 | 60A7F1E021A52D5E00D7D1CC /* Build configuration list for PBXNativeTarget "QAPMApp" */ = { 591 | isa = XCConfigurationList; 592 | buildConfigurations = ( 593 | 60A7F1E121A52D5E00D7D1CC /* Debug */, 594 | 60A7F1E221A52D5E00D7D1CC /* Release */, 595 | ); 596 | defaultConfigurationIsVisible = 0; 597 | defaultConfigurationName = Release; 598 | }; 599 | /* End XCConfigurationList section */ 600 | }; 601 | rootObject = 60A7F1C221A52D5B00D7D1CC /* Project object */; 602 | } 603 | -------------------------------------------------------------------------------- /QAPMApp.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /QAPMApp.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /QAPMApp.xcodeproj/xcshareddata/xcschemes/QAPMApp.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 39 | 40 | 41 | 42 | 43 | 44 | 54 | 56 | 62 | 63 | 64 | 65 | 66 | 67 | 73 | 75 | 81 | 82 | 83 | 84 | 86 | 87 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /QAPMApp.xcodeproj/xcuserdata/mdd.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | QAPMApp.xcscheme_^#shared#^_ 8 | 9 | orderHint 10 | 0 11 | 12 | QAPM_a.xcscheme_^#shared#^_ 13 | 14 | orderHint 15 | 1 16 | 17 | 18 | SuppressBuildableAutocreation 19 | 20 | 60A7F1C921A52D5B00D7D1CC 21 | 22 | primary 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /QAPMApp/AppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.h 3 | // QAPMApp 4 | // 5 | // Created by mdd on 2019/3/1. 6 | // Copyright © 2019年 mdd. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | @interface AppDelegate : UIResponder 14 | @property (strong, nonatomic) UIWindow *window; 15 | @end 16 | 17 | NS_ASSUME_NONNULL_END 18 | -------------------------------------------------------------------------------- /QAPMApp/AppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.m 3 | // QAPMApp 4 | // 5 | // Created by mdd on 2019/3/1. 6 | // Copyright © 2019年 mdd. All rights reserved. 7 | // 8 | 9 | #import "AppDelegate.h" 10 | 11 | #import "QAPMViewController.h" 12 | #import "QAPManager.h" // 1 包含头文件 13 | 14 | @implementation AppDelegate 15 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { 16 | // 2. 在程序启动`didFinishLaunchingWithOptions`调用初始化方法,参数均必传。 17 | [QAPManager startWithPid:@"QAPMApp" cid:@"AppStore" vid:@"1.0" uid:@"uuid"]; 18 | // 3. 一些其它设置 将@"https://github.com/qunarcorp" 过滤 19 | [[QAPManager sharedInstance] setDomainFilterList:@[@"https://github.com/qunarcorp"]]; 20 | 21 | UIWindow *window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]]; 22 | [window setBackgroundColor:[UIColor clearColor]]; 23 | QAPMViewController *vc = [[QAPMViewController alloc] init]; 24 | UINavigationController *nav = [[UINavigationController alloc] initWithRootViewController:vc]; 25 | [window setRootViewController:nav]; 26 | [window makeKeyAndVisible]; 27 | [self setWindow:window]; 28 | return YES; 29 | } 30 | @end 31 | -------------------------------------------------------------------------------- /QAPMApp/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 | "idiom" : "ios-marketing", 90 | "size" : "1024x1024", 91 | "scale" : "1x" 92 | } 93 | ], 94 | "info" : { 95 | "version" : 1, 96 | "author" : "xcode" 97 | } 98 | } -------------------------------------------------------------------------------- /QAPMApp/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /QAPMApp/Assets.xcassets/LaunchImage.launchimage/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "orientation" : "portrait", 5 | "idiom" : "ipad", 6 | "minimum-system-version" : "7.0", 7 | "extent" : "full-screen", 8 | "scale" : "2x" 9 | }, 10 | { 11 | "orientation" : "landscape", 12 | "idiom" : "ipad", 13 | "minimum-system-version" : "7.0", 14 | "extent" : "full-screen", 15 | "scale" : "1x" 16 | }, 17 | { 18 | "orientation" : "landscape", 19 | "idiom" : "ipad", 20 | "minimum-system-version" : "7.0", 21 | "extent" : "full-screen", 22 | "scale" : "2x" 23 | }, 24 | { 25 | "orientation" : "portrait", 26 | "idiom" : "iphone", 27 | "minimum-system-version" : "7.0", 28 | "scale" : "2x" 29 | }, 30 | { 31 | "orientation" : "portrait", 32 | "idiom" : "iphone", 33 | "minimum-system-version" : "7.0", 34 | "subtype" : "retina4", 35 | "scale" : "2x" 36 | }, 37 | { 38 | "orientation" : "portrait", 39 | "idiom" : "ipad", 40 | "minimum-system-version" : "7.0", 41 | "extent" : "full-screen", 42 | "scale" : "1x" 43 | } 44 | ], 45 | "info" : { 46 | "version" : 1, 47 | "author" : "xcode" 48 | } 49 | } -------------------------------------------------------------------------------- /QAPMApp/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 | NSAppTransportSecurity 24 | 25 | NSAllowsArbitraryLoads 26 | 27 | 28 | UIBackgroundModes 29 | 30 | fetch 31 | 32 | UILaunchStoryboardName 33 | LaunchScreen 34 | UIRequiredDeviceCapabilities 35 | 36 | armv7 37 | 38 | UISupportedInterfaceOrientations 39 | 40 | UIInterfaceOrientationPortrait 41 | 42 | UISupportedInterfaceOrientations~ipad 43 | 44 | UIInterfaceOrientationPortrait 45 | UIInterfaceOrientationPortraitUpsideDown 46 | UIInterfaceOrientationLandscapeLeft 47 | UIInterfaceOrientationLandscapeRight 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /QAPMApp/QAPMViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // QAPMViewController.h 3 | // QAPMApp 4 | // 5 | // Created by mdd on 2019/3/1. 6 | // Copyright © 2019年 mdd. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | @interface QAPMViewController : UIViewController 14 | 15 | @end 16 | 17 | NS_ASSUME_NONNULL_END 18 | -------------------------------------------------------------------------------- /QAPMApp/QAPMViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // QAPMViewController.m 3 | // QAPMApp 4 | // 5 | // Created by mdd on 2019/3/1. 6 | // Copyright © 2019年 mdd. All rights reserved. 7 | // 8 | 9 | #import "QAPMViewController.h" 10 | #import "QAPMonitor.h" 11 | @interface QAPMViewController () 12 | 13 | @end 14 | 15 | @implementation QAPMViewController 16 | 17 | - (void)viewDidLoad { 18 | [super viewDidLoad]; 19 | self.view.backgroundColor = [UIColor whiteColor]; 20 | [self helloQAPM]; 21 | [QAPMonitor netType]; 22 | } 23 | 24 | - (void)helloQAPM { 25 | self.title = @"APM"; 26 | UILabel *lbl = [[UILabel alloc] initWithFrame:self.view.bounds]; 27 | lbl.text = @"Hello QAPM"; 28 | lbl.textAlignment = NSTextAlignmentCenter; 29 | lbl.font = [UIFont systemFontOfSize:20]; 30 | [self.view addSubview:lbl]; 31 | } 32 | 33 | @end 34 | -------------------------------------------------------------------------------- /QAPMApp/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // QAPMApp 4 | // 5 | // Created by mdd on 2018/11/21. 6 | // Copyright © 2018年 mdd. 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## 功能介绍 2 | 3 | QAPM是去哪儿使用的APP监控系统。已在内部稳定运行3年。提供功能如下:网络请求时长、网络数据流量、网络请求成功或失败以及失败原因等。帧率检测、CPU使用率、电池电量。 4 | 5 | ## 如何使用 6 | 7 | * 支持iOS8以上版本 8 | * 包含头文件`#import "QAPManager.h"` 9 | 10 | * 在程序启动`didFinishLaunchingWithOptions`调用初始化方法,参数均必传。 11 | 12 | ``` 13 | 14 | pid 产品号,假设公司有多个APP,用此区分 15 | cid 渠道标识,比如越狱渠道、App Store、自己分发等等 16 | vid 版本号,程序版本号,一般内部使用 17 | uid 设备号,唯一区分一台设备,比如idfa 18 | 19 | + (void)startWithPid:(nonnull NSString *)pid 20 | cid:(nullable NSString *)cid 21 | vid:(nullable NSString *)vid 22 | uid:(nullable NSString *)uid; 23 | ``` 24 | 25 | * 检测网络状态`Reachability.m` 26 | 27 | 目前大多工程一般都包含了`Reachability.m`文件,为了避免链接错误,没有将`Reachability.m`放入本工程。如果你的工程没有包含`Reachability.m`,请将本工程目录的`QAPM/Monitor/Reachability.m` 加入工程中参与编译;如果已经包含请无视这里 28 | 29 | * 其它一些设置 30 | 31 | ``` 32 | /** 33 | 域名过滤,当请求的url.absoluteString包含set的内容时,不上传监控数据。实时生效 34 | 域名比如 苹果网站:www.apple.com , 或者更具体的 www.apple.com/watch ,但是前者已包含后者 35 | */ 36 | @property (nonatomic, strong) NSArray *domainFilterList; 37 | ``` 38 | 39 | ## 网络数据收集部分 40 | 41 | 比较通用的策略:一般有三种 42 | 43 | * 一、NSURLProtocol 44 | * 二、hook NSURLSession NSURLConnection 45 | * 三、苹果提供的 NSURLSessionTaskMetrics 46 | 47 | 我们采用第二种,第三种作为辅助。不使用第一种主要是因为兼容性不好,如果另外一个人也实现了NSURLProtocol,他也拦截,那我们就拦截不了了。除非他能再把这个请求重新发一遍,但是这样就对代码侵入太多了。 48 | 49 | NSURLSessionTaskMetrics 时间信息全,且准确,比较真实的网络请求时间(用hook方法,会有延时,特别是请求多,CPU繁忙时)。只是官方只支持NSURLSession且iOS10以后,想监控iOS8、iOS9、NSURLConnection就需要使用私有API了,有上架风险。且`[NSURLSession sharedSession]`也没有找到合适的解决方案实现监控。 50 | 51 | 总结下: 52 | 53 | hook方式获取网络的请求时长,流量、状态。iOS10以后NSURLSession 的非`[NSURLSession sharedSession]`发出请求用NSURLSessionTaskMetrics方式作为补充。因为它计算出来的流量、网速更真实。而hook计算出来的更贴近开发者,也更贴近用户的体验感受,两者各有用途。 54 | 55 | #### 统计范围 56 | 57 | 整体统计范围: 58 | 59 | * 通过NSURLSession、NSURLConnection发出的请求均会统计到。 60 | * UIWebview、WKWebview的ajax请求统计不到 61 | * TCP请求统计不到 62 | 63 | 一些过滤: 64 | 65 | * 请求发生在后台,或者请求过程中有退到后台 66 | * 请求被主动取消 67 | * 请求url长度等于0的 68 | * 图片请求(pathExtension 包含 png、jpg、jpeg、gif、webp判定为图片请求)成功且时长小于2秒 69 | * 请求url是 网络监控日志上传发出的请求 70 | * 只统计url 以 http:// 或者 https:// 开头的 71 | 72 | ## 存储上传部分大致实现 73 | 74 | * 存储和上传都在一个串行队列执行,线程安全 75 | * 日志会以10条作为一个文件,存入内存,当内存超过5个文件时,存入磁盘。 76 | * 够10条则上传,一次上传一个文件。上传成功删除文件。 77 | 78 | 如果想查看,存的日志可以: 79 | 80 | * 包含头文件`#import "QAPManager.h"` 81 | * 获取文件操作实例:`+ (QCacheStorage *)releaseLogInstance;` 82 | * 在头文件`#import "QCacheStorage.h"`中提供一下方法: 83 | 84 | ``` 85 | /// 获取当前所有文件名 86 | - (void)fileLists:(QGetFileListCompleteBlock)block; 87 | /// 根据文件名,删除一个文件 88 | - (void)deleteFile:(NSString *)fileName; 89 | /// 存储文件到本地或者 90 | - (void)saveData:(NSDictionary *)data toFile:(NSString *)fileName; 91 | /// 根据文件名获取文件 92 | - (void)fileDataWithFile:(NSString *)file withCompleteBlock:(QGetFileDataCompleteBlock)block; 93 | ``` 94 | 95 | 96 | 97 | 98 | 99 | 100 | --------------------------------------------------------------------------------