iosswiftuiimagephasset

iOS App Crashes from Memory Issue when Generating Images from PHAssets


I'm trying to get some photos (PHAsset's) from a user's Photos library, and convert them to UIImage's.

Here's the code for fetching the assets from the library:

     func fetchPhotos() {
        
        let fetchOptions = PHFetchOptions()
        fetchOptions.predicate = NSPredicate(format: "(mediaSubtype & %d) != 0", PHAssetMediaSubtype.photoScreenshot.rawValue)
        
        //Fetching Screen Shots
        fetchedResult = PHAsset.fetchAssets(with: .image, options: fetchOptions)
        
        let f: FetchResult = FetchResult(fetchedResult!)
        var fetchCount = 0
        
        for i in f {
            fetchCount += 1
            print("Fetch count \(fetchCount)")
            let img = i.getAssetThumbnail()
            testImages.append(img)
        }
    }

Below is the code for getting a UIImage out of a PHAsset:

extension PHAsset {
    func getAssetThumbnail() -> UIImage {
        let manager = PHImageManager.default()
        let option = PHImageRequestOptions()
        var thumbnail = UIImage()
        option.isSynchronous = true
        manager.requestImage(for: self,
                             targetSize: CGSize(width: self.pixelWidth, height: self.pixelHeight),
                             contentMode: .aspectFit,
                             options: option,
                             resultHandler: {(result, info) -> Void in
                                thumbnail = result!
                             })
            return thumbnail
    }
}

I'm currently getting this crash output when trying to run the code:

Details

The app “AppClassificationDemo” on Nick’s iPhone quit unexpectedly.
Domain: IDEDebugSessionErrorDomain
Code: 11
Failure Reason: Message from debugger: Terminated due to memory issue
User Info: {
    DVTErrorCreationDateKey = "2022-11-14 22:19:19 +0000";
    IDERunOperationFailingWorker = DBGLLDBLauncher;
}
--

Analytics Event: com.apple.dt.IDERunOperationWorkerFinished : {
    "device_model" = "iPhone14,2";
    "device_osBuild" = "16.1.1 (20B101)";
    "device_platform" = "com.apple.platform.iphoneos";
    "launchSession_schemeCommand" = Run;
    "launchSession_state" = 2;
    "launchSession_targetArch" = arm64;
    "operation_duration_ms" = 6369;
    "operation_errorCode" = 11;
    "operation_errorDomain" = IDEDebugSessionErrorDomain;
    "operation_errorWorker" = DBGLLDBLauncher;
    "operation_name" = IDEiPhoneRunOperationWorkerGroup;
    "param_consoleMode" = 0;
    "param_debugger_attachToExtensions" = 0;
    "param_debugger_attachToXPC" = 1;
    "param_debugger_type" = 5;
    "param_destination_isProxy" = 0;
    "param_destination_platform" = "com.apple.platform.iphoneos";
    "param_diag_MainThreadChecker_stopOnIssue" = 0;
    "param_diag_MallocStackLogging_enableDuringAttach" = 0;
    "param_diag_MallocStackLogging_enableForXPC" = 1;
    "param_diag_allowLocationSimulation" = 1;
    "param_diag_checker_tpc_enable" = 1;
    "param_diag_gpu_frameCapture_enable" = 0;
    "param_diag_gpu_shaderValidation_enable" = 0;
    "param_diag_gpu_validation_enable" = 0;
    "param_diag_memoryGraphOnResourceException" = 0;
    "param_diag_queueDebugging_enable" = 1;
    "param_diag_runtimeProfile_generate" = 0;
    "param_diag_sanitizer_asan_enable" = 0;
    "param_diag_sanitizer_tsan_enable" = 0;
    "param_diag_sanitizer_tsan_stopOnIssue" = 0;
    "param_diag_sanitizer_ubsan_stopOnIssue" = 0;
    "param_diag_showNonLocalizedStrings" = 0;
    "param_diag_viewDebugging_enabled" = 1;
    "param_diag_viewDebugging_insertDylibOnLaunch" = 1;
    "param_install_style" = 0;
    "param_launcher_UID" = 2;
    "param_launcher_allowDeviceSensorReplayData" = 0;
    "param_launcher_kind" = 0;
    "param_launcher_style" = 0;
    "param_launcher_substyle" = 0;
    "param_runnable_appExtensionHostRunMode" = 0;
    "param_runnable_productType" = "com.apple.product-type.application";
    "param_runnable_type" = 2;
    "param_testing_launchedForTesting" = 0;
    "param_testing_suppressSimulatorApp" = 0;
    "param_testing_usingCLI" = 0;
    "sdk_canonicalName" = "iphoneos16.1";
    "sdk_osVersion" = "16.1";
    "sdk_variant" = iphoneos;
}
--


System Information

macOS Version 13.0.1 (Build 22A400)
Xcode 14.1 (21534.1) (Build 14B47b)
Timestamp: 2022-11-14T14:19:19-08:00

I'm fairly certain it's do the size of the image thumbnail being generated; when I set width and height to both be 100, the processing works as expected.

When changing it self.pixelWidth and self.pixelHeight, the app crashes.


Solution

  • Don't try to convert the PHAsset to UIImage and store in array. Because UIImage will use the full memory size of the image. So too many images in an array will cause a memory leak.

    Hence just fetch the required PHAsset on demand and request for the UIImage when needed. Please see an example below.

    func loadImageAt(_ index:Int) -> UIImage?{
                
        let asset = fetchResult.object(at: index)
        
        let manager = PHImageManager.default()
        let option = PHImageRequestOptions()
        
        var thumbnail:UIImage?
        
        option.isSynchronous = true
        manager.requestImage(for: asset,
                             targetSize: CGSize(width: asset.pixelWidth, height: asset.pixelHeight),
                             contentMode: .aspectFit,
                             options: option,
                             resultHandler: {(result, info) -> Void in
            
            thumbnail = result
        })
        
        return thumbnail
    }
    

    Also I will suggest you to use the asynchronous image request to avoid the UI performance issues.

    Hope this helps!