From 7b0bae821dd2ddf6270b109db9c73c3833500232 Mon Sep 17 00:00:00 2001 From: makinosp Date: Sat, 12 Oct 2024 22:06:58 +0900 Subject: [PATCH 01/10] fix: issue that memberwise initialization in UserModel --- .../VRCKit/Models/User/PresenceModel.swift | 21 ++++++++++++++++ .../Models/User/UserModel+Decodable.swift | 2 +- Sources/VRCKit/Models/User/UserModel.swift | 25 +++++-------------- 3 files changed, 28 insertions(+), 20 deletions(-) create mode 100644 Sources/VRCKit/Models/User/PresenceModel.swift diff --git a/Sources/VRCKit/Models/User/PresenceModel.swift b/Sources/VRCKit/Models/User/PresenceModel.swift new file mode 100644 index 0000000..a055f6b --- /dev/null +++ b/Sources/VRCKit/Models/User/PresenceModel.swift @@ -0,0 +1,21 @@ +// +// PresenceModel.swift +// VRCKit +// +// Created by makinosp on 2024/10/12. +// + +import MemberwiseInit + +@MemberwiseInit(.public) +public struct Presence: Codable, Hashable, Sendable { + public let groups: [String] + public let id: String + public let instance: String + public let instanceType: String + public let platform: UserPlatform + public let status: UserStatus + public let travelingToInstance: String + public let travelingToWorld: String + public let world: String +} diff --git a/Sources/VRCKit/Models/User/UserModel+Decodable.swift b/Sources/VRCKit/Models/User/UserModel+Decodable.swift index 627dee2..f933dac 100644 --- a/Sources/VRCKit/Models/User/UserModel+Decodable.swift +++ b/Sources/VRCKit/Models/User/UserModel+Decodable.swift @@ -30,7 +30,7 @@ extension User: Decodable { lastPlatform = try container.decode(String.self, forKey: .lastPlatform) offlineFriends = try container.decode([String].self, forKey: .offlineFriends) onlineFriends = try container.decode([String].self, forKey: .onlineFriends) - pastDisplayNames = try container.decode([User.DisplayName].self, forKey: .pastDisplayNames) + pastDisplayNames = try container.decode([DisplayName].self, forKey: .pastDisplayNames) profilePicOverride = try? container.decodeIfPresent(URL.self, forKey: .profilePicOverride) state = try container.decode(User.State.self, forKey: .state) status = try container.decode(UserStatus.self, forKey: .status) diff --git a/Sources/VRCKit/Models/User/UserModel.swift b/Sources/VRCKit/Models/User/UserModel.swift index 8fb2837..64a10a1 100644 --- a/Sources/VRCKit/Models/User/UserModel.swift +++ b/Sources/VRCKit/Models/User/UserModel.swift @@ -41,12 +41,6 @@ public struct User: Sendable, ProfileDetailRepresentable { public let userLanguageCode: String? public let presence: Presence - @MemberwiseInit(.public) - public struct DisplayName: Codable, Sendable, Hashable { - public let displayName: String - public let updatedAt: Date - } - public enum State: String, Codable, Sendable { /// User is online in VRChat case online @@ -55,19 +49,6 @@ public struct User: Sendable, ProfileDetailRepresentable { /// User is offline case offline } - - @MemberwiseInit(.public) - public struct Presence: Codable, Hashable, Sendable { - public let groups: [String] - public let id: String - public let instance: String - public let instanceType: String - public let platform: UserPlatform - public let status: UserStatus - public let travelingToInstance: String - public let travelingToWorld: String - public let world: String - } } public extension User { @@ -79,3 +60,9 @@ public extension User { URL(string: [Const.homeBaseUrl, "user", id].joined(separator: "/")) } } + +@MemberwiseInit(.public) +public struct DisplayName: Codable, Sendable, Hashable { + public let displayName: String + public let updatedAt: Date +} From cf8b855f14f3111389ad069f83714e98a9f92057 Mon Sep 17 00:00:00 2001 From: makinosp Date: Sat, 12 Oct 2024 23:45:22 +0900 Subject: [PATCH 02/10] feat: apply memberwise initializer to FriendsLocation --- Sources/VRCKit/Models/Friend/FriendModel.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/Sources/VRCKit/Models/Friend/FriendModel.swift b/Sources/VRCKit/Models/Friend/FriendModel.swift index d12ab3e..c4d4a29 100644 --- a/Sources/VRCKit/Models/Friend/FriendModel.swift +++ b/Sources/VRCKit/Models/Friend/FriendModel.swift @@ -29,6 +29,7 @@ public struct Friend: Sendable, ProfileElementRepresentable, LocationRepresentab public let friendKey: String } +@MemberwiseInit(.public) public struct FriendsLocation: Sendable, LocationRepresentable { public let location: Location public let friends: [Friend] From ad2956950919f4f3305ebeebe731ffd95e23d234 Mon Sep 17 00:00:00 2001 From: makinosp Date: Tue, 15 Oct 2024 21:05:58 +0900 Subject: [PATCH 03/10] feat: add favoriteId property to FavoriteWorldModel --- .../VRCKit/Models/World/FavoriteWorldModel+Initializer.swift | 3 ++- Sources/VRCKit/Models/World/FavoriteWorldModel.swift | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/Sources/VRCKit/Models/World/FavoriteWorldModel+Initializer.swift b/Sources/VRCKit/Models/World/FavoriteWorldModel+Initializer.swift index 30ed9ae..199c9b8 100644 --- a/Sources/VRCKit/Models/World/FavoriteWorldModel+Initializer.swift +++ b/Sources/VRCKit/Models/World/FavoriteWorldModel+Initializer.swift @@ -7,7 +7,7 @@ public extension FavoriteWorld { /// Initialize from `World` mode. - init(world: World, favoriteGroup: String) { + init(world: World, favoriteId: String, favoriteGroup: String) { self.init( id: world.id, name: world.name, @@ -32,6 +32,7 @@ public extension FavoriteWorld { popularity: world.popularity, heat: world.heat, favoriteGroup: favoriteGroup, + favoriteId: favoriteId, version: world.version, unityPackages: world.unityPackages ) diff --git a/Sources/VRCKit/Models/World/FavoriteWorldModel.swift b/Sources/VRCKit/Models/World/FavoriteWorldModel.swift index b6caf79..203dc23 100644 --- a/Sources/VRCKit/Models/World/FavoriteWorldModel.swift +++ b/Sources/VRCKit/Models/World/FavoriteWorldModel.swift @@ -33,6 +33,7 @@ public struct FavoriteWorld: Codable, Sendable, Identifiable, Hashable { public let popularity: Int public let heat: Int public let favoriteGroup: String + public let favoriteId: String public let version: Int? public let unityPackages: [UnityPackage] } From fd6c91b3619b88a4f5c1877075b2c3944e0b7c00 Mon Sep 17 00:00:00 2001 From: makinosp Date: Wed, 16 Oct 2024 22:38:02 +0900 Subject: [PATCH 04/10] feat: fetching all favorite worlds in WorldService --- .../Protocols/WorldServiceProtocol.swift | 2 +- Sources/VRCKit/Services/WorldService.swift | 20 +++++++++++++++++-- 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/Sources/VRCKit/Protocols/WorldServiceProtocol.swift b/Sources/VRCKit/Protocols/WorldServiceProtocol.swift index 7d5546d..3b7674c 100644 --- a/Sources/VRCKit/Protocols/WorldServiceProtocol.swift +++ b/Sources/VRCKit/Protocols/WorldServiceProtocol.swift @@ -7,5 +7,5 @@ public protocol WorldServiceProtocol: Sendable { func fetchWorld(worldId: String) async throws -> World - func fetchFavoritedWorlds(n: Int) async throws -> [FavoriteWorld] + func fetchFavoritedWorlds() async throws -> [FavoriteWorld] } diff --git a/Sources/VRCKit/Services/WorldService.swift b/Sources/VRCKit/Services/WorldService.swift index 1e208fe..b04a41b 100644 --- a/Sources/VRCKit/Services/WorldService.swift +++ b/Sources/VRCKit/Services/WorldService.swift @@ -12,14 +12,30 @@ import MemberwiseInit public final actor WorldService: APIService, WorldServiceProtocol { public let client: APIClient private let path = "worlds" + private let limit = 100 public func fetchWorld(worldId: String) async throws -> World { let response = try await client.request(path: "\(path)/\(worldId)", method: .get) return try await Serializer.shared.decode(response.data) } - public func fetchFavoritedWorlds(n: Int = 100) async throws -> [FavoriteWorld] { - let queryItems = [URLQueryItem(name: "n", value: n.description)] + public func fetchFavoritedWorlds() async throws -> [FavoriteWorld] { + var allFavorites = Set() + var offset = 0 + while true { + let batch = try await fetchFavoritedWorlds(n: limit, offset: offset) + allFavorites.formUnion(batch) + if batch.count < limit { break } + offset += limit + } + return Array(allFavorites) + } + + private func fetchFavoritedWorlds(n: Int, offset: Int) async throws -> [FavoriteWorld] { + let queryItems = [ + URLQueryItem(name: "n", value: n.description), + URLQueryItem(name: "offset", value: offset.description) + ] let response = try await client.request(path: "\(path)/favorites", method: .get, queryItems: queryItems) let favoriteWorldWrapper: SafeDecodingArray = try await Serializer.shared.decode(response.data) return favoriteWorldWrapper.wrappedValue From 5351435feb568ec072aa193f293740d60e3090b5 Mon Sep 17 00:00:00 2001 From: makinosp Date: Thu, 17 Oct 2024 21:47:10 +0900 Subject: [PATCH 05/10] feat: improvement FavoriteServiceProtocol --- Sources/VRCKit/Protocols/FavoriteServiceProtocol.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Sources/VRCKit/Protocols/FavoriteServiceProtocol.swift b/Sources/VRCKit/Protocols/FavoriteServiceProtocol.swift index 2f5fd12..bed2735 100644 --- a/Sources/VRCKit/Protocols/FavoriteServiceProtocol.swift +++ b/Sources/VRCKit/Protocols/FavoriteServiceProtocol.swift @@ -7,8 +7,8 @@ public protocol FavoriteServiceProtocol: Sendable { func listFavoriteGroups() async throws -> [FavoriteGroup] - func listFavorites(n: Int, type: FavoriteType, tag: String?) async throws -> [Favorite] - func fetchFavoriteList(favoriteGroups: [FavoriteGroup]) async throws -> [FavoriteList] + func listFavorites(type: FavoriteType) async throws -> [Favorite] + func fetchFavoriteList(favoriteGroups: [FavoriteGroup], type: FavoriteType) async throws -> [FavoriteList] func addFavorite(type: FavoriteType, favoriteId: String, tag: String) async throws -> Favorite func updateFavoriteGroup( source: FavoriteGroup, From 337f5738dafd0b84388a3f7e81a9d7a38d935fd6 Mon Sep 17 00:00:00 2001 From: makinosp Date: Thu, 17 Oct 2024 21:47:43 +0900 Subject: [PATCH 06/10] feat: implement fetching all favorites in FavoriteSerivce --- Sources/VRCKit/Services/FavoriteService.swift | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/Sources/VRCKit/Services/FavoriteService.swift b/Sources/VRCKit/Services/FavoriteService.swift index b4d0284..b19dea2 100644 --- a/Sources/VRCKit/Services/FavoriteService.swift +++ b/Sources/VRCKit/Services/FavoriteService.swift @@ -20,6 +20,24 @@ public final actor FavoriteService: APIService, FavoriteServiceProtocol { return try await Serializer.shared.decode(response.data) } + /// Lists a user's all favorites with the specified parameters. + /// - Parameter type: The type of favorite (e.g., friend, world). + /// - Returns: An array of `Favorite` objects. + public func listFavorites(type: FavoriteType) async throws -> [Favorite] { + try await withThrowingTaskGroup(of: [Favorite].self) { taskGroup in + for offset in [0, 100, 200, 300] { + taskGroup.addTask { [unowned self] in + try await self.listFavorites(n: 100, offset: offset, type: type) + } + } + var results: [Favorite] = [] + for try await favorites in taskGroup { + results.append(contentsOf: favorites) + } + return results + } + } + /// Lists a user's favorites with the specified parameters. /// - Parameters: /// - n: The number of favorites to retrieve. Default is `60``. From 7cfffabdeb4cbba85a915a6390a7ffaf695536ed Mon Sep 17 00:00:00 2001 From: makinosp Date: Thu, 17 Oct 2024 21:48:01 +0900 Subject: [PATCH 07/10] refact: refactoring in FavoriteService --- Sources/VRCKit/Services/FavoriteService.swift | 30 +++++++++---------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/Sources/VRCKit/Services/FavoriteService.swift b/Sources/VRCKit/Services/FavoriteService.swift index b19dea2..4b1a712 100644 --- a/Sources/VRCKit/Services/FavoriteService.swift +++ b/Sources/VRCKit/Services/FavoriteService.swift @@ -44,14 +44,16 @@ public final actor FavoriteService: APIService, FavoriteServiceProtocol { /// - type: The type of favorite (e.g., friend, world). /// - tag: An optional tag to filter favorites. /// - Returns: An array of `Favorite` objects. - public func listFavorites( - n: Int = 60, + private func listFavorites( + n: Int = 100, + offset: Int = 0, type: FavoriteType, tag: String? = nil ) async throws -> [Favorite] { let path = "favorites" var queryItems = [ URLQueryItem(name: "n", value: n.description), + URLQueryItem(name: "offset", value: offset.description), URLQueryItem(name: "type", value: type.rawValue) ] if let tag = tag { @@ -62,28 +64,26 @@ public final actor FavoriteService: APIService, FavoriteServiceProtocol { } /// Fetches details of favorite groups asynchronously. - /// - Parameter favoriteGroups: An array of `FavoriteGroup` objects. + /// - Parameters: + /// - favoriteGroups: An array of `FavoriteGroup` objects. + /// - type: The type of favorite (e.g., friend, world). /// - Returns: An array of `FavoriteDetail` objects containing detailed information about the favorite groups. - public func fetchFavoriteList(favoriteGroups: [FavoriteGroup]) async throws -> [FavoriteList] { - var results: [FavoriteList] = [] + public func fetchFavoriteList(favoriteGroups: [FavoriteGroup], type: FavoriteType) async throws -> [FavoriteList] { try await withThrowingTaskGroup(of: FavoriteList.self) { taskGroup in - for favoriteGroup in favoriteGroups.filter({ $0.type == .friend }) { - taskGroup.addTask { [weak self] in - guard let self = self else { - throw VRCKitError.unexpected - } - let favorites = try await self.listFavorites( - type: .friend, - tag: favoriteGroup.name + for favoriteGroup in favoriteGroups.filter({ $0.type == type }) { + taskGroup.addTask { [unowned self] in + FavoriteList( + id: favoriteGroup.id, + favorites: try await listFavorites(type: type, tag: favoriteGroup.name) ) - return FavoriteList(id: favoriteGroup.id, favorites: favorites) } } + var results: [FavoriteList] = [] for try await favoriteGroupDetail in taskGroup { results.append(favoriteGroupDetail) } + return results } - return results } /// Adds a new favorite to a specific group. From b373963bb5f9f9946284b6621ca9c19f4c23b9e7 Mon Sep 17 00:00:00 2001 From: makinosp Date: Fri, 18 Oct 2024 12:38:50 +0900 Subject: [PATCH 08/10] feat: implement fetching all favorites in FavoriteSerivce, sort by offset --- Sources/VRCKit/Services/FavoriteService.swift | 2 +- Sources/VRCKit/Services/WorldService.swift | 22 ++++++++++++------- 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/Sources/VRCKit/Services/FavoriteService.swift b/Sources/VRCKit/Services/FavoriteService.swift index 4b1a712..3244663 100644 --- a/Sources/VRCKit/Services/FavoriteService.swift +++ b/Sources/VRCKit/Services/FavoriteService.swift @@ -27,7 +27,7 @@ public final actor FavoriteService: APIService, FavoriteServiceProtocol { try await withThrowingTaskGroup(of: [Favorite].self) { taskGroup in for offset in [0, 100, 200, 300] { taskGroup.addTask { [unowned self] in - try await self.listFavorites(n: 100, offset: offset, type: type) + try await listFavorites(n: 100, offset: offset, type: type) } } var results: [Favorite] = [] diff --git a/Sources/VRCKit/Services/WorldService.swift b/Sources/VRCKit/Services/WorldService.swift index b04a41b..7ef032e 100644 --- a/Sources/VRCKit/Services/WorldService.swift +++ b/Sources/VRCKit/Services/WorldService.swift @@ -13,6 +13,8 @@ public final actor WorldService: APIService, WorldServiceProtocol { public let client: APIClient private let path = "worlds" private let limit = 100 + private let maxCount = 400 + private typealias FavoriteWorldsWithOffset = (offset: Int, worlds: [FavoriteWorld]) public func fetchWorld(worldId: String) async throws -> World { let response = try await client.request(path: "\(path)/\(worldId)", method: .get) @@ -20,15 +22,19 @@ public final actor WorldService: APIService, WorldServiceProtocol { } public func fetchFavoritedWorlds() async throws -> [FavoriteWorld] { - var allFavorites = Set() - var offset = 0 - while true { - let batch = try await fetchFavoritedWorlds(n: limit, offset: offset) - allFavorites.formUnion(batch) - if batch.count < limit { break } - offset += limit + try await withThrowingTaskGroup(of: FavoriteWorldsWithOffset.self) { taskGroup in + for offset in Array(stride(from: 0, to: maxCount, by: limit)) { + taskGroup.addTask { [unowned self] in + let worlds = try await fetchFavoritedWorlds(n: limit, offset: offset) + return (offset, worlds) + } + } + var resultsDict: [FavoriteWorldsWithOffset] = [] + for try await result in taskGroup { resultsDict.append(result) } + return resultsDict + .sorted { $0.offset < $1.offset } + .flatMap { $0.worlds } } - return Array(allFavorites) } private func fetchFavoritedWorlds(n: Int, offset: Int) async throws -> [FavoriteWorld] { From d037c6f877e153c9e67f9848bcd0c3e4f1f3e74a Mon Sep 17 00:00:00 2001 From: makinosp Date: Fri, 18 Oct 2024 12:57:56 +0900 Subject: [PATCH 09/10] fix: change from unowned self to weak self --- Sources/VRCKit/Services/FavoriteService.swift | 15 +++++++++++---- Sources/VRCKit/Services/WorldService.swift | 3 ++- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/Sources/VRCKit/Services/FavoriteService.swift b/Sources/VRCKit/Services/FavoriteService.swift index 3244663..0d7a934 100644 --- a/Sources/VRCKit/Services/FavoriteService.swift +++ b/Sources/VRCKit/Services/FavoriteService.swift @@ -26,8 +26,9 @@ public final actor FavoriteService: APIService, FavoriteServiceProtocol { public func listFavorites(type: FavoriteType) async throws -> [Favorite] { try await withThrowingTaskGroup(of: [Favorite].self) { taskGroup in for offset in [0, 100, 200, 300] { - taskGroup.addTask { [unowned self] in - try await listFavorites(n: 100, offset: offset, type: type) + taskGroup.addTask { [weak self] in + guard let self = self else { return [] } + return try await listFavorites(n: 100, offset: offset, type: type) } } var results: [Favorite] = [] @@ -71,8 +72,14 @@ public final actor FavoriteService: APIService, FavoriteServiceProtocol { public func fetchFavoriteList(favoriteGroups: [FavoriteGroup], type: FavoriteType) async throws -> [FavoriteList] { try await withThrowingTaskGroup(of: FavoriteList.self) { taskGroup in for favoriteGroup in favoriteGroups.filter({ $0.type == type }) { - taskGroup.addTask { [unowned self] in - FavoriteList( + taskGroup.addTask { [weak self] in + guard let self = self else { + return FavoriteList( + id: favoriteGroup.id, + favorites: [] + ) + } + return FavoriteList( id: favoriteGroup.id, favorites: try await listFavorites(type: type, tag: favoriteGroup.name) ) diff --git a/Sources/VRCKit/Services/WorldService.swift b/Sources/VRCKit/Services/WorldService.swift index 7ef032e..55521b7 100644 --- a/Sources/VRCKit/Services/WorldService.swift +++ b/Sources/VRCKit/Services/WorldService.swift @@ -24,7 +24,8 @@ public final actor WorldService: APIService, WorldServiceProtocol { public func fetchFavoritedWorlds() async throws -> [FavoriteWorld] { try await withThrowingTaskGroup(of: FavoriteWorldsWithOffset.self) { taskGroup in for offset in Array(stride(from: 0, to: maxCount, by: limit)) { - taskGroup.addTask { [unowned self] in + taskGroup.addTask { [weak self] in + guard let self = self else { return (offset, []) } let worlds = try await fetchFavoritedWorlds(n: limit, offset: offset) return (offset, worlds) } From d9faac0ade49e0a0d0b5a175e20a248d64502237 Mon Sep 17 00:00:00 2001 From: makinosp Date: Fri, 18 Oct 2024 13:21:33 +0900 Subject: [PATCH 10/10] refact: code refactoring --- .../Models/Favorite/FavoriteListModel.swift | 6 ++++++ Sources/VRCKit/Services/FavoriteService.swift | 19 +++++++------------ Sources/VRCKit/Services/WorldService.swift | 2 +- 3 files changed, 14 insertions(+), 13 deletions(-) diff --git a/Sources/VRCKit/Models/Favorite/FavoriteListModel.swift b/Sources/VRCKit/Models/Favorite/FavoriteListModel.swift index 51dd318..a8f37e5 100644 --- a/Sources/VRCKit/Models/Favorite/FavoriteListModel.swift +++ b/Sources/VRCKit/Models/Favorite/FavoriteListModel.swift @@ -10,6 +10,12 @@ public struct FavoriteList: Sendable, Identifiable { public let favorites: [Favorite] } +public extension FavoriteList { + init(id: ID) { + self.init(id: id, favorites: []) + } +} + public extension FavoriteList { func allFavoritesAre(_ type: FavoriteType) -> Bool { favorites.allSatisfy { $0.type == type } diff --git a/Sources/VRCKit/Services/FavoriteService.swift b/Sources/VRCKit/Services/FavoriteService.swift index 0d7a934..3818eed 100644 --- a/Sources/VRCKit/Services/FavoriteService.swift +++ b/Sources/VRCKit/Services/FavoriteService.swift @@ -11,6 +11,8 @@ import MemberwiseInit @MemberwiseInit(.public) public final actor FavoriteService: APIService, FavoriteServiceProtocol { public let client: APIClient + private let limit = 100 + private let maxCount = 400 /// Asynchronously retrieves a list of favorite groups from the server. /// - Returns: An array of `FavoriteGroup` objects. @@ -25,10 +27,10 @@ public final actor FavoriteService: APIService, FavoriteServiceProtocol { /// - Returns: An array of `Favorite` objects. public func listFavorites(type: FavoriteType) async throws -> [Favorite] { try await withThrowingTaskGroup(of: [Favorite].self) { taskGroup in - for offset in [0, 100, 200, 300] { + for offset in stride(from: .zero, to: maxCount, by: limit) { taskGroup.addTask { [weak self] in guard let self = self else { return [] } - return try await listFavorites(n: 100, offset: offset, type: type) + return try await listFavorites(n: limit, offset: offset, type: type) } } var results: [Favorite] = [] @@ -73,16 +75,9 @@ public final actor FavoriteService: APIService, FavoriteServiceProtocol { try await withThrowingTaskGroup(of: FavoriteList.self) { taskGroup in for favoriteGroup in favoriteGroups.filter({ $0.type == type }) { taskGroup.addTask { [weak self] in - guard let self = self else { - return FavoriteList( - id: favoriteGroup.id, - favorites: [] - ) - } - return FavoriteList( - id: favoriteGroup.id, - favorites: try await listFavorites(type: type, tag: favoriteGroup.name) - ) + guard let self = self else { return FavoriteList(id: favoriteGroup.id) } + let favorites = try await listFavorites(type: type, tag: favoriteGroup.name) + return FavoriteList(id: favoriteGroup.id, favorites: favorites) } } var results: [FavoriteList] = [] diff --git a/Sources/VRCKit/Services/WorldService.swift b/Sources/VRCKit/Services/WorldService.swift index 55521b7..ebf6bf9 100644 --- a/Sources/VRCKit/Services/WorldService.swift +++ b/Sources/VRCKit/Services/WorldService.swift @@ -23,7 +23,7 @@ public final actor WorldService: APIService, WorldServiceProtocol { public func fetchFavoritedWorlds() async throws -> [FavoriteWorld] { try await withThrowingTaskGroup(of: FavoriteWorldsWithOffset.self) { taskGroup in - for offset in Array(stride(from: 0, to: maxCount, by: limit)) { + for offset in stride(from: .zero, to: maxCount, by: limit) { taskGroup.addTask { [weak self] in guard let self = self else { return (offset, []) } let worlds = try await fetchFavoritedWorlds(n: limit, offset: offset)