Skip to content

Improve channel list and model conversion performance#4165

Open
nuno-vieira wants to merge 7 commits into
developfrom
perf/channel-list-performance-improvements
Open

Improve channel list and model conversion performance#4165
nuno-vieira wants to merge 7 commits into
developfrom
perf/channel-list-performance-improvements

Conversation

@nuno-vieira

@nuno-vieira nuno-vieira commented Jul 9, 2026

Copy link
Copy Markdown
Member

🔗 Issue Links

🎯 Goal

Reduce the CPU cost of loading and updating the channel list, especially for channels with many members and long message histories. The work targets the database-to-model conversion path and repeated main-thread work observed while profiling the channel list.

📝 Summary

  • Convert ChatChannel and ChatMessage to final, immutable reference types to avoid repeated value copies during conversions.
  • Prewarm a cache for channel read lookups to avoid repeated scans.
  • Skip building and discarding latest messages beyond the truncation window.
  • Avoid rebuilding entity models twice per background database change.
  • Skip decoding extra data when it is an empty JSON object.
  • Avoid a redundant queue hop when reading chatClientConfig.

🛠 Implementation

The main change is making ChatChannel/ChatMessage final classes, which removes a class of redundant copying in the DB observers and conversion helpers. The remaining commits each remove specific redundant work on the conversion / background-observer path (double model rebuilds, unnecessary message materialization past the truncation window, empty extra-data decoding, and a queue hop).

🎨 Showcase

N/A — no user-facing UI changes.

🧪 Manual Testing Notes

Load a channel list where channels have many members and long histories, and confirm channel list synchronize/scroll no longer hangs the main thread. Behavior should be unchanged.

☑️ Contributor Checklist

  • I have signed the Stream CLA (required)
  • This change should be manually QAed
  • Changelog is updated with client-facing changes
  • Changelog is updated with new localization keys
  • New code is covered by unit tests
  • Documentation has been updated in the docs-content repo

Summary by CodeRabbit

  • New Features
    • Persisted, stable channel read identifiers improve read lookup behavior and help prevent duplicate entries.
    • Snapshot refreshes now reuse existing cached items more effectively for channels, messages, members, users, polls, and user groups.
  • Bug Fixes
    • Improved channel membership updates for join/removal-related events.
    • Corrected latest-message selection so truncation and “oldest” limits are applied consistently.
    • Faster/safer decoding for empty custom JSON payloads.
  • Tests
    • Added coverage for composed read identifiers, cache reuse during refresh, and decoder edge cases.

Add a composite id (cid/userId) to ChannelReadDTO so reads can be
resolved through the prewarmed cache and a single id-based fetch
instead of a compound predicate query per read. Falls back to the
legacy lookup and backfills the id for existing entries, enabling a
lightweight Core Data migration.
Eliminates per-copy refcount overhead (30-47 bumps per ChatChannel copy,
similar for ChatMessage) by making both types reference types. Since they
were already immutable snapshots with value-based Equatable/Hashable, the
semantic change is minimal while the performance gain is significant for
hot paths like channel list diffing and background DTO-to-model mapping.

Key changes:
- ChatChannel: struct → final class, membership becomes let, changing()
  membership parameter uses double-optional to distinguish "keep existing"
  from "explicitly set to nil"
- ChatMessage: struct → final class, init(fromDTO:) converted to static
  factory method, DraftMessage convenience init delegates to designated init
- LivestreamChatHandler: membership mutations folded into changing() calls
…indow

channelMessageDTOs is sorted descending by createdAt, so once a message
falls outside the oldestMessageAt/truncatedAt window, every older message
after it does too. Filtering the DTOs with prefix(while:) before building
models, instead of filtering already-built ChatMessage models twice
afterward, avoids constructing messages that get discarded and removes
two redundant array copies.
BackgroundEntityDatabaseObserver never passed itemReuseKeyPaths to the
underlying converter, so every content-change notification decoded the
item once via the change aggregator and then again from scratch when
refreshing the cached snapshot. This doubled the CoreData decode cost
for CurrentChatUser (including its muted channels), single-channel,
member, message, poll, and user group observers on every update.
decodeRawJSON ran a full Decodable pass through JSONDecoder for every
entity's extra data, even though most users, members, messages, and
channels have none and are persisted with the literal {} bytes. Short
circuit that common case to avoid the decode overhead on every model
conversion.
chatClientConfig is written once when a context is created and never
mutated again, yet every read went through performAndWait even though
callers (DTO -> model conversion) are already running on the context's
queue. This getter is invoked once per message/member/channel created,
so the extra confinement check added up to measurable overhead when
decoding channels with many nested models.
@nuno-vieira nuno-vieira requested a review from a team as a code owner July 9, 2026 19:01
@coderabbitai

coderabbitai Bot commented Jul 9, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: bbc20289-1f27-420b-8fda-156f2fbb7982

📥 Commits

Reviewing files that changed from the base of the PR and between 9f5bc0a and b6015d4.

📒 Files selected for processing (2)
  • Sources/StreamChat/Models/Channel.swift
  • Sources/StreamChat/Models/ChatMessage.swift
🚧 Files skipped from review as they are similar to previous changes (1)
  • Sources/StreamChat/Models/Channel.swift

📝 Walkthrough

Walkthrough

This PR adds observer item reuse key paths, introduces composed identifiers for channel reads, converts channel and message models to classes with related membership updates, refactors message snapshot construction, and updates DTO, database, and JSON decoding paths with tests.

Changes

Observer Reuse and Controller Wiring

Layer / File(s) Summary
BackgroundEntityDatabaseObserver item reuse
Sources/StreamChat/Controllers/DatabaseObserver/BackgroundEntityDatabaseObserver.swift, Sources/StreamChat/Controllers/ChannelController/ChannelController.swift, Sources/StreamChat/Controllers/CurrentUserController/CurrentUserController.swift, Sources/StreamChat/Controllers/MemberController/MemberController.swift, Sources/StreamChat/Controllers/MessageController/MessageController.swift, Sources/StreamChat/Controllers/PollController/PollController.swift, Sources/StreamChat/Controllers/PollController/PollVoteListController.swift, Sources/StreamChat/Controllers/UserController/UserController.swift, Sources/StreamChat/Controllers/UserGroupController/UserGroupController.swift, Tests/StreamChatTests/BackgroundEntityDatabaseObserver_Tests.swift, StreamChat.xcodeproj/project.pbxproj
Adds itemReuseKeyPaths to the observer initializer and passes reuse key paths through controller builders, with tests covering reuse behavior and project entries for the new test file.
ChannelReadDTO composed identifier
Sources/StreamChat/APIClient/Endpoints/Payloads/IdentifiableModel.swift, Sources/StreamChat/APIClient/Endpoints/Payloads/IdentifiablePayload.swift, Sources/StreamChat/Database/DTOs/ChannelReadDTO.swift, Sources/StreamChat/Database/StreamChatModel.xcdatamodeld/..., Tests/StreamChatTests/APIClient/Endpoints/Payloads/IdentifiableModel_Tests.swift, Tests/StreamChatTests/APIClient/Endpoints/Payloads/IdentifiablePayload_Tests.swift, Tests/StreamChatTests/Database/DTOs/ChannelReadDTO_Tests.swift
Adds a composed ChannelReadDTO.id, id-based loading and legacy backfill, cache insertion for read identifiers, Core Data schema/index updates, and tests for identifier and persistence behavior.
Channel and message model updates
Sources/StreamChat/Models/Channel.swift, Sources/StreamChat/Controllers/ChannelController/LivestreamChatHandler.swift, Sources/StreamChat/Models/ChatMessage.swift, Sources/StreamChat/Models/DraftMessage.swift
Converts ChatChannel and ChatMessage to final classes, changes channel membership handling to use double-optional updates through changing(...), and updates DraftMessage conversion to a convenience initializer.
MessageDTO ChatMessage factory
Sources/StreamChat/Database/DTOs/MessageDTO.swift
Replaces ChatMessage(fromDTO:depth:) initializer usage with ChatMessage.create(fromDTO:depth:) across snapshot and reply mapping paths.
DTO and decode fast paths
Sources/StreamChat/Database/DTOs/ChannelDTO.swift, Sources/StreamChat/Database/DatabaseSession.swift, Sources/StreamChat/Utils/Codable+Extensions.swift, Tests/StreamChatTests/Utils/Codable+Extensions_Tests.swift
Changes latestMessages filtering, simplifies chatClientConfig access, and adds a fast path for decoding empty JSON objects with tests.

Estimated code review effort: 4 (Complex) | ~60 minutes

Possibly related PRs

Suggested reviewers: martinmitrevski, laevandus

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 35.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the performance-focused changes to channel list synchronization and model conversion.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch perf/channel-list-performance-improvements

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions

github-actions Bot commented Jul 9, 2026

Copy link
Copy Markdown
1 Warning
⚠️ Big PR
1 Message
📖 There seems to be app changes but CHANGELOG wasn't modified.
Please include an entry if the PR includes user-facing changes.
You can find it at CHANGELOG.md.

Generated by 🚫 Danger

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
Sources/StreamChat/Database/DTOs/MessageDTO.swift (1)

1813-1819: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Increment depth when materializing latestReplies
latestReplies calls ChatMessage.create(fromDTO:depth:) directly, so nested replies reuse the same depth while other relationship fields go through relationshipAsModel(depth:). That bypasses the recursion guard for reply chains; switch this to relationshipAsModel(depth:) or increment the depth here.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Sources/StreamChat/Database/DTOs/MessageDTO.swift` around lines 1813 - 1819,
The latestReplies materialization in ChatMessage.create(fromDTO:depth:) is
bypassing the usual depth increment, so nested replies keep reusing the same
recursion level. Update the latestReplies mapping to go through
relationshipAsModel(depth:) or otherwise pass depth + 1 when creating each
ChatMessage, matching the behavior used for other relationship fields and
ensuring the recursion guard works correctly.
🧹 Nitpick comments (3)
Sources/StreamChat/Database/DTOs/ChannelReadDTO.swift (1)

68-96: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Double fetch on cache-miss for brand-new reads.

For a genuinely new read (not in cache, no row with matching id, no legacy row), this now does two sequential Core Data fetches (load(id:) at Line 79 then load(cid:userId:) at Line 85) before inserting, versus one fetch previously. This is exactly the "many members / long history" first-sync scenario the PR targets, so the extra round-trip per new read partially offsets the intended CPU win.

Consider merging the id-lookup and legacy-lookup into a single fetch with a combined predicate, backfilling id only when it's found nil:

♻️ Proposed refactor to merge the two fallback fetches
-        if let existing = load(id: readId, context: context) {
-            return existing
-        }
-
-        // Fallback for reads persisted before the `id` attribute existed: look them up by the
-        // compound predicate and backfill the identifier so subsequent lookups can use the id.
-        if let legacy = load(cid: cid, userId: userId, context: context) {
-            legacy.id = readId
-            return legacy
-        }
+        // Single fetch covering both the fast id-based path and the legacy compound-predicate
+        // fallback, instead of two separate round-trips.
+        let combinedRequest = NSFetchRequest<ChannelReadDTO>(entityName: ChannelReadDTO.entityName)
+        combinedRequest.predicate = NSPredicate(
+            format: "id == %@ OR (channel.cid == %@ && user.id == %@)",
+            readId, cid.rawValue, userId
+        )
+        if let existing = load(by: combinedRequest, context: context).first {
+            if existing.id == nil {
+                existing.id = readId
+            }
+            return existing
+        }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Sources/StreamChat/Database/DTOs/ChannelReadDTO.swift` around lines 68 - 96,
The loadOrCreate flow in ChannelReadDTO performs two Core Data fetches on a
cache miss for a brand-new read, first via load(id:context:) and then via
load(cid:userId:context:), which adds avoidable overhead. Refactor the fallback
path to use a single lookup that can match either the new id or the legacy
cid/userId identity, then backfill legacy records by setting id only when
needed. Keep the existing cache check, existing load(id:) fast path, and the
insertNewObject branch intact while consolidating the legacy fallback logic
inside loadOrCreate.
Sources/StreamChat/Models/ChatMessage.swift (1)

12-12: 🩺 Stability & Availability | 🔵 Trivial | 💤 Low value

Consider documenting why @unchecked Sendable is safe here.

All stored properties are let, but _quotedMessage: BoxedAny? type-erases its wrapped value, so the compiler can't verify Sendability automatically. A short comment noting the invariant (fully immutable, and boxed values are only read via getValue()) would help future maintainers scope/justify this exception.

As per coding guidelines, "**/*.swift: ... Use Sendable conformances where needed for cross-isolation transfers and avoid data races."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Sources/StreamChat/Models/ChatMessage.swift` at line 12, Add a brief comment
near the ChatMessage declaration explaining why the `@unchecked` Sendable
conformance is safe: the type is fully immutable because all stored properties
are let, and the type-erased _quotedMessage: BoxedAny? is only ever read through
getValue(), so the compiler cannot prove Sendability but the runtime invariant
keeps cross-isolation use safe.

Source: Coding guidelines

Sources/StreamChat/Database/StreamChatModel.xcdatamodeld/StreamChatModel.xcdatamodel/contents (1)

152-152: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win

Consider adding a uniqueness constraint on the new ChannelReadDTO.id.

The new composed id (cid + userId) is added with a fetch index but no uniquenessConstraints, unlike other entities that use an id-style identifier (e.g. MessageDTO, MemberDTO, UserDTO all constrain on id). Since loadOrCreate now relies on id-based lookup as the primary path, a DB-level constraint would provide defense-in-depth against duplicate rows for the same channel/user pair, complementing the existing cache/backfill logic in ChannelReadDTO.loadOrCreate.

♻️ Proposed addition
     <entity name="ChannelReadDTO" representedClassName="ChannelReadDTO" syncable="YES">
         <attribute name="id" optional="YES" attributeType="String"/>
         ...
         <fetchIndex name="id">
             <fetchIndexElement property="id" type="Binary" order="ascending"/>
         </fetchIndex>
+        <uniquenessConstraints>
+            <uniquenessConstraint>
+                <constraint value="id"/>
+            </uniquenessConstraint>
+        </uniquenessConstraints>
     </entity>

Also applies to: 161-163

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@Sources/StreamChat/Database/StreamChatModel.xcdatamodeld/StreamChatModel.xcdatamodel/contents`
at line 152, Add a uniqueness constraint for ChannelReadDTO.id in the Core Data
model, since loadOrCreate now depends on the composed cid+userId identifier as
the primary lookup path. Update the StreamChatModel entity definition for
ChannelReadDTO so its id field is constrained like MessageDTO, MemberDTO, and
UserDTO, and ensure the same constraint is applied consistently where the model
is defined. Keep the existing fetch index, but make the id constraint the
DB-level guard against duplicate channel/user rows.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@Sources/StreamChat/Database/DTOs/MessageDTO.swift`:
- Around line 1813-1819: The latestReplies materialization in
ChatMessage.create(fromDTO:depth:) is bypassing the usual depth increment, so
nested replies keep reusing the same recursion level. Update the latestReplies
mapping to go through relationshipAsModel(depth:) or otherwise pass depth + 1
when creating each ChatMessage, matching the behavior used for other
relationship fields and ensuring the recursion guard works correctly.

---

Nitpick comments:
In `@Sources/StreamChat/Database/DTOs/ChannelReadDTO.swift`:
- Around line 68-96: The loadOrCreate flow in ChannelReadDTO performs two Core
Data fetches on a cache miss for a brand-new read, first via load(id:context:)
and then via load(cid:userId:context:), which adds avoidable overhead. Refactor
the fallback path to use a single lookup that can match either the new id or the
legacy cid/userId identity, then backfill legacy records by setting id only when
needed. Keep the existing cache check, existing load(id:) fast path, and the
insertNewObject branch intact while consolidating the legacy fallback logic
inside loadOrCreate.

In
`@Sources/StreamChat/Database/StreamChatModel.xcdatamodeld/StreamChatModel.xcdatamodel/contents`:
- Line 152: Add a uniqueness constraint for ChannelReadDTO.id in the Core Data
model, since loadOrCreate now depends on the composed cid+userId identifier as
the primary lookup path. Update the StreamChatModel entity definition for
ChannelReadDTO so its id field is constrained like MessageDTO, MemberDTO, and
UserDTO, and ensure the same constraint is applied consistently where the model
is defined. Keep the existing fetch index, but make the id constraint the
DB-level guard against duplicate channel/user rows.

In `@Sources/StreamChat/Models/ChatMessage.swift`:
- Line 12: Add a brief comment near the ChatMessage declaration explaining why
the `@unchecked` Sendable conformance is safe: the type is fully immutable because
all stored properties are let, and the type-erased _quotedMessage: BoxedAny? is
only ever read through getValue(), so the compiler cannot prove Sendability but
the runtime invariant keeps cross-isolation use safe.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 56434f31-d2da-4479-a4e7-a99f493d38eb

📥 Commits

Reviewing files that changed from the base of the PR and between 712984a and 9f5bc0a.

📒 Files selected for processing (27)
  • Sources/StreamChat/APIClient/Endpoints/Payloads/IdentifiableModel.swift
  • Sources/StreamChat/APIClient/Endpoints/Payloads/IdentifiablePayload.swift
  • Sources/StreamChat/Controllers/ChannelController/ChannelController.swift
  • Sources/StreamChat/Controllers/ChannelController/LivestreamChatHandler.swift
  • Sources/StreamChat/Controllers/CurrentUserController/CurrentUserController.swift
  • Sources/StreamChat/Controllers/DatabaseObserver/BackgroundEntityDatabaseObserver.swift
  • Sources/StreamChat/Controllers/MemberController/MemberController.swift
  • Sources/StreamChat/Controllers/MessageController/MessageController.swift
  • Sources/StreamChat/Controllers/PollController/PollController.swift
  • Sources/StreamChat/Controllers/PollController/PollVoteListController.swift
  • Sources/StreamChat/Controllers/UserController/UserController.swift
  • Sources/StreamChat/Controllers/UserGroupController/UserGroupController.swift
  • Sources/StreamChat/Database/DTOs/ChannelDTO.swift
  • Sources/StreamChat/Database/DTOs/ChannelReadDTO.swift
  • Sources/StreamChat/Database/DTOs/MessageDTO.swift
  • Sources/StreamChat/Database/DatabaseSession.swift
  • Sources/StreamChat/Database/StreamChatModel.xcdatamodeld/StreamChatModel.xcdatamodel/contents
  • Sources/StreamChat/Models/Channel.swift
  • Sources/StreamChat/Models/ChatMessage.swift
  • Sources/StreamChat/Models/DraftMessage.swift
  • Sources/StreamChat/Utils/Codable+Extensions.swift
  • StreamChat.xcodeproj/project.pbxproj
  • Tests/StreamChatTests/APIClient/Endpoints/Payloads/IdentifiableModel_Tests.swift
  • Tests/StreamChatTests/APIClient/Endpoints/Payloads/IdentifiablePayload_Tests.swift
  • Tests/StreamChatTests/BackgroundEntityDatabaseObserver_Tests.swift
  • Tests/StreamChatTests/Database/DTOs/ChannelReadDTO_Tests.swift
  • Tests/StreamChatTests/Utils/Codable+Extensions_Tests.swift

Hoist the coalesced values in ChatChannel.changing and
ChatMessage.changing into explicit locals so the type-checker does
not have to resolve all of them inside a single large initializer.
@github-actions

github-actions Bot commented Jul 9, 2026

Copy link
Copy Markdown

Public Interface

+ public final class ChatMessage: Identifiable, @unchecked Sendable  
+ 
+   public let id: MessageId
+   public let cid: ChannelId?
+   public let text: String
+   public let type: MessageType
+   public let command: String?
+   public let createdAt: Date
+   public let locallyCreatedAt: Date?
+   public let updatedAt: Date
+   public let deletedAt: Date?
+   public let textUpdatedAt: Date?
+   public let arguments: String?
+   public let parentMessageId: MessageId?
+   public let showReplyInChannel: Bool
+   public let replyCount: Int
+   public let extraData: [String: RawJSON]
+   public var quotedMessage: ChatMessage?
+   public let draftReply: DraftMessage?
+   public let reminder: MessageReminderInfo?
+   public let isBounced: Bool
+   public let isSilent: Bool
+   public let isShadowed: Bool
+   public let deletedForMe: Bool
+   public let reactionScores: [MessageReactionType: Int]
+   public let reactionCounts: [MessageReactionType: Int]
+   public let reactionGroups: [MessageReactionType: ChatMessageReactionGroup]
+   public let author: ChatUser
+   public let mentionedUsers: Set<ChatUser>
+   public let mentionedHere: Bool
+   public let mentionedChannel: Bool
+   public let mentionedGroups: Set<UserGroupMention>
+   public let mentionedRoles: [String]
+   public let threadParticipants: [ChatUser]
+   public var threadParticipantsCount: Int
+   public var attachmentCounts: [AttachmentType: Int]
+   public let latestReplies: [ChatMessage]
+   public let localState: LocalMessageState?
+   public let isFlaggedByCurrentUser: Bool
+   public let latestReactions: Set<ChatMessageReaction>
+   public let currentUserReactions: Set<ChatMessageReaction>
+   public var currentUserReactionsCount: Int
+   public let isSentByCurrentUser: Bool
+   public let pinDetails: MessagePinDetails?
+   public let translations: [TranslationLanguage: String]?
+   public let originalLanguage: TranslationLanguage?
+   public let moderationDetails: MessageModerationDetails?
+   public let readBy: Set<ChatUser>
+   public var readByCount: Int
+   public let poll: Poll?
+   public let sharedLocation: SharedLocation?
+   public let channelRole: MemberRole?
+   
+ 
+   public func translatedText(for language: TranslationLanguage)-> String?
+   public func changing(text: String? = nil,type: MessageType? = nil,state: LocalMessageState? = nil,command: String? = nil,arguments: String? = nil,attachments: [AnyChatMessageAttachment]? = nil,translations: [TranslationLanguage: String]? = nil,originalLanguage: TranslationLanguage? = nil,moderationDetails: MessageModerationDetails? = nil,readBy: Set<ChatUser>? = nil,deletedAt: Date? = nil,extraData: [String: RawJSON]? = nil)-> ChatMessage
+   public func replacing(text: String?,extraData: [String: RawJSON]?,attachments: [AnyChatMessageAttachment]?)-> ChatMessage
+   public func replacing(text: String?,type: MessageType,state: LocalMessageState?,command: String?,arguments: String?,attachments: [AnyChatMessageAttachment]?,translations: [TranslationLanguage: String]?,originalLanguage: TranslationLanguage?,moderationDetails: MessageModerationDetails?,extraData: [String: RawJSON]?)-> ChatMessage

+ public final class ChatChannel: @unchecked Sendable  
+ 
+   public let cid: ChannelId
+   public let name: String?
+   public let imageURL: URL?
+   public let lastMessageAt: Date?
+   public let createdAt: Date
+   public let updatedAt: Date
+   public let deletedAt: Date?
+   public let truncatedAt: Date?
+   public let isHidden: Bool
+   public let createdBy: ChatUser?
+   public let config: ChannelConfig
+   public let filterTags: Set<String>
+   public let ownCapabilities: Set<ChannelCapability>
+   public let isFrozen: Bool
+   public let isDisabled: Bool
+   public let isBlocked: Bool
+   public let memberCount: Int
+   public let messageCount: Int?
+   public let lastActiveMembers: [ChatChannelMember]
+   public let currentlyTypingUsers: Set<ChatUser>
+   public let membership: ChatChannelMember?
+   public var isArchived: Bool
+   public var isPinned: Bool
+   public let lastActiveWatchers: [ChatUser]
+   public let watcherCount: Int
+   public let team: TeamId?
+   public let unreadCount: ChannelUnreadCount
+   public let latestMessages: [ChatMessage]
+   public let lastMessageFromCurrentUser: ChatMessage?
+   public let pinnedMessages: [ChatMessage]
+   public let pendingMessages: [ChatMessage]
+   public let reads: [ChatChannelRead]
+   public let muteDetails: MuteDetails?
+   public var isMuted: Bool
+   public let cooldownDuration: Int
+   public let extraData: [String: RawJSON]
+   public let draftMessage: DraftMessage?
+   public let activeLiveLocations: [SharedLocation]
+   public let pushPreference: PushPreference?
+   
+ 
+   public func replacing(name: String?,imageURL: URL?,extraData: [String: RawJSON]?)-> ChatChannel
+   public func changing(name: String? = nil,imageURL: URL? = nil,lastMessageAt: Date? = nil,createdAt: Date? = nil,deletedAt: Date? = nil,updatedAt: Date? = nil,truncatedAt: Date? = nil,isHidden: Bool? = nil,createdBy: ChatUser? = nil,config: ChannelConfig? = nil,filterTags: Set<String>? = nil,ownCapabilities: Set<ChannelCapability>? = nil,isFrozen: Bool? = nil,isDisabled: Bool? = nil,isBlocked: Bool? = nil,reads: [ChatChannelRead]? = nil,members: [ChatChannelMember]? = nil,membership: ChatChannelMember?? = nil,memberCount: Int? = nil,watchers: [ChatUser]? = nil,watcherCount: Int? = nil,team: TeamId? = nil,cooldownDuration: Int? = nil,pinnedMessages: [ChatMessage]? = nil,pushPreference: PushPreference? = nil,currentlyTypingUsers: Set<ChatUser>? = nil,extraData: [String: RawJSON]? = nil)-> ChatChannel

- public struct ChatMessage: Identifiable, Sendable  
- 
-   public let id: MessageId
-   public let cid: ChannelId?
-   public let text: String
-   public let type: MessageType
-   public let command: String?
-   public let createdAt: Date
-   public let locallyCreatedAt: Date?
-   public let updatedAt: Date
-   public let deletedAt: Date?
-   public let textUpdatedAt: Date?
-   public let arguments: String?
-   public let parentMessageId: MessageId?
-   public let showReplyInChannel: Bool
-   public let replyCount: Int
-   public let extraData: [String: RawJSON]
-   public var quotedMessage: ChatMessage?
-   public let draftReply: DraftMessage?
-   public let reminder: MessageReminderInfo?
-   public let isBounced: Bool
-   public let isSilent: Bool
-   public let isShadowed: Bool
-   public let deletedForMe: Bool
-   public let reactionScores: [MessageReactionType: Int]
-   public let reactionCounts: [MessageReactionType: Int]
-   public let reactionGroups: [MessageReactionType: ChatMessageReactionGroup]
-   public let author: ChatUser
-   public let mentionedUsers: Set<ChatUser>
-   public let mentionedHere: Bool
-   public let mentionedChannel: Bool
-   public let mentionedGroups: Set<UserGroupMention>
-   public let mentionedRoles: [String]
-   public let threadParticipants: [ChatUser]
-   public var threadParticipantsCount: Int
-   public var attachmentCounts: [AttachmentType: Int]
-   public let latestReplies: [ChatMessage]
-   public let localState: LocalMessageState?
-   public let isFlaggedByCurrentUser: Bool
-   public let latestReactions: Set<ChatMessageReaction>
-   public let currentUserReactions: Set<ChatMessageReaction>
-   public var currentUserReactionsCount: Int
-   public let isSentByCurrentUser: Bool
-   public let pinDetails: MessagePinDetails?
-   public let translations: [TranslationLanguage: String]?
-   public let originalLanguage: TranslationLanguage?
-   public let moderationDetails: MessageModerationDetails?
-   public let readBy: Set<ChatUser>
-   public var readByCount: Int
-   public let poll: Poll?
-   public let sharedLocation: SharedLocation?
-   public let channelRole: MemberRole?
-   
- 
-   public func translatedText(for language: TranslationLanguage)-> String?
-   public func changing(text: String? = nil,type: MessageType? = nil,state: LocalMessageState? = nil,command: String? = nil,arguments: String? = nil,attachments: [AnyChatMessageAttachment]? = nil,translations: [TranslationLanguage: String]? = nil,originalLanguage: TranslationLanguage? = nil,moderationDetails: MessageModerationDetails? = nil,readBy: Set<ChatUser>? = nil,deletedAt: Date? = nil,extraData: [String: RawJSON]? = nil)-> ChatMessage
-   public func replacing(text: String?,extraData: [String: RawJSON]?,attachments: [AnyChatMessageAttachment]?)-> ChatMessage
-   public func replacing(text: String?,type: MessageType,state: LocalMessageState?,command: String?,arguments: String?,attachments: [AnyChatMessageAttachment]?,translations: [TranslationLanguage: String]?,originalLanguage: TranslationLanguage?,moderationDetails: MessageModerationDetails?,extraData: [String: RawJSON]?)-> ChatMessage

- public struct ChatChannel: Sendable  
+ public struct ChatChannel: Equatable, Sendable  
-   public let cid: ChannelId
+   public var lastActiveWatchersLimit
-   public let name: String?
+   public var lastActiveMembersLimit
-   public let imageURL: URL?
+   public var latestMessagesLimit
-   public let lastMessageAt: Date?
-   public let createdAt: Date
-   public let updatedAt: Date
-   public let deletedAt: Date?
-   public let truncatedAt: Date?
-   public let isHidden: Bool
-   public let createdBy: ChatUser?
-   public let config: ChannelConfig
-   public let filterTags: Set<String>
-   public let ownCapabilities: Set<ChannelCapability>
-   public let isFrozen: Bool
-   public let isDisabled: Bool
-   public let isBlocked: Bool
-   public let memberCount: Int
-   public let messageCount: Int?
-   public let lastActiveMembers: [ChatChannelMember]
-   public let currentlyTypingUsers: Set<ChatUser>
-   public internal var membership: ChatChannelMember?
-   public var isArchived: Bool
-   public var isPinned: Bool
-   public let lastActiveWatchers: [ChatUser]
-   public let watcherCount: Int
-   public let team: TeamId?
-   public let unreadCount: ChannelUnreadCount
-   public let latestMessages: [ChatMessage]
-   public let lastMessageFromCurrentUser: ChatMessage?
-   public let pinnedMessages: [ChatMessage]
-   public let pendingMessages: [ChatMessage]
-   public let reads: [ChatChannelRead]
-   public let muteDetails: MuteDetails?
-   public var isMuted: Bool
-   public let cooldownDuration: Int
-   public let extraData: [String: RawJSON]
-   public let draftMessage: DraftMessage?
-   public let activeLiveLocations: [SharedLocation]
-   public let pushPreference: PushPreference?
-   
- 
-   public func replacing(name: String?,imageURL: URL?,extraData: [String: RawJSON]?)-> ChatChannel
-   public func changing(name: String? = nil,imageURL: URL? = nil,lastMessageAt: Date? = nil,createdAt: Date? = nil,deletedAt: Date? = nil,updatedAt: Date? = nil,truncatedAt: Date? = nil,isHidden: Bool? = nil,createdBy: ChatUser? = nil,config: ChannelConfig? = nil,filterTags: Set<String>? = nil,ownCapabilities: Set<ChannelCapability>? = nil,isFrozen: Bool? = nil,isDisabled: Bool? = nil,isBlocked: Bool? = nil,reads: [ChatChannelRead]? = nil,members: [ChatChannelMember]? = nil,membership: ChatChannelMember? = nil,memberCount: Int? = nil,watchers: [ChatUser]? = nil,watcherCount: Int? = nil,team: TeamId? = nil,cooldownDuration: Int? = nil,pinnedMessages: [ChatMessage]? = nil,pushPreference: PushPreference? = nil,currentlyTypingUsers: Set<ChatUser>? = nil,extraData: [String: RawJSON]? = nil)-> ChatChannel

@Stream-SDK-Bot

Copy link
Copy Markdown
Collaborator

SDK Size

title develop branch diff status
StreamChat 7.53 MB 7.13 MB -412 KB 🚀
StreamChatUI 4.27 MB 3.62 MB -661 KB 🚀
StreamChatCommonUI 0.84 MB 0.84 MB 0 KB 🟢

@Stream-SDK-Bot

Copy link
Copy Markdown
Collaborator

StreamChat XCSize

Object Diff (bytes)
Thread.o -64596
MessageReminder.o -64520
MentionSuggestionsProvider.o -45588
Channel.o -40236
LivestreamChatHandler.o -35640
Show 96 more objects
Object Diff (bytes)
SendMessageInterceptor.o -22504
ChatMessage.o -21531
ChannelController.o -12492
MessageController.o -9008
MessageUpdater.o -8756
MessageDTO.o -8111
ChannelListLinker.o -7568
CurrentUserController.o +5210
MessageRepository.o -4422
ChatState+Observer.o -4144
ChannelUpdater.o -3916
EnhancedMentionSuggestionsProvider.o -3764
Codable+Extensions.o +3486
ChatRemoteNotificationHandler.o -3328
GroupedChannelListLinker.o -3052
MessageSender.o -2550
ChatState.o -2546
ReadStateHandler.o -2500
ChannelListUpdater.o -2496
Chat.o -2452
LivestreamChannelController.o -2404
ManualEventHandler.o -2248
MessageEvents.o -1912
Sequence+CompactMapLoggingError.o -1880
UserGroupController.o +1870
MessageEditor.o -1620
MessageState+Observer.o -1556
ChannelEvents.o -1432
NotificationEvents.o -1332
LivestreamChatState.o -1212
PollController.o +1200
DraftMessagesRepository.o -944
MessagePayload+asModel.o -848
UnreadMessageLookup.o -844
ChannelListPayload.o +752
DataStore.o -700
DatabaseSession.o -680
ChannelPayload+asModel.o -596
IdentifiablePayload.o +564
ReactionEvents.o -528
AppSettings+Extensions.o -484
MessageState.o -480
LivestreamChat.o -456
SyncOperations.o -436
LivestreamChannelController+Combine.o -436
MemberListController.o -432
ChannelReadDTO.o +417
ActiveLiveLocationsEndTimeTracker.o -416
ChannelRepository.o -416
ChannelDTO.o -401
ChatClient+ChannelController.o -356
ThreadEvents.o -340
MulticastDelegate.o +328
SharedLocationDTO.o +320
PollVoteListController.o +312
MemberModelDTO.o -292
ListChange.o -288
DefaultMentionSuggestionsProvider.o +276
ChatClient.o +272
ThreadDTO.o -268
StreamCore.o -240
AnyAttachmentPayload.o -222
AttachmentDownloader.o -212
CurrentUserDTO.o -208
MessageSearchController.o +184
MessageDeliveryCriteriaValidator.o -180
User.o -175
ChatMessageAudioAttachment.o -172
UploadConfig.o +172
MessageSearchQuery.o -168
ChannelDeliveredMiddleware.o -148
ChannelListState+Observer.o +148
UserListState+Observer.o -144
UserController.o +144
IdentifiableModel.o +136
StreamModelsTransformer.o -132
MessageTranslationsPayload.o -124
DefaultEndpoints.o -108
DraftEvents.o -100
PushPreferenceDTO.o -100
MentionSuggestion.o +92
MarkdownParser.o -88
ThreadParticipant.o +84
ChannelList.o -76
ChatChannelWatcherListController.o -76
UserPayload+asModel.o +72
DraftMessage.o +72
ChannelMemberListUpdater.o -68
UserEvents.o -64
MessageReminderDTO.o -60
AudioAnalysisEngine.o +56
ConnectedUser.o +52
PollDTO.o -50
MemberController+Combine.o -48
ReactionListUpdater.o -44
ChannelListSortingKey.o +44

@Stream-SDK-Bot

Copy link
Copy Markdown
Collaborator

StreamChatUI XCSize

Object Diff (bytes)
ChatChannelListItemView.o -121408
ComposerVC.o -97996
InputChatMessageView.o -91726
QuotedChatMessageView.o -81166
ChatMessageHeaderDecoratorView.o -69482
Show 53 more objects
Object Diff (bytes)
ChatMessageDeliveryStatusView.o -68718
ChatThreadListItemView.o -68156
GalleryVC.o -25508
ChatMessageListVC.o -17926
ChatMessageContentView.o -6352
ChatChannelVC.o -6296
ChatMessageActionsVC.o -2800
ChatChannelAvatarView.o -1868
ChatThreadVC.o -1664
ChatMessagePopupVC.o -1112
ChatChannelListVC.o +960
PollAttachmentViewInjector.o -832
ChatMessageListRouter.o -824
ChatThreadRepliesCountDecorationView.o -730
ChatMessageReactionsPickerVC.o -702
ChatUnreadMessagesCountDecorationView.o -686
MixedAttachmentViewInjector.o -676
Algorithm.o +672
ChatChannelHeaderView.o -658
ChatMessageLayoutOptionsResolver.o -596
ChatMessageSearchVC.o -580
FileAttachmentViewInjector.o -580
MessageActionsTransitionController.o -488
GalleryAttachmentViewInjector.o -444
ChatThreadHeaderView.o -404
PollCreationFeatureCell.o +352
ChatChannelSearchVC.o -352
AudioQueuePlayerNextItemProvider.o -328
VoiceRecordingAttachmentViewInjector.o -260
GiphyAttachmentViewInjector.o -212
LinkAttachmentViewInjector.o -208
SwipeToReplyGestureHandler.o -192
AttachmentViewInjector.o -164
ImagePrefetcher.o +160
UnsupportedAttachmentViewInjector.o -160
ChatMessageListView.o -96
Components.o -96
ChatMessageListView+DiffKit.o +84
TaskFetchOriginalData.o -80
ChatMessageReactions+Types.o -80
ChatMessage+Extensions.o -80
CommandLabelView.o -80
StatefulScrollViewPaginationHandler.o +76
ChatMessageReactionsView.o -68
ContainerStackView.o -68
MediaLoader+UIKit.o -68
ChatMessageReactionAuthorsVC.o -60
ChatMessageVoiceRecordingAttachmentListView+ItemView.o -60
StreamChat.tbd +56
PollAllOptionsListItemCell.o +48
ChatThreadListVC.o +48
PollFeature.o +44
ImagePipeline.o -44

@sonarqubecloud

sonarqubecloud Bot commented Jul 9, 2026

Copy link
Copy Markdown

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants