diff --git a/AsyncDisplayKit.xcodeproj/project.pbxproj b/AsyncDisplayKit.xcodeproj/project.pbxproj index 7e7bd1a9c4..afcf74a6ad 100644 --- a/AsyncDisplayKit.xcodeproj/project.pbxproj +++ b/AsyncDisplayKit.xcodeproj/project.pbxproj @@ -204,6 +204,7 @@ 6907C2591DC4ECFE00374C66 /* ASObjectDescriptionHelpers.m in Sources */ = {isa = PBXBuildFile; fileRef = 6907C2571DC4ECFE00374C66 /* ASObjectDescriptionHelpers.m */; }; 6907C25A1DC4ECFE00374C66 /* ASObjectDescriptionHelpers.m in Sources */ = {isa = PBXBuildFile; fileRef = 6907C2571DC4ECFE00374C66 /* ASObjectDescriptionHelpers.m */; }; 69127CFE1DD2B387004BF6E2 /* ASEventLog.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 696F01EA1DD2AF450049FBD5 /* ASEventLog.h */; }; + 69309D461DF3B1B50089FA48 /* ASImageNode+Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 69309D451DF3B1B50089FA48 /* ASImageNode+Private.h */; }; 693117CE1DC7C72700DE4784 /* ASDisplayNode+Deprecated.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 683489271D70DE3400327501 /* ASDisplayNode+Deprecated.h */; }; 69527B121DC84292004785FB /* ASLayoutElementStylePrivate.h in Headers */ = {isa = PBXBuildFile; fileRef = 69527B111DC84292004785FB /* ASLayoutElementStylePrivate.h */; }; 6959433E1D70815300B0EE1F /* ASDisplayNodeLayout.mm in Sources */ = {isa = PBXBuildFile; fileRef = 6959433C1D70815300B0EE1F /* ASDisplayNodeLayout.mm */; }; @@ -1002,6 +1003,7 @@ 68FC85E81CE29C7D00EDD713 /* ASVisibilityProtocols.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASVisibilityProtocols.m; sourceTree = ""; }; 6907C2561DC4ECFE00374C66 /* ASObjectDescriptionHelpers.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASObjectDescriptionHelpers.h; sourceTree = ""; }; 6907C2571DC4ECFE00374C66 /* ASObjectDescriptionHelpers.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASObjectDescriptionHelpers.m; sourceTree = ""; }; + 69309D451DF3B1B50089FA48 /* ASImageNode+Private.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "ASImageNode+Private.h"; sourceTree = ""; }; 69527B111DC84292004785FB /* ASLayoutElementStylePrivate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ASLayoutElementStylePrivate.h; path = AsyncDisplayKit/Layout/ASLayoutElementStylePrivate.h; sourceTree = SOURCE_ROOT; }; 6959433C1D70815300B0EE1F /* ASDisplayNodeLayout.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASDisplayNodeLayout.mm; sourceTree = ""; }; 6959433D1D70815300B0EE1F /* ASDisplayNodeLayout.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASDisplayNodeLayout.h; sourceTree = ""; }; @@ -1609,6 +1611,7 @@ 68B8A4DB1CBD911D007E4543 /* ASImageNode+AnimatedImagePrivate.h */, 058D0A0D195D050800B7D73C /* ASImageNode+CGExtras.h */, 058D0A0E195D050800B7D73C /* ASImageNode+CGExtras.m */, + 69309D451DF3B1B50089FA48 /* ASImageNode+Private.h */, ACF6ED431B17847A00DA7C62 /* ASInternalHelpers.h */, ACF6ED441B17847A00DA7C62 /* ASInternalHelpers.m */, 69C4CAF51DA3147000B1EC9B /* ASLayoutElementStylePrivate.h */, @@ -1857,6 +1860,7 @@ 34EFC7631B701CBF00AD841F /* ASCenterLayoutSpec.h in Headers */, 9C70F20C1CDBE9B6007D6C76 /* ASCollectionDataController.h in Headers */, 18C2ED7F1B9B7DE800F627B3 /* ASCollectionNode.h in Headers */, + 69309D461DF3B1B50089FA48 /* ASImageNode+Private.h in Headers */, 9C8898BD1C738BB800D6B02E /* ASTextKitFontSizeAdjuster.h in Headers */, B35061F51B010EFD0018CF92 /* ASCollectionView.h in Headers */, ACE87A2C1D73696800D7FF06 /* ASSectionContext.h in Headers */, diff --git a/AsyncDisplayKit/ASImageNode.mm b/AsyncDisplayKit/ASImageNode.mm index 2678a7e8a9..153c8d95c3 100644 --- a/AsyncDisplayKit/ASImageNode.mm +++ b/AsyncDisplayKit/ASImageNode.mm @@ -199,6 +199,11 @@ struct ASImageNodeDrawParameters { #pragma mark - Setter / Getter - (void)setImage:(UIImage *)image +{ + [self __setImage:image]; +} + +- (void)__setImage:(UIImage *)image { ASDN::MutexLocker l(__instanceLock__); if (!ASObjectIsEqual(_image, image)) { diff --git a/AsyncDisplayKit/ASMultiplexImageNode.mm b/AsyncDisplayKit/ASMultiplexImageNode.mm index f5bc472509..633562c873 100644 --- a/AsyncDisplayKit/ASMultiplexImageNode.mm +++ b/AsyncDisplayKit/ASMultiplexImageNode.mm @@ -11,16 +11,17 @@ #if TARGET_OS_IOS #import "ASMultiplexImageNode.h" +#import "ASImageNode+Private.h" #import #import "ASAvailability.h" #import "ASDisplayNode+Subclasses.h" #import "ASDisplayNode+FrameworkPrivate.h" +#import "ASDisplayNodeExtras.h" #import "ASLog.h" #import "ASPhotosFrameworkImageRequest.h" #import "ASEqualityHelpers.h" #import "ASInternalHelpers.h" -#import "ASDisplayNodeExtras.h" #if !AS_IOS8_SDK_OR_LATER #error ASMultiplexImageNode can be used on iOS 7, but must be linked against the iOS 8 SDK. @@ -233,7 +234,7 @@ typedef void(^ASMultiplexImageLoadCompletionBlock)(UIImage *image, id imageIdent // setting this to nil makes the node fetch images the next time its display starts _loadedImageIdentifier = nil; - self.image = nil; + [self __setImage:nil]; } - (void)didEnterPreloadState @@ -325,6 +326,12 @@ typedef void(^ASMultiplexImageLoadCompletionBlock)(UIImage *image, id imageIdent #pragma mark - Core +- (void)setImage:(UIImage *)image +{ + ASDisplayNodeAssert(NO, @"Setting the image directly to an ASMultiplexImageNode is not allowed."); + [self __setImage:image]; +} + - (void)setDelegate:(id )delegate { if (_delegate == delegate) @@ -520,7 +527,7 @@ typedef void(^ASMultiplexImageLoadCompletionBlock)(UIImage *image, id imageIdent if (ASObjectIsEqual(strongSelf->_downloadIdentifier, downloadIdentifier) == NO && downloadIdentifier != nil) { return; } - strongSelf.image = progressImage; + [self __setImage:progressImage]; }; } [_downloader setProgressImageBlock:progress callbackQueue:dispatch_get_main_queue() withDownloadIdentifier:_downloadIdentifier]; @@ -538,7 +545,7 @@ typedef void(^ASMultiplexImageLoadCompletionBlock)(UIImage *image, id imageIdent if (shouldReleaseImageOnBackgroundThread) { ASPerformBackgroundDeallocation(image); } - self.image = nil; + [self __setImage:nil]; } #pragma mark - @@ -867,7 +874,7 @@ typedef void(^ASMultiplexImageLoadCompletionBlock)(UIImage *image, id imageIdent UIImage *previousImage = self.image; self.loadedImageIdentifier = imageIdentifier; - self.image = image; + [self __setImage:image]; if (_delegateFlags.updatedImage) { [_delegate multiplexImageNode:self didUpdateImage:image withIdentifier:imageIdentifier fromImage:previousImage withIdentifier:previousIdentifier]; diff --git a/AsyncDisplayKit/ASNetworkImageNode.mm b/AsyncDisplayKit/ASNetworkImageNode.mm index bb106aa4e6..7ab5b7dc6a 100755 --- a/AsyncDisplayKit/ASNetworkImageNode.mm +++ b/AsyncDisplayKit/ASNetworkImageNode.mm @@ -9,15 +9,16 @@ // #import "ASNetworkImageNode.h" +#import "ASImageNode+Private.h" #import "ASBasicImageDownloader.h" #import "ASDisplayNodeInternal.h" +#import "ASDisplayNodeExtras.h" #import "ASDisplayNode+Subclasses.h" #import "ASDisplayNode+FrameworkPrivate.h" #import "ASEqualityHelpers.h" #import "ASInternalHelpers.h" #import "ASImageContainerProtocolCategories.h" -#import "ASDisplayNodeExtras.h" #if PIN_REMOTE_IMAGE #import "ASPINRemoteImageDownloader.h" @@ -67,6 +68,7 @@ static const CGSize kMinReleaseImageOnBackgroundSize = {20.0, 20.0}; unsigned int cacheSupportsSynchronousFetch:1; } _cacheFlags; } + @end @implementation ASNetworkImageNode @@ -116,6 +118,12 @@ static const CGSize kMinReleaseImageOnBackgroundSize = {20.0, 20.0}; #pragma mark - Public methods -- must lock +- (void)setImage:(UIImage *)image +{ + ASDisplayNodeAssert(NO, @"Setting the image directly to an ASNetworkImageNode is not allowed. Please either use the defaultImage property or move to an ASImageNode"); + [self __setImage:image]; +} + - (void)setURL:(NSURL *)URL { [self setURL:URL resetToDefault:YES]; @@ -136,7 +144,7 @@ static const CGSize kMinReleaseImageOnBackgroundSize = {20.0, 20.0}; BOOL hasURL = _URL == nil; if (reset || hasURL) { - self.image = _defaultImage; + [self __setImage:_defaultImage]; /* We want to maintain the order that currentImageQuality is set regardless of the calling thread, so always use a dispatch_async to ensure that we queue the operations in the correct order. (see comment in displayDidFinish) */ @@ -171,7 +179,7 @@ static const CGSize kMinReleaseImageOnBackgroundSize = {20.0, 20.0}; dispatch_async(dispatch_get_main_queue(), ^{ self.currentImageQuality = hasURL ? 0.0 : 1.0; }); - self.image = defaultImage; + [self __setImage:defaultImage]; } } @@ -256,7 +264,7 @@ static const CGSize kMinReleaseImageOnBackgroundSize = {20.0, 20.0}; if (_imageLoaded == NO && _URL && _downloadIdentifier == nil) { UIImage *result = [[_cache synchronouslyFetchedCachedImageWithURL:_URL] asdk_image]; if (result) { - self.image = result; + [self __setImage:result]; _imageLoaded = YES; dispatch_async(dispatch_get_main_queue(), ^{ _currentImageQuality = 1.0; @@ -340,7 +348,7 @@ static const CGSize kMinReleaseImageOnBackgroundSize = {20.0, 20.0}; if (ASObjectIsEqual(_downloadIdentifier, downloadIdentifier) == NO && downloadIdentifier != nil) { return; } - self.image = progressImage; + [self __setImage:progressImage]; dispatch_async(dispatch_get_main_queue(), ^{ // See comment in -displayDidFinish for why this must be dispatched to main self.currentImageQuality = progress; @@ -396,7 +404,7 @@ static const CGSize kMinReleaseImageOnBackgroundSize = {20.0, 20.0}; ASPerformBackgroundDeallocation(image); } self.animatedImage = nil; - self.image = _defaultImage; + [self __setImage:_defaultImage]; _imageLoaded = NO; // See comment in -displayDidFinish for why this must be dispatched to main dispatch_async(dispatch_get_main_queue(), ^{ @@ -456,7 +464,7 @@ static const CGSize kMinReleaseImageOnBackgroundSize = {20.0, 20.0}; dispatch_async(dispatch_get_main_queue(), ^{ if (self.shouldCacheImage) { - self.image = [UIImage imageNamed:_URL.path.lastPathComponent]; + [self __setImage:[UIImage imageNamed:_URL.path.lastPathComponent]]; } else { // First try to load the path directly, for efficiency assuming a developer who // doesn't want caching is trying to be as minimal as possible. @@ -486,7 +494,7 @@ static const CGSize kMinReleaseImageOnBackgroundSize = {20.0, 20.0}; if (animatedImage != nil) { self.animatedImage = animatedImage; } else { - self.image = nonAnimatedImage; + [self __setImage:nonAnimatedImage]; } } @@ -522,7 +530,7 @@ static const CGSize kMinReleaseImageOnBackgroundSize = {20.0, 20.0}; if ([imageContainer asdk_animatedImageData] && _downloaderFlags.downloaderImplementsAnimatedImage) { strongSelf.animatedImage = [_downloader animatedImageWithData:[imageContainer asdk_animatedImageData]]; } else { - strongSelf.image = [imageContainer asdk_image]; + [strongSelf __setImage:[imageContainer asdk_image]]; } dispatch_async(dispatch_get_main_queue(), ^{ strongSelf->_currentImageQuality = 1.0; diff --git a/AsyncDisplayKit/ASVideoNode.mm b/AsyncDisplayKit/ASVideoNode.mm index 878189deb3..5cd4447af2 100644 --- a/AsyncDisplayKit/ASVideoNode.mm +++ b/AsyncDisplayKit/ASVideoNode.mm @@ -16,6 +16,7 @@ #import "ASEqualityHelpers.h" #import "ASInternalHelpers.h" #import "ASDisplayNodeExtras.h" +#import "ASImageNode+Private.h" static BOOL ASAssetIsEqual(AVAsset *asset1, AVAsset *asset2) { return ASObjectIsEqual(asset1, asset2) @@ -300,7 +301,7 @@ static NSString * const kRate = @"rate"; if (image != nil) { self.contentMode = ASContentModeFromVideoGravity(_gravity); } - self.image = image; + [self __setImage:image]; } - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context diff --git a/AsyncDisplayKit/Private/ASImageNode+Private.h b/AsyncDisplayKit/Private/ASImageNode+Private.h new file mode 100644 index 0000000000..d380953dbb --- /dev/null +++ b/AsyncDisplayKit/Private/ASImageNode+Private.h @@ -0,0 +1,22 @@ +// +// ASImageNode+Private.h +// AsyncDisplayKit +// +// Created by Michael Schneider on 12/3/16. +// Copyright © 2016 Facebook. All rights reserved. +// + +#pragma mark - ASImageNode + +#import "ASImageNode.h" + +@interface ASImageNode (Private) + +/* + * Set the image property of the ASImageNode. Subclasses like ASNetworkImageNode do not allow setting the + * image property directly and throw an assertion. There still needs to be a way for subclasses of + * ASNetworkImageNode to set the image. + */ +- (void)__setImage:(UIImage *)image; + +@end diff --git a/AsyncDisplayKitTests/ASMultiplexImageNodeTests.m b/AsyncDisplayKitTests/ASMultiplexImageNodeTests.m index 4fec68aadf..49a424b5c9 100644 --- a/AsyncDisplayKitTests/ASMultiplexImageNodeTests.m +++ b/AsyncDisplayKitTests/ASMultiplexImageNodeTests.m @@ -302,4 +302,10 @@ [mockDelegate verify]; } -@end \ No newline at end of file +- (void)testThatSettingAnImageExternallyWillThrow +{ + ASMultiplexImageNode *multiplexImageNode = [[ASMultiplexImageNode alloc] init]; + XCTAssertThrows(multiplexImageNode.image = [UIImage imageNamed:@""]); +} + +@end diff --git a/AsyncDisplayKitTests/ASNetworkImageNodeTests.m b/AsyncDisplayKitTests/ASNetworkImageNodeTests.m index b7086e6c85..8ea8ee8c5e 100644 --- a/AsyncDisplayKitTests/ASNetworkImageNodeTests.m +++ b/AsyncDisplayKitTests/ASNetworkImageNodeTests.m @@ -71,6 +71,12 @@ [downloader verifyWithDelay:5]; } +- (void)testThatSettingAnImageExternallyWillThrow +{ + ASNetworkImageNode *networkImageNode = [[ASNetworkImageNode alloc] init]; + XCTAssertThrows(networkImageNode.image = [UIImage imageNamed:@""]); +} + @end @implementation ASTestImageCache