From 4de42be6881d0b4a8d14e4b30772d3977776da39 Mon Sep 17 00:00:00 2001 From: Stefan Ceriu Date: Wed, 12 Aug 2026 11:23:52 +0300 Subject: [PATCH] Load the map framework on demand instead of at launch MapLibre ships as a dynamic framework, so being named in the app's load commands means dyld loads it before main runs on every launch: 7.3 MB paged in, 133 C++ static initialisers run and its classes registered, all for a map that only appears once a location screen is opened. Measured on an iPhone 17 Pro with two otherwise identical probe apps launched alternately, linking MapLibre takes pre-main from 11.1 ms to 23.2 ms, and on a cold launch from 32.9 ms to 60.7 ms. Loading it on demand brings pre-main back to the 11.1 ms baseline and moves the roughly 11 ms it costs to dlopen to the moment the map first appears. MapLibreShim is now embedded but not linked, and the app resolves the shim through the ObjC runtime after dlopen-ing it. MapLibre stays in the bundle as an embed-only framework reference so the shim's load command can resolve against it, expressed as an implicit framework because xcodegen can't describe embed-without-link for a package product. Timeline location messages already use the static tile view, which never touched MapLibre, so the shim only loads for interactive maps. --- ElementX.xcodeproj/project.pbxproj | 70 ++++++++--------- .../MapLibreShim/Sources/MapLibreShim.swift | 4 +- .../Other/MapLibre/MapLibreMapView.swift | 75 ++++++++++++------- ElementX/SupportingFiles/target.yml | 9 ++- 4 files changed, 97 insertions(+), 61 deletions(-) diff --git a/ElementX.xcodeproj/project.pbxproj b/ElementX.xcodeproj/project.pbxproj index e6232eaae..f14a5c01c 100644 --- a/ElementX.xcodeproj/project.pbxproj +++ b/ElementX.xcodeproj/project.pbxproj @@ -343,15 +343,15 @@ 36926D795D6D19177C7812F8 /* EncryptionResetPasswordScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = E6935A55AB3B0C94BC566DD6 /* EncryptionResetPasswordScreenCoordinator.swift */; }; 369BF960E52BBEE61F8A5BD1 /* BlockedUsersScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED60E4D2CD678E1EBF16F77A /* BlockedUsersScreen.swift */; }; 36AC963F2F04069B7FF1AA0C /* UIConstants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E6D88E8AFFBF2C1D589C0FA /* UIConstants.swift */; }; - 36AD4DD4C798E22584ED3200 /* SwiftState in Frameworks */ = {isa = PBXBuildFile; productRef = 9573B94B1C86C6DF751AF3FD /* SwiftState */; }; - 36CD6E11B37396E14F032CB6 /* GZIP in Frameworks */ = {isa = PBXBuildFile; productRef = 997C7385E1A07E061D7E2100 /* GZIP */; }; + 36AD4DD4C798E22584ED3200 /* GZIP in Frameworks */ = {isa = PBXBuildFile; productRef = 997C7385E1A07E061D7E2100 /* GZIP */; }; + 36CD6E11B37396E14F032CB6 /* Sentry in Frameworks */ = {isa = PBXBuildFile; productRef = 7731767AE437BA3BD2CC14A8 /* Sentry */; }; 36DCA11EA7320A45A4CC8D07 /* SpaceRemoveChildrenConfirmationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 44AC61A375CC2E47938B5D12 /* SpaceRemoveChildrenConfirmationView.swift */; }; 36DE961B784087D5E18EF9BA /* LogViewerScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5A07692536D66E3DA32C4964 /* LogViewerScreen.swift */; }; 370AF5BFCD4384DD455479B6 /* ElementCallWidgetDriverProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = A6C11AD9813045E44F950410 /* ElementCallWidgetDriverProtocol.swift */; }; 377980ABF16525114E72DDE2 /* SwiftSoup in Frameworks */ = {isa = PBXBuildFile; productRef = D2330417C6BE2F6872FFF96B /* SwiftSoup */; }; 37906355E207DB5703754675 /* AppLockSetupBiometricsScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9F893F4A111CB7BA5C96949 /* AppLockSetupBiometricsScreenViewModel.swift */; }; 37D789F24199B32E3FD1AA7B /* FileRoomTimelineItemContent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 216F0DDC98F2A2C162D09C28 /* FileRoomTimelineItemContent.swift */; }; - 37E47F5101C0C036289D3807 /* Emojibase in Frameworks */ = {isa = PBXBuildFile; productRef = C05729B1684C331F5FFE9232 /* Emojibase */; }; + 37E47F5101C0C036289D3807 /* WysiwygComposer in Frameworks */ = {isa = PBXBuildFile; productRef = CA07D57389DACE18AEB6A5E2 /* WysiwygComposer */; }; 383063A7924F06D54BA9B24C /* SpaceSettingsScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9475FD81B13D50103E5290EB /* SpaceSettingsScreen.swift */; }; 384D6B9A7DFD7260139D6852 /* UITestsNotificationCenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = EBEB8D9F4940E161B18FE4BC /* UITestsNotificationCenter.swift */; }; 38546A6010A2CF240EC9AF73 /* BindableState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EA1D2CBAEA5D0BD00B90D1B /* BindableState.swift */; }; @@ -422,11 +422,11 @@ 441857143FC100E0A7DE42A8 /* RoomPowerLevelProxyProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4D635709C1D6D37C225AD40E /* RoomPowerLevelProxyProtocol.swift */; }; 446BCD2D0AE27E0CFD1BDC8F /* ImageMediaEventsTimelineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FF584D757E768EA7776A532 /* ImageMediaEventsTimelineView.swift */; }; 44BDD670FF9095ACE240A3A2 /* VoiceMessageMediaManagerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = AC4F10BDD56FA77FEC742333 /* VoiceMessageMediaManagerTests.swift */; }; - 44F0E1B576C7599DF8022071 /* SentrySwiftUI in Frameworks */ = {isa = PBXBuildFile; productRef = 75361A9D8A3C5501EADB225D /* SentrySwiftUI */; }; + 44F0E1B576C7599DF8022071 /* Version in Frameworks */ = {isa = PBXBuildFile; productRef = A05AF81DDD14AD58CB0E1B9B /* Version */; }; 454311EAC17D778E19F46592 /* NotificationPermissionsScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 91868EB98818044E6FEBE532 /* NotificationPermissionsScreenCoordinator.swift */; }; 454F8DDC4442C0DE54094902 /* LABiometryType.swift in Sources */ = {isa = PBXBuildFile; fileRef = D3F219838588C62198E726E3 /* LABiometryType.swift */; }; 45D6DC594816288983627484 /* UITestsScreenIdentifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6CEBE5EA91E8691EDF364EC2 /* UITestsScreenIdentifier.swift */; }; - 4610C57A4785FFF5E67F0C6D /* WysiwygComposer in Frameworks */ = {isa = PBXBuildFile; productRef = CA07D57389DACE18AEB6A5E2 /* WysiwygComposer */; }; + 4610C57A4785FFF5E67F0C6D /* SwiftOGG in Frameworks */ = {isa = PBXBuildFile; productRef = 391D11F92DFC91666AA1503F /* SwiftOGG */; }; 46562110EE202E580A5FFD9C /* RoomScreenViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93CF7B19FFCF8EFBE0A8696A /* RoomScreenViewModelTests.swift */; }; 4681820102DAC8BA586357D4 /* VoiceMessageCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = DAB8D7926A5684E18196B538 /* VoiceMessageCache.swift */; }; 46A183C6125A669AEB005699 /* UserProfileScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = F134D2D91DFF732FB75B2CB7 /* UserProfileScreenViewModelProtocol.swift */; }; @@ -723,7 +723,7 @@ 74DF5BC17DE9F51E077FD457 /* LinkNewDeviceServiceMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA257D747DD7E6FFA5C2BE2D /* LinkNewDeviceServiceMock.swift */; }; 7501442D52A65F73DF79FFD4 /* PaginationIndicatorRoomTimelineItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0B987FC3FDBAA0E1C5AA235C /* PaginationIndicatorRoomTimelineItem.swift */; }; 75072C529C9C363F531721F1 /* RecoveryKeyScreenHook.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AE97AD18893E2D8120F559D /* RecoveryKeyScreenHook.swift */; }; - 754602A7B2AAD443C4228ED4 /* MapLibre in Frameworks */ = {isa = PBXBuildFile; productRef = ED146E565C4CB792FF2F37EA /* MapLibre */; }; + 754602A7B2AAD443C4228ED4 /* PostHog in Frameworks */ = {isa = PBXBuildFile; productRef = 4278261E147DB2DE5CFB7FC5 /* PostHog */; }; 755395927DDD6EBDDA5E217A /* SettingsFlowCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = D28F7A6CEEA4A2815B0F0F55 /* SettingsFlowCoordinator.swift */; }; 755727E0B756430DFFEC4732 /* SessionVerificationViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DF05DA24F71B455E8EFEBC3B /* SessionVerificationViewModelTests.swift */; }; 756BD0B80C1AF2A6DF0786CB /* CommonSettings+NotificationName.swift in Sources */ = {isa = PBXBuildFile; fileRef = AB7A66212DC7D097886BD01F /* CommonSettings+NotificationName.swift */; }; @@ -788,6 +788,7 @@ 7D6DC832DE7A3DE874E2E9BC /* MatrixRustSDK in Frameworks */ = {isa = PBXBuildFile; productRef = BB111AE9D390233CDD2C7FD5 /* MatrixRustSDK */; }; 7DCFC31B49BDD2BC32184E58 /* TimelineControllerFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8C0D5AE752AF87DA0A920812 /* TimelineControllerFactory.swift */; }; 7E2BB42805C59DB57E95610F /* PillView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7773CBFDBD458E0B7E270507 /* PillView.swift */; }; + 7E429C44DD0C99870C6E9972 /* MapLibre.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 1DBA4A688563F86CC0D3E537 /* MapLibre.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 7E43FBB918AAC136034F2758 /* test_image.png in Resources */ = {isa = PBXBuildFile; fileRef = 810133CF215075C285FC6F3A /* test_image.png */; }; 7E91BAC17963ED41208F489B /* UserSessionStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E8BDC092D817B68CD9040C5 /* UserSessionStore.swift */; }; 7F0B6EB5CB52D7B7A2BB7D15 /* BannedRoomProxyMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FD7E851E2BA8C5A8D284B2A /* BannedRoomProxyMock.swift */; }; @@ -1013,7 +1014,7 @@ A076E0A9338FD2D950C3C4A1 /* ChatsSpaceFiltersScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = E63072981793CCA84EE12798 /* ChatsSpaceFiltersScreenViewModelProtocol.swift */; }; A0861B727B273B5B3DD7FBF6 /* KnockRequestsListScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D09227E671DB30795C43FFFD /* KnockRequestsListScreenViewModel.swift */; }; A0868BDE84D2140A885BE3C9 /* EncryptionResetScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7E8562F4D7DE073BC32902AB /* EncryptionResetScreenViewModelProtocol.swift */; }; - A0D7E5BD0298A97DCBDCE40B /* Sentry in Frameworks */ = {isa = PBXBuildFile; productRef = 7731767AE437BA3BD2CC14A8 /* Sentry */; }; + A0D7E5BD0298A97DCBDCE40B /* SentrySwiftUI in Frameworks */ = {isa = PBXBuildFile; productRef = 75361A9D8A3C5501EADB225D /* SentrySwiftUI */; }; A10D6CCDE2010C09EEA1A593 /* HomeScreenRoomList.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7661EFFCAA307A97D71132A /* HomeScreenRoomList.swift */; }; A14A9419105A1CD42F0511C4 /* UserIndicatorModalView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E43005941B3A2C9671E23C85 /* UserIndicatorModalView.swift */; }; A1672EF491FE6F3BBF7878BE /* test_apple_image.heic in Resources */ = {isa = PBXBuildFile; fileRef = BB576F4118C35E6B5124FA22 /* test_apple_image.heic */; }; @@ -1060,18 +1061,17 @@ A6FFC4C5154C446BAD6B40D8 /* TimelineItemProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8520AFD6680CBAD388F6D927 /* TimelineItemProvider.swift */; }; A722F426FD81FC67706BB1E0 /* CustomLayoutLabelStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 42236480CF0431535EBE8387 /* CustomLayoutLabelStyle.swift */; }; A74438ED16F8683A4B793E6A /* AnalyticsSettingsScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0BCE3FAF40932AC7C7639AC4 /* AnalyticsSettingsScreenViewModel.swift */; }; - A79634DCE52B5021F549E0E9 /* MapLibreShim.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 44E8BAA982EC260E74CCCB22 /* MapLibreShim.framework */; }; A7D48E44D485B143AADDB77D /* Strings+Untranslated.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A18F6CE4D694D21E4EA9B25 /* Strings+Untranslated.swift */; }; A7DB75E090542331F6668A23 /* CreateRoomScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = CF19027E7FFA5E63D148873A /* CreateRoomScreenViewModel.swift */; }; A808DC3F72D15C6C5A52317E /* TimelineItemDebugView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BCDA016D05107DED3B9495CB /* TimelineItemDebugView.swift */; }; A816F7087C495D85048AC50E /* RoomMemberDetailsScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B6E30BB748F3F480F077969 /* RoomMemberDetailsScreenModels.swift */; }; A851635B3255C6DC07034A12 /* RoomScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8108C8F0ACF6A7EB72D0117 /* RoomScreenCoordinator.swift */; }; - A88328D7E17F73AB64501B51 /* SwiftOGG in Frameworks */ = {isa = PBXBuildFile; productRef = 391D11F92DFC91666AA1503F /* SwiftOGG */; }; + A88328D7E17F73AB64501B51 /* SwiftSoup in Frameworks */ = {isa = PBXBuildFile; productRef = EDFB92E97D9AC4BA8540C18C /* SwiftSoup */; }; A8DB299C5C891EDAE74A22F4 /* UserLocationCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3F7482D5D1526E3399B00174 /* UserLocationCell.swift */; }; A8E324E700E596E36B0A311B /* BootDetectionManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F054DE7D47849687662C9D9 /* BootDetectionManager.swift */; }; A8FA7671948E3DF27F320026 /* BugReportFlowCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7367B3B9A8CAF902220F31D1 /* BugReportFlowCoordinator.swift */; }; A91D125414C3D9ABBABCF2F1 /* KZFileWatchers in Frameworks */ = {isa = PBXBuildFile; productRef = 6690850AA47ECED7E1CAB345 /* KZFileWatchers */; }; - A93661C962B12942C08864B6 /* Version in Frameworks */ = {isa = PBXBuildFile; productRef = A05AF81DDD14AD58CB0E1B9B /* Version */; }; + A93661C962B12942C08864B6 /* Emojibase in Frameworks */ = {isa = PBXBuildFile; productRef = C05729B1684C331F5FFE9232 /* Emojibase */; }; A950C95855C474F75B30CA7B /* PollFormScreenTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCDAB580109C09A6AA97AF7E /* PollFormScreenTests.swift */; }; A969147E0EEE0E27EE226570 /* MediaUploadPreviewScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47F29139BC2A804CE5E0757E /* MediaUploadPreviewScreenViewModel.swift */; }; A975D60EA49F6AF73308809F /* RoomMembersListScreenMemberCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = CC03209FDE8CE0810617BFFF /* RoomMembersListScreenMemberCell.swift */; }; @@ -1104,7 +1104,7 @@ AFE2AB612A1460E49578D746 /* JoinRoomScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3BDCCD2F6B405C14B9BCE94E /* JoinRoomScreenCoordinator.swift */; }; B004A08BE32F06832BF41CFC /* RoomDetailsScreenHook.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3674212CAB6C5143DD437AC4 /* RoomDetailsScreenHook.swift */; }; B04E9EB589CE99C3929E817A /* HomeScreenRecoveryKeyConfirmationBanner.swift in Sources */ = {isa = PBXBuildFile; fileRef = 05512FB13987D221B7205DE0 /* HomeScreenRecoveryKeyConfirmationBanner.swift */; }; - B0CB16349B96262AA65A04AF /* PostHog in Frameworks */ = {isa = PBXBuildFile; productRef = 4278261E147DB2DE5CFB7FC5 /* PostHog */; }; + B0CB16349B96262AA65A04AF /* SwiftState in Frameworks */ = {isa = PBXBuildFile; productRef = 9573B94B1C86C6DF751AF3FD /* SwiftState */; }; B1088417801A962BA861C13F /* RoomPowerLevel.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7E247E29C7A0E421DB839D7 /* RoomPowerLevel.swift */; }; B10F7D5C237417DA160F4603 /* LongPressWithFeedback.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D9F148717D74F73BE724434 /* LongPressWithFeedback.swift */; }; B13774779EA19FDD7A35A4A8 /* RoomRolesAndPermissionsScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C28B70BEFD3676F11D5D51F /* RoomRolesAndPermissionsScreenCoordinator.swift */; }; @@ -1257,7 +1257,6 @@ C9F5B48D15B9BCAE1F8D564E /* RoomNotificationModeProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1511766C534367700C8DD75 /* RoomNotificationModeProxy.swift */; }; CA12AE0DCD57D49CD96C699A /* WaveformCursorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FB9EABCA9348DFA27439A809 /* WaveformCursorView.swift */; }; CA2BFC5504AD91A028CFA45B /* AnalyticsConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA2397174D0DC3918A7A8A7B /* AnalyticsConfiguration.swift */; }; - CAEE1AF71E8C13756D572CF4 /* DSWaveformImageViews in Frameworks */ = {isa = PBXBuildFile; productRef = 2A4106A0A96DC4C273128AA5 /* DSWaveformImageViews */; }; CB07184D37D5D65327A5A693 /* AuthenticationFlowCoordinatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37F46CC4FD89ECF4CF26391A /* AuthenticationFlowCoordinatorTests.swift */; }; CB137BFB3E083C33E398A6CB /* DeviceKit in Frameworks */ = {isa = PBXBuildFile; productRef = A7CA6F33C553805035C3B114 /* DeviceKit */; }; CB498F4E27AA0545DCEF0F6F /* KeychainAccess in Frameworks */ = {isa = PBXBuildFile; productRef = 78A5A8DE1E2B09C978C7F3B0 /* KeychainAccess */; }; @@ -1408,14 +1407,14 @@ E4B07FF075C99D04D9AF792D /* AppLockSetupPINScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = B410B32B72C90BF94E481F33 /* AppLockSetupPINScreenModels.swift */; }; E4D261E237D5D45E6DF2D0F1 /* ManageRoomMemberSheetModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 787E84119E626E2F0E0BFBE8 /* ManageRoomMemberSheetModels.swift */; }; E52F1E00FBAABA703DA510F9 /* ContentScannerService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1EDFCC7391761D4DB2CC0D97 /* ContentScannerService.swift */; }; - E587A513B3E1EE9B56E9AB9C /* Flow in Frameworks */ = {isa = PBXBuildFile; productRef = 4D7E6BFC89715FC3CF0349D0 /* Flow */; }; + E587A513B3E1EE9B56E9AB9C /* DSWaveformImageViews in Frameworks */ = {isa = PBXBuildFile; productRef = 2A4106A0A96DC4C273128AA5 /* DSWaveformImageViews */; }; E58F1F3276E98A93F7D39219 /* RoomPollsHistoryScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = D1D8479BB704B7EF696F8ABE /* RoomPollsHistoryScreenCoordinator.swift */; }; E591742E509A2A009BF25F9D /* RoomEventStringBuilderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6AE5800184E93CD5E02C6543 /* RoomEventStringBuilderTests.swift */; }; E593841CEBFDDC5387A75EBA /* NotificationToneManagerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D148332A613AD27E9A2896C9 /* NotificationToneManagerTests.swift */; }; E5AB28123E2488F97E953AC0 /* CallNotificationRoomTimelineItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1ED17433ADC77287F8904F9 /* CallNotificationRoomTimelineItem.swift */; }; E5E43A0CA99AF5BA11B194A2 /* EncryptionSettingsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 57AD14D3ADADE8F6A10F9E88 /* EncryptionSettingsTests.swift */; }; E5F4C992845388B50BABACAA /* ServerSelectionScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB8BC4C791D0E88CFCF4E5DF /* ServerSelectionScreenCoordinator.swift */; }; - E673A5442A9A912DE6CE097A /* SwiftSoup in Frameworks */ = {isa = PBXBuildFile; productRef = EDFB92E97D9AC4BA8540C18C /* SwiftSoup */; }; + E673A5442A9A912DE6CE097A /* Flow in Frameworks */ = {isa = PBXBuildFile; productRef = 4D7E6BFC89715FC3CF0349D0 /* Flow */; }; E67418DACEDBC29E988E6ACD /* message.caf in Resources */ = {isa = PBXBuildFile; fileRef = ED482057AE39D5C6D9C5F3D8 /* message.caf */; }; E6A2F6E4795C1054025AACFF /* NotificationItemProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3AB32690C6CE5C62A86D6FA /* NotificationItemProxy.swift */; }; E6FA87F773424B27614B23E9 /* TimelineItemAccessibilityModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1DA7E93C2E148B96EF6A8500 /* TimelineItemAccessibilityModifier.swift */; }; @@ -1685,6 +1684,7 @@ C9C562D85999E436C7265AF1 /* MatrixRustSDK in Embed Frameworks */, 874704A177B0A6D78E037908 /* MapLibreInterface.framework in Embed Frameworks */, 5B2CE8FE14CB16F2FCAFC4B2 /* MapLibreShim.framework in Embed Frameworks */, + 7E429C44DD0C99870C6E9972 /* MapLibre.framework in Embed Frameworks */, ); name = "Embed Frameworks"; runOnlyForDeploymentPostprocessing = 0; @@ -1885,6 +1885,7 @@ 1D9F2E1FC5A0139571211CE8 /* SpaceHeaderTopicSheetView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SpaceHeaderTopicSheetView.swift; sourceTree = ""; }; 1DA7E93C2E148B96EF6A8500 /* TimelineItemAccessibilityModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineItemAccessibilityModifier.swift; sourceTree = ""; }; 1DB2FC2AA9A07EE792DF65CF /* NotificationPermissionsScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationPermissionsScreenModels.swift; sourceTree = ""; }; + 1DBA4A688563F86CC0D3E537 /* MapLibre.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = MapLibre.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 1DD2A058F3566FEEBA1D11B3 /* RoomSelectionScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomSelectionScreenViewModelProtocol.swift; sourceTree = ""; }; 1DE7969EBCAF078813E18EA1 /* RoomRolesAndPermissionsScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomRolesAndPermissionsScreenModels.swift; sourceTree = ""; }; 1DF8F7A3AD83D04C08D75E01 /* RoomDetailsEditScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomDetailsEditScreenViewModelProtocol.swift; sourceTree = ""; }; @@ -3307,20 +3308,18 @@ FC10228E73323BDC09526F97 /* LRUCache in Frameworks */, EAC6FE2CD4F50A43068ADCD8 /* Mantis in Frameworks */, 76D4D5312ED8E42B35C29FA3 /* MapLibreInterface.framework in Frameworks */, - A79634DCE52B5021F549E0E9 /* MapLibreShim.framework in Frameworks */, - 754602A7B2AAD443C4228ED4 /* MapLibre in Frameworks */, - B0CB16349B96262AA65A04AF /* PostHog in Frameworks */, - 36AD4DD4C798E22584ED3200 /* SwiftState in Frameworks */, - 36CD6E11B37396E14F032CB6 /* GZIP in Frameworks */, - A0D7E5BD0298A97DCBDCE40B /* Sentry in Frameworks */, - 44F0E1B576C7599DF8022071 /* SentrySwiftUI in Frameworks */, - A93661C962B12942C08864B6 /* Version in Frameworks */, - 37E47F5101C0C036289D3807 /* Emojibase in Frameworks */, - 4610C57A4785FFF5E67F0C6D /* WysiwygComposer in Frameworks */, - A88328D7E17F73AB64501B51 /* SwiftOGG in Frameworks */, - E673A5442A9A912DE6CE097A /* SwiftSoup in Frameworks */, - E587A513B3E1EE9B56E9AB9C /* Flow in Frameworks */, - CAEE1AF71E8C13756D572CF4 /* DSWaveformImageViews in Frameworks */, + 754602A7B2AAD443C4228ED4 /* PostHog in Frameworks */, + B0CB16349B96262AA65A04AF /* SwiftState in Frameworks */, + 36AD4DD4C798E22584ED3200 /* GZIP in Frameworks */, + 36CD6E11B37396E14F032CB6 /* Sentry in Frameworks */, + A0D7E5BD0298A97DCBDCE40B /* SentrySwiftUI in Frameworks */, + 44F0E1B576C7599DF8022071 /* Version in Frameworks */, + A93661C962B12942C08864B6 /* Emojibase in Frameworks */, + 37E47F5101C0C036289D3807 /* WysiwygComposer in Frameworks */, + 4610C57A4785FFF5E67F0C6D /* SwiftOGG in Frameworks */, + A88328D7E17F73AB64501B51 /* SwiftSoup in Frameworks */, + E673A5442A9A912DE6CE097A /* Flow in Frameworks */, + E587A513B3E1EE9B56E9AB9C /* DSWaveformImageViews in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -4322,6 +4321,7 @@ 2197234282B4BC0CE79AAC74 /* Secrets */, 823ED0EC3F1B6CF47D284011 /* Tools */, 9413F680ECDFB2B0DDB0DEF2 /* Packages */, + DE77D5FC2A361306E4440687 /* Frameworks */, 681566846AF307E9BA4C72C6 /* Products */, ); sourceTree = ""; @@ -6898,6 +6898,14 @@ path = View; sourceTree = ""; }; + DE77D5FC2A361306E4440687 /* Frameworks */ = { + isa = PBXGroup; + children = ( + 1DBA4A688563F86CC0D3E537 /* MapLibre.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; DF10E31B5AA21015971C3FAD /* View */ = { isa = PBXGroup; children = ( @@ -7568,7 +7576,6 @@ 1A6B622CCFDEFB92D9CF1CA5 /* LoremSwiftum */, 1081D3630AAD3ACEDDEC3A98 /* LRUCache */, 8C30177487BBB5D000E4DDDA /* Mantis */, - ED146E565C4CB792FF2F37EA /* MapLibre */, 4278261E147DB2DE5CFB7FC5 /* PostHog */, 9573B94B1C86C6DF751AF3FD /* SwiftState */, 997C7385E1A07E061D7E2100 /* GZIP */, @@ -11213,11 +11220,6 @@ isa = XCSwiftPackageProductDependency; productName = Macros; }; - ED146E565C4CB792FF2F37EA /* MapLibre */ = { - isa = XCSwiftPackageProductDependency; - package = 0CBF57301AA172C21F76CE86 /* XCRemoteSwiftPackageReference "maplibre-gl-native-distribution" */; - productName = MapLibre; - }; EDFB92E97D9AC4BA8540C18C /* SwiftSoup */ = { isa = XCSwiftPackageProductDependency; package = AB8E808A59756170682BEC20 /* XCRemoteSwiftPackageReference "SwiftSoup" */; diff --git a/ElementX/MapLibre/MapLibreShim/Sources/MapLibreShim.swift b/ElementX/MapLibre/MapLibreShim/Sources/MapLibreShim.swift index 021df86b7..642d1cc7b 100644 --- a/ElementX/MapLibre/MapLibreShim/Sources/MapLibreShim.swift +++ b/ElementX/MapLibre/MapLibreShim/Sources/MapLibreShim.swift @@ -9,7 +9,9 @@ import MapLibre import MapLibreInterface import SwiftUI -/// The shim's entry point, vending the interactive map view to the app. +/// The shim's entry point: the app looks this class up by name through the ObjC runtime +/// after `dlopen`-ing the framework, keeping MapLibre off the app's launch path. +@objc(MapLibreShim) public final class MapLibreShim: NSObject, MapLibreShimProtocol { public func configureLogging(_ handler: @escaping @Sendable (MapLogSeverity, String) -> Void) { MLNLoggingConfiguration.shared.loggingLevel = .debug diff --git a/ElementX/Sources/Other/MapLibre/MapLibreMapView.swift b/ElementX/Sources/Other/MapLibre/MapLibreMapView.swift index 32241ba83..c5e1cbc73 100644 --- a/ElementX/Sources/Other/MapLibre/MapLibreMapView.swift +++ b/ElementX/Sources/Other/MapLibre/MapLibreMapView.swift @@ -9,33 +9,14 @@ import Compound import CoreLocation import MapLibreInterface -import MapLibreShim import SwiftUI -/// The interactive map, rendered by the `MapLibreShim` framework. +/// The interactive map, rendered by the `MapLibreShim` framework which is loaded on first use. +/// +/// The app deliberately doesn't link MapLibre: dyld would otherwise load it, run its static +/// initialisers and register its classes on every single launch, for a map that is only shown +/// on the location screens. struct MapLibreMapView: View { - /// Built once, so that MapLibre's logging is only configured once. - private static let shim: MapLibreShimProtocol = { - let shim = MapLibreShim() - - shim.configureLogging { severity, message in - switch severity { - case .error: - MXLog.error(message) - case .warning: - MXLog.warning(message) - case .info: - MXLog.info(message) - case .debug: - MXLog.debug(message) - case .verbose: - MXLog.verbose(message) - } - } - - return shim - }() - struct Options { /// the final zoom level used when the first user location emit let zoomLevel: Double @@ -77,7 +58,13 @@ struct MapLibreMapView: View { var userDidPan: (() -> Void)? var body: some View { - Self.shim.makeMapView(configuration: configuration) + if let shim = MapLibreLoader.shim { + shim.makeMapView(configuration: configuration) + } else { + // The shim failed to load, surface it the same way as a map loading failure. + Color.clear + .onAppear { error = .failedLoadingMap } + } } /// The configuration for the shim, with the app-only pieces (marker views, tint, styles) @@ -106,3 +93,41 @@ struct MapLibreMapView: View { userDidPan: userDidPan) } } + +/// Loads the `MapLibreShim` framework on first use. +enum MapLibreLoader { + static let shim: MapLibreShimProtocol? = { + guard let frameworksPath = Bundle.main.privateFrameworksPath else { + MXLog.error("Missing frameworks path.") + return nil + } + + guard dlopen("\(frameworksPath)/MapLibreShim.framework/MapLibreShim", RTLD_NOW) != nil else { + MXLog.error("Failed loading MapLibreShim: \(dlerror().map { String(cString: $0) } ?? "unknown error")") + return nil + } + + guard let shimClass = NSClassFromString("MapLibreShim") as? NSObject.Type, + let shim = shimClass.init() as? MapLibreShimProtocol else { + MXLog.error("Failed resolving MapLibreShim.") + return nil + } + + shim.configureLogging { severity, message in + switch severity { + case .error: + MXLog.error(message) + case .warning: + MXLog.warning(message) + case .info: + MXLog.info(message) + case .debug: + MXLog.debug(message) + case .verbose: + MXLog.verbose(message) + } + } + + return shim + }() +} diff --git a/ElementX/SupportingFiles/target.yml b/ElementX/SupportingFiles/target.yml index 893d11f63..c3c04d063 100644 --- a/ElementX/SupportingFiles/target.yml +++ b/ElementX/SupportingFiles/target.yml @@ -250,9 +250,16 @@ targets: - package: Mantis - target: MapLibreInterface embed: true + # Embedded but not linked, dlopen'd on first map use - target: MapLibreShim + link: false + embed: true + # The app just embeds the framework so it's in the bundle for the shim's load + # command to resolve against. + - framework: ../../MapLibre.framework + implicit: true + link: false embed: true - - package: MapLibre - package: PostHog - package: SwiftState - package: GZIP