Kommunikation zwischen iOS-Komponenten, eine weitere charakteristische Implementierung

Dieser Artikel hat an der Aktivität „Neuankömmling-Schöpfungszeremonie“ teilgenommen, und gemeinsam werden wir den Weg zur Goldnugget-Schöpfung einschlagen.

Kommunikation zwischen Komponenten, aber jedes größere Projekt wird eine modulare Entwicklung durchführen, und es wird unweigerlich auf Probleme der Entkopplung und Kommunikation zwischen Geschwisterkomponenten stoßen.

Wie können Module einander Nachrichten übermitteln, ohne sich aufeinander zu verlassen? Es gibt viele Lösungen online, wie zum Beispiel:

  1. URL-Routing
  2. Zielaktion
  3. Protokoll

iOS: drei Kommunikationslösungen für die Komponentisierung

Es gibt auch viele Vertreter von Komponenten von Drittanbietern, MGJRouter, CTMediator, BeeHive, ZIKRouterusw.

Aber sie haben mehr oder weniger ihre eigenen Vor- und Nachteile, und ich werde hier nicht ins Detail gehen, aber im Grunde gibt es mehrere Probleme:

  1. Es ist umständlich zu verwenden, muss die Kosten verstehen und muss für die Entwicklung viel redundanten Code schreiben.
  2. Grundsätzlich müssen Sie sich zuerst registrieren und dann implementieren. Dann gibt es keine Garantie dafür, dass der Code eine Implementierung haben muss, und es kann auch nicht garantiert werden, ob die Implementierung mit der Registrierung inkonsistent ist (natürlich können Sie einige Überprüfungsmethoden hinzufügen, z. B. statische Erkennung). Das ist bei größeren Projekten sehr schmerzhaft, entweder traut man sich nicht, den historischen Code zu löschen, um Schulden anzuhäufen, oder man ist in der Vergangenheit leichtsinnig und es gibt Probleme beim Testen oder online [Handbuch Hundekopf].
  3. Wenn ein Modell übergeben werden muss, sinken Sie entweder zum öffentlichen Modul oder übertragen Sie NSDictionary. Oder öffentliche Schichtschulden oder Modelländerungen verursachen Laufzeitprobleme.

Gibt es eine Wunderwaffe? Dies ist die Implementierungsmethode, über die ich dieses Mal sprechen und das Problem aus einem anderen Blickwinkel lösen werde.

verschiedene Lösungen

Überlegen Sie anhand der obigen Fragen, was wir erreichen wollen:

  1. Es besteht keine Notwendigkeit, die Entwicklungskosten zu erhöhen, und es besteht keine Notwendigkeit, das allgemeine Implementierungsprinzip zu verstehen.
  2. Vom Komponentenanbieter bereitgestellt, gibt es zuerst eine Implementierung und dann eine Definition, um sicherzustellen, dass die API vollständig verfügbar ist. Wenn sich die Implementierung ändert, kompiliert der Aufrufer und meldet einen Fehler (bevor das Problem aufgedeckt wird). Und andere Module sind nicht davon abhängig, können diese Methode aber genau aufrufen.
  3. Alle Arten von Modellen können normal im Modul verwendet werden und können auch normal verwendet werden, wenn sie der Außenwelt ausgesetzt sind, aber es besteht keine Notwendigkeit, sie in das öffentliche Modul zu versenken.

Fühlt es sich an, als würde man zu viel verlangen? Das ist wie ein Dreckskerl, der dich nicht heiraten will, aber auch Kinder mit dir haben will [manueller Hundekopf].

但能不能实现呢,确实是可以的。但解决办法不在 iOS 本身,而在 codegen。铺垫到这里,我们来看看具体实现。

GDAPI 原理

在笔者所在的稿定,之前用的是 CTMediator 方案做组件间通信,当然也就有上面的那些问题,甚至线上也出现过因为 Protocol 找不到 Mediator 导致的线上 crash。

为了解决定义和实现不匹配的问题,我们希望定义一定要有实现,实现一定要跟定义一致。

那是否就可以换个思路,先有实现,再有定义,从实现生成定义。

这点参考了 JAVA 的注解机制,我们定义了一个宏 GDM_EXPORT_MODULE(),用于说明哪些方法是需要开发给其他模块使用的。

// XXLoginManager.h

/// 判断是否登陆
- (BOOL)isLogin GDM_EXPORT_MODULE();
复制代码

这样在组件开发方就完成了 API 开放,剩下的工作就是如何生成一个调用层代码。

调用层代码其实也就是 CTMediator 的翻版,通过 iOS 的运行时反射机制去寻找实现类

// XXService.m

static id<GDXXXAPI> _mXXXService = nil;
+ (id<GDXXXAPI>)XXXService {
    if (_mXXXService == nil) {
        _mXXXService = [self implementorOfName:@"GDXXXManager"];
    }
    return _mXXXService;
}
复制代码

我们把这些生成的方法调用,生成到一个 GDAPI 模块统一存储,当然这个模块除了上述模块的 Service 层是要有具体的 .m 来做落地,其他都是 .h 的头文件。

那调用侧只需要 pod 增加依赖 s.dependency 'GDAPI/XXXXService' 即可调用到具体实现了

@import GDAPI;

...

bool isLogin = [GDAPI.XXService isLogin];

复制代码

这里肯定有同学会问,生成过程呢???

笔者是用 Ruby 代码实现了整个 codegen 过程,当时没选择 Python 主要是为了跟 cocoapods 使用相同的开发语言,易于做侵入设计,但其实用其他语言都没问题,通过 shell 脚本做中转即可。

Bild.png

这里源码有些定制化实现,放出来现在也是徒增大家烦恼,所以讲一下生成关键过程:

  1. 遍历组件所在目录,取出所有的 .h 文件,缓存在 Map<文件路径,文件内容>(一级缓存)
  2. 解析存在 GDM_EXPORT_MODULE() 的方法,将方法的名称、参数、注释通过正则手段分解成相应的属性,存储到 Map<模块名,API 模型列表> (二级缓存)
  3. 对于每一个 API 模型进行进一步解析,解析入参和出参,判断参数类型是否为自定义类型(模型、代理、枚举、包括复杂的 NSArray<CustomModel *> * 等),如果有存在,则遍历一级缓存,找到自定义类型的定义,生成对应的 Model -> Procotol 等,且存储在多个 Map 中 Map<类名/代理名/枚举名,具体解析后的模型>(三级缓存)

有了上述各种模型,就差不多完成了 AST (抽象语法树) 的生成过程,至于为什么是用的正则而不是 iOS 的 AST 工具,主要原因是想做的很轻,尽量减少大家的构建时长,不要通过编译来实现。

  1. 有了 AST 生成就变得很简单,模版代码 + 模版输出即可

Bild.png

可以看到已经有大量模块生成了相应的 GDAPI

Bild.png

执行时长在 2S 左右,因为有一个预执行的过程,来做组件项目化,这个也算是特殊实现了。 实质上执行也就 1S 即可。

还有一点要说的是执行时机是在 pod install / update 之前,这个是通过 hooks cocoapods 的执行过程做到的。

一些难点

嵌套模型

上面虽然粗略的讲了下 Model / Procotol 会生成 Protocol,但其实这一部分确实是最困难的,也是因为历史积债问题,下沉在公共模块的庞大的模型在各个组件里传输。

那要把它完全的 API 化,就需要对它的属性进行递归解析,生成完全符合的 protocol

例如:

... 举例为伪代码,OC 代码确实很啰嗦

class A extends B {
    C c;
    
    NSArray<D> d;
}

/// 测试
- (void)test:(A *)a GDM_EXPORT_MODULE();
复制代码

生成结果就如下图(伪代码):


@protocol GDAPI_A {
    NSObject<GDAPI_C> c;
    
    NSArray<NSObject<GDAPI_D>> d;
}

@protocol GDAPI_B {
}

@protocol GDAPI_C {
}

@protocol GDAPI_D {
}

以及调用服务

@protocol GDXXXAPI <NSObject> 
/// 测试
- (void)test:(NSObject<GDAPI_A, GDAPI_B>)a;

复制代码

这个在落地过程中坑确实非常多。

B 模块想创建 A 模块的模型

当然这个是很不合理的,但现实中确实很多这样的历史问题。

当然也不能用模型下沉开倒车,那解决上用了一个巧劲

/// 创建 XX
- (XXXModel *)createXXX GDM_EXPORT_MODULE();
复制代码

提供一个创建模型的 API 给外部使用,这样对于 Model 的管理还是在模块内,外部模块使用上从 new XXX() 改为 [GDAPI.XXService createXX]; 即可。

零零碎碎

Es ist auch in einigen Bibliotheken von Drittanbietern und Drittanbietern sehr üblich, Regelmäßigkeit zu verwenden, um AST zu beurteilen und zu erfassen, aber es ist OCwirklich hinaus gibt es viele historische Codes, die nicht standardisiert sind, und es gibt sie diverse Leerzeichen und Kommentare Das Schreiben einer allgemeinen Adaption gilt als relativ zeitintensiv.

Es gibt auch einige personalisierte Kompatibilität, und es gibt auch einige hartcodierte Situationen.Zum Beispiel befindet sich das Modell, dem einige Komponenten zugeordnet sind, im Framework, und eine entsprechende Tabelle wird @classaus .

Nachverfolgen

Der Artikel (jing) und die Länge (li) sind begrenzt, daher erkläre ich es nicht mehr. Diese Umsetzungsidee hat viele der späteren Entwicklungsprozesse des Autors beeinflusst. Wenn Sie interessiert sind, können Sie den Artikel des Autors auf Flutter lesen. die auch Codegen ausgiebig nutzt.

Wenn Sie Fragen haben, können Sie diese im Kommentarbereich gemeinsam besprechen.

Es ist nicht einfach, mit der Hand zu klopfen. Wenn Sie Inspiration für Ihr Studium und Ihre Arbeit haben, hinterlassen Sie bitte ein Like, danke fürs Lesen~~

Ich denke du magst

Origin juejin.im/post/7137240001112178702
Empfohlen
Rangfolge