客戶端(iOS)性能穩(wěn)定方案

一、性能檢測工具工具(Dokit)

注意:官方提醒 ????DoKit的所有功能都只針對Debug環(huán)境,Release環(huán)境未經(jīng)過實際驗證(推薦單獨創(chuàng)建test分支,需要檢測的版本內(nèi)容 merge to test,test 不合并到 其他分支,只用來測試)

1.集成方式

1.1 cocoapods依賴
   pod 'DoraemonKit/Core', '~> 3.0.4', :configurations => ['Debug'] #必選
   pod 'DoraemonKit/WithGPS', '~> 3.0.4', :configurations => ['Debug'] #可選
   pod 'DoraemonKit/WithLoad', '~> 3.0.4', :configurations => ['Debug'] #可選
   pod 'DoraemonKit/WithLogger', '~> 3.0.4', :configurations => ['Debug'] #可選
   pod 'DoraemonKit/WithDatabase', '~> 3.0.4', :configurations => ['Debug'] #可選
   pod 'DoraemonKit/WithMLeaksFinder', '~> 3.0.4', :configurations => ['Debug'] #可選
   pod 'DoraemonKit/WithWeex', '~> 3.0.4', :configurations => ['Debug'] #可選
subspec 功能介紹(說明文檔
Core 作為核心,必須引入,其他幾個subspec你可以按照你的需求選擇性接入。
WithGPS 模擬定位功能
WithLoad Load耗時檢測的話
WithLogger 基于CocoaLumberjack的日志
WithDatabase 基于YYDebugDatabase可以在網(wǎng)頁端調(diào)式數(shù)據(jù)庫的話
WithMLeaksFinder 基于MLeaksFinder查找內(nèi)存泄漏
WithWeex Weex的相關(guān)專項工具

1.2: Carthage依賴

說明文檔

    git "https://github.com/didi/DoraemonKit.git"  "c3.0.0"

tip:只在Debug環(huán)境中進行集成,不要帶到線上。有一些hook操作會污染線上代碼。

2.項目集成

#ifdef DEBUG
#import <DoraemonKit/DoraemonManager.h>
#endif

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    #ifdef DEBUG
    [[DoraemonManager shareInstance] installWithPid:@"productId"];//productId為在“平臺端操作指南”中申請的產(chǎn)品id
    #endif
}

通過以上步驟你就可以使用DorameonKit所有的內(nèi)置工具集合。如果你想把自己與業(yè)務(wù)相關(guān)的一些工具代碼加入到DoraemonKit中做統(tǒng)一管理的話,你可以按照3的步驟來做。

3. 添加自定義測試模塊到Doraemon面板中(非必要)

比如我們要在Doraemon面板中添加一個環(huán)境切換的功能。

第一步:新建一個類,實現(xiàn)DoraemonPluginProtocol協(xié)議中的pluginDidLoad方法,該方法就是以后點擊Doraemon工具面板中“環(huán)境切換”按鈕觸發(fā)的事件。

比如以代駕司機端為例,點擊按鈕之后會進入環(huán)境切換頁面。

@implementation KDDoraemonEnvPlugin
- (void)pluginDidLoad{
    [APP_INTERACOTR.rootNav openURL:@"KDSJ://KDDoraemonSFViewController"];
    [[DoraemonManager shareInstance] hiddenHomeWindow];
}
 @end

第二步:在Doraemon初始化的地方添加第一步中添加的“環(huán)境切換”插件

調(diào)用DoraemonManager的以下方法:

[[DoraemonManager shareInstance] addPluginWithTitle:@"環(huán)境切換" icon:@"doraemon_default" desc:@"用于app內(nèi)部環(huán)境切換功能" pluginName:@"KDDoraemonEnvPlugin" atModule:@"業(yè)務(wù)專區(qū)"];

依次代表 集成到DoraemonKit面板中的標題,圖標,描述,插件名稱,和所屬于的模塊。

注意:接入過程中,如果接入WithMLeaksFinder,因為依賴facebook開源的FBRetainCycleDetector,它的最后更新時間 on Nov 21, 2017,比較久了,會有沖突,需要適配,

適配方案:podfile中添加

  post_install do |installer|
    ## Fix for XCode 12.5
    find_and_replace("Pods/FBRetainCycleDetector/FBRetainCycleDetector/Layout/Classes/FBClassStrongLayout.mm",
      "layoutCache[currentClass] = ivars;", "layoutCache[(id<NSCopying>)currentClass] = ivars;")
    installer.pods_project.targets.each do |target|
      target.build_configurations.each do |config|
        config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '10.0'
        #消除TwitterCore警告
        xcconfig_path = config.base_configuration_reference.real_path
        xcconfig = File.read(xcconfig_path)
        xcconfig_mod = xcconfig.gsub(/-framework "TwitterCore"/, "")
        File.open(xcconfig_path, "w") { |file| file << xcconfig_mod }

        if config.name == 'Debug'
          config.build_settings['OTHER_SWIFT_FLAGS'] = ['$(inherited)', '-Onone']
          config.build_settings['SWIFT_OPTIMIZATION_LEVEL'] = '-Owholemodule'
        end

        if config.name == 'Test'
          config.build_settings['OTHER_SWIFT_FLAGS'] = ['$(inherited)', '-Onone']
          config.build_settings['SWIFT_OPTIMIZATION_LEVEL'] = '-Owholemodule'
        end

      end
    end
    installer.pods_project.build_configurations.each do |config|
      config.build_settings[‘EXCLUDED_ARCHS[sdk=iphonesimulator*]’] = ‘a(chǎn)rm64’
    end
  end


  def find_and_replace(dir, findstr, replacestr)
    Dir[dir].each do |name|
        text = File.read(name)
        replace = text.gsub(findstr,replacestr)
        if text != replace
            puts "Fix: " + name
            File.open(name, "w") { |file| file.puts replace }
            STDOUT.flush
        end
    end
    Dir[dir + '*/'].each(&method(:find_and_replace))
  end

4. 使用說明

接入成功之后樣式如下圖


2707146-5eefd26025403af3.jpeg

接入Dokit之后,可以根據(jù)自己需求,單獨檢測某一項指標,比如 幀率、CPU、內(nèi)存、卡頓、層級...

同時測試過程中,也可以進行健康體檢,體檢完成之后將本次體檢數(shù)據(jù)上傳至Dokit官網(wǎng)中,查看本次體檢結(jié)果。如下圖


image.png

二、靜態(tài)代碼審查工具(OCLint

OCLint是一個通過檢查C,C++或Objective-C代碼來提高代碼質(zhì)量、降低錯誤率的靜態(tài)代碼分析工具,代碼通過OCLint檢測后,可以發(fā)現(xiàn)一些潛在的問題,如:

- 可能的bug:if/else/try/catch/finally 空語句空變量
- 代碼無用:并未使用的本地變量和參數(shù)
- 代碼過于復(fù)雜:高復(fù)雜度的循環(huán)、判斷
- 代碼冗余:冗余的if判斷和多余的括號
- 代碼異味:長的方法和長參數(shù)列表
- 不好的嘗試:反向邏輯、參數(shù)重復(fù)賦值

靜態(tài)代碼分析是一個很重要的技術(shù)發(fā)現(xiàn)編譯器中那些不可視的缺點,OCLint自動完成這些檢測需要依賴以下特點:

- 依賴源代碼的抽象語法樹來保證精準度和效率,盡可能減少誤報,避免有用的結(jié)果被跳過;
- 動態(tài)加載規(guī)則到系統(tǒng)中(甚至是運行期間加載規(guī)則);
- 靈活可擴展的配置保證用戶可以定制化靜態(tài)代碼檢查工具;
- 為了技術(shù)問題盡早的被修復(fù),降低維護成本,使用命令行運行命令,在代碼開發(fā)過程中,對代碼進行持續(xù)集成和檢測;

1. OCLint的安裝

1.1 安裝OCLint

在安裝前,確保安裝了 homebrew。然后先執(zhí)行brew命令安裝第三方依賴庫-oclint/formulae,之后再安裝oclint,具體兩個指令如下:

brew tap oclint/formulae
brew install oclint

安裝正常完成,即證明OCLint安裝成功(使用 oclint --version 查看是否安裝成功)。

DavindeMacBook-Pro:~ davin$ brew tap oclint/formulae
Running `brew update --auto-update`...
==> Tapping oclint/formulae
Cloning into '/usr/local/Homebrew/Library/Taps/oclint/homebrew-formulae'...
remote: Enumerating objects: 58, done.
remote: Counting objects: 100% (9/9), done.
remote: Compressing objects: 100% (7/7), done.
remote: Total 58 (delta 1), reused 3 (delta 1), pack-reused 49
Receiving objects: 100% (58/58), 9.37 KiB | 54.00 KiB/s, done.
Resolving deltas: 100% (13/13), done.
Tapped 1 formula (14 files, 18KB).
DavindeMacBook-Pro:~ davin$ brew install oclint
Warning: Treating oclint as a formula. For the cask, use homebrew/cask/oclint
Warning: You are using macOS 13.
We do not provide support for this pre-release version.
You will encounter build failures with some formulae.
Please create pull requests instead of asking for help on Homebrew's GitHub,
Twitter or any other official channels. You are responsible for resolving
any issues you experience while you are running this
pre-release version.

==> Downloading https://github.com/oclint/oclint/releases/download/v20.11/oclint-20.11-llvm-11.0.0-x8
######################################################################## 100.0%
==> Installing oclint from oclint/formulae
??  /usr/local/Cellar/oclint/20.11: 488 files, 140.2MB, built in 6 seconds
==> Running `brew cleanup oclint`...
Disable this behaviour by setting HOMEBREW_NO_INSTALL_CLEANUP.
Hide these hints with HOMEBREW_NO_ENV_HINTS (see `man brew`).
==> `brew cleanup` has not been run in the last 30 days, running now...
Disable this behaviour by setting HOMEBREW_NO_INSTALL_CLEANUP.


DavindeMacBook-Pro:~ davin$ oclint --version
OCLint (http://oclint.org/):
  OCLint version 20.11.
  Built Nov 14 2020 (10:19:05).
DavindeMacBook-Pro:~ davin$ 
1.2 安裝xcodebuild

xcodebuild是xcode的編譯命令,xcode 下載安裝好就已經(jīng)成功安裝了,無需額外安裝

1.3 安裝xcpretty

需要使用OCLint對日志信息進行分析運行命令,安裝xcpretty,使用xcpretty命令分析日志信息。xcpretty是用來格式化xcodebuild輸出的工具,使用ruby開發(fā)。安裝:

gem install xcpretty

安裝完成????

DavindeMacBook-Pro:~ davin$ gem install xcpretty
Fetching xcpretty-0.3.0.gem
Fetching rouge-2.0.7.gem
Successfully installed rouge-2.0.7
Successfully installed xcpretty-0.3.0
Parsing documentation for rouge-2.0.7
Installing ri documentation for rouge-2.0.7
Parsing documentation for xcpretty-0.3.0
Installing ri documentation for xcpretty-0.3.0
Done installing documentation for rouge, xcpretty after 1 seconds
2 gems installed

2. OCLint命令行使用

2.1 進入指定項目(測試demo)
DavindeMacBook-Pro:~ davin$ cd Desktop
DavindeMacBook-Pro:OnPro_IOS davin$ cd AnimationTest/
2.2 查看項目基本信息
xcodebuild -list

打印輸出

DavindeMacBook-Pro:AnimationTest davin$ xcodebuild -list
2023-04-06 09:52:23.967 xcodebuild[7033:57226] DVTCoreDeviceEnabledState: DVTCoreDeviceEnabledState_Disabled set via user default (DVTEnableCoreDevice=disabled)
Command line invocation:
    /Applications/Xcode.app/Contents/Developer/usr/bin/xcodebuild -list

User defaults from command line:
    IDEPackageSupportUseBuiltinSCM = YES

Information about project "AnimationTest":
    Targets:
        AnimationTest
        AnimationTestTests
        AnimationTestUITests

    Build Configurations:
        Debug
        Release

    If no build configuration is specified and -scheme is not passed then "Release" is used.

    Schemes:
        AnimationTest
2.3 編譯項目

先clean指定項目HDLOnPro,因為集成了pod,所以使用HDLOnPro.xcworkspace;然后再Debug 編譯項目了;最后通過xcpretty,使用 -r json-compilation-database 可以生成指定格式的數(shù)據(jù)。編譯成功后,會在項目的文件夾下出現(xiàn) compile_commands.json 文件

xcodebuild -scheme AnimationTest -workspace AnimationTest.xcworkspace clean && xcodebuild -scheme AnimationTest -workspace AnimationTest.xcworkspace -configuration Debug COMPILER_INDEX_STORE_ENABLE=NO | xcpretty -r json-compilation-database -o compile_commands.json

編譯成功會出現(xiàn)? Build Succeeded字樣

注意項

  • 如果項目使用了 Cocopod,則需要指定 -workspace xxx.workspace
  • 每次編譯之前需要 clean (xcodebuild clean)
  • 如果你的xcode 版本為14.3可能會出現(xiàn)下面錯誤
?  ld: file not found: /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/arc/libarclite_iphoneos.a

?  clang: error: linker command failed with exit code 1 (use -v to see invocation)

解決方案:
在podfile文件中添加

  post_install do |installer|
    installer.generated_projects.each do |project|
      project.targets.each do |target|
          target.build_configurations.each do |config|
              config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '11.0'
           end
      end
    end
  end

pod install后重新執(zhí)行編譯命令

2.4 生成 html 報表
  • 使用 oclint-json-compilation-database 命令對上一步生成的json數(shù)據(jù)進行分析,對項目代碼進行分析,最終生成report.html文件。OCLint目前支持輸出html,json,xml,pmd,Xcode格式文件
 oclint-json-compilation-database -e Pods -- -report-type html -o oclintReport.html

error1
oclint: error: compilation contains multiple jobs:
解決方案:
將 Project 和 Targets 中 Building Settings 下的 COMPILER_INDEX_STORE_ENABLE 設(shè)置為 NO

image.png

podfile中添加config.build_settings['COMPILER_INDEX_STORE_ENABLE'] = "NO"

post_install do |installer|
    installer.pods_project.targets.each do |target|
        target.build_configurations.each do |config|
            config.build_settings['COMPILER_INDEX_STORE_ENABLE'] = "NO"
            config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '12.0'
        end
    end
end
  • 看到有報錯,但是報錯信息太多了,不好定位,利用下面的腳本則可以將報錯信息寫入 log 文件,方便查看
oclint-json-compilation-database -e Pods -- -report-type html -o oclintReport.html 2>&1 | tee 1.log
  • 如果項目工程太大,整個 lint 會比較耗時,所幸 oclint 支持針對某個代碼文件夾進行 lint
oclint-json-compilation-database -i 需要靜態(tài)分析的文件夾或文件 -- -report-type html -o oclintReport.html  其他的參數(shù)
  • 如有錯誤可根據(jù)下一小節(jié)內(nèi)容進行修改,或查找其他資料解決。執(zhí)行成功后,查看 html 文件可以具體定位哪個代碼文件,哪一行哪一列有什么問題,方便修改


    image.png

三、靜態(tài)代碼審查(infer)

Infer 是一個Facebook 的靜態(tài)分析工具。Infer 可以分析 Objective-C, Java 或者 C 代碼,報告潛在的問題。

Infer捕捉的bug類型

  • C/OC中捕捉的bug類型
    • 1:Resource leak
    • 2:Memory leak
    • 3:Null dereference
    • 4:Premature nil termination argument
  • 只在 OC中捕捉的bug類型
    • 1:Retain cycle
    • 2:Parameter not null checked
    • 3:Ivar not null checked

Infer優(yōu)點

  • 效率高,規(guī)模大,幾分鐘能掃描數(shù)千行代碼;
  • 支持增量及非增量分析(后邊會解釋)
  • 分解分析,整合輸出結(jié)果。(infer能將代碼分解,小范圍分析后再將結(jié)果整合在一起,兼顧分析的深度和速度)

Infer的安裝與使用

brew、xcpretty 、xcodebuild同上,都是需要安裝的

1. 下載infer:https://github.com/facebook/infer
2. 環(huán)境依賴
image.png
brew install autoconf automake cmake opam pkg-config sqlite gmp mpfr java
3. 安裝infer
image.png
# Checkout Infer
git clone https://github.com/facebook/infer.git
cd infer
# Compile Infer
./build-infer.sh clang
# install Infer system-wide...
sudo make install
# ...or, alternatively, install Infer into your PATH
export PATH=`pwd`/infer/bin:$PATH
4. 將infer的執(zhí)行目錄配置到環(huán)境變量
# 將infer/bin添加到.bash_profile或.zshrc文件中
export PATH="$PATH:[替換為infer-main保存的路徑]/infer/bin"

# 修改.bash_profile文件,并更新環(huán)境變量 (適用于: Linux、macOS Mojave 之前的系統(tǒng))
open -e $HOME/.bash_profile
source $HOME/.bash_profile

# 修改.zshrc文件,并更新環(huán)境變量 (適用于:macOS Catalina)
open -e $HOME/.zshrc
source $HOME/.zshrc
5. 驗證是否安裝成功:
infer --version

infer使用

#先清理緩存
xcodebuild -scheme AnimationTest -workspace AnimationTest.xcworkspace clean 

#編譯數(shù)據(jù) database
infer -- xcodebuild -workspace AnimationTest.xcworkspace -scheme AnimationTest -configuration Debug
#xcodebuild -workspace AnimationTest.xcworkspace -scheme AnimationTest -sdk iphonesimulator  -arch x86_64 COMPILER_INDEX_STORE_ENABLE=NO| tee xcodebuild.log | xcpretty -r json-compilation-database -o compile_commands.json


# 執(zhí)行infer指令
infer run --keep-going --skip-analysis-in-path Pods --compilation-database-escaped compile_commands.json

# 掃描生成報告
infer-explore --html

結(jié)果????


image.png

四、書寫習(xí)慣&非空判斷

日常寫代碼的時候多注意非空判斷,多注意數(shù)據(jù)越界,

1. 通過Category處理了一些nil的情況,可以起到防crash的效果

參考demo:WXWCategory

example:獲取字典鍵值對時

- (NSArray *)getArray:(NSString *)name {
    id value = [self replaceValue:name];
    
    if (value == nil || value == [NSNull null]) {
        return nil;
    }
    if ([value isKindOfClass:[NSArray class]]) {
        return value;
    }
    return nil;
}
- (NSString *)getString:(NSString *)name {
    id value = [self replaceValue:name];
    
    if (value == nil || value == [NSNull null]) {
        return @"";
    }
    if ([[value description] isEqualToString:@"(null)"]) {
        return @"";
    }
    if ([value isKindOfClass:[NSString class]]) {
        return (NSString*)value;
    }
    if ([value isKindOfClass:[NSNumber class]]) {
        return [value stringValue];
    }
    return @"";
}
- (NSDictionary *)getDictionary:(NSString *)name {
    id value = [self replaceValue:name];
    
    if (value == nil || value == [NSNull null]) {
        return nil;
    }
    if ([value isKindOfClass:[NSDictionary class]]) {
        return value;
    }
    return nil;
}
2. 數(shù)組下標獲取元素前判空
- (void)configCell:(ChatMessageCell *)cell indexRow:(NSIndexPath *)indexPath {
    if (self.mMessageArray.count <= indexPath.row) return;

    MessageModel *messageModel = self.mMessageArray[indexPath.row];
    
    [cell congifAIFriendAvatar:self.friendModel.headPortrait];
    [cell configWithMessage:messageModel.message isQuesstion:messageModel.isQuestion askTime:messageModel.askTime isLoading:messageModel.isLoading];
}

五、Crash自動防護(XXShield)

XXShield 庫有兩個重要的功能:

  • 防止Crash
  • 捕獲異常狀態(tài)下的崩潰信息

防止Crash包含一下幾種類型(詳細說明)

  1. Unrecognized Selector Crash
  2. KVO Crash
  3. Container Crash
  4. NSNotification Crash
  5. NSNull Crash
  6. NSTimer Crash
  7. 野指針 Crash

1. XXShield 集成

cocoaPods接入

  pod "XXShield"

2.使用


/**
 注冊匯報中心
 
 @param record 匯報中心
 */
+ (void)registerRecordHandler:(id<XXRecordProtocol>)record;

/**
 注冊SDK,默認只要開啟就打開防Crash,如果需要DEBUG關(guān)閉,請在調(diào)用處使用條件編譯
 本注冊方式不包含EXXShieldTypeDangLingPointer類型
 */
+ (void)registerStabilitySDK;

/**
 本注冊方式不包含EXXShieldTypeDangLingPointer類型
 
 @param ability ability
 */
+ (void)registerStabilityWithAbility:(EXXShieldType)ability;

/**
 ///注冊EXXShieldTypeDangLingPointer需要傳入存儲類名的array,暫時請不要傳入系統(tǒng)框架類
 
 @param ability ability description
 @param classNames 野指針類列表
 */
+ (void)registerStabilityWithAbility:(EXXShieldType)ability withClassNames:(nonnull NSArray<NSString *> *)classNames;

常見crash 測試案例:(添加XXShield之后不會出現(xiàn)崩潰)

#warning mock crash
    [XXShieldSDK registerStabilitySDK];

    //  Unrecoginzed Selector Crash (點擊按鈕之后不會出現(xiàn)崩潰)
    UIButton *aaa = [UIButton buttonWithType:UIButtonTypeCustom];
    aaa.frame = CGRectMake(200, CGRectGetHeight(self.view.bounds) - 200, 100, 40);
    [aaa setTitle:@"測試" forState:UIControlStateNormal];
    [aaa addTarget:self action:@selector(aaaAction) forControlEvents:UIControlEventTouchUpInside];
    [self.view addSubview:aaa];
    
    // NSNull Crash (setObject:nil 不會出現(xiàn)崩潰)
    NSMutableDictionary *dictM = [[NSMutableDictionary alloc] init];
    [dictM setObject:nil forKey:@"xxshild"];
    NSLog(@"############:%@", [dictM objectForKey:@"xxshild"]);
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容