UIButton+YYWebImage.m 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420
  1. //
  2. // UIButton+YYWebImage.m
  3. // YYKit <https://github.com/ibireme/YYKit>
  4. //
  5. // Created by ibireme on 15/2/23.
  6. // Copyright (c) 2015 ibireme.
  7. //
  8. // This source code is licensed under the MIT-style license found in the
  9. // LICENSE file in the root directory of this source tree.
  10. //
  11. #import "UIButton+YYWebImage.h"
  12. #import "YYWebImageOperation.h"
  13. #import "_YYWebImageSetter.h"
  14. #import <libkern/OSAtomic.h>
  15. #import <objc/runtime.h>
  16. // Dummy class for category
  17. @interface UIButton_YYWebImage : NSObject @end
  18. @implementation UIButton_YYWebImage @end
  19. static inline NSNumber *UIControlStateSingle(UIControlState state) {
  20. if (state & UIControlStateHighlighted) return @(UIControlStateHighlighted);
  21. if (state & UIControlStateDisabled) return @(UIControlStateDisabled);
  22. if (state & UIControlStateSelected) return @(UIControlStateSelected);
  23. return @(UIControlStateNormal);
  24. }
  25. static inline NSArray *UIControlStateMulti(UIControlState state) {
  26. NSMutableArray *array = [NSMutableArray new];
  27. if (state & UIControlStateHighlighted) [array addObject:@(UIControlStateHighlighted)];
  28. if (state & UIControlStateDisabled) [array addObject:@(UIControlStateDisabled)];
  29. if (state & UIControlStateSelected) [array addObject:@(UIControlStateSelected)];
  30. if ((state & 0xFF) == 0) [array addObject:@(UIControlStateNormal)];
  31. return array;
  32. }
  33. static int _YYWebImageSetterKey;
  34. static int _YYWebImageBackgroundSetterKey;
  35. @interface _YYWebImageSetterDicForButton : NSObject
  36. - (_YYWebImageSetter *)setterForState:(NSNumber *)state;
  37. - (_YYWebImageSetter *)lazySetterForState:(NSNumber *)state;
  38. @end
  39. @implementation _YYWebImageSetterDicForButton {
  40. NSMutableDictionary *_dic;
  41. OSSpinLock _lock;
  42. }
  43. - (instancetype)init {
  44. self = [super init];
  45. _lock = OS_SPINLOCK_INIT;
  46. _dic = [NSMutableDictionary new];
  47. return self;
  48. }
  49. - (_YYWebImageSetter *)setterForState:(NSNumber *)state {
  50. OSSpinLockLock(&_lock);
  51. _YYWebImageSetter *setter = _dic[state];
  52. OSSpinLockUnlock(&_lock);
  53. return setter;
  54. }
  55. - (_YYWebImageSetter *)lazySetterForState:(NSNumber *)state {
  56. OSSpinLockLock(&_lock);
  57. _YYWebImageSetter *setter = _dic[state];
  58. if (!setter) {
  59. setter = [_YYWebImageSetter new];
  60. _dic[state] = setter;
  61. }
  62. OSSpinLockUnlock(&_lock);
  63. return setter;
  64. }
  65. @end
  66. @implementation UIButton (YYWebImage)
  67. #pragma mark - image
  68. - (void)_yy_setImageWithURL:(NSURL *)imageURL
  69. forSingleState:(NSNumber *)state
  70. placeholder:(UIImage *)placeholder
  71. options:(YYWebImageOptions)options
  72. manager:(YYWebImageManager *)manager
  73. progress:(YYWebImageProgressBlock)progress
  74. transform:(YYWebImageTransformBlock)transform
  75. completion:(YYWebImageCompletionBlock)completion {
  76. if ([imageURL isKindOfClass:[NSString class]]) imageURL = [NSURL URLWithString:(id)imageURL];
  77. manager = manager ? manager : [YYWebImageManager sharedManager];
  78. _YYWebImageSetterDicForButton *dic = objc_getAssociatedObject(self, &_YYWebImageSetterKey);
  79. if (!dic) {
  80. dic = [_YYWebImageSetterDicForButton new];
  81. objc_setAssociatedObject(self, &_YYWebImageSetterKey, dic, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
  82. }
  83. _YYWebImageSetter *setter = [dic lazySetterForState:state];
  84. int32_t sentinel = [setter cancelWithNewURL:imageURL];
  85. _yy_dispatch_sync_on_main_queue(^{
  86. if (!imageURL) {
  87. if (!(options & YYWebImageOptionIgnorePlaceHolder)) {
  88. [self setImage:placeholder forState:state.integerValue];
  89. }
  90. return;
  91. }
  92. // get the image from memory as quickly as possible
  93. UIImage *imageFromMemory = nil;
  94. if (manager.cache &&
  95. !(options & YYWebImageOptionUseNSURLCache) &&
  96. !(options & YYWebImageOptionRefreshImageCache)) {
  97. imageFromMemory = [manager.cache getImageForKey:[manager cacheKeyForURL:imageURL] withType:YYImageCacheTypeMemory];
  98. }
  99. if (imageFromMemory) {
  100. if (!(options & YYWebImageOptionAvoidSetImage)) {
  101. [self setImage:imageFromMemory forState:state.integerValue];
  102. }
  103. if(completion) completion(imageFromMemory, imageURL, YYWebImageFromMemoryCacheFast, YYWebImageStageFinished, nil);
  104. return;
  105. }
  106. if (!(options & YYWebImageOptionIgnorePlaceHolder)) {
  107. [self setImage:placeholder forState:state.integerValue];
  108. }
  109. __weak typeof(self) _self = self;
  110. dispatch_async([_YYWebImageSetter setterQueue], ^{
  111. YYWebImageProgressBlock _progress = nil;
  112. if (progress) _progress = ^(NSInteger receivedSize, NSInteger expectedSize) {
  113. dispatch_async(dispatch_get_main_queue(), ^{
  114. progress(receivedSize, expectedSize);
  115. });
  116. };
  117. YYWebImageCompletionBlock _completion = ^(UIImage *image, NSURL *url, YYWebImageFromType from, YYWebImageStage stage, NSError *error) {
  118. __strong typeof(_self) self = _self;
  119. BOOL setImage = (stage == YYWebImageStageFinished || stage == YYWebImageStageProgress) && image && !(options & YYWebImageOptionAvoidSetImage);
  120. dispatch_async(dispatch_get_main_queue(), ^{
  121. if (setImage && self) {
  122. [self setImage:image forState:state.integerValue];
  123. }
  124. if (completion) completion(image, url, from, stage, error);
  125. });
  126. };
  127. [setter setOperationWithSentinel:sentinel url:imageURL options:options manager:manager progress:_progress transform:transform completion:_completion];
  128. });
  129. });
  130. }
  131. - (void)_yy_cancelImageRequestForSingleState:(NSNumber *)state {
  132. _YYWebImageSetterDicForButton *dic = objc_getAssociatedObject(self, &_YYWebImageSetterKey);
  133. _YYWebImageSetter *setter = [dic setterForState:state];
  134. if (setter) [setter cancel];
  135. }
  136. - (NSURL *)yy_imageURLForState:(UIControlState)state {
  137. _YYWebImageSetterDicForButton *dic = objc_getAssociatedObject(self, &_YYWebImageSetterKey);
  138. _YYWebImageSetter *setter = [dic setterForState:UIControlStateSingle(state)];
  139. return setter.imageURL;
  140. }
  141. - (void)yy_setImageWithURL:(NSURL *)imageURL
  142. forState:(UIControlState)state
  143. placeholder:(UIImage *)placeholder {
  144. [self yy_setImageWithURL:imageURL
  145. forState:state
  146. placeholder:placeholder
  147. options:kNilOptions
  148. manager:nil
  149. progress:nil
  150. transform:nil
  151. completion:nil];
  152. }
  153. - (void)yy_setImageWithURL:(NSURL *)imageURL
  154. forState:(UIControlState)state
  155. options:(YYWebImageOptions)options {
  156. [self yy_setImageWithURL:imageURL
  157. forState:state
  158. placeholder:nil
  159. options:options
  160. manager:nil
  161. progress:nil
  162. transform:nil
  163. completion:nil];
  164. }
  165. - (void)yy_setImageWithURL:(NSURL *)imageURL
  166. forState:(UIControlState)state
  167. placeholder:(UIImage *)placeholder
  168. options:(YYWebImageOptions)options
  169. completion:(YYWebImageCompletionBlock)completion {
  170. [self yy_setImageWithURL:imageURL
  171. forState:state
  172. placeholder:placeholder
  173. options:options
  174. manager:nil
  175. progress:nil
  176. transform:nil
  177. completion:completion];
  178. }
  179. - (void)yy_setImageWithURL:(NSURL *)imageURL
  180. forState:(UIControlState)state
  181. placeholder:(UIImage *)placeholder
  182. options:(YYWebImageOptions)options
  183. progress:(YYWebImageProgressBlock)progress
  184. transform:(YYWebImageTransformBlock)transform
  185. completion:(YYWebImageCompletionBlock)completion {
  186. [self yy_setImageWithURL:imageURL
  187. forState:state
  188. placeholder:placeholder
  189. options:options
  190. manager:nil
  191. progress:progress
  192. transform:transform
  193. completion:completion];
  194. }
  195. - (void)yy_setImageWithURL:(NSURL *)imageURL
  196. forState:(UIControlState)state
  197. placeholder:(UIImage *)placeholder
  198. options:(YYWebImageOptions)options
  199. manager:(YYWebImageManager *)manager
  200. progress:(YYWebImageProgressBlock)progress
  201. transform:(YYWebImageTransformBlock)transform
  202. completion:(YYWebImageCompletionBlock)completion {
  203. for (NSNumber *num in UIControlStateMulti(state)) {
  204. [self _yy_setImageWithURL:imageURL
  205. forSingleState:num
  206. placeholder:placeholder
  207. options:options
  208. manager:manager
  209. progress:progress
  210. transform:transform
  211. completion:completion];
  212. }
  213. }
  214. - (void)yy_cancelImageRequestForState:(UIControlState)state {
  215. for (NSNumber *num in UIControlStateMulti(state)) {
  216. [self _yy_cancelImageRequestForSingleState:num];
  217. }
  218. }
  219. #pragma mark - background image
  220. - (void)_yy_setBackgroundImageWithURL:(NSURL *)imageURL
  221. forSingleState:(NSNumber *)state
  222. placeholder:(UIImage *)placeholder
  223. options:(YYWebImageOptions)options
  224. manager:(YYWebImageManager *)manager
  225. progress:(YYWebImageProgressBlock)progress
  226. transform:(YYWebImageTransformBlock)transform
  227. completion:(YYWebImageCompletionBlock)completion {
  228. if ([imageURL isKindOfClass:[NSString class]]) imageURL = [NSURL URLWithString:(id)imageURL];
  229. manager = manager ? manager : [YYWebImageManager sharedManager];
  230. _YYWebImageSetterDicForButton *dic = objc_getAssociatedObject(self, &_YYWebImageBackgroundSetterKey);
  231. if (!dic) {
  232. dic = [_YYWebImageSetterDicForButton new];
  233. objc_setAssociatedObject(self, &_YYWebImageBackgroundSetterKey, dic, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
  234. }
  235. _YYWebImageSetter *setter = [dic lazySetterForState:state];
  236. int32_t sentinel = [setter cancelWithNewURL:imageURL];
  237. _yy_dispatch_sync_on_main_queue(^{
  238. if (!imageURL) {
  239. if (!(options & YYWebImageOptionIgnorePlaceHolder)) {
  240. [self setBackgroundImage:placeholder forState:state.integerValue];
  241. }
  242. return;
  243. }
  244. // get the image from memory as quickly as possible
  245. UIImage *imageFromMemory = nil;
  246. if (manager.cache &&
  247. !(options & YYWebImageOptionUseNSURLCache) &&
  248. !(options & YYWebImageOptionRefreshImageCache)) {
  249. imageFromMemory = [manager.cache getImageForKey:[manager cacheKeyForURL:imageURL] withType:YYImageCacheTypeMemory];
  250. }
  251. if (imageFromMemory) {
  252. if (!(options & YYWebImageOptionAvoidSetImage)) {
  253. [self setBackgroundImage:imageFromMemory forState:state.integerValue];
  254. }
  255. if(completion) completion(imageFromMemory, imageURL, YYWebImageFromMemoryCacheFast, YYWebImageStageFinished, nil);
  256. return;
  257. }
  258. if (!(options & YYWebImageOptionIgnorePlaceHolder)) {
  259. [self setBackgroundImage:placeholder forState:state.integerValue];
  260. }
  261. __weak typeof(self) _self = self;
  262. dispatch_async([_YYWebImageSetter setterQueue], ^{
  263. YYWebImageProgressBlock _progress = nil;
  264. if (progress) _progress = ^(NSInteger receivedSize, NSInteger expectedSize) {
  265. dispatch_async(dispatch_get_main_queue(), ^{
  266. progress(receivedSize, expectedSize);
  267. });
  268. };
  269. YYWebImageCompletionBlock _completion = ^(UIImage *image, NSURL *url, YYWebImageFromType from, YYWebImageStage stage, NSError *error) {
  270. __strong typeof(_self) self = _self;
  271. BOOL setImage = (stage == YYWebImageStageFinished || stage == YYWebImageStageProgress) && image && !(options & YYWebImageOptionAvoidSetImage);
  272. dispatch_async(dispatch_get_main_queue(), ^{
  273. if (setImage && self) {
  274. [self setBackgroundImage:image forState:state.integerValue];
  275. }
  276. if (completion) completion(image, url, from, stage, error);
  277. });
  278. };
  279. [setter setOperationWithSentinel:sentinel url:imageURL options:options manager:manager progress:_progress transform:transform completion:_completion];
  280. });
  281. });
  282. }
  283. - (void)_yy_cancelBackgroundImageRequestForSingleState:(NSNumber *)state {
  284. _YYWebImageSetterDicForButton *dic = objc_getAssociatedObject(self, &_YYWebImageBackgroundSetterKey);
  285. _YYWebImageSetter *setter = [dic setterForState:state];
  286. if (setter) [setter cancel];
  287. }
  288. - (NSURL *)yy_backgroundImageURLForState:(UIControlState)state {
  289. _YYWebImageSetterDicForButton *dic = objc_getAssociatedObject(self, &_YYWebImageBackgroundSetterKey);
  290. _YYWebImageSetter *setter = [dic setterForState:UIControlStateSingle(state)];
  291. return setter.imageURL;
  292. }
  293. - (void)yy_setBackgroundImageWithURL:(NSURL *)imageURL
  294. forState:(UIControlState)state
  295. placeholder:(UIImage *)placeholder {
  296. [self yy_setBackgroundImageWithURL:imageURL
  297. forState:state
  298. placeholder:placeholder
  299. options:kNilOptions
  300. manager:nil
  301. progress:nil
  302. transform:nil
  303. completion:nil];
  304. }
  305. - (void)yy_setBackgroundImageWithURL:(NSURL *)imageURL
  306. forState:(UIControlState)state
  307. options:(YYWebImageOptions)options {
  308. [self yy_setBackgroundImageWithURL:imageURL
  309. forState:state
  310. placeholder:nil
  311. options:options
  312. manager:nil
  313. progress:nil
  314. transform:nil
  315. completion:nil];
  316. }
  317. - (void)yy_setBackgroundImageWithURL:(NSURL *)imageURL
  318. forState:(UIControlState)state
  319. placeholder:(UIImage *)placeholder
  320. options:(YYWebImageOptions)options
  321. completion:(YYWebImageCompletionBlock)completion {
  322. [self yy_setBackgroundImageWithURL:imageURL
  323. forState:state
  324. placeholder:placeholder
  325. options:options
  326. manager:nil
  327. progress:nil
  328. transform:nil
  329. completion:completion];
  330. }
  331. - (void)yy_setBackgroundImageWithURL:(NSURL *)imageURL
  332. forState:(UIControlState)state
  333. placeholder:(UIImage *)placeholder
  334. options:(YYWebImageOptions)options
  335. progress:(YYWebImageProgressBlock)progress
  336. transform:(YYWebImageTransformBlock)transform
  337. completion:(YYWebImageCompletionBlock)completion {
  338. [self yy_setBackgroundImageWithURL:imageURL
  339. forState:state
  340. placeholder:placeholder
  341. options:options
  342. manager:nil
  343. progress:progress
  344. transform:transform
  345. completion:completion];
  346. }
  347. - (void)yy_setBackgroundImageWithURL:(NSURL *)imageURL
  348. forState:(UIControlState)state
  349. placeholder:(UIImage *)placeholder
  350. options:(YYWebImageOptions)options
  351. manager:(YYWebImageManager *)manager
  352. progress:(YYWebImageProgressBlock)progress
  353. transform:(YYWebImageTransformBlock)transform
  354. completion:(YYWebImageCompletionBlock)completion {
  355. for (NSNumber *num in UIControlStateMulti(state)) {
  356. [self _yy_setBackgroundImageWithURL:imageURL
  357. forSingleState:num
  358. placeholder:placeholder
  359. options:options
  360. manager:manager
  361. progress:progress
  362. transform:transform
  363. completion:completion];
  364. }
  365. }
  366. - (void)yy_cancelBackgroundImageRequestForState:(UIControlState)state {
  367. for (NSNumber *num in UIControlStateMulti(state)) {
  368. [self _yy_cancelBackgroundImageRequestForSingleState:num];
  369. }
  370. }
  371. @end