日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問(wèn) 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 编程资源 > 编程问答 >内容正文

编程问答

Swift 在淘宝商品评价的技术重构与实践

發(fā)布時(shí)間:2024/1/1 编程问答 34 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Swift 在淘宝商品评价的技术重构与实践 小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

淘寶新版商品評(píng)價(jià)列表在經(jīng)歷一個(gè)半月的技術(shù)重構(gòu),幾個(gè)月的迭代和放量,最終在 2021 年的雙十一上,以 100% 的流量穩(wěn)定的跑完了整個(gè)過(guò)程。我們不僅在業(yè)務(wù)上有了比較明確的提升,同時(shí)還沉淀了不少技術(shù)探索,比如沉淀基于 DinamicX + 事件鏈編排的輕模式研發(fā)框架、推動(dòng)原生語(yǔ)言升級(jí)成 Swift/Kotlin,最終使得整體研發(fā)效率和穩(wěn)定性有一個(gè)比較大的提升。(注:DinamicX 為內(nèi)部自研動(dòng)態(tài)化 UI 框架)

這篇文章,我會(huì)重點(diǎn)談?wù)撽P(guān)于 Swift 的部分。如果你想了解關(guān)于 Swift 如何提升研發(fā)效率/質(zhì)量、現(xiàn)有項(xiàng)目/模塊是否需要 Swift 作為原生語(yǔ)言如何選型、在商品評(píng)價(jià)落地 Swift 過(guò)程中我們遇到了哪些問(wèn)題以及最后有哪些收益和結(jié)論的一些問(wèn)題,希望這篇文章可以給你帶來(lái)一些幫助。

首先是,我為什么會(huì)選擇學(xué)習(xí) Swift?

技術(shù)變革,未來(lái)已來(lái)

因?yàn)?#xff0c;我內(nèi)心十分堅(jiān)定,相比較于 OC,Swift 更能承載未來(lái)。

???堅(jiān)強(qiáng)后盾

最主要的原因就是它有一個(gè)堅(jiān)強(qiáng)的后盾,Swift 作為 Apple 未來(lái)最重要的開(kāi)發(fā)語(yǔ)言,光對(duì)外輸出的 WWDC 內(nèi)容就已經(jīng)高達(dá) 73 個(gè),包括但不限于語(yǔ)法、設(shè)計(jì)、性能、開(kāi)發(fā)工具鏈等,具體內(nèi)容如圖所示:

回過(guò)頭來(lái)看 Swift 這幾年的發(fā)展,從 2014 年開(kāi)始正式對(duì)外發(fā)布,到現(xiàn)在已經(jīng)經(jīng)歷了 7 個(gè)年頭了,在整個(gè)過(guò)程中,Apple ?投入了大量精力建設(shè) Swift,尤其是 Swift Only 框架的出現(xiàn),也意味著 Apple 正在積極倡導(dǎo)各位投入到 Swift 開(kāi)發(fā)中來(lái)。

???三大優(yōu)勢(shì)

其次,Swift 有三個(gè)比較明確的優(yōu)勢(shì):更快、更安全且更具備表達(dá)性

更快?是指 Swift 在執(zhí)行效率上做了很多優(yōu)化。比如,Swift 系統(tǒng)庫(kù)本身就采用了很多不需要引用計(jì)數(shù)的基礎(chǔ)類(lèi)型,無(wú)論是內(nèi)存分配大小、引用計(jì)數(shù)損耗、方法派發(fā)靜態(tài)分析等方面的問(wèn)題都得到了一個(gè)有效的提升。具體細(xì)節(jié)這里就不展開(kāi)分析,感興趣的可以移步 Understanding Swift Performance 了解細(xì)節(jié)。

所謂的?安全?不等于不發(fā)生 Crash,而是指任何的輸入都有一個(gè)比較明確的表現(xiàn)定義。Swift 設(shè)計(jì)初衷是希望開(kāi)發(fā)者無(wú)需任何不安全的數(shù)據(jù)結(jié)構(gòu)就能編寫(xiě)代碼,因此 Swift 擁有一個(gè)十分健壯的類(lèi)型系統(tǒng),開(kāi)發(fā)者幾乎不需要考慮指針的問(wèn)題,就能完成所有的開(kāi)發(fā)工作。同時(shí)還提供了一系列前綴為 Unsafe 的類(lèi)型或函數(shù),用于與不安全語(yǔ)言(例如 C 語(yǔ)言)的高性能交互、操作原始內(nèi)存等相對(duì)不安全的操作,一方面以 Unsafe 警惕開(kāi)發(fā)者使用這些 API ,另外一方面是區(qū)分類(lèi)型以保證大部分開(kāi)發(fā)場(chǎng)景使用的都是安全的類(lèi)型。

這里可以分享一個(gè)數(shù)據(jù),我之前參與的一個(gè) App 項(xiàng)目,是用 Pure Swift 編寫(xiě)的(99%+),我們的線上 crash 率常年持續(xù)在十萬(wàn)分之 8 左右,這對(duì)于一個(gè)小型團(tuán)隊(duì)(單端 4 人)來(lái)說(shuō),是一個(gè)十分可觀的結(jié)果。我們幾乎不使用 Unsafe 的 API,使得我們的大部分問(wèn)題都能在編譯期間避免,可選類(lèi)型及可選綁定的設(shè)計(jì)強(qiáng)制開(kāi)發(fā)者需要去思考如何處理值為空的場(chǎng)景,使得在軟件發(fā)布之前就把開(kāi)發(fā)者的錯(cuò)誤扼殺在萌芽之中。

更具備表達(dá)性?簡(jiǎn)單點(diǎn)說(shuō)就是用更少的代碼來(lái)表達(dá)一段完整的邏輯。在 Swift Evolution 項(xiàng)目中已經(jīng)有 330 個(gè)提案來(lái)增強(qiáng) Swift 的表達(dá)性,也得益于這些特性,使得 Swift 的代碼量比 OC 少了大概 30% - 50% 左右。我們舉幾個(gè)實(shí)際例子

Builder Pattern

當(dāng)我們定義了一個(gè)有很多屬性的復(fù)雜 model 時(shí),我們不希望這個(gè) model 的屬性在初始化完成后可以被變更。我們就需要通過(guò) builder 模式來(lái)解決,代碼如下:

// OCDemoModelBuilder.h@interface OCDemoModelBuilder : NSObject@property (nonatomic, copy, nonnull) NSString *a; @property (nonatomic, copy, nonnull) NSString *b; @property (nonatomic, copy, nonnull) NSString *c; @property (nonatomic, copy, nonnull) NSString *d; @property (nonatomic, copy, nonnull) NSString *e; @property (nonatomic, copy, nonnull) NSString *f; @property (nonatomic, copy, nonnull) NSString *g; @property (nonatomic, copy, nonnull) NSString *h;@end// OCDemoModelBuilder.m@implementation OCDemoModelBuilder- (instancetype)init {if (self = [super init]) {_a = @"a";_b = @"b";_c = @"c";_d = @"d";_e = @"e";_f = @"f";_g = @"g";_h = @"h";}return self; }@end// OCDemoModel.h@interface OCDemoModel : NSObject@property (nonatomic, readonly, nonnull) NSString *a; @property (nonatomic, readonly, nonnull) NSString *b; @property (nonatomic, readonly, nonnull) NSString *c; @property (nonatomic, readonly, nonnull) NSString *d; @property (nonatomic, readonly, nonnull) NSString *e; @property (nonatomic, readonly, nonnull) NSString *f; @property (nonatomic, readonly, nonnull) NSString *g; @property (nonatomic, readonly, nonnull) NSString *h;- (instancetype)initWithBuilder:(void(^)(OCDemoModelBuilder *builder))builderBlock;@end// OCDemoModel.m@implementation OCDemoModel- (instancetype)initWithBuilder:(void(^)(OCDemoModelBuilder *builder))builderBlock {if (self = [super init]) {OCDemoModelBuilder * builder = [[OCDemoModelBuilder alloc] init];if (builderBlock) {builderBlock(builder);}_a = builder.a;_b = builder.b;_c = builder.c;_d = builder.d;_e = builder.e;_f = builder.f;_g = builder.g;_h = builder.h;}return self; }@end// UsageOCDemoModel *ret = [[OCDemoModel alloc] initWithBuilder:^(OCDemoModelBuilder * _Nonnull builder) {builder.b = @"b1"; }];// ret = a,b1,c,d,e,f,g

但是 Swift 的 Struct 支持屬性默認(rèn)值和初始化構(gòu)造器,使得 builder pattern 意義并不是很大,代碼如下:

struct SwiftDemoModel {var a = "a"var b = "b"var c = "c"var d = "d"var e = "e"var f = "f"var g = "g"var h = "h" }// Usagelet ret = SwiftDemoModel(b: "b1")// ret = a,b1,c,d,e,f,g

State Pattern

當(dāng)一個(gè)函數(shù)的執(zhí)行結(jié)果可能存在多種不同的狀態(tài)時(shí),我們通常會(huì)采用狀態(tài)模式來(lái)解決問(wèn)題。

例如我們定義一個(gè)函數(shù)執(zhí)行結(jié)果可能存在 finish\failure\none 三種狀態(tài),由于存在一些關(guān)聯(lián)值,我們不能使用枚舉來(lái)解決。需要定義三個(gè)不同的具體類(lèi)型,具體代碼如下所示:

/// Executable.h @protocol Executable <NSObject>- (nullable NSDictionary *)toFormattedData;@end/// OCDemoExecutedResult.h @interface OCDemoExecutedResult: NSObject<Executable>/// 構(gòu)造一個(gè)空返回值 + (OCDemoNoneResult *)none;/// 構(gòu)造成功返回值 + (OCDemoFinishedResult *)finishedWithData:(nullable NSDictionary *)datatype:(nullable NSString *)type; /// 構(gòu)造一個(gè)錯(cuò)誤返回值 + (OCDemoFailureResult *)failureWithErrorCode:(nonnull NSString *)errorCodeerrorMsg:(nonnull NSString *)errorMsguserInfo:(nullable NSDictionary *)userInfo;@end/// OCDemoExecutedResult.m @implementation OCDemoExecutedResult/// 構(gòu)造一個(gè)空返回值 + (OCDemoNoneResult *)none {return [OCDemoNoneResult new]; }+ (OCDemoFinishedResult *)finishedWithData:(nullable NSDictionary *)data type:(nullable NSString *)type {return [[OCDemoFinishedResult alloc] initWithData:data type:type]; }+ (OCDemoFailureResult *)failureWithErrorCode:(nonnull NSString *)errorCode errorMsg:(nonnull NSString *)errorMsg userInfo:(nullable NSDictionary *)userInfo {return [[OCDemoFailureResult alloc] initWithErrorCode:errorCode errorMsg:errorMsg userInfo:userInfo]; }- (nullable NSDictionary *)toFormattedData {return nil; }@end/// OCDemoNoneResult.h @interface OCDemoNoneResult : OCDemoExecutedResult@end/// OCDemoNoneResult.m @implementation OCDemoNoneResult@end/// OCDemoFinishedResult.h @interface OCDemoFinishedResult: OCDemoExecutedResult/// 類(lèi)型 @property (nonatomic, copy, nonnull) NSString *type; /// 關(guān)聯(lián)值 @property (nonatomic, copy, nullable) NSDictionary *data;/// 初始化方法 - (instancetype)initWithData:(nullable NSDictionary *)data type:(nullable NSString *)type;@end/// OCDemoFinishedResult.h @implementation OCDemoFinishedResult- (instancetype)initWithData:(nullable NSDictionary *)data type:(nullable NSString *)type {if (self = [super init]) {_data = [data copy];_type = [(type ?:@"result") copy];}return self; }- (NSDictionary *)toFormattedData {return @{@"type": self.type,@"data": self.data ?: [NSNull null]}; }@end/// OCDemoFailureResult.h @interface OCDemoFailureResult: OCDemoExecutedResult/// 錯(cuò)誤碼 @property (nonatomic, copy, readonly, nonnull) NSString *errorCode; /// 錯(cuò)誤信息 @property (nonatomic, copy, readonly, nonnull) NSString *errorMsg; /// 關(guān)聯(lián)值 @property (nonatomic, copy, readonly, nullable) NSDictionary *userInfo;/// 初始化方法 - (instancetype)initWithErrorCode:(NSString *)errorCode errorMsg:(NSString *)errorMsg userInfo:(nullable NSDictionary *)userInfo;@end/// OCDemoFailureResult.m @implementation OCDemoFailureResult- (OCDemoFailureResult *)initWithErrorCode:(NSString *)errorCode errorMsg:(NSString *)errorMsg userInfo:(nullable NSDictionary *)userInfo {if (self = [super init]) {_errorCode = [errorCode copy];_errorMsg = [errorMsg copy];_userInfo = [userInfo copy];}return self; }- (NSDictionary *)toFormattedData {return @{@"code": self.errorCode,@"msg": self.errorMsg,@"data": self.userInfo ?: [NSNull null]}; }@end

但是如果我們使用 Swift 的 enum 特性,代碼就會(huì)變的簡(jiǎn)潔很多很多:

public?enum?SwiftDemoExecutedResult?{/// 正確返回值case finished(type: String, result: [String: Any]?)/// 錯(cuò)誤返回值case failure(errorCode: String, errorMsg: String, userInfo: [String: Any]?)/// 空返回值case none/// 格式化func toFormattedData() -> [String: Any]? {switch self {case .finished(type: let type, result: let result):var ret: [String: Any] = [:]ret["type"] = typeret["data"] = resultreturn retcase .failure(errorCode: let errorCode, errorMsg: let errorMsg, userInfo: let userInfo):var ret: [String: Any] = [:]ret["code"] = errorCoderet["msg"] = errorMsgret["data"] = userInforeturn retcase .none:return nil}} }

Facade Pattern

當(dāng)我們定義一個(gè)入?yún)⑿枰隙鄠€(gè)協(xié)議類(lèi)型時(shí),我們通常會(huì)使用 Facade Pattern 來(lái)解決問(wèn)題。

例如我們有四個(gè)協(xié)議?JSONDecodable、JSONEncodable、XMLDecodable、XMLEncodable?以及一帶有兩個(gè)入?yún)⒌姆椒?#xff0c;入?yún)?1 為 json 要求同時(shí)滿足?JSONDecodable、JSONEncodable?兩個(gè)協(xié)議,入?yún)?2 為 xml 同時(shí)滿足?XMLDecodable、XMLEncodable。當(dāng)我們使用 OC 來(lái)解決問(wèn)題時(shí)通常會(huì)這么寫(xiě):

@protocol JSONDecodable <NSObject> @end@protocol JSONEncodable <NSObject> @end@protocol XMLDecodable <NSObject> @end@protocol XMLEncodable <NSObject> @end@protocol JSONCodable <JSONDecodable, JSONEncodable> @end@protocol XMLCodable <XMLDecodable, XMLEncodable> @end- (void)decodeJSON:(id<JSONCodable>)json xml:(id<XMLCodable>)xml {}

額外定義了兩個(gè)協(xié)議?JSONCodable、XMLCodable?來(lái)解決這個(gè)問(wèn)題。但是在 Swift 中我們可以使用?&?來(lái)解決這個(gè)問(wèn)題,不再需要定義額外的類(lèi)型,代碼如下:

protocol JSONDecodable {} protocol JSONEncodable {} protocol XMLDecodable {} protocol XMLEncodable {}func decode(json: JSONDecodable & JSONEncodable, xml: XMLDecodable & XMLEncodable) { }

以上是 Swift 在?更具備表達(dá)性?方面的一些內(nèi)容,當(dāng)然優(yōu)勢(shì)也遠(yuǎn)不止這些,但是篇幅有限這里不再展開(kāi)。

總而言之,得益于 Swift 的高表達(dá)性,使得開(kāi)發(fā)者可以通過(guò)更少的代碼可以表達(dá)一段完整的邏輯,在一定程度上減少了開(kāi)發(fā)成本,同時(shí)也降低了問(wèn)題的產(chǎn)生。

???勢(shì)不可擋

Swift 除了有一個(gè)堅(jiān)強(qiáng)的后盾以及三大優(yōu)勢(shì)以外,這幾年的發(fā)展趨勢(shì)也比較好。

首先根據(jù) Githut 顯示,Swift 語(yǔ)言在 Github 的活躍度(Pull request) 已經(jīng)超越了 OC 了,如下圖所示:(數(shù)據(jù)截止至 2021/10/25)

同時(shí),國(guó)內(nèi) Top 100 的 Swift 混編應(yīng)用也有明顯增加,從 19 年的 22% 已經(jīng)上升到了 59%:(數(shù)據(jù)截止至 2021/04/22)

這里的提升,一方面是國(guó)內(nèi)許多一線互聯(lián)網(wǎng)公司都開(kāi)始布局,另外一方面是 WidgetKit 等 Swift Only 的框架出現(xiàn)也在促使大家開(kāi)始建設(shè) Swift 基礎(chǔ)設(shè)施。

當(dāng)然,國(guó)外數(shù)據(jù)更加亮眼,已經(jīng)達(dá)到了 91%,幾乎可以說(shuō)是全部都已經(jīng)用上了,為什么這么說(shuō)呢?因?yàn)槊腊媲?100 中 Google 系有 8 個(gè)應(yīng)用都沒(méi)有使用上 Swift。

這里再和大家分享一個(gè)數(shù)據(jù),在業(yè)余時(shí)間組織《WWDC內(nèi)參》作者招募的時(shí)候,我們收集了作者的技術(shù)棧和興趣點(diǎn),最終發(fā)現(xiàn)有超過(guò)一半的作者有比較豐富的 Swift 開(kāi)發(fā)經(jīng)驗(yàn),還有 2/3 的人對(duì) Swift ?這個(gè)專(zhuān)題的內(nèi)容比較感興趣(總共 180 人樣本)。可以看得出社區(qū)對(duì)于 Swift 的熱情還是十分高的,長(zhǎng)遠(yuǎn)角度看,是否使用 Swift 進(jìn)行開(kāi)發(fā)也會(huì)成為大家選擇工作的原因之一。

為什么選擇商品評(píng)價(jià)列表?

也許很多人在看到第一部分之后,會(huì)有一種我得馬上在我們項(xiàng)目中用上 Swift 的沖動(dòng)。為了避免你為你的“沖動(dòng)”買(mǎi)單,下面我分享一下「手淘商品評(píng)價(jià)列表」選擇 Swift 的心路歷程。

先簡(jiǎn)單講下自己來(lái)手淘的經(jīng)歷,起初我加入的是手淘基礎(chǔ)架構(gòu)組,主要工作職責(zé)之一就是建設(shè) Swift 基礎(chǔ)設(shè)施,但是后來(lái)因?yàn)榻M織需要,我加入到了一個(gè)新的業(yè)務(wù)架構(gòu)組,工作重心也由原來(lái)的從 Swift 基礎(chǔ)升級(jí)驅(qū)動(dòng)業(yè)務(wù),轉(zhuǎn)變成業(yè)務(wù)試點(diǎn)驅(qū)動(dòng)基礎(chǔ)技術(shù)升級(jí)。在這個(gè)過(guò)程中,我們主要經(jīng)歷了三次技術(shù)決策:

  • 團(tuán)隊(duì)最開(kāi)始接手的項(xiàng)目:手淘訂單協(xié)議升級(jí)為新奧創(chuàng)

  • 基于對(duì)業(yè)務(wù)研發(fā)的領(lǐng)域理解,團(tuán)隊(duì)提出新的事件鏈編排能力,并與DX共建

  • 商品評(píng)價(jià)重構(gòu),包括評(píng)價(jià)列表、交互等

  • 每個(gè)階段我都有思考過(guò)我是否要使用 Swift,但最終前兩次我都放棄了使用我自己比較擅長(zhǎng)的 Swift,主要出于下面幾點(diǎn)考慮:

    ???需要具備使用 Swift 的前提

    訂單新奧創(chuàng)項(xiàng)目之所以沒(méi)有采用 Swift 為主要開(kāi)發(fā)語(yǔ)言,最大的問(wèn)題就是當(dāng)時(shí)的基本基礎(chǔ)設(shè)施還不夠完備。依賴(lài)的大部分模塊幾乎都不支持 Module,如果要硬上 Swift 幾乎是不可能的事情,會(huì)增加很多的工作量,對(duì)于一個(gè)工期較趕的項(xiàng)目來(lái)說(shuō),不是一個(gè)明智之舉,權(quán)衡之下,暫時(shí)放棄了使用 Swift 的念頭。

    ???什么樣的業(yè)務(wù)更適合使用 Swift 重構(gòu)

    在基本條件都很完備的情況下,對(duì)于一個(gè)業(yè)務(wù)重構(gòu)項(xiàng)目來(lái)說(shuō),Swift 會(huì)是一個(gè)更好的選擇。無(wú)論是大環(huán)境的趨勢(shì),還是 Swift 獨(dú)有的優(yōu)勢(shì)來(lái)說(shuō),已經(jīng)不太適合繼續(xù)使用 OC 去重構(gòu)一個(gè)業(yè)務(wù)模塊了。

    對(duì)于想嘗試 Swift 的大型項(xiàng)目來(lái)說(shuō),建議可以?xún)?yōu)先考慮包袱小、牽連小的業(yè)務(wù)做試點(diǎn)。當(dāng)時(shí)我們?cè)谟唵涡聤W創(chuàng)項(xiàng)目放棄使用 Swift 的另外一個(gè)重要原因就是因?yàn)閵W創(chuàng)整體架構(gòu)較為復(fù)雜,搭建和數(shù)據(jù)混合在一起、局部改動(dòng)成本過(guò)高會(huì)導(dǎo)致?tīng)恳话l(fā)而動(dòng)全身的問(wèn)題,整體對(duì)端側(cè)新技術(shù)交互的開(kāi)放包容有限。但是手淘商品評(píng)價(jià)就沒(méi)有這類(lèi)問(wèn)題,可以選擇的空間比較多,因此我們就比較堅(jiān)定的選擇了 Swift 作為端側(cè)主要開(kāi)發(fā)語(yǔ)言。

    ???既要因地制宜、又要獲取支持

    當(dāng)項(xiàng)目具備使用 Swift 的條件之后,一定要結(jié)合自身團(tuán)隊(duì)現(xiàn)狀進(jìn)行綜合考慮。

    首先,團(tuán)隊(duì)需要提前培養(yǎng)或者配備一位有 Swift 開(kāi)發(fā)經(jīng)驗(yàn)的人,來(lái)保證復(fù)雜問(wèn)題的攻堅(jiān)以及代碼質(zhì)量的把控。尤其是代碼質(zhì)量,大部分最初從 OC 接觸 Swift 的人,都會(huì)經(jīng)歷一段“不適”期,在這段時(shí)期,很容易寫(xiě)出「OC 味」的 Swift 代碼,所以特別需要一位有熱情、有相關(guān)經(jīng)驗(yàn)和技術(shù)能力的人來(lái)實(shí)踐并表率。

    同時(shí),我們還需要獲得主管的支持,這點(diǎn)很關(guān)鍵,光有技術(shù)熱愛(ài)很難把一件事件持續(xù)做下去。需要結(jié)合項(xiàng)目情況持續(xù)與主管保持溝通,并且在交流過(guò)程中不斷升級(jí)自己對(duì)一個(gè)技術(shù)的思考,讓主管從最初的質(zhì)疑到最后的支持,也是一個(gè)十分有趣的過(guò)程。

    ???需要有一定技術(shù)基礎(chǔ)支撐

    首先,在基礎(chǔ)設(shè)施完備性上,我們做了一次大范圍的 Module 適配工作,解決了混編的核心問(wèn)題。同時(shí)升級(jí)了 DevOps,將包管理工具 tpod 升級(jí)到了 1.9.1 支持了源碼級(jí)別的靜態(tài)庫(kù)版本 framework 工程,同時(shí)還提供了 tpodedit 模式解決頭文件依賴(lài)問(wèn)題,以及在發(fā)布鏈路新增了一些核心卡口檢查防止工程劣化。

    其次,我們基于手淘已有的技術(shù)方案,權(quán)衡性能與效率之類(lèi)的問(wèn)題之后,最終我們結(jié)合對(duì)業(yè)務(wù)研發(fā)的痛點(diǎn)理解,開(kāi)展基于事件鏈編排的研發(fā)模式升級(jí)探索,并從成本上考慮初期在 DX 內(nèi)部共建、并輸出到新奧創(chuàng),整體架構(gòu)如下所示:


    在 UI 層,我們使用 XML 作為 DSL 保證雙端一致性的同時(shí)降低了雙端的開(kāi)發(fā)成本。

    在邏輯編排上,我們?cè)O(shè)計(jì)了事件鏈技術(shù)方案盡可能的原子化每一個(gè)端側(cè)基礎(chǔ)能力,從而保證端側(cè)能力開(kāi)發(fā)者可以聚焦在能力的開(kāi)發(fā)上。

    基于上述框架支持下,開(kāi)發(fā)者可以自行決定單個(gè)基礎(chǔ)能力所使用的開(kāi)發(fā)語(yǔ)言, 對(duì)于新手使用 Swift 的上手成本,可以下降一個(gè)檔次,不再需要和復(fù)雜的環(huán)境做斗爭(zhēng)。

    遇到了哪些問(wèn)題?

    坦率說(shuō),雖然我們?cè)诩夹g(shù)決策的時(shí)候做了深度思考,但當(dāng)真的執(zhí)行起來(lái)的時(shí)候,依舊遇到了不少問(wèn)題。

    ???基礎(chǔ)庫(kù) API 并未適配 Swift

    雖然 Xcode 提供了 “自動(dòng)” 生成橋接文件的能力,但由于 OC 和 Swift 語(yǔ)法差異過(guò)大,大部分自動(dòng)生成的 Swift API 并不遵循?“API Design Guidelines”,這會(huì)導(dǎo)致目前接入的 Swift 業(yè)務(wù)庫(kù)寫(xiě)出很多可讀性差且不好維護(hù)的代碼。

    同時(shí),由于 Swift 的可選值設(shè)計(jì),使得 OC SDK 提供給 Swift 使用時(shí)需要梳理清楚每一個(gè)對(duì)外的 API 入?yún)⒑统鰠⒌目蛇x設(shè)定。商品評(píng)價(jià)重度依賴(lài)的一個(gè)基礎(chǔ) SDK 就沒(méi)有很好的做到這一點(diǎn),以至于我們遇到了不少問(wèn)題。

    • 錯(cuò)誤推導(dǎo)導(dǎo)致的不必要兼容

    我們先看下,下面這段代碼:

    // DemoConfig.h@interface DemoConfig : NSObject/* 此處已省略無(wú)用代碼 */- (instancetype)initWithBizType:(NSString *)bizType;@end// DemoConfig.m@implementation DemoConfig- (instancetype)initWithBizType:(NSString *)bizType {if (self = [super init]) {_bizType = bizType;}return self; }/* 此處已省略無(wú)用代碼 */@end

    由于 DemoConfig 這個(gè)類(lèi)并沒(méi)有注明初始化方法返回值是否可選,以至于 Xcode 默認(rèn)推導(dǎo)的 API 變成了。

    // 自動(dòng)生成的 Swift API open class DemoConfig : NSObject {/* 此處已省略無(wú)用代碼 */public init!(bizType: String!) }

    開(kāi)發(fā)者就不得不去思考如何解決初始化為空的場(chǎng)景,這顯然是多余的。

    除了 SDK 做可選語(yǔ)義適配以外,我們也可以新增一個(gè)分類(lèi),提供一個(gè)返回值不為空的 OC 方法,代碼如下:

    /// DemoConfig+SwiftyRateKit.hNS_ASSUME_NONNULL_BEGIN@interface DemoConfig (SwiftyRateKit)- (instancetype)initWithType:(NSString *)bizType;@endNS_ASSUME_NONNULL_END/// DemoConfig+SwiftyRateKit.m #import <SwiftyRateKit/DemoConfig+SwiftyRateKit.h>@implementation DemoConfig (SwiftyRateKit)- (instancetype)initWithType:(NSString *)bizType {return [self initWithBizType:bizType]; }@end
    • 不安全 API

    沒(méi)有寫(xiě)清楚可選設(shè)定的 OC API 被橋接到 Swift 本質(zhì)上都是不安全的。為什么這么說(shuō)呢?

    我們拿一個(gè)線上 Crash 真實(shí)案例來(lái)舉例,堆棧如下:

    Thread 0 Crashed: 0x0000000000000012 Swift runtime failure: Unexpectedly found nil while implicitly unwrapping an Optional value DemoEventHandler.swift 0x0000000000000011 handle DemoEventHandler.swift 0x0000000000000010 handle <compiler-generated> 0x0000000000000009 -[XXXXXXXXXX XXXXXXXXXX:XXXXXXXXXX:XXXXXXXXXX:] XXXXXXXXXX.m 0x0000000000000008 -[XXXXXXXX XXXXXXXX:XXXXXXXX:XXXXXXXX:] XXXXXXXX.m 0x0000000000000007 +[XXXXXXX XXXXXXX:XXXXXXX:XXXXXXX:] XXXXXXX.m 0x0000000000000006 -[XXXXXX XXXXXX:] XXXXXX.m 0x0000000000000005 -[XXXXX XXXXX:] XXXXX.m 0x0000000000000004 -[XXXX XXXX:] XXXX.m 0x0000000000000003 -[XXX XXX:XXX:] XXX.m 0x0000000000000002 -[XX XX:] 0x0000000000000001 -[X X:]

    客戶(hù)端的實(shí)現(xiàn)代碼如下:

    class?DemoEventHandler:?SwiftyEventHandler?{override func handle(event: DemoEvent?, args: [Any], context: DemoContext?) {guard let ret = context?.demoCtx.engine.value else {return}/// 此處省略無(wú)用代碼} }

    導(dǎo)致 Crash 的原因是?context?.demoCtx.engine.value?這段代碼。

    本質(zhì)原因是 demoCtx 未注明可選語(yǔ)義,導(dǎo)致 OC 橋接到 Swift 的時(shí)候默認(rèn)使用了隱式解包。在讀取過(guò)程中,如果值并沒(méi)有值,會(huì)由于強(qiáng)制解包而直接產(chǎn)生?Unexpectedly found nil while implicitly unwrapping an Optional value?的 Crash。

    要解決這個(gè)問(wèn)題,除了 SDK 做可選語(yǔ)義適配以外,我們還可以可以把調(diào)用代碼都改成可選調(diào)用避免強(qiáng)制解包的問(wèn)題:

    • 破壞性繼承

    在使用上面這個(gè)基礎(chǔ) SDK 遇到最大的問(wèn)題就是?DemoArray?的破壞性繼承。

    DemoArray?繼承自?NSArray?并且重寫(xiě)了不少方法,其中就有?objectAtIndex:?這個(gè)方法。

    在?NSArray?頭文件中清楚的定義了objectAtIndex:?這個(gè)方法的返回值一定不為空,但是 SDK 在?DemoArray?這個(gè)子類(lèi)實(shí)現(xiàn) ?objectAtIndex:?這個(gè)方法時(shí)居然返回了?nil,代碼如下所示:

    這使得使用 Swift 開(kāi)發(fā) SDK 自定義 EventHandler 壓根無(wú)法進(jìn)行。

    核心原因是實(shí)現(xiàn)一個(gè) SDK 自定義 EventHandler 首先要符合 DemoEventHandler 協(xié)議,符合協(xié)議必須實(shí)現(xiàn)?- (void)handleEvent:(DemoEvent *)event args:(NSArray *)args context:(DemoContext *)context;?這個(gè)方法,由于協(xié)議上約定的是?NSArray?類(lèi)型,因此轉(zhuǎn)換成 Swift API args 就變成了?[Any]?類(lèi)型,如下圖所示:

    但是 SDK 傳給 DemoEventHandler 的類(lèi)型本質(zhì)上是一個(gè)?DemoArray?類(lèi)型:

    倘若?DemoArray?里面存在 [Null null] 對(duì)象,就會(huì)導(dǎo)致attempt to insert nil object from objects[0]?的 Crash,如下圖所示:

    具體原因是在調(diào)用?handleEvent(_:args:context:)?時(shí)候,Swift 內(nèi)部會(huì)調(diào)用?static Array._unconditionallyBridgeFromObjectiveC(_:)?把 args 入?yún)⒂?NSArray 轉(zhuǎn)變成 Swift 的 Array,而在調(diào)用 bridge 函數(shù)的時(shí)候,會(huì)先對(duì)原數(shù)組進(jìn)行一次 copy 操作,而在 NSArray Copy 的時(shí)候會(huì)調(diào)用?-[__NSPlaceholderArray initWithObjects:count:]?,由于?DemoArray?的 NSNull 被轉(zhuǎn)變成了 nil,初始化會(huì)失敗,直接 Crash。

    要避免這個(gè)問(wèn)題,讓 SDK 修改?DemoArray?顯然是不現(xiàn)實(shí)的,由于調(diào)用方實(shí)在是過(guò)多,無(wú)論是影響面還是回歸測(cè)試成本短期內(nèi)都無(wú)法評(píng)估。所以只能增加一個(gè)中間層來(lái)解決這個(gè)問(wèn)題。我們首先設(shè)計(jì)了一個(gè) OC 的類(lèi)叫 DemoEventHandlerBox 用于包裝和橋接,代碼如下:

    /// DemoEventHandlerBox.h@class SwiftyEventHandler;NS_ASSUME_NONNULL_BEGIN@interface DemoEventHandlerBox : NSObject<DemoEventHandler>-(instancetype)initWithHandler:(SwiftyEventHandler *)eventHandler;@endNS_ASSUME_NONNULL_END/// DemoEventHandlerBox.m #import <SwiftyRateKit/DemoEventHandlerBox.h> #import <SwiftyRateKit/SwiftyRateKit-Swift.h>@interface DXEventHandlerBox ()/// 處理事件對(duì)象 @property (nonatomic, strong) SwiftyEventHandler *eventHandler;@end@implementation DemoEventHandlerBox-(instancetype)initWithHandler:(SwiftyEventHandler *)eventHandler {self = [super init];if (self) {_eventHandler = eventHandler;}return self; }- (void)handleEvent:(DemoEvent *)event args:(NSArray *)args context:(DemoContext *)context {[self.eventHandler handle:event args:args context:context];return; }@end

    DemoEventHandlerBox 中有個(gè)類(lèi)型為 SwiftyEventHandler 類(lèi)用于邏輯處理,代碼如下:

    @objcMembers public class SwiftyEventHandler: NSObject {@objcpublic final func handle(_ event: DemoEvent?, args: NSArray?, context: DemoContext?) {var ret: [Any] = []if let value = args as? DemoArray {ret = value.origin} else {ret = args as? [Any] ?? []}return handle(event: event, args: ret, context: context)}func handle(event: DemoEvent?, args: [Any], context: DemoContext?) {return}}

    SwiftyEventHandler 暴露給 OC 的方法設(shè)置為 final 同時(shí)做好將?DemoArray?轉(zhuǎn)回 NSArray 的邏輯兼容。最后 Swift 這邊的所有 EventHandler 實(shí)現(xiàn)類(lèi)都繼承自 SwiftyEventHandler 并重寫(xiě)?handle(event:args:context)?方法。這樣就可以完美避免由于破壞性繼承導(dǎo)致的問(wèn)題了。

    ???Clang Module 構(gòu)建錯(cuò)誤

    第二大類(lèi)問(wèn)題主要和依賴(lài)有關(guān),雖然前文有提到,目前的基本基礎(chǔ)設(shè)施已經(jīng)完備,但依舊存在一些問(wèn)題。

    • 依賴(lài)更新不及時(shí)

    很多人剛開(kāi)始寫(xiě) Swift 的時(shí)候,經(jīng)常會(huì)遇到一個(gè)問(wèn)題?Could not build Objective-C module,一般情況下的原因是因?yàn)槟闼蕾?lài)的模塊并沒(méi)有適配 Module,但由于手淘基礎(chǔ)設(shè)施基本已經(jīng)完備,大部分庫(kù)都已經(jīng)完成 Module 化適配,所以你可能只需要更新一下模塊依賴(lài)就可以很好的解決這類(lèi)問(wèn)題。

    例如 STD 這個(gè)庫(kù),手淘目前依賴(lài)的版本是 1.6.3.2,但當(dāng)你的 Swift 模塊需要依賴(lài) STD 的時(shí)候,使用 1.6.3.2 會(huì)導(dǎo)致無(wú)法編譯通過(guò)。這時(shí)候你的 Swift 模塊可能需要升級(jí)到 1.6.3.3 才能解決這個(gè)問(wèn)題。本質(zhì)上 1.6.3.3 和 1.6.3.2 的區(qū)別就是模塊化適配,因此你也不用擔(dān)心會(huì)產(chǎn)生什么副作用。

    • 混編導(dǎo)致的依賴(lài)問(wèn)題

    前文提到的 Module 適配雖然解決了大部分問(wèn)題,但是還是存在一些異常 case,這里展開(kāi)說(shuō)下。

    我們?cè)谏唐吩u(píng)價(jià)重構(gòu)的過(guò)程中,為了保證項(xiàng)目可以循序漸進(jìn)的放量,我們做了代碼的物理隔離,新創(chuàng)建了一個(gè)模塊叫 SwiftyRateKit 是一個(gè) Swift 模塊。但是評(píng)價(jià)列表的入口類(lèi)都在一個(gè)叫 TBRatedisplay 的 OC 模塊。因此為了做切流,TBRatedisplay 需要依賴(lài) SwiftyRateKit 的一些實(shí)現(xiàn)。但當(dāng)我們將 TBRatedisplay 依賴(lài)了 SwiftyRateKit 開(kāi)始編譯之后,就遇到了下面這么一個(gè)問(wèn)題:

    Xcode 將暴露給 OC 的 Swift 類(lèi) ExpandableFastTextViewWidgetNode 的頭文件聲明寫(xiě)到了?SwiftyRateKit-Swift.h?中,ExpandableFastTextViewWidgetNode 是繼承自 TBDinamic 的類(lèi) DXFastTextWidgetNode 的。

    因?yàn)楫?dāng)時(shí) TBRatedisplay 并沒(méi)有開(kāi)啟 Clang Module 開(kāi)關(guān)(CLANG_ENABLE_MODULES),導(dǎo)致?SwiftyRateKit-Swift.h?的下面這么一段宏定義并沒(méi)有生效,因此就不知道 ExpandableFastTextViewWidgetNode 這個(gè)類(lèi)是在哪里定義的了:

    但當(dāng)我們開(kāi)啟 TBRatedisplay 的 Clang Module 開(kāi)關(guān)之后,更恐怖的事情發(fā)生了。由于 TBDinamic 沒(méi)有開(kāi)啟 ?Clang Module 開(kāi)關(guān),導(dǎo)致?@import TBDinamic?無(wú)法編譯通過(guò),進(jìn)入了一個(gè)“死循環(huán)”,最后不得不臨時(shí)移除了所有沒(méi)有支持 ?Clang Module 開(kāi)關(guān)的 OC 模塊導(dǎo)出。

    這里概念比較抽象,我用一張圖來(lái)表示一下依賴(lài)關(guān)系:

    首先,對(duì)于一個(gè) Swift 模塊來(lái)說(shuō),只要模塊開(kāi)啟了 DEFINES_MODULE = YES 且提供了 Umbrella Header 就可以通過(guò)?import TBDinamic?的方式導(dǎo)入依賴(lài)。因此 SwiftyRateKit 可以在 TBDinamic 沒(méi)有開(kāi)啟 Clang Module 開(kāi)關(guān)的時(shí)候就顯示依賴(lài),并可以編譯通過(guò)。

    但對(duì)于一個(gè) OC 模塊來(lái)說(shuō),導(dǎo)入另外一個(gè)模塊分兩種情況。

  • 第一種是開(kāi)啟了 DEFINES_MODULE = YES 的模塊,我們可以通過(guò)?#import <TBDinamic/TBDinamic_Umbrella.h>?導(dǎo)入。

  • 第二種是開(kāi)啟了 Clang Module 開(kāi)關(guān)的時(shí)候,我們可以通過(guò)?@import TBDinamic?導(dǎo)入

  • 由于 TBRatedisplay 依賴(lài)了 SwiftyRateKit,Xcode 自動(dòng)生成的?SwiftyRateKit-Swift.h?頭文件采用的是?@import TBDinamic?的方式來(lái)導(dǎo)入模塊的,因此就造成了上面的問(wèn)題。

    所以我個(gè)人建議現(xiàn)階段要盡量避免或者減少將一個(gè) Swift 模塊的 API 提供給 OC 使用,不然就會(huì)導(dǎo)致這個(gè) Swift 對(duì)外 API 需要依賴(lài)的 OC 模塊都需要開(kāi)啟 Clang Module,同時(shí)依賴(lài)了這個(gè) Swift 模塊的 OC 模塊也需要開(kāi)啟 Clang Module。而且,由于 Swift 和 OC 語(yǔ)法不對(duì)等,會(huì)讓 Swift 開(kāi)發(fā)出來(lái)的接口層能力非常受限,從而導(dǎo)致 Swift 對(duì)外的 API 變得相當(dāng)不協(xié)調(diào)。

    • 類(lèi)名與 Module 同名

    理論上 Swift 模塊之間互相調(diào)用是不會(huì)存在問(wèn)題的。但由于手淘模塊眾多,歷史包袱過(guò)重,我們?cè)谧錾唐吩u(píng)價(jià)改造的時(shí)候遇到了一個(gè)「類(lèi)名與 Module 同名」的苦逼問(wèn)題。

    我們個(gè) SDK 叫 STDPop,這個(gè) SDK 的 Module 名也叫 STDPop,同時(shí)還有一個(gè)工具類(lèi)也叫 STDPop。這會(huì)導(dǎo)致什么問(wèn)題呢?所有依賴(lài) STDPop 的 Swift 模塊,都無(wú)法被另外一個(gè) Swift 模塊所使用的,會(huì)報(bào)一個(gè)神奇的錯(cuò)誤:'XXX' is not a member type of class 'STDPop.STDPop'?主要原因是因?yàn)橐蕾?lài) STDPop 的 Swift 模塊生成的 .swiftinterface 文件時(shí)會(huì)給每個(gè) STDPop 的類(lèi)加一個(gè) STDPop 的前綴。例如?PopManager?會(huì)變成?STDPop.PopManager?但由于 STDPop 本身就一個(gè)類(lèi)叫 STDPop 會(huì)就導(dǎo)致編譯器無(wú)法理解 STDPop 到底是 Module 名還是類(lèi)名。

    而能解決這個(gè)問(wèn)題的唯一辦法就是需要 STDPop 這個(gè)模塊移除或者修改 STDPop 這個(gè)類(lèi)名。

    具體有哪些方面的收益?

    我們?cè)谝淮紊钏际鞈]之后,踏上了披荊斬棘的 Swift 落地之路,雖然在整個(gè)過(guò)程中遇到了很多前所未有的挑戰(zhàn),但現(xiàn)在回過(guò)來(lái)看,我們當(dāng)初的技術(shù)選型還是比較正確的。主要體現(xiàn)在下面幾個(gè)方面:

    ???代碼量減少,Coding 效率提高

    得益于 Swift 的強(qiáng)表達(dá)性,我們可以用更少的代碼去實(shí)現(xiàn)一個(gè)原本用 OC 實(shí)現(xiàn)的邏輯,如下圖所示,我們不再需要寫(xiě)過(guò)多的防御性編程的代碼,就可以清晰的表達(dá)出我們要實(shí)現(xiàn)的邏輯。

    同時(shí),我們對(duì)原有 13 個(gè)用 OC 實(shí)現(xiàn)的表達(dá)式,用 Swift 重新寫(xiě)了一遍,整體代碼量的變化如下:

    代碼量的變少意味著需要投入開(kāi)發(fā)的時(shí)間變少了,同時(shí)產(chǎn)生 bug 的機(jī)會(huì)也就變少了。

    ???大幅降低交叉 Review 的成本

    OC 奇特的語(yǔ)法使得大部分其他開(kāi)發(fā)壓根無(wú)法看懂具體的邏輯,從而導(dǎo)致 iOS 和 Android 雙端交叉 Review 的成本相當(dāng)之高,也會(huì)使得很多庫(kù)經(jīng)常存在雙端邏輯不一致性。

    當(dāng)初在做訂單遷移新奧創(chuàng)時(shí),面對(duì)較多雙端API不一致,且部分代碼邏輯的味道較復(fù)雜,項(xiàng)目上發(fā)生過(guò)多起臨時(shí)問(wèn)題排查影響節(jié)奏的事情。

    因此,我們另辟蹊徑,采用 Swift & Kotlin 的模式進(jìn)行開(kāi)發(fā),由于 Swift 和 Kotlin 的語(yǔ)法極度相似,使得我們交叉 Review 毫無(wú)壓力。

    同時(shí),得益于商品評(píng)價(jià)使用的腳手架,后續(xù)需求迭代也大幅下降。我們以「評(píng)價(jià) Item 新增分享按鈕」為例:

    如果采用 OC & Java 模式,因?yàn)殡p端代碼都看不懂。所以需求評(píng)審都雙端需要各派 1 名,加上討論等各種事宜大概需要 0.5 人日。然后雙端討論方案后一個(gè)人進(jìn)行模板開(kāi)發(fā),需要 1 人日左右。最后雙端各自實(shí)現(xiàn)原生分享原子能力,需要各 2 人日左右(其中有 1 人日需要調(diào)研如何接入分享 SDK),總計(jì) 2 * 0.5 + 1 + 2 * 2 = 6 人日。

    但是如果采用 Swift & Kotlin 的模式,我們只需要有 1 人取參加需求 Review,0.5 人日。單人完成技術(shù)調(diào)研、模板開(kāi)發(fā) 3 人日左右。最后再把寫(xiě)好的代碼給另外一端看,另外一端可以直接 copy 代碼并根據(jù)自己端的特點(diǎn)進(jìn)行適配 1 人日左右。總計(jì) 0.5 + 3 + 1 = 4.5 人日左右。大約節(jié)省 25% 的時(shí)間。

    ???項(xiàng)目穩(wěn)定性有所提高

    因?yàn)闆](méi)有比較好的量化指標(biāo),只能談?wù)劯惺堋?/p>

    首先,由于編碼問(wèn)題導(dǎo)致的提測(cè)問(wèn)題明顯下降,基本上的異常分支流得益于 Swift 的可選值設(shè)計(jì),都已經(jīng)在開(kāi)發(fā)階段考慮清楚了,總體提測(cè)問(wèn)題明顯比使用 OC 時(shí)少了很多。

    其次,線上問(wèn)題也明顯下降,除了上文提到的 Crash 問(wèn)題。商品評(píng)價(jià)重構(gòu)項(xiàng)目基本上沒(méi)有發(fā)生線上問(wèn)題。

    ???優(yōu)先享受技術(shù)紅利

    無(wú)論是 WidgetKit 還是 DocC,可以很明顯的看得出來(lái),蘋(píng)果內(nèi)部對(duì)于新特性以及開(kāi)發(fā)工具鏈的升級(jí)一定是 Swift 優(yōu)先于 OC,因此所有使用 Swift 的同學(xué)都能很快速的使用上所有蘋(píng)果新開(kāi)發(fā)的特性和工具。

    同時(shí),也得益于 Swift 的開(kāi)源,我們不僅可以通過(guò)源碼去學(xué)習(xí)一些不錯(cuò)的設(shè)計(jì)模式,還可以定位一些疑難雜癥,不再需要和生澀難懂的匯編代碼作斗爭(zhēng)。

    總結(jié)與展望

    以上算是我們?cè)谑痔蕴剿?Swift 業(yè)務(wù)落地的一個(gè)總結(jié),希望可以給大家在技術(shù)選型或者探索避坑的時(shí)候給到一點(diǎn)幫助,當(dāng)然,這只是一個(gè)開(kāi)始,還有很多事情值得去做。首先,我們需要一起去完善和規(guī)范 Swift 的編碼規(guī)范,甚至沉淀一系列最佳實(shí)踐去引導(dǎo)大家更低成本的從 OC 轉(zhuǎn)型到 Swift;其次,我們也需要針對(duì)前文提到的混編問(wèn)題,推動(dòng)基礎(chǔ) SDK 做 Swift Layer 建設(shè)以及繼續(xù)優(yōu)化現(xiàn)有 Swift 工具鏈;最后,我們還需要引入一些優(yōu)秀的開(kāi)源庫(kù)避免重復(fù)造輪子,以及利用好 Apple 提供的能力(例如 DocC),并最終找到一個(gè) Swift 在手淘的最佳實(shí)踐。

    文獻(xiàn)參考

  • https://mp.weixin.qq.com/s/pQiLyl572fSgMX1Fq3RDhw

  • https://github.com/apple/swift-evolution

  • https://mp.weixin.qq.com/s/5SXAozM2c6Ivyzl7B9IfQQ

  • https://www.swift.org/documentation/api-design-guidelines/

  • https://www.jianshu.com/p/0d3db4422954

  • https://developer.apple.com/videos/play/wwdc2020/10648

  • https://madnight.github.io/githut/

  • https://mp.weixin.qq.com/s/O5sVO5gVLzDCHGGNcjQ1ag

  • 團(tuán)隊(duì)介紹

    我們是大淘寶平臺(tái)技術(shù)的「終端平臺(tái)技術(shù)部」。我們擁有世界最大的電商場(chǎng)景和一流的移動(dòng)技術(shù)平臺(tái),打造著領(lǐng)先行業(yè)的技術(shù)產(chǎn)品,服務(wù)遍布全世界10億+的消費(fèi)者,處理每日千億級(jí)的海量用戶(hù)請(qǐng)求。

    作為阿里最重要的客戶(hù)端團(tuán)隊(duì),我們負(fù)責(zé)淘寶移動(dòng)域研發(fā)運(yùn)維支撐、原生技術(shù)挖掘、核心技術(shù)建設(shè),包括不限于客戶(hù)端體驗(yàn)、框架及創(chuàng)新體驗(yàn)、廠商與系統(tǒng)技術(shù)、用戶(hù)增長(zhǎng)及移動(dòng)平臺(tái)。無(wú)論是基礎(chǔ)設(shè)施、業(yè)務(wù)創(chuàng)新還是技術(shù)發(fā)展,我們團(tuán)隊(duì)都能為你提供巨大的機(jī)遇和成長(zhǎng)空間,期待您加入

    感興趣的同學(xué)可將簡(jiǎn)歷發(fā)送到:zhejian.wzj#alibaba-inc.com(發(fā)送郵件時(shí)請(qǐng)把#改為@),獲取優(yōu)先內(nèi)推資格!

    ???拓展閱讀

    作者|柘劍

    編輯|橙子君

    出品|阿里巴巴新零售淘系技術(shù)

    總結(jié)

    以上是生活随笔為你收集整理的Swift 在淘宝商品评价的技术重构与实践的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。

    如果覺(jué)得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。