<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          當(dāng)我使用ChatGPT寫一個(gè)IOS的熱更新

          共 7480字,需瀏覽 15分鐘

           ·

          2023-06-08 06:37

          前些天注冊了一個(gè)ChatGPT賬號(hào),總是問一些沒有營養(yǎng)的問題顯得太過無聊。想起之前寫了一個(gè)熱更新的文章,僅寫了Android端的實(shí)現(xiàn),沒有寫IOS端的實(shí)現(xiàn)方式。

          為什么不寫IOS端的實(shí)現(xiàn)呢,主要就是不會(huì)寫Objective-C/Swift。既然自己不寫,那就讓最強(qiáng)人工智能來幫我寫一個(gè)。

          熱更新的思路可以查看上一篇《React Native 熱更新探索》,再簡單梳理一下,大概流程如下:

          1. 借助Metro生成熱更新文件
          2. 構(gòu)建熱更新服務(wù),用于下發(fā)熱更新文件
          3. Native端支持熱更新,能夠切換熱更新文件
          4. 檢查熱更新,這一步可以在客戶端也可以在RN端,這里用RN端實(shí)現(xiàn)比較簡單

          生成熱更新文件

          這一步比較簡單,直接調(diào)用Metro即可。

                
                npx react-native bundle --entry-file index.js --bundle-output ./update/bundle/index.android.bundle --platform ios --assets-dest ./update/bundle --dev false --reset-cache

          熱更新服務(wù)

          熱更新服務(wù),可簡單也可以復(fù)雜,簡單的話,只需要判斷一下版本號(hào),給出新的熱更新文件下載地址即可。復(fù)雜來做,需要可能需要考慮多項(xiàng)目、多版本、權(quán)限控制等諸多因素,這里不做贅述,依舊起一個(gè)簡單的PHP Mock Server來驗(yàn)證。

                
                <?php
          $versionCode = $_GET['versionCode'] ? (int)$_GET['versionCode'] : 0;
          $updateList = [
          100000001 => [
          "url" => "http://10.12.164.89:8360/bundle/update.zip",
          "content" => "1. 增加熱更新功能測試。\n2. 增加圖片。\n3. 殺了一個(gè)設(shè)計(jì)師祭天。",
          "version" => "1.0.1"
          ]
          ];
          $res = null;
          foreach($updateList as $key => $val) {
          if ($key > $versionCode) {
          $res = $val;
          }
          }
          echo json_encode($res);
          ?>

          Ios 端的熱更新改造

          原理上與Android差不多,也是在ReactHost實(shí)例化時(shí),通過暴露的接口修改ReactHost加載Bundle文件的地址,在Android需要修改getJSBundleFile這個(gè)函數(shù),查找到熱更新文件并返回其路徑。

          在IOS中原理肯定是類似的,IOS儲(chǔ)備知識(shí)不多,看看工程代碼猜一猜看,仔細(xì)查看,在AppDelegate.mm文件中,可以發(fā)現(xiàn)如下可疑代碼。

                
                - (NSURL *)sourceURLForBridge:(RCTBridge *)bridge
          {
          #if DEBUG
          return [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index"];
          #else
          return [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"];
          #endif
          }

          功能上與Android端類似,命名上也能理解這個(gè)函數(shù)的含義是獲取ReactNative的入口文件。重點(diǎn)來了,雖然知道思路,但是苦于不會(huì)寫Objective-C的代碼啊,是時(shí)候讓ChatGPT出手了。

          ChatGPT幫我寫代碼

          聽說ChatGPT很聰明,那我也不兜著了。直接上來就是一句: 幫我寫一個(gè)react native ios端替換bundle文件位置的代碼

          859cb9aca227ae521d50cdeacbb462e3.webp


          也不能說毫無關(guān)聯(lián),有那么點(diǎn)意思,但是明顯我想要的效果。

          于是我再問: 我需要的是ios 端代碼修改react native bundle文件位置的功能

          290ca0a852cd03c214d8672b155e6fa6.webp

          我希望把這個(gè)功能做成一個(gè)原生模塊,這樣代碼可以復(fù)用: 將這個(gè)功能封裝成一個(gè)react native 原生模塊,包名叫@leona-rn/client

          588df197318f4446dca14cff9e323f16.webp

          看起來更好了一點(diǎn),但是這樣也完不成熱更新的流程。原來的文章中知識(shí)簡單讀取一個(gè)固定位置Bundle文件來完成熱更新。這次我希望更加完善一點(diǎn):

          1. 可以根據(jù)熱更新接口給出的結(jié)果來選擇最新的Bundle包
          2. 多次熱更新的結(jié)果不沖突,永遠(yuǎn)使用最新的Bundle包

          想實(shí)現(xiàn)這一點(diǎn),可以將熱更新包都下載到一個(gè)固定的位置,并使用版本號(hào)作為文件名,查詢熱更新文件時(shí),遍歷目錄下的熱更新文件版本號(hào),選出版本號(hào)最大的那個(gè),但是隨著熱更新越來越多,文件肯定也越來越多,遍歷文件目錄可能會(huì)成為性能問題。

          也可以使用數(shù)據(jù)庫獲取其他本地存儲(chǔ)方案將最新的版本號(hào)存起來,每次只需要讀取這個(gè)版本號(hào)即可,這里選取最簡單的文件存儲(chǔ):

                
                // manifest.json
          {
          "newest": 100
          }

          在拉取熱更新文件的時(shí)候,只需要替換這個(gè)版本號(hào)即可,在梳理一下,獲取熱更新地址的邏輯變成了:

          1. 讀取manifest.json文件,如果存在newest字段且大于0,就返回版本號(hào)對應(yīng)的文件地址。
          2. 如果不滿足條件1,則返回空,使用內(nèi)置的Bundle包。

          第2個(gè)邏輯可能存在漏洞,如果某些原因,導(dǎo)致manifest.json文件出錯(cuò),可能會(huì)導(dǎo)致熱更新失效,從而導(dǎo)致應(yīng)用回退使用到內(nèi)置包。兼容這個(gè)邏輯也比較復(fù)雜,這里選擇在熱更新階段保證manifest.json文件的正確性,從而保障整個(gè)熱更新流程的安全性。

          于是我把需求整理了一下,發(fā)給了他一個(gè)完整的需求

                
                我需要一個(gè)叫 @leona-rn/client 的react native 原生模塊,有以下要求:
          1. 提供一個(gè)對外函數(shù),這個(gè)函數(shù)可以讀取應(yīng)用程序沙盒文檔目錄下的 /updates/manifest.json 文件。
          manifest.json中有兩個(gè)字段,分別是current和newest,它們都是數(shù)字類型。
          如果newest有數(shù)值,返回應(yīng)用程序沙盒文檔目錄 + /updates/{newest}/index.ios.js 組成的字符串,并且將current置為newest的值,newest置為空,再將新的json數(shù)據(jù)寫回到原來的manifest.json文件中。
          如果current有數(shù)值,返回應(yīng)用程序沙盒文檔目錄 + /updates/{current}/index.ios.js 組成的字符串。
          如果都不滿足條件,返回空
          2. 這個(gè)對外函數(shù)可以在任何react native項(xiàng)目中引入,并使用這個(gè)函數(shù)修改react native應(yīng)用獲取js bundle位置的方式

          最后ChatGPT宕機(jī)了,沒有給我答案……終究是免費(fèi)用戶不配了。多試幾次之后,ChatGPT給出了答案,但是還是不理想,就不貼了。

          ChatGPT其實(shí)很聰明,但是對于復(fù)雜的需求,而且不是英文,它理解起來可能跟開發(fā)者理解的意思不太一樣,開發(fā)者描述出來的需求可能也并不是那么易于理解。

          我可以將需求拆解一下,我其實(shí)需要的一個(gè)讀取manifest.json并返回里面某個(gè)字段與根目錄拼接的路徑。將這個(gè)細(xì)分需求拋給ChatGPT看看。直接看最終結(jié)果吧,調(diào)教過程就不看了,有興趣的可以自行去調(diào)教一下試試。

          eeb71b6597910bf783627647a604c8ff.webp

          ChatGPT寫的代碼確 實(shí)挺漂亮。漂亮歸漂亮,還是會(huì)忽略一些異常處理,比如文件不存在的情況或者是文件操作/json操作失敗的異常情況,當(dāng)然只要你反饋給ChatGPT,它很快就可以修正,確實(shí)很厲害。

          整體上來說,功能已經(jīng)實(shí)現(xiàn)的七七八八,后面對此對話,給出的代碼還是不合我意,于是不再依靠ChatGPT,自己查看一些資料,將這個(gè)功能封裝成一個(gè)ReactNative原生模塊,發(fā)布成npm包??纯醋罱K代碼:

                
                + (nullable NSURL *)getJSBundlePath:(BOOL)useHermes
          {
          NSURL *baseBundleUrl = [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"];

          NSString *basePath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject];
          NSString *fileName = useHermes ? @"index.hermes" : @"index.js";
          NSString *manifestPath = [basePath stringByAppendingPathComponent:@"updates/manifest.json"];
          NSFileManager *fileManager = [NSFileManager defaultManager];

          NSLog(@"manifest.json 文件地址: %@", manifestPath);

          if (![fileManager fileExistsAtPath:manifestPath]) {
          // 文件不存在,返回 nil
          return baseBundleUrl;
          }

          NSData *data = [NSData dataWithContentsOfFile:manifestPath];

          NSError *error;
          NSDictionary *json = [NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:&error];
          if (error) {
          NSLog(@"manifest.json 文件解析失敗: %@", error.localizedDescription);
          return baseBundleUrl;
          }

          NSNumber *newest = json[@"newest"];

          if (newest && [newest integerValue] > 0) {
          NSString *filePath = [basePath stringByAppendingPathComponent:[NSString stringWithFormat:@"updates/%@/%@", newest, fileName]];

          // 文件不存在,返回 nil
          if ([fileManager fileExistsAtPath:filePath]) {
          return [NSURL fileURLWithPath:filePath];
          }
          }
          return baseBundleUrl;
          }

          完全零基礎(chǔ),依靠ChatGPT寫出這么一段代碼,也算不錯(cuò)了。

          ChatGPT 怎么幫程序員解決難題

          在測試過程中Xcode還拋出unrecognized selector sent to class這么一個(gè)錯(cuò)誤,谷歌了一個(gè)小時(shí)也沒有找到答案。

          最后我將所有代碼以及調(diào)用方式丟給ChatGPT,很快就給出了答案,原來是實(shí)例函數(shù)與靜態(tài)函數(shù)的問題。

          b319241a03e52c567fd8bd7f7cc5a271.webp

          原先的函數(shù)定義為

                
                - (nullable NSURL *)getJSBundlePath:(BOOL)useHermes

          改為即可解決

                
                + (nullable NSURL *)getJSBundlePath:(BOOL)useHermes

          對于內(nèi)行人來說,可能只是一個(gè)很小的問題,對于外行來說,很難想到是一個(gè)加減號(hào)的問題,ChatGPT比搜索引擎強(qiáng)的地方就在于這里了。

          檢查更新

          檢查更新就更簡單了,請求接口->下載Bundle包->將版本號(hào)寫入就可以了

                
                export default class Leona {
          // xxxx

          public async checkUpdate() {
          if (!this.enable) {
          return;
          }

          // 包版本無效則不做更新
          if (!this.bundleVersion || isNaN(this.bundleVersion)) {
          return;
          }

          const update = await this.getUpdateInfo();
          if (!update?.zip_url) {
          return;
          }
          console.info('熱更新請求成功,準(zhǔn)備下載熱更新文件');

          const download = await this.downBundle(update);
          if (!download) {
          return;
          }
          console.info('熱更新文件下載成功,開始更新manifest.json');
          await this.updateManifest(update.version);
          console.info('熱更新成功,重啟生效');
          }

          /**
          * 更新manifest.json文件
          * @param data UpdateResult
          */

          private async updateManifest(newest: number) {
          await RNFS.writeFile(this.manifestFile, JSON.stringify({ newest }), 'utf8');
          }

          /**
          * 使用RNFS下載熱更新文件
          * @param param UpdateResult
          * @returns boolean
          */

          private async downBundle({ zip_url, version }: UpdateResult) {
          const exist = await RNFS.exists(this.cacheDir);
          if (!exist) {
          RNFS.mkdir(this.cacheDir);
          }

          const distFile = `${this.cacheDir}/${version}.zip`;
          const down = await RNFS.downloadFile({
          fromUrl: zip_url,
          toFile: distFile,
          }).promise;

          if (down.statusCode === 200) {
          await unzip(distFile, `${this.distDir}/${version}`);
          await RNFS.unlink(distFile);
          return true;
          }
          return false;
          }

          /**
          * 請求熱更新接口
          * @returns UpdateResult
          */

          private async getUpdateInfo() {
          const res = await request.get<UpdateResult>(
          this.updateUrl,
          {
          version_code: this.versionCode,
          bundle_version: this.bundleVersion,
          platform: this.platform,
          },
          {
          headers: this.requestHeader,
          }
          );
          return res;
          }

          }

          總結(jié)

          1. 對于熱更新來說,簡單實(shí)現(xiàn)沒有那么復(fù)雜,只需要管理好Bundle包的下載于路徑讀取即可,復(fù)雜應(yīng)用可以考慮更多。
          2. 做不出來的需求可以丟給ChatGPT試試,它總能給你實(shí)現(xiàn),但是調(diào)教過程可能較長,需要慢慢引導(dǎo)。
          3. 谷歌不到的難題也可以丟給ChatGPT試試,沒準(zhǔn)比谷歌更快!

          收工,輸出文章一篇。順利造出三個(gè)輪子:@leona-rn/cli、@leona-rn/client、@leona-rn/core,分別負(fù)責(zé)RN打包與上傳、客戶端替換熱更新文件、RN端檢查與下載熱更新并做版本管理。

          關(guān)注公眾號(hào)

          歡迎關(guān)注作者公眾號(hào)?前端方程式


          瀏覽 91
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  高清无码视频免费版本在线观看 | 人妻天天爽夜夜爽 | 欧美成人系列 | AV天堂观看| 亚洲动漫一区 |