diff --git a/src/ARMeilleure/CodeGen/Arm64/HardwareCapabilities.cs b/src/ARMeilleure/CodeGen/Arm64/HardwareCapabilities.cs
index 86afc2b4d..374f4746b 100644
--- a/src/ARMeilleure/CodeGen/Arm64/HardwareCapabilities.cs
+++ b/src/ARMeilleure/CodeGen/Arm64/HardwareCapabilities.cs
@@ -20,7 +20,7 @@ namespace ARMeilleure.CodeGen.Arm64
                 LinuxFeatureInfoHwCap2 = (LinuxFeatureFlagsHwCap2)getauxval(AT_HWCAP2);
             }
 
-            if (OperatingSystem.IsMacOS())
+            if (OperatingSystem.IsMacOS() || OperatingSystem.IsIOS())
             {
                 for (int i = 0; i < _sysctlNames.Length; i++)
                 {
@@ -130,6 +130,7 @@ namespace ARMeilleure.CodeGen.Arm64
         private static unsafe partial int sysctlbyname([MarshalAs(UnmanagedType.LPStr)] string name, out int oldValue, ref ulong oldSize, IntPtr newValue, ulong newValueSize);
 
         [SupportedOSPlatform("macos")]
+        [SupportedOSPlatform("ios")]
         private static bool CheckSysctlName(string name)
         {
             ulong size = sizeof(int);
diff --git a/src/ARMeilleure/CodeGen/CompiledFunction.cs b/src/ARMeilleure/CodeGen/CompiledFunction.cs
index 3844cbfc9..485c85d16 100644
--- a/src/ARMeilleure/CodeGen/CompiledFunction.cs
+++ b/src/ARMeilleure/CodeGen/CompiledFunction.cs
@@ -58,9 +58,9 @@ namespace ARMeilleure.CodeGen
         /// <typeparam name="T">Type of delegate</typeparam>
         /// <param name="codePointer">Pointer to the function code in memory</param>
         /// <returns>A delegate of type <typeparamref name="T"/> pointing to the mapped function</returns>
-        public T MapWithPointer<T>(out IntPtr codePointer)
+        public T MapWithPointer<T>(out IntPtr codePointer, bool deferProtect = false)
         {
-            codePointer = JitCache.Map(this);
+            codePointer = JitCache.Map(this, deferProtect);
 
             return Marshal.GetDelegateForFunctionPointer<T>(codePointer);
         }
diff --git a/src/ARMeilleure/Native/JitSupportDarwin.cs b/src/ARMeilleure/Native/JitSupportDarwin.cs
index ed347b9cf..bf94514ed 100644
--- a/src/ARMeilleure/Native/JitSupportDarwin.cs
+++ b/src/ARMeilleure/Native/JitSupportDarwin.cs
@@ -5,9 +5,41 @@ using System.Runtime.Versioning;
 namespace ARMeilleure.Native
 {
     [SupportedOSPlatform("macos")]
+    [SupportedOSPlatform("ios")]
     internal static partial class JitSupportDarwin
     {
         [LibraryImport("libarmeilleure-jitsupport", EntryPoint = "armeilleure_jit_memcpy")]
         public static partial void Copy(IntPtr dst, IntPtr src, ulong n);
     }
+
+    [SupportedOSPlatform("ios")]
+    internal static partial class JitSupportDarwinAot
+    {
+        [LibraryImport("pthread", EntryPoint = "pthread_jit_write_protect_np")]
+        private static partial void pthread_jit_write_protect_np(int enabled);
+
+        [LibraryImport("libc", EntryPoint = "sys_icache_invalidate")]
+        private static partial void sys_icache_invalidate(IntPtr start, IntPtr length);
+
+        public static unsafe void Copy(IntPtr dst, IntPtr src, ulong n) {
+            // When NativeAOT is in use, we can toggle per-thread write protection without worrying about breaking .NET code.
+
+            //pthread_jit_write_protect_np(0);
+            
+            var srcSpan = new Span<byte>(src.ToPointer(), (int)n);
+            var dstSpan = new Span<byte>(dst.ToPointer(), (int)n);
+            srcSpan.CopyTo(dstSpan);
+
+            //pthread_jit_write_protect_np(1);
+
+            // Ensure that the instruction cache for this range is invalidated.
+            sys_icache_invalidate(dst, (IntPtr)n);
+        }
+
+        public static unsafe void Invalidate(IntPtr dst, ulong n)
+        {
+            // Ensure that the instruction cache for this range is invalidated.
+            sys_icache_invalidate(dst, (IntPtr)n);
+        }
+    }
 }
diff --git a/src/ARMeilleure/Signal/NativeSignalHandler.cs b/src/ARMeilleure/Signal/NativeSignalHandler.cs
index 31ec16cb1..d0d1aab7c 100644
--- a/src/ARMeilleure/Signal/NativeSignalHandler.cs
+++ b/src/ARMeilleure/Signal/NativeSignalHandler.cs
@@ -112,7 +112,7 @@ namespace ARMeilleure.Signal
 
                 ref SignalHandlerConfig config = ref GetConfigRef();
 
-                if (OperatingSystem.IsLinux() || OperatingSystem.IsMacOS())
+                if (OperatingSystem.IsLinux() || OperatingSystem.IsMacOS() || OperatingSystem.IsIOS())
                 {
                     _signalHandlerPtr = Marshal.GetFunctionPointerForDelegate(GenerateUnixSignalHandler(_handlerConfig));
 
@@ -252,13 +252,13 @@ namespace ARMeilleure.Signal
 
         private static Operand GenerateUnixFaultAddress(EmitterContext context, Operand sigInfoPtr)
         {
-            ulong structAddressOffset = OperatingSystem.IsMacOS() ? 24ul : 16ul; // si_addr
+            ulong structAddressOffset = (OperatingSystem.IsMacOS() || OperatingSystem.IsIOS()) ? 24ul : 16ul; // si_addr
             return context.Load(OperandType.I64, context.Add(sigInfoPtr, Const(structAddressOffset)));
         }
 
         private static Operand GenerateUnixWriteFlag(EmitterContext context, Operand ucontextPtr)
         {
-            if (OperatingSystem.IsMacOS())
+            if (OperatingSystem.IsMacOS() || OperatingSystem.IsIOS())
             {
                 const ulong McontextOffset = 48; // uc_mcontext
                 Operand ctxPtr = context.Load(OperandType.I64, context.Add(ucontextPtr, Const(McontextOffset)));
diff --git a/src/ARMeilleure/Signal/UnixSignalHandlerRegistration.cs b/src/ARMeilleure/Signal/UnixSignalHandlerRegistration.cs
index 70e9f2204..433aab636 100644
--- a/src/ARMeilleure/Signal/UnixSignalHandlerRegistration.cs
+++ b/src/ARMeilleure/Signal/UnixSignalHandlerRegistration.cs
@@ -62,7 +62,7 @@ namespace ARMeilleure.Signal
                 throw new InvalidOperationException($"Could not register SIGSEGV sigaction. Error: {result}");
             }
 
-            if (OperatingSystem.IsMacOS())
+            if (OperatingSystem.IsMacOS() || OperatingSystem.IsIOS())
             {
                 result = sigaction(SIGBUS, ref sig, out _);
 
@@ -77,7 +77,7 @@ namespace ARMeilleure.Signal
 
         public static bool RestoreExceptionHandler(SigAction oldAction)
         {
-            return sigaction(SIGSEGV, ref oldAction, out SigAction _) == 0 && (!OperatingSystem.IsMacOS() || sigaction(SIGBUS, ref oldAction, out SigAction _) == 0);
+            return sigaction(SIGSEGV, ref oldAction, out SigAction _) == 0 && (!OperatingSystem.IsMacOS() || OperatingSystem.IsIOS() || sigaction(SIGBUS, ref oldAction, out SigAction _) == 0);
         }
     }
 }
diff --git a/src/ARMeilleure/Translation/Cache/CacheMemoryAllocator.cs b/src/ARMeilleure/Translation/Cache/CacheMemoryAllocator.cs
index f36bf7a3d..a1bd3933a 100644
--- a/src/ARMeilleure/Translation/Cache/CacheMemoryAllocator.cs
+++ b/src/ARMeilleure/Translation/Cache/CacheMemoryAllocator.cs
@@ -30,21 +30,26 @@ namespace ARMeilleure.Translation.Cache
             _blocks.Add(new MemoryBlock(0, capacity));
         }
 
-        public int Allocate(int size)
+        public int Allocate(ref int size, int alignment)
         {
+            int alignM1 = alignment - 1;
             for (int i = 0; i < _blocks.Count; i++)
             {
                 MemoryBlock block = _blocks[i];
+                int misAlignment = ((block.Offset + alignM1) & (~alignM1)) - block.Offset;
+                int alignedSize = size + misAlignment;
 
-                if (block.Size > size)
+                if (block.Size > alignedSize)
                 {
-                    _blocks[i] = new MemoryBlock(block.Offset + size, block.Size - size);
-                    return block.Offset;
+                    size = alignedSize;
+                    _blocks[i] = new MemoryBlock(block.Offset + alignedSize, block.Size - alignedSize);
+                    return block.Offset + misAlignment;
                 }
-                else if (block.Size == size)
+                else if (block.Size == alignedSize)
                 {
+                    size = alignedSize;
                     _blocks.RemoveAt(i);
-                    return block.Offset;
+                    return block.Offset + misAlignment;
                 }
             }
 
diff --git a/src/ARMeilleure/Translation/Cache/JitCache.cs b/src/ARMeilleure/Translation/Cache/JitCache.cs
index e2b5e2d10..db4be66e5 100644
--- a/src/ARMeilleure/Translation/Cache/JitCache.cs
+++ b/src/ARMeilleure/Translation/Cache/JitCache.cs
@@ -4,6 +4,7 @@ using ARMeilleure.Memory;
 using ARMeilleure.Native;
 using Ryujinx.Memory;
 using System;
+using System.Collections.Concurrent;
 using System.Collections.Generic;
 using System.Diagnostics;
 using System.Runtime.InteropServices;
@@ -18,6 +19,7 @@ namespace ARMeilleure.Translation.Cache
 
         private const int CodeAlignment = 4; // Bytes.
         private const int CacheSize = 2047 * 1024 * 1024;
+        private const int CacheSizeIOS = 512 * 1024 * 1024;
 
         private static ReservedRegion _jitRegion;
         private static JitCacheInvalidation _jitCacheInvalidator;
@@ -47,9 +49,9 @@ namespace ARMeilleure.Translation.Cache
                     return;
                 }
 
-                _jitRegion = new ReservedRegion(allocator, CacheSize);
+                _jitRegion = new ReservedRegion(allocator, (ulong)(OperatingSystem.IsIOS() ? CacheSizeIOS : CacheSize));
 
-                if (!OperatingSystem.IsWindows() && !OperatingSystem.IsMacOS())
+                if (!OperatingSystem.IsWindows() && !OperatingSystem.IsMacOS() && !OperatingSystem.IsIOS())
                 {
                     _jitCacheInvalidator = new JitCacheInvalidation(allocator);
                 }
@@ -65,7 +67,17 @@ namespace ARMeilleure.Translation.Cache
             }
         }
 
-        public static IntPtr Map(CompiledFunction func)
+        static ConcurrentQueue<(int funcOffset, int length)> _deferredRxProtect = new();
+
+        public static void RunDeferredRxProtects()
+        {
+            while (_deferredRxProtect.TryDequeue(out var result))
+            {
+                ReprotectAsExecutable(result.funcOffset, result.length);
+            }
+        }  
+
+        public static IntPtr Map(CompiledFunction func, bool deferProtect)
         {
             byte[] code = func.Code;
 
@@ -73,11 +85,25 @@ namespace ARMeilleure.Translation.Cache
             {
                 Debug.Assert(_initialized);
 
-                int funcOffset = Allocate(code.Length);
+                int funcOffset = Allocate(code.Length, deferProtect);
 
                 IntPtr funcPtr = _jitRegion.Pointer + funcOffset;
 
-                if (OperatingSystem.IsMacOS() && RuntimeInformation.ProcessArchitecture == Architecture.Arm64)
+                if (OperatingSystem.IsIOS())
+                {
+                    Marshal.Copy(code, 0, funcPtr, code.Length);
+                    if (deferProtect)
+                    {
+                        _deferredRxProtect.Enqueue((funcOffset, code.Length));
+                    }
+                    else
+                    {
+                        ReprotectAsExecutable(funcOffset, code.Length);
+
+                        JitSupportDarwinAot.Invalidate(funcPtr, (ulong)code.Length);
+                    }
+                }
+                else if (OperatingSystem.IsMacOS()&& RuntimeInformation.ProcessArchitecture == Architecture.Arm64)
                 {
                     unsafe
                     {
@@ -111,6 +137,11 @@ namespace ARMeilleure.Translation.Cache
 
         public static void Unmap(IntPtr pointer)
         {
+            if (OperatingSystem.IsIOS())
+            {
+                return;
+            }
+
             lock (_lock)
             {
                 Debug.Assert(_initialized);
@@ -145,11 +176,20 @@ namespace ARMeilleure.Translation.Cache
             _jitRegion.Block.MapAsRx((ulong)regionStart, (ulong)(regionEnd - regionStart));
         }
 
-        private static int Allocate(int codeSize)
+        private static int Allocate(int codeSize, bool deferProtect = false)
         {
-            codeSize = AlignCodeSize(codeSize);
+            codeSize = AlignCodeSize(codeSize, deferProtect);
 
-            int allocOffset = _cacheAllocator.Allocate(codeSize);
+            int alignment = CodeAlignment;
+
+            if (OperatingSystem.IsIOS() && !deferProtect)
+            {
+                alignment = 0x4000;
+            }
+
+            int allocOffset = _cacheAllocator.Allocate(ref codeSize, alignment);
+
+            Console.WriteLine($"{allocOffset:x8}: {codeSize:x8} {alignment:x8}");
 
             if (allocOffset < 0)
             {
@@ -161,9 +201,16 @@ namespace ARMeilleure.Translation.Cache
             return allocOffset;
         }
 
-        private static int AlignCodeSize(int codeSize)
+        private static int AlignCodeSize(int codeSize, bool deferProtect = false)
         {
-            return checked(codeSize + (CodeAlignment - 1)) & ~(CodeAlignment - 1);
+            int alignment = CodeAlignment;
+
+            if (OperatingSystem.IsIOS() && !deferProtect)
+            {
+                alignment = 0x4000;
+            }
+
+            return checked(codeSize + (alignment - 1)) & ~(alignment - 1);
         }
 
         private static void Add(int offset, int size, UnwindInfo unwindInfo)
diff --git a/src/ARMeilleure/Translation/PTC/Ptc.cs b/src/ARMeilleure/Translation/PTC/Ptc.cs
index 6f6dfcadf..5ed27927a 100644
--- a/src/ARMeilleure/Translation/PTC/Ptc.cs
+++ b/src/ARMeilleure/Translation/PTC/Ptc.cs
@@ -3,6 +3,7 @@ using ARMeilleure.CodeGen.Linking;
 using ARMeilleure.CodeGen.Unwinding;
 using ARMeilleure.Common;
 using ARMeilleure.Memory;
+using ARMeilleure.Translation.Cache;
 using Ryujinx.Common;
 using Ryujinx.Common.Configuration;
 using Ryujinx.Common.Logging;
@@ -744,7 +745,7 @@ namespace ARMeilleure.Translation.PTC
             bool highCq)
         {
             var cFunc = new CompiledFunction(code, unwindInfo, RelocInfo.Empty);
-            var gFunc = cFunc.MapWithPointer<GuestFunction>(out IntPtr gFuncPointer);
+            var gFunc = cFunc.MapWithPointer<GuestFunction>(out IntPtr gFuncPointer, true);
 
             return new TranslatedFunction(gFunc, gFuncPointer, callCounter, guestSize, highCq);
         }
@@ -826,7 +827,7 @@ namespace ARMeilleure.Translation.PTC
 
                     Debug.Assert(Profiler.IsAddressInStaticCodeRange(address));
 
-                    TranslatedFunction func = translator.Translate(address, item.funcProfile.Mode, item.funcProfile.HighCq);
+                    TranslatedFunction func = translator.Translate(address, item.funcProfile.Mode, item.funcProfile.HighCq, deferProtect: true);
 
                     bool isAddressUnique = translator.Functions.TryAdd(address, func.GuestSize, func);
 
@@ -1004,6 +1005,7 @@ namespace ARMeilleure.Translation.PTC
             osPlatform |= (OperatingSystem.IsLinux()   ? 1u : 0u) << 1;
             osPlatform |= (OperatingSystem.IsMacOS()   ? 1u : 0u) << 2;
             osPlatform |= (OperatingSystem.IsWindows() ? 1u : 0u) << 3;
+            osPlatform |= (OperatingSystem.IsIOS()     ? 1u : 0u) << 4;
 #pragma warning restore IDE0055
 
             return osPlatform;
diff --git a/src/ARMeilleure/Translation/Translator.cs b/src/ARMeilleure/Translation/Translator.cs
index dc18038ba..88656b68f 100644
--- a/src/ARMeilleure/Translation/Translator.cs
+++ b/src/ARMeilleure/Translation/Translator.cs
@@ -112,6 +112,8 @@ namespace ARMeilleure.Translation
                     Debug.Assert(Functions.Count == 0);
                     _ptc.LoadTranslations(this);
                     _ptc.MakeAndSaveTranslations(this);
+
+                    JitCache.RunDeferredRxProtects();
                 }
 
                 _ptc.Profiler.Start();
@@ -250,7 +252,7 @@ namespace ARMeilleure.Translation
             }
         }
 
-        internal TranslatedFunction Translate(ulong address, ExecutionMode mode, bool highCq, bool singleStep = false)
+        internal TranslatedFunction Translate(ulong address, ExecutionMode mode, bool highCq, bool singleStep = false, bool deferProtect = false)
         {
             var context = new ArmEmitterContext(
                 Memory,
@@ -308,7 +310,7 @@ namespace ARMeilleure.Translation
                 _ptc.WriteCompiledFunction(address, funcSize, hash, highCq, compiledFunc);
             }
 
-            GuestFunction func = compiledFunc.MapWithPointer<GuestFunction>(out IntPtr funcPointer);
+            GuestFunction func = compiledFunc.MapWithPointer<GuestFunction>(out IntPtr funcPointer, deferProtect);
 
             Allocators.ResetAll();
 
diff --git a/src/Ryujinx.Audio.Backends.SDL2/SDL2HardwareDeviceDriver.cs b/src/Ryujinx.Audio.Backends.SDL2/SDL2HardwareDeviceDriver.cs
index b83e63dbc..58137bb38 100644
--- a/src/Ryujinx.Audio.Backends.SDL2/SDL2HardwareDeviceDriver.cs
+++ b/src/Ryujinx.Audio.Backends.SDL2/SDL2HardwareDeviceDriver.cs
@@ -23,7 +23,7 @@ namespace Ryujinx.Audio.Backends.SDL2
         // TODO: Add this to SDL2-CS
         // NOTE: We use a DllImport here because of marshaling issue for spec.
 #pragma warning disable SYSLIB1054
-        [DllImport("SDL2")]
+        [DllImport("SDL2.framework/SDL2")]
         private static extern int SDL_GetDefaultAudioInfo(IntPtr name, out SDL_AudioSpec spec, int isCapture);
 #pragma warning restore SYSLIB1054
 
diff --git a/src/Ryujinx.Common/Configuration/AppDataManager.cs b/src/Ryujinx.Common/Configuration/AppDataManager.cs
index 2b4a594d3..010b39dbf 100644
--- a/src/Ryujinx.Common/Configuration/AppDataManager.cs
+++ b/src/Ryujinx.Common/Configuration/AppDataManager.cs
@@ -50,6 +50,10 @@ namespace Ryujinx.Common.Configuration
             {
                 appDataPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), "Library", "Application Support");
             }
+            else if (OperatingSystem.IsIOS())
+            {
+                appDataPath = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);
+            }
             else
             {
                 appDataPath = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData);
diff --git a/src/Ryujinx.Common/Logging/Targets/ConsoleLogTarget.cs b/src/Ryujinx.Common/Logging/Targets/ConsoleLogTarget.cs
index 42ba00cd5..effc8f507 100644
--- a/src/Ryujinx.Common/Logging/Targets/ConsoleLogTarget.cs
+++ b/src/Ryujinx.Common/Logging/Targets/ConsoleLogTarget.cs
@@ -30,15 +30,26 @@ namespace Ryujinx.Common.Logging.Targets
 
         public void Log(object sender, LogEventArgs args)
         {
-            Console.ForegroundColor = GetLogColor(args.Level);
-            Console.WriteLine(_formatter.Format(args));
-            Console.ResetColor();
+            if (OperatingSystem.IsIOS())
+            {
+                Console.WriteLine(_formatter.Format(args));
+            }
+            else
+            {
+                Console.ForegroundColor = GetLogColor(args.Level);
+                Console.WriteLine(_formatter.Format(args));
+                Console.ResetColor();
+            }
         }
 
         public void Dispose()
         {
             GC.SuppressFinalize(this);
-            Console.ResetColor();
+
+            if (!OperatingSystem.IsIOS())
+            {
+                Console.ResetColor();
+            }
         }
     }
 }
diff --git a/src/Ryujinx.Common/ReleaseInformation.cs b/src/Ryujinx.Common/ReleaseInformation.cs
index ab65a98f3..bf68cbbc8 100644
--- a/src/Ryujinx.Common/ReleaseInformation.cs
+++ b/src/Ryujinx.Common/ReleaseInformation.cs
@@ -30,6 +30,11 @@ namespace Ryujinx.Common
 
         public static string GetVersion()
         {
+            if (OperatingSystem.IsIOS())
+            {
+                return "ios";
+            }
+
             if (IsValid())
             {
                 return BuildVersion;
@@ -46,7 +51,7 @@ namespace Ryujinx.Common
 #else
         public static string GetBaseApplicationDirectory()
         {
-            if (IsFlatHubBuild() || OperatingSystem.IsMacOS())
+            if (IsFlatHubBuild() || OperatingSystem.IsMacOS() || OperatingSystem.IsIOS())
             {
                 return AppDataManager.BaseDirPath;
             }
diff --git a/src/Ryujinx.Common/SystemInterop/StdErrAdapter.cs b/src/Ryujinx.Common/SystemInterop/StdErrAdapter.cs
index a04c404d8..1cf7a1928 100644
--- a/src/Ryujinx.Common/SystemInterop/StdErrAdapter.cs
+++ b/src/Ryujinx.Common/SystemInterop/StdErrAdapter.cs
@@ -19,7 +19,7 @@ namespace Ryujinx.Common.SystemInterop
 
         public StdErrAdapter()
         {
-            if (OperatingSystem.IsLinux() || OperatingSystem.IsMacOS())
+            if (OperatingSystem.IsLinux() || OperatingSystem.IsMacOS()) // TODO: iOS?
             {
                 RegisterPosix();
             }
@@ -27,6 +27,7 @@ namespace Ryujinx.Common.SystemInterop
 
         [SupportedOSPlatform("linux")]
         [SupportedOSPlatform("macos")]
+        [SupportedOSPlatform("ios")]
         private void RegisterPosix()
         {
             const int StdErrFileno = 2;
@@ -44,6 +45,7 @@ namespace Ryujinx.Common.SystemInterop
 
         [SupportedOSPlatform("linux")]
         [SupportedOSPlatform("macos")]
+        [SupportedOSPlatform("ios")]
         private async Task EventWorkerAsync(CancellationToken cancellationToken)
         {
             using TextReader reader = new StreamReader(_pipeReader, leaveOpen: true);
@@ -92,6 +94,7 @@ namespace Ryujinx.Common.SystemInterop
 
         [SupportedOSPlatform("linux")]
         [SupportedOSPlatform("macos")]
+        [SupportedOSPlatform("ios")]
         private static Stream CreateFileDescriptorStream(int fd)
         {
             return new FileStream(
diff --git a/src/Ryujinx.Graphics.Gpu/GraphicsConfig.cs b/src/Ryujinx.Graphics.Gpu/GraphicsConfig.cs
index fbb7399ca..9707d4fac 100644
--- a/src/Ryujinx.Graphics.Gpu/GraphicsConfig.cs
+++ b/src/Ryujinx.Graphics.Gpu/GraphicsConfig.cs
@@ -40,7 +40,7 @@ namespace Ryujinx.Graphics.Gpu
         /// <summary>
         /// Enables or disables the Just-in-Time compiler for GPU Macro code.
         /// </summary>
-        public static bool EnableMacroJit = true;
+        public static bool EnableMacroJit = false;
 
         /// <summary>
         /// Enables or disables high-level emulation of common GPU Macro code.
diff --git a/src/Ryujinx.Graphics.Nvdec.FFmpeg/Native/FFmpegApi.cs b/src/Ryujinx.Graphics.Nvdec.FFmpeg/Native/FFmpegApi.cs
index 5167ff9fe..c0a49b5f7 100644
--- a/src/Ryujinx.Graphics.Nvdec.FFmpeg/Native/FFmpegApi.cs
+++ b/src/Ryujinx.Graphics.Nvdec.FFmpeg/Native/FFmpegApi.cs
@@ -26,7 +26,7 @@ namespace Ryujinx.Graphics.Nvdec.FFmpeg.Native
             {
                 return $"lib{libraryName}.so.{version}";
             }
-            else if (OperatingSystem.IsMacOS())
+            else if (OperatingSystem.IsMacOS() || OperatingSystem.IsIOS()) // TODO: ffmpeg on ios
             {
                 return $"lib{libraryName}.{version}.dylib";
             }
diff --git a/src/Ryujinx.Graphics.Vulkan/MoltenVK/MVKInitialization.cs b/src/Ryujinx.Graphics.Vulkan/MoltenVK/MVKInitialization.cs
index 457240aa0..fadfc66dd 100644
--- a/src/Ryujinx.Graphics.Vulkan/MoltenVK/MVKInitialization.cs
+++ b/src/Ryujinx.Graphics.Vulkan/MoltenVK/MVKInitialization.cs
@@ -6,6 +6,7 @@ using System.Runtime.Versioning;
 namespace Ryujinx.Graphics.Vulkan.MoltenVK
 {
     [SupportedOSPlatform("macos")]
+    [SupportedOSPlatform("ios")]
     public static partial class MVKInitialization
     {
         [LibraryImport("libMoltenVK.dylib")]
diff --git a/src/Ryujinx.Graphics.Vulkan/VulkanRenderer.cs b/src/Ryujinx.Graphics.Vulkan/VulkanRenderer.cs
index 893ecf1a9..d379b9c18 100644
--- a/src/Ryujinx.Graphics.Vulkan/VulkanRenderer.cs
+++ b/src/Ryujinx.Graphics.Vulkan/VulkanRenderer.cs
@@ -101,7 +101,7 @@ namespace Ryujinx.Graphics.Vulkan
             Textures = new HashSet<ITexture>();
             Samplers = new HashSet<SamplerHolder>();
 
-            if (OperatingSystem.IsMacOS())
+            if (OperatingSystem.IsMacOS() || OperatingSystem.IsIOS())
             {
                 MVKInitialization.Initialize();
 
diff --git a/src/Ryujinx.HLE/HOS/Services/Nifm/StaticService/Types/IpAddressSetting.cs b/src/Ryujinx.HLE/HOS/Services/Nifm/StaticService/Types/IpAddressSetting.cs
index 4fa674de9..359c9b75e 100644
--- a/src/Ryujinx.HLE/HOS/Services/Nifm/StaticService/Types/IpAddressSetting.cs
+++ b/src/Ryujinx.HLE/HOS/Services/Nifm/StaticService/Types/IpAddressSetting.cs
@@ -15,7 +15,7 @@ namespace Ryujinx.HLE.HOS.Services.Nifm.StaticService.Types
 
         public IpAddressSetting(IPInterfaceProperties interfaceProperties, UnicastIPAddressInformation unicastIPAddressInformation)
         {
-            IsDhcpEnabled = OperatingSystem.IsMacOS() || interfaceProperties.DhcpServerAddresses.Count != 0;
+            IsDhcpEnabled = OperatingSystem.IsMacOS() || OperatingSystem.IsIOS() || interfaceProperties.DhcpServerAddresses.Count != 0;
             Address = new IpV4Address(unicastIPAddressInformation.Address);
             IPv4Mask = new IpV4Address(unicastIPAddressInformation.IPv4Mask);
             GatewayAddress = (interfaceProperties.GatewayAddresses.Count == 0) ? new IpV4Address() : new IpV4Address(interfaceProperties.GatewayAddresses[0].Address);
diff --git a/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Impl/WinSockHelper.cs b/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Impl/WinSockHelper.cs
index e2ef75f80..b6d8be135 100644
--- a/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Impl/WinSockHelper.cs
+++ b/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Impl/WinSockHelper.cs
@@ -283,7 +283,7 @@ namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd.Impl
 
         public static LinuxError ConvertError(WsaError errorCode)
         {
-            if (OperatingSystem.IsMacOS())
+            if (OperatingSystem.IsMacOS() || OperatingSystem.IsIOS())
             {
                 if (_errorMapMacOs.TryGetValue((int)errorCode, out LinuxError errno))
                 {
diff --git a/src/Ryujinx.Headless.SDL2/Program.cs b/src/Ryujinx.Headless.SDL2/Program.cs
index 7e3c79f54..bd077bc7e 100644
--- a/src/Ryujinx.Headless.SDL2/Program.cs
+++ b/src/Ryujinx.Headless.SDL2/Program.cs
@@ -33,6 +33,7 @@ using Silk.NET.Vulkan;
 using System;
 using System.Collections.Generic;
 using System.IO;
+using System.Runtime.InteropServices;
 using System.Text.Json;
 using System.Threading;
 using ConfigGamepadInputId = Ryujinx.Common.Configuration.Hid.Controller.GamepadInputId;
@@ -60,16 +61,46 @@ namespace Ryujinx.Headless.SDL2
 
         private static readonly InputConfigJsonSerializerContext _serializerContext = new(JsonHelper.GetDefaultSerializerOptions());
 
+        [UnmanagedCallersOnly(EntryPoint = "main_ryujinx_sdl")]
+        public static unsafe int MainExternal(int argCount, IntPtr* pArgs)
+        {
+            string[] args = new string[argCount];
+
+            try
+            {
+                for (int i = 0; i < argCount; i++)
+                {
+                    args[i] = Marshal.PtrToStringAnsi(pArgs[i]);
+
+                    Console.WriteLine(args[i]);
+                }
+
+                Main(args);
+            }
+            catch (Exception e)
+            {
+                Console.WriteLine(e.ToString());
+                return -1;
+            }
+
+            return 0;
+        }
+
         static void Main(string[] args)
         {
-            Version = ReleaseInformation.GetVersion();
-
             // Make process DPI aware for proper window sizing on high-res screens.
             ForceDpiAware.Windows();
 
-            Console.Title = $"Ryujinx Console {Version} (Headless SDL2)";
+            Silk.NET.Core.Loader.SearchPathContainer.Platform = Silk.NET.Core.Loader.UnderlyingPlatform.MacOS;
 
-            if (OperatingSystem.IsMacOS() || OperatingSystem.IsLinux())
+            Version = ReleaseInformation.GetVersion();
+
+            if (!OperatingSystem.IsIOS())
+            {
+                Console.Title = $"Ryujinx Console {Version} (Headless SDL2)";
+            }
+
+            if (OperatingSystem.IsMacOS() || OperatingSystem.IsIOS() || OperatingSystem.IsLinux())
             {
                 AutoResetEvent invoked = new(false);
 
@@ -344,12 +375,12 @@ namespace Ryujinx.Headless.SDL2
 
             GraphicsConfig.EnableShaderCache = true;
 
-            if (OperatingSystem.IsMacOS())
+            if (OperatingSystem.IsMacOS() || OperatingSystem.IsIOS())
             {
                 if (option.GraphicsBackend == GraphicsBackend.OpenGl)
                 {
                     option.GraphicsBackend = GraphicsBackend.Vulkan;
-                    Logger.Warning?.Print(LogClass.Application, "OpenGL is not supported on macOS, switching to Vulkan!");
+                    Logger.Warning?.Print(LogClass.Application, "OpenGL is not supported on Apple platforms, switching to Vulkan!");
                 }
             }
 
diff --git a/src/Ryujinx.Headless.SDL2/Ryujinx.Headless.SDL2.csproj b/src/Ryujinx.Headless.SDL2/Ryujinx.Headless.SDL2.csproj
index 7b13df736..bb43ced2e 100644
--- a/src/Ryujinx.Headless.SDL2/Ryujinx.Headless.SDL2.csproj
+++ b/src/Ryujinx.Headless.SDL2/Ryujinx.Headless.SDL2.csproj
@@ -2,21 +2,88 @@
 
   <PropertyGroup>
     <TargetFramework>net8.0</TargetFramework>
-    <RuntimeIdentifiers>win-x64;osx-x64;linux-x64</RuntimeIdentifiers>
-    <OutputType>Exe</OutputType>
+    <RuntimeIdentifiers>ios-arm64</RuntimeIdentifiers>
     <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
     <Version>1.0.0-dirty</Version>
     <DefineConstants Condition=" '$(ExtraDefineConstants)' != '' ">$(DefineConstants);$(ExtraDefineConstants)</DefineConstants>
     <SigningCertificate Condition=" '$(SigningCertificate)' == '' ">-</SigningCertificate>
     <TieredPGO>true</TieredPGO>
+
+    <PublishAot>true</PublishAot>
+    <PublishAotUsingRuntimePack>true</PublishAotUsingRuntimePack>
+    <UseNativeAOTRuntime>true</UseNativeAOTRuntime>
+    <TrimmerSingleWarn>false</TrimmerSingleWarn>
   </PropertyGroup>
 
+  <!-- iOS linking stuff from godot -->
+
+  <PropertyGroup>
+    <InvariantGlobalization>true</InvariantGlobalization>
+    <LinkStandardCPlusPlusLibrary>true</LinkStandardCPlusPlusLibrary>
+    <FindXCode Condition=" '$(XCodePath)' == '' and '$([MSBuild]::IsOsPlatform(OSX))' ">true</FindXCode>
+    <XCodePath Condition=" '$(XCodePath)' == '' ">/Applications/Xcode.app/Contents/Developer</XCodePath>
+    <XCodePath>$([MSBuild]::EnsureTrailingSlash('$(XCodePath)'))</XCodePath>
+  </PropertyGroup>
+
+  <Target Name="PrepareBeforeIlcCompile"
+          BeforeTargets="IlcCompile">
+
+    <Copy SourceFiles="%(ResolvedRuntimePack.PackageDirectory)/runtimes/$(RuntimeIdentifier)/native/icudt.dat" DestinationFolder="$(PublishDir)"/>
+
+    <!-- We need to find the path to Xcode so we can set manual linker args to the correct SDKs
+        Once https://github.com/dotnet/runtime/issues/88737 is released, we can take this out
+    -->
+
+    <Exec Command="xcrun xcode-select -p" ConsoleToMSBuild="true" Condition=" '$(FindXCode)' == 'true' ">
+      <Output TaskParameter="ConsoleOutput" PropertyName="XcodeSelect" />
+    </Exec>
+
+    <PropertyGroup Condition=" '$(FindXCode)' == 'true' ">
+      <XCodePath>$(XcodeSelect)</XCodePath>
+      <XCodePath>$([MSBuild]::EnsureTrailingSlash('$(XCodePath)'))</XCodePath>
+    </PropertyGroup>
+
+    <Message Importance="normal" Text="Found XCode at $(XcodeSelect)"  Condition=" '$(FindXCode)' == 'true' "/>
+
+    <ItemGroup>
+      <LinkerArg Include="-Wl,-ld_classic" />
+      <LinkerArg Include="-isysroot %22$(XCodePath)Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk%22"
+                 Condition=" $(RuntimeIdentifier.Contains('simulator')) "/>
+      <LinkerArg Include="-isysroot %22$(XCodePath)Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS.sdk%22"
+                 Condition=" !$(RuntimeIdentifier.Contains('simulator')) "/>
+    </ItemGroup>
+
+  </Target>
+
+  <Target Name="FixDylib"
+          AfterTargets="Publish">
+    <!-- The dylib id needs to be changed to have @rpath -->
+
+    <Exec Command="install_name_tool -id @rpath/$(TargetName).dylib $(NativeBinary)" ConsoleToMSBuild="true" />
+  </Target>
+
+  <Target Name="FixSymbols"
+          AfterTargets="Publish">
+
+    <RemoveDir Directories="$(PublishDir)$(TargetName).framework.dSYM"/>
+
+    <!-- create-xcframework (called from the export plugin wants the symbol files in a directory
+    with a slightly different name from the one created by dotnet publish, so we copy them over
+    to the correctly-named directory -->
+    <ItemGroup>
+      <SymbolFiles Include="$(NativeBinary).dsym\**\*.*"/>
+    </ItemGroup>
+    <Copy SourceFiles="@(SymbolFiles)" DestinationFolder="$(PublishDir)$(TargetName).framework.dSYM"/>
+  </Target>
+
+  <!-- end iOS linking stuff from godot -->
+
   <ItemGroup>
     <PackageReference Include="OpenTK.Core" />
     <PackageReference Include="Ryujinx.Graphics.Nvdec.Dependencies" />
   </ItemGroup>
 
-  <Target Name="PostBuild" AfterTargets="PostBuildEvent" Condition="$([MSBuild]::IsOSPlatform('OSX'))">
+  <Target Name="PostBuild" AfterTargets="PostBuildEvent" Condition="false">
     <Exec Command="codesign --entitlements '$(ProjectDir)..\..\distribution\macos\entitlements.xml' -f --deep -s $(SigningCertificate) '$(TargetDir)$(TargetName)'" />
   </Target>
 
@@ -65,7 +132,7 @@
   </PropertyGroup>
 
   <PropertyGroup Condition="'$(RuntimeIdentifier)' != ''">
-    <PublishSingleFile>true</PublishSingleFile>
+    <PublishSingleFile>false</PublishSingleFile>
     <PublishTrimmed>true</PublishTrimmed>
     <TrimMode>partial</TrimMode>
   </PropertyGroup>
diff --git a/src/Ryujinx.Headless.SDL2/WindowBase.cs b/src/Ryujinx.Headless.SDL2/WindowBase.cs
index adab07641..9603a86be 100644
--- a/src/Ryujinx.Headless.SDL2/WindowBase.cs
+++ b/src/Ryujinx.Headless.SDL2/WindowBase.cs
@@ -36,7 +36,7 @@ namespace Ryujinx.Headless.SDL2
 
         private static readonly ConcurrentQueue<Action> _mainThreadActions = new();
 
-        [LibraryImport("SDL2")]
+        [LibraryImport("SDL2.framework/SDL2")]
         // TODO: Remove this as soon as SDL2-CS was updated to expose this method publicly
         private static partial IntPtr SDL_LoadBMP_RW(IntPtr src, int freesrc);
 
diff --git a/src/Ryujinx.Memory/MachJitWorkaround.cs b/src/Ryujinx.Memory/MachJitWorkaround.cs
new file mode 100644
index 000000000..cfb1e419c
--- /dev/null
+++ b/src/Ryujinx.Memory/MachJitWorkaround.cs
@@ -0,0 +1,168 @@
+using System;
+using System.Runtime.InteropServices;
+using System.Runtime.Versioning;
+
+namespace Ryujinx.Memory
+{
+    [SupportedOSPlatform("ios")]
+    static unsafe partial class MachJitWorkaround
+    {
+        [LibraryImport("libc")]
+        public static partial int mach_task_self();
+
+        [LibraryImport("libc")]
+        public static partial int mach_make_memory_entry_64(IntPtr target_task, IntPtr* size, IntPtr offset, int permission, IntPtr* object_handle, IntPtr parent_entry);
+
+        [LibraryImport("libc")]
+        public static partial int mach_memory_entry_ownership(IntPtr mem_entry, IntPtr owner, int ledger_tag, int ledger_flags);
+
+        [LibraryImport("libc")]
+        public static partial int vm_map(IntPtr target_task, IntPtr* address, IntPtr size, IntPtr mask, int flags, IntPtr obj, IntPtr offset, int copy, int cur_protection, int max_protection, int inheritance);
+
+        [LibraryImport("libc")]
+        public static partial int vm_allocate(IntPtr target_task, IntPtr* address, IntPtr size, int flags);
+
+        [LibraryImport("libc")]
+        public static partial int vm_deallocate(IntPtr target_task, IntPtr address, IntPtr size);
+
+        [LibraryImport("libc")]
+        public static partial int vm_remap(IntPtr target_task, IntPtr* target_address, IntPtr size, IntPtr mask, int flags, IntPtr src_task, IntPtr src_address, int copy, int* cur_protection, int* max_protection, int inheritance);
+
+        const int MAP_MEM_LEDGER_TAGGED = 0x002000;
+        const int MAP_MEM_NAMED_CREATE = 0x020000;
+
+        const int VM_PROT_READ = 0x01;
+        const int VM_PROT_WRITE = 0x02;
+        const int VM_PROT_EXECUTE = 0x04;
+
+        const int VM_LEDGER_TAG_DEFAULT = 0x00000001;
+        const int VM_LEDGER_FLAG_NO_FOOTPRINT = 0x00000001;
+
+        const int VM_INHERIT_COPY = 1;
+        const int VM_INHERIT_DEFAULT = VM_INHERIT_COPY;
+
+        const int VM_FLAGS_FIXED = 0x0000;
+        const int VM_FLAGS_ANYWHERE = 0x0001;
+        const int VM_FLAGS_OVERWRITE = 0x4000;
+
+        const IntPtr TASK_NULL = 0;
+
+        public static void ReallocateBlock(IntPtr address, int size)
+        {
+            IntPtr selfTask = mach_task_self();
+            IntPtr memorySize = (IntPtr)size;
+            IntPtr memoryObjectPort = IntPtr.Zero;
+
+            int err = mach_make_memory_entry_64(selfTask, &memorySize, 0, MAP_MEM_NAMED_CREATE | MAP_MEM_LEDGER_TAGGED | VM_PROT_READ | VM_PROT_WRITE | VM_PROT_EXECUTE, &memoryObjectPort, 0);
+
+            if (err != 0)
+            {
+                throw new InvalidOperationException($"Make memory entry failed: {err}");
+            }
+
+            try
+            {
+                if (memorySize != (IntPtr)size)
+                {
+                    throw new InvalidOperationException($"Created with size {memorySize} instead of {size}.");
+                }
+
+                err = mach_memory_entry_ownership(memoryObjectPort, TASK_NULL, VM_LEDGER_TAG_DEFAULT, VM_LEDGER_FLAG_NO_FOOTPRINT);
+
+                if (err != 0)
+                {
+                    throw new InvalidOperationException($"Failed to set ownership: {err}");
+                }
+
+                IntPtr mapAddress = address;
+
+                err = vm_map(
+                    selfTask,
+                    &mapAddress,
+                    memorySize,
+                    /*mask=*/ 0,
+                    /*flags=*/ VM_FLAGS_OVERWRITE,
+                    memoryObjectPort,
+                    /*offset=*/ 0,
+                    /*copy=*/ 0,
+                    VM_PROT_READ | VM_PROT_WRITE,
+                    VM_PROT_READ | VM_PROT_WRITE | VM_PROT_EXECUTE,
+                    VM_INHERIT_COPY);
+
+                if (err != 0)
+                {
+                    throw new InvalidOperationException($"Failed to map: {err}");
+                }
+
+                if (address != mapAddress)
+                {
+                    throw new InvalidOperationException($"Remap changed address");
+                }
+            }
+            finally
+            {
+                //mach_port_deallocate(selfTask, memoryObjectPort);
+            }
+
+            Console.WriteLine($"Reallocated an area... {address:x16}");
+        }
+
+        public static void ReallocateAreaWithOwnership(IntPtr address, int size)
+        {
+            int mapChunkSize = 128 * 1024 * 1024;
+            IntPtr endAddress = address + size;
+            IntPtr blockAddress = address;
+            while (blockAddress < endAddress)
+            {
+                int blockSize = Math.Min(mapChunkSize, (int)(endAddress - blockAddress));
+
+                ReallocateBlock(blockAddress, blockSize);
+
+                blockAddress += blockSize;
+            }
+        }
+
+        public static IntPtr AllocateSharedMemory(ulong size, bool reserve)
+        {
+            IntPtr address = 0;
+
+            int err = vm_allocate(mach_task_self(), &address, (IntPtr)size, VM_FLAGS_ANYWHERE);
+
+            if (err != 0)
+            {
+                throw new InvalidOperationException($"Failed to allocate shared memory: {err}");
+            }
+
+            return address;
+        }
+
+        public static void DestroySharedMemory(IntPtr handle, ulong size)
+        {
+            vm_deallocate(mach_task_self(), handle, (IntPtr)size);
+        }
+
+        public static IntPtr MapView(IntPtr sharedMemory, ulong srcOffset, IntPtr location, ulong size)
+        {
+            IntPtr taskSelf = mach_task_self();
+            IntPtr srcAddress = (IntPtr)((ulong)sharedMemory + srcOffset);
+            IntPtr dstAddress = location;
+
+            int cur_protection = 0;
+            int max_protection = 0;
+
+            int err = vm_remap(taskSelf, &dstAddress, (IntPtr)size, 0, VM_FLAGS_OVERWRITE, taskSelf, srcAddress, 0, &cur_protection, &max_protection, VM_INHERIT_DEFAULT);
+
+            if (err != 0)
+            {
+                throw new InvalidOperationException($"Failed to allocate remap memory: {err}");
+            }
+
+            return dstAddress;
+        }
+
+        public static void UnmapView(IntPtr location, ulong size)
+        {
+            vm_deallocate(mach_task_self(), location, (IntPtr)size);
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/Ryujinx.Memory/MemoryBlock.cs b/src/Ryujinx.Memory/MemoryBlock.cs
index 7fe7862a9..477be893a 100644
--- a/src/Ryujinx.Memory/MemoryBlock.cs
+++ b/src/Ryujinx.Memory/MemoryBlock.cs
@@ -426,7 +426,7 @@ namespace Ryujinx.Memory
                     return OperatingSystem.IsWindowsVersionAtLeast(10, 0, 17134);
                 }
 
-                return OperatingSystem.IsLinux() || OperatingSystem.IsMacOS();
+                return OperatingSystem.IsLinux() || OperatingSystem.IsMacOS() || OperatingSystem.IsIOS();
             }
 
             return true;
diff --git a/src/Ryujinx.Memory/MemoryManagement.cs b/src/Ryujinx.Memory/MemoryManagement.cs
index 860d3f368..a23fafb57 100644
--- a/src/Ryujinx.Memory/MemoryManagement.cs
+++ b/src/Ryujinx.Memory/MemoryManagement.cs
@@ -10,7 +10,7 @@ namespace Ryujinx.Memory
             {
                 return MemoryManagementWindows.Allocate((IntPtr)size);
             }
-            else if (OperatingSystem.IsLinux() || OperatingSystem.IsMacOS())
+            else if (OperatingSystem.IsLinux() || OperatingSystem.IsMacOS() || OperatingSystem.IsIOS())
             {
                 return MemoryManagementUnix.Allocate(size, forJit);
             }
@@ -26,7 +26,7 @@ namespace Ryujinx.Memory
             {
                 return MemoryManagementWindows.Reserve((IntPtr)size, viewCompatible);
             }
-            else if (OperatingSystem.IsLinux() || OperatingSystem.IsMacOS())
+            else if (OperatingSystem.IsLinux() || OperatingSystem.IsMacOS() || OperatingSystem.IsIOS())
             {
                 return MemoryManagementUnix.Reserve(size, forJit);
             }
@@ -42,7 +42,7 @@ namespace Ryujinx.Memory
             {
                 MemoryManagementWindows.Commit(address, (IntPtr)size);
             }
-            else if (OperatingSystem.IsLinux() || OperatingSystem.IsMacOS())
+            else if (OperatingSystem.IsLinux() || OperatingSystem.IsMacOS() || OperatingSystem.IsIOS())
             {
                 MemoryManagementUnix.Commit(address, size, forJit);
             }
@@ -58,7 +58,7 @@ namespace Ryujinx.Memory
             {
                 MemoryManagementWindows.Decommit(address, (IntPtr)size);
             }
-            else if (OperatingSystem.IsLinux() || OperatingSystem.IsMacOS())
+            else if (OperatingSystem.IsLinux() || OperatingSystem.IsMacOS() || OperatingSystem.IsIOS())
             {
                 MemoryManagementUnix.Decommit(address, size);
             }
@@ -74,7 +74,7 @@ namespace Ryujinx.Memory
             {
                 MemoryManagementWindows.MapView(sharedMemory, srcOffset, address, (IntPtr)size, owner);
             }
-            else if (OperatingSystem.IsLinux() || OperatingSystem.IsMacOS())
+            else if (OperatingSystem.IsLinux() || OperatingSystem.IsMacOS() || OperatingSystem.IsIOS())
             {
                 MemoryManagementUnix.MapView(sharedMemory, srcOffset, address, size);
             }
@@ -90,7 +90,7 @@ namespace Ryujinx.Memory
             {
                 MemoryManagementWindows.UnmapView(sharedMemory, address, (IntPtr)size, owner);
             }
-            else if (OperatingSystem.IsLinux() || OperatingSystem.IsMacOS())
+            else if (OperatingSystem.IsLinux() || OperatingSystem.IsMacOS() || OperatingSystem.IsIOS())
             {
                 MemoryManagementUnix.UnmapView(address, size);
             }
@@ -108,7 +108,7 @@ namespace Ryujinx.Memory
             {
                 result = MemoryManagementWindows.Reprotect(address, (IntPtr)size, permission, forView);
             }
-            else if (OperatingSystem.IsLinux() || OperatingSystem.IsMacOS())
+            else if (OperatingSystem.IsLinux() || OperatingSystem.IsMacOS() || OperatingSystem.IsIOS())
             {
                 result = MemoryManagementUnix.Reprotect(address, size, permission);
             }
@@ -129,7 +129,7 @@ namespace Ryujinx.Memory
             {
                 return MemoryManagementWindows.Free(address, (IntPtr)size);
             }
-            else if (OperatingSystem.IsLinux() || OperatingSystem.IsMacOS())
+            else if (OperatingSystem.IsLinux() || OperatingSystem.IsMacOS() || OperatingSystem.IsIOS())
             {
                 return MemoryManagementUnix.Free(address);
             }
@@ -145,7 +145,7 @@ namespace Ryujinx.Memory
             {
                 return MemoryManagementWindows.CreateSharedMemory((IntPtr)size, reserve);
             }
-            else if (OperatingSystem.IsLinux() || OperatingSystem.IsMacOS())
+            else if (OperatingSystem.IsLinux() || OperatingSystem.IsMacOS() || OperatingSystem.IsIOS())
             {
                 return MemoryManagementUnix.CreateSharedMemory(size, reserve);
             }
@@ -161,7 +161,7 @@ namespace Ryujinx.Memory
             {
                 MemoryManagementWindows.DestroySharedMemory(handle);
             }
-            else if (OperatingSystem.IsLinux() || OperatingSystem.IsMacOS())
+            else if (OperatingSystem.IsLinux() || OperatingSystem.IsMacOS() || OperatingSystem.IsIOS())
             {
                 MemoryManagementUnix.DestroySharedMemory(handle);
             }
@@ -177,7 +177,7 @@ namespace Ryujinx.Memory
             {
                 return MemoryManagementWindows.MapSharedMemory(handle);
             }
-            else if (OperatingSystem.IsLinux() || OperatingSystem.IsMacOS())
+            else if (OperatingSystem.IsLinux() || OperatingSystem.IsMacOS() || OperatingSystem.IsIOS())
             {
                 return MemoryManagementUnix.MapSharedMemory(handle, size);
             }
@@ -193,7 +193,7 @@ namespace Ryujinx.Memory
             {
                 MemoryManagementWindows.UnmapSharedMemory(address);
             }
-            else if (OperatingSystem.IsLinux() || OperatingSystem.IsMacOS())
+            else if (OperatingSystem.IsLinux() || OperatingSystem.IsMacOS() || OperatingSystem.IsIOS())
             {
                 MemoryManagementUnix.UnmapSharedMemory(address, size);
             }
diff --git a/src/Ryujinx.Memory/MemoryManagementUnix.cs b/src/Ryujinx.Memory/MemoryManagementUnix.cs
index e132dbbb8..78966777e 100644
--- a/src/Ryujinx.Memory/MemoryManagementUnix.cs
+++ b/src/Ryujinx.Memory/MemoryManagementUnix.cs
@@ -1,5 +1,6 @@
 using System;
 using System.Collections.Concurrent;
+using System.Collections.Generic;
 using System.Runtime.InteropServices;
 using System.Runtime.Versioning;
 using static Ryujinx.Memory.MemoryManagerUnixHelper;
@@ -8,6 +9,7 @@ namespace Ryujinx.Memory
 {
     [SupportedOSPlatform("linux")]
     [SupportedOSPlatform("macos")]
+    [SupportedOSPlatform("ios")]
     static class MemoryManagementUnix
     {
         private static readonly ConcurrentDictionary<IntPtr, ulong> _allocations = new();
@@ -40,7 +42,7 @@ namespace Ryujinx.Memory
                 flags |= MmapFlags.MAP_NORESERVE;
             }
 
-            if (OperatingSystem.IsMacOSVersionAtLeast(10, 14) && forJit)
+            if (OperatingSystem.IsMacOS() && OperatingSystem.IsMacOSVersionAtLeast(10, 14) && forJit)
             {
                 flags |= MmapFlags.MAP_JIT_DARWIN;
 
@@ -57,6 +59,11 @@ namespace Ryujinx.Memory
                 throw new SystemException(Marshal.GetLastPInvokeErrorMessage());
             }
 
+            if (OperatingSystem.IsIOS() && forJit)
+            {
+                MachJitWorkaround.ReallocateAreaWithOwnership(ptr, (int)size);
+            }
+
             if (!_allocations.TryAdd(ptr, size))
             {
                 // This should be impossible, kernel shouldn't return an already mapped address.
@@ -70,7 +77,7 @@ namespace Ryujinx.Memory
         {
             MmapProts prot = MmapProts.PROT_READ | MmapProts.PROT_WRITE;
 
-            if (OperatingSystem.IsMacOSVersionAtLeast(10, 14) && forJit)
+            if ((OperatingSystem.IsIOS() || OperatingSystem.IsMacOSVersionAtLeast(10, 14)) && forJit)
             {
                 prot |= MmapProts.PROT_EXEC;
             }
@@ -134,11 +141,21 @@ namespace Ryujinx.Memory
             return munmap(address, size) == 0;
         }
 
+        private static Dictionary<IntPtr, ulong> _sharedMemorySizes = new Dictionary<nint, ulong>();
+
         public unsafe static IntPtr CreateSharedMemory(ulong size, bool reserve)
         {
             int fd;
 
-            if (OperatingSystem.IsMacOS())
+            if (OperatingSystem.IsIOS())
+            {
+                IntPtr baseAddress = MachJitWorkaround.AllocateSharedMemory(size, reserve);
+
+                _sharedMemorySizes.Add(baseAddress, size);
+
+                return baseAddress;
+            }
+            else if (OperatingSystem.IsMacOS())
             {
                 byte[] memName = "Ryujinx-XXXXXX"u8.ToArray();
 
@@ -185,27 +202,64 @@ namespace Ryujinx.Memory
 
         public static void DestroySharedMemory(IntPtr handle)
         {
-            close(handle.ToInt32());
+            if (OperatingSystem.IsIOS())
+            {
+                if (_sharedMemorySizes.TryGetValue(handle, out ulong size))
+                {
+                    MachJitWorkaround.DestroySharedMemory(handle, size);
+                }
+            }
+            else
+            {
+                close(handle.ToInt32());
+            }
         }
 
         public static IntPtr MapSharedMemory(IntPtr handle, ulong size)
         {
-            return Mmap(IntPtr.Zero, size, MmapProts.PROT_READ | MmapProts.PROT_WRITE, MmapFlags.MAP_SHARED, handle.ToInt32(), 0);
+            if (OperatingSystem.IsIOS())
+            {
+                // The base of the shared memory is already mapped - it's the handle.
+                // Views are remapped from it.
+
+                return handle;
+            }
+            else
+            {
+                return Mmap(IntPtr.Zero, size, MmapProts.PROT_READ | MmapProts.PROT_WRITE, MmapFlags.MAP_SHARED, handle.ToInt32(), 0);
+            }
         }
 
         public static void UnmapSharedMemory(IntPtr address, ulong size)
         {
-            munmap(address, size);
+            if (!OperatingSystem.IsIOS())
+            {
+                munmap(address, size);
+            }
         }
 
         public static void MapView(IntPtr sharedMemory, ulong srcOffset, IntPtr location, ulong size)
         {
-            Mmap(location, size, MmapProts.PROT_READ | MmapProts.PROT_WRITE, MmapFlags.MAP_FIXED | MmapFlags.MAP_SHARED, sharedMemory.ToInt32(), (long)srcOffset);
+            if (OperatingSystem.IsIOS())
+            {
+                MachJitWorkaround.MapView(sharedMemory, srcOffset, location, size);
+            }
+            else
+            {
+                Mmap(location, size, MmapProts.PROT_READ | MmapProts.PROT_WRITE, MmapFlags.MAP_FIXED | MmapFlags.MAP_SHARED, sharedMemory.ToInt32(), (long)srcOffset);
+            }
         }
 
         public static void UnmapView(IntPtr location, ulong size)
         {
-            Mmap(location, size, MmapProts.PROT_NONE, MmapFlags.MAP_FIXED | MmapFlags.MAP_PRIVATE | MmapFlags.MAP_ANONYMOUS | MmapFlags.MAP_NORESERVE, -1, 0);
+            if (OperatingSystem.IsIOS())
+            {
+                MachJitWorkaround.UnmapView(location, size);
+            }
+            else
+            {
+                Mmap(location, size, MmapProts.PROT_NONE, MmapFlags.MAP_FIXED | MmapFlags.MAP_PRIVATE | MmapFlags.MAP_ANONYMOUS | MmapFlags.MAP_NORESERVE, -1, 0);
+            }
         }
     }
 }
diff --git a/src/Ryujinx.Memory/MemoryManagerUnixHelper.cs b/src/Ryujinx.Memory/MemoryManagerUnixHelper.cs
index 43888c85b..6ed4d2387 100644
--- a/src/Ryujinx.Memory/MemoryManagerUnixHelper.cs
+++ b/src/Ryujinx.Memory/MemoryManagerUnixHelper.cs
@@ -6,6 +6,7 @@ namespace Ryujinx.Memory
 {
     [SupportedOSPlatform("linux")]
     [SupportedOSPlatform("macos")]
+    [SupportedOSPlatform("ios")]
     public static partial class MemoryManagerUnixHelper
     {
         [Flags]
@@ -114,7 +115,7 @@ namespace Ryujinx.Memory
                 {
                     result |= MAP_ANONYMOUS_LINUX_GENERIC;
                 }
-                else if (OperatingSystem.IsMacOS())
+                else if (OperatingSystem.IsMacOS() || OperatingSystem.IsIOS())
                 {
                     result |= MAP_ANONYMOUS_DARWIN;
                 }
@@ -130,7 +131,7 @@ namespace Ryujinx.Memory
                 {
                     result |= MAP_NORESERVE_LINUX_GENERIC;
                 }
-                else if (OperatingSystem.IsMacOS())
+                else if (OperatingSystem.IsMacOS() || OperatingSystem.IsIOS())
                 {
                     result |= MAP_NORESERVE_DARWIN;
                 }
@@ -146,7 +147,7 @@ namespace Ryujinx.Memory
                 {
                     result |= MAP_UNLOCKED_LINUX_GENERIC;
                 }
-                else if (OperatingSystem.IsMacOS())
+                else if (OperatingSystem.IsMacOS() || OperatingSystem.IsIOS())
                 {
                     // FIXME: Doesn't exist on Darwin
                 }
@@ -156,7 +157,7 @@ namespace Ryujinx.Memory
                 }
             }
 
-            if (flags.HasFlag(MmapFlags.MAP_JIT_DARWIN) && OperatingSystem.IsMacOSVersionAtLeast(10, 14))
+            if (flags.HasFlag(MmapFlags.MAP_JIT_DARWIN) && (OperatingSystem.IsIOS() || OperatingSystem.IsMacOSVersionAtLeast(10, 14)))
             {
                 result |= (int)MmapFlags.MAP_JIT_DARWIN;
             }
diff --git a/src/Ryujinx.Ui.Common/Configuration/ConfigurationState.cs b/src/Ryujinx.Ui.Common/Configuration/ConfigurationState.cs
index b017d384c..52575a7e3 100644
--- a/src/Ryujinx.Ui.Common/Configuration/ConfigurationState.cs
+++ b/src/Ryujinx.Ui.Common/Configuration/ConfigurationState.cs
@@ -1536,7 +1536,7 @@ namespace Ryujinx.Ui.Common.Configuration
         {
             // Any system running macOS or returning any amount of valid Vulkan devices should default to Vulkan.
             // Checks for if the Vulkan version and featureset is compatible should be performed within VulkanRenderer.
-            if (OperatingSystem.IsMacOS() || VulkanRenderer.GetPhysicalDevices().Length > 0)
+            if (OperatingSystem.IsMacOS() || OperatingSystem.IsIOS() || VulkanRenderer.GetPhysicalDevices().Length > 0)
             {
                 return GraphicsBackend.Vulkan;
             }
diff --git a/src/Ryujinx.Ui.Common/Helper/ObjectiveC.cs b/src/Ryujinx.Ui.Common/Helper/ObjectiveC.cs
index af8723e2f..16a67ecb2 100644
--- a/src/Ryujinx.Ui.Common/Helper/ObjectiveC.cs
+++ b/src/Ryujinx.Ui.Common/Helper/ObjectiveC.cs
@@ -5,6 +5,7 @@ using System.Runtime.Versioning;
 namespace Ryujinx.Ui.Common.Helper
 {
     [SupportedOSPlatform("macos")]
+    [SupportedOSPlatform("ios")]
     public static partial class ObjectiveC
     {
         private const string ObjCRuntime = "/usr/lib/libobjc.A.dylib";
diff --git a/src/Ryujinx/Ui/Helper/MetalHelper.cs b/src/Ryujinx/Ui/Helper/MetalHelper.cs
index a7af2aed2..f46a5e36e 100644
--- a/src/Ryujinx/Ui/Helper/MetalHelper.cs
+++ b/src/Ryujinx/Ui/Helper/MetalHelper.cs
@@ -8,6 +8,7 @@ namespace Ryujinx.Ui.Helper
     public delegate void UpdateBoundsCallbackDelegate(Window window);
 
     [SupportedOSPlatform("macos")]
+    [SupportedOSPlatform("ios")]
     static partial class MetalHelper
     {
         private const string LibObjCImport = "/usr/lib/libobjc.A.dylib";