This is a working version without significant leak.

import photos import time import concurrent.futures import objc_util import ui @objc_util.on_main_thread def update(image_view, ui_image): image_view.image = ui_image def loop_photos(): all_photos = photos.get_assets() while True: for p in all_photos: with objc_util.autoreleasepool(): ui_image = p.get_ui_image(size=(300,300)) #image_data = p.get_image_data() update(v['img'], ui_image) time.sleep(.5) time.sleep(5) v = ui.View() v.add_subview(ui.ImageView(name='img')) v.present() with concurrent.futures.ThreadPoolExecutor(max_workers=1) as executor: executor.submit(loop_photos)

Thanks very much for all the suggestions. I'm grateful for the tip about threading and will try to incorporate it into the rest of the project but the thing that does the lifting here is the autoreleasepool context manager; with just this addition and removing the threading it works just fine.