← Tous les articles

De quoi est faite SwiftUI

SwiftUI repose sur trois fonctionnalités du langage Swift : les result builders, les types de retour opaques et un arbre de vues à types valeur. Une fois le substrat visible, les parties de SwiftUI qui surprennent les développeurs (AnyView, Group, ViewBuilder, les paramètres @ViewBuilder, la redoutable erreur some View vs any View) cessent d’être mystérieuses.

Une vue SwiftUI est un type valeur qui se conforme à un unique protocole comportant une unique exigence. Le reste du framework est construit sur des fonctionnalités du langage Swift qui existent en dehors de SwiftUI : result builders, types opaques, génériques avec contraintes, property wrappers. Si vous comprenez les fonctionnalités du langage, le framework se lit comme du API Swift normal. Sinon, le framework se lit comme une magie qui mord à l’occasion.

Cet article parcourt le substrat. Il n’y a pas de LiveActivityManager ici, pas de capture d’écran de Get Bananas. Le sujet est le framework, pas un projet ; une fois le framework lisible, chaque article du cluster qui parle de code livré se lit plus clairement.

TL;DR

  • Une vue SwiftUI est un type valeur Swift qui se conforme à View. Le protocole a une seule exigence : var body: some View { get }. Tout le reste est construit par-dessus des fonctionnalités du langage Swift.
  • @ViewBuilder est un result builder. Le body de chaque View en est un. Les result builders transforment des expressions séparées par des virgules en une seule valeur de retour à travers des appels synthétisés par le compilateur.
  • some View est un type de retour opaque. Le compilateur connaît le type concret ; l’appelant ne le connaît pas. Ce type opaque est ce qui rend les bodies de vue rapides à la compilation et à l’exécution ; AnyView est la trappe d’évacuation à effacement de type pour les cas où l’opacité ne fonctionne pas.
  • Group, EmptyView, TupleView, _ConditionalContent sont les types d’implémentation que les result builders synthétisent. Ils sont documentés mais rarement écrits à la main.

Le protocole qui démarre tout

Le protocole View a une seule exigence :1

public protocol View {
    associatedtype Body : View
    @ViewBuilder var body: Self.Body { get }
}

Deux éléments de ce protocole comptent pour comprendre le reste de SwiftUI.

Le type associé Body : View. Le body d’une vue est lui-même une vue. C’est cette récursion qui rend le framework composable. Chaque View retourne une autre View depuis son body, et ainsi de suite, jusqu’à ce que la chaîne se termine sur l’une des vues primitives du framework (telles que Text, Color, Image, EmptyView) dont le Body est Never. Les vues primitives sont les feuilles de l’arbre ; les vues que vous écrivez en sont les branches.

L’attribut @ViewBuilder sur body. Chaque body est une closure de result builder. Les result builders sont une fonctionnalité du langage Swift documentée dans SE-0289 (formalisée sous @resultBuilder dans Swift 5.4) qui permet à une closure contenant une séquence d’expressions d’être transformée par le compilateur en une seule valeur de retour via des appels de méthodes synthétisés.2 C’est cette transformation qui fait fonctionner la syntaxe sans virgule, en forme d’instructions, à l’intérieur d’un body SwiftUI.

La forme du protocole est inhabituelle pour deux raisons.

D’abord, l’exigence est une propriété calculée, pas une méthode. Le body de la vue est recalculé à chaque passe de rendu lorsque SwiftUI décide que l’état de la vue a changé. Le framework considère que body est peu coûteux à appeler ; les calculs longs à l’intérieur de body sont un anti-pattern parce qu’ils s’exécutent à chaque rendu.

Ensuite, Self.Body est associé, pas effacé. Le type de body concret d’une vue fait partie de sa signature à la compilation. Le type de body de Text("Hello") est Never ; le type de body d’une vue personnalisée est tout ce que @ViewBuilder a synthétisé pour le body. La conception en type associé est ce qui permet au compilateur d’optimiser l’arbre de vues sans contrôles de type à l’exécution. C’est aussi ce qui crée l’exigence some View lorsqu’une vue personnalisée retourne du contenu conditionnel.

Result builders : le DSL sans virgule

Un result builder est une fonctionnalité du langage Swift qui transforme une closure en une seule valeur de retour en insérant des appels de méthodes synthétisés par le compilateur. @ViewBuilder est un result builder. Le body de chaque vue SwiftUI est sa closure.2

Considérez cette vue :

struct ExampleView: View {
    var body: some View {
        Text("Title")
        Text("Subtitle")
        Image(systemName: "star")
    }
}

Le body comporte trois instructions sans séparateur. En Swift normal, c’est une erreur de compilation : une closure ne peut retourner qu’une seule valeur. Les result builders réécrivent la closure avant compilation. Le code réel que voit le compilateur, après l’expansion de @ViewBuilder, ressemble à peu près à ceci :

struct ExampleView: View {
    var body: some View {
        ViewBuilder.buildBlock(
            Text("Title"),
            Text("Subtitle"),
            Image(systemName: "star")
        )
    }
}

ViewBuilder.buildBlock(_:_:_:) est une méthode statique qui prend trois vues et retourne un TupleView<(Text, Text, Image)>. Le body retourne cette unique valeur de tuple-view. Les anciennes versions de SwiftUI livraient un ensemble fixe de surcharges de buildBlock pour 1, 2, 3, … jusqu’à 10 enfants ; la version actuelle de SwiftUI utilise la prise en charge des génériques variadiques de Swift (buildBlock<each Content>), de sorte qu’un body avec onze vues sœurs ou plus n’est plus un cas particulier.

Le même schéma gère le flux de contrôle. Un body de vue avec une instruction if ressemble à ceci :

struct ConditionalView: View {
    let isActive: Bool
    var body: some View {
        if isActive {
            Text("Active")
        } else {
            Text("Inactive")
        }
    }
}

@ViewBuilder le réécrit via des appels buildEither(first:) / buildEither(second:), produisant un _ConditionalContent<Text, Text>. Le compilateur connaît le type de résultat à la compilation, même si une seule branche s’exécute lors d’un rendu donné.

if let, switch, le déballage d’optionnels et quelques autres constructions sont gérés par les diverses méthodes statiques buildXxx du result builder.3 Le contenu répété est le seul cas notable que la fonctionnalité du langage prend en charge via buildArray mais que @ViewBuilder ne prend pas en charge : une boucle for brute à l’intérieur d’un body échoue avec « closure containing control flow statement cannot be used with result builder ‘ViewBuilder’. » La réponse à la SwiftUI est ForEach, qui prend un RandomAccessCollection et une closure de contenu et synthétise l’itération en une seule vue à type valeur. Le DSL n’est pas sur mesure ; ce sont les result builders Swift, configurés pour les vues.

some View et le problème de l’opacité

Le body d’une vue personnalisée retourne généralement some View. Le mot-clé désigne un type de retour opaque et a été ajouté dans Swift 5.1.4

some View dit : « Je retourne un type spécifique qui se conforme à View, mais je ne vous dis pas lequel. » Le compilateur suit le type concret en interne pour l’optimisation ; l’appelant de votre vue ne voit que le témoin de protocole. Ce schéma est ce qui permet au body d’une vue de retourner un type complexe comme VStack<TupleView<(Text, Image, Spacer)>> sans vous obliger à écrire ce type dans votre code source.

Deux choses concernant some View qui troublent les nouveaux développeurs SwiftUI :

some View est un type spécifique unique, même quand vous retournez des choses différentes. L’expression if condition { Text("A") } else { Image("b") } est autorisée à l’intérieur d’un body @ViewBuilder parce que le result builder enveloppe les deux branches dans _ConditionalContent, produisant un seul type concret. Mais l’expression if condition { return Text("A") } else { return Image("b") } en dehors d’un result builder est une erreur de compilation : les deux branches retournent des types concrets différents, et some View en exige un seul. Ce sont les result builders qui font fonctionner les formes de retour conditionnelles ; les return explicites perdent la transformation du result builder.

some View n’est pas la même chose que any View. some View est opaque (un type spécifique unique, caché) ; any View est existentiel (une boîte qui peut contenir n’importe quel type conforme, avec une surcharge à l’exécution). Une fonction libre ou une propriété peut légalement retourner any View. Le body du protocole View, en revanche, ne le peut pas : le protocole exige associatedtype Body: View, et any View lui-même ne se conforme pas à View, donc var body: any View n’arrive pas à satisfaire le protocole et le compilateur suggère some View. La règle pratique : utilisez some View pour les bodies de vue, recourez à AnyView (le wrapper à effacement de type) pour les types de vue variables à l’exécution. Le message d’erreur « function declares an opaque return type but the return statements in its body do not have matching underlying types » signifie presque toujours que vous avez essayé de retourner différents types concrets depuis une fonction some View et que vous avez besoin soit de la branchement par result builder, soit d’AnyView.

AnyView : la trappe d’évacuation

AnyView est un wrapper de vue à effacement de type. La construction est AnyView(myView). Le wrapper contient n’importe quelle vue conforme, et SwiftUI l’accepte là où une View est attendue.5

Le cas d’usage de la trappe d’évacuation est une fonction qui retourne différents types concrets en fonction de données d’exécution et qui ne peut pas être exprimée par le branchement de result builder :

func viewForKind(_ kind: Kind) -> AnyView {
    switch kind {
    case .text: return AnyView(Text("hello"))
    case .image: return AnyView(Image("photo"))
    case .custom: return AnyView(MyCustomView())
    }
}

Le coût d’AnyView est que le type sous-jacent ne fait pas partie de l’identité statique de la vue. La documentation d’Apple décrit la conséquence directement : lorsque le type enveloppé dans un AnyView change d’un rendu à l’autre, la hiérarchie de vues existante est détruite et une nouvelle hiérarchie est créée à sa place, ce qui signifie un état perdu, des animations redémarrées et une identité perdue. Réenvelopper le même type concret ne déclenche pas cette destruction, mais le diffing piloté par les types statiques que le framework préfère n’est plus disponible non plus.

La bonne règle est : préférez le branchement par result builder @ViewBuilder pour les vues conditionnelles (if, switch, for), préférez les vues paramétrées pour les types variables, ne recourez à AnyView que lorsque ni l’un ni l’autre ne fonctionne. Une fonction viewForKind qui retourne AnyView est généralement le signe que vous devriez faire en sorte que viewForKind retourne some View et placer un switch à l’intérieur d’une closure de result builder.

Group, EmptyView, TupleView : les types d’implémentation

Le result builder synthétise des types de vue concrets spécifiques. Trois d’entre eux sont utiles à reconnaître :6

Group est un conteneur transparent. Il accepte jusqu’à dix vues en contenu et les présente comme sœurs au layout parent. Le conteneur lui-même n’ajoute aucune structure visuelle ; le contenu se rend exactement comme il le ferait individuellement. Le cas d’usage est l’enveloppement de plusieurs vues dans un contexte qui attend une seule vue (un modificateur .if, un retour conditionnel, une fonction qui produit « une vue »). Group { Text("A"); Text("B") } est une vue unique qui en contient deux ; c’est la forme explicite de ce que les result builders font implicitement.

EmptyView est une vue qui ne rend rien. Le result builder l’utilise comme branche fausse conditionnelle lorsqu’un if n’a pas d’else. Retourner EmptyView() depuis votre propre code est une façon de désactiver le rendu sans changer le type de retour de la fonction.

TupleView est le type concret que les result builders produisent lorsqu’un body comporte plusieurs vues sœurs. L’expression au début de cet article qui retourne trois vues sœurs retourne en réalité un TupleView<(Text, Text, Image)>. Vous n’écrivez presque jamais TupleView directement ; vous le lisez dans les messages d’erreur.

_ConditionalContent (avec le tiret bas en préfixe) est le type qui gère les branches if/else. Le type apparaît dans la surface publique de ViewBuilder, mais le nom souligné signale « ne pas écrire contre cela à la légère » ; laissez le result builder le synthétiser à partir d’if/else plutôt que de le construire à la main.

@ViewBuilder sur vos propres fonctions

Les result builders ne sont pas réservés au body. N’importe quelle fonction ou paramètre de closure peut être annoté @ViewBuilder, et la même syntaxe DSL devient légale à l’intérieur.2

struct Card<Content: View>: View {
    let content: Content

    init(@ViewBuilder content: () -> Content) {
        self.content = content()
    }

    var body: some View {
        VStack(alignment: .leading, spacing: 8) {
            content
        }
        .padding()
        .background(.regularMaterial)
        .clipShape(RoundedRectangle(cornerRadius: 12))
    }
}

// Usage: callers get the result-builder DSL inside the closure.
Card {
    Text("Title")
    Text("Subtitle")
    Image(systemName: "star")
}

C’est ce schéma qui permet aux propres VStack, HStack, ZStack, List, Form, Section, Group et NavigationStack de SwiftUI d’accepter plusieurs enfants. Chacun de ces types prend un paramètre @ViewBuilder content: () -> Content. Reconnaître le schéma signifie que vous pouvez écrire vos propres vues de conteneur avec la même ergonomie que celles du framework, sans aucun support spécial du compilateur requis.

La raison pour laquelle vous écrivez init(@ViewBuilder content:) et pas seulement init(content:) est que l’attribut sur le paramètre est ce qui active la transformation de result builder à l’intérieur du body de la closure que l’appelant passe. Sans l’attribut, Card { Text("A"); Text("B") } est une erreur de compilation parce que la closure a deux instructions et aucun @ViewBuilder pour les transformer.

État, bindings et la couche des property wrappers

Tout ce qui précède concerne la forme de l’arbre de vues. L’autre moitié de SwiftUI, c’est l’état, et cette moitié est construite sur les property wrappers de Swift.7

Les property wrappers les plus pertinents pour l’écriture de vues :

@State possède une portion d’état à type valeur à l’intérieur d’une seule vue. Lire la propriété lit le stockage sous-jacent ; lui assigner une valeur déclenche un nouveau rendu de la vue. Le wrapper convient à un état simple, local à la vue (l’état activé/désactivé d’un toggle, la chaîne de brouillon d’un champ texte).

@Binding est une référence bidirectionnelle vers l’état d’une autre vue. Une vue enfant qui a besoin de lire et d’écrire l’état d’un parent prend un paramètre Binding<T>. Le parent construit le binding via $state (la projection avec le signe dollar sur @State).

@Observable (iOS 17+) est une macro qui remplace l’ancien schéma de conformité ObservableObject. La macro appliquée à une classe génère le suivi du framework Observation, de sorte que les propriétés de la classe déclenchent un nouveau rendu des vues lorsqu’elles sont lues à l’intérieur d’un body et modifiées par la suite. La possession côté vue d’une classe @Observable passe de @StateObject à un simple @State ; les vues en aval qui ont besoin d’un handle bidirectionnel utilisent @Bindable au lieu d’@ObservedObject.

@Environment lit des valeurs injectées par dépendance depuis la chaîne d’environnement. SwiftUI fournit des clés d’environnement intégrées (locale, schéma de couleurs, action de fermeture) ; les applications ajoutent des clés personnalisées pour l’injection de dépendances spécifiques au domaine.

La couche des property wrappers est ce qui permet au body d’une vue de se réexécuter lorsque l’état change. SwiftUI suit les lectures à l’intérieur de body à travers deux mécanismes distincts : AttributeGraph (le graphe de dépendances privé d’Apple qui sous-tend @State, @Binding et @Environment) pour l’ancien chemin des property wrappers, et le framework Observation de la bibliothèque standard (withObservationTracking, public sur iOS 17+) pour les types @Observable.8 Lorsqu’une propriété suivie est mutée, les bodies correspondants sont réexécutés et la machinerie de diffing calcule le changement minimal de l’arbre de vues.

Les deux moitiés (la couche de l’arbre de vues et la couche d’état) sont faiblement couplées. L’arbre de vues est à types valeur et rapide à recalculer. La couche d’état est à types référence (pour @Observable) ou à types valeur avec pointeur de stockage (pour @State) et suit les lectures. Ensemble, elles produisent le modèle « décrivez ce qui devrait être à l’écran en fonction de l’état, et le framework calcule le diff » du framework.

Ce que vous reconnaissez désormais dans les messages d’erreur

Lire les erreurs de compilation SwiftUI avec le substrat visible :

« Function declares an opaque return type, but the return statements in its body do not have matching underlying types. » Deux instructions return avec différents types concrets dans une fonction some View. Correctif : utilisez @ViewBuilder pour que le result builder enveloppe les deux dans _ConditionalContent, ou enveloppez les deux retours dans AnyView.

« The compiler is unable to type-check this expression in reasonable time. » Les longues chaînes de body avec de nombreux modificateurs épuisent le vérificateur de types. Correctif : décomposez le body en propriétés calculées plus petites ou en sous-vues ; chaque morceau retournant some View simplifie le travail d’inférence.

« Cannot convert value of type ‘TupleView<…>’ to expected type ‘some View’. » Une fonction attendant une seule vue a reçu le résultat d’un body multi-instructions sans @ViewBuilder. Correctif : ajoutez @ViewBuilder au paramètre de closure qui accepte le contenu multi-instructions.

« Generic parameter ‘Content’ could not be inferred. » Un conteneur personnalisé prend @ViewBuilder content: () -> Content et le site d’appel a une closure vide. Correctif : les result builders ont besoin d’au moins une expression pour inférer Content ; les closures vides retombent sur EmptyView() si le site d’appel le fournit explicitement.

Les messages d’erreur sont peu accueillants parce que le substrat est invisible. Les lire avec le substrat visible transforme la plupart d’entre eux en « ah, le result builder ne peut pas transformer cela » ou « ah, j’ai besoin soit d’un branchement, soit d’AnyView ».

Quand sortir du substrat

Quelques schémas que le substrat ne gère pas proprement :

Types concrets variadiques. Une fonction qui retourne un type de View différent par branche et que vous ne pouvez pas envelopper dans un branchement de result builder a besoin d’AnyView. Acceptez le coût (diffing perdu, pas d’animation) et documentez le site d’appel.

Vues conditionnelles multi-plateformes. Le #if os(iOS) à la compilation fonctionne à l’intérieur d’un body @ViewBuilder mais limite le nombre de branchements du result builder ; les bodies conditionnels multi-OS atteignent parfois la limite « expression too complex ». Le correctif consiste à extraire les sous-vues spécifiques à chaque plateforme dans des fonctions séparées, retournant chacune some View.

Construction de vue impérative. Le framework s’attend à ce que les vues soient des expressions, pas des objets construits-puis-mutés. Le style UIKit « créer le label, définir le texte, ajouter au subview » ne se traduit pas ; l’équivalent SwiftUI est un Text("...") à type valeur retourné depuis un body. Les schémas qui exigent une construction impérative sont généralement le signe que le travail relève d’une passerelle UIViewRepresentable vers UIKit.

Ce que ce schéma signifie pour les apps livrées sur iOS 26+

Trois enseignements.

  1. SwiftUI est du Swift, pas de la magie. Les result builders, les types de retour opaques et les property wrappers figurent tous dans la référence du langage Swift. Lire le framework comme du code Swift, et non comme un DSL spécial, rend les parties surprenantes prévisibles.

  2. some View et AnyView résolvent des problèmes différents. Les types de retour opaques sont la valeur par défaut ; l’effacement de type est la trappe d’évacuation. Recourir à AnyView devrait être le cas rare ; recourir à some View plus le branchement par result builder devrait être le cas courant.

  3. Les result builders sont l’ensemble du DSL. Partout où une fonction ou un paramètre est @ViewBuilder, la syntaxe sans virgule, en forme d’instructions, est disponible. Écrire vos propres vues de conteneur avec la même ergonomie que VStack, c’est un attribut et un paramètre de closure.

Associez cet article à la série « code livré » du cluster : le SwiftUI multi-plateformes (Return tourne sur cinq plateformes avec un seul cœur SwiftUI partagé) ; la couche visuelle Liquid Glass ; la machine à états Live Activities sur iOS ; le contrat d’exécution watchOS sur l’Apple Watch. Le hub se trouve à la série Écosystème Apple. Pour un contexte plus large iOS-avec-agents-IA, consultez le guide de développement d’agents iOS.

FAQ

Qu’est-ce que le protocole View dans SwiftUI ?

Le protocole View a une seule exigence : var body: some View { get }. Chaque vue SwiftUI est un type valeur Swift conforme à View, avec une propriété calculée body qui retourne une autre vue (ou Never pour les vues primitives comme Text, Color, Image, EmptyView). Le body est annoté @ViewBuilder afin de pouvoir utiliser la syntaxe DSL sans virgule de SwiftUI.

Que signifie some View ?

some View est un type de retour opaque (Swift 5.1+). Le compilateur connaît le type concret ; l’appelant ne voit que le témoin de protocole. Les types opaques permettent aux bodies de vue de retourner des types complexes comme VStack<TupleView<(Text, Image, Spacer)>> sans les épeler, tout en préservant l’optimisation à la compilation. some View est un type spécifique unique, même si ce type n’est pas visible sur le site d’appel.

Quand devrais-je utiliser AnyView ?

N’utilisez AnyView que lorsque ni le branchement par result builder @ViewBuilder (if, switch, for) ni les génériques paramétrés ne résolvent le problème. Lorsque le type concret enveloppé change d’un rendu à l’autre, la hiérarchie de vues existante est détruite et une nouvelle est créée à sa place ; c’est à ce moment que les animations redémarrent et que l’état des vues se réinitialise. Réenvelopper le même type concret ne déclenche pas cette destruction, mais le diffing piloté par les types statiques que le framework préfère n’est plus disponible non plus. Si vous vous surprenez à recourir souvent à AnyView, le schéma à changer se trouve en amont : préférez les vues paramétrées ou poussez la condition à l’intérieur d’un body de result builder.

Qu’est-ce que @ViewBuilder et où puis-je l’utiliser ?

@ViewBuilder est un result builder (fonctionnalité du langage Swift). Il transforme une closure comportant plusieurs expressions en une seule valeur de retour en insérant des appels buildBlock, buildEither, buildOptional, etc. synthétisés par le compilateur. Le body de chaque vue SwiftUI est @ViewBuilder par défaut. Vous pouvez appliquer @ViewBuilder à toute fonction ou tout paramètre de closure pour donner aux appelants la même syntaxe DSL ; VStack, Card et Section utilisent le même schéma pour accepter plusieurs enfants.

Pourquoi le body de ma vue se rerend-il alors que je ne m’y attendais pas ?

SwiftUI réexécute body chaque fois qu’une propriété d’état lue par le body est mutée. Les property wrappers (@State, @Binding, @Observable, @Environment) suivent les lectures et déclenchent un nouveau rendu sur les écritures. Les rerenders inattendus se ramènent généralement à un changement d’état dans une vue parente, à un changement de valeur d’environnement, ou à la modification d’une propriété lue d’un objet @Observable. Le diffing du framework calcule alors le changement minimal de l’arbre.

Références


  1. Apple Developer, « View » et « Configuring views ». Le protocole View, le type associé Body et l’attribut @ViewBuilder sur body

  2. Swift Evolution, « SE-0289: Result builders ». La proposition de langage qui a formalisé les result builders (introduits sous _functionBuilder en 5.1, formalisés sous @resultBuilder en 5.4). Définit buildBlock, buildEither, buildOptional, buildArray, buildExpression, buildFinalResult et leurs voisins. 

  3. Apple Developer, « ViewBuilder » et « ForEach ». Le type de result builder que SwiftUI utilise pour les bodies de vue (buildBlock à génériques variadiques, buildEither, déballage d’optionnels). ViewBuilder n’expose pas buildArray, donc ForEach est la primitive d’itération pour répéter une vue sur une collection. 

  4. Swift Evolution, « SE-0244: Opaque result types ». Le mot-clé some pour les types de retour opaques, ajouté dans Swift 5.1. 

  5. Apple Developer, « AnyView ». Wrapper de vue à effacement de type, construction et compromis de diffing. 

  6. Apple Developer, « Group », « EmptyView » et « TupleView ». Types d’implémentation que les result builders synthétisent. 

  7. Apple Developer, « State and Data Flow ». La couche des property wrappers : @State, @Binding, @Observable, @Environment. Le système d’observation de SwiftUI et la macro @Observable d’iOS 17+. 

  8. Apple Developer, « Observation » et « Migrating from the Observable Object protocol to the Observable macro ». Le framework Observation de la bibliothèque standard, y compris withObservationTracking(_:onChange:), ainsi que le chemin de migration iOS 17 d’ObservableObject vers @Observable

Articles connexes

Le runtime watchOS est un contrat, pas une tâche en arrière-plan

watchOS n'a pas d'arrière-plan à la manière d'iOS. WKExtendedRuntimeSession est le contrat ; sans lui, l'app est suspend…

16 min de lecture

Trois surfaces : humaine, Apple Intelligence, agent

Toute capacité d'application iOS fait face à trois surfaces : humaine, Apple Intelligence, agent. Chacune a des obligati…

16 min de lecture

La couche de nettoyage est le véritable marché des agents IA

Charlie Labs a pivoté de la construction d'agents au nettoyage derrière eux. Le marché des agents IA passe de la générat…

15 min de lecture