শাওন্তি ফন্ড্রিসি প্রদত্ত সমাধানটি প্রায় নিখুঁত। কিন্তু সারিবদ্ধ সঞ্চালনের মত কোড বা কোডের যেমন একটি টুকরা UICollectionView
'র reloadData()
জন্য NSOperationQueue
' র mainQueue
প্রকৃতপক্ষে রান লুপ পরবর্তী ইভেন্ট লুপ, যা বানাতে পারে শুরুতে সঞ্চালনের সময়জ্ঞান রাখে UICollectionView
একটি ঝাঁকুনি সঙ্গে আপডেট।
এই সমস্যাটি সমাধান করার জন্য। আমাদের অবশ্যই একই ইভেন্টের কোডের প্রয়োগের সময়টি বর্তমান ইভেন্ট লুপের শেষে রাখতে হবে তবে পরবর্তীটির শুরুতে নয়। এবং আমরা এটি ব্যবহার করে এটি অর্জন করতে পারি CFRunLoopObserver
।
CFRunLoopObserver
সমস্ত ইনপুট উত্স অপেক্ষার ক্রিয়াকলাপ এবং রান লুপের প্রবেশ এবং প্রস্থান কার্যকলাপ পর্যবেক্ষণ করে।
public struct CFRunLoopActivity : OptionSetType {
public init(rawValue: CFOptionFlags)
public static var Entry: CFRunLoopActivity { get }
public static var BeforeTimers: CFRunLoopActivity { get }
public static var BeforeSources: CFRunLoopActivity { get }
public static var BeforeWaiting: CFRunLoopActivity { get }
public static var AfterWaiting: CFRunLoopActivity { get }
public static var Exit: CFRunLoopActivity { get }
public static var AllActivities: CFRunLoopActivity { get }
}
এই ক্রিয়াকলাপগুলির মধ্যে, .AfterWaiting
যখন বর্তমান ইভেন্ট লুপটি শেষ .BeforeWaiting
হতে চলেছে তখন পর্যবেক্ষণ করা যেতে পারে এবং পরবর্তী ইভেন্ট লুপটি সবে শুরু হওয়ার পরে লক্ষ্য করা যায়।
যেহেতু এখানে কেবলমাত্র একটি NSRunLoop
উদাহরণ রয়েছে NSThread
এবং NSRunLoop
সঠিকভাবে চালিত হয় NSThread
, আমরা বিবেচনা করতে পারি যে অ্যাক্সেসগুলি একই ধরণের থেকে আসে NSRunLoop
সর্বদা থ্রেডকে অতিক্রম করে না।
পূর্বে উল্লিখিত পয়েন্টগুলির ভিত্তিতে, আমরা এখন কোডটি লিখতে পারি: একটি এনএসআরনলুপ-ভিত্তিক টাস্ক প্রেরণকারী:
import Foundation
import ObjectiveC
public struct Weak<T: AnyObject>: Hashable {
private weak var _value: T?
public weak var value: T? { return _value }
public init(_ aValue: T) { _value = aValue }
public var hashValue: Int {
guard let value = self.value else { return 0 }
return ObjectIdentifier(value).hashValue
}
}
public func ==<T: AnyObject where T: Equatable>(lhs: Weak<T>, rhs: Weak<T>)
-> Bool
{
return lhs.value == rhs.value
}
public func ==<T: AnyObject>(lhs: Weak<T>, rhs: Weak<T>) -> Bool {
return lhs.value === rhs.value
}
public func ===<T: AnyObject>(lhs: Weak<T>, rhs: Weak<T>) -> Bool {
return lhs.value === rhs.value
}
private var dispatchObserverKey =
"com.WeZZard.Nest.NSRunLoop.TaskDispatcher.DispatchObserver"
private var taskQueueKey =
"com.WeZZard.Nest.NSRunLoop.TaskDispatcher.TaskQueue"
private var taskAmendQueueKey =
"com.WeZZard.Nest.NSRunLoop.TaskDispatcher.TaskAmendQueue"
private typealias DeallocFunctionPointer =
@convention(c) (Unmanaged<NSRunLoop>, Selector) -> Void
private var original_dealloc_imp: IMP?
private let swizzled_dealloc_imp: DeallocFunctionPointer = {
(aSelf: Unmanaged<NSRunLoop>,
aSelector: Selector)
-> Void in
let unretainedSelf = aSelf.takeUnretainedValue()
if unretainedSelf.isDispatchObserverLoaded {
let observer = unretainedSelf.dispatchObserver
CFRunLoopObserverInvalidate(observer)
}
if let original_dealloc_imp = original_dealloc_imp {
let originalDealloc = unsafeBitCast(original_dealloc_imp,
DeallocFunctionPointer.self)
originalDealloc(aSelf, aSelector)
} else {
fatalError("The original implementation of dealloc for NSRunLoop cannot be found!")
}
}
public enum NSRunLoopTaskInvokeTiming: Int {
case NextLoopBegan
case CurrentLoopEnded
case Idle
}
extension NSRunLoop {
public func perform(closure: ()->Void) -> Task {
objc_sync_enter(self)
loadDispatchObserverIfNeeded()
let task = Task(self, closure)
taskQueue.append(task)
objc_sync_exit(self)
return task
}
public override class func initialize() {
super.initialize()
struct Static {
static var token: dispatch_once_t = 0
}
if self !== NSRunLoop.self {
return
}
dispatch_once(&Static.token) {
let selectorDealloc: Selector = "dealloc"
original_dealloc_imp =
class_getMethodImplementation(self, selectorDealloc)
let swizzled_dealloc = unsafeBitCast(swizzled_dealloc_imp, IMP.self)
class_replaceMethod(self, selectorDealloc, swizzled_dealloc, "@:")
}
}
public final class Task {
private let weakRunLoop: Weak<NSRunLoop>
private var _invokeTiming: NSRunLoopTaskInvokeTiming
private var invokeTiming: NSRunLoopTaskInvokeTiming {
var theInvokeTiming: NSRunLoopTaskInvokeTiming = .NextLoopBegan
guard let amendQueue = weakRunLoop.value?.taskAmendQueue else {
fatalError("Accessing a dealloced run loop")
}
dispatch_sync(amendQueue) { () -> Void in
theInvokeTiming = self._invokeTiming
}
return theInvokeTiming
}
private var _modes: NSRunLoopMode
private var modes: NSRunLoopMode {
var theModes: NSRunLoopMode = []
guard let amendQueue = weakRunLoop.value?.taskAmendQueue else {
fatalError("Accessing a dealloced run loop")
}
dispatch_sync(amendQueue) { () -> Void in
theModes = self._modes
}
return theModes
}
private let closure: () -> Void
private init(_ runLoop: NSRunLoop, _ aClosure: () -> Void) {
weakRunLoop = Weak<NSRunLoop>(runLoop)
_invokeTiming = .NextLoopBegan
_modes = .defaultMode
closure = aClosure
}
public func forModes(modes: NSRunLoopMode) -> Task {
if let amendQueue = weakRunLoop.value?.taskAmendQueue {
dispatch_async(amendQueue) { [weak self] () -> Void in
self?._modes = modes
}
}
return self
}
public func when(invokeTiming: NSRunLoopTaskInvokeTiming) -> Task {
if let amendQueue = weakRunLoop.value?.taskAmendQueue {
dispatch_async(amendQueue) { [weak self] () -> Void in
self?._invokeTiming = invokeTiming
}
}
return self
}
}
private var isDispatchObserverLoaded: Bool {
return objc_getAssociatedObject(self, &dispatchObserverKey) !== nil
}
private func loadDispatchObserverIfNeeded() {
if !isDispatchObserverLoaded {
let invokeTimings: [NSRunLoopTaskInvokeTiming] =
[.CurrentLoopEnded, .NextLoopBegan, .Idle]
let activities =
CFRunLoopActivity(invokeTimings.map{ CFRunLoopActivity($0) })
let observer = CFRunLoopObserverCreateWithHandler(
kCFAllocatorDefault,
activities.rawValue,
true, 0,
handleRunLoopActivityWithObserver)
CFRunLoopAddObserver(getCFRunLoop(),
observer,
kCFRunLoopCommonModes)
let wrappedObserver = NSAssociated<CFRunLoopObserver>(observer)
objc_setAssociatedObject(self,
&dispatchObserverKey,
wrappedObserver,
.OBJC_ASSOCIATION_RETAIN_NONATOMIC)
}
}
private var dispatchObserver: CFRunLoopObserver {
loadDispatchObserverIfNeeded()
return (objc_getAssociatedObject(self, &dispatchObserverKey)
as! NSAssociated<CFRunLoopObserver>)
.value
}
private var taskQueue: [Task] {
get {
if let taskQueue = objc_getAssociatedObject(self,
&taskQueueKey)
as? [Task]
{
return taskQueue
} else {
let initialValue = [Task]()
objc_setAssociatedObject(self,
&taskQueueKey,
initialValue,
.OBJC_ASSOCIATION_RETAIN_NONATOMIC)
return initialValue
}
}
set {
objc_setAssociatedObject(self,
&taskQueueKey,
newValue,
.OBJC_ASSOCIATION_RETAIN_NONATOMIC)
}
}
private var taskAmendQueue: dispatch_queue_t {
if let taskQueue = objc_getAssociatedObject(self,
&taskAmendQueueKey)
as? dispatch_queue_t
{
return taskQueue
} else {
let initialValue =
dispatch_queue_create(
"com.WeZZard.Nest.NSRunLoop.TaskDispatcher.TaskAmendQueue",
DISPATCH_QUEUE_SERIAL)
objc_setAssociatedObject(self,
&taskAmendQueueKey,
initialValue,
.OBJC_ASSOCIATION_RETAIN_NONATOMIC)
return initialValue
}
}
private func handleRunLoopActivityWithObserver(observer: CFRunLoopObserver!,
activity: CFRunLoopActivity)
-> Void
{
var removedIndices = [Int]()
let runLoopMode: NSRunLoopMode = currentRunLoopMode
for (index, eachTask) in taskQueue.enumerate() {
let expectedRunLoopModes = eachTask.modes
let expectedRunLoopActivitiy =
CFRunLoopActivity(eachTask.invokeTiming)
let runLoopModesMatches = expectedRunLoopModes.contains(runLoopMode)
|| expectedRunLoopModes.contains(.commonModes)
let runLoopActivityMatches =
activity.contains(expectedRunLoopActivitiy)
if runLoopModesMatches && runLoopActivityMatches {
eachTask.closure()
removedIndices.append(index)
}
}
taskQueue.removeIndicesInPlace(removedIndices)
}
}
extension CFRunLoopActivity {
private init(_ invokeTiming: NSRunLoopTaskInvokeTiming) {
switch invokeTiming {
case .NextLoopBegan: self = .AfterWaiting
case .CurrentLoopEnded: self = .BeforeWaiting
case .Idle: self = .Exit
}
}
}
আগের কোড সহ, আমরা এখন UICollectionView
এর reloadData()
ইভেন্টের লুপের শেষের দিকে এ জাতীয় কোডের একটি অংশ দ্বারা প্রেরণ করতে পারি :
NSRunLoop.currentRunLoop().perform({ () -> Void in
collectionView.reloadData()
}).when(.CurrentLoopEnded)
প্রকৃতপক্ষে, এই জাতীয় এনএসআরনলুপ ভিত্তিক টাস্ক ডিসপাচার ইতিমধ্যে আমার ব্যক্তিগত ব্যবহৃত কাঠামোর মধ্যে রয়েছে: নেস্ট। এবং এখানে গিটহাবে এটির সংগ্রহস্থল: https://github.com/WeZZard/ নীস্ট
reloadData
ভিউডিড অ্যাপের পরে কল করা সমস্যাটি সমাধান করছে বলে মনে হচ্ছে, এর ভয়াবহ পরিশ্রম, এবং ঠিক করা দরকার। আমি আশা করি কেউ এখানে সাহায্য করবে।