From 2a18244d84b6ea50b4bd685c0cd28ecaee8f80e7 Mon Sep 17 00:00:00 2001 From: CMK Date: Thu, 6 May 2021 16:24:21 +0800 Subject: [PATCH] fix: learn word cost too much CPU issue. Update TwitterTextEditor package source to upstream --- Mastodon.xcodeproj/project.pbxproj | 38 +++++++++---------- .../xcschemes/xcschememanagement.plist | 4 +- .../xcshareddata/swiftpm/Package.resolved | 10 ++--- .../Scene/Compose/ComposeViewController.swift | 22 ++--------- ...rvice+CustomEmojiViewModel+LoadState.swift | 2 +- .../EmojiService+CustomEmojiViewModel.swift | 21 ++++++++++ 6 files changed, 52 insertions(+), 45 deletions(-) diff --git a/Mastodon.xcodeproj/project.pbxproj b/Mastodon.xcodeproj/project.pbxproj index 2d5c0711..6b1b9ed1 100644 --- a/Mastodon.xcodeproj/project.pbxproj +++ b/Mastodon.xcodeproj/project.pbxproj @@ -202,6 +202,8 @@ DB2B3AE925E38850007045F9 /* UIViewPreview.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB2B3AE825E38850007045F9 /* UIViewPreview.swift */; }; DB2F073525E8ECF000957B2D /* AuthenticationViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB2F073325E8ECF000957B2D /* AuthenticationViewModel.swift */; }; DB2FF510260B113300ADA9FE /* ComposeStatusPollExpiresOptionCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB2FF50F260B113300ADA9FE /* ComposeStatusPollExpiresOptionCollectionViewCell.swift */; }; + DB35B0B32643D821006AC73B /* TwitterTextEditor in Frameworks */ = {isa = PBXBuildFile; productRef = DB35B0B22643D821006AC73B /* TwitterTextEditor */; }; + DB35B0B42643D821006AC73B /* TwitterTextEditor in Embed Frameworks */ = {isa = PBXBuildFile; productRef = DB35B0B22643D821006AC73B /* TwitterTextEditor */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; }; DB35FC1F2612F1D9006193C9 /* ProfileRelationshipActionButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB35FC1E2612F1D9006193C9 /* ProfileRelationshipActionButton.swift */; }; DB35FC252612FD7A006193C9 /* ProfileFieldView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB35FC242612FD7A006193C9 /* ProfileFieldView.swift */; }; DB35FC2F26130172006193C9 /* MastodonField.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB35FC2E26130172006193C9 /* MastodonField.swift */; }; @@ -434,8 +436,6 @@ DBE54ABF2636C889004E7C0B /* UserDefaults.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB6D1B23263684C600ACB481 /* UserDefaults.swift */; }; DBE54AC62636C89F004E7C0B /* NotificationPreference.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBE54AC52636C89F004E7C0B /* NotificationPreference.swift */; }; DBE54ACC2636C8FD004E7C0B /* NotificationPreference.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBE54AC52636C89F004E7C0B /* NotificationPreference.swift */; }; - DBE64A8B260C49D200E6359A /* TwitterTextEditor in Frameworks */ = {isa = PBXBuildFile; productRef = DBE64A8A260C49D200E6359A /* TwitterTextEditor */; }; - DBE64A8C260C49D200E6359A /* TwitterTextEditor in Embed Frameworks */ = {isa = PBXBuildFile; productRef = DBE64A8A260C49D200E6359A /* TwitterTextEditor */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; }; DBF8AE16263293E400C9C23C /* NotificationService.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBF8AE15263293E400C9C23C /* NotificationService.swift */; }; DBF8AE1A263293E400C9C23C /* NotificationService.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = DBF8AE13263293E400C9C23C /* NotificationService.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; DBF8AE862632992800C9C23C /* Base85 in Frameworks */ = {isa = PBXBuildFile; productRef = DBF8AE852632992800C9C23C /* Base85 */; }; @@ -545,8 +545,8 @@ dstSubfolderSpec = 10; files = ( DB6804872637CD4C00430867 /* AppShared.framework in Embed Frameworks */, - DBE64A8C260C49D200E6359A /* TwitterTextEditor in Embed Frameworks */, DB89BA0425C10FD0008580ED /* CoreDataStack.framework in Embed Frameworks */, + DB35B0B42643D821006AC73B /* TwitterTextEditor in Embed Frameworks */, ); name = "Embed Frameworks"; runOnlyForDeploymentPostprocessing = 0; @@ -1006,6 +1006,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + DB35B0B32643D821006AC73B /* TwitterTextEditor in Frameworks */, DB0140BD25C40D7500F9F3CF /* CommonOSLog in Frameworks */, DB89BA0325C10FD0008580ED /* CoreDataStack.framework in Frameworks */, 2D42FF6125C8177C004A627A /* ActiveLabel in Frameworks */, @@ -1018,7 +1019,6 @@ DBF96326262EC0A6001D8D25 /* AuthenticationServices.framework in Frameworks */, 2D61336925C18A4F00CAE157 /* AlamofireNetworkActivityIndicator in Frameworks */, DB3D0FF325BAA61700EAA174 /* AlamofireImage in Frameworks */, - DBE64A8B260C49D200E6359A /* TwitterTextEditor in Frameworks */, 2D5981BA25E4D7F8000FB903 /* ThirdPartyMailer in Frameworks */, 87FFDA5D898A5C42ADCB35E7 /* Pods_Mastodon.framework in Frameworks */, DB6804C82637CE2F00430867 /* AppShared.framework in Frameworks */, @@ -2408,8 +2408,8 @@ 2D5981B925E4D7F8000FB903 /* ThirdPartyMailer */, 2D939AC725EE14620076FA61 /* CropViewController */, DB9A487D2603456B008B817C /* UITextView+Placeholder */, - DBE64A8A260C49D200E6359A /* TwitterTextEditor */, DBB525072611EAC0002F1F29 /* Tabman */, + DB35B0B22643D821006AC73B /* TwitterTextEditor */, ); productName = Mastodon; productReference = DB427DD225BAA00100D1B89D /* Mastodon.app */; @@ -2596,10 +2596,10 @@ 2D5981B825E4D7F8000FB903 /* XCRemoteSwiftPackageReference "ThirdPartyMailer" */, 2D939AC625EE14620076FA61 /* XCRemoteSwiftPackageReference "TOCropViewController" */, DB9A487C2603456B008B817C /* XCRemoteSwiftPackageReference "UITextView-Placeholder" */, - DBE64A89260C49D200E6359A /* XCRemoteSwiftPackageReference "TwitterTextEditor" */, DBB525062611EAC0002F1F29 /* XCRemoteSwiftPackageReference "Tabman" */, DBF8AE842632992700C9C23C /* XCRemoteSwiftPackageReference "Base85" */, DB6804722637CC1200430867 /* XCRemoteSwiftPackageReference "KeychainAccess" */, + DB35B0B12643D821006AC73B /* XCRemoteSwiftPackageReference "TwitterTextEditor" */, ); productRefGroup = DB427DD325BAA00100D1B89D /* Products */; projectDirPath = ""; @@ -3937,6 +3937,14 @@ minimumVersion = 0.1.1; }; }; + DB35B0B12643D821006AC73B /* XCRemoteSwiftPackageReference "TwitterTextEditor" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/twitter/TwitterTextEditor"; + requirement = { + kind = upToNextMajorVersion; + minimumVersion = 1.1.0; + }; + }; DB3D0FF125BAA61700EAA174 /* XCRemoteSwiftPackageReference "AlamofireImage" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/Alamofire/AlamofireImage.git"; @@ -3977,14 +3985,6 @@ minimumVersion = 2.11.0; }; }; - DBE64A89260C49D200E6359A /* XCRemoteSwiftPackageReference "TwitterTextEditor" */ = { - isa = XCRemoteSwiftPackageReference; - repositoryURL = "https://github.com/MainasuK/TwitterTextEditor"; - requirement = { - branch = "feature/input-view"; - kind = branch; - }; - }; DBF8AE842632992700C9C23C /* XCRemoteSwiftPackageReference "Base85" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/MainasuK/Base85.git"; @@ -4030,6 +4030,11 @@ package = DB0140BB25C40D7500F9F3CF /* XCRemoteSwiftPackageReference "CommonOSLog" */; productName = CommonOSLog; }; + DB35B0B22643D821006AC73B /* TwitterTextEditor */ = { + isa = XCSwiftPackageProductDependency; + package = DB35B0B12643D821006AC73B /* XCRemoteSwiftPackageReference "TwitterTextEditor" */; + productName = TwitterTextEditor; + }; DB3D0FF225BAA61700EAA174 /* AlamofireImage */ = { isa = XCSwiftPackageProductDependency; package = DB3D0FF125BAA61700EAA174 /* XCRemoteSwiftPackageReference "AlamofireImage" */; @@ -4060,11 +4065,6 @@ package = DBB525062611EAC0002F1F29 /* XCRemoteSwiftPackageReference "Tabman" */; productName = Tabman; }; - DBE64A8A260C49D200E6359A /* TwitterTextEditor */ = { - isa = XCSwiftPackageProductDependency; - package = DBE64A89260C49D200E6359A /* XCRemoteSwiftPackageReference "TwitterTextEditor" */; - productName = TwitterTextEditor; - }; DBF8AE852632992800C9C23C /* Base85 */ = { isa = XCSwiftPackageProductDependency; package = DBF8AE842632992700C9C23C /* XCRemoteSwiftPackageReference "Base85" */; diff --git a/Mastodon.xcodeproj/xcuserdata/mainasuk.xcuserdatad/xcschemes/xcschememanagement.plist b/Mastodon.xcodeproj/xcuserdata/mainasuk.xcuserdatad/xcschemes/xcschememanagement.plist index 32685726..f092f973 100644 --- a/Mastodon.xcodeproj/xcuserdata/mainasuk.xcuserdatad/xcschemes/xcschememanagement.plist +++ b/Mastodon.xcodeproj/xcuserdata/mainasuk.xcuserdatad/xcschemes/xcschememanagement.plist @@ -12,7 +12,7 @@ CoreDataStack.xcscheme_^#shared#^_ orderHint - 14 + 15 Mastodon - RTL.xcscheme_^#shared#^_ @@ -32,7 +32,7 @@ NotificationService.xcscheme_^#shared#^_ orderHint - 15 + 14 SuppressBuildableAutocreation diff --git a/Mastodon.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Mastodon.xcworkspace/xcshareddata/swiftpm/Package.resolved index b9f39148..3295adb4 100644 --- a/Mastodon.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/Mastodon.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -69,7 +69,7 @@ "repositoryURL": "https://github.com/onevcat/Kingfisher.git", "state": { "branch": null, - "revision": "15d199e84677303a7004ed2c5ecaa1a90f3863f8", + "revision": "bbc4bc4def7eb05a7ba8e1219f80ee9be327334e", "version": "6.2.1" } }, @@ -138,11 +138,11 @@ }, { "package": "TwitterTextEditor", - "repositoryURL": "https://github.com/MainasuK/TwitterTextEditor", + "repositoryURL": "https://github.com/twitter/TwitterTextEditor", "state": { - "branch": "feature/input-view", - "revision": "1e565d13e3c26fc2bedeb418890df42f80d6e3d5", - "version": null + "branch": null, + "revision": "dfe0edc3bcb6703ee2fd0e627f95e726b63e732a", + "version": "1.1.0" } }, { diff --git a/Mastodon/Scene/Compose/ComposeViewController.swift b/Mastodon/Scene/Compose/ComposeViewController.swift index a18cf921..dedcd405 100644 --- a/Mastodon/Scene/Compose/ComposeViewController.swift +++ b/Mastodon/Scene/Compose/ComposeViewController.swift @@ -300,24 +300,9 @@ extension ComposeViewController { } } .store(in: &disposeBag) - - // bind text editor for custom emojis update event - viewModel.customEmojiViewModel - .compactMap { $0?.emojis } - .switchToLatest() - .sink(receiveValue: { [weak self] emojis in - guard let self = self else { return } - for emoji in emojis { - UITextChecker.learnWord(emoji.shortcode) - UITextChecker.learnWord(":" + emoji.shortcode + ":") - } - self.textEditorView()?.setNeedsUpdateTextAttributes() - }) - .store(in: &disposeBag) // bind custom emoji picker UI viewModel.customEmojiViewModel - .receive(on: DispatchQueue.main) .map { viewModel -> AnyPublisher<[Mastodon.Entity.Emoji], Never> in guard let viewModel = viewModel else { return Just([]).eraseToAnyPublisher() @@ -325,6 +310,7 @@ extension ComposeViewController { return viewModel.emojis.eraseToAnyPublisher() } .switchToLatest() + .receive(on: DispatchQueue.main) .sink(receiveValue: { [weak self] emojis in guard let self = self else { return } if emojis.isEmpty { @@ -581,6 +567,7 @@ extension ComposeViewController: TextEditorViewTextAttributesDelegate { updateAttributedString attributedString: NSAttributedString, completion: @escaping (NSAttributedString?) -> Void ) { + // FIXME: needs O(1) update completion to fix profermance issue DispatchQueue.global().async { let string = attributedString.string os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s: update: %s", ((#file as NSString).lastPathComponent), #line, #function, string) @@ -631,11 +618,10 @@ extension ComposeViewController: TextEditorViewTextAttributesDelegate { } // emoji - let emojis = customEmojiViewModel?.emojis.value ?? [] - if !emojis.isEmpty { + if let customEmojiViewModel = customEmojiViewModel, !customEmojiViewModel.emojiDict.value.isEmpty { for match in emojiMatches { guard let name = string.substring(with: match, at: 2) else { continue } - guard let emoji = emojis.first(where: { $0.shortcode == name }) else { continue } + guard let emoji = customEmojiViewModel.emoji(shortcode: name) else { continue } os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s: handle emoji: %s", ((#file as NSString).lastPathComponent), #line, #function, name) // set emoji token invisiable (without upper bounce space) diff --git a/Mastodon/Service/EmojiService/EmojiService+CustomEmojiViewModel+LoadState.swift b/Mastodon/Service/EmojiService/EmojiService+CustomEmojiViewModel+LoadState.swift index 4fdab0bb..a03af9bd 100644 --- a/Mastodon/Service/EmojiService/EmojiService+CustomEmojiViewModel+LoadState.swift +++ b/Mastodon/Service/EmojiService/EmojiService+CustomEmojiViewModel+LoadState.swift @@ -41,7 +41,7 @@ extension EmojiService.CustomEmojiViewModel.LoadState { guard let viewModel = viewModel, let apiService = viewModel.service?.apiService, let stateMachine = stateMachine else { return } apiService.customEmoji(domain: viewModel.domain) - .receive(on: DispatchQueue.main) + // .receive(on: DispatchQueue.main) .sink { completion in switch completion { case .failure(let error): diff --git a/Mastodon/Service/EmojiService/EmojiService+CustomEmojiViewModel.swift b/Mastodon/Service/EmojiService/EmojiService+CustomEmojiViewModel.swift index f866f4a0..d1b8494d 100644 --- a/Mastodon/Service/EmojiService/EmojiService+CustomEmojiViewModel.swift +++ b/Mastodon/Service/EmojiService/EmojiService+CustomEmojiViewModel.swift @@ -32,10 +32,31 @@ extension EmojiService { return stateMachine }() let emojis = CurrentValueSubject<[Mastodon.Entity.Emoji], Never>([]) + let emojiDict = CurrentValueSubject<[String: [Mastodon.Entity.Emoji]], Never>([:]) + + private var learnedEmoji: Set = Set() init(domain: String, service: EmojiService) { self.domain = domain self.service = service + + emojis + .map { Dictionary(grouping: $0, by: { $0.shortcode }) } + .assign(to: \.value, on: emojiDict) + .store(in: &disposeBag) + } + + func emoji(shortcode: String) -> Mastodon.Entity.Emoji? { + if !learnedEmoji.contains(shortcode) { + learnedEmoji.insert(shortcode) + + DispatchQueue.global().async { + UITextChecker.learnWord(shortcode) + UITextChecker.learnWord(":" + shortcode + ":") + } + } + + return emojiDict.value[shortcode]?.first } }