[Darwin][Network.framework] Add src/platform/Darwin/system/SystemLayerImplDispatch.mm such that the unit tests runs using the dispatch mechanism instead of the socket based implementation (#38912)

diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml
index 53fda46..4cebe83 100644
--- a/.github/workflows/lint.yml
+++ b/.github/workflows/lint.yml
@@ -126,6 +126,7 @@
                      --known-failure platform/DeviceSafeQueue.h \
                      --known-failure platform/GLibTypeDeleter.h \
                      --known-failure platform/SingletonConfigurationManager.cpp \
+                     --known-failure system/SystemLayerImplDispatch.h \
                   "
                   # These ARE actually orphaned but due to dynamic-server we have code paths
                   # for them. Keeping them as a list as they still need review ...
diff --git a/src/inet/tests/TestInetCommonPosix.cpp b/src/inet/tests/TestInetCommonPosix.cpp
index 6795154..4648a08 100644
--- a/src/inet/tests/TestInetCommonPosix.cpp
+++ b/src/inet/tests/TestInetCommonPosix.cpp
@@ -358,16 +358,22 @@
         }
     }
 
+#if !CHIP_SYSTEM_CONFIG_USE_DISPATCH
     // Start a timer (with a no-op callback) to ensure that WaitForEvents() does not block longer than aSleepTimeMilliseconds.
     gSystemLayer.StartTimer(
         System::Clock::Milliseconds32(aSleepTimeMilliseconds), [](System::Layer *, void *) -> void {}, nullptr);
+#endif
 
-#if CHIP_SYSTEM_CONFIG_USE_SOCKETS
+#if CHIP_SYSTEM_CONFIG_USE_SOCKETS && !CHIP_SYSTEM_CONFIG_USE_DISPATCH
     gSystemLayer.PrepareEvents();
     gSystemLayer.WaitForEvents();
     gSystemLayer.HandleEvents();
 #endif
 
+#if CHIP_SYSTEM_CONFIG_USE_DISPATCH
+    gSystemLayer.HandleDispatchQueueEvents(System::Clock::Milliseconds32(aSleepTimeMilliseconds));
+#endif
+
 #if CHIP_SYSTEM_CONFIG_USE_LWIP || CHIP_SYSTEM_CONFIG_USE_OPENTHREAD_ENDPOINT
     if (gSystemLayer.IsInitialized())
     {
diff --git a/src/platform/Darwin/BUILD.gn b/src/platform/Darwin/BUILD.gn
index b32f1e4..d80120c 100644
--- a/src/platform/Darwin/BUILD.gn
+++ b/src/platform/Darwin/BUILD.gn
@@ -16,6 +16,7 @@
 import("//build_overrides/nlassert.gni")
 
 import("${chip_root}/src/platform/device.gni")
+import("${chip_root}/src/system/system.gni")
 
 assert(chip_device_platform == "darwin")
 
@@ -79,8 +80,14 @@
     "UserDefaults.mm",
     "inet/NetworkMonitor.h",
     "inet/NetworkMonitor.mm",
+    "system/SystemLayerImplDispatch.h",
+    "system/SystemLayerImplDispatch.mm",
   ]
 
+  if (chip_system_config_use_sockets) {
+    sources += [ "system/SystemLayerImplDispatchSockets.mm" ]
+  }
+
   if (chip_enable_wifi) {
     sources += [
       "WiFi/ConfigurationManagerImplWiFi.cpp",
diff --git a/src/platform/Darwin/PlatformManagerImpl.mm b/src/platform/Darwin/PlatformManagerImpl.mm
index 81b2a94..282f702 100644
--- a/src/platform/Darwin/PlatformManagerImpl.mm
+++ b/src/platform/Darwin/PlatformManagerImpl.mm
@@ -76,7 +76,7 @@
 
 #if CHIP_SYSTEM_CONFIG_USE_DISPATCH
         // Ensure there is a dispatch queue available
-        static_cast<System::LayerSocketsLoop &>(DeviceLayer::SystemLayer()).SetDispatchQueue(GetWorkQueue());
+        static_cast<System::LayerDispatch &>(DeviceLayer::SystemLayer()).SetDispatchQueue(GetWorkQueue());
 #endif
 
         // Call _InitChipStack() on the generic implementation base class
diff --git a/src/platform/Darwin/system/SystemLayerImplDispatch.h b/src/platform/Darwin/system/SystemLayerImplDispatch.h
new file mode 100644
index 0000000..8857b5c
--- /dev/null
+++ b/src/platform/Darwin/system/SystemLayerImplDispatch.h
@@ -0,0 +1,134 @@
+/*
+ *
+ *    Copyright (c) 2025 Project CHIP Authors
+ *
+ *    Licensed under the Apache License, Version 2.0 (the "License");
+ *    you may not use this file except in compliance with the License.
+ *    You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *    Unless required by applicable law or agreed to in writing, software
+ *    distributed under the License is distributed on an "AS IS" BASIS,
+ *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *    See the License for the specific language governing permissions and
+ *    limitations under the License.
+ */
+
+/**
+ *    @file
+ *      This file declares an implementation of System::Layer using dispatch.
+ */
+
+#pragma once
+
+#include "system/SystemConfig.h"
+
+#include <lib/support/ObjectLifeCycle.h>
+#include <system/SystemLayer.h>
+#include <system/SystemTimer.h>
+
+namespace chip {
+namespace System {
+
+class LayerImplDispatch : public LayerDispatch
+{
+public:
+    LayerImplDispatch() = default;
+    ~LayerImplDispatch() override { VerifyOrDie(mLayerState.Destroy()); }
+
+    // Layer overrides.
+    CHIP_ERROR Init() override;
+    void Shutdown() override;
+    bool IsInitialized() const override { return mLayerState.IsInitialized(); }
+    CHIP_ERROR StartTimer(Clock::Timeout delay, TimerCompleteCallback onComplete, void * appState) override;
+    CHIP_ERROR ExtendTimerTo(Clock::Timeout delay, TimerCompleteCallback onComplete, void * appState) override;
+    bool IsTimerActive(TimerCompleteCallback onComplete, void * appState) override;
+    Clock::Timeout GetRemainingTime(TimerCompleteCallback onComplete, void * appState) override;
+    void CancelTimer(TimerCompleteCallback onComplete, void * appState) override;
+    CHIP_ERROR ScheduleWork(TimerCompleteCallback onComplete, void * appState) override;
+
+    // LayerDispatch overrides.
+    void SetDispatchQueue(dispatch_queue_t dispatchQueue) override { mDispatchQueue = dispatchQueue; };
+    dispatch_queue_t GetDispatchQueue() override { return mDispatchQueue; };
+    void HandleDispatchQueueEvents(Clock::Timeout timeout) override;
+    CHIP_ERROR ScheduleWorkWithBlock(dispatch_block_t block) override;
+
+#if CHIP_SYSTEM_CONFIG_USE_SOCKETS
+    // LayerSocket overrides.
+    CHIP_ERROR StartWatchingSocket(int fd, SocketWatchToken * tokenOut) override;
+    CHIP_ERROR SetCallback(SocketWatchToken token, SocketWatchCallback callback, intptr_t data) override;
+    CHIP_ERROR RequestCallbackOnPendingRead(SocketWatchToken token) override;
+    CHIP_ERROR RequestCallbackOnPendingWrite(SocketWatchToken token) override;
+    CHIP_ERROR ClearCallbackOnPendingRead(SocketWatchToken token) override;
+    CHIP_ERROR ClearCallbackOnPendingWrite(SocketWatchToken token) override;
+    CHIP_ERROR StopWatchingSocket(SocketWatchToken * tokenInOut) override;
+    SocketWatchToken InvalidSocketWatchToken() override { return reinterpret_cast<SocketWatchToken>(nullptr); }
+#endif
+
+protected:
+#if CHIP_SYSTEM_CONFIG_USE_SOCKETS
+    struct SelectSets
+    {
+        fd_set mReadSet;
+        fd_set mWriteSet;
+        fd_set mErrorSet;
+    };
+
+    struct SocketWatch
+    {
+        int mFD;
+        SocketEvents mPendingIO;
+        SocketWatchCallback mCallback;
+        intptr_t mCallbackData;
+
+        dispatch_source_t mRdSource = nullptr;
+        dispatch_source_t mWrSource = nullptr;
+
+        bool IsActive() const { return mFD != kInvalidFd; }
+        void PrepareEvents(SelectSets & sets, int & maxFd) const;
+        void HandleEvents(const SelectSets & sets) const;
+        void Clear();
+    };
+
+    class SocketWatchPool
+    {
+    public:
+        CHIP_ERROR Allocate(int fd, SocketWatch *& outWatch);
+        CHIP_ERROR Release(SocketWatch * watch);
+        void Clear();
+
+        bool PrepareEvents(SelectSets & sets, timeval timeout) const;
+        void HandleEvents(const SelectSets & sets) const;
+
+    private:
+        static constexpr int kSocketWatchMax = (INET_CONFIG_ENABLE_TCP_ENDPOINT ? INET_CONFIG_NUM_TCP_ENDPOINTS : 0) +
+            (INET_CONFIG_ENABLE_UDP_ENDPOINT ? INET_CONFIG_NUM_UDP_ENDPOINTS : 0);
+        SocketWatch mPool[kSocketWatchMax];
+    };
+
+    SocketWatchPool mSocketWatchPool;
+    void HandleSocketsAndTimerEvents(Clock::Timeout timeout);
+    CHIP_ERROR RequestCallback(SocketWatchToken token, SocketEventFlags flag);
+    CHIP_ERROR ClearCallback(SocketWatchToken token, SocketEventFlags flag);
+#endif
+
+    TimerPool<TimerList::Node> mTimerPool;
+    TimerList mTimerList;
+    // List of expired timers being processed right now.  Stored in a member so
+    // we can cancel them.
+    TimerList mExpiredTimers;
+    void EnableTimer(const char * source, TimerList::Node *);
+    void DisableTimer(const char * source, TimerList::Node *);
+    CHIP_ERROR StartTimer(Clock::Timeout delay, TimerCompleteCallback onComplete, void * appState, bool shouldCancel);
+    void HandleTimerEvents(Clock::Timeout timeout);
+
+    ObjectLifeCycle mLayerState;
+
+    dispatch_queue_t mDispatchQueue = nullptr;
+};
+
+using LayerImpl = LayerImplDispatch;
+
+} // namespace System
+} // namespace chip
diff --git a/src/platform/Darwin/system/SystemLayerImplDispatch.mm b/src/platform/Darwin/system/SystemLayerImplDispatch.mm
new file mode 100644
index 0000000..ada0b12
--- /dev/null
+++ b/src/platform/Darwin/system/SystemLayerImplDispatch.mm
@@ -0,0 +1,301 @@
+/*
+ *
+ *    Copyright (c) 2025 Project CHIP Authors
+ *
+ *    Licensed under the Apache License, Version 2.0 (the "License");
+ *    you may not use this file except in compliance with the License.
+ *    You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *    Unless required by applicable law or agreed to in writing, software
+ *    distributed under the License is distributed on an "AS IS" BASIS,
+ *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *    See the License for the specific language governing permissions and
+ *    limitations under the License.
+ */
+
+/**
+ *    @file
+ *      This file implements Layer using dispatch.
+ */
+
+#if !__has_feature(objc_arc)
+#error This file must be compiled with ARC. Use -fobjc-arc flag (or convert project to ARC).
+#endif
+
+#include <system/SystemLayerImplDispatch.h>
+
+#include <lib/support/CodeUtils.h>
+#include <platform/LockTracker.h>
+#include <system/SystemFaultInjection.h>
+
+#define SYSTEM_LAYER_IMPL_DISPATCH_DEBUG 0
+
+namespace chip {
+namespace System {
+    namespace {
+        struct TimerCompleteBlockCallbackContext {
+            dispatch_block_t block;
+        };
+
+        static void TimerCompleteBlockCallback(Layer * aLayer, void * appState)
+        {
+            __auto_type * ctx = static_cast<TimerCompleteBlockCallbackContext *>(appState);
+            if (ctx->block) {
+                ctx->block();
+            }
+            delete ctx;
+        }
+    }
+
+    void LayerImplDispatch::EnableTimer(const char * source, TimerList::Node * timer)
+    {
+#if CONFIG_BUILD_FOR_HOST_UNIT_TEST
+        VerifyOrReturn(nullptr != timer->mTimerSource);
+#else
+        VerifyOrDie(nullptr != timer->mTimerSource);
+#endif
+
+#if SYSTEM_LAYER_IMPL_DISPATCH_DEBUG
+        bool resumed = timer->mTimerSource ? dispatch_testcancel(timer->mTimerSource) : false;
+        ChipLogError(Inet, "%s (%s) : timer=%p - source=%p - resumed=%d", __func__, source, timer, timer->mTimerSource, resumed);
+#endif
+        dispatch_resume(timer->mTimerSource);
+    }
+
+    void LayerImplDispatch::DisableTimer(const char * source, TimerList::Node * timer)
+    {
+#if CONFIG_BUILD_FOR_HOST_UNIT_TEST
+        VerifyOrReturn(nullptr != timer->mTimerSource);
+#else
+        VerifyOrDie(nullptr != timer->mTimerSource);
+#endif
+
+#if SYSTEM_LAYER_IMPL_DISPATCH_DEBUG
+        bool resumed = timer->mTimerSource ? dispatch_testcancel(timer->mTimerSource) : false;
+        ChipLogError(Inet, "%s (%s) : timer=%p - source=%p - resumed=%d", __func__, source, timer, timer->mTimerSource, resumed);
+#endif
+
+        if (!dispatch_testcancel(timer->mTimerSource)) {
+            dispatch_source_cancel(timer->mTimerSource);
+        }
+        timer->mTimerSource = nullptr;
+    }
+
+    CHIP_ERROR LayerImplDispatch::Init()
+    {
+        VerifyOrReturnError(mLayerState.SetInitializing(), CHIP_ERROR_INCORRECT_STATE);
+
+        RegisterPOSIXErrorFormatter();
+
+#if CHIP_SYSTEM_CONFIG_USE_SOCKETS
+        mSocketWatchPool.Clear();
+#endif
+
+        VerifyOrReturnError(mLayerState.SetInitialized(), CHIP_ERROR_INCORRECT_STATE);
+        return CHIP_NO_ERROR;
+    }
+
+    void LayerImplDispatch::Shutdown()
+    {
+        VerifyOrReturn(mLayerState.SetShuttingDown());
+
+        TimerList::Node * timer;
+        while ((timer = mTimerList.PopEarliest()) != nullptr) {
+            DisableTimer(__func__, timer);
+        }
+        mTimerPool.ReleaseAll();
+
+#if CHIP_SYSTEM_CONFIG_USE_SOCKETS
+        mSocketWatchPool.Clear();
+#endif
+
+        mLayerState.ResetFromShuttingDown(); // Return to uninitialized state to permit re-initialization.
+    }
+
+    CHIP_ERROR LayerImplDispatch::ScheduleWorkWithBlock(dispatch_block_t block)
+    {
+#if SYSTEM_LAYER_IMPL_DISPATCH_DEBUG
+        ChipLogError(Inet, "%s (block: %p)", __func__, block);
+#endif
+
+        VerifyOrReturnError(mLayerState.IsInitialized(), CHIP_ERROR_INCORRECT_STATE);
+
+        __auto_type dispatchQueue = GetDispatchQueue();
+#if !CONFIG_BUILD_FOR_HOST_UNIT_TEST
+        VerifyOrDie(nullptr != dispatchQueue);
+#endif
+
+        if (dispatchQueue) {
+            dispatch_async(dispatchQueue, ^{
+                block();
+            });
+            return CHIP_NO_ERROR;
+        }
+
+        TimerCompleteBlockCallbackContext * ctx = new TimerCompleteBlockCallbackContext { .block = block };
+        VerifyOrReturnError(ctx != nullptr, CHIP_ERROR_NO_MEMORY);
+        return ScheduleWork(TimerCompleteBlockCallback, ctx);
+    }
+
+    CHIP_ERROR LayerImplDispatch::ScheduleWork(TimerCompleteCallback onComplete, void * appState)
+    {
+#if SYSTEM_LAYER_IMPL_DISPATCH_DEBUG
+        ChipLogError(Inet, "%s (onComplete: %p - appState: %p)", __func__, onComplete, appState);
+#endif
+        return StartTimer(System::Clock::kZero, onComplete, appState, false /* shouldCancel */);
+    }
+
+    CHIP_ERROR LayerImplDispatch::StartTimer(Clock::Timeout delay, TimerCompleteCallback onComplete, void * appState)
+    {
+#if SYSTEM_LAYER_IMPL_DISPATCH_DEBUG
+        ChipLogError(Inet, "%s (onComplete: %p - appState: %p)", __func__, onComplete, appState);
+#endif
+        return StartTimer(delay, onComplete, appState, true /* shouldCancel */);
+    }
+
+    CHIP_ERROR LayerImplDispatch::StartTimer(Clock::Timeout delay, TimerCompleteCallback onComplete, void * appState, bool shouldCancel)
+    {
+        assertChipStackLockedByCurrentThread();
+
+        VerifyOrReturnError(mLayerState.IsInitialized(), CHIP_ERROR_INCORRECT_STATE);
+
+        CHIP_SYSTEM_FAULT_INJECT(FaultInjection::kFault_TimeoutImmediate, delay = System::Clock::kZero);
+
+        if (shouldCancel) {
+            CancelTimer(onComplete, appState);
+        }
+
+        dispatch_queue_t dispatchQueue = GetDispatchQueue();
+#if !CONFIG_BUILD_FOR_HOST_UNIT_TEST
+        VerifyOrReturnError(nullptr != dispatchQueue, CHIP_ERROR_INTERNAL);
+#endif
+
+        __auto_type * timer = mTimerPool.Create(*this, SystemClock().GetMonotonicTimestamp() + delay, onComplete, appState);
+        VerifyOrReturnError(timer != nullptr, CHIP_ERROR_NO_MEMORY);
+
+        if (dispatchQueue) {
+            __auto_type timerSource = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, DISPATCH_TIMER_STRICT, dispatchQueue);
+            VerifyOrDie(timerSource != nullptr);
+
+            timer->mTimerSource = timerSource;
+
+            dispatch_source_set_timer(
+                timerSource, dispatch_walltime(nullptr, static_cast<int64_t>(Clock::Milliseconds64(delay).count() * NSEC_PER_MSEC)),
+                DISPATCH_TIME_FOREVER, 2 * NSEC_PER_MSEC);
+
+            dispatch_source_set_event_handler(timerSource, ^{
+                DisableTimer(__func__, timer);
+                mTimerList.Remove(timer);
+                mTimerPool.Invoke(timer);
+            });
+
+            EnableTimer(__func__, timer);
+        }
+
+        (void) mTimerList.Add(timer);
+        return CHIP_NO_ERROR;
+    }
+
+    CHIP_ERROR LayerImplDispatch::ExtendTimerTo(Clock::Timeout delay, TimerCompleteCallback onComplete, void * appState)
+    {
+#if SYSTEM_LAYER_IMPL_DISPATCH_DEBUG
+        ChipLogError(Inet, "%s (onComplete: %p - appState: %p)", __func__, onComplete, appState);
+#endif
+        VerifyOrReturnError(delay.count() > 0, CHIP_ERROR_INVALID_ARGUMENT);
+
+        assertChipStackLockedByCurrentThread();
+
+        Clock::Timeout remainingTime = mTimerList.GetRemainingTime(onComplete, appState);
+        if (remainingTime.count() < delay.count()) {
+            if (remainingTime == Clock::kZero) {
+                // If remaining time is Clock::kZero, it might possible that our timer is in
+                // the mExpiredTimers list and about to be fired. Remove it from that list, since we are extending it.
+                __auto_type * timer = mExpiredTimers.Remove(onComplete, appState);
+                if (nullptr != timer) {
+                    DisableTimer(__func__, timer);
+                    mTimerPool.Release(timer);
+                }
+            }
+            return StartTimer(delay, onComplete, appState);
+        }
+
+        return CHIP_NO_ERROR;
+    }
+
+    bool LayerImplDispatch::IsTimerActive(TimerCompleteCallback onComplete, void * appState)
+    {
+#if SYSTEM_LAYER_IMPL_DISPATCH_DEBUG
+        ChipLogError(Inet, "%s (onComplete: %p - appState: %p)", __func__, onComplete, appState);
+#endif
+        bool timerIsActive = (mTimerList.GetRemainingTime(onComplete, appState) > Clock::kZero);
+
+        if (!timerIsActive) {
+            // check if the timer is in the mExpiredTimers list about to be fired.
+            for (TimerList::Node * timer = mExpiredTimers.Earliest(); timer != nullptr; timer = timer->mNextTimer) {
+                if (timer->GetCallback().GetOnComplete() == onComplete && timer->GetCallback().GetAppState() == appState) {
+                    return true;
+                }
+            }
+        }
+
+        return timerIsActive;
+    }
+
+    Clock::Timeout LayerImplDispatch::GetRemainingTime(TimerCompleteCallback onComplete, void * appState)
+    {
+#if SYSTEM_LAYER_IMPL_DISPATCH_DEBUG
+        ChipLogError(Inet, "%s (onComplete: %p - appState: %p)", __func__, onComplete, appState);
+#endif
+        return mTimerList.GetRemainingTime(onComplete, appState);
+    }
+
+    void LayerImplDispatch::CancelTimer(TimerCompleteCallback onComplete, void * appState)
+    {
+#if SYSTEM_LAYER_IMPL_DISPATCH_DEBUG
+        ChipLogError(Inet, "%s (onComplete: %p - appState: %p)", __func__, onComplete, appState);
+#endif
+        assertChipStackLockedByCurrentThread();
+
+        VerifyOrReturn(mLayerState.IsInitialized());
+
+        __auto_type * timer = mTimerList.Remove(onComplete, appState);
+        if (timer == nullptr) {
+            // The timer was not in our "will fire in the future" list, but it might
+            // be in the "we're about to fire these" chunk we already grabbed from
+            // that list.  Check for it there too, and if found there we still want
+            // to cancel it.
+            timer = mExpiredTimers.Remove(onComplete, appState);
+        }
+        VerifyOrReturn(timer != nullptr);
+
+        DisableTimer(__func__, timer);
+        mTimerPool.Release(timer);
+    }
+
+    void LayerImplDispatch::HandleDispatchQueueEvents(Clock::Timeout timeout)
+    {
+#if CHIP_SYSTEM_CONFIG_USE_SOCKETS
+        HandleSocketsAndTimerEvents(timeout);
+#else
+        HandleTimerEvents(timeout);
+#endif
+    }
+
+    void LayerImplDispatch::HandleTimerEvents(Clock::Timeout timeout)
+    {
+#if CONFIG_BUILD_FOR_HOST_UNIT_TEST
+        // Obtain the list of currently expired timers. Any new timers added by timer callback are NOT handled on this pass,
+        // since that could result in infinite handling of new timers blocking any other progress.
+        VerifyOrDieWithMsg(mExpiredTimers.Empty(), DeviceLayer, "Re-entry into HandleEvents from a timer callback?");
+        mExpiredTimers = mTimerList.ExtractEarlier(Clock::Timeout(1) + SystemClock().GetMonotonicTimestamp());
+        TimerList::Node * timer = nullptr;
+        while ((timer = mExpiredTimers.PopEarliest()) != nullptr) {
+            DisableTimer(__func__, timer);
+            mTimerPool.Invoke(timer);
+        }
+#endif
+    }
+} // namespace System
+} // namespace chip
diff --git a/src/platform/Darwin/system/SystemLayerImplDispatchSockets.mm b/src/platform/Darwin/system/SystemLayerImplDispatchSockets.mm
new file mode 100644
index 0000000..f9620aa
--- /dev/null
+++ b/src/platform/Darwin/system/SystemLayerImplDispatchSockets.mm
@@ -0,0 +1,293 @@
+/*
+ *
+ *    Copyright (c) 2025 Project CHIP Authors
+ *
+ *    Licensed under the Apache License, Version 2.0 (the "License");
+ *    you may not use this file except in compliance with the License.
+ *    You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *    Unless required by applicable law or agreed to in writing, software
+ *    distributed under the License is distributed on an "AS IS" BASIS,
+ *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *    See the License for the specific language governing permissions and
+ *    limitations under the License.
+ */
+
+/**
+ *    @file
+ *      This file implements Layer using dispatch and sockets.
+ */
+
+#if !__has_feature(objc_arc)
+#error This file must be compiled with ARC. Use -fobjc-arc flag (or convert project to ARC).
+#endif
+
+#include <system/SystemLayerImplDispatch.h>
+
+#include <lib/support/CodeUtils.h>
+
+namespace chip {
+namespace System {
+    CHIP_ERROR LayerImplDispatch::StartWatchingSocket(int fd, SocketWatchToken * tokenOut)
+    {
+        SocketWatch * watch = nullptr;
+
+        ReturnErrorOnFailure(mSocketWatchPool.Allocate(fd, watch));
+        *tokenOut = reinterpret_cast<SocketWatchToken>(watch);
+
+        return CHIP_NO_ERROR;
+    }
+
+    CHIP_ERROR LayerImplDispatch::StopWatchingSocket(SocketWatchToken * tokenInOut)
+    {
+        __auto_type * watch = reinterpret_cast<SocketWatch *>(*tokenInOut);
+
+        ReturnErrorOnFailure(mSocketWatchPool.Release(watch));
+        *tokenInOut = InvalidSocketWatchToken();
+
+        return CHIP_NO_ERROR;
+    }
+
+    CHIP_ERROR LayerImplDispatch::SetCallback(SocketWatchToken token, SocketWatchCallback callback, intptr_t data)
+    {
+        __auto_type * watch = reinterpret_cast<SocketWatch *>(token);
+        VerifyOrReturnError(nullptr != watch, CHIP_ERROR_INVALID_ARGUMENT);
+
+        watch->mCallback = callback;
+        watch->mCallbackData = data;
+        return CHIP_NO_ERROR;
+    }
+
+    CHIP_ERROR LayerImplDispatch::RequestCallback(SocketWatchToken token, SocketEventFlags flag)
+    {
+        __auto_type * watch = reinterpret_cast<SocketWatch *>(token);
+        VerifyOrReturnError(nullptr != watch, CHIP_ERROR_INVALID_ARGUMENT);
+
+        watch->mPendingIO.Set(flag);
+
+        __auto_type & source = (flag == SocketEventFlags::kRead) ? watch->mRdSource : watch->mWrSource;
+        __auto_type sourceType = (flag == SocketEventFlags::kRead) ? DISPATCH_SOURCE_TYPE_READ : DISPATCH_SOURCE_TYPE_WRITE;
+
+        VerifyOrReturnError(nullptr == source, CHIP_NO_ERROR);
+
+        // First time requesting callback for those events: install a dispatch source
+        __auto_type dispatchQueue = GetDispatchQueue();
+
+#if CONFIG_BUILD_FOR_HOST_UNIT_TEST
+        // Note: if no dispatch queue is available, callbacks most probably will not work, unless,
+        //       as in some tests from a test-specific local loop, the select based event handling is invoked.
+        VerifyOrReturnError(nullptr != dispatchQueue, CHIP_NO_ERROR);
+#else
+        VerifyOrDie(nullptr != dispatchQueue);
+#endif
+
+        source = dispatch_source_create(sourceType, static_cast<uintptr_t>(watch->mFD), 0, dispatchQueue);
+        VerifyOrReturnError(nullptr != source, CHIP_ERROR_NO_MEMORY);
+
+        dispatch_source_set_event_handler(source, ^{
+            if (watch->mPendingIO.Has(flag) && watch->mCallback != nullptr) {
+                SocketEvents events;
+                events.Set(flag);
+                watch->mCallback(events, watch->mCallbackData);
+            }
+        });
+        // only now we are sure the source exists and can become active
+        dispatch_activate(source);
+
+        return CHIP_NO_ERROR;
+    }
+
+    CHIP_ERROR LayerImplDispatch::ClearCallback(SocketWatchToken token, SocketEventFlags flag)
+    {
+        __auto_type * watch = reinterpret_cast<SocketWatch *>(token);
+        VerifyOrReturnError(nullptr != watch, CHIP_ERROR_INVALID_ARGUMENT);
+
+        watch->mPendingIO.Clear(flag);
+
+        return CHIP_NO_ERROR;
+    }
+
+    CHIP_ERROR LayerImplDispatch::RequestCallbackOnPendingRead(SocketWatchToken token)
+    {
+        return RequestCallback(token, SocketEventFlags::kRead);
+    }
+
+    CHIP_ERROR LayerImplDispatch::RequestCallbackOnPendingWrite(SocketWatchToken token)
+    {
+        return RequestCallback(token, SocketEventFlags::kWrite);
+    }
+
+    CHIP_ERROR LayerImplDispatch::ClearCallbackOnPendingRead(SocketWatchToken token)
+    {
+        return ClearCallback(token, SocketEventFlags::kRead);
+    }
+
+    CHIP_ERROR LayerImplDispatch::ClearCallbackOnPendingWrite(SocketWatchToken token)
+    {
+        return ClearCallback(token, SocketEventFlags::kWrite);
+    }
+
+#if !CONFIG_BUILD_FOR_HOST_UNIT_TEST
+    void LayerImplDispatch::HandleSocketsAndTimerEvents(Clock::Timeout timeout) {};
+    bool LayerImplDispatch::SocketWatchPool::PrepareEvents(SelectSets & sets, timeval timeout) const { return false; };
+    void LayerImplDispatch::SocketWatchPool::HandleEvents(const SelectSets & sets) const {};
+    void LayerImplDispatch::SocketWatch::PrepareEvents(SelectSets & sets, int & maxFd) const {};
+    void LayerImplDispatch::SocketWatch::HandleEvents(const SelectSets & sets) const {};
+#else
+    void LayerImplDispatch::HandleSocketsAndTimerEvents(Clock::Timeout timeout)
+    {
+        const Clock::Timestamp currentTime = SystemClock().GetMonotonicTimestamp();
+        Clock::Timestamp awakenTime = currentTime + timeout;
+
+        TimerList::Node * timer = mTimerList.Earliest();
+        if (timer) {
+            awakenTime = std::min(awakenTime, timer->AwakenTime());
+        }
+
+        const Clock::Timestamp sleepTime = (awakenTime > currentTime) ? (awakenTime - currentTime) : Clock::kZero;
+        timeval nextTimeout;
+        Clock::ToTimeval(sleepTime, nextTimeout);
+
+        SelectSets sets;
+        bool hasEvents = mSocketWatchPool.PrepareEvents(sets, nextTimeout);
+
+        HandleTimerEvents(timeout);
+
+        if (hasEvents) {
+            mSocketWatchPool.HandleEvents(sets);
+        }
+    }
+
+    bool LayerImplDispatch::SocketWatchPool::PrepareEvents(SelectSets & sets, timeval timeout) const
+    {
+        // NOLINTBEGIN(clang-analyzer-security.insecureAPI.bzero)
+        //
+        // NOTE: darwin uses bzero to clear out FD sets. This is not a security concern.
+        FD_ZERO(&sets.mReadSet);
+        FD_ZERO(&sets.mWriteSet);
+        FD_ZERO(&sets.mErrorSet);
+        // NOLINTEND(clang-analyzer-security.insecureAPI.bzero)
+
+        int maxFd = kInvalidFd;
+        for (auto & w : mPool) {
+            if (w.IsActive()) {
+                w.PrepareEvents(sets, maxFd);
+            }
+        }
+        VerifyOrReturnValue(kInvalidFd != maxFd, false);
+
+        int selectResult = select(maxFd + 1, &sets.mReadSet, &sets.mWriteSet, &sets.mErrorSet, &timeout);
+        VerifyOrReturnValue(selectResult > 0, false);
+
+        return true;
+    }
+
+    void LayerImplDispatch::SocketWatchPool::HandleEvents(const SelectSets & sets) const
+    {
+        for (auto & w : mPool) {
+            if (w.IsActive()) {
+                w.HandleEvents(sets);
+            }
+        }
+    }
+
+    void LayerImplDispatch::SocketWatch::PrepareEvents(SelectSets & sets, int & maxFd) const
+    {
+        if (mFD > maxFd) {
+            maxFd = mFD;
+        }
+
+        if (mPendingIO.Has(SocketEventFlags::kRead)) {
+            FD_SET(mFD, &sets.mReadSet);
+        }
+
+        if (mPendingIO.Has(SocketEventFlags::kWrite)) {
+            FD_SET(mFD, &sets.mWriteSet);
+        }
+    }
+
+    void LayerImplDispatch::SocketWatch::HandleEvents(const SelectSets & sets) const
+    {
+        VerifyOrReturn(nullptr != mCallback);
+
+        SocketEvents events;
+
+        // POSIX does not define the fd_set parameter of FD_ISSET() as const, even though it isn't modified.
+        if (FD_ISSET(mFD, const_cast<fd_set *>(&sets.mReadSet))) {
+            events.Set(SocketEventFlags::kRead);
+        }
+
+        if (FD_ISSET(mFD, const_cast<fd_set *>(&sets.mWriteSet))) {
+            events.Set(SocketEventFlags::kWrite);
+        }
+
+        if (FD_ISSET(mFD, const_cast<fd_set *>(&sets.mErrorSet))) {
+            events.Set(SocketEventFlags::kExcept);
+        }
+
+        VerifyOrReturn(events.HasAny());
+
+        mCallback(events, mCallbackData);
+    }
+#endif
+
+    CHIP_ERROR LayerImplDispatch::SocketWatchPool::Allocate(int fd, SocketWatch *& outWatch)
+    {
+        // 1) See if we already have one for this fd.
+        for (auto & w : mPool) {
+            if (w.mFD == fd) {
+                outWatch = &w;
+                return CHIP_NO_ERROR;
+            }
+        }
+
+        // 2) Otherwise find a free slot.
+        for (auto & w : mPool) {
+            if (!w.IsActive()) {
+                w.mFD = fd;
+                outWatch = &w;
+                return CHIP_NO_ERROR;
+            }
+        }
+
+        return CHIP_ERROR_ENDPOINT_POOL_FULL;
+    }
+
+    CHIP_ERROR LayerImplDispatch::SocketWatchPool::Release(SocketWatch * watch)
+    {
+        VerifyOrReturnError(nullptr != watch, CHIP_ERROR_INVALID_ARGUMENT);
+        VerifyOrReturnError(watch->IsActive(), CHIP_ERROR_INVALID_ARGUMENT);
+
+        watch->Clear();
+        return CHIP_NO_ERROR;
+    }
+
+    void LayerImplDispatch::SocketWatchPool::Clear()
+    {
+        for (auto & w : mPool) {
+            w.Clear();
+        }
+    }
+
+    void LayerImplDispatch::SocketWatch::Clear()
+    {
+        mFD = kInvalidFd;
+        mPendingIO.ClearAll();
+        mCallback = nullptr;
+        mCallbackData = 0;
+
+        if (mRdSource) {
+            dispatch_source_cancel(mRdSource);
+            mRdSource = nullptr;
+        }
+
+        if (mWrSource) {
+            dispatch_source_cancel(mWrSource);
+            mWrSource = nullptr;
+        }
+    }
+
+} // namespace System
+} // namespace chip
diff --git a/src/system/BUILD.gn b/src/system/BUILD.gn
index 0c62118..2052e7e 100644
--- a/src/system/BUILD.gn
+++ b/src/system/BUILD.gn
@@ -249,10 +249,14 @@
     # or
     #    - SystemLayerImplSelect.h
     #    - SystemLayerImplSelect.cpp
-    sources += [
-      "SystemLayerImpl${chip_system_config_event_loop}.cpp",
-      "SystemLayerImpl${chip_system_config_event_loop}.h",
-    ]
+    if (chip_system_config_use_dispatch) {
+      sources += [ "${chip_root}/src/platform/Darwin/system/SystemLayerImpl${chip_system_config_event_loop}.h" ]
+    } else {
+      sources += [
+        "SystemLayerImpl${chip_system_config_event_loop}.cpp",
+        "SystemLayerImpl${chip_system_config_event_loop}.h",
+      ]
+    }
   }
 
   cflags = [ "-Wconversion" ]
diff --git a/src/system/SystemLayer.h b/src/system/SystemLayer.h
index 0890392..c49c753 100644
--- a/src/system/SystemLayer.h
+++ b/src/system/SystemLayer.h
@@ -40,10 +40,10 @@
 #include <system/SystemError.h>
 #include <system/SystemEvent.h>
 
-#if CHIP_SYSTEM_CONFIG_USE_SOCKETS || CHIP_SYSTEM_CONFIG_USE_NETWORK_FRAMEWORK
+#if CHIP_SYSTEM_CONFIG_USE_SOCKETS
 #include <lib/support/IntrusiveList.h>
 #include <system/SocketEvents.h>
-#endif // CHIP_SYSTEM_CONFIG_USE_SOCKETS || CHIP_SYSTEM_USE_NETWORK_FRAMEWORK
+#endif // CHIP_SYSTEM_CONFIG_USE_SOCKETS
 
 #if CHIP_SYSTEM_CONFIG_USE_DISPATCH
 #include <dispatch/dispatch.h>
@@ -235,8 +235,7 @@
 
 #endif // CHIP_SYSTEM_CONFIG_USE_LWIP
 
-#if CHIP_SYSTEM_CONFIG_USE_SOCKETS || CHIP_SYSTEM_CONFIG_USE_NETWORK_FRAMEWORK
-
+#if CHIP_SYSTEM_CONFIG_USE_SOCKETS
 class LayerSockets : public Layer
 {
 public:
@@ -338,25 +337,36 @@
     virtual void HandleEvents()    = 0;
     virtual void EventLoopEnds()   = 0;
 
-#if !CHIP_SYSTEM_CONFIG_USE_DISPATCH
     virtual void AddLoopHandler(EventLoopHandler & handler)    = 0;
     virtual void RemoveLoopHandler(EventLoopHandler & handler) = 0;
-#endif // !CHIP_SYSTEM_CONFIG_USE_DISPATCH
 
-#if CHIP_SYSTEM_CONFIG_USE_DISPATCH
-    virtual void SetDispatchQueue(dispatch_queue_t dispatchQueue) = 0;
-    virtual dispatch_queue_t GetDispatchQueue()                   = 0;
-#elif CHIP_SYSTEM_CONFIG_USE_LIBEV
+#if CHIP_SYSTEM_CONFIG_USE_LIBEV
     virtual void SetLibEvLoop(struct ev_loop * aLibEvLoopP) = 0;
     virtual struct ev_loop * GetLibEvLoop()                 = 0;
-#endif // CHIP_SYSTEM_CONFIG_USE_DISPATCH/LIBEV
+#endif // CHIP_SYSTEM_CONFIG_USE_LIBEV
 
 protected:
     // Expose EventLoopHandler.mState as a non-const reference to sub-classes
     decltype(EventLoopHandler::mState) & LoopHandlerState(EventLoopHandler & handler) { return handler.mState; }
 };
 
-#endif // CHIP_SYSTEM_CONFIG_USE_SOCKETS || CHIP_SYSTEM_CONFIG_USE_NETWORK_FRAMEWORK
+#endif // CHIP_SYSTEM_CONFIG_USE_SOCKETS
+
+#if CHIP_SYSTEM_CONFIG_USE_DISPATCH
+class LayerDispatch :
+#if CHIP_SYSTEM_CONFIG_USE_SOCKETS
+    public LayerSockets
+#else
+    public Layer
+#endif
+{
+public:
+    virtual void SetDispatchQueue(dispatch_queue_t dispatchQueue)    = 0;
+    virtual dispatch_queue_t GetDispatchQueue()                      = 0;
+    virtual void HandleDispatchQueueEvents(Clock::Timeout timeout)   = 0;
+    virtual CHIP_ERROR ScheduleWorkWithBlock(dispatch_block_t block) = 0;
+};
+#endif
 
 } // namespace System
 } // namespace chip
diff --git a/src/system/SystemLayerImplDispatch.h b/src/system/SystemLayerImplDispatch.h
new file mode 100644
index 0000000..9792d6b
--- /dev/null
+++ b/src/system/SystemLayerImplDispatch.h
@@ -0,0 +1,23 @@
+/*
+ *
+ *    Copyright (c) 2025 Project CHIP Authors
+ *
+ *    Licensed under the Apache License, Version 2.0 (the "License");
+ *    you may not use this file except in compliance with the License.
+ *    You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *    Unless required by applicable law or agreed to in writing, software
+ *    distributed under the License is distributed on an "AS IS" BASIS,
+ *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *    See the License for the specific language governing permissions and
+ *    limitations under the License.
+ */
+
+/**
+ *    @file
+ *      This file declares an implementation of System::Layer using select().
+ */
+
+#include <platform/Darwin/system/SystemLayerImplDispatch.h>
diff --git a/src/system/SystemLayerImplSelect.cpp b/src/system/SystemLayerImplSelect.cpp
index 52354f9..a6c9580 100644
--- a/src/system/SystemLayerImplSelect.cpp
+++ b/src/system/SystemLayerImplSelect.cpp
@@ -67,10 +67,10 @@
     mHandleSelectThread = PTHREAD_NULL;
 #endif // CHIP_SYSTEM_CONFIG_POSIX_LOCKING
 
-#if !CHIP_SYSTEM_CONFIG_USE_LIBEV && !CHIP_SYSTEM_CONFIG_USE_NETWORK_FRAMEWORK
+#if !CHIP_SYSTEM_CONFIG_USE_LIBEV
     // Create an event to allow an arbitrary thread to wake the thread in the select loop.
     ReturnErrorOnFailure(mWakeEvent.Open(*this));
-#endif // !CHIP_SYSTEM_CONFIG_USE_LIBEV && !CHIP_SYSTEM_CONFIG_USE_NETWORK_FRAMEWORK
+#endif // !CHIP_SYSTEM_CONFIG_USE_LIBEV
 
     VerifyOrReturnError(mLayerState.SetInitialized(), CHIP_ERROR_INCORRECT_STATE);
     return CHIP_NO_ERROR;
@@ -80,23 +80,7 @@
 {
     VerifyOrReturn(mLayerState.SetShuttingDown());
 
-#if CHIP_SYSTEM_CONFIG_USE_DISPATCH
-    TimerList::Node * timer;
-    while ((timer = mTimerList.PopEarliest()) != nullptr)
-    {
-        if (timer->mTimerSource != nullptr)
-        {
-            dispatch_source_cancel(timer->mTimerSource);
-            dispatch_release(timer->mTimerSource);
-        }
-    }
-    mTimerPool.ReleaseAll();
-
-    for (auto & w : mSocketWatchPool)
-    {
-        w.DisableAndClear();
-    }
-#elif CHIP_SYSTEM_CONFIG_USE_LIBEV
+#if CHIP_SYSTEM_CONFIG_USE_LIBEV
     TimerList::Node * timer;
     while ((timer = mTimerList.PopEarliest()) != nullptr)
     {
@@ -114,11 +98,11 @@
 #else
     mTimerList.Clear();
     mTimerPool.ReleaseAll();
-#endif // CHIP_SYSTEM_CONFIG_USE_DISPATCH/LIBEV
+#endif // CHIP_SYSTEM_CONFIG_USE_LIBEV
 
-#if !CHIP_SYSTEM_CONFIG_USE_LIBEV && !CHIP_SYSTEM_CONFIG_USE_NETWORK_FRAMEWORK
+#if !CHIP_SYSTEM_CONFIG_USE_LIBEV
     mWakeEvent.Close(*this);
-#endif // !CHIP_SYSTEM_CONFIG_USE_LIBEV && !CHIP_SYSTEM_CONFIG_USE_NETWORK_FRAMEWORK
+#endif // !CHIP_SYSTEM_CONFIG_USE_LIBEV
 
     mLayerState.ResetFromShuttingDown(); // Return to uninitialized state to permit re-initialization.
 }
@@ -127,8 +111,6 @@
 {
 #if CHIP_SYSTEM_CONFIG_USE_LIBEV
     ChipLogError(DeviceLayer, "Signal() should not be called in CHIP_SYSTEM_CONFIG_USE_LIBEV builds (might be ok in tests)");
-#elif CHIP_SYSTEM_CONFIG_USE_NETWORK_FRAMEWORK
-    ChipLogError(DeviceLayer, "Signal() should not be called in CHIP_SYSTEM_CONFIG_USE_NETWORK_FRAMEWORK builds");
 #else
     /*
      * Wake up the I/O thread by writing a single byte to the wake pipe.
@@ -153,7 +135,7 @@
 
         ChipLogError(chipSystemLayer, "System wake event notify failed: %" CHIP_ERROR_FORMAT, status.Format());
     }
-#endif // !CHIP_SYSTEM_CONFIG_USE_LIBEV && !CHIP_SYSTEM_CONFIG_USE_NETWORK_FRAMEWORK
+#endif // !CHIP_SYSTEM_CONFIG_USE_LIBEV
 }
 
 CHIP_ERROR LayerImplSelect::StartTimer(Clock::Timeout delay, TimerCompleteCallback onComplete, void * appState)
@@ -169,31 +151,7 @@
     TimerList::Node * timer = mTimerPool.Create(*this, SystemClock().GetMonotonicTimestamp() + delay, onComplete, appState);
     VerifyOrReturnError(timer != nullptr, CHIP_ERROR_NO_MEMORY);
 
-#if CHIP_SYSTEM_CONFIG_USE_DISPATCH
-    dispatch_queue_t dispatchQueue = GetDispatchQueue();
-    if (dispatchQueue)
-    {
-        (void) mTimerList.Add(timer);
-        dispatch_source_t timerSource = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, DISPATCH_TIMER_STRICT, dispatchQueue);
-        VerifyOrDie(timerSource != nullptr);
-
-        timer->mTimerSource = timerSource;
-        dispatch_source_set_timer(
-            timerSource, dispatch_walltime(nullptr, static_cast<int64_t>(Clock::Milliseconds64(delay).count() * NSEC_PER_MSEC)),
-            DISPATCH_TIME_FOREVER, 2 * NSEC_PER_MSEC);
-        dispatch_source_set_event_handler(timerSource, ^{
-            dispatch_source_cancel(timerSource);
-            dispatch_release(timerSource);
-
-            this->HandleTimerComplete(timer);
-        });
-        dispatch_resume(timerSource);
-        return CHIP_NO_ERROR;
-    }
-#if CHIP_SYSTEM_CONFIG_USE_NETWORK_FRAMEWORK
-    return CHIP_ERROR_INTERNAL;
-#endif // CHIP_SYSTEM_CONFIG_USE_NETWORK_FRAMEWORK
-#elif CHIP_SYSTEM_CONFIG_USE_LIBEV
+#if CHIP_SYSTEM_CONFIG_USE_LIBEV
     VerifyOrDie(mLibEvLoopP != nullptr);
     ev_timer_init(&timer->mLibEvTimer, &LayerImplSelect::HandleLibEvTimer, 1, 0);
     timer->mLibEvTimer.data = timer;
@@ -208,20 +166,15 @@
     ev_timer_set(&timer->mLibEvTimer, (static_cast<double>(t) / 1E3) + ev_time() - ev_now(mLibEvLoopP), 0.);
     (void) mTimerList.Add(timer);
     ev_timer_start(mLibEvLoopP, &timer->mLibEvTimer);
-    return CHIP_NO_ERROR;
-#endif
-#if !CHIP_SYSTEM_CONFIG_USE_LIBEV && !CHIP_SYSTEM_CONFIG_USE_NETWORK_FRAMEWORK
-    // Note: The dispatch-based implementation (using sockets but not Network.framework) requires this as a fallback
-    // for testing purposes. However, it is not needed for LIBEV or when using Network.framework (which lacks a testing
-    // configuration).  Since dead code is also not allowed with -Werror, we need to ifdef this code out
-    // in those configurations.
+#else
     if (mTimerList.Add(timer) == timer)
     {
         // The new timer is the earliest, so the time until the next event has probably changed.
         Signal();
     }
+#endif // CHIP_SYSTEM_CONFIG_USE_LIBEV
+
     return CHIP_NO_ERROR;
-#endif // !CHIP_SYSTEM_CONFIG_USE_LIBEV && !CHIP_SYSTEM_CONFIG_USE_NETWORK_FRAMEWORK
 }
 
 CHIP_ERROR LayerImplSelect::ExtendTimerTo(Clock::Timeout delay, TimerCompleteCallback onComplete, void * appState)
@@ -286,20 +239,14 @@
     }
     VerifyOrReturn(timer != nullptr);
 
-#if CHIP_SYSTEM_CONFIG_USE_DISPATCH
-    if (timer->mTimerSource != nullptr)
-    {
-        dispatch_source_cancel(timer->mTimerSource);
-        dispatch_release(timer->mTimerSource);
-    }
-#elif CHIP_SYSTEM_CONFIG_USE_LIBEV
+#if CHIP_SYSTEM_CONFIG_USE_LIBEV
     VerifyOrDie(mLibEvLoopP != nullptr);
     ev_timer_stop(mLibEvLoopP, &timer->mLibEvTimer);
-#endif // CHIP_SYSTEM_CONFIG_USE_DISPATCH/LIBEV
+#endif // CHIP_SYSTEM_CONFIG_USE_LIBEV
 
     mTimerPool.Release(timer);
-#if !CHIP_SYSTEM_CONFIG_USE_LIBEV && !CHIP_SYSTEM_CONFIG_USE_NETWORK_FRAMEWORK
-    // Neither LIBEV nor CHIP_SYSTEM_CONFIG_USE_NETWORK_FRAMEWORK builds include an I/O wakeup thread, so must not call Signal().
+#if !CHIP_SYSTEM_CONFIG_USE_LIBEV
+    // LIBEV builds does not include an I/O wakeup thread, so must not call Signal().
     Signal();
 #endif
 }
@@ -310,19 +257,7 @@
 
     VerifyOrReturnError(mLayerState.IsInitialized(), CHIP_ERROR_INCORRECT_STATE);
 
-#if CHIP_SYSTEM_CONFIG_USE_DISPATCH
-    dispatch_queue_t dispatchQueue = GetDispatchQueue();
-    if (dispatchQueue)
-    {
-        dispatch_async(dispatchQueue, ^{
-            onComplete(this, appState);
-        });
-        return CHIP_NO_ERROR;
-    }
-#if CHIP_SYSTEM_CONFIG_USE_NETWORK_FRAMEWORK
-    return CHIP_ERROR_INTERNAL;
-#endif // CHIP_SYSTEM_CONFIG_USE_NETWORK_FRAMEWORK
-#elif CHIP_SYSTEM_CONFIG_USE_LIBEV
+#if CHIP_SYSTEM_CONFIG_USE_LIBEV
     // schedule as timer with no delay, but do NOT cancel previous timers with same onComplete/appState!
     TimerList::Node * timer = mTimerPool.Create(*this, SystemClock().GetMonotonicTimestamp(), onComplete, appState);
     VerifyOrReturnError(timer != nullptr, CHIP_ERROR_NO_MEMORY);
@@ -333,13 +268,7 @@
     ev_timer_set(&timer->mLibEvTimer, static_cast<double>(t) / 1E3, 0.);
     (void) mTimerList.Add(timer);
     ev_timer_start(mLibEvLoopP, &timer->mLibEvTimer);
-    return CHIP_NO_ERROR;
-#endif // CHIP_SYSTEM_CONFIG_USE_DISPATCH/LIBEV
-#if !CHIP_SYSTEM_CONFIG_USE_LIBEV && !CHIP_SYSTEM_CONFIG_USE_NETWORK_FRAMEWORK
-    // Note: The dispatch-based implementation (using sockets but not Network.framework) requires this as a fallback
-    // for testing purposes. However, it is not needed for LIBEV or when using Network.framework (which lacks a testing
-    // configuration). Since dead code is also not allowed with -Werror, we need to ifdef this code out
-    // in those configurations.
+#else
     // Ideally we would not use a timer here at all, but if we try to just
     // ScheduleLambda the lambda needs to capture the following:
     // 1) onComplete
@@ -373,8 +302,9 @@
         // The new timer is the earliest, so the time until the next event has probably changed.
         Signal();
     }
+#endif // CHIP_SYSTEM_CONFIG_USE_LIBEV
+
     return CHIP_NO_ERROR;
-#endif // !CHIP_SYSTEM_CONFIG_USE_LIBEV && !CHIP_SYSTEM_CONFIG_USE_NETWORK_FRAMEWORK
 }
 
 CHIP_ERROR LayerImplSelect::StartWatchingSocket(int fd, SocketWatchToken * tokenOut)
@@ -424,37 +354,7 @@
 
     watch->mPendingIO.Set(SocketEventFlags::kRead);
 
-#if CHIP_SYSTEM_CONFIG_USE_DISPATCH
-    if (watch->mRdSource == nullptr)
-    {
-        // First time requesting callback for read events: install a dispatch source
-        dispatch_queue_t dispatchQueue = GetDispatchQueue();
-        if (dispatchQueue == nullptr)
-        {
-            // Note: if no dispatch queue is available, callbacks most probably will not work,
-            //       unless, as in some tests from a test-specific local loop,
-            //       the select based event handling (Prepare/WaitFor/HandleEvents) is invoked.
-            ChipLogError(DeviceLayer,
-                         "RequestCallbackOnPendingRead with no dispatch queue: callback may not work (might be ok in tests)");
-        }
-        else
-        {
-            watch->mRdSource =
-                dispatch_source_create(DISPATCH_SOURCE_TYPE_READ, static_cast<uintptr_t>(watch->mFD), 0, dispatchQueue);
-            VerifyOrReturnError(watch->mRdSource != nullptr, CHIP_ERROR_NO_MEMORY);
-            dispatch_source_set_event_handler(watch->mRdSource, ^{
-                if (watch->mPendingIO.Has(SocketEventFlags::kRead) && watch->mCallback != nullptr)
-                {
-                    SocketEvents events;
-                    events.Set(SocketEventFlags::kRead);
-                    watch->mCallback(events, watch->mCallbackData);
-                }
-            });
-            // only now we are sure the source exists and can become active
-            dispatch_activate(watch->mRdSource);
-        }
-    }
-#elif CHIP_SYSTEM_CONFIG_USE_LIBEV
+#if CHIP_SYSTEM_CONFIG_USE_LIBEV
     VerifyOrDie(mLibEvLoopP != nullptr);
     int evs = (watch->mPendingIO.Has(SocketEventFlags::kRead) ? EV_READ : 0) |
         (watch->mPendingIO.Has(SocketEventFlags::kWrite) ? EV_WRITE : 0);
@@ -472,7 +372,7 @@
         ev_io_modify(&watch->mIoWatcher, evs);
         ev_io_start(mLibEvLoopP, &watch->mIoWatcher);
     }
-#endif // CHIP_SYSTEM_CONFIG_USE_DISPATCH
+#endif // CHIP_SYSTEM_CONFIG_USE_LIBEV
 
     return CHIP_NO_ERROR;
 }
@@ -484,37 +384,7 @@
 
     watch->mPendingIO.Set(SocketEventFlags::kWrite);
 
-#if CHIP_SYSTEM_CONFIG_USE_DISPATCH
-    if (watch->mWrSource == nullptr)
-    {
-        // First time requesting callback for read events: install a dispatch source
-        dispatch_queue_t dispatchQueue = GetDispatchQueue();
-        if (dispatchQueue == nullptr)
-        {
-            // Note: if no dispatch queue is available, callbacks most probably will not work,
-            //       unless, as in some tests from a test-specific local loop,
-            //       the select based event handling (Prepare/WaitFor/HandleEvents) is invoked.
-            ChipLogError(DeviceLayer,
-                         "RequestCallbackOnPendingWrite with no dispatch queue: callback may not work (might be ok in tests)");
-        }
-        else
-        {
-            watch->mWrSource =
-                dispatch_source_create(DISPATCH_SOURCE_TYPE_WRITE, static_cast<uintptr_t>(watch->mFD), 0, dispatchQueue);
-            VerifyOrReturnError(watch->mWrSource != nullptr, CHIP_ERROR_NO_MEMORY);
-            dispatch_source_set_event_handler(watch->mWrSource, ^{
-                if (watch->mPendingIO.Has(SocketEventFlags::kWrite) && watch->mCallback != nullptr)
-                {
-                    SocketEvents events;
-                    events.Set(SocketEventFlags::kWrite);
-                    watch->mCallback(events, watch->mCallbackData);
-                }
-            });
-            // only now we are sure the source exists and can become active
-            dispatch_activate(watch->mWrSource);
-        }
-    }
-#elif CHIP_SYSTEM_CONFIG_USE_LIBEV
+#if CHIP_SYSTEM_CONFIG_USE_LIBEV
     VerifyOrDie(mLibEvLoopP != nullptr);
     int evs = (watch->mPendingIO.Has(SocketEventFlags::kRead) ? EV_READ : 0) |
         (watch->mPendingIO.Has(SocketEventFlags::kWrite) ? EV_WRITE : 0);
@@ -532,7 +402,7 @@
         ev_io_modify(&watch->mIoWatcher, evs);
         ev_io_start(mLibEvLoopP, &watch->mIoWatcher);
     }
-#endif // CHIP_SYSTEM_CONFIG_USE_DISPATCH
+#endif // CHIP_SYSTEM_CONFIG_USE_LIBEV
 
     return CHIP_NO_ERROR;
 }
@@ -581,7 +451,7 @@
     VerifyOrReturnError(watch != nullptr, CHIP_ERROR_INVALID_ARGUMENT);
     VerifyOrReturnError(watch->mFD >= 0, CHIP_ERROR_INCORRECT_STATE);
 
-#if CHIP_SYSTEM_CONFIG_USE_DISPATCH || CHIP_SYSTEM_CONFIG_USE_LIBEV
+#if CHIP_SYSTEM_CONFIG_USE_LIBEV
     watch->DisableAndClear();
 #else
     watch->Clear();
@@ -624,7 +494,6 @@
     return res;
 }
 
-#if !CHIP_SYSTEM_CONFIG_USE_DISPATCH
 enum : intptr_t
 {
     kLoopHandlerInactive = 0, // default value for EventLoopHandler::mState
@@ -648,7 +517,6 @@
     mLoopHandlers.Remove(&handler);
     LoopHandlerState(handler) = kLoopHandlerInactive;
 }
-#endif // !CHIP_SYSTEM_CONFIG_USE_DISPATCH
 
 void LayerImplSelect::PrepareEvents()
 {
@@ -663,7 +531,6 @@
         awakenTime = std::min(awakenTime, timer->AwakenTime());
     }
 
-#if !CHIP_SYSTEM_CONFIG_USE_DISPATCH
     // Activate added EventLoopHandlers and call PrepareEvents on active handlers.
     auto loopIter = mLoopHandlers.begin();
     while (loopIter != mLoopHandlers.end())
@@ -679,7 +546,6 @@
             break;
         }
     }
-#endif // !CHIP_SYSTEM_CONFIG_USE_DISPATCH
 
     const Clock::Timestamp sleepTime = (awakenTime > currentTime) ? (awakenTime - currentTime) : Clock::kZero;
     Clock::ToTimeval(sleepTime, mNextTimeout);
@@ -759,7 +625,6 @@
         }
     }
 
-#if !CHIP_SYSTEM_CONFIG_USE_DISPATCH
     // Call HandleEvents for active loop handlers
     auto loopIter = mLoopHandlers.begin();
     while (loopIter != mLoopHandlers.end())
@@ -770,22 +635,13 @@
             loop.HandleEvents();
         }
     }
-#endif // !CHIP_SYSTEM_CONFIG_USE_DISPATCH
 
 #if CHIP_SYSTEM_CONFIG_POSIX_LOCKING
     mHandleSelectThread = PTHREAD_NULL;
 #endif // CHIP_SYSTEM_CONFIG_POSIX_LOCKING
 }
 
-#if CHIP_SYSTEM_CONFIG_USE_DISPATCH
-
-void LayerImplSelect::HandleTimerComplete(TimerList::Node * timer)
-{
-    mTimerList.Remove(timer);
-    mTimerPool.Invoke(timer);
-}
-
-#elif CHIP_SYSTEM_CONFIG_USE_LIBEV
+#if CHIP_SYSTEM_CONFIG_USE_LIBEV
 
 void LayerImplSelect::HandleLibEvTimer(EV_P_ struct ev_timer * t, int revents)
 {
@@ -818,7 +674,7 @@
     }
 }
 
-#endif // CHIP_SYSTEM_CONFIG_USE_DISPATCH/LIBEV
+#endif // CHIP_SYSTEM_CONFIG_USE_LIBEV
 
 void LayerImplSelect::SocketWatch::Clear()
 {
@@ -826,30 +682,12 @@
     mPendingIO.ClearAll();
     mCallback     = nullptr;
     mCallbackData = 0;
-#if CHIP_SYSTEM_CONFIG_USE_DISPATCH
-    mRdSource = nullptr;
-    mWrSource = nullptr;
-#elif CHIP_SYSTEM_CONFIG_USE_LIBEV
+#if CHIP_SYSTEM_CONFIG_USE_LIBEV
     mLayerImplSelectP = nullptr;
 #endif
 }
 
-#if CHIP_SYSTEM_CONFIG_USE_DISPATCH
-void LayerImplSelect::SocketWatch::DisableAndClear()
-{
-    if (mRdSource)
-    {
-        dispatch_source_cancel(mRdSource);
-        dispatch_release(mRdSource);
-    }
-    if (mWrSource)
-    {
-        dispatch_source_cancel(mWrSource);
-        dispatch_release(mWrSource);
-    }
-    Clear();
-}
-#elif CHIP_SYSTEM_CONFIG_USE_LIBEV
+#if CHIP_SYSTEM_CONFIG_USE_LIBEV
 void LayerImplSelect::SocketWatch::DisableAndClear()
 {
     if (mLayerImplSelectP != nullptr && mLayerImplSelectP->mLibEvLoopP != nullptr)
@@ -858,7 +696,7 @@
     }
     Clear();
 }
-#endif // CHIP_SYSTEM_CONFIG_USE_DISPATCH/LIBEV
+#endif // CHIP_SYSTEM_CONFIG_USE_LIBEV
 
 } // namespace System
 } // namespace chip
diff --git a/src/system/SystemLayerImplSelect.h b/src/system/SystemLayerImplSelect.h
index dde12a3..fdc6d11 100644
--- a/src/system/SystemLayerImplSelect.h
+++ b/src/system/SystemLayerImplSelect.h
@@ -39,9 +39,6 @@
 
 #if CHIP_SYSTEM_CONFIG_USE_LIBEV
 #include <ev.h>
-#if CHIP_SYSTEM_CONFIG_USE_DISPATCH
-#error "CHIP_SYSTEM_CONFIG_USE_LIBEV and CHIP_SYSTEM_CONFIG_USE_DISPATCH are mutually exclusive"
-#endif
 #endif // CHIP_SYSTEM_CONFIG_USE_LIBEV
 
 #include <lib/support/ObjectLifeCycle.h>
@@ -87,21 +84,15 @@
     void HandleEvents() override;
     void EventLoopEnds() override {}
 
-#if !CHIP_SYSTEM_CONFIG_USE_DISPATCH
     void AddLoopHandler(EventLoopHandler & handler) override;
     void RemoveLoopHandler(EventLoopHandler & handler) override;
-#endif // !CHIP_SYSTEM_CONFIG_USE_DISPATCH
 
-#if CHIP_SYSTEM_CONFIG_USE_DISPATCH
-    void SetDispatchQueue(dispatch_queue_t dispatchQueue) override { mDispatchQueue = dispatchQueue; };
-    dispatch_queue_t GetDispatchQueue() override { return mDispatchQueue; };
-    void HandleTimerComplete(TimerList::Node * timer);
-#elif CHIP_SYSTEM_CONFIG_USE_LIBEV
+#if CHIP_SYSTEM_CONFIG_USE_LIBEV
     virtual void SetLibEvLoop(struct ev_loop * aLibEvLoopP) override { mLibEvLoopP = aLibEvLoopP; };
     virtual struct ev_loop * GetLibEvLoop() override { return mLibEvLoopP; };
     static void HandleLibEvTimer(EV_P_ struct ev_timer * t, int revents);
     static void HandleLibEvIoWatcher(EV_P_ struct ev_io * i, int revents);
-#endif // CHIP_SYSTEM_CONFIG_USE_DISPATCH/LIBEV
+#endif // CHIP_SYSTEM_CONFIG_USE_LIBEV
 
     // Expose the result of WaitForEvents() for non-blocking socket implementations.
     bool IsSelectResultValid() const { return mSelectResult >= 0; }
@@ -118,11 +109,6 @@
         int mFD;
         SocketEvents mPendingIO;
         SocketWatchCallback mCallback;
-#if CHIP_SYSTEM_CONFIG_USE_DISPATCH
-        dispatch_source_t mRdSource;
-        dispatch_source_t mWrSource;
-        void DisableAndClear();
-#endif
 #if CHIP_SYSTEM_CONFIG_USE_LIBEV
         struct ev_io mIoWatcher;
         LayerImplSelect * mLayerImplSelectP;
@@ -140,9 +126,7 @@
     TimerList mExpiredTimers;
     timeval mNextTimeout;
 
-#if !CHIP_SYSTEM_CONFIG_USE_DISPATCH
     IntrusiveList<EventLoopHandler> mLoopHandlers;
-#endif
 
     // Members for select loop
     struct SelectSets
@@ -158,7 +142,7 @@
     int mSelectResult;
 
     ObjectLifeCycle mLayerState;
-#if !CHIP_SYSTEM_CONFIG_USE_LIBEV && !CHIP_SYSTEM_CONFIG_USE_NETWORK_FRAMEWORK
+#if !CHIP_SYSTEM_CONFIG_USE_LIBEV
     WakeEvent mWakeEvent;
 #endif
 
@@ -166,9 +150,7 @@
     std::atomic<pthread_t> mHandleSelectThread;
 #endif // CHIP_SYSTEM_CONFIG_POSIX_LOCKING
 
-#if CHIP_SYSTEM_CONFIG_USE_DISPATCH
-    dispatch_queue_t mDispatchQueue = nullptr;
-#elif CHIP_SYSTEM_CONFIG_USE_LIBEV
+#if CHIP_SYSTEM_CONFIG_USE_LIBEV
     struct ev_loop * mLibEvLoopP;
 #endif
 };
diff --git a/src/system/SystemTimer.h b/src/system/SystemTimer.h
index 99c3d86..9839e22 100644
--- a/src/system/SystemTimer.h
+++ b/src/system/SystemTimer.h
@@ -91,7 +91,7 @@
     Callback mCallback;
 
 #if CHIP_SYSTEM_CONFIG_USE_DISPATCH
-    friend class LayerImplSelect;
+    friend class LayerImplDispatch;
     dispatch_source_t mTimerSource = nullptr;
 #elif CHIP_SYSTEM_CONFIG_USE_LIBEV
     friend class LayerImplSelect;
diff --git a/src/system/system.gni b/src/system/system.gni
index a92a507..66f5349 100644
--- a/src/system/system.gni
+++ b/src/system/system.gni
@@ -58,6 +58,8 @@
   if (chip_system_config_use_lwip ||
       chip_system_config_use_openthread_inet_endpoints) {
     chip_system_config_event_loop = "FreeRTOS"
+  } else if (chip_system_config_use_dispatch) {
+    chip_system_config_event_loop = "Dispatch"
   } else {
     chip_system_config_event_loop = "Select"
   }
diff --git a/src/system/tests/TestSystemTimer.cpp b/src/system/tests/TestSystemTimer.cpp
index a4e9d2e..2420969 100644
--- a/src/system/tests/TestSystemTimer.cpp
+++ b/src/system/tests/TestSystemTimer.cpp
@@ -53,8 +53,19 @@
     static void ServiceEvents(Layer & aLayer) {}
 };
 
-#if CHIP_SYSTEM_CONFIG_USE_SOCKETS || CHIP_SYSTEM_CONFIG_USE_NETWORK_FRAMEWORK
-
+#if CHIP_SYSTEM_CONFIG_USE_DISPATCH
+template <class LayerImpl>
+class LayerEvents<LayerImpl, typename std::enable_if<std::is_base_of<LayerDispatch, LayerImpl>::value>::type>
+{
+public:
+    static bool HasServiceEvents() { return true; }
+    static void ServiceEvents(Layer & aLayer)
+    {
+        LayerDispatch & layer = static_cast<LayerDispatch &>(aLayer);
+        layer.HandleDispatchQueueEvents(chip::System::Clock::kZero);
+    }
+};
+#elif CHIP_SYSTEM_CONFIG_USE_SOCKETS
 template <class LayerImpl>
 class LayerEvents<LayerImpl, typename std::enable_if<std::is_base_of<LayerSocketsLoop, LayerImpl>::value>::type>
 {
@@ -68,8 +79,7 @@
         layer.HandleEvents();
     }
 };
-
-#endif // CHIP_SYSTEM_CONFIG_USE_SOCKETS || CHIP_SYSTEM_CONFIG_USE_NETWORK_FRAMEWORK
+#endif // CHIP_SYSTEM_CONFIG_USE_DISPATCH
 
 #if CHIP_SYSTEM_CONFIG_USE_LWIP || CHIP_SYSTEM_CONFIG_USE_OPENTHREAD_ENDPOINT
 
diff --git a/src/system/tests/TestSystemWakeEvent.cpp b/src/system/tests/TestSystemWakeEvent.cpp
index 02d7c02..1de103a 100644
--- a/src/system/tests/TestSystemWakeEvent.cpp
+++ b/src/system/tests/TestSystemWakeEvent.cpp
@@ -36,7 +36,7 @@
 
 using namespace chip::System;
 
-#if CHIP_SYSTEM_CONFIG_USE_SOCKETS
+#if CHIP_SYSTEM_CONFIG_USE_SOCKETS && !CHIP_SYSTEM_CONFIG_USE_DISPATCH
 
 namespace chip {
 namespace System {
@@ -151,4 +151,4 @@
 }
 } // namespace
 
-#endif // CHIP_SYSTEM_CONFIG_USE_SOCKETS
+#endif // CHIP_SYSTEM_CONFIG_USE_SOCKETS && !CHIP_SYSTEM_CONFIG_USE_DISPATCH