diff --git a/Source/ASDisplayNode.mm b/Source/ASDisplayNode.mm index 892376265..2fac28e8f 100644 --- a/Source/ASDisplayNode.mm +++ b/Source/ASDisplayNode.mm @@ -416,13 +416,12 @@ - (ASDisplayNodeMethodOverrides)methodOverrides - (void)onDidLoad:(ASDisplayNodeDidLoadBlock)body { - ASDN::UniqueLock l(__instanceLock__); + ASDN::MutexLocker l(__instanceLock__); if ([self _locked_isNodeLoaded]) { ASDisplayNodeAssertThreadAffinity(self); - l.unlock(); + ASDN::MutexUnlocker l(__instanceLock__); body(self); - return; } else if (_onDidLoadBlocks == nil) { _onDidLoadBlocks = [NSMutableArray arrayWithObject:body]; } else { @@ -602,7 +601,7 @@ - (BOOL)_locked_isNodeLoaded - (UIView *)view { - ASDN::UniqueLock l(__instanceLock__); + ASDN::MutexLocker l(__instanceLock__); ASDisplayNodeAssert(!_flags.layerBacked, @"Call to -view undefined on layer-backed nodes"); BOOL isLayerBacked = _flags.layerBacked; @@ -627,28 +626,30 @@ - (UIView *)view // in the background on a loaded node, which isn't currently supported. if (_pendingViewState.hasSetNeedsLayout) { // Need to unlock before calling setNeedsLayout to avoid deadlocks. - l.unlock(); + // MutexUnlocker will re-lock at the end of scope. + ASDN::MutexUnlocker u(__instanceLock__); [self __setNeedsLayout]; - l.lock(); } [self _locked_applyPendingStateToViewOrLayer]; - // The following methods should not be called with a lock - l.unlock(); + { + // The following methods should not be called with a lock + ASDN::MutexUnlocker u(__instanceLock__); - // No need for the lock as accessing the subviews or layers are always happening on main - [self _addSubnodeViewsAndLayers]; - - // A subclass hook should never be called with a lock - [self _didLoad]; + // No need for the lock as accessing the subviews or layers are always happening on main + [self _addSubnodeViewsAndLayers]; + + // A subclass hook should never be called with a lock + [self _didLoad]; + } return _view; } - (CALayer *)layer { - ASDN::UniqueLock l(__instanceLock__); + ASDN::MutexLocker l(__instanceLock__); if (_layer != nil) { return _layer; } @@ -660,30 +661,31 @@ - (CALayer *)layer // Loading a layer needs to happen on the main thread ASDisplayNodeAssertMainThread(); [self _locked_loadViewOrLayer]; - CALayer *layer = _layer; // FIXME: Ideally we'd call this as soon as the node receives -setNeedsLayout // but automatic subnode management would require us to modify the node tree // in the background on a loaded node, which isn't currently supported. if (_pendingViewState.hasSetNeedsLayout) { // Need to unlock before calling setNeedsLayout to avoid deadlocks. - l.unlock(); + // MutexUnlocker will re-lock at the end of scope. + ASDN::MutexUnlocker u(__instanceLock__); [self __setNeedsLayout]; - l.lock(); } [self _locked_applyPendingStateToViewOrLayer]; - // The following methods should not be called with a lock - l.unlock(); + { + // The following methods should not be called with a lock + ASDN::MutexUnlocker u(__instanceLock__); - // No need for the lock as accessing the subviews or layers are always happening on main - [self _addSubnodeViewsAndLayers]; - - // A subclass hook should never be called with a lock - [self _didLoad]; + // No need for the lock as accessing the subviews or layers are always happening on main + [self _addSubnodeViewsAndLayers]; + + // A subclass hook should never be called with a lock + [self _didLoad]; + } - return layer; + return _layer; } // Returns nil if the layer is not an _ASDisplayLayer; will not create the layer if nil. @@ -1031,7 +1033,7 @@ - (void)__layout BOOL loaded = NO; { - ASDN::UniqueLock l(__instanceLock__); + ASDN::MutexLocker l(__instanceLock__); loaded = [self _locked_isNodeLoaded]; CGRect bounds = _threadSafeBounds; @@ -1053,9 +1055,10 @@ - (void)__layout // This method will confirm that the layout is up to date (and update if needed). // Importantly, it will also APPLY the layout to all of our subnodes if (unless parent is transitioning). - l.unlock(); - [self _u_measureNodeWithBoundsIfNecessary:bounds]; - l.lock(); + { + ASDN::MutexUnlocker u(__instanceLock__); + [self _u_measureNodeWithBoundsIfNecessary:bounds]; + } [self _locked_layoutPlaceholderIfNecessary]; } @@ -1118,7 +1121,7 @@ - (ASLayout *)calculateLayoutThatFits:(ASSizeRange)constrainedSize { __ASDisplayNodeCheckForLayoutMethodOverrides; - ASDN::UniqueLock l(__instanceLock__); + ASDN::MutexLocker l(__instanceLock__); #if YOGA // There are several cases where Yoga could arrive here: @@ -1135,13 +1138,11 @@ - (ASLayout *)calculateLayoutThatFits:(ASSizeRange)constrainedSize if ([self shouldHaveYogaMeasureFunc] == NO) { // If we're a yoga root, tree node, or leaf with no measure func (e.g. spacer), then // initiate a new Yoga calculation pass from root. - + ASDN::MutexUnlocker ul(__instanceLock__); as_activity_create_for_scope("Yoga layout calculation"); if (self.yogaLayoutInProgress == NO) { ASYogaLog("Calculating yoga layout from root %@, %@", self, NSStringFromASSizeRange(constrainedSize)); - l.unlock(); [self calculateLayoutFromYogaRoot:constrainedSize]; - l.lock(); } else { ASYogaLog("Reusing existing yoga layout %@", _yogaCalculatedLayout); } @@ -2265,14 +2266,7 @@ - (void)_insertSubnode:(ASDisplayNode *)subnode atSubnodeIndex:(NSInteger)subnod [_subnodes insertObject:subnode atIndex:subnodeIndex]; _cachedSubnodes = nil; __instanceLock__.unlock(); - - if (!isRasterized && self.nodeLoaded) { - // Trigger the subnode to load its layer, which will create its view if it needs one. - // By doing this prior to downward propagation of .interfaceState in _setSupernode:, - // we can guarantee that -didEnterVisibleState is only called with .isNodeLoaded = YES. - [subnode layer]; - } - + // This call will apply our .hierarchyState to the new subnode. // If we are a managed hierarchy, as in ASCellNode trees, it will also apply our .interfaceState. [subnode _setSupernode:self]; @@ -3285,19 +3279,11 @@ - (void)didEnterVisibleState { // subclass override ASDisplayNodeAssertMainThread(); - -#if ASDISPLAYNODE_ASSERTIONS_ENABLED - // Rasterized node's loading state is merged with root node of rasterized tree. - if (!(self.hierarchyState & ASHierarchyStateRasterized)) { - ASDisplayNodeAssert(self.isNodeLoaded, @"Node should be loaded before entering visible state."); - } -#endif - + ASAssertUnlocked(__instanceLock__); [self enumerateInterfaceStateDelegates:^(id del) { [del didEnterVisibleState]; }]; - #if AS_ENABLE_TIPS [ASTipsController.shared nodeDidAppear:self]; #endif @@ -3545,15 +3531,15 @@ - (void)applyPendingViewState ASDisplayNodeAssertMainThread(); ASAssertUnlocked(__instanceLock__); - ASDN::UniqueLock l(__instanceLock__); + ASDN::MutexLocker l(__instanceLock__); // FIXME: Ideally we'd call this as soon as the node receives -setNeedsLayout // but automatic subnode management would require us to modify the node tree // in the background on a loaded node, which isn't currently supported. if (_pendingViewState.hasSetNeedsLayout) { // Need to unlock before calling setNeedsLayout to avoid deadlocks. - l.unlock(); + // MutexUnlocker will re-lock at the end of scope. + ASDN::MutexUnlocker u(__instanceLock__); [self __setNeedsLayout]; - l.lock(); } [self _locked_applyPendingViewState]; diff --git a/Source/ASImageNode.mm b/Source/ASImageNode.mm index bed665560..1ace8c74b 100644 --- a/Source/ASImageNode.mm +++ b/Source/ASImageNode.mm @@ -434,12 +434,12 @@ + (UIImage *)displayWithParameters:(id)parameter isCancelled:(NS_NOESC static ASWeakMap *cache = nil; // Allocate cacheLock on the heap to prevent destruction at app exit (https://github.com/TextureGroup/Texture/issues/136) -static auto *cacheLock = new ASDN::Mutex; +static ASDN::StaticMutex& cacheLock = *new ASDN::StaticMutex; + (ASWeakMapEntry *)contentsForkey:(ASImageNodeContentsKey *)key drawParameters:(id)drawParameters isCancelled:(asdisplaynode_iscancelled_block_t)isCancelled { { - ASDN::MutexLocker l(*cacheLock); + ASDN::StaticMutexLocker l(cacheLock); if (!cache) { cache = [[ASWeakMap alloc] init]; } @@ -456,7 +456,7 @@ + (ASWeakMapEntry *)contentsForkey:(ASImageNodeContentsKey *)key drawParameters: } { - ASDN::MutexLocker l(*cacheLock); + ASDN::StaticMutexLocker l(cacheLock); return [cache setObject:contents forKey:key]; } } diff --git a/Source/ASTextNode2.mm b/Source/ASTextNode2.mm index f3ec51fa3..6946ec95a 100644 --- a/Source/ASTextNode2.mm +++ b/Source/ASTextNode2.mm @@ -55,14 +55,14 @@ @implementation ASTextCacheValue */ static NS_RETURNS_RETAINED ASTextLayout *ASTextNodeCompatibleLayoutWithContainerAndText(ASTextContainer *container, NSAttributedString *text) { // Allocate layoutCacheLock on the heap to prevent destruction at app exit (https://github.com/TextureGroup/Texture/issues/136) - static auto *layoutCacheLock = new ASDN::Mutex; + static ASDN::StaticMutex& layoutCacheLock = *new ASDN::StaticMutex; static NSCache *textLayoutCache; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ textLayoutCache = [[NSCache alloc] init]; }); - layoutCacheLock->lock(); + layoutCacheLock.lock(); ASTextCacheValue *cacheValue = [textLayoutCache objectForKey:text]; if (cacheValue == nil) { @@ -72,7 +72,7 @@ @implementation ASTextCacheValue // Lock the cache item for the rest of the method. Only after acquiring can we release the NSCache. ASDN::MutexLocker lock(cacheValue->_m); - layoutCacheLock->unlock(); + layoutCacheLock.unlock(); CGRect containerBounds = (CGRect){ .size = container.size }; { diff --git a/Source/ASVideoNode.mm b/Source/ASVideoNode.mm index fe368a12c..4cb4ba8c5 100644 --- a/Source/ASVideoNode.mm +++ b/Source/ASVideoNode.mm @@ -10,7 +10,6 @@ #import #import #import -#import #import #import #import @@ -323,7 +322,7 @@ - (void)setVideoPlaceholderImage:(UIImage *)image - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { - ASDN::UniqueLock l(__instanceLock__); + ASLockScopeSelf(); if (object == _currentPlayerItem) { if ([keyPath isEqualToString:kStatus]) { @@ -331,9 +330,8 @@ - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(N if (self.playerState != ASVideoNodePlayerStatePlaying) { self.playerState = ASVideoNodePlayerStateReadyToPlay; if (_shouldBePlaying && ASInterfaceStateIncludesVisible(self.interfaceState)) { - l.unlock(); + ASUnlockScope(self); [self play]; - l.lock(); } } // If we don't yet have a placeholder image update it now that we should have data available for it @@ -356,10 +354,8 @@ - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(N [self.delegate videoNodeDidRecoverFromStall:self]; } - l.unlock(); + ASUnlockScope(self); [self play]; // autoresume after buffer catches up - // NOTE: Early return without re-locking. - return; } } else if ([keyPath isEqualToString:kplaybackBufferEmpty]) { if (_shouldBePlaying && [change[NSKeyValueChangeNewKey] boolValue] == YES && ASInterfaceStateIncludesVisible(self.interfaceState)) { @@ -377,8 +373,6 @@ - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(N } } } - - // NOTE: Early return above. } - (void)tapped diff --git a/Source/Details/ASBasicImageDownloader.mm b/Source/Details/ASBasicImageDownloader.mm index c13fc728b..24ccf95bf 100644 --- a/Source/Details/ASBasicImageDownloader.mm +++ b/Source/Details/ASBasicImageDownloader.mm @@ -39,11 +39,11 @@ @implementation ASBasicImageDownloaderContext static NSMutableDictionary *currentRequests = nil; // Allocate currentRequestsLock on the heap to prevent destruction at app exit (https://github.com/TextureGroup/Texture/issues/136) -static auto *currentRequestsLock = new ASDN::Mutex; +static ASDN::StaticMutex& currentRequestsLock = *new ASDN::StaticMutex; + (ASBasicImageDownloaderContext *)contextForURL:(NSURL *)URL { - ASDN::MutexLocker l(*currentRequestsLock); + ASDN::StaticMutexLocker l(currentRequestsLock); if (!currentRequests) { currentRequests = [[NSMutableDictionary alloc] init]; } @@ -57,7 +57,7 @@ + (ASBasicImageDownloaderContext *)contextForURL:(NSURL *)URL + (void)cancelContextWithURL:(NSURL *)URL { - ASDN::MutexLocker l(*currentRequestsLock); + ASDN::StaticMutexLocker l(currentRequestsLock); if (currentRequests) { [currentRequests removeObjectForKey:URL]; } diff --git a/Source/Details/ASMainSerialQueue.mm b/Source/Details/ASMainSerialQueue.mm index 3a5fa00f8..42d937ad4 100644 --- a/Source/Details/ASMainSerialQueue.mm +++ b/Source/Details/ASMainSerialQueue.mm @@ -40,21 +40,19 @@ - (NSUInteger)numberOfScheduledBlocks - (void)performBlockOnMainThread:(dispatch_block_t)block { - - ASDN::UniqueLock l(_serialQueueLock); + ASDN::MutexLocker l(_serialQueueLock); [_blocks addObject:block]; { - l.unlock(); + ASDN::MutexUnlocker u(_serialQueueLock); [self runBlocks]; - l.lock(); } } - (void)runBlocks { dispatch_block_t mainThread = ^{ - ASDN::UniqueLock l(self->_serialQueueLock); do { + ASDN::MutexLocker l(self->_serialQueueLock); dispatch_block_t block; if (self->_blocks.count > 0) { block = _blocks[0]; @@ -63,9 +61,8 @@ - (void)runBlocks break; } { - l.unlock(); + ASDN::MutexUnlocker u(self->_serialQueueLock); block(); - l.lock(); } } while (true); }; diff --git a/Source/Details/ASThread.h b/Source/Details/ASThread.h index 701e6c10a..bdcfd2618 100644 --- a/Source/Details/ASThread.h +++ b/Source/Details/ASThread.h @@ -95,6 +95,7 @@ ASDISPLAYNODE_INLINE void _ASUnlockScopeCleanup(id __strong *lockPtr) #ifdef __cplusplus +#define TIME_LOCKER 0 /** * Enable this flag to collect information on the owning thread and ownership level of a mutex. * These properties are useful to determine if a mutex has been acquired and in case of a recursive mutex, how many times that happened. @@ -111,8 +112,11 @@ ASDISPLAYNODE_INLINE void _ASUnlockScopeCleanup(id __strong *lockPtr) #define CHECK_LOCKING_SAFETY 0 #endif +#if TIME_LOCKER +#import +#endif + #include -#include // This MUST always execute, even when assertions are disabled. Otherwise all lock operations become no-ops! // (To be explicit, do not turn this into an NSAssert, assert(), or any other kind of statement where the @@ -139,6 +143,106 @@ ASDISPLAYNODE_INLINE void _ASUnlockScopeCleanup(id __strong *lockPtr) namespace ASDN { + template + class Locker + { + T &_l; + +#if TIME_LOCKER + CFTimeInterval _ti; + const char *_name; +#endif + + public: +#if !TIME_LOCKER + + Locker (T &l) noexcept : _l (l) { + _l.lock (); + } + + ~Locker () { + _l.unlock (); + } + + // non-copyable. + Locker(const Locker&) = delete; + Locker &operator=(const Locker&) = delete; + +#else + + Locker (T &l, const char *name = NULL) noexcept : _l (l), _name(name) { + _ti = CACurrentMediaTime(); + _l.lock (); + } + + ~Locker () { + _l.unlock (); + if (_name) { + printf(_name, NULL); + printf(" dt:%f\n", CACurrentMediaTime() - _ti); + } + } + +#endif + + }; + + template + class SharedLocker + { + std::shared_ptr _l; + +#if TIME_LOCKER + CFTimeInterval _ti; + const char *_name; +#endif + + public: +#if !TIME_LOCKER + + SharedLocker (std::shared_ptr const& l) noexcept : _l (l) { + ASDisplayNodeCAssertTrue(_l != nullptr); + _l->lock (); + } + + ~SharedLocker () { + _l->unlock (); + } + + // non-copyable. + SharedLocker(const SharedLocker&) = delete; + SharedLocker &operator=(const SharedLocker&) = delete; + +#else + + SharedLocker (std::shared_ptr const& l, const char *name = NULL) noexcept : _l (l), _name(name) { + _ti = CACurrentMediaTime(); + _l->lock (); + } + + ~SharedLocker () { + _l->unlock (); + if (_name) { + printf(_name, NULL); + printf(" dt:%f\n", CACurrentMediaTime() - _ti); + } + } + +#endif + + }; + + template + class Unlocker + { + T &_l; + public: + Unlocker (T &l) noexcept : _l (l) { _l.unlock (); } + ~Unlocker () {_l.lock ();} + Unlocker(Unlocker&) = delete; + Unlocker &operator=(Unlocker&) = delete; + }; + // Set once in Mutex constructor. Linker fails if this is a member variable. ?? static BOOL gMutex_unfair; @@ -310,8 +414,41 @@ namespace ASDN { RecursiveMutex () : Mutex (true) {} }; - typedef std::lock_guard MutexLocker; - typedef std::unique_lock UniqueLock; + typedef Locker MutexLocker; + typedef SharedLocker MutexSharedLocker; + typedef Unlocker MutexUnlocker; + + /** + If you are creating a static mutex, use StaticMutex. This avoids expensive constructor overhead at startup (or worse, ordering + issues between different static objects). It also avoids running a destructor on app exit time (needless expense). + + Note that you can, but should not, use StaticMutex for non-static objects. It will leak its mutex on destruction, + so avoid that! + */ + struct StaticMutex + { + StaticMutex () : _m (PTHREAD_MUTEX_INITIALIZER) {} + + // non-copyable. + StaticMutex(const StaticMutex&) = delete; + StaticMutex &operator=(const StaticMutex&) = delete; + + void lock () { + AS_POSIX_ASSERT_NOERR(pthread_mutex_lock (this->mutex())); + } + + void unlock () { + AS_POSIX_ASSERT_NOERR(pthread_mutex_unlock (this->mutex())); + } + + pthread_mutex_t *mutex () { return &_m; } + + private: + pthread_mutex_t _m; + }; + + typedef Locker StaticMutexLocker; + typedef Unlocker StaticMutexUnlocker; } // namespace ASDN diff --git a/Source/Private/ASLayoutTransition.mm b/Source/Private/ASLayoutTransition.mm index b7d9cda44..1e482afa6 100644 --- a/Source/Private/ASLayoutTransition.mm +++ b/Source/Private/ASLayoutTransition.mm @@ -81,7 +81,7 @@ - (instancetype)initWithNode:(ASDisplayNode *)node - (BOOL)isSynchronous { - ASDN::MutexLocker l(*__instanceLock__); + ASDN::MutexSharedLocker l(__instanceLock__); return !ASLayoutCanTransitionAsynchronous(_pendingLayout.layout); } @@ -93,7 +93,7 @@ - (void)commitTransition - (void)applySubnodeInsertionsAndMoves { - ASDN::MutexLocker l(*__instanceLock__); + ASDN::MutexSharedLocker l(__instanceLock__); [self calculateSubnodeOperationsIfNeeded]; // Create an activity even if no subnodes affected. @@ -131,7 +131,7 @@ - (void)applySubnodeInsertionsAndMoves - (void)applySubnodeRemovals { as_activity_scope(as_activity_create("Apply subnode removals", AS_ACTIVITY_CURRENT, OS_ACTIVITY_FLAG_DEFAULT)); - ASDN::MutexLocker l(*__instanceLock__); + ASDN::MutexSharedLocker l(__instanceLock__); [self calculateSubnodeOperationsIfNeeded]; if (_removedSubnodes.count == 0) { @@ -151,7 +151,7 @@ - (void)applySubnodeRemovals - (void)calculateSubnodeOperationsIfNeeded { - ASDN::MutexLocker l(*__instanceLock__); + ASDN::MutexSharedLocker l(__instanceLock__); if (_calculatedSubnodeOperations) { return; } @@ -206,27 +206,27 @@ - (void)calculateSubnodeOperationsIfNeeded - (NSArray *)currentSubnodesWithTransitionContext:(_ASTransitionContext *)context { - ASDN::MutexLocker l(*__instanceLock__); + ASDN::MutexSharedLocker l(__instanceLock__); return _node.subnodes; } - (NSArray *)insertedSubnodesWithTransitionContext:(_ASTransitionContext *)context { - ASDN::MutexLocker l(*__instanceLock__); + ASDN::MutexSharedLocker l(__instanceLock__); [self calculateSubnodeOperationsIfNeeded]; return _insertedSubnodes; } - (NSArray *)removedSubnodesWithTransitionContext:(_ASTransitionContext *)context { - ASDN::MutexLocker l(*__instanceLock__); + ASDN::MutexSharedLocker l(__instanceLock__); [self calculateSubnodeOperationsIfNeeded]; return _removedSubnodes; } - (ASLayout *)transitionContext:(_ASTransitionContext *)context layoutForKey:(NSString *)key { - ASDN::MutexLocker l(*__instanceLock__); + ASDN::MutexSharedLocker l(__instanceLock__); if ([key isEqualToString:ASTransitionContextFromLayoutKey]) { return _previousLayout.layout; } else if ([key isEqualToString:ASTransitionContextToLayoutKey]) { @@ -238,7 +238,7 @@ - (ASLayout *)transitionContext:(_ASTransitionContext *)context layoutForKey:(NS - (ASSizeRange)transitionContext:(_ASTransitionContext *)context constrainedSizeForKey:(NSString *)key { - ASDN::MutexLocker l(*__instanceLock__); + ASDN::MutexSharedLocker l(__instanceLock__); if ([key isEqualToString:ASTransitionContextFromLayoutKey]) { return _previousLayout.constrainedSize; } else if ([key isEqualToString:ASTransitionContextToLayoutKey]) { diff --git a/Source/TextKit/ASTextKitContext.mm b/Source/TextKit/ASTextKitContext.mm index fa0fb8834..0995a66f6 100644 --- a/Source/TextKit/ASTextKitContext.mm +++ b/Source/TextKit/ASTextKitContext.mm @@ -32,9 +32,9 @@ - (instancetype)initWithAttributedString:(NSAttributedString *)attributedString { if (self = [super init]) { // Concurrently initialising TextKit components crashes (rdar://18448377) so we use a global lock. - // Allocate mutex on the heap to prevent destruction at app exit (https://github.com/TextureGroup/Texture/issues/136) - static auto *mutex = new ASDN::Mutex; - ASDN::MutexLocker l(*mutex); + // Allocate __staticMutex on the heap to prevent destruction at app exit (https://github.com/TextureGroup/Texture/issues/136) + static ASDN::StaticMutex& __staticMutex = *new ASDN::StaticMutex; + ASDN::StaticMutexLocker l(__staticMutex); __instanceLock__ = std::make_shared(); @@ -66,7 +66,7 @@ - (void)performBlockWithLockedTextKitComponents:(NS_NOESCAPE void (^)(NSLayoutMa NSTextStorage *, NSTextContainer *))block { - ASDN::MutexLocker l(*__instanceLock__); + ASDN::MutexSharedLocker l(__instanceLock__); if (block) { block(_layoutManager, _textStorage, _textContainer); } diff --git a/Tests/ASNetworkImageNodeTests.mm b/Tests/ASNetworkImageNodeTests.mm index 6b60030ff..83ff69459 100644 --- a/Tests/ASNetworkImageNodeTests.mm +++ b/Tests/ASNetworkImageNodeTests.mm @@ -57,7 +57,6 @@ - (void)DISABLED_testThatProgressBlockIsSetAndClearedCorrectlyOnVisibility - (void)testThatProgressBlockIsSetAndClearedCorrectlyOnChangeURL { - [node layer]; [node enterInterfaceState:ASInterfaceStateInHierarchy]; // Set URL while visible, should set progress block diff --git a/Tests/ASVideoNodeTests.mm b/Tests/ASVideoNodeTests.mm index 81515312d..8b30b0052 100644 --- a/Tests/ASVideoNodeTests.mm +++ b/Tests/ASVideoNodeTests.mm @@ -199,9 +199,9 @@ - (void)testPlayerLayerNodeIsNotAddedIfVisibleButShouldNotBePlayingWithUrl - (void)doPlayerLayerNodeIsNotAddedIfVisibleButShouldNotBePlaying { [_videoNode pause]; - [_videoNode layer]; [_videoNode setInterfaceState:ASInterfaceStateVisible | ASInterfaceStateDisplay]; - + [_videoNode didLoad]; + XCTAssert(![_videoNode.subnodes containsObject:_videoNode.playerNode]); } @@ -226,8 +226,7 @@ - (void)doVideoStartsPlayingOnDidDidBecomeVisibleWhenShouldAutoplay return playerLayer; }]; _videoNode.playerNode.layer.frame = CGRectZero; - - [_videoNode layer]; + [_videoNode didEnterVisibleState]; XCTAssertTrue(_videoNode.shouldBePlaying); @@ -305,7 +304,7 @@ - (void)testVideoThatDoesNotAutorepeatsShouldPauseOnPlaybackEnd _videoNode.asset = assetMock; _videoNode.shouldAutorepeat = NO; - [_videoNode layer]; + [_videoNode didLoad]; [_videoNode setInterfaceState:ASInterfaceStateVisible | ASInterfaceStateDisplay | ASInterfaceStatePreload]; [_videoNode prepareToPlayAsset:assetMock withKeys:_requestedKeys]; [_videoNode play]; @@ -326,7 +325,7 @@ - (void)testVideoThatAutorepeatsShouldRepeatOnPlaybackEnd _videoNode.asset = assetMock; _videoNode.shouldAutorepeat = YES; - [_videoNode layer]; + [_videoNode didLoad]; [_videoNode setInterfaceState:ASInterfaceStateVisible | ASInterfaceStateDisplay | ASInterfaceStatePreload]; [_videoNode prepareToPlayAsset:assetMock withKeys:_requestedKeys]; [_videoNode play]; @@ -343,7 +342,6 @@ - (void)testVideoResumedWhenBufferIsLikelyToKeepUp _videoNode.asset = assetMock; - [_videoNode layer]; [_videoNode setInterfaceState:ASInterfaceStateVisible | ASInterfaceStateDisplay | ASInterfaceStatePreload]; [_videoNode prepareToPlayAsset:assetMock withKeys:_requestedKeys]; ASCATransactionQueueWait(nil);