主頁 > 移動端開發 > 解讀 iOS 組件化與路由的本質

解讀 iOS 組件化與路由的本質

2020-10-02 14:07:46 移動端開發

前言

雖然 iOS 組件化與路由的話題在業界談了很久,但是貌似很多人都對其有所誤解,甚至沒搞明白“組件”、“模塊”、“路由”、“解耦”的含義。

相關的博文也蠻多,其實除了那幾個名家寫的,具有參考價值的很少,況且名家的觀點也并非都完全正確。架構往往需要權衡業務場景、學習成本、開發效率等,所以架構方案能客觀解釋卻又帶了些主觀色彩,加上些個人特色的修飾就特別容易讓人本末倒置。

所以要保持頭腦清晰,以辯證的態度看待問題,以下是業界比較有參考價值的文章:
iOS應用架構談 組件化方案
蘑菇街 App 的組件化之路
iOS 組件化 —— 路由設計思路分析
Category 特性在 iOS 組件化中的應用與管控
iOS 組件化方案探索

本文主要是筆者對 iOS 組件化和路由的理解,力求以更客觀與簡潔的方式來解釋各種方案的利弊,歡迎批評指正。

本文的 DEMO

一、組件與模塊的區別

image.png

“組件”強調的是復用,它被各個模塊或組件直接依賴,是基礎設施,它一般不包含業務或者包含弱業務,屬于縱向分層(比如網路請求組件、圖片下載組件)。

“模塊”強調的是封裝,它更多的是指功能獨立的業務模塊,屬于橫向分層(比如購物車模塊、個人中心模塊)。

所以從大家實施“組件化”的目的來看,叫做“模塊化”似乎更為合理。

但“組件”與“模塊”都是前人定義的意義,“iOS 組件化”的概念也已經先入為主,所以只需要明白“iOS 組件化”更多的是做業務模塊之間的解耦就行了。

二、路由的意義

首先要明確的是,路由并非只是指的界面跳轉,還包括資料獲取等幾乎所有業務。

(一) 簡單的路由

內部呼叫的方式

效仿 web 路由,最初的 iOS 原生路由看起來是這樣的:

[Mediator gotoURI:@"protocol://detail?name=xx"];
缺點很明顯:字串 URI 并不能表征 iOS 系統原生型別,要閱讀對應模塊的使用檔案,大量的硬編碼。

代碼實作大概就是:

+ (void)gotoURI:(NSString *)URI {    決議 URI 得到目標和引數    NSString *aim = ...;    NSDictionary *parmas = ...;        if ([aim isEqualToString:@"Detail"]) {        DetailController *vc = [DetailController new];        vc.name = parmas[@"name"];        [... pushViewController:vc animated:YES];    } else if ([aim isEqualToString:@"list"]) {        ...    }}
形象一點:

image.png

拿到 URI 過后,始終有轉換為目標和引數 (aim/params) 的邏輯,然后再真正的呼叫原生模塊。顯而易見,對于內部呼叫來說,決議 URI 這一步就是畫蛇添足 (casa 在博客中說過這個問題)。

路由方法簡化如下:

+ (void)gotoDetailWithName:(NSString *)name {    DetailController *vc = [DetailController new];    vc.name = name;    [... pushViewController:vc animated:YES];}
使用起來就很簡單了:

[Mediator gotoDetailWithName:@"xx"];
如此,方法的引數串列便能替代額外的檔案,并且經過編譯器檢查。

如何支持外部 URI 方式呼叫

那么對于外部呼叫,只需要為它們添加 URI 決議的配接器就能解決問題:

image.png

路由方法寫在哪兒

統一路由呼叫類便于管理和使用,所以通常需要定義一個Mediator類。又考慮到不同模塊的維護者都需要修改Mediator來添加路由方法,可能存在作業流沖突。所以利用裝飾模式,為每一個模塊添加一個分類是不錯的實踐:

@interface Mediator (Detail)+ (void)gotoDetailWithName:(NSString *)name;@end
然后對應模塊的路由方法就寫到對應的分類中。

簡單路由的作用

這里的封裝,解除了業務模塊之間的直接耦合,然而它們還是間接耦合了(因為路由類需要匯入具體業務):

image.png

不過,一個簡單的路由不需關心耦合問題,就算是這樣一個簡單的處理也有如下好處:

清晰的引數串列,方便呼叫者使用。

解開業務模塊之間的耦合,業務更改時或許介面不需變動,外部呼叫就不用更改代碼。

就算是業務更改,路由方法必須得變動,得益于編譯器的檢查,也能直接定位呼叫位置進行更改。

(二) 支持動態呼叫的路由

動態呼叫,顧名思義就是呼叫路徑在不更新 App 的情況下發生變化。比如點擊 A 觸發跳轉到 B 界面,某一時刻又需要點擊 A 跳轉到 C 界面。

要保證最小粒度的動態呼叫,就需要目標業務的完整資訊,比如上面說的aim和params,即目標和引數。

然后需要一套規則,這個規則有兩個來源:

來著服務器的配置。

本地的一些判斷邏輯。

預知的動態呼叫

+ (void)gotoDetailWithName:(NSString *)name {    if (本地防護邏輯判斷 DetailController 出現例外) {        跳轉到 DetailOldController        return;    }    DetailController *vc = [DetailController new];    vc.name = name;    [... pushViewController:vc animated:YES];}
開發者需要明確的知道“某個業務”支持動態呼叫并且動態呼叫的目標是“某個業務”。也就是說,這是一種“偽”動態呼叫,代碼邏輯是寫死的,只是觸發點是動態的而已。

自動化的動態呼叫

試想,上面那種方法+ (void)gotoDetailWithName:(NSString *)name;能支持自動的動態呼叫么?

答案是否定的,要實作真正的“自動化”,必須要滿足一個條件:需要所有路由方法的一個切面。

這個切面的目的就是攔截路由目標和引數,然后做動態調度。一提到 AOP 大家可能會想到 Hook 技術,但是對于下面兩個路由方法:

+ (void)gotoDetailWithName:(NSString *)name;+ (void)pushOldDetail;
你無法找到它們之間的相同點,難以命中。

所以,拿到一個切面的方法筆者能想到的只有一個:統一路由方法入口。

定義這樣一個方法:

- (void)gotoAim:(NSString *)aim params:(NSDictionary *)params {    1、動態呼叫邏輯(通過服務器下發配置判斷)     2、通過 aim 和 params 動態呼叫具體業務}
(關于如何動態呼叫具體業務的技術實作后文會講,這里先不用管,只需要知道這里通過這兩個引數就能動態定位到具體業務。)

然后,路由方法里面就這么寫了:

+ (void)gotoDetailWithName:(NSString *)name {    [self gotoAim:@"detail" params:@{@"name":name}];}
注意@”detail”是約定好的 Aim,內部可以動態定位到具體業務。

由此可見,統一路由方法入口必然需要硬編碼,對于此方案來說自動化的動態呼叫必然需要硬編碼。

那么,這里使用一個分類方法+ (void)gotoDetailWithName:(NSString *)name;將硬編碼包裝起來是個不錯的選擇,把這些 hard code 交給對應業務的工程師去維護吧。

Casa 的 CTMediator 分類就是如此做的,而這也正是蘑菇街組件化方案可以優化的地方。

路由總結

可以發現筆者用了大篇幅講了路由,卻未提及組件化,那是因為有路由不一定需要組件化。

路由的設計主要是考慮需不需要做全鏈路的自動化動態呼叫,列舉幾個場景:

原生頁面出現問題,需要切換到對應的 wap 頁面。

wap 訪問流量過大切換到原生頁面降低消耗。

可以發現,真正的全鏈路動態呼叫成本是非常高的。

三、組件化的意義

前面對路由的分析提到了使用目標和引數 (aim/params) 動態定位到具體業務的技術點。實際上在 iOS Objective-C 中大概有反射和依賴注入兩種思路:

將aim轉化為具體的Class和SEL,利用 runtime 運行時呼叫到具體業務。

對于代碼來說,行程空間是共享的,所以維護一個全域的映射表,提前將aim映射到一段代碼,呼叫時執行具體業務。

可以明確的是,這兩種方式都已經讓Mediator免去了對業務模塊的依賴:

image.png

而這些解耦技術,正是 iOS 組件化的核心。

組件化主要目的是為了讓各個業務模塊獨立運行,互不干擾,那么業務模塊之間的完全解耦是必然的,同時對于業務模塊的拆分也非常考究,更應該追求功能獨立而不是最小粒度。

(一) Runtime 解耦

為 Mediator 定義了一個統一入口方法:

/// 此方法就是一個攔截器,可做容錯以及動態調度- (id)performTarget:(NSString *)target action:(NSString *)action params:(NSDictionary *)params {    Class cls; id obj; SEL sel;    cls = NSClassFromString(target);    if (!cls) goto fail;    sel = NSSelectorFromString(action);    if (!sel) goto fail;    obj = [cls new];    if (![obj respondsToSelector:sel]) goto fail;#pragma clang diagnostic push#pragma clang diagnostic ignored "-Warc-performSelector-leaks"    return [obj performSelector:sel withObject:params];#pragma clang diagnostic popfail:    NSLog(@"找不到目標,寫容錯邏輯");    return nil;}
簡單寫了下代碼,原理很簡單,可用 Demo 測驗。對于內部呼叫,為每一個模塊寫一個分類:

@implementation BMediator (BAim)- (void)gotoBAimControllerWithName:(NSString *)name callBack:(void (^)(void))callBack {    [self performTarget:@"BTarget" action:@"gotoBAimController:" params:@{@"name":name, @"callBack":callBack}];}@end
可以看到這里是給BTarget發送訊息:

@interface BTarget : NSObject- (void)gotoBAimController:(NSDictionary *)params; @end@implementation BTarget- (void)gotoBAimController:(NSDictionary *)params {    BAimController *vc = [BAimController new];    vc.name = params[@"name"];    vc.callBack = params[@"callBack"];    [UIViewController.yb_top.navigationController pushViewController:vc animated:YES];}@end
為什么要定義分類

定義分類的目的前面也說了,相當于一個語法糖,讓呼叫者輕松使用,讓 hard code 交給對應的業務工程師。

為什么要定義 Target “靶子”

避免同一模塊路由邏輯散落各地,便于管理。

路由并非只有控制器跳轉,某些業務可能無法放代碼(比如網路請求就需要額外創建類來接受路由呼叫)。

便于方案的接入和摒棄(靈活性)。

可能有些人對這些類的管理存在疑慮,下圖就表示它們的關系(一個塊表示一個 repo):

image.png

圖中“注意”處箭頭,B 模塊是否需要引入它自己的分類 repo,取決于是否需要做所有界面跳轉的攔截,如果需要那么 B 模塊仍然要引入自己的 repo 使用。

完整的方案和代碼可以查看 Casa 的 CTMediator,設計得比較完備,筆者沒挑出什么毛病。

(二) Block 解耦

下面簡單實作了兩個方法:

- (void)registerKey:(NSString *)key block:(nonnull id _Nullable (^)(NSDictionary * _Nullable))block {    if (!key || !block) return;    self.map[key] = block;}/// 此方法就是一個攔截器,可做容錯以及動態調度- (id)excuteBlockWithKey:(NSString *)key params:(NSDictionary *)params {    if (!key) return nil;    id(^block)(NSDictionary *) = self.map[key];    if (!block) return nil;    return block(params);}
維護一個全域的字典 (Key -> Block),只需要保證閉包的注冊在業務代碼跑起來之前,很容易想到在+load中寫:

@implementation DRegister+ (void)load {    [DMediator.share registerKey:@"gotoDAimKey" block:^id _Nullable(NSDictionary * _Nullable params) {        DAimController *vc = [DAimController new];        vc.name = params[@"name"];        vc.callBack = params[@"callBack"];        [UIViewController.yb_top.navigationController pushViewController:vc animated:YES];        return nil;    }];}@end
至于為什么要使用一個單獨的DRegister類,和前面“Runtime 解耦”為什么要定義一個Target是一個道理。同樣的,使用一個分類來簡化內部呼叫(這是蘑菇街方案可以優化的地方):

@implementation DMediator (DAim)- (void)gotoDAimControllerWithName:(NSString *)name callBack:(void (^)(void))callBack {    [self excuteBlockWithKey:@"gotoDAimKey" params:@{@"name":name, @"callBack":callBack}];}@end
可以看到,Block 方案和 Runtime 方案 repo 架構上可以基本一致(見圖6),只是 Block 多了注冊這一步。

為了靈活性,Demo 中讓 Key -> Block,這就讓 Block 里面要寫很多代碼,如果縮小范圍將 Key -> UIViewController.class 可以減少注冊的代碼量,但這樣又難以覆寫所有場景。

注冊所產生的記憶體占用并不是負擔,主要是大量的注冊可能會明顯拖慢啟動速度。

(三) Protocol 解耦

這種方式仍然要注冊,使用一個全域的字典 (Protocol -> Class) 存盤起來。

- (void)registerService:(Protocol *)service class:(Class)cls {    if (!service || !cls) return;    self.map[NSStringFromProtocol(service)] = cls;}- (id)getObject:(Protocol *)service {    if (!service) return nil;    Class cls = self.map[NSStringFromProtocol(service)];    id obj = [cls new];    if ([obj conformsToProtocol:service]) {        return obj;    }    return nil;}
定義一個協議服務:

@protocol CAimService - (void)gotoCAimControllerWithName:(NSString *)name callBack:(void (^)(void))callBack;@end
用一個類實作協議并且注冊協議:

@implementation CAimServiceProvider+ (void)load {    [CMediator.share registerService:@protocol(CAimService) class:self];}#pragma mark - - (void)gotoCAimControllerWithName:(NSString *)name callBack:(void (^)(void))callBack {    CAimController *vc = [CAimController new];    vc.name = name;    vc.callBack = callBack;    [UIViewController.yb_top.navigationController pushViewController:vc animated:YES];}@end
至于為什么要使用一個單獨的ServiceProvider類,和前面“Runtime 解耦”為什么要定義一個Target是一個道理。

使用起來很優雅:

id service = [CMediator.share getObject:@protocol(CAimService)];[service gotoCAimControllerWithName:@"From C" callBack:^{       NSLog(@"CAim CallBack");}];
看起來這種方案不需要硬編碼很舒服,但是它有個致命的問題 ——— 無法攔截所有路由方法。

這也就意味著這種方案做不了自動化動態呼叫。

阿里的 BeeHive 是目前的最佳實踐。注冊部分它可以將待注冊的類字串寫入 Data 段,然后在 Image 加載的時候讀取出來注冊。這個操作只是將注冊的執行放到了+load方法之前,仍然會拖慢啟動速度,所以這個優化筆者沒有看到價值。

為什么 Protocol -> Class 和 Key -> Block 需要注冊?

想象一下,解耦意味著呼叫方只有系統原生的標識,如何定位到目標業務?
必然有個映射。
而 runtime 可以直接呼叫目標業務,其它兩種方式只有建立映射表。
當然 Protocol 方式也可以不建立映射表,直接遍歷所有類,找出遵循這個協議的類也能找到,不過明顯這樣是低效且不安全的。

組件化總結

對于很多專案來說,并非一開始就需要實施組件化,為了避免在將來業務穩定需要實施的時候束手無策,在專案之初最好有一些前瞻性的設計,同時編碼程序中也要盡量降低各個業務模塊的耦合。

在設計路由時,盡量降低將來組件化時的遷移成本,所以理解各種方案的實施條件很重要。如果專案將來幾乎不可能做自動化動態路由,那么使用 Protocol -> Class 方案就能去除硬編碼;否則,還是使用 Runtime 或者 Key -> Block 方案,兩者都有不同程度的硬編碼但 Runtime 不需要注冊。

后語

設計一個方案時,最好的方式是窮舉所有方案,分別找出優勢和劣勢,然后根據業務需求,進行權衡和取舍。可能有的時候業界的方案并不完全適合自己的專案,這個時候就需要做一些創造性的改進。

不要總說“就應該是這樣”,而多想“為什么要這樣”。

作者:indulge_in

轉載請註明出處,本文鏈接:https://www.uj5u.com/yidong/148491.html

標籤:iOS

上一篇:Mac微信雙開如何實作?

下一篇:互聯網企業又發生大事件啦!你猜是什么大事?

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 【從零開始擼一個App】Dagger2

    Dagger2是一個IOC框架,一般用于Android平臺,第一次接觸的朋友,一定會被搞得暈頭轉向。它延續了Java平臺Spring框架代碼碎片化,注解滿天飛的傳統。嘗試將各處代碼片段串聯起來,理清思緒,真不是件容易的事。更不用說還有各版本細微的差別。 與Spring不同的是,Spring是通過反射 ......

    uj5u.com 2020-09-10 06:57:59 more
  • Flutter Weekly Issue 66

    新聞 Flutter 季度調研結果分享 教程 Flutter+FaaS一體化任務編排的思考與設計 詳解Dart中如何通過注解生成代碼 GitHub 用對了嗎?Flutter 團隊分享如何管理大型開源專案 插件 flutter-bubble-tab-indicator A Flutter librar ......

    uj5u.com 2020-09-10 06:58:52 more
  • Proguard 常用規則

    介紹 Proguard 入口,如何查看輸出,如何使用 keep 設定入口以及使用實體,如何配置壓縮,混淆,校驗等規則。

    ......

    uj5u.com 2020-09-10 06:59:00 more
  • Android 開發技術周報 Issue#292

    新聞 Android即將獲得類AirDrop功能:可向附近設備快速分享檔案 谷歌為安卓檔案管理應用引入可安全隱藏資料的Safe Folder功能 Android TV新主界面將顯示電影、電視節目和應用推薦內容 泄露的Android檔案暗示了傳說中的谷歌Pixel 5a與折疊屏新機 谷歌發布Andro ......

    uj5u.com 2020-09-10 07:00:37 more
  • AutoFitTextureView Error inflating class

    報錯: Binary XML file line #0: Binary XML file line #0: Error inflating class xxx.AutoFitTextureView 解決: <com.example.testy2.AutoFitTextureView android: ......

    uj5u.com 2020-09-10 07:00:41 more
  • 根據Uri,Cursor沒有獲取到對應的屬性

    Android: 背景:呼叫攝像頭,拍攝視頻,指定保存的地址,但是回傳的Cursor檔案,只有名稱和大小的屬性,沒有其他諸如時長,連ID屬性都沒有 使用 cursor.getInt(cursor.getColumnIndexOrThrow(MediaStore.Video.Media.DURATIO ......

    uj5u.com 2020-09-10 07:00:44 more
  • Android連載29-持久化技術

    一、持久化技術 我們平時所使用的APP產生的資料,在記憶體中都是瞬時的,會隨著斷電、關機等丟失資料,因此android系統采用了持久化技術,用于存盤這些“瞬時”資料 持久化技術包括:檔案存盤、SharedPreference存盤以及資料庫存盤,還有更復雜的SD卡記憶體儲。 二、檔案存盤 最基本存盤方式, ......

    uj5u.com 2020-09-10 07:00:47 more
  • Android Camera2Video整合到自己專案里

    背景: Android專案里呼叫攝像頭拍攝視頻,原本使用的 MediaStore.ACTION_VIDEO_CAPTURE, 后來因專案需要,改成了camera2 1.Camera2Video 官方demo有點問題,下載后,不能直接整合到專案 問題1.多次拍攝視頻崩潰 問題2.雙擊record按鈕, ......

    uj5u.com 2020-09-10 07:00:50 more
  • Android 開發技術周報 Issue#293

    新聞 谷歌為Android TV開發者提供多種新功能 Android 11將自動填表功能整合到鍵盤輸入建議中 谷歌宣布Android Auto即將支持更多的導航和數字停車應用 谷歌Pixel 5只有XL版本 搭載驍龍765G且將比Pixel 4更便宜 [圖]Wear OS將迎來重磅更新:應用啟動時間 ......

    uj5u.com 2020-09-10 07:01:38 more
  • 海豚星空掃碼投屏 Android 接收端 SDK 集成 六步驟

    掃碼投屏,開放網路,獨占設備,不需要額外下載軟體,微信掃碼,發現設備。支持標準DLNA協議,支持倍速播放。視頻,音頻,圖片投屏。好點意思。還支持自定義基于 DLNA 擴展的操作動作。好像要收費,沒體驗。 這里簡單記錄一下集成程序。 一 跟目錄的build.gradle添加私有mevan倉庫 mave ......

    uj5u.com 2020-09-10 07:01:43 more
最新发布
  • 歡迎頁輪播影片

    如圖,引導開始,球從上落下,同時淡入文字,然后文字開始輪播,最后一頁時停止,點擊進入首頁。 在來看看效果圖。 重力球先不講,主要歡迎輪播簡單實作 首先新建一個類 TextTranslationXGuideView,用于影片展示 文本是類似的,最后會有個圖片箭頭影片,布局很簡單,就是一個 TextVi ......

    uj5u.com 2023-04-20 08:40:31 more
  • 【FAQ】關于華為推送服務因營銷訊息頻次管控導致服務通訊類訊息

    一. 問題描述 使用華為推送服務下發IM訊息時,下發訊息請求成功且code碼為80000000,但是手機總是收不到訊息; 在華為推送自助分析(Beta)平臺查看發現,訊息發送觸發了頻控。 二. 問題原因及背景 2023年1月05日起,華為推送服務對咨詢營銷類訊息做了單個設備每日推送數量上限管理,具體 ......

    uj5u.com 2023-04-20 08:40:11 more
  • 歡迎頁輪播影片

    如圖,引導開始,球從上落下,同時淡入文字,然后文字開始輪播,最后一頁時停止,點擊進入首頁。 在來看看效果圖。 重力球先不講,主要歡迎輪播簡單實作 首先新建一個類 TextTranslationXGuideView,用于影片展示 文本是類似的,最后會有個圖片箭頭影片,布局很簡單,就是一個 TextVi ......

    uj5u.com 2023-04-20 08:39:36 more
  • 【FAQ】關于華為推送服務因營銷訊息頻次管控導致服務通訊類訊息

    一. 問題描述 使用華為推送服務下發IM訊息時,下發訊息請求成功且code碼為80000000,但是手機總是收不到訊息; 在華為推送自助分析(Beta)平臺查看發現,訊息發送觸發了頻控。 二. 問題原因及背景 2023年1月05日起,華為推送服務對咨詢營銷類訊息做了單個設備每日推送數量上限管理,具體 ......

    uj5u.com 2023-04-20 08:39:13 more
  • iOS從UI記憶體地址到讀取成員變數(oc/swift)

    開發除錯時,我們發現bug時常首先是從UI顯示發現例外,下一步才會去定位UI相關連的資料的。XCode有給我們提供一系列debug工具,但是很多人可能還沒有形成一套穩定的除錯流程,因此本文嘗試解決這個問題,順便提出一個暴論:UI顯示例外問題只需要兩個步驟就能完成定位作業的80%: 定位例外 UI 組 ......

    uj5u.com 2023-04-19 09:16:23 more
  • FIDE重磅更新!性能飛躍!體驗有禮!

    FIDE 開發者工具重構升級啦!實作500%性能提升,誠邀體驗! 一直以來不少開發者朋友在社區反饋,在使用 FIDE 工具的程序中,時常會遇到諸如加載不及時、代碼預覽/渲染性能不如意的情況,十分影響開發體驗。 作為技術團隊,我們深知一件趁手的開發工具對開發者的重要性,因此,在2023年開年,FinC ......

    uj5u.com 2023-04-19 09:16:15 more
  • 游戲內嵌社區服務開放,助力開發者提升玩家互動與留存

    華為 HMS Core 游戲內嵌社區服務提供快速訪問華為游戲中心論壇能力,支持玩家直接在游戲內瀏覽帖子和交流互動,助力開發者擴展內容生產和觸達的場景。 一、為什么要游戲內嵌社區? 二、游戲內嵌社區的典型使用場景 1、游戲內打開論壇 您可以在游戲內繪制論壇入口,為玩家提供沉浸式發帖、瀏覽、點贊、回帖、 ......

    uj5u.com 2023-04-19 09:15:46 more
  • iOS從UI記憶體地址到讀取成員變數(oc/swift)

    開發除錯時,我們發現bug時常首先是從UI顯示發現例外,下一步才會去定位UI相關連的資料的。XCode有給我們提供一系列debug工具,但是很多人可能還沒有形成一套穩定的除錯流程,因此本文嘗試解決這個問題,順便提出一個暴論:UI顯示例外問題只需要兩個步驟就能完成定位作業的80%: 定位例外 UI 組 ......

    uj5u.com 2023-04-19 09:14:53 more
  • FIDE重磅更新!性能飛躍!體驗有禮!

    FIDE 開發者工具重構升級啦!實作500%性能提升,誠邀體驗! 一直以來不少開發者朋友在社區反饋,在使用 FIDE 工具的程序中,時常會遇到諸如加載不及時、代碼預覽/渲染性能不如意的情況,十分影響開發體驗。 作為技術團隊,我們深知一件趁手的開發工具對開發者的重要性,因此,在2023年開年,FinC ......

    uj5u.com 2023-04-19 09:14:08 more
  • 游戲內嵌社區服務開放,助力開發者提升玩家互動與留存

    華為 HMS Core 游戲內嵌社區服務提供快速訪問華為游戲中心論壇能力,支持玩家直接在游戲內瀏覽帖子和交流互動,助力開發者擴展內容生產和觸達的場景。 一、為什么要游戲內嵌社區? 二、游戲內嵌社區的典型使用場景 1、游戲內打開論壇 您可以在游戲內繪制論壇入口,為玩家提供沉浸式發帖、瀏覽、點贊、回帖、 ......

    uj5u.com 2023-04-19 09:08:34 more