Commit 45186681 authored by jz's avatar jz

迁移shareView

parent a13fbc8c
......@@ -2,7 +2,10 @@ PODS:
- BDOpenSDKKit (1.0.0)
- DouyinOpenSDK (1.4.1):
- BDOpenSDKKit (~> 1.0.0)
- GMCache (1.0.1):
- TMCache (= 2.1.0)
- GMFoundation (1.0.3)
- GMJSONModel (1.7.4)
- GMKit (1.1.6):
- GMKit/Category (= 1.1.6)
- GMKit/Color (= 1.1.6)
......@@ -37,16 +40,25 @@ PODS:
- GMKit/Protocol (1.1.6):
- Masonry
- SDWebImage
- GMPhobos (1.3.5):
- GMCache
- GMKit
- GMShareSDK (0.1.5):
- DouyinOpenSDK
- GMFoundation
- GMJSONModel
- GMKit
- GMPhobos
- Masonry
- MBProgressHUD
- WechatOpenSDK
- WeiboSDK
- Masonry (1.1.0)
- MBProgressHUD (1.1.0)
- SDWebImage (5.4.0):
- SDWebImage/Core (= 5.4.0)
- SDWebImage/Core (5.4.0)
- TMCache (2.1.0)
- WechatOpenSDK (1.8.6)
- WeiboSDK (3.1.3)
......@@ -55,13 +67,18 @@ DEPENDENCIES:
SPEC REPOS:
"git@git.wanmeizhensuo.com:gengmeiios/GMSpecs.git":
- GMCache
- GMFoundation
- GMJSONModel
- GMKit
- GMPhobos
https://github.com/cocoapods/specs.git:
- BDOpenSDKKit
- DouyinOpenSDK
- Masonry
- MBProgressHUD
- SDWebImage
- TMCache
- WechatOpenSDK
- WeiboSDK
......@@ -72,11 +89,16 @@ EXTERNAL SOURCES:
SPEC CHECKSUMS:
BDOpenSDKKit: 3fb530ce73f85a7d6ee69e7fd3d9158444c5bd09
DouyinOpenSDK: 5ba83de22963ba7a3ba70c8ff11dfcb2885ecc2b
GMCache: b78d8e46db864405e91d226ce640cc80d966c611
GMFoundation: 2bdf7cddf02e5251503274c9158ac1c851f2b8da
GMJSONModel: 5e81a98de668e9f93cf6ff77869f77b0d1a806be
GMKit: 2573350637f4d4e200c8cf3426b7b96a924af15e
GMShareSDK: 2d22c8133f1ad18f26cb7aad16a7bdb2193c1d04
GMPhobos: 1e2d68c456b69bf156276d7242877498107474db
GMShareSDK: 116ca081261a2cc1b2b1fbb9e2acae65dd9ffb46
Masonry: 678fab65091a9290e40e2832a55e7ab731aad201
MBProgressHUD: e7baa36a220447d8aeb12769bf0585582f3866d9
SDWebImage: 5bf6aec6481ae2a062bdc59f9d6c1d1e552090e0
TMCache: 95ebcc9b3c7e90fb5fd8fc3036cba3aa781c9bed
WechatOpenSDK: 368ae03b72ee3ea1328c4f11326fbb5d2721d118
WeiboSDK: acb067053668102cf07d01aa7604350162c2e466
......
//
// GMCache.h
// Gengmei
//
// Created by Thierry on 1/5/15.
// Copyright (c) 2015 Wanmeichuangyi. All rights reserved.
//
#import <Foundation/Foundation.h>
#import <TMCache/TMCache.h>
@interface GMCache : NSObject
#pragma mark - 异步磁盘读写操作
/*** @brief 由对应的键获取对应的缓存数据*/
+ (void)storeObjectAtDiskWithkey:(NSString *)key
object:(id <NSCoding>)object
block:(TMDiskCacheObjectBlock)block;
/*** @brief 给特定的键,标记缓存数据并缓存*/
+ (void)fetchObjectAtDiskWithkey:(NSString *)key
block:(TMDiskCacheObjectBlock)block;
/*** @brief 删除特定的键,对应的缓存数据*/
+ (void)removeObjectAtDiskWithkey:(NSString *)key
block:(TMDiskCacheObjectBlock)block;
/*** @brief 清空所有的缓存数据*/
+ (void)removeAllObjectsAtDiskWithBlock:(TMDiskCacheBlock)block;
#pragma mark - 同步磁盘读写操作
/*** @brief 由对应的键获取对应的缓存数据*/
+ (void)storeObjectAtDiskWithkey:(NSString *)key
object:(id <NSCoding>)object;
/*** @brief 给特定的键,标记缓存数据并缓存*/
+ (id)fetchObjectAtDiskWithkey:(NSString *)key;
/*** @brief 删除特定的键,对应的缓存数据*/
+ (void)removeObjectAtDiskWithkey:(NSString *)key;
/*** @brief 清空所有的缓存数据*/
+ (void)removeAllObjectsAtDisk;
#pragma mark - 异步内存读写操作
/*** @brief 由对应的键获取对应的缓存数据*/
+ (void)storeObjectAtMemoryWithkey:(NSString *)key
object:(id <NSCoding>)object
block:(TMMemoryCacheObjectBlock)block;
/*** @brief 给特定的键,标记缓存数据并缓存*/
+ (void)fetchObjectAtMemoryWithkey:(NSString *)key
block:(TMMemoryCacheObjectBlock)block;
/*** @brief 删除特定的键,对应的缓存数据*/
+ (void)removeObjectAtMemoryWithkey:(NSString *)key
block:(TMMemoryCacheObjectBlock)block;
/*** @brief 清空所有的缓存数据*/
+ (void)removeAllObjectsAtMemoryWithBlock:(TMMemoryCacheBlock)block;
#pragma mark - 同步内存读写操作
/*** @brief 由对应的键获取对应的缓存数据*/
+ (void)storeObjectAtMemoryWithkey:(NSString *)key
object:(id)object;
/*** @brief 给特定的键,标记缓存数据并缓存*/
+ (id)fetchObjectAtMemoryWithkey:(NSString *)key;
/*** @brief 删除特定的键,对应的缓存数据*/
+ (void)removeObjectAtMemoryWithkey:(NSString *)key;
/*** @brief 清空所有的缓存数据*/
+ (void)removeAllObjectsAtMemory;
#pragma mark - 这里将数据缓存到Ducument目录下。异步
+ (void)storeObjectAtDocumentPathWithkey:(NSString *)key
object:(id <NSCoding>)object
block:(TMDiskCacheObjectBlock)block;
+ (void)fetchObjectAtDocumentPathWithkey:(NSString *)key
block:(TMDiskCacheObjectBlock)block;
+ (void)removeObjectAtDocumentPathWithkey:(NSString *)key
block:(TMDiskCacheObjectBlock)block;
+ (void)removeAllObjectsAtDocumentPathWithBlock:(TMDiskCacheBlock)block;
#pragma mark - 这里将数据缓存到Ducument目录下。同步内存读写操作
+ (void)storeObjectAtDocumentPathWithkey:(NSString *)key
object:(id <NSCoding>)object;
+ (id)fetchObjectAtDocumentPathWithkey:(NSString *)key;
+ (void)removeObjectAtDocumentPathWithkey:(NSString *)key;
+ (void)removeAllObjectsAtDocumentPath;
@end
//
// GMCache.m
// Gengmei
//
// Created by Thierry on 1/5/15.
// Copyright (c) 2015 Wanmeichuangyi. All rights reserved.
//
#import "GMCache.h"
@interface WMDocumentCache : TMCache
@end
@implementation GMCache
+ (void)storeObjectAtDiskWithkey:(NSString *)key object:(id <NSCoding>)object block:(TMDiskCacheObjectBlock)block{
NSAssert([GMCache gmCache_isNonEmpty:key], @"key不能为空");
if (![GMCache gmCache_isNonEmpty:key]) {
return;
}
[[TMCache sharedCache].diskCache setObject:object forKey:key block:block];
}
+ (void)fetchObjectAtDiskWithkey:(NSString *)key block:(TMDiskCacheObjectBlock)block{
NSAssert([GMCache gmCache_isNonEmpty:key], @"key不能为空");
if (![GMCache gmCache_isNonEmpty:key]) {
return;
}
[[TMCache sharedCache].diskCache objectForKey:key block:block];
}
+ (void)removeObjectAtDiskWithkey:(NSString *)key block:(TMDiskCacheObjectBlock)block{
NSAssert([GMCache gmCache_isNonEmpty:key], @"key不能为空");
if (![GMCache gmCache_isNonEmpty:key]) {
return;
}
[[TMCache sharedCache].diskCache removeObjectForKey:key block:block];
}
+ (void)removeAllObjectsAtDiskWithBlock:(TMDiskCacheBlock)block{
[[TMCache sharedCache].diskCache removeAllObjects:block];
}
+ (void)storeObjectAtDiskWithkey:(NSString *)key object:(id <NSCoding>)object{
NSAssert([GMCache gmCache_isNonEmpty:key], @"key不能为空");
if (![GMCache gmCache_isNonEmpty:key]) {
return;
}
[[TMCache sharedCache].diskCache setObject:object forKey:key];
}
+ (id)fetchObjectAtDiskWithkey:(NSString *)key{
NSAssert([GMCache gmCache_isNonEmpty:key], @"key不能为空");
if (![GMCache gmCache_isNonEmpty:key]) {
return nil;
}
return [[TMCache sharedCache].diskCache objectForKey:key];
}
+ (void)removeObjectAtDiskWithkey:(NSString *)key{
NSAssert([GMCache gmCache_isNonEmpty:key], @"key不能为空");
if (![GMCache gmCache_isNonEmpty:key]) {
return;
}
[[TMCache sharedCache].diskCache removeObjectForKey:key];
}
+ (void)removeAllObjectsAtDisk{
[[TMCache sharedCache].diskCache removeAllObjects];
}
+ (void)storeObjectAtMemoryWithkey:(NSString *)key object:(id <NSCoding>)object block:(TMMemoryCacheObjectBlock)block{
NSAssert([GMCache gmCache_isNonEmpty:key], @"key不能为空");
if (![GMCache gmCache_isNonEmpty:key]) {
return;
}
[[TMCache sharedCache].memoryCache setObject:object forKey:key block:block];
}
+ (void)fetchObjectAtMemoryWithkey:(NSString *)key block:(TMMemoryCacheObjectBlock)block{
NSAssert([GMCache gmCache_isNonEmpty:key], @"key不能为空");
if (![GMCache gmCache_isNonEmpty:key]) {
return;
}
[[TMCache sharedCache].memoryCache objectForKey:key block:block];
}
+ (void)removeObjectAtMemoryWithkey:(NSString *)key block:(TMMemoryCacheObjectBlock)block{
NSAssert([GMCache gmCache_isNonEmpty:key], @"key不能为空");
if (![GMCache gmCache_isNonEmpty:key]) {
return;
}
[[TMCache sharedCache].memoryCache removeObjectForKey:key block:block];
}
+ (void)removeAllObjectsAtMemoryWithBlock:(TMMemoryCacheBlock)block{
[[TMCache sharedCache].memoryCache removeAllObjects:block];
}
+ (void)storeObjectAtMemoryWithkey:(NSString *)key object:(id)object{
NSAssert([GMCache gmCache_isNonEmpty:key], @"key不能为空");
if (![GMCache gmCache_isNonEmpty:key]) {
return;
}
[[TMCache sharedCache].memoryCache setObject:object forKey:key];
}
+ (id)fetchObjectAtMemoryWithkey:(NSString *)key{
NSAssert([GMCache gmCache_isNonEmpty:key], @"key不能为空");
if (![GMCache gmCache_isNonEmpty:key]) {
return nil;
}
return [[TMCache sharedCache].memoryCache objectForKey:key];
}
+ (void)removeObjectAtMemoryWithkey:(NSString *)key{
NSAssert([GMCache gmCache_isNonEmpty:key], @"key不能为空");
if (![GMCache gmCache_isNonEmpty:key]) {
return;
}
[[TMCache sharedCache].memoryCache removeObjectForKey:key];
}
+ (void)removeAllObjectsAtMemory{
[[TMCache sharedCache].memoryCache removeAllObjects];
}
+ (void)storeObjectAtDocumentPathWithkey:(NSString *)key
object:(id <NSCoding>)object
block:(TMDiskCacheObjectBlock)block{
NSAssert([GMCache gmCache_isNonEmpty:key], @"key不能为空");
if (![GMCache gmCache_isNonEmpty:key]) {
return;
}
[[WMDocumentCache sharedCache].diskCache setObject:object forKey:key block:block];
}
+ (void)fetchObjectAtDocumentPathWithkey:(NSString *)key
block:(TMDiskCacheObjectBlock)block{
NSAssert([GMCache gmCache_isNonEmpty:key], @"key不能为空");
if (![GMCache gmCache_isNonEmpty:key]) {
return;
}
[[WMDocumentCache sharedCache].diskCache objectForKey:key block:block];
}
+ (void)removeObjectAtDocumentPathWithkey:(NSString *)key
block:(TMDiskCacheObjectBlock)block{
NSAssert([GMCache gmCache_isNonEmpty:key], @"key不能为空");
if (![GMCache gmCache_isNonEmpty:key]) {
return;
}
[[WMDocumentCache sharedCache].diskCache removeObjectForKey:key block:block];
}
+ (void)removeAllObjectsAtDocumentPathWithBlock:(TMDiskCacheBlock)block{
[[WMDocumentCache sharedCache].diskCache removeAllObjects:block];
}
+ (void)storeObjectAtDocumentPathWithkey:(NSString *)key
object:(id <NSCoding>)object{
NSAssert([GMCache gmCache_isNonEmpty:key], @"key不能为空");
if (![GMCache gmCache_isNonEmpty:key]) {
return;
}
[[WMDocumentCache sharedCache].diskCache setObject:object forKey:key];
}
+ (id)fetchObjectAtDocumentPathWithkey:(NSString *)key{
NSAssert([GMCache gmCache_isNonEmpty:key], @"key不能为空");
if (![GMCache gmCache_isNonEmpty:key]) {
return nil;
}
return [[WMDocumentCache sharedCache].diskCache objectForKey:key];
}
+ (void)removeObjectAtDocumentPathWithkey:(NSString *)key{
NSAssert([GMCache gmCache_isNonEmpty:key], @"key不能为空");
if (![GMCache gmCache_isNonEmpty:key]) {
return;
}
[[WMDocumentCache sharedCache].diskCache removeObjectForKey:key];
}
+ (void)removeAllObjectsAtDocumentPath{
[[WMDocumentCache sharedCache].diskCache removeAllObjects];
}
+ (BOOL)gmCache_isNonEmpty:(NSString *)key {
NSMutableCharacterSet *emptyStringSet = [[NSMutableCharacterSet alloc] init];
[emptyStringSet formUnionWithCharacterSet: [NSCharacterSet whitespaceAndNewlineCharacterSet]];
[emptyStringSet formUnionWithCharacterSet: [NSCharacterSet characterSetWithCharactersInString: @" "]];
if ([key length] == 0) {
return NO;
}
NSString* str = [key stringByTrimmingCharactersInSet:emptyStringSet];
return [str length] > 0;
}
@end
NSString * const WMCacheSharedName = @"WMCacheShared";
@implementation WMDocumentCache
+ (instancetype)sharedCache{
static id cache;
static dispatch_once_t predicate;
dispatch_once(&predicate, ^{
cache = [[self alloc] initWithName:WMCacheSharedName rootPath:[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0]];
});
return cache;
}
@end
Copyright (c) 2016 wangyang <wangyang@wanmeizhensuo.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
# GMCache
[![CI Status](http://img.shields.io/travis/wangyang/GMCache.svg?style=flat)](https://travis-ci.org/wangyang/GMCache)
[![Version](https://img.shields.io/cocoapods/v/GMCache.svg?style=flat)](http://cocoapods.org/pods/GMCache)
[![License](https://img.shields.io/cocoapods/l/GMCache.svg?style=flat)](http://cocoapods.org/pods/GMCache)
[![Platform](https://img.shields.io/cocoapods/p/GMCache.svg?style=flat)](http://cocoapods.org/pods/GMCache)
## Usage
To run the example project, clone the repo, and run `pod install` from the Example directory first.
## Requirements
## Installation
GMCache is available through [CocoaPods](http://cocoapods.org). To install
it, simply add the following line to your Podfile:
```ruby
pod "GMCache"
```
## Author
wangyang, wangyang@wanmeizhensuo.com
## License
GMCache is available under the MIT license. See the LICENSE file for more info.
//
// JSONModel.h
// JSONModel
//
#import <Foundation/Foundation.h>
#import "JSONModelError.h"
#import "JSONValueTransformer.h"
#import "JSONKeyMapper.h"
/////////////////////////////////////////////////////////////////////////////////////////////
#if TARGET_IPHONE_SIMULATOR
#define JMLog( s, ... ) NSLog( @"[%@:%d] %@", [[NSString stringWithUTF8String:__FILE__] \
lastPathComponent], __LINE__, [NSString stringWithFormat:(s), ##__VA_ARGS__] )
#else
#define JMLog( s, ... )
#endif
/////////////////////////////////////////////////////////////////////////////////////////////
DEPRECATED_ATTRIBUTE
@protocol ConvertOnDemand
@end
DEPRECATED_ATTRIBUTE
@protocol Index
@end
#pragma mark - Property Protocols
/**
* Protocol for defining properties in a JSON Model class that should not be considered at all
* neither while importing nor when exporting JSON.
*
* @property (strong, nonatomic) NSString <Ignore> *propertyName;
*
*/
@protocol Ignore
@end
/**
* Protocol for defining optional properties in a JSON Model class. Use like below to define
* model properties that are not required to have values in the JSON input:
*
* @property (strong, nonatomic) NSString <Optional> *propertyName;
*
*/
@protocol Optional
@end
/**
* Make all objects compatible to avoid compiler warnings
*/
@interface NSObject (JSONModelPropertyCompatibility) <Optional, Ignore>
@end
/////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark - JSONModel protocol
/**
* A protocol describing an abstract JSONModel class
* JSONModel conforms to this protocol, so it can use itself abstractly
*/
@protocol AbstractJSONModelProtocol <NSCopying, NSCoding>
@required
/**
* All JSONModel classes should implement initWithDictionary:
*
* For most classes the default initWithDictionary: inherited from JSONModel itself
* should suffice, but developers have the option to also overwrite it if needed.
*
* @param dict a dictionary holding JSON objects, to be imported in the model.
* @param err an error or NULL
*/
- (instancetype)initWithDictionary:(NSDictionary *)dict error:(NSError **)err;
/**
* All JSONModel classes should implement initWithData:error:
*
* For most classes the default initWithData: inherited from JSONModel itself
* should suffice, but developers have the option to also overwrite it if needed.
*
* @param data representing a JSON response (usually fetched from web), to be imported in the model.
* @param error an error or NULL
*/
- (instancetype)initWithData:(NSData *)data error:(NSError **)error;
/**
* All JSONModel classes should be able to export themselves as a dictionary of
* JSON compliant objects.
*
* For most classes the inherited from JSONModel default toDictionary implementation
* should suffice.
*
* @return NSDictionary dictionary of JSON compliant objects
* @exception JSONModelTypeNotAllowedException thrown when one of your model's custom class properties
* does not have matching transformer method in an JSONValueTransformer.
*/
- (NSDictionary *)toDictionary;
/**
* Export a model class to a dictionary, including only given properties
*
* @param propertyNames the properties to export; if nil, all properties exported
* @return NSDictionary dictionary of JSON compliant objects
* @exception JSONModelTypeNotAllowedException thrown when one of your model's custom class properties
* does not have matching transformer method in an JSONValueTransformer.
*/
- (NSDictionary *)toDictionaryWithKeys:(NSArray <NSString *> *)propertyNames;
@end
/////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark - JSONModel interface
/**
* The JSONModel is an abstract model class, you should not instantiate it directly,
* as it does not have any properties, and therefore cannot serve as a data model.
* Instead you should subclass it, and define the properties you want your data model
* to have as properties of your own class.
*/
@interface JSONModel : NSObject <AbstractJSONModelProtocol, NSSecureCoding>
// deprecated
+ (NSMutableArray *)arrayOfModelsFromDictionaries:(NSArray *)array DEPRECATED_MSG_ATTRIBUTE("use arrayOfModelsFromDictionaries:error:");
+ (void)setGlobalKeyMapper:(JSONKeyMapper *)globalKeyMapper DEPRECATED_MSG_ATTRIBUTE("override +keyMapper in a base model class instead");
+ (NSString *)protocolForArrayProperty:(NSString *)propertyName DEPRECATED_MSG_ATTRIBUTE("use classForCollectionProperty:");
- (void)mergeFromDictionary:(NSDictionary *)dict useKeyMapping:(BOOL)useKeyMapping DEPRECATED_MSG_ATTRIBUTE("use mergeFromDictionary:useKeyMapping:error:");
- (NSString *)indexPropertyName DEPRECATED_ATTRIBUTE;
- (NSComparisonResult)compare:(id)object DEPRECATED_ATTRIBUTE;
/** @name Creating and initializing models */
/**
* Create a new model instance and initialize it with the JSON from a text parameter. The method assumes UTF8 encoded input text.
* @param string JSON text data
* @param err an initialization error or nil
* @exception JSONModelTypeNotAllowedException thrown when unsupported type is found in the incoming JSON,
* or a property type in your model is not supported by JSONValueTransformer and its categories
* @see initWithString:usingEncoding:error: for use of custom text encodings
*/
- (instancetype)initWithString:(NSString *)string error:(JSONModelError **)err;
/**
* Create a new model instance and initialize it with the JSON from a text parameter using the given encoding.
* @param string JSON text data
* @param encoding the text encoding to use when parsing the string (see NSStringEncoding)
* @param err an initialization error or nil
* @exception JSONModelTypeNotAllowedException thrown when unsupported type is found in the incoming JSON,
* or a property type in your model is not supported by JSONValueTransformer and its categories
*/
- (instancetype)initWithString:(NSString *)string usingEncoding:(NSStringEncoding)encoding error:(JSONModelError **)err;
/** @name Exporting model contents */
/**
* Export the whole object to a JSON data text string
* @return JSON text describing the data model
*/
- (NSString *)toJSONString;
/**
* Export the whole object to a JSON data text string
* @return JSON text data describing the data model
*/
- (NSData *)toJSONData;
/**
* Export the specified properties of the object to a JSON data text string
* @param propertyNames the properties to export; if nil, all properties exported
* @return JSON text describing the data model
*/
- (NSString *)toJSONStringWithKeys:(NSArray <NSString *> *)propertyNames;
/**
* Export the specified properties of the object to a JSON data text string
* @param propertyNames the properties to export; if nil, all properties exported
* @return JSON text data describing the data model
*/
- (NSData *)toJSONDataWithKeys:(NSArray <NSString *> *)propertyNames;
/** @name Batch methods */
/**
* If you have a list of dictionaries in a JSON feed, you can use this method to create an NSArray
* of model objects. Handy when importing JSON data lists.
* This method will loop over the input list and initialize a data model for every dictionary in the list.
*
* @param array list of dictionaries to be imported as models
* @return list of initialized data model objects
* @exception JSONModelTypeNotAllowedException thrown when unsupported type is found in the incoming JSON,
* or a property type in your model is not supported by JSONValueTransformer and its categories
* @exception JSONModelInvalidDataException thrown when the input data does not include all required keys
* @see arrayOfDictionariesFromModels:
*/
+ (NSMutableArray *)arrayOfModelsFromDictionaries:(NSArray *)array error:(NSError **)err;
+ (NSMutableArray *)arrayOfModelsFromData:(NSData *)data error:(NSError **)err;
+ (NSMutableArray *)arrayOfModelsFromString:(NSString *)string error:(NSError **)err;
+ (NSMutableDictionary *)dictionaryOfModelsFromDictionary:(NSDictionary *)dictionary error:(NSError **)err;
+ (NSMutableDictionary *)dictionaryOfModelsFromData:(NSData *)data error:(NSError **)err;
+ (NSMutableDictionary *)dictionaryOfModelsFromString:(NSString *)string error:(NSError **)err;
/**
* If you have an NSArray of data model objects, this method takes it in and outputs a list of the
* matching dictionaries. This method does the opposite of arrayOfObjectsFromDictionaries:
* @param array list of JSONModel objects
* @return a list of NSDictionary objects
* @exception JSONModelTypeNotAllowedException thrown when unsupported type is found in the incoming JSON,
* or a property type in your model is not supported by JSONValueTransformer and its categories
* @see arrayOfModelsFromDictionaries:
*/
+ (NSMutableArray *)arrayOfDictionariesFromModels:(NSArray *)array;
+ (NSMutableDictionary *)dictionaryOfDictionariesFromModels:(NSDictionary *)dictionary;
/** @name Validation */
/**
* Overwrite the validate method in your own models if you need to perform some custom validation over the model data.
* This method gets called at the very end of the JSONModel initializer, thus the model is in the state that you would
* get it back when initialized. Check the values of any property that needs to be validated and if any invalid values
* are encountered return NO and set the error parameter to an NSError object. If the model is valid return YES.
*
* NB: Only setting the error parameter is not enough to fail the validation, you also need to return a NO value.
*
* @param error a pointer to an NSError object, to pass back an error if needed
* @return a BOOL result, showing whether the model data validates or not. You can use the convenience method
* [JSONModelError errorModelIsInvalid] to set the NSError param if the data fails your custom validation
*/
- (BOOL)validate:(NSError **)error;
/** @name Key mapping */
/**
* Overwrite in your models if your property names don't match your JSON key names.
* Lookup JSONKeyMapper docs for more details.
*/
+ (JSONKeyMapper *)keyMapper;
/**
* Indicates whether the property with the given name is Optional.
* To have a model with all of its properties being Optional just return YES.
* This method returns by default NO, since the default behaviour is to have all properties required.
* @param propertyName the name of the property
* @return a BOOL result indicating whether the property is optional
*/
+ (BOOL)propertyIsOptional:(NSString *)propertyName;
/**
* Indicates whether the property with the given name is Ignored.
* To have a model with all of its properties being Ignored just return YES.
* This method returns by default NO, since the default behaviour is to have all properties required.
* @param propertyName the name of the property
* @return a BOOL result indicating whether the property is ignored
*/
+ (BOOL)propertyIsIgnored:(NSString *)propertyName;
/**
* Indicates the class used for the elements of a collection property.
* Rather than using:
* @property (strong) NSArray <MyType> *things;
* You can implement classForCollectionProperty: and keep your property
* defined like:
* @property (strong) NSArray *things;
* @param propertyName the name of the property
* @return Class the class used to deserialize the elements of the collection
*
* Example in Swift 3.0:
* override static func classForCollectionProperty(propertyName: String) -> AnyClass? {
* switch propertyName {
* case "childModel":
* return ChildModel.self
* default:
* return nil
* }
* }
*/
+ (Class)classForCollectionProperty:(NSString *)propertyName NS_SWIFT_NAME(classForCollectionProperty(propertyName:));
/**
* Merges values from the given dictionary into the model instance.
* @param dict dictionary with values
* @param useKeyMapping if YES the method will use the model's key mapper and the global key mapper, if NO
* it'll just try to match the dictionary keys to the model's properties
*/
- (BOOL)mergeFromDictionary:(NSDictionary *)dict useKeyMapping:(BOOL)useKeyMapping error:(NSError **)error;
@end
//
// JSONModel.m
// JSONModel
//
#if !__has_feature(objc_arc)
#error The JSONMOdel framework is ARC only, you can enable ARC on per file basis.
#endif
#import <objc/runtime.h>
#import <objc/message.h>
#import "JSONModel.h"
#import "JSONModelClassProperty.h"
#pragma mark - associated objects names
static const char * kMapperObjectKey;
static const char * kClassPropertiesKey;
static const char * kClassRequiredPropertyNamesKey;
static const char * kIndexPropertyNameKey;
#pragma mark - class static variables
static NSArray* allowedJSONTypes = nil;
static NSArray* allowedPrimitiveTypes = nil;
static JSONValueTransformer* valueTransformer = nil;
static Class JSONModelClass = NULL;
#pragma mark - model cache
static JSONKeyMapper* globalKeyMapper = nil;
#pragma mark - JSONModel implementation
@implementation JSONModel
{
NSString* _description;
}
#pragma mark - initialization methods
+(void)load
{
static dispatch_once_t once;
dispatch_once(&once, ^{
// initialize all class static objects,
// which are common for ALL JSONModel subclasses
@autoreleasepool {
allowedJSONTypes = @[
[NSString class], [NSNumber class], [NSDecimalNumber class], [NSArray class], [NSDictionary class], [NSNull class], //immutable JSON classes
[NSMutableString class], [NSMutableArray class], [NSMutableDictionary class] //mutable JSON classes
];
allowedPrimitiveTypes = @[
@"BOOL", @"float", @"int", @"long", @"double", @"short",
@"unsigned int", @"usigned long", @"long long", @"unsigned long long", @"unsigned short", @"char", @"unsigned char",
//and some famous aliases
@"NSInteger", @"NSUInteger",
@"Block"
];
valueTransformer = [[JSONValueTransformer alloc] init];
// This is quite strange, but I found the test isSubclassOfClass: (line ~291) to fail if using [JSONModel class].
// somewhat related: https://stackoverflow.com/questions/6524165/nsclassfromstring-vs-classnamednsstring
// //; seems to break the unit tests
// Using NSClassFromString instead of [JSONModel class], as this was breaking unit tests, see below
//http://stackoverflow.com/questions/21394919/xcode-5-unit-test-seeing-wrong-class
JSONModelClass = NSClassFromString(NSStringFromClass(self));
}
});
}
-(void)__setup__
{
//if first instance of this model, generate the property list
if (!objc_getAssociatedObject(self.class, &kClassPropertiesKey)) {
[self __inspectProperties];
}
//if there's a custom key mapper, store it in the associated object
id mapper = [[self class] keyMapper];
if ( mapper && !objc_getAssociatedObject(self.class, &kMapperObjectKey) ) {
objc_setAssociatedObject(
self.class,
&kMapperObjectKey,
mapper,
OBJC_ASSOCIATION_RETAIN // This is atomic
);
}
}
-(id)init
{
self = [super init];
if (self) {
//do initial class setup
[self __setup__];
}
return self;
}
-(instancetype)initWithData:(NSData *)data error:(NSError *__autoreleasing *)err
{
//check for nil input
if (!data) {
if (err) *err = [JSONModelError errorInputIsNil];
return nil;
}
//read the json
JSONModelError* initError = nil;
id obj = [NSJSONSerialization JSONObjectWithData:data
options:kNilOptions
error:&initError];
if (initError) {
if (err) *err = [JSONModelError errorBadJSON];
return nil;
}
//init with dictionary
id objModel = [self initWithDictionary:obj error:&initError];
if (initError && err) *err = initError;
return objModel;
}
-(id)initWithString:(NSString*)string error:(JSONModelError**)err
{
JSONModelError* initError = nil;
id objModel = [self initWithString:string usingEncoding:NSUTF8StringEncoding error:&initError];
if (initError && err) *err = initError;
return objModel;
}
-(id)initWithString:(NSString *)string usingEncoding:(NSStringEncoding)encoding error:(JSONModelError**)err
{
//check for nil input
if (!string) {
if (err) *err = [JSONModelError errorInputIsNil];
return nil;
}
JSONModelError* initError = nil;
id objModel = [self initWithData:[string dataUsingEncoding:encoding] error:&initError];
if (initError && err) *err = initError;
return objModel;
}
-(id)initWithDictionary:(NSDictionary*)dict error:(NSError**)err
{
//check for nil input
if (!dict) {
if (err) *err = [JSONModelError errorInputIsNil];
return nil;
}
//invalid input, just create empty instance
if (![dict isKindOfClass:[NSDictionary class]]) {
if (err) *err = [JSONModelError errorInvalidDataWithMessage:@"Attempt to initialize JSONModel object using initWithDictionary:error: but the dictionary parameter was not an 'NSDictionary'."];
return nil;
}
//create a class instance
self = [self init];
if (!self) {
//super init didn't succeed
if (err) *err = [JSONModelError errorModelIsInvalid];
return nil;
}
//check incoming data structure
if (![self __doesDictionary:dict matchModelWithKeyMapper:self.__keyMapper error:err]) {
return nil;
}
//import the data from a dictionary
if (![self __importDictionary:dict withKeyMapper:self.__keyMapper validation:YES error:err]) {
return nil;
}
//run any custom model validation
if (![self validate:err]) {
return nil;
}
//model is valid! yay!
return self;
}
-(JSONKeyMapper*)__keyMapper
{
//get the model key mapper
return objc_getAssociatedObject(self.class, &kMapperObjectKey);
}
-(BOOL)__doesDictionary:(NSDictionary*)dict matchModelWithKeyMapper:(JSONKeyMapper*)keyMapper error:(NSError**)err
{
//check if all required properties are present
NSArray* incomingKeysArray = [dict allKeys];
NSMutableSet* requiredProperties = [self __requiredPropertyNames].mutableCopy;
NSSet* incomingKeys = [NSSet setWithArray: incomingKeysArray];
//transform the key names, if necessary
if (keyMapper || globalKeyMapper) {
NSMutableSet* transformedIncomingKeys = [NSMutableSet setWithCapacity: requiredProperties.count];
NSString* transformedName = nil;
//loop over the required properties list
for (JSONModelClassProperty* property in [self __properties__]) {
transformedName = (keyMapper||globalKeyMapper) ? [self __mapString:property.name withKeyMapper:keyMapper] : property.name;
//check if exists and if so, add to incoming keys
id value;
@try {
value = [dict valueForKeyPath:transformedName];
}
@catch (NSException *exception) {
value = dict[transformedName];
}
if (value) {
[transformedIncomingKeys addObject: property.name];
}
}
//overwrite the raw incoming list with the mapped key names
incomingKeys = transformedIncomingKeys;
}
//check for missing input keys
if (![requiredProperties isSubsetOfSet:incomingKeys]) {
//get a list of the missing properties
[requiredProperties minusSet:incomingKeys];
//not all required properties are in - invalid input
JMLog(@"Incoming data was invalid [%@ initWithDictionary:]. Keys missing: %@", self.class, requiredProperties);
if (err) *err = [JSONModelError errorInvalidDataWithMissingKeys:requiredProperties];
return NO;
}
//not needed anymore
incomingKeys= nil;
requiredProperties= nil;
return YES;
}
-(NSString*)__mapString:(NSString*)string withKeyMapper:(JSONKeyMapper*)keyMapper
{
if (keyMapper) {
//custom mapper
NSString* mappedName = [keyMapper convertValue:string];
if (globalKeyMapper && [mappedName isEqualToString: string]) {
mappedName = [globalKeyMapper convertValue:string];
}
string = mappedName;
} else if (globalKeyMapper) {
//global keymapper
string = [globalKeyMapper convertValue:string];
}
return string;
}
-(BOOL)__importDictionary:(NSDictionary*)dict withKeyMapper:(JSONKeyMapper*)keyMapper validation:(BOOL)validation error:(NSError**)err
{
//loop over the incoming keys and set self's properties
for (JSONModelClassProperty* property in [self __properties__]) {
//convert key name to model keys, if a mapper is provided
NSString* jsonKeyPath = (keyMapper||globalKeyMapper) ? [self __mapString:property.name withKeyMapper:keyMapper] : property.name;
//JMLog(@"keyPath: %@", jsonKeyPath);
//general check for data type compliance
id jsonValue;
@try {
jsonValue = [dict valueForKeyPath: jsonKeyPath];
}
@catch (NSException *exception) {
jsonValue = dict[jsonKeyPath];
}
//check for Optional properties
if (isNull(jsonValue)) {
// 更美start: JSON返回了Null类型,这种情况需要捕获
if ([jsonValue isKindOfClass:[NSNull class]]) {
if (err) {
JSONModelError *dataErr = (JSONModelError*)*err;
NSString *msg = [NSString stringWithFormat:@"%@返回了Null类型", jsonKeyPath];
if (dataErr) {
*err = [dataErr errorByAppendingSoftMsg:msg class:[self class]];
} else {
*err = [JSONModelError errorSoftMsg:msg class:[self class]];
}
}
}
// 更美end
//skip this property, continue with next property
if (property.isOptional || !validation) continue;
if (err) {
//null value for required property
NSString* msg = [NSString stringWithFormat:@"Value of required model key %@ is null", property.name];
JSONModelError* dataErr = [JSONModelError errorInvalidDataWithMessage:msg];
*err = [dataErr errorByPrependingKeyPathComponent:property.name];
}
return NO;
}
Class jsonValueClass = [jsonValue class];
BOOL isValueOfAllowedType = NO;
for (Class allowedType in allowedJSONTypes) {
if ( [jsonValueClass isSubclassOfClass: allowedType] ) {
isValueOfAllowedType = YES;
break;
}
}
if (isValueOfAllowedType==NO) {
//type not allowed
JMLog(@"Type %@ is not allowed in JSON.", NSStringFromClass(jsonValueClass));
if (err) {
NSString* msg = [NSString stringWithFormat:@"Type %@ is not allowed in JSON.", NSStringFromClass(jsonValueClass)];
JSONModelError* dataErr = [JSONModelError errorInvalidDataWithMessage:msg];
*err = [dataErr errorByPrependingKeyPathComponent:property.name];
}
return NO;
}
//check if there's matching property in the model
if (property) {
// check for custom setter, than the model doesn't need to do any guessing
// how to read the property's value from JSON
if ([self __customSetValue:jsonValue forProperty:property]) {
//skip to next JSON key
continue;
};
// 0) handle primitives
if (property.type == nil && property.structName==nil) {
//generic setter
if (jsonValue != [self valueForKey:property.name]) {
[self setValue:jsonValue forKey: property.name];
}
// 更美start:object的该属性是原始类型,但是JSON却返回了其它类型,这种情况需要捕获
if ([jsonValue isKindOfClass:[NSString class]] && err) {
JSONModelError *dataErr = (JSONModelError*)*err;
NSString *msg = [NSString stringWithFormat:@"%@是原始类型,但是返回了String", jsonKeyPath];
if (dataErr) {
*err = [dataErr errorByAppendingSoftMsg:msg class:[self class]];
} else {
*err = [JSONModelError errorSoftMsg:msg class:[self class]];
}
}
// 更美end
//skip directly to the next key
continue;
}
// 0.5) handle nils
if (isNull(jsonValue)) {
if ([self valueForKey:property.name] != nil) {
[self setValue:nil forKey: property.name];
}
continue;
}
// 1) check if property is itself a JSONModel
if ([self __isJSONModelSubClass:property.type]) {
//initialize the property's model, store it
JSONModelError* initErr = nil;
id value = [[property.type alloc] initWithDictionary: jsonValue error:&initErr];
// 更美start: 如果该属性是一个JSONModel Object,并且该Object的某个属性出错,需要在这儿进行拼接
if((err != nil) && (initErr != nil)) {
*err = [initErr errorByAppendingSubPropertyMsg:jsonKeyPath class:[self class]];
}
// 更美end
if (!value) {
//skip this property, continue with next property
if (property.isOptional || !validation) continue;
// Propagate the error, including the property name as the key-path component
if((err != nil) && (initErr != nil))
{
*err = [initErr errorByPrependingKeyPathComponent:property.name];
}
return NO;
}
if (![value isEqual:[self valueForKey:property.name]]) {
[self setValue:value forKey: property.name];
}
//for clarity, does the same without continue
continue;
} else {
// 2) check if there's a protocol to the property
// ) might or not be the case there's a built in transform for it
if (property.protocol) {
//JMLog(@"proto: %@", p.protocol);
jsonValue = [self __transform:jsonValue forProperty:property error:err];
if (!jsonValue) {
if ((err != nil) && (*err == nil)) {
NSString* msg = [NSString stringWithFormat:@"Failed to transform value, but no error was set during transformation. (%@)", property];
JSONModelError* dataErr = [JSONModelError errorInvalidDataWithMessage:msg];
*err = [dataErr errorByPrependingKeyPathComponent:property.name];
}
return NO;
}
}
// 3.1) handle matching standard JSON types
if (property.isStandardJSONType && [jsonValue isKindOfClass: property.type]) {
//mutable properties
if (property.isMutable) {
jsonValue = [jsonValue mutableCopy];
}
//set the property value
if (![jsonValue isEqual:[self valueForKey:property.name]]) {
[self setValue:jsonValue forKey: property.name];
}
continue;
}
// 3.3) handle values to transform
if (
(![jsonValue isKindOfClass:property.type] && !isNull(jsonValue))
||
//the property is mutable
property.isMutable
||
//custom struct property
property.structName
) {
// searched around the web how to do this better
// but did not find any solution, maybe that's the best idea? (hardly)
Class sourceClass = [JSONValueTransformer classByResolvingClusterClasses:[jsonValue class]];
//JMLog(@"to type: [%@] from type: [%@] transformer: [%@]", p.type, sourceClass, selectorName);
//build a method selector for the property and json object classes
NSString* selectorName = [NSString stringWithFormat:@"%@From%@:",
(property.structName? property.structName : property.type), //target name
sourceClass]; //source name
SEL selector = NSSelectorFromString(selectorName);
//check for custom transformer
BOOL foundCustomTransformer = NO;
if ([valueTransformer respondsToSelector:selector]) {
foundCustomTransformer = YES;
} else {
//try for hidden custom transformer
selectorName = [NSString stringWithFormat:@"__%@",selectorName];
selector = NSSelectorFromString(selectorName);
if ([valueTransformer respondsToSelector:selector]) {
foundCustomTransformer = YES;
}
}
//check if there's a transformer with that name
if (foundCustomTransformer) {
IMP imp = [valueTransformer methodForSelector:selector];
id (*func)(id, SEL, id) = (void *)imp;
jsonValue = func(valueTransformer, selector, jsonValue);
if (![jsonValue isEqual:[self valueForKey:property.name]])
[self setValue:jsonValue forKey:property.name];
} else {
if (err) {
NSString* msg = [NSString stringWithFormat:@"%@ type not supported for %@.%@", property.type, [self class], property.name];
JSONModelError* dataErr = [JSONModelError errorInvalidDataWithTypeMismatch:msg];
*err = [dataErr errorByPrependingKeyPathComponent:property.name];
}
return NO;
}
} else {
// 3.4) handle "all other" cases (if any)
if (![jsonValue isEqual:[self valueForKey:property.name]])
[self setValue:jsonValue forKey:property.name];
}
}
}
}
return YES;
}
#pragma mark - property inspection methods
-(BOOL)__isJSONModelSubClass:(Class)class
{
// http://stackoverflow.com/questions/19883472/objc-nsobject-issubclassofclass-gives-incorrect-failure
#ifdef UNIT_TESTING
return [@"JSONModel" isEqualToString: NSStringFromClass([class superclass])];
#else
return [class isSubclassOfClass:JSONModelClass];
#endif
}
//returns a set of the required keys for the model
-(NSMutableSet*)__requiredPropertyNames
{
//fetch the associated property names
NSMutableSet* classRequiredPropertyNames = objc_getAssociatedObject(self.class, &kClassRequiredPropertyNamesKey);
if (!classRequiredPropertyNames) {
classRequiredPropertyNames = [NSMutableSet set];
[[self __properties__] enumerateObjectsUsingBlock:^(JSONModelClassProperty* p, NSUInteger idx, BOOL *stop) {
if (!p.isOptional) [classRequiredPropertyNames addObject:p.name];
}];
//persist the list
objc_setAssociatedObject(
self.class,
&kClassRequiredPropertyNamesKey,
classRequiredPropertyNames,
OBJC_ASSOCIATION_RETAIN // This is atomic
);
}
return classRequiredPropertyNames;
}
//returns a list of the model's properties
-(NSArray*)__properties__
{
//fetch the associated object
NSDictionary* classProperties = objc_getAssociatedObject(self.class, &kClassPropertiesKey);
if (classProperties) return [classProperties allValues];
//if here, the class needs to inspect itself
[self __setup__];
//return the property list
classProperties = objc_getAssociatedObject(self.class, &kClassPropertiesKey);
return [classProperties allValues];
}
//inspects the class, get's a list of the class properties
-(void)__inspectProperties
{
//JMLog(@"Inspect class: %@", [self class]);
NSMutableDictionary* propertyIndex = [NSMutableDictionary dictionary];
//temp variables for the loops
Class class = [self class];
NSScanner* scanner = nil;
NSString* propertyType = nil;
// inspect inherited properties up to the JSONModel class
while (class != [JSONModel class]) {
//JMLog(@"inspecting: %@", NSStringFromClass(class));
unsigned int propertyCount;
objc_property_t *properties = class_copyPropertyList(class, &propertyCount);
//loop over the class properties
for (unsigned int i = 0; i < propertyCount; i++) {
JSONModelClassProperty* p = [[JSONModelClassProperty alloc] init];
//get property name
objc_property_t property = properties[i];
const char *propertyName = property_getName(property);
p.name = @(propertyName);
//JMLog(@"property: %@", p.name);
//get property attributes
const char *attrs = property_getAttributes(property);
NSString* propertyAttributes = @(attrs);
NSArray* attributeItems = [propertyAttributes componentsSeparatedByString:@","];
//ignore read-only properties
if ([attributeItems containsObject:@"R"]) {
continue; //to next property
}
scanner = [NSScanner scannerWithString: propertyAttributes];
//JMLog(@"attr: %@", [NSString stringWithCString:attrs encoding:NSUTF8StringEncoding]);
[scanner scanUpToString:@"T" intoString: nil];
[scanner scanString:@"T" intoString:nil];
//check if the property is an instance of a class
if ([scanner scanString:@"@\"" intoString: &propertyType]) {
[scanner scanUpToCharactersFromSet:[NSCharacterSet characterSetWithCharactersInString:@"\"<"]
intoString:&propertyType];
//JMLog(@"type: %@", propertyClassName);
p.type = NSClassFromString(propertyType);
p.isMutable = ([propertyType rangeOfString:@"Mutable"].location != NSNotFound);
p.isStandardJSONType = [allowedJSONTypes containsObject:p.type];
//read through the property protocols
while ([scanner scanString:@"<" intoString:NULL]) {
NSString* protocolName = nil;
[scanner scanUpToString:@">" intoString: &protocolName];
if ([protocolName isEqualToString:@"Optional"]) {
p.isOptional = YES;
} else if([protocolName isEqualToString:@"Index"]) {
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
p.isIndex = YES;
#pragma GCC diagnostic pop
objc_setAssociatedObject(
self.class,
&kIndexPropertyNameKey,
p.name,
OBJC_ASSOCIATION_RETAIN // This is atomic
);
} else if([protocolName isEqualToString:@"Ignore"]) {
p = nil;
} else {
p.protocol = protocolName;
}
[scanner scanString:@">" intoString:NULL];
}
}
//check if the property is a structure
else if ([scanner scanString:@"{" intoString: &propertyType]) {
[scanner scanCharactersFromSet:[NSCharacterSet alphanumericCharacterSet]
intoString:&propertyType];
p.isStandardJSONType = NO;
p.structName = propertyType;
}
//the property must be a primitive
else {
//the property contains a primitive data type
[scanner scanUpToCharactersFromSet:[NSCharacterSet characterSetWithCharactersInString:@","]
intoString:&propertyType];
//get the full name of the primitive type
propertyType = valueTransformer.primitivesNames[propertyType];
if (![allowedPrimitiveTypes containsObject:propertyType]) {
//type not allowed - programmer mistaken -> exception
@throw [NSException exceptionWithName:@"JSONModelProperty type not allowed"
reason:[NSString stringWithFormat:@"Property type of %@.%@ is not supported by JSONModel.", self.class, p.name]
userInfo:nil];
}
}
NSString *nsPropertyName = @(propertyName);
if([[self class] propertyIsOptional:nsPropertyName]){
p.isOptional = YES;
}
if([[self class] propertyIsIgnored:nsPropertyName]){
p = nil;
}
Class customClass = [[self class] classForCollectionProperty:nsPropertyName];
if (customClass) {
p.protocol = NSStringFromClass(customClass);
}
//few cases where JSONModel will ignore properties automatically
if ([propertyType isEqualToString:@"Block"]) {
p = nil;
}
//add the property object to the temp index
if (p && ![propertyIndex objectForKey:p.name]) {
[propertyIndex setValue:p forKey:p.name];
}
// generate custom setters and getter
if (p)
{
NSString *name = [p.name stringByReplacingCharactersInRange:NSMakeRange(0, 1) withString:[p.name substringToIndex:1].uppercaseString];
// getter
SEL getter = NSSelectorFromString([NSString stringWithFormat:@"JSONObjectFor%@", name]);
if ([self respondsToSelector:getter])
p.customGetter = getter;
// setters
p.customSetters = [NSMutableDictionary new];
SEL genericSetter = NSSelectorFromString([NSString stringWithFormat:@"set%@WithJSONObject:", name]);
if ([self respondsToSelector:genericSetter])
p.customSetters[@"generic"] = [NSValue valueWithBytes:&genericSetter objCType:@encode(SEL)];
for (Class type in allowedJSONTypes)
{
NSString *class = NSStringFromClass([JSONValueTransformer classByResolvingClusterClasses:type]);
if (p.customSetters[class])
continue;
SEL setter = NSSelectorFromString([NSString stringWithFormat:@"set%@With%@:", name, class]);
if ([self respondsToSelector:setter])
p.customSetters[class] = [NSValue valueWithBytes:&setter objCType:@encode(SEL)];
}
}
}
free(properties);
//ascend to the super of the class
//(will do that until it reaches the root class - JSONModel)
class = [class superclass];
}
//finally store the property index in the static property index
objc_setAssociatedObject(
self.class,
&kClassPropertiesKey,
[propertyIndex copy],
OBJC_ASSOCIATION_RETAIN // This is atomic
);
}
#pragma mark - built-in transformer methods
//few built-in transformations
-(id)__transform:(id)value forProperty:(JSONModelClassProperty*)property error:(NSError**)err
{
Class protocolClass = NSClassFromString(property.protocol);
if (!protocolClass) {
//no other protocols on arrays and dictionaries
//except JSONModel classes
if ([value isKindOfClass:[NSArray class]]) {
@throw [NSException exceptionWithName:@"Bad property protocol declaration"
reason:[NSString stringWithFormat:@"<%@> is not allowed JSONModel property protocol, and not a JSONModel class.", property.protocol]
userInfo:nil];
}
return value;
}
//if the protocol is actually a JSONModel class
if ([self __isJSONModelSubClass:protocolClass]) {
//check if it's a list of models
if ([property.type isSubclassOfClass:[NSArray class]]) {
// Expecting an array, make sure 'value' is an array
if(![[value class] isSubclassOfClass:[NSArray class]])
{
if(err != nil)
{
NSString* mismatch = [NSString stringWithFormat:@"Property '%@' is declared as NSArray<%@>* but the corresponding JSON value is not a JSON Array.", property.name, property.protocol];
JSONModelError* typeErr = [JSONModelError errorInvalidDataWithTypeMismatch:mismatch];
*err = [typeErr errorByPrependingKeyPathComponent:property.name];
}
return nil;
}
//one shot conversion
JSONModelError* arrayErr = nil;
value = [[protocolClass class] arrayOfModelsFromDictionaries:value error:&arrayErr];
if((err != nil) && (arrayErr != nil))
{
*err = [arrayErr errorByPrependingKeyPathComponent:property.name];
return nil;
}
}
//check if it's a dictionary of models
if ([property.type isSubclassOfClass:[NSDictionary class]]) {
// Expecting a dictionary, make sure 'value' is a dictionary
if(![[value class] isSubclassOfClass:[NSDictionary class]])
{
if(err != nil)
{
NSString* mismatch = [NSString stringWithFormat:@"Property '%@' is declared as NSDictionary<%@>* but the corresponding JSON value is not a JSON Object.", property.name, property.protocol];
JSONModelError* typeErr = [JSONModelError errorInvalidDataWithTypeMismatch:mismatch];
*err = [typeErr errorByPrependingKeyPathComponent:property.name];
}
return nil;
}
NSMutableDictionary* res = [NSMutableDictionary dictionary];
for (NSString* key in [value allKeys]) {
JSONModelError* initErr = nil;
id obj = [[[protocolClass class] alloc] initWithDictionary:value[key] error:&initErr];
if (obj == nil)
{
// Propagate the error, including the property name as the key-path component
if((err != nil) && (initErr != nil))
{
initErr = [initErr errorByPrependingKeyPathComponent:key];
*err = [initErr errorByPrependingKeyPathComponent:property.name];
}
return nil;
}
[res setValue:obj forKey:key];
}
value = [NSDictionary dictionaryWithDictionary:res];
}
}
return value;
}
//built-in reverse transformations (export to JSON compliant objects)
-(id)__reverseTransform:(id)value forProperty:(JSONModelClassProperty*)property
{
Class protocolClass = NSClassFromString(property.protocol);
if (!protocolClass) return value;
//if the protocol is actually a JSONModel class
if ([self __isJSONModelSubClass:protocolClass]) {
//check if should export list of dictionaries
if (property.type == [NSArray class] || property.type == [NSMutableArray class]) {
NSMutableArray* tempArray = [NSMutableArray arrayWithCapacity: [(NSArray*)value count] ];
for (NSObject<AbstractJSONModelProtocol>* model in (NSArray*)value) {
if ([model respondsToSelector:@selector(toDictionary)]) {
[tempArray addObject: [model toDictionary]];
} else
[tempArray addObject: model];
}
return [tempArray copy];
}
//check if should export dictionary of dictionaries
if (property.type == [NSDictionary class] || property.type == [NSMutableDictionary class]) {
NSMutableDictionary* res = [NSMutableDictionary dictionary];
for (NSString* key in [(NSDictionary*)value allKeys]) {
id<AbstractJSONModelProtocol> model = value[key];
[res setValue: [model toDictionary] forKey: key];
}
return [NSDictionary dictionaryWithDictionary:res];
}
}
return value;
}
#pragma mark - custom transformations
- (BOOL)__customSetValue:(id <NSObject>)value forProperty:(JSONModelClassProperty *)property
{
NSString *class = NSStringFromClass([JSONValueTransformer classByResolvingClusterClasses:[value class]]);
SEL setter = nil;
[property.customSetters[class] getValue:&setter];
if (!setter)
[property.customSetters[@"generic"] getValue:&setter];
if (!setter)
return NO;
IMP imp = [self methodForSelector:setter];
void (*func)(id, SEL, id <NSObject>) = (void *)imp;
func(self, setter, value);
return YES;
}
- (BOOL)__customGetValue:(id *)value forProperty:(JSONModelClassProperty *)property
{
SEL getter = property.customGetter;
if (!getter)
return NO;
IMP imp = [self methodForSelector:getter];
id (*func)(id, SEL) = (void *)imp;
*value = func(self, getter);
return YES;
}
#pragma mark - persistance
-(void)__createDictionariesForKeyPath:(NSString*)keyPath inDictionary:(NSMutableDictionary**)dict
{
//find if there's a dot left in the keyPath
NSUInteger dotLocation = [keyPath rangeOfString:@"."].location;
if (dotLocation==NSNotFound) return;
//inspect next level
NSString* nextHierarchyLevelKeyName = [keyPath substringToIndex: dotLocation];
NSDictionary* nextLevelDictionary = (*dict)[nextHierarchyLevelKeyName];
if (nextLevelDictionary==nil) {
//create non-existing next level here
nextLevelDictionary = [NSMutableDictionary dictionary];
}
//recurse levels
[self __createDictionariesForKeyPath:[keyPath substringFromIndex: dotLocation+1]
inDictionary:&nextLevelDictionary ];
//create the hierarchy level
[*dict setValue:nextLevelDictionary forKeyPath: nextHierarchyLevelKeyName];
}
-(NSDictionary*)toDictionary
{
return [self toDictionaryWithKeys:nil];
}
-(NSString*)toJSONString
{
return [self toJSONStringWithKeys:nil];
}
-(NSData*)toJSONData
{
return [self toJSONDataWithKeys:nil];
}
//exports the model as a dictionary of JSON compliant objects
- (NSDictionary *)toDictionaryWithKeys:(NSArray <NSString *> *)propertyNames
{
NSArray* properties = [self __properties__];
NSMutableDictionary* tempDictionary = [NSMutableDictionary dictionaryWithCapacity:properties.count];
id value;
//loop over all properties
for (JSONModelClassProperty* p in properties) {
//skip if unwanted
if (propertyNames != nil && ![propertyNames containsObject:p.name])
continue;
//fetch key and value
NSString* keyPath = (self.__keyMapper||globalKeyMapper) ? [self __mapString:p.name withKeyMapper:self.__keyMapper] : p.name;
value = [self valueForKey: p.name];
//JMLog(@"toDictionary[%@]->[%@] = '%@'", p.name, keyPath, value);
if ([keyPath rangeOfString:@"."].location != NSNotFound) {
//there are sub-keys, introduce dictionaries for them
[self __createDictionariesForKeyPath:keyPath inDictionary:&tempDictionary];
}
//check for custom getter
if ([self __customGetValue:&value forProperty:p]) {
//custom getter, all done
[tempDictionary setValue:value forKeyPath:keyPath];
continue;
}
//export nil when they are not optional values as JSON null, so that the structure of the exported data
//is still valid if it's to be imported as a model again
if (isNull(value)) {
if (value == nil)
{
[tempDictionary removeObjectForKey:keyPath];
}
else
{
[tempDictionary setValue:[NSNull null] forKeyPath:keyPath];
}
continue;
}
//check if the property is another model
if ([value isKindOfClass:JSONModelClass]) {
//recurse models
value = [(JSONModel*)value toDictionary];
[tempDictionary setValue:value forKeyPath: keyPath];
//for clarity
continue;
} else {
// 1) check for built-in transformation
if (p.protocol) {
value = [self __reverseTransform:value forProperty:p];
}
// 2) check for standard types OR 2.1) primitives
if (p.structName==nil && (p.isStandardJSONType || p.type==nil)) {
//generic get value
[tempDictionary setValue:value forKeyPath: keyPath];
continue;
}
// 3) try to apply a value transformer
if (YES) {
//create selector from the property's class name
NSString* selectorName = [NSString stringWithFormat:@"%@From%@:", @"JSONObject", p.type?p.type:p.structName];
SEL selector = NSSelectorFromString(selectorName);
BOOL foundCustomTransformer = NO;
if ([valueTransformer respondsToSelector:selector]) {
foundCustomTransformer = YES;
} else {
//try for hidden transformer
selectorName = [NSString stringWithFormat:@"__%@",selectorName];
selector = NSSelectorFromString(selectorName);
if ([valueTransformer respondsToSelector:selector]) {
foundCustomTransformer = YES;
}
}
//check if there's a transformer declared
if (foundCustomTransformer) {
IMP imp = [valueTransformer methodForSelector:selector];
id (*func)(id, SEL, id) = (void *)imp;
value = func(valueTransformer, selector, value);
[tempDictionary setValue:value forKeyPath:keyPath];
} else {
//in this case most probably a custom property was defined in a model
//but no default reverse transformer for it
@throw [NSException exceptionWithName:@"Value transformer not found"
reason:[NSString stringWithFormat:@"[JSONValueTransformer %@] not found", selectorName]
userInfo:nil];
return nil;
}
}
}
}
return [tempDictionary copy];
}
//exports model to a dictionary and then to a JSON string
- (NSData *)toJSONDataWithKeys:(NSArray <NSString *> *)propertyNames
{
NSData* jsonData = nil;
NSError* jsonError = nil;
@try {
NSDictionary* dict = [self toDictionaryWithKeys:propertyNames];
jsonData = [NSJSONSerialization dataWithJSONObject:dict options:kNilOptions error:&jsonError];
}
@catch (NSException *exception) {
//this should not happen in properly design JSONModel
//usually means there was no reverse transformer for a custom property
JMLog(@"EXCEPTION: %@", exception.description);
return nil;
}
return jsonData;
}
- (NSString *)toJSONStringWithKeys:(NSArray <NSString *> *)propertyNames
{
return [[NSString alloc] initWithData: [self toJSONDataWithKeys: propertyNames]
encoding: NSUTF8StringEncoding];
}
#pragma mark - import/export of lists
//loop over an NSArray of JSON objects and turn them into models
+(NSMutableArray*)arrayOfModelsFromDictionaries:(NSArray*)array
{
return [self arrayOfModelsFromDictionaries:array error:nil];
}
+ (NSMutableArray *)arrayOfModelsFromData:(NSData *)data error:(NSError **)err
{
id json = [NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:err];
if (!json || ![json isKindOfClass:[NSArray class]]) return nil;
return [self arrayOfModelsFromDictionaries:json error:err];
}
+ (NSMutableArray *)arrayOfModelsFromString:(NSString *)string error:(NSError **)err
{
return [self arrayOfModelsFromData:[string dataUsingEncoding:NSUTF8StringEncoding] error:err];
}
// Same as above, but with error reporting
+(NSMutableArray*)arrayOfModelsFromDictionaries:(NSArray*)array error:(NSError**)err
{
//bail early
if (isNull(array)) return nil;
//parse dictionaries to objects
NSMutableArray* list = [NSMutableArray arrayWithCapacity: [array count]];
for (id d in array)
{
if ([d isKindOfClass:NSDictionary.class])
{
JSONModelError* initErr = nil;
id obj = [[self alloc] initWithDictionary:d error:&initErr];
if (obj == nil)
{
// Propagate the error, including the array index as the key-path component
if((err != nil) && (initErr != nil))
{
NSString* path = [NSString stringWithFormat:@"[%lu]", (unsigned long)list.count];
*err = [initErr errorByPrependingKeyPathComponent:path];
}
return nil;
}
[list addObject: obj];
} else if ([d isKindOfClass:NSArray.class])
{
[list addObjectsFromArray:[self arrayOfModelsFromDictionaries:d error:err]];
} else
{
// This is very bad
}
}
return list;
}
+ (NSMutableDictionary *)dictionaryOfModelsFromString:(NSString *)string error:(NSError **)err
{
return [self dictionaryOfModelsFromData:[string dataUsingEncoding:NSUTF8StringEncoding] error:err];
}
+ (NSMutableDictionary *)dictionaryOfModelsFromData:(NSData *)data error:(NSError **)err
{
id json = [NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:err];
if (!json || ![json isKindOfClass:[NSDictionary class]]) return nil;
return [self dictionaryOfModelsFromDictionary:json error:err];
}
+ (NSMutableDictionary *)dictionaryOfModelsFromDictionary:(NSDictionary *)dictionary error:(NSError **)err
{
NSMutableDictionary *output = [NSMutableDictionary dictionaryWithCapacity:dictionary.count];
for (NSString *key in dictionary.allKeys)
{
id object = dictionary[key];
if ([object isKindOfClass:NSDictionary.class])
{
id obj = [[self alloc] initWithDictionary:object error:err];
if (obj == nil) return nil;
output[key] = obj;
}
else if ([object isKindOfClass:NSArray.class])
{
id obj = [self arrayOfModelsFromDictionaries:object error:err];
if (obj == nil) return nil;
output[key] = obj;
}
else
{
if (err) {
*err = [JSONModelError errorInvalidDataWithTypeMismatch:@"Only dictionaries and arrays are supported"];
}
return nil;
}
}
return output;
}
//loop over NSArray of models and export them to JSON objects
+(NSMutableArray*)arrayOfDictionariesFromModels:(NSArray*)array
{
//bail early
if (isNull(array)) return nil;
//convert to dictionaries
NSMutableArray* list = [NSMutableArray arrayWithCapacity: [array count]];
for (id<AbstractJSONModelProtocol> object in array) {
id obj = [object toDictionary];
if (!obj) return nil;
[list addObject: obj];
}
return list;
}
//loop over NSArray of models and export them to JSON objects with specific properties
+(NSMutableArray*)arrayOfDictionariesFromModels:(NSArray*)array propertyNamesToExport:(NSArray*)propertyNamesToExport;
{
//bail early
if (isNull(array)) return nil;
//convert to dictionaries
NSMutableArray* list = [NSMutableArray arrayWithCapacity: [array count]];
for (id<AbstractJSONModelProtocol> object in array) {
id obj = [object toDictionaryWithKeys:propertyNamesToExport];
if (!obj) return nil;
[list addObject: obj];
}
return list;
}
+(NSMutableDictionary *)dictionaryOfDictionariesFromModels:(NSDictionary *)dictionary
{
//bail early
if (isNull(dictionary)) return nil;
NSMutableDictionary *output = [NSMutableDictionary dictionaryWithCapacity:dictionary.count];
for (NSString *key in dictionary.allKeys) {
id <AbstractJSONModelProtocol> object = dictionary[key];
id obj = [object toDictionary];
if (!obj) return nil;
output[key] = obj;
}
return output;
}
#pragma mark - custom comparison methods
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
-(NSString*)indexPropertyName
{
//custom getter for an associated object
return objc_getAssociatedObject(self.class, &kIndexPropertyNameKey);
}
-(BOOL)isEqual:(id)object
{
//bail early if different classes
if (![object isMemberOfClass:[self class]]) return NO;
if (self.indexPropertyName) {
//there's a defined ID property
id objectId = [object valueForKey: self.indexPropertyName];
return [[self valueForKey: self.indexPropertyName] isEqual:objectId];
}
//default isEqual implementation
return [super isEqual:object];
}
-(NSComparisonResult)compare:(id)object
{
if (self.indexPropertyName) {
id objectId = [object valueForKey: self.indexPropertyName];
if ([objectId respondsToSelector:@selector(compare:)]) {
return [[self valueForKey:self.indexPropertyName] compare:objectId];
}
}
//on purpose postponing the asserts for speed optimization
//these should not happen anyway in production conditions
NSAssert(self.indexPropertyName, @"Can't compare models with no <Index> property");
NSAssert1(NO, @"The <Index> property of %@ is not comparable class.", [self class]);
return kNilOptions;
}
- (NSUInteger)hash
{
if (self.indexPropertyName) {
id val = [self valueForKey:self.indexPropertyName];
if (val) {
return [val hash];
}
}
return [super hash];
}
#pragma GCC diagnostic pop
#pragma mark - custom data validation
-(BOOL)validate:(NSError**)error
{
return YES;
}
#pragma mark - custom recursive description
//custom description method for debugging purposes
-(NSString*)description
{
NSMutableString* text = [NSMutableString stringWithFormat:@"<%@> \n", [self class]];
for (JSONModelClassProperty *p in [self __properties__]) {
id value = ([p.name isEqualToString:@"description"])?self->_description:[self valueForKey:p.name];
NSString* valueDescription = (value)?[value description]:@"<nil>";
if (p.isStandardJSONType && ![value respondsToSelector:@selector(count)] && [valueDescription length]>60) {
//cap description for longer values
valueDescription = [NSString stringWithFormat:@"%@...", [valueDescription substringToIndex:59]];
}
valueDescription = [valueDescription stringByReplacingOccurrencesOfString:@"\n" withString:@"\n "];
[text appendFormat:@" [%@]: %@\n", p.name, valueDescription];
}
[text appendFormat:@"</%@>", [self class]];
return text;
}
#pragma mark - key mapping
+(JSONKeyMapper*)keyMapper
{
return nil;
}
+(void)setGlobalKeyMapper:(JSONKeyMapper*)globalKeyMapperParam
{
globalKeyMapper = globalKeyMapperParam;
}
+(BOOL)propertyIsOptional:(NSString*)propertyName
{
return NO;
}
+(BOOL)propertyIsIgnored:(NSString *)propertyName
{
return NO;
}
+(NSString*)protocolForArrayProperty:(NSString *)propertyName
{
return nil;
}
+(Class)classForCollectionProperty:(NSString *)propertyName
{
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
NSString *protocolName = [self protocolForArrayProperty:propertyName];
#pragma GCC diagnostic pop
if (!protocolName)
return nil;
return NSClassFromString(protocolName);
}
#pragma mark - working with incomplete models
- (void)mergeFromDictionary:(NSDictionary *)dict useKeyMapping:(BOOL)useKeyMapping
{
[self mergeFromDictionary:dict useKeyMapping:useKeyMapping error:nil];
}
- (BOOL)mergeFromDictionary:(NSDictionary *)dict useKeyMapping:(BOOL)useKeyMapping error:(NSError **)error
{
return [self __importDictionary:dict withKeyMapper:(useKeyMapping)? self.__keyMapper:nil validation:NO error:error];
}
#pragma mark - NSCopying, NSCoding
-(instancetype)copyWithZone:(NSZone *)zone
{
return [NSKeyedUnarchiver unarchiveObjectWithData:
[NSKeyedArchiver archivedDataWithRootObject:self]
];
}
-(instancetype)initWithCoder:(NSCoder *)decoder
{
NSString* json;
if ([decoder respondsToSelector:@selector(decodeObjectOfClass:forKey:)]) {
json = [decoder decodeObjectOfClass:[NSString class] forKey:@"json"];
} else {
json = [decoder decodeObjectForKey:@"json"];
}
JSONModelError *error = nil;
self = [self initWithString:json error:&error];
if (error) {
JMLog(@"%@",[error localizedDescription]);
}
return self;
}
-(void)encodeWithCoder:(NSCoder *)encoder
{
[encoder encodeObject:self.toJSONString forKey:@"json"];
}
+ (BOOL)supportsSecureCoding
{
return YES;
}
@end
//
// JSONModelClassProperty.h
// JSONModel
//
#import <Foundation/Foundation.h>
/**
* **You do not need to instantiate this class yourself.** This class is used internally by JSONModel
* to inspect the declared properties of your model class.
*
* Class to contain the information, representing a class property
* It features the property's name, type, whether it's a required property,
* and (optionally) the class protocol
*/
@interface JSONModelClassProperty : NSObject
// deprecated
@property (assign, nonatomic) BOOL isIndex DEPRECATED_ATTRIBUTE;
/** The name of the declared property (not the ivar name) */
@property (copy, nonatomic) NSString *name;
/** A property class type */
@property (assign, nonatomic) Class type;
/** Struct name if a struct */
@property (strong, nonatomic) NSString *structName;
/** The name of the protocol the property conforms to (or nil) */
@property (copy, nonatomic) NSString *protocol;
/** If YES, it can be missing in the input data, and the input would be still valid */
@property (assign, nonatomic) BOOL isOptional;
/** If YES - don't call any transformers on this property's value */
@property (assign, nonatomic) BOOL isStandardJSONType;
/** If YES - create a mutable object for the value of the property */
@property (assign, nonatomic) BOOL isMutable;
/** a custom getter for this property, found in the owning model */
@property (assign, nonatomic) SEL customGetter;
/** custom setters for this property, found in the owning model */
@property (strong, nonatomic) NSMutableDictionary *customSetters;
@end
//
// JSONModelClassProperty.m
// JSONModel
//
#import "JSONModelClassProperty.h"
@implementation JSONModelClassProperty
-(NSString*)description
{
//build the properties string for the current class property
NSMutableArray* properties = [NSMutableArray arrayWithCapacity:8];
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
if (self.isIndex) [properties addObject:@"Index"];
#pragma GCC diagnostic pop
if (self.isOptional) [properties addObject:@"Optional"];
if (self.isMutable) [properties addObject:@"Mutable"];
if (self.isStandardJSONType) [properties addObject:@"Standard JSON type"];
if (self.customGetter) [properties addObject:[NSString stringWithFormat: @"Getter = %@", NSStringFromSelector(self.customGetter)]];
if (self.customSetters)
{
NSMutableArray *setters = [NSMutableArray array];
for (id obj in self.customSetters.allValues)
{
SEL selector;
[obj getValue:&selector];
[setters addObject:NSStringFromSelector(selector)];
}
[properties addObject:[NSString stringWithFormat: @"Setters = [%@]", [setters componentsJoinedByString:@", "]]];
}
NSString* propertiesString = @"";
if (properties.count>0) {
propertiesString = [NSString stringWithFormat:@"(%@)", [properties componentsJoinedByString:@", "]];
}
//return the name, type and additional properties
return [NSString stringWithFormat:@"@property %@%@ %@ %@",
self.type?[NSString stringWithFormat:@"%@*",self.type]:(self.structName?self.structName:@"primitive"),
self.protocol?[NSString stringWithFormat:@"<%@>", self.protocol]:@"",
self.name,
propertiesString
];
}
@end
//
// JSONModelError.h
// JSONModel
//
#import <Foundation/Foundation.h>
/////////////////////////////////////////////////////////////////////////////////////////////
typedef NS_ENUM(int, kJSONModelErrorTypes)
{
kJSONModelErrorInvalidData = 1,
kJSONModelErrorBadResponse = 2,
kJSONModelErrorBadJSON = 3,
kJSONModelErrorModelIsInvalid = 4,
kJSONModelErrorNilInput = 5,
kJSONModelErrorModelSoft = 6,
};
/////////////////////////////////////////////////////////////////////////////////////////////
/** The domain name used for the JSONModelError instances */
extern NSString *const JSONModelErrorDomain;
/**
* If the model JSON input misses keys that are required, check the
* userInfo dictionary of the JSONModelError instance you get back -
* under the kJSONModelMissingKeys key you will find a list of the
* names of the missing keys.
*/
extern NSString *const kJSONModelMissingKeys;
/**
* If JSON input has a different type than expected by the model, check the
* userInfo dictionary of the JSONModelError instance you get back -
* under the kJSONModelTypeMismatch key you will find a description
* of the mismatched types.
*/
extern NSString *const kJSONModelTypeMismatch;
/**
* If an error occurs in a nested model, check the userInfo dictionary of
* the JSONModelError instance you get back - under the kJSONModelKeyPath
* key you will find key-path at which the error occurred.
*/
extern NSString *const kJSONModelKeyPath;
extern NSString* const kJSONModelSoftKeyPath;
/////////////////////////////////////////////////////////////////////////////////////////////
/**
* Custom NSError subclass with shortcut methods for creating
* the common JSONModel errors
*/
@interface JSONModelError : NSError
@property (strong, nonatomic) NSHTTPURLResponse *httpResponse;
@property (strong, nonatomic) NSData *responseData;
/**
* Creates a JSONModelError instance with code kJSONModelErrorInvalidData = 1
*/
+ (id)errorInvalidDataWithMessage:(NSString *)message;
/**
* Creates a JSONModelError instance with code kJSONModelErrorInvalidData = 1
* @param keys a set of field names that were required, but not found in the input
*/
+ (id)errorInvalidDataWithMissingKeys:(NSSet *)keys;
/**
* Creates a JSONModelError instance with code kJSONModelErrorInvalidData = 1
* @param mismatchDescription description of the type mismatch that was encountered.
*/
+ (id)errorInvalidDataWithTypeMismatch:(NSString *)mismatchDescription;
/**
* Creates a JSONModelError instance with code kJSONModelErrorBadResponse = 2
*/
+ (id)errorBadResponse;
/**
* Creates a JSONModelError instance with code kJSONModelErrorBadJSON = 3
*/
+ (id)errorBadJSON;
/**
* Creates a JSONModelError instance with code kJSONModelErrorModelIsInvalid = 4
*/
+ (id)errorModelIsInvalid;
/**
* Creates a JSONModelError instance with code kJSONModelErrorNilInput = 5
*/
+ (id)errorInputIsNil;
/**
* Creates a new JSONModelError with the same values plus information about the key-path of the error.
* Properties in the new error object are the same as those from the receiver,
* except that a new key kJSONModelKeyPath is added to the userInfo dictionary.
* This key contains the component string parameter. If the key is already present
* then the new error object has the component string prepended to the existing value.
*/
- (instancetype)errorByPrependingKeyPathComponent:(NSString *)component;
/////////////////////////////////////////////////////////////////////////////////////////////
// 以下三种方法用来处理 kJSONModelErrorModelSoft
/**
生成新的 kJSONModelErrorModelSoft。msg通常格式为:{当前keyPath}应该是aaa,但是返回了bbb
*/
+ (id)errorSoftMsg:(NSString *)msg class:(Class)cls;
- (instancetype)errorByAppendingSoftMsg:(NSString *)msg class:(Class)cls;
- (instancetype)errorByAppendingSubPropertyMsg:(NSString *)msg class:(Class)cls;
@end
//
// JSONModelError.m
// JSONModel
//
#import "JSONModelError.h"
NSString* const JSONModelErrorDomain = @"JSONModelErrorDomain";
NSString* const kJSONModelMissingKeys = @"kJSONModelMissingKeys";
NSString* const kJSONModelTypeMismatch = @"kJSONModelTypeMismatch";
NSString* const kJSONModelKeyPath = @"kJSONModelKeyPath";
NSString* const kJSONModelSoftKeyPath = @"kJSONModelSoftKeyPath";
@implementation JSONModelError
+(id)errorInvalidDataWithMessage:(NSString*)message
{
message = [NSString stringWithFormat:@"Invalid JSON data: %@", message];
return [JSONModelError errorWithDomain:JSONModelErrorDomain
code:kJSONModelErrorInvalidData
userInfo:@{NSLocalizedDescriptionKey:message}];
}
+(id)errorInvalidDataWithMissingKeys:(NSSet *)keys
{
return [JSONModelError errorWithDomain:JSONModelErrorDomain
code:kJSONModelErrorInvalidData
userInfo:@{NSLocalizedDescriptionKey:@"Invalid JSON data. Required JSON keys are missing from the input. Check the error user information.",kJSONModelMissingKeys:[keys allObjects]}];
}
+(id)errorInvalidDataWithTypeMismatch:(NSString*)mismatchDescription
{
return [JSONModelError errorWithDomain:JSONModelErrorDomain
code:kJSONModelErrorInvalidData
userInfo:@{NSLocalizedDescriptionKey:@"Invalid JSON data. The JSON type mismatches the expected type. Check the error user information.",kJSONModelTypeMismatch:mismatchDescription}];
}
+(id)errorBadResponse
{
return [JSONModelError errorWithDomain:JSONModelErrorDomain
code:kJSONModelErrorBadResponse
userInfo:@{NSLocalizedDescriptionKey:@"Bad network response. Probably the JSON URL is unreachable."}];
}
+(id)errorBadJSON
{
return [JSONModelError errorWithDomain:JSONModelErrorDomain
code:kJSONModelErrorBadJSON
userInfo:@{NSLocalizedDescriptionKey:@"Malformed JSON. Check the JSONModel data input."}];
}
+(id)errorModelIsInvalid
{
return [JSONModelError errorWithDomain:JSONModelErrorDomain
code:kJSONModelErrorModelIsInvalid
userInfo:@{NSLocalizedDescriptionKey:@"Model does not validate. The custom validation for the input data failed."}];
}
+(id)errorInputIsNil
{
return [JSONModelError errorWithDomain:JSONModelErrorDomain
code:kJSONModelErrorNilInput
userInfo:@{NSLocalizedDescriptionKey:@"Initializing model with nil input object."}];
}
- (instancetype)errorByPrependingKeyPathComponent:(NSString*)component
{
// Create a mutable copy of the user info so that we can add to it and update it
NSMutableDictionary* userInfo = [self.userInfo mutableCopy];
// Create or update the key-path
NSString* existingPath = userInfo[kJSONModelKeyPath];
NSString* separator = [existingPath hasPrefix:@"["] ? @"" : @".";
NSString* updatedPath = (existingPath == nil) ? component : [component stringByAppendingFormat:@"%@%@", separator, existingPath];
userInfo[kJSONModelKeyPath] = updatedPath;
// Create the new error
return [JSONModelError errorWithDomain:self.domain
code:self.code
userInfo:[NSDictionary dictionaryWithDictionary:userInfo]];
}
- (instancetype)errorByAppendingSoftMsg:(NSString *)msg class:(Class)cls {
/*
softError: 特指kJSONModelErrorModelSoft错误
criticalError: 非kJSONModelErrorModelSoft错误
在userInfo中追加新的key-value。只会发生在softError中。
如果第一次产生的是softError,再发生criticalError,将只返回criticalError,softError将会被丢弃
*/
NSMutableDictionary* userInfo = [self.userInfo mutableCopy];
NSString *existMsg = userInfo[kJSONModelSoftKeyPath] ?: @"";
userInfo[kJSONModelSoftKeyPath] = [NSString stringWithFormat:@"%@.%@/%@", NSStringFromClass(cls), msg, existMsg];
return [JSONModelError errorWithDomain:JSONModelErrorDomain
code:kJSONModelErrorModelSoft
userInfo:[userInfo copy]];
}
- (instancetype)errorByAppendingSubPropertyMsg:(NSString *)msg class:(Class)cls {
/*
softError: 特指kJSONModelErrorModelSoft错误
criticalError: 非kJSONModelErrorModelSoft错误
在userInfo中追加新的key-value。只会发生在softError中。
如果第一次产生的是softError,再发生criticalError,将只返回criticalError,softError将会被丢弃
*/
NSMutableDictionary* userInfo = [self.userInfo mutableCopy];
NSString *existMsg = userInfo[kJSONModelSoftKeyPath] ?: @"";
userInfo[kJSONModelSoftKeyPath] = [NSString stringWithFormat:@"%@.%@->%@", NSStringFromClass(cls), msg, existMsg];
return [JSONModelError errorWithDomain:JSONModelErrorDomain
code:kJSONModelErrorModelSoft
userInfo:[userInfo copy]];
}
+(id)errorSoftMsg:(NSString *)msg class:(Class)cls {
NSDictionary *dic = @{NSLocalizedDescriptionKey:@"Some json value typee is wrong, but not critical.",
kJSONModelSoftKeyPath: [NSString stringWithFormat:@"%@.%@", NSStringFromClass(cls), msg]};
return [JSONModelError errorWithDomain:JSONModelErrorDomain
code:kJSONModelErrorModelSoft
userInfo:dic];
}
@end
//
// JSONModelLib.h
// JSONModel
//
#import <Foundation/Foundation.h>
// core
#import "JSONModel.h"
#import "JSONModelError.h"
// transformations
#import "JSONValueTransformer.h"
#import "JSONKeyMapper.h"
// networking (deprecated)
#import "JSONHTTPClient.h"
#import "JSONModel+networking.h"
#import "JSONAPI.h"
//
// JSONAPI.h
// JSONModel
//
#import <Foundation/Foundation.h>
#import "JSONHTTPClient.h"
DEPRECATED_ATTRIBUTE
@interface JSONAPI : NSObject
+ (void)setAPIBaseURLWithString:(NSString *)base DEPRECATED_ATTRIBUTE;
+ (void)setContentType:(NSString *)ctype DEPRECATED_ATTRIBUTE;
+ (void)getWithPath:(NSString *)path andParams:(NSDictionary *)params completion:(JSONObjectBlock)completeBlock DEPRECATED_ATTRIBUTE;
+ (void)postWithPath:(NSString *)path andParams:(NSDictionary *)params completion:(JSONObjectBlock)completeBlock DEPRECATED_ATTRIBUTE;
+ (void)rpcWithMethodName:(NSString *)method andArguments:(NSArray *)args completion:(JSONObjectBlock)completeBlock DEPRECATED_ATTRIBUTE;
+ (void)rpc2WithMethodName:(NSString *)method andParams:(id)params completion:(JSONObjectBlock)completeBlock DEPRECATED_ATTRIBUTE;
@end
//
// JSONAPI.m
// JSONModel
//
#import "JSONAPI.h"
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
#pragma GCC diagnostic ignored "-Wdeprecated-implementations"
#pragma mark - helper error model class
@interface JSONAPIRPCErrorModel: JSONModel
@property (assign, nonatomic) int code;
@property (strong, nonatomic) NSString* message;
@property (strong, nonatomic) id<Optional> data;
@end
#pragma mark - static variables
static JSONAPI* sharedInstance = nil;
static long jsonRpcId = 0;
#pragma mark - JSONAPI() private interface
@interface JSONAPI ()
@property (strong, nonatomic) NSString* baseURLString;
@end
#pragma mark - JSONAPI implementation
@implementation JSONAPI
#pragma mark - initialize
+(void)initialize
{
static dispatch_once_t once;
dispatch_once(&once, ^{
sharedInstance = [[JSONAPI alloc] init];
});
}
#pragma mark - api config methods
+(void)setAPIBaseURLWithString:(NSString*)base
{
sharedInstance.baseURLString = base;
}
+(void)setContentType:(NSString*)ctype
{
[JSONHTTPClient setRequestContentType: ctype];
}
#pragma mark - GET methods
+(void)getWithPath:(NSString*)path andParams:(NSDictionary*)params completion:(JSONObjectBlock)completeBlock
{
NSString* fullURL = [NSString stringWithFormat:@"%@%@", sharedInstance.baseURLString, path];
[JSONHTTPClient getJSONFromURLWithString: fullURL params:params completion:^(NSDictionary *json, JSONModelError *e) {
completeBlock(json, e);
}];
}
#pragma mark - POST methods
+(void)postWithPath:(NSString*)path andParams:(NSDictionary*)params completion:(JSONObjectBlock)completeBlock
{
NSString* fullURL = [NSString stringWithFormat:@"%@%@", sharedInstance.baseURLString, path];
[JSONHTTPClient postJSONFromURLWithString: fullURL params:params completion:^(NSDictionary *json, JSONModelError *e) {
completeBlock(json, e);
}];
}
#pragma mark - RPC methods
+(void)__rpcRequestWithObject:(id)jsonObject completion:(JSONObjectBlock)completeBlock
{
NSData* jsonRequestData = [NSJSONSerialization dataWithJSONObject:jsonObject
options:kNilOptions
error:nil];
NSString* jsonRequestString = [[NSString alloc] initWithData:jsonRequestData encoding: NSUTF8StringEncoding];
NSAssert(sharedInstance.baseURLString, @"API base URL not set");
[JSONHTTPClient postJSONFromURLWithString: sharedInstance.baseURLString
bodyString: jsonRequestString
completion:^(NSDictionary *json, JSONModelError* e) {
if (completeBlock) {
//handle the rpc response
NSDictionary* result = json[@"result"];
if (!result) {
JSONAPIRPCErrorModel* error = [[JSONAPIRPCErrorModel alloc] initWithDictionary:json[@"error"] error:nil];
if (error) {
//custom server error
if (!error.message) error.message = @"Generic json rpc error";
e = [JSONModelError errorWithDomain:JSONModelErrorDomain
code:error.code
userInfo: @{ NSLocalizedDescriptionKey : error.message}];
} else {
//generic error
e = [JSONModelError errorBadResponse];
}
}
//invoke the callback
completeBlock(result, e);
}
}];
}
+(void)rpcWithMethodName:(NSString*)method andArguments:(NSArray*)args completion:(JSONObjectBlock)completeBlock
{
NSAssert(method, @"No method specified");
if (!args) args = @[];
[self __rpcRequestWithObject:@{
//rpc 1.0
@"id": @(++jsonRpcId),
@"params": args,
@"method": method
} completion:completeBlock];
}
+(void)rpc2WithMethodName:(NSString*)method andParams:(id)params completion:(JSONObjectBlock)completeBlock
{
NSAssert(method, @"No method specified");
if (!params) params = @[];
[self __rpcRequestWithObject:@{
//rpc 2.0
@"jsonrpc": @"2.0",
@"id": @(++jsonRpcId),
@"params": params,
@"method": method
} completion:completeBlock];
}
@end
#pragma mark - helper rpc error model class implementation
@implementation JSONAPIRPCErrorModel
@end
//
// JSONModelHTTPClient.h
// JSONModel
//
#import "JSONModel.h"
extern NSString *const kHTTPMethodGET DEPRECATED_ATTRIBUTE;
extern NSString *const kHTTPMethodPOST DEPRECATED_ATTRIBUTE;
extern NSString *const kContentTypeAutomatic DEPRECATED_ATTRIBUTE;
extern NSString *const kContentTypeJSON DEPRECATED_ATTRIBUTE;
extern NSString *const kContentTypeWWWEncoded DEPRECATED_ATTRIBUTE;
typedef void (^JSONObjectBlock)(id json, JSONModelError *err) DEPRECATED_ATTRIBUTE;
DEPRECATED_ATTRIBUTE
@interface JSONHTTPClient : NSObject
+ (NSMutableDictionary *)requestHeaders DEPRECATED_ATTRIBUTE;
+ (void)setDefaultTextEncoding:(NSStringEncoding)encoding DEPRECATED_ATTRIBUTE;
+ (void)setCachingPolicy:(NSURLRequestCachePolicy)policy DEPRECATED_ATTRIBUTE;
+ (void)setTimeoutInSeconds:(int)seconds DEPRECATED_ATTRIBUTE;
+ (void)setRequestContentType:(NSString *)contentTypeString DEPRECATED_ATTRIBUTE;
+ (void)getJSONFromURLWithString:(NSString *)urlString completion:(JSONObjectBlock)completeBlock DEPRECATED_ATTRIBUTE;
+ (void)getJSONFromURLWithString:(NSString *)urlString params:(NSDictionary *)params completion:(JSONObjectBlock)completeBlock DEPRECATED_ATTRIBUTE;
+ (void)JSONFromURLWithString:(NSString *)urlString method:(NSString *)method params:(NSDictionary *)params orBodyString:(NSString *)bodyString completion:(JSONObjectBlock)completeBlock DEPRECATED_ATTRIBUTE;
+ (void)JSONFromURLWithString:(NSString *)urlString method:(NSString *)method params:(NSDictionary *)params orBodyString:(NSString *)bodyString headers:(NSDictionary *)headers completion:(JSONObjectBlock)completeBlock DEPRECATED_ATTRIBUTE;
+ (void)JSONFromURLWithString:(NSString *)urlString method:(NSString *)method params:(NSDictionary *)params orBodyData:(NSData *)bodyData headers:(NSDictionary *)headers completion:(JSONObjectBlock)completeBlock DEPRECATED_ATTRIBUTE;
+ (void)postJSONFromURLWithString:(NSString *)urlString params:(NSDictionary *)params completion:(JSONObjectBlock)completeBlock DEPRECATED_ATTRIBUTE;
+ (void)postJSONFromURLWithString:(NSString *)urlString bodyString:(NSString *)bodyString completion:(JSONObjectBlock)completeBlock DEPRECATED_ATTRIBUTE;
+ (void)postJSONFromURLWithString:(NSString *)urlString bodyData:(NSData *)bodyData completion:(JSONObjectBlock)completeBlock DEPRECATED_ATTRIBUTE;
@end
//
// JSONModelHTTPClient.m
// JSONModel
//
#import "JSONHTTPClient.h"
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
#pragma GCC diagnostic ignored "-Wdeprecated-implementations"
typedef void (^RequestResultBlock)(NSData *data, JSONModelError *error);
#pragma mark - constants
NSString* const kHTTPMethodGET = @"GET";
NSString* const kHTTPMethodPOST = @"POST";
NSString* const kContentTypeAutomatic = @"jsonmodel/automatic";
NSString* const kContentTypeJSON = @"application/json";
NSString* const kContentTypeWWWEncoded = @"application/x-www-form-urlencoded";
#pragma mark - static variables
/**
* Defaults for HTTP requests
*/
static NSStringEncoding defaultTextEncoding = NSUTF8StringEncoding;
static NSURLRequestCachePolicy defaultCachePolicy = NSURLRequestReloadIgnoringLocalCacheData;
static int defaultTimeoutInSeconds = 60;
/**
* Custom HTTP headers to send over with *each* request
*/
static NSMutableDictionary* requestHeaders = nil;
/**
* Default request content type
*/
static NSString* requestContentType = nil;
#pragma mark - implementation
@implementation JSONHTTPClient
#pragma mark - initialization
+(void)initialize
{
static dispatch_once_t once;
dispatch_once(&once, ^{
requestHeaders = [NSMutableDictionary dictionary];
requestContentType = kContentTypeAutomatic;
});
}
#pragma mark - configuration methods
+(NSMutableDictionary*)requestHeaders
{
return requestHeaders;
}
+(void)setDefaultTextEncoding:(NSStringEncoding)encoding
{
defaultTextEncoding = encoding;
}
+(void)setCachingPolicy:(NSURLRequestCachePolicy)policy
{
defaultCachePolicy = policy;
}
+(void)setTimeoutInSeconds:(int)seconds
{
defaultTimeoutInSeconds = seconds;
}
+(void)setRequestContentType:(NSString*)contentTypeString
{
requestContentType = contentTypeString;
}
#pragma mark - helper methods
+(NSString*)contentTypeForRequestString:(NSString*)requestString
{
//fetch the charset name from the default string encoding
NSString* contentType = requestContentType;
if (requestString.length>0 && [contentType isEqualToString:kContentTypeAutomatic]) {
//check for "eventual" JSON array or dictionary
NSString* firstAndLastChar = [NSString stringWithFormat:@"%@%@",
[requestString substringToIndex:1],
[requestString substringFromIndex: requestString.length -1]
];
if ([firstAndLastChar isEqualToString:@"{}"] || [firstAndLastChar isEqualToString:@"[]"]) {
//guessing for a JSON request
contentType = kContentTypeJSON;
} else {
//fallback to www form encoded params
contentType = kContentTypeWWWEncoded;
}
}
//type is set, just add charset
NSString *charset = (NSString *)CFStringConvertEncodingToIANACharSetName(CFStringConvertNSStringEncodingToEncoding(NSUTF8StringEncoding));
return [NSString stringWithFormat:@"%@; charset=%@", contentType, charset];
}
+(NSString*)urlEncode:(id<NSObject>)value
{
//make sure param is a string
if ([value isKindOfClass:[NSNumber class]]) {
value = [(NSNumber*)value stringValue];
}
NSAssert([value isKindOfClass:[NSString class]], @"request parameters can be only of NSString or NSNumber classes. '%@' is of class %@.", value, [value class]);
NSString *str = (NSString *)value;
#if __IPHONE_OS_VERSION_MIN_REQUIRED >= __IPHONE_7_0 || __MAC_OS_X_VERSION_MIN_REQUIRED >= __MAC_10_9
return [str stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLQueryAllowedCharacterSet]];
#else
return (NSString *)CFBridgingRelease(CFURLCreateStringByAddingPercentEscapes(
NULL,
(__bridge CFStringRef)str,
NULL,
(CFStringRef)@"!*'();:@&=+$,/?%#[]",
kCFStringEncodingUTF8));
#endif
}
#pragma mark - networking worker methods
+(void)requestDataFromURL:(NSURL*)url method:(NSString*)method requestBody:(NSData*)bodyData headers:(NSDictionary*)headers handler:(RequestResultBlock)handler
{
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL: url
cachePolicy: defaultCachePolicy
timeoutInterval: defaultTimeoutInSeconds];
[request setHTTPMethod:method];
if ([requestContentType isEqualToString:kContentTypeAutomatic]) {
//automatic content type
if (bodyData) {
NSString *bodyString = [[NSString alloc] initWithData:bodyData encoding:NSUTF8StringEncoding];
[request setValue: [self contentTypeForRequestString: bodyString] forHTTPHeaderField:@"Content-type"];
}
} else {
//user set content type
[request setValue: requestContentType forHTTPHeaderField:@"Content-type"];
}
//add all the custom headers defined
for (NSString* key in [requestHeaders allKeys]) {
[request setValue:requestHeaders[key] forHTTPHeaderField:key];
}
//add the custom headers
for (NSString* key in [headers allKeys]) {
[request setValue:headers[key] forHTTPHeaderField:key];
}
if (bodyData) {
[request setHTTPBody: bodyData];
[request setValue:[NSString stringWithFormat:@"%lu", (unsigned long)bodyData.length] forHTTPHeaderField:@"Content-Length"];
}
void (^completionHandler)(NSData *, NSURLResponse *, NSError *) = ^(NSData *data, NSURLResponse *origResponse, NSError *origError) {
NSHTTPURLResponse *response = (NSHTTPURLResponse *)origResponse;
JSONModelError *error = nil;
//convert an NSError to a JSONModelError
if (origError) {
error = [JSONModelError errorWithDomain:origError.domain code:origError.code userInfo:origError.userInfo];
}
//special case for http error code 401
if (error.code == NSURLErrorUserCancelledAuthentication) {
response = [[NSHTTPURLResponse alloc] initWithURL:url statusCode:401 HTTPVersion:@"HTTP/1.1" headerFields:@{}];
}
//if not OK status set the err to a JSONModelError instance
if (!error && (response.statusCode >= 300 || response.statusCode < 200)) {
error = [JSONModelError errorBadResponse];
}
//if there was an error, assign the response to the JSONModel instance
if (error) {
error.httpResponse = [response copy];
}
//empty respone, return nil instead
if (!data.length) {
data = nil;
}
handler(data, error);
};
//fire the request
#if __IPHONE_OS_VERSION_MIN_REQUIRED >= __IPHONE_7_0 || __MAC_OS_X_VERSION_MIN_REQUIRED >= __MAC_10_10
NSURLSessionTask *task = [[NSURLSession sharedSession] dataTaskWithRequest:request completionHandler:completionHandler];
[task resume];
#else
NSOperationQueue *queue = [NSOperationQueue new];
[NSURLConnection sendAsynchronousRequest:request queue:queue completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) {
completionHandler(data, response, error);
}];
#endif
}
+(void)requestDataFromURL:(NSURL*)url method:(NSString*)method params:(NSDictionary*)params headers:(NSDictionary*)headers handler:(RequestResultBlock)handler
{
//create the request body
NSMutableString* paramsString = nil;
if (params) {
//build a simple url encoded param string
paramsString = [NSMutableString stringWithString:@""];
for (NSString* key in [[params allKeys] sortedArrayUsingSelector:@selector(compare:)]) {
[paramsString appendFormat:@"%@=%@&", key, [self urlEncode:params[key]] ];
}
if ([paramsString hasSuffix:@"&"]) {
paramsString = [[NSMutableString alloc] initWithString: [paramsString substringToIndex: paramsString.length-1]];
}
}
//set the request params
if ([method isEqualToString:kHTTPMethodGET] && params) {
//add GET params to the query string
url = [NSURL URLWithString:[NSString stringWithFormat: @"%@%@%@",
[url absoluteString],
[url query] ? @"&" : @"?",
paramsString
]];
}
//call the more general synq request method
[self requestDataFromURL: url
method: method
requestBody: [method isEqualToString:kHTTPMethodPOST]?[paramsString dataUsingEncoding:NSUTF8StringEncoding]:nil
headers: headers
handler:handler];
}
#pragma mark - Async network request
+(void)JSONFromURLWithString:(NSString*)urlString method:(NSString*)method params:(NSDictionary*)params orBodyString:(NSString*)bodyString completion:(JSONObjectBlock)completeBlock
{
[self JSONFromURLWithString:urlString
method:method
params:params
orBodyString:bodyString
headers:nil
completion:completeBlock];
}
+(void)JSONFromURLWithString:(NSString *)urlString method:(NSString *)method params:(NSDictionary *)params orBodyString:(NSString *)bodyString headers:(NSDictionary *)headers completion:(JSONObjectBlock)completeBlock
{
[self JSONFromURLWithString:urlString
method:method
params:params
orBodyData:[bodyString dataUsingEncoding:NSUTF8StringEncoding]
headers:headers
completion:completeBlock];
}
+(void)JSONFromURLWithString:(NSString*)urlString method:(NSString*)method params:(NSDictionary *)params orBodyData:(NSData*)bodyData headers:(NSDictionary*)headers completion:(JSONObjectBlock)completeBlock
{
RequestResultBlock handler = ^(NSData *responseData, JSONModelError *error) {
id jsonObject = nil;
//step 3: if there's no response so far, return a basic error
if (!responseData && !error) {
//check for false response, but no network error
error = [JSONModelError errorBadResponse];
}
//step 4: if there's a response at this and no errors, convert to object
if (error==nil) {
// Note: it is possible to have a valid response with empty response data (204 No Content).
// So only create the JSON object if there is some response data.
if(responseData.length > 0)
{
//convert to an object
jsonObject = [NSJSONSerialization JSONObjectWithData:responseData options:kNilOptions error:&error];
}
}
//step 4.5: cover an edge case in which meaningful content is return along an error HTTP status code
else if (error && responseData && jsonObject==nil) {
//try to get the JSON object, while preserving the original error object
jsonObject = [NSJSONSerialization JSONObjectWithData:responseData options:kNilOptions error:nil];
//keep responseData just in case it contains error information
error.responseData = responseData;
}
//step 5: invoke the complete block
dispatch_async(dispatch_get_main_queue(), ^{
if (completeBlock) {
completeBlock(jsonObject, error);
}
});
};
NSURL *url = [NSURL URLWithString:urlString];
if (bodyData) {
[self requestDataFromURL:url method:method requestBody:bodyData headers:headers handler:handler];
} else {
[self requestDataFromURL:url method:method params:params headers:headers handler:handler];
}
}
#pragma mark - request aliases
+(void)getJSONFromURLWithString:(NSString*)urlString completion:(JSONObjectBlock)completeBlock
{
[self JSONFromURLWithString:urlString method:kHTTPMethodGET
params:nil
orBodyString:nil completion:^(id json, JSONModelError* e) {
if (completeBlock) completeBlock(json, e);
}];
}
+(void)getJSONFromURLWithString:(NSString*)urlString params:(NSDictionary*)params completion:(JSONObjectBlock)completeBlock
{
[self JSONFromURLWithString:urlString method:kHTTPMethodGET
params:params
orBodyString:nil completion:^(id json, JSONModelError* e) {
if (completeBlock) completeBlock(json, e);
}];
}
+(void)postJSONFromURLWithString:(NSString*)urlString params:(NSDictionary*)params completion:(JSONObjectBlock)completeBlock
{
[self JSONFromURLWithString:urlString method:kHTTPMethodPOST
params:params
orBodyString:nil completion:^(id json, JSONModelError* e) {
if (completeBlock) completeBlock(json, e);
}];
}
+(void)postJSONFromURLWithString:(NSString*)urlString bodyString:(NSString*)bodyString completion:(JSONObjectBlock)completeBlock
{
[self JSONFromURLWithString:urlString method:kHTTPMethodPOST
params:nil
orBodyString:bodyString completion:^(id json, JSONModelError* e) {
if (completeBlock) completeBlock(json, e);
}];
}
+(void)postJSONFromURLWithString:(NSString*)urlString bodyData:(NSData*)bodyData completion:(JSONObjectBlock)completeBlock
{
[self JSONFromURLWithString:urlString method:kHTTPMethodPOST
params:nil
orBodyString:[[NSString alloc] initWithData:bodyData encoding:defaultTextEncoding]
completion:^(id json, JSONModelError* e) {
if (completeBlock) completeBlock(json, e);
}];
}
@end
//
// JSONModel+networking.h
// JSONModel
//
#import "JSONModel.h"
#import "JSONHTTPClient.h"
typedef void (^JSONModelBlock)(id model, JSONModelError *err) DEPRECATED_ATTRIBUTE;
@interface JSONModel (Networking)
@property (assign, nonatomic) BOOL isLoading DEPRECATED_ATTRIBUTE;
- (instancetype)initFromURLWithString:(NSString *)urlString completion:(JSONModelBlock)completeBlock DEPRECATED_ATTRIBUTE;
+ (void)getModelFromURLWithString:(NSString *)urlString completion:(JSONModelBlock)completeBlock DEPRECATED_ATTRIBUTE;
+ (void)postModel:(JSONModel *)post toURLWithString:(NSString *)urlString completion:(JSONModelBlock)completeBlock DEPRECATED_ATTRIBUTE;
@end
//
// JSONModel+networking.m
// JSONModel
//
#import "JSONModel+networking.h"
#import "JSONHTTPClient.h"
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
#pragma GCC diagnostic ignored "-Wdeprecated-implementations"
BOOL _isLoading;
@implementation JSONModel(Networking)
@dynamic isLoading;
-(BOOL)isLoading
{
return _isLoading;
}
-(void)setIsLoading:(BOOL)isLoading
{
_isLoading = isLoading;
}
-(instancetype)initFromURLWithString:(NSString *)urlString completion:(JSONModelBlock)completeBlock
{
id placeholder = [super init];
__block id blockSelf = self;
if (placeholder) {
//initialization
self.isLoading = YES;
[JSONHTTPClient getJSONFromURLWithString:urlString
completion:^(NSDictionary *json, JSONModelError* e) {
JSONModelError* initError = nil;
blockSelf = [self initWithDictionary:json error:&initError];
if (completeBlock) {
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 1 * NSEC_PER_MSEC), dispatch_get_main_queue(), ^{
completeBlock(blockSelf, e?e:initError );
});
}
self.isLoading = NO;
}];
}
return placeholder;
}
+ (void)getModelFromURLWithString:(NSString*)urlString completion:(JSONModelBlock)completeBlock
{
[JSONHTTPClient getJSONFromURLWithString:urlString
completion:^(NSDictionary* jsonDict, JSONModelError* err)
{
JSONModel* model = nil;
if(err == nil)
{
model = [[self alloc] initWithDictionary:jsonDict error:&err];
}
if(completeBlock != nil)
{
dispatch_async(dispatch_get_main_queue(), ^
{
completeBlock(model, err);
});
}
}];
}
+ (void)postModel:(JSONModel*)post toURLWithString:(NSString*)urlString completion:(JSONModelBlock)completeBlock
{
[JSONHTTPClient postJSONFromURLWithString:urlString
bodyString:[post toJSONString]
completion:^(NSDictionary* jsonDict, JSONModelError* err)
{
JSONModel* model = nil;
if(err == nil)
{
model = [[self alloc] initWithDictionary:jsonDict error:&err];
}
if(completeBlock != nil)
{
dispatch_async(dispatch_get_main_queue(), ^
{
completeBlock(model, err);
});
}
}];
}
@end
//
// JSONKeyMapper.h
// JSONModel
//
#import <Foundation/Foundation.h>
typedef NSString *(^JSONModelKeyMapBlock)(NSString *keyName);
/**
* **You won't need to create or store instances of this class yourself.** If you want your model
* to have different property names than the JSON feed keys, look below on how to
* make your model use a key mapper.
*
* For example if you consume JSON from twitter
* you get back underscore_case style key names. For example:
*
* <pre>"profile_sidebar_border_color": "0094C2",
* "profile_background_tile": false,</pre>
*
* To comply with Obj-C accepted camelCase property naming for your classes,
* you need to provide mapping between JSON keys and ObjC property names.
*
* In your model overwrite the + (JSONKeyMapper *)keyMapper method and provide a JSONKeyMapper
* instance to convert the key names for your model.
*
* If you need custom mapping it's as easy as:
* <pre>
* + (JSONKeyMapper *)keyMapper {
* &nbsp; return [[JSONKeyMapper&nbsp;alloc]&nbsp;initWithDictionary:@{@"crazy_JSON_name":@"myCamelCaseName"}];
* }
* </pre>
* In case you want to handle underscore_case, **use the predefined key mapper**, like so:
* <pre>
* + (JSONKeyMapper *)keyMapper {
* &nbsp; return [JSONKeyMapper&nbsp;mapperFromUnderscoreCaseToCamelCase];
* }
* </pre>
*/
@interface JSONKeyMapper : NSObject
// deprecated
@property (readonly, nonatomic) JSONModelKeyMapBlock JSONToModelKeyBlock DEPRECATED_ATTRIBUTE;
- (NSString *)convertValue:(NSString *)value isImportingToModel:(BOOL)importing DEPRECATED_MSG_ATTRIBUTE("use convertValue:");
- (instancetype)initWithDictionary:(NSDictionary *)map DEPRECATED_MSG_ATTRIBUTE("use initWithModelToJSONDictionary:");
- (instancetype)initWithJSONToModelBlock:(JSONModelKeyMapBlock)toModel modelToJSONBlock:(JSONModelKeyMapBlock)toJSON DEPRECATED_MSG_ATTRIBUTE("use initWithModelToJSONBlock:");
+ (instancetype)mapper:(JSONKeyMapper *)baseKeyMapper withExceptions:(NSDictionary *)exceptions DEPRECATED_MSG_ATTRIBUTE("use baseMapper:withModelToJSONExceptions:");
+ (instancetype)mapperFromUnderscoreCaseToCamelCase DEPRECATED_MSG_ATTRIBUTE("use mapperForSnakeCase:");
+ (instancetype)mapperFromUpperCaseToLowerCase DEPRECATED_ATTRIBUTE;
/** @name Name converters */
/** Block, which takes in a property name and converts it to the corresponding JSON key name */
@property (readonly, nonatomic) JSONModelKeyMapBlock modelToJSONKeyBlock;
/** Combined converter method
* @param value the source name
* @return JSONKeyMapper instance
*/
- (NSString *)convertValue:(NSString *)value;
/** @name Creating a key mapper */
/**
* Creates a JSONKeyMapper instance, based on the block you provide this initializer.
* The parameter takes in a JSONModelKeyMapBlock block:
* <pre>NSString *(^JSONModelKeyMapBlock)(NSString *keyName)</pre>
* The block takes in a string and returns the transformed (if at all) string.
* @param toJSON transforms your model property name to a JSON key
*/
- (instancetype)initWithModelToJSONBlock:(JSONModelKeyMapBlock)toJSON;
/**
* Creates a JSONKeyMapper instance, based on the mapping you provide.
* Use your JSONModel property names as keys, and the JSON key names as values.
* @param toJSON map dictionary, in the format: <pre>@{@"myCamelCaseName":@"crazy_JSON_name"}</pre>
* @return JSONKeyMapper instance
*/
- (instancetype)initWithModelToJSONDictionary:(NSDictionary <NSString *, NSString *> *)toJSON;
/**
* Given a camelCase model property, this mapper finds JSON keys using the snake_case equivalent.
*/
+ (instancetype)mapperForSnakeCase;
/**
* Given a camelCase model property, this mapper finds JSON keys using the TitleCase equivalent.
*/
+ (instancetype)mapperForTitleCase;
/**
* Creates a JSONKeyMapper based on a built-in JSONKeyMapper, with specific exceptions.
* Use your JSONModel property names as keys, and the JSON key names as values.
*/
+ (instancetype)baseMapper:(JSONKeyMapper *)baseKeyMapper withModelToJSONExceptions:(NSDictionary *)toJSON;
@end
//
// JSONKeyMapper.m
// JSONModel
//
#import "JSONKeyMapper.h"
@implementation JSONKeyMapper
- (instancetype)initWithJSONToModelBlock:(JSONModelKeyMapBlock)toModel modelToJSONBlock:(JSONModelKeyMapBlock)toJSON
{
return [self initWithModelToJSONBlock:toJSON];
}
- (instancetype)initWithModelToJSONBlock:(JSONModelKeyMapBlock)toJSON
{
if (!(self = [self init]))
return nil;
_modelToJSONKeyBlock = toJSON;
return self;
}
- (instancetype)initWithDictionary:(NSDictionary *)map
{
NSDictionary *toJSON = [JSONKeyMapper swapKeysAndValuesInDictionary:map];
return [self initWithModelToJSONDictionary:toJSON];
}
- (instancetype)initWithModelToJSONDictionary:(NSDictionary <NSString *, NSString *> *)toJSON
{
if (!(self = [super init]))
return nil;
_modelToJSONKeyBlock = ^NSString *(NSString *keyName)
{
return [toJSON valueForKeyPath:keyName] ?: keyName;
};
return self;
}
- (JSONModelKeyMapBlock)JSONToModelKeyBlock
{
return nil;
}
+ (NSDictionary *)swapKeysAndValuesInDictionary:(NSDictionary *)dictionary
{
NSArray *keys = dictionary.allKeys;
NSArray *values = [dictionary objectsForKeys:keys notFoundMarker:[NSNull null]];
return [NSDictionary dictionaryWithObjects:keys forKeys:values];
}
- (NSString *)convertValue:(NSString *)value isImportingToModel:(BOOL)importing
{
return [self convertValue:value];
}
- (NSString *)convertValue:(NSString *)value
{
return _modelToJSONKeyBlock(value);
}
+ (instancetype)mapperFromUnderscoreCaseToCamelCase
{
return [self mapperForSnakeCase];
}
+ (instancetype)mapperForSnakeCase
{
return [[self alloc] initWithModelToJSONBlock:^NSString *(NSString *keyName)
{
NSMutableString *result = [NSMutableString stringWithString:keyName];
NSRange range;
// handle upper case chars
range = [result rangeOfCharacterFromSet:[NSCharacterSet uppercaseLetterCharacterSet]];
while (range.location != NSNotFound)
{
NSString *lower = [result substringWithRange:range].lowercaseString;
[result replaceCharactersInRange:range withString:[NSString stringWithFormat:@"_%@", lower]];
range = [result rangeOfCharacterFromSet:[NSCharacterSet uppercaseLetterCharacterSet]];
}
// handle numbers
range = [result rangeOfCharacterFromSet:[NSCharacterSet decimalDigitCharacterSet]];
while (range.location != NSNotFound)
{
NSRange end = [result rangeOfString:@"\\D" options:NSRegularExpressionSearch range:NSMakeRange(range.location, result.length - range.location)];
// spans to the end of the key name
if (end.location == NSNotFound)
end = NSMakeRange(result.length, 1);
NSRange replaceRange = NSMakeRange(range.location, end.location - range.location);
NSString *digits = [result substringWithRange:replaceRange];
[result replaceCharactersInRange:replaceRange withString:[NSString stringWithFormat:@"_%@", digits]];
range = [result rangeOfCharacterFromSet:[NSCharacterSet decimalDigitCharacterSet] options:0 range:NSMakeRange(end.location + 1, result.length - end.location - 1)];
}
return result;
}];
}
+ (instancetype)mapperForTitleCase
{
return [[self alloc] initWithModelToJSONBlock:^NSString *(NSString *keyName)
{
return [keyName stringByReplacingCharactersInRange:NSMakeRange(0, 1) withString:[keyName substringToIndex:1].uppercaseString];
}];
}
+ (instancetype)mapperFromUpperCaseToLowerCase
{
return [[self alloc] initWithModelToJSONBlock:^NSString *(NSString *keyName)
{
return keyName.uppercaseString;
}];
}
+ (instancetype)mapper:(JSONKeyMapper *)baseKeyMapper withExceptions:(NSDictionary *)exceptions
{
NSDictionary *toJSON = [JSONKeyMapper swapKeysAndValuesInDictionary:exceptions];
return [self baseMapper:baseKeyMapper withModelToJSONExceptions:toJSON];
}
+ (instancetype)baseMapper:(JSONKeyMapper *)baseKeyMapper withModelToJSONExceptions:(NSDictionary *)toJSON
{
return [[self alloc] initWithModelToJSONBlock:^NSString *(NSString *keyName)
{
if (!keyName)
return nil;
if (toJSON[keyName])
return toJSON[keyName];
return baseKeyMapper.modelToJSONKeyBlock(keyName);
}];
}
@end
//
// JSONValueTransformer.h
// JSONModel
//
#import <Foundation/Foundation.h>
/////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark - extern definitions
/**
* Boolean function to check for null values. Handy when you need to both check
* for nil and [NSNUll null]
*/
extern BOOL isNull(id value);
/////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark - JSONValueTransformer interface
/**
* **You don't need to call methods of this class manually.**
*
* Class providing methods to transform values from one class to another.
* You are given a number of built-in transformers, but you are encouraged to
* extend this class with your own categories to add further value transformers.
* Just few examples of what can you add to JSONValueTransformer: hex colors in JSON to UIColor,
* hex numbers in JSON to NSNumber model properties, base64 encoded strings in JSON to UIImage properties, and more.
*
* The class is invoked by JSONModel while transforming incoming
* JSON types into your target class property classes, and vice versa.
* One static copy is create and store in the JSONModel class scope.
*/
@interface JSONValueTransformer : NSObject
@property (strong, nonatomic, readonly) NSDictionary *primitivesNames;
/** @name Resolving cluster class names */
/**
* This method returns the umbrella class for any standard class cluster members.
* For example returns NSString when given as input NSString, NSMutableString, __CFString and __CFConstantString
* The method currently looksup a pre-defined list.
* @param sourceClass the class to get the umbrella class for
* @return Class
*/
+ (Class)classByResolvingClusterClasses:(Class)sourceClass;
#pragma mark - NSMutableString <-> NSString
/** @name Transforming to Mutable copies */
/**
* Transforms a string value to a mutable string value
* @param string incoming string
* @return mutable string
*/
- (NSMutableString *)NSMutableStringFromNSString:(NSString *)string;
#pragma mark - NSMutableArray <-> NSArray
/**
* Transforms an array to a mutable array
* @param array incoming array
* @return mutable array
*/
- (NSMutableArray *)NSMutableArrayFromNSArray:(NSArray *)array;
#pragma mark - NSMutableDictionary <-> NSDictionary
/**
* Transforms a dictionary to a mutable dictionary
* @param dict incoming dictionary
* @return mutable dictionary
*/
- (NSMutableDictionary *)NSMutableDictionaryFromNSDictionary:(NSDictionary *)dict;
#pragma mark - NSSet <-> NSArray
/** @name Transforming Sets */
/**
* Transforms an array to a set
* @param array incoming array
* @return set with the array's elements
*/
- (NSSet *)NSSetFromNSArray:(NSArray *)array;
/**
* Transforms an array to a mutable set
* @param array incoming array
* @return mutable set with the array's elements
*/
- (NSMutableSet *)NSMutableSetFromNSArray:(NSArray *)array;
/**
* Transforms a set to an array
* @param set incoming set
* @return an array with the set's elements
*/
- (NSArray *)JSONObjectFromNSSet:(NSSet *)set;
/**
* Transforms a mutable set to an array
* @param set incoming mutable set
* @return an array with the set's elements
*/
- (NSArray *)JSONObjectFromNSMutableSet:(NSMutableSet *)set;
#pragma mark - BOOL <-> number/string
/** @name Transforming JSON types */
/**
* Transforms a number object to a bool number object
* @param number the number to convert
* @return the resulting number
*/
- (NSNumber *)BOOLFromNSNumber:(NSNumber *)number;
/**
* Transforms a number object to a bool number object
* @param string the string value to convert, "0" converts to NO, everything else to YES
* @return the resulting number
*/
- (NSNumber *)BOOLFromNSString:(NSString *)string;
/**
* Transforms a BOOL value to a bool number object
* @param number an NSNumber value coming from the model
* @return the result number
*/
- (NSNumber *)JSONObjectFromBOOL:(NSNumber *)number;
#pragma mark - string <-> number
/**
* Transforms a string object to a number object
* @param string the string to convert
* @return the resulting number
*/
- (NSNumber *)NSNumberFromNSString:(NSString *)string;
/**
* Transforms a number object to a string object
* @param number the number to convert
* @return the resulting string
*/
- (NSString *)NSStringFromNSNumber:(NSNumber *)number;
/**
* Transforms a string object to a nsdecimalnumber object
* @param string the string to convert
* @return the resulting number
*/
- (NSDecimalNumber *)NSDecimalNumberFromNSString:(NSString *)string;
/**
* Transforms a nsdecimalnumber object to a string object
* @param number the number to convert
* @return the resulting string
*/
- (NSString *)NSStringFromNSDecimalNumber:(NSDecimalNumber *)number;
#pragma mark - string <-> url
/** @name Transforming URLs */
/**
* Transforms a string object to an NSURL object
* @param string the string to convert
* @return the resulting url object
*/
- (NSURL *)NSURLFromNSString:(NSString *)string;
/**
* Transforms an NSURL object to a string
* @param url the url object to convert
* @return the resulting string
*/
- (NSString *)JSONObjectFromNSURL:(NSURL *)url;
#pragma mark - string <-> time zone
/** @name Transforming NSTimeZone */
/**
* Transforms a string object to an NSTimeZone object
* @param string the string to convert
* @return the resulting NSTimeZone object
*/
- (NSTimeZone *)NSTimeZoneFromNSString:(NSString *)string;
/**
* Transforms an NSTimeZone object to a string
* @param timeZone the time zone object to convert
* @return the resulting string
*/
- (NSString *)JSONObjectFromNSTimeZone:(NSTimeZone *)timeZone;
#pragma mark - string <-> date
/** @name Transforming Dates */
/**
* The following two methods are not public. This way if there is a category on converting
* dates it'll override them. If there isn't a category the default methods found in the .m
* file will be invoked. If these are public a warning is produced at the point of overriding
* them in a category, so they have to stay hidden here.
*/
//- (NSDate *)NSDateFromNSString:(NSString *)string;
//- (NSString *)JSONObjectFromNSDate:(NSDate *)date;
#pragma mark - number <-> date
/**
* Transforms a number to an NSDate object
* @param number the number to convert
* @return the resulting date
*/
- (NSDate *)NSDateFromNSNumber:(NSNumber *)number;
@end
//
// JSONValueTransformer.m
// JSONModel
//
#import "JSONValueTransformer.h"
#pragma mark - functions
extern BOOL isNull(id value)
{
if (!value) return YES;
if ([value isKindOfClass:[NSNull class]]) return YES;
return NO;
}
@implementation JSONValueTransformer
-(id)init
{
self = [super init];
if (self) {
_primitivesNames = @{@"f":@"float", @"i":@"int", @"d":@"double", @"l":@"long", @"B":@"BOOL", @"s":@"short",
@"I":@"unsigned int", @"L":@"usigned long", @"q":@"long long", @"Q":@"unsigned long long", @"S":@"unsigned short", @"c":@"char", @"C":@"unsigned char",
//and some famous aliases of primitive types
// BOOL is now "B" on iOS __LP64 builds
@"I":@"NSInteger", @"Q":@"NSUInteger", @"B":@"BOOL",
@"@?":@"Block"};
}
return self;
}
+(Class)classByResolvingClusterClasses:(Class)sourceClass
{
//check for all variations of strings
if ([sourceClass isSubclassOfClass:[NSString class]]) {
return [NSString class];
}
//check for all variations of numbers
if ([sourceClass isSubclassOfClass:[NSNumber class]]) {
return [NSNumber class];
}
//check for all variations of dictionaries
if ([sourceClass isSubclassOfClass:[NSArray class]]) {
return [NSArray class];
}
//check for all variations of arrays
if ([sourceClass isSubclassOfClass:[NSDictionary class]]) {
return [NSDictionary class];
}
//check for all variations of dates
if ([sourceClass isSubclassOfClass:[NSDate class]]) {
return [NSDate class];
}
//no cluster parent class found
return sourceClass;
}
#pragma mark - NSMutableString <-> NSString
-(NSMutableString*)NSMutableStringFromNSString:(NSString*)string
{
return [NSMutableString stringWithString:string];
}
#pragma mark - NSMutableArray <-> NSArray
-(NSMutableArray*)NSMutableArrayFromNSArray:(NSArray*)array
{
return [NSMutableArray arrayWithArray:array];
}
#pragma mark - NSMutableDictionary <-> NSDictionary
-(NSMutableDictionary*)NSMutableDictionaryFromNSDictionary:(NSDictionary*)dict
{
return [NSMutableDictionary dictionaryWithDictionary:dict];
}
#pragma mark - NSSet <-> NSArray
-(NSSet*)NSSetFromNSArray:(NSArray*)array
{
return [NSSet setWithArray:array];
}
-(NSMutableSet*)NSMutableSetFromNSArray:(NSArray*)array
{
return [NSMutableSet setWithArray:array];
}
-(id)JSONObjectFromNSSet:(NSSet*)set
{
return [set allObjects];
}
-(id)JSONObjectFromNSMutableSet:(NSMutableSet*)set
{
return [set allObjects];
}
//
// 0 converts to NO, everything else converts to YES
//
#pragma mark - BOOL <-> number/string
-(NSNumber*)BOOLFromNSNumber:(NSNumber*)number
{
if (isNull(number)) return [NSNumber numberWithBool:NO];
return [NSNumber numberWithBool: number.intValue==0?NO:YES];
}
-(NSNumber*)BOOLFromNSString:(NSString*)string
{
if (string != nil &&
([string caseInsensitiveCompare:@"true"] == NSOrderedSame ||
[string caseInsensitiveCompare:@"yes"] == NSOrderedSame)) {
return [NSNumber numberWithBool:YES];
}
return [NSNumber numberWithBool: ([string intValue]==0)?NO:YES];
}
-(NSNumber*)JSONObjectFromBOOL:(NSNumber*)number
{
return [NSNumber numberWithBool: number.intValue==0?NO:YES];
}
#pragma mark - string/number <-> float
-(float)floatFromObject:(id)obj
{
return [obj floatValue];
}
-(float)floatFromNSString:(NSString*)string
{
return [self floatFromObject:string];
}
-(float)floatFromNSNumber:(NSNumber*)number
{
return [self floatFromObject:number];
}
-(NSNumber*)NSNumberFromfloat:(float)f
{
return [NSNumber numberWithFloat:f];
}
#pragma mark - string <-> number
-(NSNumber*)NSNumberFromNSString:(NSString*)string
{
return [NSNumber numberWithDouble:[string doubleValue]];
}
-(NSString*)NSStringFromNSNumber:(NSNumber*)number
{
return [number stringValue];
}
-(NSDecimalNumber*)NSDecimalNumberFromNSString:(NSString*)string
{
return [NSDecimalNumber decimalNumberWithString:string];
}
-(NSString*)NSStringFromNSDecimalNumber:(NSDecimalNumber*)number
{
return [number stringValue];
}
#pragma mark - string <-> url
-(NSURL*)NSURLFromNSString:(NSString*)string
{
// do not change this behavior - there are other ways of overriding it
// see: https://github.com/jsonmodel/jsonmodel/pull/119
return [NSURL URLWithString:string];
}
-(NSString*)JSONObjectFromNSURL:(NSURL*)url
{
return [url absoluteString];
}
#pragma mark - string <-> date
-(NSDateFormatter*)importDateFormatter
{
static dispatch_once_t onceInput;
static NSDateFormatter* inputDateFormatter;
dispatch_once(&onceInput, ^{
inputDateFormatter = [[NSDateFormatter alloc] init];
[inputDateFormatter setLocale:[[NSLocale alloc] initWithLocaleIdentifier:@"en_US_POSIX"]];
[inputDateFormatter setDateFormat:@"yyyy-MM-dd'T'HHmmssZZZ"];
});
return inputDateFormatter;
}
-(NSDate*)__NSDateFromNSString:(NSString*)string
{
string = [string stringByReplacingOccurrencesOfString:@":" withString:@""]; // this is such an ugly code, is this the only way?
return [self.importDateFormatter dateFromString: string];
}
-(NSString*)__JSONObjectFromNSDate:(NSDate*)date
{
static dispatch_once_t onceOutput;
static NSDateFormatter *outputDateFormatter;
dispatch_once(&onceOutput, ^{
outputDateFormatter = [[NSDateFormatter alloc] init];
[outputDateFormatter setLocale:[[NSLocale alloc] initWithLocaleIdentifier:@"en_US_POSIX"]];
[outputDateFormatter setDateFormat:@"yyyy-MM-dd'T'HH:mm:ssZZZ"];
});
return [outputDateFormatter stringFromDate:date];
}
#pragma mark - number <-> date
- (NSDate*)NSDateFromNSNumber:(NSNumber*)number
{
return [NSDate dateWithTimeIntervalSince1970:number.doubleValue];
}
#pragma mark - string <-> NSTimeZone
- (NSTimeZone *)NSTimeZoneFromNSString:(NSString *)string {
return [NSTimeZone timeZoneWithName:string];
}
- (id)JSONObjectFromNSTimeZone:(NSTimeZone *)timeZone {
return [timeZone name];
}
#pragma mark - hidden transform for empty dictionaries
//https://github.com/jsonmodel/jsonmodel/issues/163
-(NSDictionary*)__NSDictionaryFromNSArray:(NSArray*)array
{
if (array.count==0) return @{};
return (id)array;
}
-(NSMutableDictionary*)__NSMutableDictionaryFromNSArray:(NSArray*)array
{
if (array.count==0) return [[self __NSDictionaryFromNSArray:array] mutableCopy];
return (id)array;
}
@end
Copyright (c) 2012-2016 Marin Todorov and JSONModel contributors
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
# JSONModel - Magical Data Modeling Framework for JSON
JSONModel allows rapid creation of smart data models. You can use it in your
iOS, macOS, watchOS and tvOS apps. Automatic introspection of your model classes
and JSON input drastically reduces the amount of code you have to write.
See [CHANGELOG.md](CHANGELOG.md) for details on changes.
## Installation
### CocoaPods
```ruby
pod 'JSONModel'
```
### Carthage
```ruby
github "jsonmodel/jsonmodel"
```
### Manual
0. download the JSONModel repository
0. copy the JSONModel sub-folder into your Xcode project
0. link your app to SystemConfiguration.framework
## Basic Usage
Consider you have JSON like this:
```json
{ "id": 10, "country": "Germany", "dialCode": 49, "isInEurope": true }
```
- create a JSONModel subclass for your data model
- declare properties in your header file with the name of the JSON keys:
```objc
@interface CountryModel : JSONModel
@property (nonatomic) NSInteger id;
@property (nonatomic) NSString *country;
@property (nonatomic) NSString *dialCode;
@property (nonatomic) BOOL isInEurope;
@end
```
There's no need to do anything in the implementation (`.m`) file.
- initialize your model with data:
```objc
NSError *error;
CountryModel *country = [[CountryModel alloc] initWithString:myJson error:&error];
```
If the validation of the JSON passes. you have all the corresponding properties
in your model populated from the JSON. JSONModel will also try to convert as
much data to the types you expect. In the example above it will:
- convert `id` from string (in the JSON) to an `int` for your class
- copy the `country` value
- convert `dialCode` from a number (in the JSON) to an `NSString` value
- copy the `isInEurope` value
All you have to do is define the properties and their expected types.
## Examples
### Automatic name based mapping
```json
{
"id": 123,
"name": "Product name",
"price": 12.95
}
```
```objc
@interface ProductModel : JSONModel
@property (nonatomic) NSInteger id;
@property (nonatomic) NSString *name;
@property (nonatomic) float price;
@end
```
### Model cascading (models including other models)
```json
{
"orderId": 104,
"totalPrice": 13.45,
"product": {
"id": 123,
"name": "Product name",
"price": 12.95
}
}
```
```objc
@interface ProductModel : JSONModel
@property (nonatomic) NSInteger id;
@property (nonatomic) NSString *name;
@property (nonatomic) float price;
@end
@interface OrderModel : JSONModel
@property (nonatomic) NSInteger orderId;
@property (nonatomic) float totalPrice;
@property (nonatomic) ProductModel *product;
@end
```
### Model collections
```json
{
"orderId": 104,
"totalPrice": 103.45,
"products": [
{
"id": 123,
"name": "Product #1",
"price": 12.95
},
{
"id": 137,
"name": "Product #2",
"price": 82.95
}
]
}
```
```objc
@protocol ProductModel;
@interface ProductModel : JSONModel
@property (nonatomic) NSInteger id;
@property (nonatomic) NSString *name;
@property (nonatomic) float price;
@end
@interface OrderModel : JSONModel
@property (nonatomic) NSInteger orderId;
@property (nonatomic) float totalPrice;
@property (nonatomic) NSArray <ProductModel> *products;
@end
```
Note: the angle brackets after `NSArray` contain a protocol. This is not the
same as the Objective-C generics system. They are not mutually exclusive, but
for JSONModel to work, the protocol must be in place.
Also property can have generics info for compiler
```objc
@interface OrderModel : JSONModel
@property (nonatomic) NSInteger orderId;
@property (nonatomic) float totalPrice;
@property (nonatomic) NSArray<ProductModel *> <ProductModel> *products;
@end
```
### Nested key mapping
```json
{
"orderId": 104,
"orderDetails": {
"name": "Product #1",
"price": {
"usd": 12.95
}
}
}
```
```objc
@interface OrderModel : JSONModel
@property (nonatomic) NSInteger id;
@property (nonatomic) NSString *productName;
@property (nonatomic) float price;
@end
@implementation OrderModel
+ (JSONKeyMapper *)keyMapper
{
return [[JSONKeyMapper alloc] initWithModelToJSONDictionary:@{
@"id": @"orderId",
@"productName": @"orderDetails.name",
@"price": @"orderDetails.price.usd"
}];
}
@end
```
### Map automatically to snake_case
```json
{
"order_id": 104,
"order_product": "Product #1",
"order_price": 12.95
}
```
```objc
@interface OrderModel : JSONModel
@property (nonatomic) NSInteger orderId;
@property (nonatomic) NSString *orderProduct;
@property (nonatomic) float orderPrice;
@end
@implementation OrderModel
+ (JSONKeyMapper *)keyMapper
{
return [JSONKeyMapper mapperForSnakeCase];
}
@end
```
### Optional properties (i.e. can be missing or null)
```json
{
"id": 123,
"name": null,
"price": 12.95
}
```
```objc
@interface ProductModel : JSONModel
@property (nonatomic) NSInteger id;
@property (nonatomic) NSString <Optional> *name;
@property (nonatomic) float price;
@property (nonatomic) NSNumber <Optional> *uuid;
@end
```
### Ignored properties (i.e. JSONModel completely ignores them)
```json
{
"id": 123,
"name": null
}
```
```objc
@interface ProductModel : JSONModel
@property (nonatomic) NSInteger id;
@property (nonatomic) NSString <Ignore> *customProperty;
@end
```
### Making scalar types optional
```json
{
"id": null
}
```
```objc
@interface ProductModel : JSONModel
@property (nonatomic) NSInteger id;
@end
@implementation ProductModel
+ (BOOL)propertyIsOptional:(NSString *)propertyName
{
if ([propertyName isEqualToString:@"id"])
return YES;
return NO;
}
@end
```
### Export model to `NSDictionary` or JSON
```objc
ProductModel *pm = [ProductModel new];
pm.name = @"Some Name";
// convert to dictionary
NSDictionary *dict = [pm toDictionary];
// convert to json
NSString *string = [pm toJSONString];
```
### Custom data transformers
```objc
@interface JSONValueTransformer (CustomTransformer)
@end
@implementation JSONValueTransformer (CustomTransformer)
- (NSDate *)NSDateFromNSString:(NSString *)string
{
NSDateFormatter *formatter = [NSDateFormatter new];
formatter.dateFormat = APIDateFormat;
return [formatter dateFromString:string];
}
- (NSString *)JSONObjectFromNSDate:(NSDate *)date
{
NSDateFormatter *formatter = [NSDateFormatter new];
formatter.dateFormat = APIDateFormat;
return [formatter stringFromDate:date];
}
@end
```
### Custom getters/setters
```objc
@interface ProductModel : JSONModel
@property (nonatomic) NSInteger id;
@property (nonatomic) NSString *name;
@property (nonatomic) float price;
@property (nonatomic) NSLocale *locale;
@end
@implementation ProductModel
- (void)setLocaleWithNSString:(NSString *)string
{
self.locale = [NSLocale localeWithLocaleIdentifier:string];
}
- (void)setLocaleWithNSDictionary:(NSDictionary *)dictionary
{
self.locale = [NSLocale localeWithLocaleIdentifier:dictionary[@"identifier"]];
}
- (NSString *)JSONObjectForLocale
{
return self.locale.localeIdentifier;
}
@end
```
### Custom JSON validation
```objc
@interface ProductModel : JSONModel
@property (nonatomic) NSInteger id;
@property (nonatomic) NSString *name;
@property (nonatomic) float price;
@property (nonatomic) NSLocale *locale;
@property (nonatomic) NSNumber <Ignore> *minNameLength;
@end
@implementation ProductModel
- (BOOL)validate:(NSError **)error
{
if (![super validate:error])
return NO;
if (self.name.length < self.minNameLength.integerValue)
{
*error = [NSError errorWithDomain:@"me.mycompany.com" code:1 userInfo:nil];
return NO;
}
return YES;
}
@end
```
## License
MIT licensed - see [LICENSE](LICENSE) file.
## Contributing
We love pull requests! See [CONTRIBUTING.md](CONTRIBUTING.md) for full details.
//
// Phobos.h
// GengmeiDoctor
// Data Statistic Client For Mars
// Created by Thierry on 16/1/26.
// Copyright © 2016年 wanmeizhensuo. All rights reserved.
//
#import <Foundation/Foundation.h>
#import "PhobosPVProtocol.h"
#import <CoreLocation/CLLocation.h>
NS_ASSUME_NONNULL_BEGIN
typedef NS_ENUM (NSInteger, PhobosSigningType) {
PhobosSigningTypeUndefined = 0,
PhobosSigningTypeAppStore,
PhobosSigningTypeRelease,
PhobosSigningTypeDebug
};
@interface Phobos : NSObject
/**
* @brief 开启Phobos统计,默认以BATCH方式发送log.
*
* @param appName 通常由数据端与客户端一起确认的区分不同app的名字
* @param channelId 发布渠道
*
* @return Phobos实例
*
* @since 0.0.1
*/
+ (Phobos *)clientWithAppName:(NSString *)appName channelId:(NSString *)channelId;
+ (instancetype)sharedClient;
+ (void)setSharedClient:(Phobos *)client;
#pragma mark - SDK配置
// Phobos在处理业务端传递来的参数时会检查是否某个value为空,如果为空会调用这个block以通知业务层,业务层可以上报这个异常,以助解决问题
@property(nonatomic, copy) void (^captureNullExpection) (NSString *eventId, NSDictionary *info);
/**
网络状态 wifi=1, mobile=0, 不连通=-1
*/
@property (nonatomic, copy) NSString *netStatus;
/**
//没有网络连接
public static final String NETWORN_NONE = "none";
//wifi连接
public static final String NETWORN_WIFI = "wifi";
//手机网络数据连接类型
public static final String NETWORN_2G = "2G";
public static final String NETWORN_3G = "3G";
public static final String NETWORN_4G = "4G";
public static final String NETWORN_MOBILE = "other";
*/
@property (nonatomic, copy) NSString *networkStatus;
/**
* @brief 设置是否打印sdk的log信息,默认不开启
*
* @since 0.0.1
*/
@property (assign, nonatomic) BOOL logEnabled;
/**
* @brief 设置当前登录用户的ID,如果没有默认为@""
*
*
* @since 0.0.2
*/
@property (strong, nonatomic) NSString *userId;
/*!
* @author zhaiguojun, 16-05-31
*
* @brief 用户当前的城市id
*
*
* @since 0.2.7
*/
@property (strong, nonatomic) NSString *currentCityId;
@property (strong, nonatomic) CLLocation *gps;
/**
* 记录用户类型
*/
@property (strong, nonatomic) NSMutableDictionary *userType;
/**
数据接收的服务器API
*/
@property (copy, nonatomic) NSString *serverAPI;
/**
当前APP请求接口的 APIHOST(GMServerDomains.apiHost 主要用于flutter AppDelegate 中初始化需要传值
*/
@property (nonatomic, copy) NSString *apiHost;
/**
当前APP请求接口的 cookie(主要用于flutter) 获取到cookie 的时候穿过来 或者cookie 有变化的时候传过来
*/
@property (nonatomic, copy) NSString *cookie;
/**
灰度组, since 7.7.65
*/
@property (nonatomic, copy) NSString *greyType;
/**
包的类型:APPSTORE、RELEASE、DEBUG
*/
@property (nonatomic, assign) PhobosSigningType signingType;
/**
从主项目获取当前显示的controller
*/
@property (nonatomic, copy) UIViewController * (^getTopController) (void);
#pragma mark - 事件采集
/**
* @brief 自定义事件,数量统计.
*
* @param eventId 事件Id
* @attributes 参数
* @sendNow 是否实时发送,默认为NO
*
* @since 0.0.1
*/
+ (void)track:(NSString *)eventId attributes:(NSDictionary *)attributes;
+ (void)track:(NSString *)eventId attributes:(NSDictionary *)attributes sendNow:(BOOL)sendNow;
+ (void)track:(NSString *)eventId;
/**
* @brief 自定义事件,数量统计 7730 精准曝光.
*
* @param eventId 事件Id
* @attributes 参数
* @sendNow 是否实时发送,默认为NO
* @currentAPI 当前传过来的API
* @
*/
+ (void)track:(NSString *)eventId attributes:(NSDictionary *)attributes currentAPI:(NSString *)currentAPI;
+ (void)track:(NSString *)eventId attributes:(NSDictionary *)attributes sendNow:(BOOL)sendNow currentAPI:(NSString *)currentAPI;
+ (void)track:(NSString *)eventId currentAPI:(NSString *)currentAPI;
/**
* @author 翟国钧, 16-02-03 16:02:30
*
* @brief H5调用的埋点方法
*
* @param jsonString h5传过来的参数
*
* @since 0.0.1
*/
+ (void)trackJsEvent:(NSString *)jsonString;
/**
* @brief PV事件开始。当controller viewWillAppear时调用
*/
- (void)onPVStart:(UIResponder<PhobosPVProtocol> *)page;
/**
* @brief PV事件结束。当controller viewWillDisAppear时调用
*/
- (void)onPVEnd:(UIResponder<PhobosPVProtocol> *)page;
/**
* @author 翟国钧, 16-03-08 11:03:45
*
* @brief 有些事件需要模拟pv事件,统一用该方法处理,in out 时间相同.点击一次即触发
*
* @param pageName 控件所在VC的pageName
* @param bid 业务id
* @param referer 上个月面的pagename
*
* @since 5.9.1
*/
- (void)simulativePV:(NSString *)pageName businessId:(NSString *)bid referer:(NSString *)referer;
@end
NS_ASSUME_NONNULL_END
//
// Phobos.m
// GengmeiDoctor
//
// Created by Thierry on 16/1/26.
// Copyright © 2016年 wanmeizhensuo. All rights reserved.
//
#import "Phobos.h"
#import <AdSupport/AdSupport.h>
#import "PhobosUtil.h"
#import "PhobosConfig.h"
#import "UIResponder+PhobosPV.h"
#import "PhobosUtil.h"
#import "PhobosCustomVisibleController.h"
#import "UIDevice+Resolutions.h"
#import <GMCache/GMCache.h>
static Phobos *sharedClient = nil;
static NSString *sdkVersion = @"110";
@interface Phobos ()
@property (strong, nonatomic) UIViewController *visibleController;
@property (strong, nonatomic) NSDateFormatter *dateFormatter;
@property (strong, nonatomic) NSString *appName;
@property (strong, nonatomic) NSString *channelId;
@property (strong, nonatomic) NSString *appVersion;
@property (strong, nonatomic) NSString *sessionId;
/* 每一条埋点数据的物理ID,自增,生命周期和sessionId相同。特别注意:在sessionOver的时候,要把他置为0 */
@property (assign, nonatomic) NSInteger serialId;
// 用来记录除serverAPI以外的API
@property (strong, nonatomic) NSMutableArray *APIArray;
@end
@implementation Phobos
+ (Phobos *)clientWithAppName:(NSString *)appName channelId:(NSString *)channelId{
return [[self alloc] initWithAppName:appName channelId:channelId];
}
+ (Phobos *)sharedClient{
return sharedClient;
}
+ (void)setSharedClient:(Phobos *)client{
sharedClient = client;
}
- (instancetype)initWithAppName:(NSString *)appName channelId:(NSString *)channelId{
self = [super init];
if (self) {
_appName = appName;
_channelId = channelId;
_logEnabled = NO;
_userId = @"";
_netStatus = @"";
_currentCityId = @"";
_serverAPI = @"";
_greyType = @"";
_userType = [[NSMutableDictionary alloc] initWithCapacity:0];
_appVersion = [PhobosUtil getAppVersion];
_APIArray = [NSMutableArray array];
_signingType = PhobosSigningTypeUndefined;
[self setupNotification];
[self handleSessionStart];
[self synchronizePhobosKey];
phobosLog(@"starts to orbit");
}
return self;
}
/**
* disk下的PhobosHaveOpenApp只要存在,就把他取出来放到document下(注:当不支持7.6.16版本的时候,干掉这个方法)
*/
- (void)synchronizePhobosKey {
if ([GMCache fetchObjectAtDiskWithkey:PhobosHaveOpenApp]) {
NSString *prefePath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
prefePath = [NSString stringWithFormat:@"%@/com.tumblr.TMDiskCache.WMCacheShared/%@",prefePath ,PhobosHaveOpenApp];
NSURL *fileUrl = [NSURL fileURLWithPathComponents:@[prefePath]];
BOOL written = [NSKeyedArchiver archiveRootObject:PhobosHaveOpenApp toFile:[fileUrl path]];
}
}
- (void)dealloc{
if (self) {
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
}
- (void)setUserType:(NSMutableDictionary *)userType {
if (userType == nil && userType.count == 0) {
return;
}
NSArray *newKeys = userType.allKeys;
NSArray *oldKeys = _userType.allKeys;
for (NSString *newKey in newKeys) {
if ([oldKeys containsObject:newKey]) {
NSString *newValue = [[userType objectForKey:newKey] stringValue];
NSString *oldValue = [[_userType objectForKey:newKey] stringValue];
if (![newValue isEqualToString:oldValue]) {
[_userType setObject:[userType objectForKey:newKey] forKey:newKey];
}
} else {
[_userType setObject:[userType objectForKey:newKey] forKey:newKey];
}
}
}
/**
在APP启动、从后台到前台的时候需要记录device_opened埋点
*/
- (void)handleEventDeviceOpened{
/** 每次打开APP埋点 **/
NSDictionary *dict = @{@"build_cpu_abi": [PhobosUtil currentDeviceCPUType],
@"cpu_count": [PhobosUtil currentDeviceCPUCount],
@"mac_address": [PhobosUtil getMacAddress],
@"phone_operator": [PhobosUtil getTelephonyInfo],
@"total_memory": [PhobosUtil getTotalMemorySize],
@"run_time": [PhobosUtil deviceRunTime],
@"uuid": [PhobosUtil deviceId],
@"build_version_release": [[UIDevice currentDevice] systemVersion],
@"build_model": [UIDevice deviceVersion],
};
[Phobos track:@"device_opened" attributes:dict sendNow:YES];
/** 第一次打开APP埋点 **/
// 当不再支持7.6.15版本时,只保留
if (![GMCache fetchObjectAtDocumentPathWithkey:PhobosHaveOpenApp]) {
[Phobos track:@"device_activated" attributes:@{} sendNow:YES];
[Phobos track:@"device_activated" attributes:@{} sendNow:NO];
[GMCache storeObjectAtDocumentPathWithkey:PhobosHaveOpenApp object:PhobosHaveOpenApp];
}
}
- (UIViewController *)visibleController {
id target = self.getTopController();
if ([target conformsToProtocol:NSProtocolFromString(@"PhobosCustomVisibleController")]) {
target = [target performSelector:@selector(phobosVisibleController)];
}
return target;
}
#pragma mark - notification handler
/**
* @brief 设置对APP的通知监听
*
* @since 0.0.1
*/
- (void)setupNotification{
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleAppFinishLaunch:) name:UIApplicationDidFinishLaunchingNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleAppInForeground:) name:UIApplicationWillEnterForegroundNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleAppInBackgound:) name:UIApplicationDidEnterBackgroundNotification object:nil];
}
/**
* @brief 每次打开APP或返回前台,即Session开始的时候的处理
* @notification didFinishLaunch和willEnterForeground的时候都需要记录
*
* @since 0.0.1
*/
- (void)handleSessionStart{
_serialId = 0;
_sessionId = [[NSUUID UUID] UUIDString];
[GMCache storeObjectAtDocumentPathWithkey:PhobosBeginTime object:[PhobosUtil currentTime]];
}
/**
* @brief 应用打开时的处理
*
* @since 0.0.1
*/
- (void)handleAppFinishLaunch:(id)sender{
phobosLog(@"handleAppFinishLaunch");
[self handleSessionStart];
[self handleEventDeviceOpened];
}
/**
* @brief 应用进入前台的处理
*
* @since 0.0.1
*/
- (void)handleAppInForeground:(id)sender{
phobosLog(@"handleAppInForeground");
[self handleSessionStart];
[self handleEventDeviceOpened];
[self fetchDataAndSend];
[self handlePVEventAppInForeground];
}
/**
* @brief 应用进入后台的处理
*
* @since 0.0.1
*/
- (void)handleAppInBackgound:(id)sender{
phobosLog(@"handleAppInBackgound");
[self handlePVEventAppInBackgound];
[self handleSessionOver];
[self fetchDataAndSend];
}
/**
* @brief 会话结束时候的处理
*
* @since 0.0.1
*/
- (void)handleSessionOver{
//进入后台的同时,把记录时间同步到服务端,把已使用时间清空
double beginTime = [[GMCache fetchObjectAtDocumentPathWithkey:PhobosBeginTime] doubleValue];
if (beginTime == 0) {
return;
}
NSDate *date = [NSDate date];
double endTime = [date timeIntervalSince1970];
NSString *usedTime = [NSString stringWithFormat:@"%ld",(long)(endTime - beginTime)];
NSDictionary *dict = @{@"duration":usedTime,
@"build_cpu_abi": [PhobosUtil currentDeviceCPUType],
@"cpu_count": [PhobosUtil currentDeviceCPUCount],
@"mac_address": [PhobosUtil getMacAddress],
@"phone_operator": [PhobosUtil getTelephonyInfo],
@"total_memory": [PhobosUtil getTotalMemorySize],
@"run_time": [PhobosUtil deviceRunTime],
@"uuid": [PhobosUtil deviceId],
@"build_version_release": [[UIDevice currentDevice] systemVersion],
@"build_model": [UIDevice deviceVersion],
};
[Phobos track:@"on_app_session_over" attributes:dict];
[GMCache removeObjectAtDocumentPathWithkey:PhobosBeginTime];
//当前session结束之后,把id置为0
_serialId = 0;
}
/**
APP从后台到前台的时候,重新初始化pagename等信息
@author zhaiguojun 16-10-11 in (null)
*/
- (void)handlePVEventAppInForeground {
if (self.visibleController != nil) {
[self onPVStart:self.visibleController];
}
}
/**
APP进到后台的时候,把当前pageview时间结束
@author zhaiguojun 16-10-11 in (null)
*/
- (void)handlePVEventAppInBackgound {
if (self.visibleController != nil) {
[self onPVEnd:self.visibleController];
}
}
#pragma mark - track event handler
+ (void)track:(NSString *)eventId{
[Phobos track:eventId attributes:@{}];
}
+ (void)track:(NSString *)eventId attributes:(NSDictionary *)attributes{
[self track:eventId attributes:attributes sendNow:NO];
NSArray *array = [GMCache fetchObjectAtDocumentPathWithkey:PhobosCacheKey];
//超过一定数量的话,统一发送一次
if (array.count > PhobosShardCount) {
[sharedClient sendArray:array cleanCacheRightNow:YES];
}
}
+ (void)track:(NSString *)eventId attributes:(NSDictionary *)attributes sendNow:(BOOL)sendNow{
NSDictionary *dict = [sharedClient prepareDictionaryForEvent:eventId attributes:attributes];
@try {
NSData *JSON = [PhobosUtil encodeJSON:dict];
if (sendNow) {
NSArray *array = @[dict];
// 实时发送的埋点,不能立即清楚缓存
[sharedClient sendArray:array cleanCacheRightNow:NO];
}else{
[sharedClient save:dict];
}
}
@catch (NSException *exception) {
NSAssert(NO, @"哎呀呀,VALUE不能为NSObject ");
}
}
+ (void)track:(NSString *)eventId attributes:(NSDictionary *)attributes currentAPI:(NSString *)currentAPI {
[self track:eventId attributes:attributes sendNow:NO currentAPI:currentAPI];
NSArray *array = [GMCache fetchObjectAtDocumentPathWithkey:[PhobosUtil MD5String:currentAPI]];
//超过一定数量的话,统一发送一次
if (array.count > PhobosShardCount) {
[sharedClient sendArray:array currentAPI:currentAPI cleanCacheRightNow:YES];
}
}
+ (void)track:(NSString *)eventId attributes:(NSDictionary *)attributes sendNow:(BOOL)sendNow currentAPI:(NSString *)currentAPI {
[sharedClient addNewApi:currentAPI]; // 记录新的API
NSDictionary *dict = [sharedClient prepareDictionaryForEvent:eventId attributes:attributes];
@try {
NSData *JSON = [PhobosUtil encodeJSON:dict];
if (sendNow) {
NSArray *array = @[dict];
// 实时发送的埋点,不能立即清楚缓存
[sharedClient sendArray:array currentAPI:currentAPI cleanCacheRightNow:NO];
}else{
[sharedClient save:dict currentAPI:currentAPI];
}
}
@catch (NSException *exception) {
NSAssert(NO, @"哎呀呀,VALUE不能为NSObject ");
}
}
+ (void)track:(NSString *)eventId currentAPI:(NSString *)currentAPI {
[Phobos track:eventId attributes:@{} currentAPI:currentAPI];
}
+ (void)trackJsEvent:(NSString *)jsonString{
@try {
NSData *data = [jsonString dataUsingEncoding:NSUnicodeStringEncoding];
NSMutableDictionary *dict = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingAllowFragments error:nil];
id pa = dict[@"params"];
NSDictionary *json;
if ([pa isKindOfClass:[NSString class]]) {
NSError *jsonError;
NSData *objectData = [pa dataUsingEncoding:NSUTF8StringEncoding];
json = [NSJSONSerialization JSONObjectWithData:objectData
options:NSJSONReadingMutableContainers
error:&jsonError];
[Phobos track:dict[@"type"] attributes:json];
}else{
[Phobos track:dict[@"type"]];
}
}
@catch (NSException *exception) {
phobosLog(exception);
}
}
#pragma mark - PV
- (void)onPVStart:(UIResponder<PhobosPVProtocol> *)page {
// 必须在此处调用一下referer,因为onControllerStart
[page initReferer];
[page initRefererLink];
[page initReferrerIdIfNil];
[page initReferrerTabName];
page.inTime = [PhobosUtil currentTime];
}
- (void)onPVEnd:(UIResponder<PhobosPVProtocol> *)page {
if (![PhobosUtil isNonEmpty:page.pageName] || !page.needLogPV) {
return;
}
NSMutableDictionary *dict = [[NSMutableDictionary alloc] init];
@try {
[dict setObject:[PhobosUtil currentTime] forKey:@"out"];
[dict setObject:page.inTime?:@"" forKey:@"in"];
[dict setObject:page.pageName forKey:@"page_name"];
[dict setObject:page.businessId?:@"" forKey:@"business_id"];
[dict setObject:page.referer?:@"" forKey:@"referrer"];
[dict setObject:page.referrerLink ? : @[] forKey:@"referrer_link"];
[dict setObject:@(0) forKey:@"fake"];
[dict setObject:page.referrerId ? : @"" forKey:@"referrer_id"];
[dict setObject:page.extraParam ? : @"" forKey:@"extra_param"];
[dict setObject:page.referrerTabName ? : @"" forKey:@"referrer_tab_name"];
[dict setObject:page.isPush.intValue ? @(page.isPush.intValue) : @(0) forKey:@"is_push"];
if (page.inTime.length > 0) {
// 页面显示时间为空时不记录页面pv事件
[Phobos track:@"page_view" attributes:dict];
}
}
@catch (NSException *exception) {
phobosLog(exception);
}
}
- (void)simulativePV:(NSString *)pageName businessId:(NSString *)bid referer:(NSString *)referer{
NSMutableDictionary *dict = [[NSMutableDictionary alloc] init];
@try {
// fake(模拟)的事件,所以in与out一样,就是这么规定的
[dict setObject:[PhobosUtil currentTime] forKey:@"in"];
[dict setObject:[PhobosUtil currentTime] forKey:@"out"];
[dict setObject:pageName?:@"" forKey:@"page_name"];
[dict setObject:bid?:@"" forKey:@"business_id"];
[dict setObject:referer?:@"" forKey:@"referrer"];
[dict setObject:@(1) forKey:@"fake"];
[Phobos track:@"page_view" attributes:dict];
}
@catch (NSException *exception) {
phobosLog(exception);
}
}
#pragma mark - 事件存储、发送
/**
* @brief 将埋点时间封装成词典数据
*
* @since 0.0.1
*/
- (NSDictionary *)prepareDictionaryForEvent:(NSString *)eventId attributes:(NSDictionary *)attributes{
NSArray *referrerLink = sharedClient.visibleController.referrerLink;
if (![eventId isEqualToString:@"page_view"]) {
NSMutableDictionary *attributesParams = [NSMutableDictionary dictionaryWithDictionary:attributes];
[attributesParams setValue:referrerLink ? : @[] forKey:@"referrer_link"];
attributes = attributesParams;
}
[self catchNullForEvent:eventId attributes:attributes];
NSMutableDictionary *dict = [[NSMutableDictionary alloc] init];
@try {
NSString *currentTime = [PhobosUtil currentTime];
NSMutableDictionary *deviceParams = [NSMutableDictionary dictionaryWithObjectsAndKeys:
[[[ASIdentifierManager sharedManager] advertisingIdentifier] UUIDString],@"idfa",
[[[UIDevice currentDevice] identifierForVendor] UUIDString],@"idfv",
[PhobosUtil deviceId],@"device_id",
@"ios",@"device_type",
@"Apple",@"manufacturer",
@(self.gps.coordinate.latitude),@"lat",
@(self.gps.coordinate.longitude),@"lng",
_netStatus,@"is_WiFi",
[PhobosUtil getIPAddress:YES],@"ip",nil];
[deviceParams setValue:_networkStatus forKey:@"net_type"];
[deviceParams setValue:[UIDevice platform] forKey:@"model"];
[deviceParams setValue:[UIDevice currentDevice].systemVersion forKey:@"sys_version"];
NSMutableDictionary *appParams = [NSMutableDictionary dictionaryWithObjectsAndKeys:
_greyType, @"grey_type",
self.appName, @"name",
self.appVersion, @"version",
self.channelId,@"channel",
_userType,@"user_type",
self.currentCityId,@"current_city_id",
@(_serialId++), @"serial_id",nil];
if (_signingType == PhobosSigningTypeDebug || _signingType == PhobosSigningTypeRelease) {
[dict setObject:@(0) forKey:@"is_release"];
}
[dict setObject:eventId forKey:@"type"];
[dict setObject:appParams forKey:@"app"];
[dict setObject:sdkVersion forKey:@"version"];
[dict setObject:deviceParams forKey:@"device"];
[dict setObject:_userId forKey:@"user_id"];
[dict setObject:currentTime forKey:@"create_at"];
[dict setObject:attributes forKey:@"params"];
[dict setObject:_sessionId forKey:@"app_session_id"];
}
@catch (NSException *exception) {
phobosLog(exception);
}
return dict;
}
/**
* @brief 保存数据到缓存层
*
* @param data 数据
*
* @since 0.0.1
*/
- (void)save:(NSDictionary *)data
{
if (_logEnabled) {
phobosLog([NSString stringWithFormat:@"save dictionary: %@",data]);
}
NSMutableArray *dataArray = [GMCache fetchObjectAtDocumentPathWithkey:PhobosCacheKey];
if (dataArray) {
#ifdef POD_CONFIGURATION_APP_STORE
[dataArray addObject:data];
#else
if (data[@"type"] && [data[@"type"] isEqualToString:@"page_view"]) {
NSDictionary *pageParams = data[@"params"];
long long pageInTime = [pageParams[@"in"] longLongValue];
long long pageOutTime = [pageParams[@"out"] longLongValue];
// phobosLog(@"pageInTime------%lld",pageInTime);
// phobosLog(@"pageOutTime------%lld",pageOutTime);
if (pageInTime && pageOutTime) {
__block BOOL checkTimeError;
for (int i = 0; i < dataArray.count; i++) {
NSDictionary *obj = dataArray[i];
if (obj[@"type"] && [obj[@"type"] isEqualToString:@"page_view"]) {
NSDictionary *params = obj[@"params"];
long long objInTime = [params[@"in"] longLongValue];
long long objOutTime = [params[@"out"] longLongValue];
if ( (fabsl(objInTime - pageInTime)) < 1 || (fabsl(objOutTime - pageOutTime)) < 1) {
phobosLog(@"两个埋点inTime 时间差------%f", (fabsl(objInTime - pageInTime)));
phobosLog(@"两个埋点outTime 时间差------%f", (fabsl(objOutTime - pageOutTime)));
checkTimeError = YES;
phobosLog(@"%s____数据校验失败,PV埋点可能重复", __func__);
/*
dispatch_async(dispatch_get_main_queue(), ^{
NSString *stringTitle = [NSString stringWithFormat:@"%@ PV埋点可能重复", data];
UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:stringTitle message:nil delegate:nil cancelButtonTitle:@"确定" otherButtonTitles:nil, nil];
[alertView show];
});*/
break;
}
}
}
}
}
// 不管成功还是失败 给开发提示方便DEBUG
// phobosLog(@"%s____数据校验成功", __func__);
[dataArray addObject:data];
#endif
} else {
dataArray = [NSMutableArray arrayWithObject:data];
}
[GMCache storeObjectAtDocumentPathWithkey:PhobosCacheKey object:dataArray];
}
/**
* @brief 保存数据到缓存层
*
* @param data 数据
*
*/
- (void)save:(NSDictionary *)data currentAPI:(NSString *)currentAPI
{
if (_logEnabled) {
phobosLog([NSString stringWithFormat:@"save dictionary: %@",data]);
}
NSMutableArray *dataArray = [GMCache fetchObjectAtDocumentPathWithkey:[PhobosUtil MD5String:currentAPI]];
if (dataArray) {
[dataArray addObject:data];
}else{
dataArray = [NSMutableArray arrayWithObject:data];
}
[GMCache storeObjectAtDocumentPathWithkey:[PhobosUtil MD5String:currentAPI] object:dataArray];
}
/**
* @brief 从缓存中获取数据,并发送
*
* @since 0.0.1
*/
- (void)fetchDataAndSend{
NSArray *paramsArray = [GMCache fetchObjectAtDocumentPathWithkey:PhobosCacheKey];
if (paramsArray.count>0) {
[self sendArray:paramsArray cleanCacheRightNow:YES];
}
// 其他接口的埋点
for (NSString *newApi in self.APIArray) {
NSArray *paramsArray = [GMCache fetchObjectAtDocumentPathWithkey:[PhobosUtil MD5String:newApi]];
if (paramsArray.count>0) {
[self sendArray:paramsArray currentAPI:newApi cleanCacheRightNow:YES];
}
}
}
/*
从缓存区获取数据,发给服务器,请求成功的时候,把缓存区的数据删除掉
*/
- (void)sendArray {
NSMutableArray *dataArray = [GMCache fetchObjectAtDocumentPathWithkey:PhobosTempCacheKey];
if (_logEnabled) {
NSData *data = [NSJSONSerialization dataWithJSONObject:dataArray options:NSJSONWritingPrettyPrinted error:nil];
NSString *jsonString = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
phobosLog([NSString stringWithFormat:@"array prepare to fly --✈: %@", jsonString]);
}
@try {
NSData *JSON = [PhobosUtil encodeJSON:dataArray];
NSData *compressedData = [PhobosUtil compressData:JSON];
if (compressedData) {
[PhobosUtil sendData:compressedData success:^(NSInteger code) {
phobosLog(@"✈ ---------- ✈ data arrived Mars");
[GMCache removeObjectAtDocumentPathWithkey:PhobosTempCacheKey];
}];
}
}
@catch (NSException *exception) {
phobosLog(exception);
}
}
/*
从缓存区获取数据,发给服务器,请求成功的时候,把缓存区的数据删除掉
*/
- (void)sendArrayWithCurrentAPI:(NSString *)currentAPI {
NSString *PhobosTempCacheKeyStr = [PhobosUtil MD5String:[PhobosUtil MD5String:currentAPI]]; // 两次加密作为key值,和缓存的key值作区分
NSMutableArray *dataArray = [GMCache fetchObjectAtDocumentPathWithkey:PhobosTempCacheKeyStr];
if (_logEnabled) {
NSData *data = [NSJSONSerialization dataWithJSONObject:dataArray options:NSJSONWritingPrettyPrinted error:nil];
NSString *jsonString = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
phobosLog([NSString stringWithFormat:@"array prepare to fly --✈: %@", jsonString]);
}
@try {
NSData *JSON = [PhobosUtil encodeJSON:dataArray];
NSData *compressedData = [PhobosUtil compressData:JSON];
if (compressedData) {
[PhobosUtil sendData:compressedData currentAPI:currentAPI success:^(NSInteger code) {
phobosLog(@"✈ ---------- ✈ data arrived Mars");
[GMCache removeObjectAtDocumentPathWithkey:PhobosTempCacheKeyStr];
}];
}
}
@catch (NSException *exception) {
phobosLog(exception);
}
}
/**
该方法有改动,现在的逻辑是:当前方法只接受发送的请求,然后把数据转存到另一个缓存区,
让sendArray方法负责发送,数据一旦转移到缓存区,就把原有的数据干掉。
@author zhaiguojun 16-10-17 in (null)
@param array 参数
@param clean 是否立即清楚缓存
*/
- (void)sendArray:(NSArray *)array cleanCacheRightNow:(BOOL)clean {
@try {
//1.获取缓存区的数据,把新数据追加进去
NSMutableArray *dataArray = [GMCache fetchObjectAtDocumentPathWithkey:PhobosTempCacheKey];
if (dataArray) {
[dataArray addObjectsFromArray:array];
}else{
dataArray = [NSMutableArray arrayWithArray:array];
}
[GMCache storeObjectAtDocumentPathWithkey:PhobosTempCacheKey object:dataArray];
//2.把缓存区的数据发送给服务器
[self sendArray];
//3.把原有的数据删除
if (clean) {
[GMCache removeObjectAtDocumentPathWithkey:PhobosCacheKey];
}
}
@catch (NSException *exception) {
phobosLog(exception);
}
}
/**
上边方法新加了一个API字段
*/
- (void)sendArray:(NSArray *)array currentAPI:(NSString *)currentAPI cleanCacheRightNow:(BOOL)clean {
@try {
//1.获取缓存区的数据,把新数据追加进去
NSString *PhobosTempCacheKeyStr = [PhobosUtil MD5String:[PhobosUtil MD5String:currentAPI]]; // 两次加密作为key值,和缓存的key值作区分
NSMutableArray *dataArray = [GMCache fetchObjectAtDocumentPathWithkey:PhobosTempCacheKeyStr];
if (dataArray) {
[dataArray addObjectsFromArray:array];
}else{
dataArray = [NSMutableArray arrayWithArray:array];
}
[GMCache storeObjectAtDocumentPathWithkey:PhobosTempCacheKeyStr object:dataArray];
//2.把缓存区的数据发送给服务器
[self sendArrayWithCurrentAPI:currentAPI];
//3.把原有的数据删除
[GMCache removeObjectAtDocumentPathWithkey:[PhobosUtil MD5String:currentAPI]];
}
@catch (NSException *exception) {
phobosLog(exception);
}
}
#pragma mark - helpers
- (void)catchNullForEvent:(NSString *)eventId attributes:(NSDictionary *)attributes {
dispatch_async(dispatch_get_global_queue(0, 0), ^{
@try {
for (NSString *key in attributes.allKeys) {
if ([attributes[key] isMemberOfClass:[NSNull class]]) {
if (self.captureNullExpection) {
self.captureNullExpection(eventId, attributes);
}
break;
}
}
} @catch (NSException *exception) {
}
});
}
- (void)addNewApi:(NSString *)api {
for (NSString *item in self.APIArray) {
if ([api isEqualToString:item]) {
break;
}
[self.APIArray addObject:api];
}
}
@end
//
// PhobosConfig.h
// Pods
//
// Created by Thierry on 16/2/15.
//
//
#ifndef PhobosConfig_h
#define PhobosConfig_h
#ifdef DEBUG
#define phobosLog(...) NSLog(@"[Phobos] %@",__VA_ARGS__)
#else
#define phobosLog(...)
#endif
#define PhobosHaveOpenApp @"PhobosHaveOpenApp" //是否打开过APP
#define PhobosBeginTime @"PhobosBeginTime" //记录APP打开|从后台启动时的时间戳
#define PhobosEndTime @"PhobosEndTime" //记录APP退出|退到后台时的时间戳
#define PhobosCacheKey @"PhobosCacheKey" //存放持久化埋点数据的key
#define PhobosTempCacheKey @"PhobosTempCacheKey" //临时存放待发送埋点数据的key
#define PhobosShardCount 50 //收集数据分段发送的个数
#endif /* PhobosConfig_h */
//
// PhobosCustomVisibleController.h
// Pods
//
// Created by wangyang on 2017/4/27.
//
//
#import <Foundation/Foundation.h>
/**
containerController没有pageName,而是使用不同child的pageName,那么该controller需要挂载该协议以取得正确的visibleController
参考搜索页PV的记录
*/
@protocol PhobosCustomVisibleController <NSObject>
- (UIViewController *)phobosVisibleController;
@end
//
// PhobosPVProtocol.h
// Pods
//
// Created by wangyang on 2017/2/7.
//
//
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@protocol PhobosPVProtocol <NSObject>
/**
* @author 翟国钧, 16-02-24 17:02:22
*
* @brief 埋点的时候,有些埋点都需要业务id,比如DoctorId,针对那些只能在父类中埋点的业务,要在子类中设置当前id,然后在父类中取到
* 在一些详情页的分享和收藏的时候,由于分享、收藏的方法在basewebview里,所以,统一在里面做处理,但是需要在子类中把想要的参数传过去。包括:type(类型)、from(来自哪)、businessId(对应业务id)
* @since 5.9.1
*/
@property (nonatomic, copy, nonnull) NSString *businessId;
/**
* @author 翟国钧 in 16-02-25 19:02:32
*
* 埋点pv事件中当前页面的别名
* @since 5.9.1
*/
@property (nonatomic, copy, nonnull) NSString *pageName;
/**
* @author 翟国钧 in 16-02-25 19:02:32
*
* @brief 获取前一个页面的pageName。
*
* @since 5.9.1
*/
@property (nonatomic, copy, nonnull) NSString *referer;
/**
当前VC.view 显示的时候的时间戳
@author zhaiguojun 16-10-12
*/
@property (nonatomic, copy, nonnull) NSString *inTime;
/**
前一个页面的businessId。该属性有可能为空字符串
*/
@property (nonatomic, copy, nonnull) NSString *referrerId;
/**
controller是否需要记录pv事件,默认为YES。
controller作为childController时,需要设置childController.needLogPV = NO,以防止影响containerController的pv事件
*/
@property(nonatomic, assign) BOOL needLogPV;
/**
需要额外添加参数
*/
@property (nonatomic, copy, nonnull) NSString *extraParam;
/**
首页tab名称
*/
@property (nonatomic, copy) NSString *tabName;
/**
获取上一个页面的tab名称
*/
@property (nonatomic, copy) NSString *referrerTabName;
/**
is_push:判断是否是推送标识
1: 是推送页面跳转
0: 普通页面跳转
*/
@property (nonatomic, copy) NSString *isPush;
/**
获取上一个页面链路的page_name link by 7.20.0 如果有此页面有page_name则添加 , 没有添加""
*/
@property (nonatomic, copy) NSArray *referrerLink;
@end
NS_ASSUME_NONNULL_END
//
// PhobosUtil.h
// Phobos工具类
//
// Created by Thierry on 16/2/15.
//
//
#import <Foundation/Foundation.h>
typedef void (^SendDataSuccessBlock)(NSInteger code);
@interface PhobosUtil : NSObject
/**
* @brief 压缩待上传的数据。经过测试,50条数据压缩大概6毫秒,所以不要放在异步线程中处理
*
* @param originData 压缩前的数据
*
* @return 压缩后的数据
*
* @since 0.0.1
*/
+ (NSData *)compressData:(NSData *)originData;
/**
* @brief 上传数据
*
*
* @since 0.0.1
*/
+ (void)sendData:(NSData *)data success:(SendDataSuccessBlock)success;
/**
* @brief 上传数据
*
*
* @7735 精准曝光
*/
+ (void)sendData:(NSData *)data currentAPI:(NSString *)currentAPI success:(SendDataSuccessBlock)success;
/**
* @brief 获取当前时间的秒数
* @since 0.0.1
*/
+ (NSString *)currentTime;
/**
获取当前时间的毫秒数
@return v7.14.0
*/
+ (NSString *)currentMMTime;
+ (NSString *)getAppVersion;
+ (BOOL)isNonEmpty:(NSString *)string;
+ (NSData *)encodeJSON:(id)obj;
+ (NSString *)deviceId;
/**
* 获取IP地址
*/
+ (NSString *)getIPAddress:(BOOL)preferIPv4;
/**
* 获取cup指令集
*/
+ (NSString *)currentDeviceCPUType;
/***
* CPU总数目
**/
+ (NSString *)currentDeviceCPUCount;
/***
* 网卡地址
**/
+ (NSString *)getMacAddress;
/**
* 获取运营商信息
*/
+ (NSString *)getTelephonyInfo;
/**
* 内存大小,单位:兆
*/
+ (NSString *)getTotalMemorySize;
/**
* 获取手机运行时间,从开机到现在
*/
+ (NSString *)deviceRunTime;
/**
* MD5加密
*/
+ (NSString *)MD5String:(NSString *)str;
@end
//
// PhobosUtil.m
// Pods
//
// Created by Thierry on 16/2/15.
//
//
#import "PhobosUtil.h"
#import <zlib.h>
#import "PhobosConfig.h"
#import <AdSupport/AdSupport.h>
#import <ifaddrs.h>
#import <arpa/inet.h>
#import <net/if.h>
#import <net/if_dl.h>
#import <CoreTelephony/CTCarrier.h>
#import <CoreTelephony/CTTelephonyNetworkInfo.h>
#import <sys/types.h>
#import <sys/sysctl.h>
#import <mach/machine.h>
#import "sys/utsname.h"
#import "Phobos.h"
#import <CommonCrypto/CommonCryptor.h>
#import <CommonCrypto/CommonDigest.h>
#define IOS_CELLULAR @"pdp_ip0"
#define IOS_WIFI @"en0"
#define IOS_VPN @"utun0"
#define IP_ADDR_IPv4 @"ipv4"
#define IP_ADDR_IPv6 @"ipv6"
@implementation PhobosUtil
+ (NSData *)compressData:(NSData *)originData{
if (!originData || [originData length] == 0)
{
return nil;
}
z_stream zlibStreamStruct;
zlibStreamStruct.zalloc = Z_NULL; // Set zalloc, zfree, and opaque to Z_NULL so
zlibStreamStruct.zfree = Z_NULL; // that when we call deflateInit2 they will be
zlibStreamStruct.opaque = Z_NULL; // updated to use default allocation functions.
zlibStreamStruct.total_out = 0; // Total number of output bytes produced so far
zlibStreamStruct.next_in = (Bytef*)[originData bytes]; // Pointer to input bytes
zlibStreamStruct.avail_in = (uInt)[originData length]; // Number of input bytes left to process
int initError = deflateInit2(&zlibStreamStruct, Z_DEFAULT_COMPRESSION, Z_DEFLATED, (15+16), 8, Z_DEFAULT_STRATEGY);
if (initError != Z_OK)
{
NSString *errorMsg = nil;
switch (initError)
{
case Z_STREAM_ERROR:
errorMsg = @"Invalid parameter passed in to function.";
break;
case Z_MEM_ERROR:
errorMsg = @"Insufficient memory.";
break;
case Z_VERSION_ERROR:
errorMsg = @"The version of zlib.h and the version of the library linked do not match.";
break;
default:
errorMsg = @"Unknown error code.";
break;
}
return nil;
}
NSMutableData *compressedData = [NSMutableData dataWithLength:[originData length] * 1.01 + 12];
NSInteger deflateStatus;
do
{
zlibStreamStruct.next_out = [compressedData mutableBytes] + zlibStreamStruct.total_out;
zlibStreamStruct.avail_out = (uInt)([compressedData length] - zlibStreamStruct.total_out);
deflateStatus = deflate(&zlibStreamStruct, Z_FINISH);
} while ( deflateStatus == Z_OK );
// Check for zlib error and convert code to usable error message if appropriate
if (deflateStatus != Z_STREAM_END)
{
NSString *errorMsg = nil;
switch (deflateStatus)
{
case Z_ERRNO:
errorMsg = @"Error occured while reading file.";
break;
case Z_STREAM_ERROR:
errorMsg = @"The stream state was inconsistent (e.g., next_in or next_out was NULL).";
break;
case Z_DATA_ERROR:
errorMsg = @"The deflate data was invalid or incomplete.";
break;
case Z_MEM_ERROR:
errorMsg = @"Memory could not be allocated for processing.";
break;
case Z_BUF_ERROR:
errorMsg = @"Ran out of output buffer for writing compressed bytes.";
break;
case Z_VERSION_ERROR:
errorMsg = @"The version of zlib.h and the version of the library linked do not match.";
break;
default:
errorMsg = @"Unknown error code.";
break;
}
deflateEnd(&zlibStreamStruct);
return nil;
}
deflateEnd(&zlibStreamStruct);
[compressedData setLength: zlibStreamStruct.total_out];
phobosLog([NSString stringWithFormat:@"Compressed file from %lu B to %lu B", [originData length], [compressedData length]]);
return compressedData;
}
+ (void)sendData:(NSData *)data success:(SendDataSuccessBlock)success {
@try {
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:[NSURL URLWithString:[Phobos sharedClient].serverAPI]];
[request setValue:@"gzip" forHTTPHeaderField:@"Content-Encoding"];
request.HTTPBody = data;
request.HTTPMethod = @"POST";
// sendAsynchronousRequest 在iOS9以后y被废除了
NSURLSession *session = [NSURLSession sharedSession];
NSURLSessionDataTask *dataTask = [session dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
if (!error) {
//没有错误,返回正确;
if (success) {
success(200);
}
}
}];
[dataTask resume];
}
@catch (NSException *exception) {
phobosLog(exception);
}
}
+ (void)sendData:(NSData *)data currentAPI:(NSString *)currentAPI success:(SendDataSuccessBlock)success {
@try {
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:[NSURL URLWithString:currentAPI]];
[request setValue:@"gzip" forHTTPHeaderField:@"Content-Encoding"];
request.HTTPBody = data;
request.HTTPMethod = @"POST";
// sendAsynchronousRequest 在iOS9以后y被废除了
NSURLSession *session = [NSURLSession sharedSession];
NSURLSessionDataTask *dataTask = [session dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
if (!error) {
//没有错误,返回正确;
if (success) {
success(200);
}
}
}];
[dataTask resume];
}
@catch (NSException *exception) {
phobosLog(exception);
}
}
/**
* @brief 将对象转成JSON格式数据
*
* @since 0.0.1
*/
+ (NSData *)encodeJSON:(id)obj {
NSData *data = [NSJSONSerialization dataWithJSONObject:obj options:0 error:nil];
return data;
}
/**
* @brief 获取当前时间的秒数
* @since 0.0.1
*/
+ (NSString *)currentTime {
NSDate *date = [NSDate date];
NSTimeInterval interval = [date timeIntervalSince1970];
NSString *timeIntervalStr = [NSString stringWithFormat:@"%ld",(long)interval];
return timeIntervalStr;
}
/**
获取当前时间的毫秒数
@return v7.14.0
*/
+ (NSString *)currentMMTime {
NSDate *date = [NSDate date];
NSTimeInterval interval = [date timeIntervalSince1970];
NSString *timeIntervalStr = [NSString stringWithFormat:@"%ld",(long)(interval*1000)];
return timeIntervalStr;
}
/**
* @brief 获取当前APP的版本号
*
* @since 0.0.1
*/
+ (NSString *)getAppVersion {
NSString *buildVersion = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleShortVersionString"];
if (buildVersion) {
return buildVersion;
}else{
return @"";
}
}
+ (BOOL)isNonEmpty:(NSString *)string {
if (!string) {
return NO;
}
NSMutableCharacterSet *emptyStringSet = [[NSMutableCharacterSet alloc] init];
[emptyStringSet formUnionWithCharacterSet: [NSCharacterSet whitespaceAndNewlineCharacterSet]];
[emptyStringSet formUnionWithCharacterSet: [NSCharacterSet characterSetWithCharactersInString: @" "]];
if ([string length] == 0) {
return NO;
}
NSString* str = [string stringByTrimmingCharactersInSet:emptyStringSet];
return [str length] > 0;
}
+ (NSString *)deviceId {
NSString *idfa = [[[ASIdentifierManager sharedManager] advertisingIdentifier] UUIDString];
NSString *idfv = [[[UIDevice currentDevice] identifierForVendor] UUIDString];
if (idfa != nil && ![idfa isEqualToString:@"00000000-0000-0000-0000-000000000000"]) {
return idfa;
}
return idfv;
}
+ (NSString *)getIPAddress:(BOOL)preferIPv4 {
NSArray *searchArray = preferIPv4 ?
@[ IOS_VPN @"/" IP_ADDR_IPv4, IOS_VPN @"/" IP_ADDR_IPv6, IOS_WIFI @"/" IP_ADDR_IPv4, IOS_WIFI @"/" IP_ADDR_IPv6, IOS_CELLULAR @"/" IP_ADDR_IPv4, IOS_CELLULAR @"/" IP_ADDR_IPv6 ] :
@[ IOS_VPN @"/" IP_ADDR_IPv6, IOS_VPN @"/" IP_ADDR_IPv4, IOS_WIFI @"/" IP_ADDR_IPv6, IOS_WIFI @"/" IP_ADDR_IPv4, IOS_CELLULAR @"/" IP_ADDR_IPv6, IOS_CELLULAR @"/" IP_ADDR_IPv4 ] ;
NSDictionary *addresses = [self getIPAddresses];
__block NSString *address;
[searchArray enumerateObjectsUsingBlock:^(NSString *key, NSUInteger idx, BOOL *stop)
{
address = addresses[key];
//筛选出IP地址格式
if([self isValidatIP:address]) *stop = YES;
} ];
return address ? address : @"0.0.0.0";
}
+ (BOOL)isValidatIP:(NSString *)ipAddress {
if (ipAddress.length == 0) {
return NO;
}
NSString *urlRegEx = @"^([01]?\\d\\d?|2[0-4]\\d|25[0-5])\\."
"([01]?\\d\\d?|2[0-4]\\d|25[0-5])\\."
"([01]?\\d\\d?|2[0-4]\\d|25[0-5])\\."
"([01]?\\d\\d?|2[0-4]\\d|25[0-5])$";
NSError *error;
NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:urlRegEx options:0 error:&error];
if (regex != nil) {
NSTextCheckingResult *firstMatch=[regex firstMatchInString:ipAddress options:0 range:NSMakeRange(0, [ipAddress length])];
if (firstMatch) {
return YES;
}
}
return NO;
}
+ (NSDictionary *)getIPAddresses {
NSMutableDictionary *addresses = [NSMutableDictionary dictionaryWithCapacity:8];
// retrieve the current interfaces - returns 0 on success
struct ifaddrs *interfaces;
if(!getifaddrs(&interfaces)) {
// Loop through linked list of interfaces
struct ifaddrs *interface;
for(interface=interfaces; interface; interface=interface->ifa_next) {
if(!(interface->ifa_flags & IFF_UP) /* || (interface->ifa_flags & IFF_LOOPBACK) */ ) {
continue; // deeply nested code harder to read
}
const struct sockaddr_in *addr = (const struct sockaddr_in*)interface->ifa_addr;
char addrBuf[ MAX(INET_ADDRSTRLEN, INET6_ADDRSTRLEN) ];
if(addr && (addr->sin_family==AF_INET || addr->sin_family==AF_INET6)) {
NSString *name = [NSString stringWithUTF8String:interface->ifa_name];
NSString *type;
if(addr->sin_family == AF_INET) {
if(inet_ntop(AF_INET, &addr->sin_addr, addrBuf, INET_ADDRSTRLEN)) {
type = IP_ADDR_IPv4;
}
} else {
const struct sockaddr_in6 *addr6 = (const struct sockaddr_in6*)interface->ifa_addr;
if(inet_ntop(AF_INET6, &addr6->sin6_addr, addrBuf, INET6_ADDRSTRLEN)) {
type = IP_ADDR_IPv6;
}
}
if(type) {
NSString *key = [NSString stringWithFormat:@"%@/%@", name, type];
addresses[key] = [NSString stringWithUTF8String:addrBuf];
}
}
}
// Free memory
freeifaddrs(interfaces);
}
return [addresses count] ? addresses : nil;
}
/**
* 这里就叫做CPU指令集吧,目前主要采集这四种,armv6、armv7、armv7s、arm64
*/
+ (NSString *)currentDeviceCPUType {
NSMutableString *cpuType = [[NSMutableString alloc] init];
size_t size;
cpu_type_t type;
cpu_subtype_t subtype;
size = sizeof(type);
sysctlbyname("hw.cputype", &type, &size, NULL, 0);
size = sizeof(subtype);
sysctlbyname("hw.cpusubtype", &subtype, &size, NULL, 0);
if (type == CPU_TYPE_X86) {
[cpuType appendString:@"x86"];
} else if (type == CPU_TYPE_ARM) {
[cpuType appendString:@"ARM"];
switch(subtype) {
case CPU_SUBTYPE_ARM_V6:
[cpuType appendString:@"V6"];
break;
case CPU_SUBTYPE_ARM_V7:
[cpuType appendString:@"V7"];
break;
case CPU_SUBTYPE_ARM_V7S:
[cpuType appendString:@"V7s"];
break;
}
} else if (type == CPU_TYPE_ARM64) {
[cpuType appendString:@"ARM64"];
} else {
// 暂时不采集其他的
[cpuType appendString:@"nuknow type"];
}
return cpuType;
}
/***
* CPU总数目
**/
+ (NSString *)currentDeviceCPUCount {
NSInteger count = [NSProcessInfo processInfo].activeProcessorCount;
return [NSString stringWithFormat:@"%ld", (long)count];
}
/***
* 网卡地址
**/
+ (NSString *)getMacAddress {
int mib[6];
size_t len;
char *buf;
unsigned char *ptr;
struct if_msghdr *ifm;
struct sockaddr_dl *sdl;
mib[0] = CTL_NET;
mib[1] = AF_ROUTE;
mib[2] = 0;
mib[3] = AF_LINK;
mib[4] = NET_RT_IFLIST;
if ((mib[5] = if_nametoindex("en0")) == 0) {
printf("Error: if_nametoindex error/n");
return @"";
}
if (sysctl(mib, 6, NULL, &len, NULL, 0) < 0) {
printf("Error: sysctl, take 1/n");
return @"";
}
if ((buf = malloc(len)) == NULL) {
printf("Could not allocate memory. error!/n");
return @"";
}
if (sysctl(mib, 6, buf, &len, NULL, 0) < 0) {
printf("Error: sysctl, take 2");
return @"";
}
ifm = (struct if_msghdr *)buf;
sdl = (struct sockaddr_dl *)(ifm + 1);
ptr = (unsigned char *)LLADDR(sdl);
NSString *outstring = [NSString stringWithFormat:@"%02x%02x%02x%02x%02x%02x", *ptr, *(ptr+1), *(ptr+2), *(ptr+3), *(ptr+4), *(ptr+5)];
free(buf);
NSString *result = [outstring uppercaseString];
return result ?: @"";
}
/**
* 获取运营商信息
*/
+ (NSString *)getTelephonyInfo {
CTTelephonyNetworkInfo *info = [[CTTelephonyNetworkInfo alloc] init];
CTCarrier *carrier = [info subscriberCellularProvider];
if (carrier == nil) {
return @"";
}
return [carrier carrierName] != nil ? [carrier carrierName] : @"";
}
/**
* 内存大小,单位:兆
*/
+ (NSString *)getTotalMemorySize {
unsigned long long resultSize = [NSProcessInfo processInfo].physicalMemory/1024/1024;
return [NSString stringWithFormat:@"%llu", resultSize];
}
/**
* 获取手机运行时间,从开机到现在
*/
+ (NSString *)deviceRunTime {
struct timeval boottime;
int mib[2] = {CTL_KERN, KERN_BOOTTIME};
size_t size = sizeof(boottime);
time_t now;
time_t uptime = -1;//默认为没有使用吧
(void)time(&now);
if (sysctl(mib, 2, &boottime, &size, NULL, 0) != -1 && boottime.tv_sec != 0) {
uptime = now - boottime.tv_sec;
}
return [NSString stringWithFormat:@"%ld", uptime];
}
+ (NSString *)MD5String:(NSString *)str
{
if(str == nil || [str length] == 0)
return nil;
const char *value = [str UTF8String];
unsigned char outputBuffer[CC_MD5_DIGEST_LENGTH];
CC_MD5(value, (uint32_t)strlen(value), outputBuffer);
NSMutableString *outputString = [[NSMutableString alloc] initWithCapacity:CC_MD5_DIGEST_LENGTH * 2];
for(NSInteger count = 0; count < CC_MD5_DIGEST_LENGTH; count++){
[outputString appendFormat:@"%02x",outputBuffer[count]];
}
return outputString;
}
@end
//
// UIResponder+PhobosPV.h
// Pods
//
// Created by wangyang on 2017/2/7.
//
//
#import <UIKit/UIKit.h>
#import "PhobosPVProtocol.h"
@interface UIResponder (PhobosPV) <PhobosPVProtocol>
/**
只适用于controller自动初始化referer
*/
- (void)initReferer;
/**
// // 适用于链路中页面浏览事件的统计 添加 refererLink 参数
*/
- (void)initRefererLink;
/**
此方法在onPvStart时调用,如果发现已经有值了,不会给referrerId再次赋值
*/
- (void)initReferrerIdIfNil;
/**
此方法在onPvStart时调用,给referrerTabName赋值
*/
- (void)initReferrerTabName;
@end
//
// UIResponder+PhobosPV.m
// Pods
//
// Created by wangyang on 2017/2/7.
//
//
#import "UIResponder+PhobosPV.h"
#import <objc/runtime.h>
#import "PhobosUtil.h"
@implementation UIResponder (PhobosPV)
/**
* @author 翟国钧, 16-03-01 15:03:24
*
* @brief 取当前导航栈中当前VC的上级VC,如果该VC存在,就获取他的pageName
*
* @since 5.9.1
*/
- (void)initReferer {
// 只有不为空,且是controller的情况下才自动获取
if ([self.referer isEqualToString:@""] && [self isKindOfClass:[UIViewController class]]) {
// 分present与navigation两种情况
UIViewController *me = (UIViewController *)self;
if (me.presentingViewController != nil) {
// app全局只有一个navigation,发现此时用navigation.topViewController presentViewController时,最终使用的是navigation弹出的
// 所以此处要判断,如果是navigation弹出,最后还是要定位到topViewController
if ([me.presentingViewController isKindOfClass:[UINavigationController class]]) {
UIViewController *top = ((UINavigationController *)me.presentingViewController).topViewController;
objc_setAssociatedObject(self, @selector(referer), top.pageName, OBJC_ASSOCIATION_COPY);
} else {
objc_setAssociatedObject(self, @selector(referer), me.presentingViewController.pageName, OBJC_ASSOCIATION_COPY);
}
} else {
NSArray *navigationPool = ((UIViewController *)self).navigationController.viewControllers;
NSInteger refererIndex = navigationPool.count - 2;
if (refererIndex < 0 ) {
return ;
}
UIViewController *controller = navigationPool[refererIndex];
objc_setAssociatedObject(self, @selector(referer), controller.pageName, OBJC_ASSOCIATION_COPY);
}
}
}
// 适用于链路中页面浏览事件的统计 添加 refererLink 参数
- (void)initRefererLink {
if ([self isKindOfClass:[UIViewController class]]) {
// 分present与navigation两种情况
UIViewController *me = (UIViewController *)self;
if (me.isPush.intValue) { // 如果是推送进来的,结果页面的referrerLink 为空数组, 此处添加过滤。
objc_setAssociatedObject(self, @selector(referrerLink), @[], OBJC_ASSOCIATION_RETAIN_NONATOMIC);
return;
}
if (me.presentingViewController != nil) {
// app全局只有一个navigation,发现此时用navigation.topViewController presentViewController时,最终使用的是navigation弹出的
// 所以此处要判断,如果是navigation弹出,最后还是要定位到topViewController
if ([me.presentingViewController isKindOfClass:[UINavigationController class]]) {
UIViewController *top = ((UINavigationController *)me.presentingViewController).topViewController;
NSMutableArray *tempLink = [NSMutableArray arrayWithArray:top.referrerLink];
[tempLink addObject:top.pageName];
objc_setAssociatedObject(self, @selector(referrerLink), tempLink, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
} else {
NSMutableArray *tempLink = [NSMutableArray arrayWithArray:me.presentingViewController.referrerLink];
[tempLink addObject:me.presentingViewController.pageName];
objc_setAssociatedObject(self, @selector(referrerLink), tempLink, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
} else {
NSArray *navigationPool = ((UIViewController *)self).navigationController.viewControllers;
NSInteger refererIndex = navigationPool.count - 2;
if (refererIndex < 0 ) {
return ;
}
UIViewController *controller = navigationPool[refererIndex];
NSMutableArray *tempLink = [NSMutableArray arrayWithArray:controller.referrerLink];
[tempLink addObject:controller.pageName];
objc_setAssociatedObject(self, @selector(referrerLink), tempLink, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
}
}
- (void)setReferrerLink:(NSArray *)referrerLink {
objc_setAssociatedObject(self, @selector(referrerLink), referrerLink, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
-(NSArray *)referrerLink {
NSArray *referrerLink = objc_getAssociatedObject(self, @selector(referrerLink));
return referrerLink;
}
/**
此方法在onPvStart时调用,给referrerTabName赋值
*/
- (void)initReferrerTabName {
// 只有是controller的情况下才自动获取
if ([self isKindOfClass:[UIViewController class]]) {
// 分present与navigation两种情况
UIViewController *me = (UIViewController *)self;
if (me.presentingViewController != nil) {
// app全局只有一个navigation,发现此时用navigation.topViewController presentViewController时,最终使用的是navigation弹出的
// 所以此处要判断,如果是navigation弹出,最后还是要定位到topViewController
if ([me.presentingViewController isKindOfClass:[UINavigationController class]]) {
UIViewController *top = ((UINavigationController *)me.presentingViewController).topViewController;
objc_setAssociatedObject(self, @selector(referrerTabName), top.tabName, OBJC_ASSOCIATION_COPY);
} else {
objc_setAssociatedObject(self, @selector(referrerTabName), me.presentingViewController.tabName, OBJC_ASSOCIATION_COPY);
}
} else {
NSArray *navigationPool = ((UIViewController *)self).navigationController.viewControllers;
NSInteger refererIndex = navigationPool.count - 2;
if (refererIndex < 0 ) {
return ;
}
UIViewController *controller = navigationPool[refererIndex];
objc_setAssociatedObject(self, @selector(referrerTabName), controller.tabName, OBJC_ASSOCIATION_COPY);
}
}
}
- (void)setReferer:(NSString *)referer {
objc_setAssociatedObject(self, @selector(referer), referer, OBJC_ASSOCIATION_COPY);
}
- (NSString *)referer
{
NSString *referer = objc_getAssociatedObject(self, @selector(referer));
return referer == nil ? @"" : referer;
}
- (NSString *)pageName {
NSString *name = objc_getAssociatedObject(self, @selector(pageName));
return name == nil ? @"" : name;
}
- (void)setPageName:(NSString *)pageName {
objc_setAssociatedObject(self, @selector(pageName), pageName, OBJC_ASSOCIATION_COPY);
}
- (NSString *)businessId {
NSString *businessId = objc_getAssociatedObject(self, @selector(businessId));
return businessId == nil ? @"" : businessId;
}
- (void)setBusinessId:(NSString *)businessId {
objc_setAssociatedObject(self, @selector(businessId), businessId, OBJC_ASSOCIATION_COPY);
}
- (void)setInTime:(NSString *)inTime {
objc_setAssociatedObject(self, @selector(inTime), inTime, OBJC_ASSOCIATION_COPY);
}
/**
这个地方inTime的值为nil的情况不做考虑,因为Phobos不会发送。inTime必须在controller viewWillAppear时赋值
*/
- (NSString *)inTime {
NSString *inTime = objc_getAssociatedObject(self, @selector(inTime));
return inTime;
}
- (void)initReferrerIdIfNil {
// 只有不为空,且是controller的情况下才自动获取
if ([self.referrerId isEqualToString:@""] && [self isKindOfClass:[UIViewController class]]) {
// 分present与navigation两种情况
UIViewController *me = (UIViewController *)self;
if (me.presentingViewController != nil) {
objc_setAssociatedObject(self, @selector(referrerId), me.presentingViewController.businessId, OBJC_ASSOCIATION_COPY);
} else {
NSArray *navigationPool = ((UIViewController *)self).navigationController.viewControllers;
NSInteger refererIdIndex = navigationPool.count - 2;
if (refererIdIndex < 0) {
objc_setAssociatedObject(self, @selector(referrerId), @"", OBJC_ASSOCIATION_COPY);
} else {
UIViewController *controller = navigationPool[refererIdIndex];
NSString *preBusinessId = controller.businessId;
objc_setAssociatedObject(self, @selector(referrerId), preBusinessId, OBJC_ASSOCIATION_COPY);
}
}
}
}
- (void)setReferrerId:(NSString *)referrerId {
objc_setAssociatedObject(self, @selector(referrerId), referrerId, OBJC_ASSOCIATION_COPY);
}
- (NSString *)referrerId {
NSString *referrerId = objc_getAssociatedObject(self, @selector(referrerId));
return referrerId == nil ? @"" : referrerId;
}
- (BOOL)needLogPV {
NSNumber *needLogPV = objc_getAssociatedObject(self, @selector(needLogPV));
return needLogPV == nil ? YES : needLogPV.boolValue;
}
- (void)setNeedLogPV:(BOOL)needLogPV {
objc_setAssociatedObject(self, @selector(needLogPV), @(needLogPV), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (void)setExtraParam:(NSString *)extraParam {
objc_setAssociatedObject(self, @selector(extraParam), extraParam, OBJC_ASSOCIATION_COPY);
}
- (NSString *)extraParam {
NSString *extraParam = objc_getAssociatedObject(self, @selector(extraParam));
return extraParam == nil ? @"" : extraParam;
}
- (void)setTabName:(NSString *)tabName {
objc_setAssociatedObject(self, @selector(tabName), tabName, OBJC_ASSOCIATION_COPY);
}
- (NSString *)tabName {
NSString *tabName = objc_getAssociatedObject(self, @selector(tabName));
return tabName == nil ? @"" : tabName;
}
- (void)setReferrerTabName:(NSString *)referrerTabName {
objc_setAssociatedObject(self, @selector(referrerTabName), referrerTabName, OBJC_ASSOCIATION_COPY);
}
- (NSString *)referrerTabName {
NSString *referrerTabName = objc_getAssociatedObject(self, @selector(referrerTabName));
return referrerTabName == nil ? @"" : referrerTabName;
}
- (void)setIsPush:(NSString *)isPush {
objc_setAssociatedObject(self, @selector(isPush), isPush, OBJC_ASSOCIATION_COPY);
}
- (NSString *)isPush {
NSString *isPush = objc_getAssociatedObject(self, @selector(isPush));
return isPush == nil ? @"" : isPush;
}
@end
Copyright (c) 2016 licong <1240690490@qq.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
# GMPhobos
## About The Name
The name is from the Data Center who named Mars
Phobos (systematic designation: Mars I) is the larger and innermost of the two natural satellites of Mars, the other being Deimos. Both moons were discovered in 1877 by American astronomer Asaph Hall.
Phobos is a small, irregularly shaped object with a mean radius of 11 km (7 mi),[1] and is seven times more massive than Deimos, Mars's outer moon.
Phobos is named after the Greek god Phobos, a son of Ares (Mars) and Aphrodite (Venus) which was the personification of Horror.
The name "Phobos" is pronounced /ˈfoʊbəs/ foh-bəs, or like the Greek Φόβος.
## Requirements
Require GMCache and libz
## Installation
GMPhobos is available through [CocoaPods](http://cocoapods.org). To install
it, simply add the following line to your Podfile:
```ruby
pod "GMPhobos"
```
Alternatively, you can install manually.
1 Get the code:
```ruby
git clone git@git.gengmei.cc:gengmeiios/GMPhobos.git
```
2 Drag the Phobos subfolder to your project. Check both “copy items into destination group’s folder” and your target.
## Configuration
While you are free to initialize as many instances of Phobos as is appropriate for your application, there is a shared singleton instance that is globally available.
This singleton instance is often configured in your app delegate’s application:didFinishLaunchingWithOptions: method:
```objc
#import "Phobos.h"
- (BOOL)application:(UIApplication *)application
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
Phobos *client = [Phobos clientWithAppName:@"appname" channelId:@"appchannel"];
[Phobos setSharedClient:client];
/* ... */
return YES;
}
```
If you would like to specify a user, you can set the userId that is used with the -[setUserId:] Instance method.
```objc
WMUser *user = [[WMLoginManager shareInstance] user];
[client setUserId:user.userId];
```
If you would like to use Phobos, you must set the currentCityId that is used with the -[setCurrentCityId:] Instance method.
```objc
WMCityObject *city = [GMCache fetchObjectAtDocumentPathWithkey:kCurrentCity];
if (city) {
[client setCurrentCityId:city.id];
}
```
## Sending Messages
Sending a basic message:
```objc
[Phobos track:@"event_id"];
```
Sending a message with another attributes:
```objc
[Phobos track:@"event_id" attributes:@{}];
```
Sending a message with another attributes and send immediately:
```objc
[Phobos track:@"event_id" attributes:@{} sendNow:YES];
```
## Author
Guojun Zhai and Thierry Xing
## License
GMPhobos is available under the MIT license. See the LICENSE file for more info.
../../../GMCache/GMCache/Classes/GMCache.h
\ No newline at end of file
../../../GMJSONModel/JSONModel/JSONModelNetworking/JSONAPI.h
\ No newline at end of file
../../../GMJSONModel/JSONModel/JSONModelNetworking/JSONHTTPClient.h
\ No newline at end of file
../../../GMJSONModel/JSONModel/JSONModelTransformations/JSONKeyMapper.h
\ No newline at end of file
../../../GMJSONModel/JSONModel/JSONModelNetworking/JSONModel+networking.h
\ No newline at end of file
../../../GMJSONModel/JSONModel/JSONModel/JSONModel.h
\ No newline at end of file
../../../GMJSONModel/JSONModel/JSONModel/JSONModelClassProperty.h
\ No newline at end of file
../../../GMJSONModel/JSONModel/JSONModel/JSONModelError.h
\ No newline at end of file
../../../GMJSONModel/JSONModel/JSONModelLib.h
\ No newline at end of file
../../../GMJSONModel/JSONModel/JSONModelTransformations/JSONValueTransformer.h
\ No newline at end of file
../../../GMPhobos/GMPhobos/Classes/Phobos.h
\ No newline at end of file
../../../GMPhobos/GMPhobos/Classes/PhobosConfig.h
\ No newline at end of file
../../../GMPhobos/GMPhobos/Classes/PhobosCustomVisibleController.h
\ No newline at end of file
../../../GMPhobos/GMPhobos/Classes/PhobosPVProtocol.h
\ No newline at end of file
../../../GMPhobos/GMPhobos/Classes/PhobosUtil.h
\ No newline at end of file
../../../GMPhobos/GMPhobos/Classes/UIResponder+PhobosPV.h
\ No newline at end of file
../../../../../GMShareSDK/Classes/Share/ALShareView/ALPlatformView.h
\ No newline at end of file
../../../../../GMShareSDK/Classes/Share/ALShareView/ALShareButton.h
\ No newline at end of file
../../../../../GMShareSDK/Classes/Share/ALShareView/ALShareTopicCardView.h
\ No newline at end of file
../../../../../GMShareSDK/Classes/Share/GMDiaryShareInfoView.h
\ No newline at end of file
../../../../../GMShareSDK/Classes/Share/GMDiaryShareView.h
\ No newline at end of file
../../../../../GMShareSDK/Classes/Share/GMShareBaseObject.h
\ No newline at end of file
../../../../../GMShareSDK/Classes/Share/GMShareCell.h
\ No newline at end of file
../../../../../GMShareSDK/Classes/Share/GMShareFlowLayout.h
\ No newline at end of file
../../../../../GMShareSDK/Classes/Share/GMShareTopView.h
\ No newline at end of file
../../../../../GMShareSDK/Classes/Share/GMShareVideoAlertView.h
\ No newline at end of file
../../../../../GMShareSDK/Classes/Share/GMShareView.h
\ No newline at end of file
../../../../../GMShareSDK/Classes/Share/GMWeixinShareView.h
\ No newline at end of file
../../../../../GMShareSDK/Classes/Share/WMShareObject.h
\ No newline at end of file
../../../MBProgressHUD/MBProgressHUD.h
\ No newline at end of file
../../../TMCache/TMCache/TMCache.h
\ No newline at end of file
../../../TMCache/TMCache/TMCacheBackgroundTaskManager.h
\ No newline at end of file
../../../TMCache/TMCache/TMDiskCache.h
\ No newline at end of file
../../../TMCache/TMCache/TMMemoryCache.h
\ No newline at end of file
../../../GMCache/GMCache/Classes/GMCache.h
\ No newline at end of file
../../../GMJSONModel/JSONModel/JSONModelNetworking/JSONAPI.h
\ No newline at end of file
../../../GMJSONModel/JSONModel/JSONModelNetworking/JSONHTTPClient.h
\ No newline at end of file
../../../GMJSONModel/JSONModel/JSONModelTransformations/JSONKeyMapper.h
\ No newline at end of file
../../../GMJSONModel/JSONModel/JSONModelNetworking/JSONModel+networking.h
\ No newline at end of file
../../../GMJSONModel/JSONModel/JSONModel/JSONModel.h
\ No newline at end of file
../../../GMJSONModel/JSONModel/JSONModel/JSONModelClassProperty.h
\ No newline at end of file
../../../GMJSONModel/JSONModel/JSONModel/JSONModelError.h
\ No newline at end of file
../../../GMJSONModel/JSONModel/JSONModelLib.h
\ No newline at end of file
../../../GMJSONModel/JSONModel/JSONModelTransformations/JSONValueTransformer.h
\ No newline at end of file
../../../GMPhobos/GMPhobos/Classes/Phobos.h
\ No newline at end of file
../../../GMPhobos/GMPhobos/Classes/PhobosConfig.h
\ No newline at end of file
../../../GMPhobos/GMPhobos/Classes/PhobosCustomVisibleController.h
\ No newline at end of file
../../../GMPhobos/GMPhobos/Classes/PhobosPVProtocol.h
\ No newline at end of file
../../../GMPhobos/GMPhobos/Classes/PhobosUtil.h
\ No newline at end of file
../../../GMPhobos/GMPhobos/Classes/UIResponder+PhobosPV.h
\ No newline at end of file
../../../../../GMShareSDK/Classes/Share/ALShareView/ALPlatformView.h
\ No newline at end of file
../../../../../GMShareSDK/Classes/Share/ALShareView/ALShareButton.h
\ No newline at end of file
../../../../../GMShareSDK/Classes/Share/ALShareView/ALShareTopicCardView.h
\ No newline at end of file
../../../../../GMShareSDK/Classes/Share/GMDiaryShareInfoView.h
\ No newline at end of file
../../../../../GMShareSDK/Classes/Share/GMDiaryShareView.h
\ No newline at end of file
../../../../../GMShareSDK/Classes/Share/GMShareBaseObject.h
\ No newline at end of file
../../../../../GMShareSDK/Classes/Share/GMShareCell.h
\ No newline at end of file
../../../../../GMShareSDK/Classes/Share/GMShareFlowLayout.h
\ No newline at end of file
../../../../../GMShareSDK/Classes/Share/GMShareTopView.h
\ No newline at end of file
../../../../../GMShareSDK/Classes/Share/GMShareVideoAlertView.h
\ No newline at end of file
../../../../../GMShareSDK/Classes/Share/GMShareView.h
\ No newline at end of file
../../../../../GMShareSDK/Classes/Share/GMWeixinShareView.h
\ No newline at end of file
../../../../../GMShareSDK/Classes/Share/WMShareObject.h
\ No newline at end of file
../../../MBProgressHUD/MBProgressHUD.h
\ No newline at end of file
../../../TMCache/TMCache/TMCache.h
\ No newline at end of file
../../../TMCache/TMCache/TMCacheBackgroundTaskManager.h
\ No newline at end of file
../../../TMCache/TMCache/TMDiskCache.h
\ No newline at end of file
../../../TMCache/TMCache/TMMemoryCache.h
\ No newline at end of file
......@@ -32,9 +32,21 @@
],
"GMKit": [
],
"MBProgressHUD": [
],
"GMJSONModel": [
],
"GMPhobos": [
],
"GMFoundation": [
],
"Masonry": [
]
},
"frameworks": [
......
Copyright © 2009-2016 Matej Bukovinski
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
\ No newline at end of file
//
// MBProgressHUD.h
// Version 1.1.0
// Created by Matej Bukovinski on 2.4.09.
//
// This code is distributed under the terms and conditions of the MIT license.
// Copyright © 2009-2016 Matej Bukovinski
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
#import <CoreGraphics/CoreGraphics.h>
@class MBBackgroundView;
@protocol MBProgressHUDDelegate;
extern CGFloat const MBProgressMaxOffset;
typedef NS_ENUM(NSInteger, MBProgressHUDMode) {
/// UIActivityIndicatorView.
MBProgressHUDModeIndeterminate,
/// A round, pie-chart like, progress view.
MBProgressHUDModeDeterminate,
/// Horizontal progress bar.
MBProgressHUDModeDeterminateHorizontalBar,
/// Ring-shaped progress view.
MBProgressHUDModeAnnularDeterminate,
/// Shows a custom view.
MBProgressHUDModeCustomView,
/// Shows only labels.
MBProgressHUDModeText
};
typedef NS_ENUM(NSInteger, MBProgressHUDAnimation) {
/// Opacity animation
MBProgressHUDAnimationFade,
/// Opacity + scale animation (zoom in when appearing zoom out when disappearing)
MBProgressHUDAnimationZoom,
/// Opacity + scale animation (zoom out style)
MBProgressHUDAnimationZoomOut,
/// Opacity + scale animation (zoom in style)
MBProgressHUDAnimationZoomIn
};
typedef NS_ENUM(NSInteger, MBProgressHUDBackgroundStyle) {
/// Solid color background
MBProgressHUDBackgroundStyleSolidColor,
/// UIVisualEffectView or UIToolbar.layer background view
MBProgressHUDBackgroundStyleBlur
};
typedef void (^MBProgressHUDCompletionBlock)(void);
NS_ASSUME_NONNULL_BEGIN
/**
* Displays a simple HUD window containing a progress indicator and two optional labels for short messages.
*
* This is a simple drop-in class for displaying a progress HUD view similar to Apple's private UIProgressHUD class.
* The MBProgressHUD window spans over the entire space given to it by the initWithFrame: constructor and catches all
* user input on this region, thereby preventing the user operations on components below the view.
*
* @note To still allow touches to pass through the HUD, you can set hud.userInteractionEnabled = NO.
* @attention MBProgressHUD is a UI class and should therefore only be accessed on the main thread.
*/
@interface MBProgressHUD : UIView
/**
* Creates a new HUD, adds it to provided view and shows it. The counterpart to this method is hideHUDForView:animated:.
*
* @note This method sets removeFromSuperViewOnHide. The HUD will automatically be removed from the view hierarchy when hidden.
*
* @param view The view that the HUD will be added to
* @param animated If set to YES the HUD will appear using the current animationType. If set to NO the HUD will not use
* animations while appearing.
* @return A reference to the created HUD.
*
* @see hideHUDForView:animated:
* @see animationType
*/
+ (instancetype)showHUDAddedTo:(UIView *)view animated:(BOOL)animated;
/// @name Showing and hiding
/**
* Finds the top-most HUD subview that hasn't finished and hides it. The counterpart to this method is showHUDAddedTo:animated:.
*
* @note This method sets removeFromSuperViewOnHide. The HUD will automatically be removed from the view hierarchy when hidden.
*
* @param view The view that is going to be searched for a HUD subview.
* @param animated If set to YES the HUD will disappear using the current animationType. If set to NO the HUD will not use
* animations while disappearing.
* @return YES if a HUD was found and removed, NO otherwise.
*
* @see showHUDAddedTo:animated:
* @see animationType
*/
+ (BOOL)hideHUDForView:(UIView *)view animated:(BOOL)animated;
/**
* Finds the top-most HUD subview that hasn't finished and returns it.
*
* @param view The view that is going to be searched.
* @return A reference to the last HUD subview discovered.
*/
+ (nullable MBProgressHUD *)HUDForView:(UIView *)view;
/**
* A convenience constructor that initializes the HUD with the view's bounds. Calls the designated constructor with
* view.bounds as the parameter.
*
* @param view The view instance that will provide the bounds for the HUD. Should be the same instance as
* the HUD's superview (i.e., the view that the HUD will be added to).
*/
- (instancetype)initWithView:(UIView *)view;
/**
* Displays the HUD.
*
* @note You need to make sure that the main thread completes its run loop soon after this method call so that
* the user interface can be updated. Call this method when your task is already set up to be executed in a new thread
* (e.g., when using something like NSOperation or making an asynchronous call like NSURLRequest).
*
* @param animated If set to YES the HUD will appear using the current animationType. If set to NO the HUD will not use
* animations while appearing.
*
* @see animationType
*/
- (void)showAnimated:(BOOL)animated;
/**
* Hides the HUD. This still calls the hudWasHidden: delegate. This is the counterpart of the show: method. Use it to
* hide the HUD when your task completes.
*
* @param animated If set to YES the HUD will disappear using the current animationType. If set to NO the HUD will not use
* animations while disappearing.
*
* @see animationType
*/
- (void)hideAnimated:(BOOL)animated;
/**
* Hides the HUD after a delay. This still calls the hudWasHidden: delegate. This is the counterpart of the show: method. Use it to
* hide the HUD when your task completes.
*
* @param animated If set to YES the HUD will disappear using the current animationType. If set to NO the HUD will not use
* animations while disappearing.
* @param delay Delay in seconds until the HUD is hidden.
*
* @see animationType
*/
- (void)hideAnimated:(BOOL)animated afterDelay:(NSTimeInterval)delay;
/**
* The HUD delegate object. Receives HUD state notifications.
*/
@property (weak, nonatomic) id<MBProgressHUDDelegate> delegate;
/**
* Called after the HUD is hiden.
*/
@property (copy, nullable) MBProgressHUDCompletionBlock completionBlock;
/*
* Grace period is the time (in seconds) that the invoked method may be run without
* showing the HUD. If the task finishes before the grace time runs out, the HUD will
* not be shown at all.
* This may be used to prevent HUD display for very short tasks.
* Defaults to 0 (no grace time).
*/
@property (assign, nonatomic) NSTimeInterval graceTime;
/**
* The minimum time (in seconds) that the HUD is shown.
* This avoids the problem of the HUD being shown and than instantly hidden.
* Defaults to 0 (no minimum show time).
*/
@property (assign, nonatomic) NSTimeInterval minShowTime;
/**
* Removes the HUD from its parent view when hidden.
* Defaults to NO.
*/
@property (assign, nonatomic) BOOL removeFromSuperViewOnHide;
/// @name Appearance
/**
* MBProgressHUD operation mode. The default is MBProgressHUDModeIndeterminate.
*/
@property (assign, nonatomic) MBProgressHUDMode mode;
/**
* A color that gets forwarded to all labels and supported indicators. Also sets the tintColor
* for custom views on iOS 7+. Set to nil to manage color individually.
* Defaults to semi-translucent black on iOS 7 and later and white on earlier iOS versions.
*/
@property (strong, nonatomic, nullable) UIColor *contentColor UI_APPEARANCE_SELECTOR;
/**
* The animation type that should be used when the HUD is shown and hidden.
*/
@property (assign, nonatomic) MBProgressHUDAnimation animationType UI_APPEARANCE_SELECTOR;
/**
* The bezel offset relative to the center of the view. You can use MBProgressMaxOffset
* and -MBProgressMaxOffset to move the HUD all the way to the screen edge in each direction.
* E.g., CGPointMake(0.f, MBProgressMaxOffset) would position the HUD centered on the bottom edge.
*/
@property (assign, nonatomic) CGPoint offset UI_APPEARANCE_SELECTOR;
/**
* The amount of space between the HUD edge and the HUD elements (labels, indicators or custom views).
* This also represents the minimum bezel distance to the edge of the HUD view.
* Defaults to 20.f
*/
@property (assign, nonatomic) CGFloat margin UI_APPEARANCE_SELECTOR;
/**
* The minimum size of the HUD bezel. Defaults to CGSizeZero (no minimum size).
*/
@property (assign, nonatomic) CGSize minSize UI_APPEARANCE_SELECTOR;
/**
* Force the HUD dimensions to be equal if possible.
*/
@property (assign, nonatomic, getter = isSquare) BOOL square UI_APPEARANCE_SELECTOR;
/**
* When enabled, the bezel center gets slightly affected by the device accelerometer data.
* Has no effect on iOS < 7.0. Defaults to YES.
*/
@property (assign, nonatomic, getter=areDefaultMotionEffectsEnabled) BOOL defaultMotionEffectsEnabled UI_APPEARANCE_SELECTOR;
/// @name Progress
/**
* The progress of the progress indicator, from 0.0 to 1.0. Defaults to 0.0.
*/
@property (assign, nonatomic) float progress;
/// @name ProgressObject
/**
* The NSProgress object feeding the progress information to the progress indicator.
*/
@property (strong, nonatomic, nullable) NSProgress *progressObject;
/// @name Views
/**
* The view containing the labels and indicator (or customView).
*/
@property (strong, nonatomic, readonly) MBBackgroundView *bezelView;
/**
* View covering the entire HUD area, placed behind bezelView.
*/
@property (strong, nonatomic, readonly) MBBackgroundView *backgroundView;
/**
* The UIView (e.g., a UIImageView) to be shown when the HUD is in MBProgressHUDModeCustomView.
* The view should implement intrinsicContentSize for proper sizing. For best results use approximately 37 by 37 pixels.
*/
@property (strong, nonatomic, nullable) UIView *customView;
/**
* A label that holds an optional short message to be displayed below the activity indicator. The HUD is automatically resized to fit
* the entire text.
*/
@property (strong, nonatomic, readonly) UILabel *label;
/**
* A label that holds an optional details message displayed below the labelText message. The details text can span multiple lines.
*/
@property (strong, nonatomic, readonly) UILabel *detailsLabel;
/**
* A button that is placed below the labels. Visible only if a target / action is added.
*/
@property (strong, nonatomic, readonly) UIButton *button;
@end
@protocol MBProgressHUDDelegate <NSObject>
@optional
/**
* Called after the HUD was fully hidden from the screen.
*/
- (void)hudWasHidden:(MBProgressHUD *)hud;
@end
/**
* A progress view for showing definite progress by filling up a circle (pie chart).
*/
@interface MBRoundProgressView : UIView
/**
* Progress (0.0 to 1.0)
*/
@property (nonatomic, assign) float progress;
/**
* Indicator progress color.
* Defaults to white [UIColor whiteColor].
*/
@property (nonatomic, strong) UIColor *progressTintColor;
/**
* Indicator background (non-progress) color.
* Only applicable on iOS versions older than iOS 7.
* Defaults to translucent white (alpha 0.1).
*/
@property (nonatomic, strong) UIColor *backgroundTintColor;
/*
* Display mode - NO = round or YES = annular. Defaults to round.
*/
@property (nonatomic, assign, getter = isAnnular) BOOL annular;
@end
/**
* A flat bar progress view.
*/
@interface MBBarProgressView : UIView
/**
* Progress (0.0 to 1.0)
*/
@property (nonatomic, assign) float progress;
/**
* Bar border line color.
* Defaults to white [UIColor whiteColor].
*/
@property (nonatomic, strong) UIColor *lineColor;
/**
* Bar background color.
* Defaults to clear [UIColor clearColor];
*/
@property (nonatomic, strong) UIColor *progressRemainingColor;
/**
* Bar progress color.
* Defaults to white [UIColor whiteColor].
*/
@property (nonatomic, strong) UIColor *progressColor;
@end
@interface MBBackgroundView : UIView
/**
* The background style.
* Defaults to MBProgressHUDBackgroundStyleBlur on iOS 7 or later and MBProgressHUDBackgroundStyleSolidColor otherwise.
* @note Due to iOS 7 not supporting UIVisualEffectView, the blur effect differs slightly between iOS 7 and later versions.
*/
@property (nonatomic) MBProgressHUDBackgroundStyle style;
#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 80000 || TARGET_OS_TV
/**
* The blur effect style, when using MBProgressHUDBackgroundStyleBlur.
* Defaults to UIBlurEffectStyleLight.
*/
@property (nonatomic) UIBlurEffectStyle blurEffectStyle;
#endif
/**
* The background color or the blur tint color.
* @note Due to iOS 7 not supporting UIVisualEffectView, the blur effect differs slightly between iOS 7 and later versions.
*/
@property (nonatomic, strong) UIColor *color;
@end
@interface MBProgressHUD (Deprecated)
+ (NSArray *)allHUDsForView:(UIView *)view __attribute__((deprecated("Store references when using more than one HUD per view.")));
+ (NSUInteger)hideAllHUDsForView:(UIView *)view animated:(BOOL)animated __attribute__((deprecated("Store references when using more than one HUD per view.")));
- (id)initWithWindow:(UIWindow *)window __attribute__((deprecated("Use initWithView: instead.")));
- (void)show:(BOOL)animated __attribute__((deprecated("Use showAnimated: instead.")));
- (void)hide:(BOOL)animated __attribute__((deprecated("Use hideAnimated: instead.")));
- (void)hide:(BOOL)animated afterDelay:(NSTimeInterval)delay __attribute__((deprecated("Use hideAnimated:afterDelay: instead.")));
- (void)showWhileExecuting:(SEL)method onTarget:(id)target withObject:(id)object animated:(BOOL)animated __attribute__((deprecated("Use GCD directly.")));
- (void)showAnimated:(BOOL)animated whileExecutingBlock:(dispatch_block_t)block __attribute__((deprecated("Use GCD directly.")));
- (void)showAnimated:(BOOL)animated whileExecutingBlock:(dispatch_block_t)block completionBlock:(nullable MBProgressHUDCompletionBlock)completion __attribute__((deprecated("Use GCD directly.")));
- (void)showAnimated:(BOOL)animated whileExecutingBlock:(dispatch_block_t)block onQueue:(dispatch_queue_t)queue __attribute__((deprecated("Use GCD directly.")));
- (void)showAnimated:(BOOL)animated whileExecutingBlock:(dispatch_block_t)block onQueue:(dispatch_queue_t)queue
completionBlock:(nullable MBProgressHUDCompletionBlock)completion __attribute__((deprecated("Use GCD directly.")));
@property (assign) BOOL taskInProgress __attribute__((deprecated("No longer needed.")));
@property (nonatomic, copy) NSString *labelText __attribute__((deprecated("Use label.text instead.")));
@property (nonatomic, strong) UIFont *labelFont __attribute__((deprecated("Use label.font instead.")));
@property (nonatomic, strong) UIColor *labelColor __attribute__((deprecated("Use label.textColor instead.")));
@property (nonatomic, copy) NSString *detailsLabelText __attribute__((deprecated("Use detailsLabel.text instead.")));
@property (nonatomic, strong) UIFont *detailsLabelFont __attribute__((deprecated("Use detailsLabel.font instead.")));
@property (nonatomic, strong) UIColor *detailsLabelColor __attribute__((deprecated("Use detailsLabel.textColor instead.")));
@property (assign, nonatomic) CGFloat opacity __attribute__((deprecated("Customize bezelView properties instead.")));
@property (strong, nonatomic) UIColor *color __attribute__((deprecated("Customize the bezelView color instead.")));
@property (assign, nonatomic) CGFloat xOffset __attribute__((deprecated("Set offset.x instead.")));
@property (assign, nonatomic) CGFloat yOffset __attribute__((deprecated("Set offset.y instead.")));
@property (assign, nonatomic) CGFloat cornerRadius __attribute__((deprecated("Set bezelView.layer.cornerRadius instead.")));
@property (assign, nonatomic) BOOL dimBackground __attribute__((deprecated("Customize HUD background properties instead.")));
@property (strong, nonatomic) UIColor *activityIndicatorColor __attribute__((deprecated("Use UIAppearance to customize UIActivityIndicatorView. E.g.: [UIActivityIndicatorView appearanceWhenContainedIn:[MBProgressHUD class], nil].color = [UIColor redColor];")));
@property (atomic, assign, readonly) CGSize size __attribute__((deprecated("Get the bezelView.frame.size instead.")));
@end
NS_ASSUME_NONNULL_END
//
// MBProgressHUD.m
// Version 1.1.0
// Created by Matej Bukovinski on 2.4.09.
//
#import "MBProgressHUD.h"
#import <tgmath.h>
#ifndef kCFCoreFoundationVersionNumber_iOS_7_0
#define kCFCoreFoundationVersionNumber_iOS_7_0 847.20
#endif
#ifndef kCFCoreFoundationVersionNumber_iOS_8_0
#define kCFCoreFoundationVersionNumber_iOS_8_0 1129.15
#endif
#define MBMainThreadAssert() NSAssert([NSThread isMainThread], @"MBProgressHUD needs to be accessed on the main thread.");
CGFloat const MBProgressMaxOffset = 1000000.f;
static const CGFloat MBDefaultPadding = 4.f;
static const CGFloat MBDefaultLabelFontSize = 16.f;
static const CGFloat MBDefaultDetailsLabelFontSize = 12.f;
@interface MBProgressHUD () {
// Deprecated
UIColor *_activityIndicatorColor;
CGFloat _opacity;
}
@property (nonatomic, assign) BOOL useAnimation;
@property (nonatomic, assign, getter=hasFinished) BOOL finished;
@property (nonatomic, strong) UIView *indicator;
@property (nonatomic, strong) NSDate *showStarted;
@property (nonatomic, strong) NSArray *paddingConstraints;
@property (nonatomic, strong) NSArray *bezelConstraints;
@property (nonatomic, strong) UIView *topSpacer;
@property (nonatomic, strong) UIView *bottomSpacer;
@property (nonatomic, weak) NSTimer *graceTimer;
@property (nonatomic, weak) NSTimer *minShowTimer;
@property (nonatomic, weak) NSTimer *hideDelayTimer;
@property (nonatomic, weak) CADisplayLink *progressObjectDisplayLink;
// Deprecated
@property (assign) BOOL taskInProgress;
@end
@interface MBProgressHUDRoundedButton : UIButton
@end
@implementation MBProgressHUD
#pragma mark - Class methods
+ (instancetype)showHUDAddedTo:(UIView *)view animated:(BOOL)animated {
MBProgressHUD *hud = [[self alloc] initWithView:view];
hud.removeFromSuperViewOnHide = YES;
[view addSubview:hud];
[hud showAnimated:animated];
return hud;
}
+ (BOOL)hideHUDForView:(UIView *)view animated:(BOOL)animated {
MBProgressHUD *hud = [self HUDForView:view];
if (hud != nil) {
hud.removeFromSuperViewOnHide = YES;
[hud hideAnimated:animated];
return YES;
}
return NO;
}
+ (MBProgressHUD *)HUDForView:(UIView *)view {
NSEnumerator *subviewsEnum = [view.subviews reverseObjectEnumerator];
for (UIView *subview in subviewsEnum) {
if ([subview isKindOfClass:self]) {
MBProgressHUD *hud = (MBProgressHUD *)subview;
if (hud.hasFinished == NO) {
return hud;
}
}
}
return nil;
}
#pragma mark - Lifecycle
- (void)commonInit {
// Set default values for properties
_animationType = MBProgressHUDAnimationFade;
_mode = MBProgressHUDModeIndeterminate;
_margin = 20.0f;
_opacity = 1.f;
_defaultMotionEffectsEnabled = YES;
// Default color, depending on the current iOS version
BOOL isLegacy = kCFCoreFoundationVersionNumber < kCFCoreFoundationVersionNumber_iOS_7_0;
_contentColor = isLegacy ? [UIColor whiteColor] : [UIColor colorWithWhite:0.f alpha:0.7f];
// Transparent background
self.opaque = NO;
self.backgroundColor = [UIColor clearColor];
// Make it invisible for now
self.alpha = 0.0f;
self.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
self.layer.allowsGroupOpacity = NO;
[self setupViews];
[self updateIndicators];
[self registerForNotifications];
}
- (instancetype)initWithFrame:(CGRect)frame {
if ((self = [super initWithFrame:frame])) {
[self commonInit];
}
return self;
}
- (instancetype)initWithCoder:(NSCoder *)aDecoder {
if ((self = [super initWithCoder:aDecoder])) {
[self commonInit];
}
return self;
}
- (id)initWithView:(UIView *)view {
NSAssert(view, @"View must not be nil.");
return [self initWithFrame:view.bounds];
}
- (void)dealloc {
[self unregisterFromNotifications];
}
#pragma mark - Show & hide
- (void)showAnimated:(BOOL)animated {
MBMainThreadAssert();
[self.minShowTimer invalidate];
self.useAnimation = animated;
self.finished = NO;
// If the grace time is set, postpone the HUD display
if (self.graceTime > 0.0) {
NSTimer *timer = [NSTimer timerWithTimeInterval:self.graceTime target:self selector:@selector(handleGraceTimer:) userInfo:nil repeats:NO];
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
self.graceTimer = timer;
}
// ... otherwise show the HUD immediately
else {
[self showUsingAnimation:self.useAnimation];
}
}
- (void)hideAnimated:(BOOL)animated {
MBMainThreadAssert();
[self.graceTimer invalidate];
self.useAnimation = animated;
self.finished = YES;
// If the minShow time is set, calculate how long the HUD was shown,
// and postpone the hiding operation if necessary
if (self.minShowTime > 0.0 && self.showStarted) {
NSTimeInterval interv = [[NSDate date] timeIntervalSinceDate:self.showStarted];
if (interv < self.minShowTime) {
NSTimer *timer = [NSTimer timerWithTimeInterval:(self.minShowTime - interv) target:self selector:@selector(handleMinShowTimer:) userInfo:nil repeats:NO];
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
self.minShowTimer = timer;
return;
}
}
// ... otherwise hide the HUD immediately
[self hideUsingAnimation:self.useAnimation];
}
- (void)hideAnimated:(BOOL)animated afterDelay:(NSTimeInterval)delay {
// Cancel any scheduled hideDelayed: calls
[self.hideDelayTimer invalidate];
NSTimer *timer = [NSTimer timerWithTimeInterval:delay target:self selector:@selector(handleHideTimer:) userInfo:@(animated) repeats:NO];
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
self.hideDelayTimer = timer;
}
#pragma mark - Timer callbacks
- (void)handleGraceTimer:(NSTimer *)theTimer {
// Show the HUD only if the task is still running
if (!self.hasFinished) {
[self showUsingAnimation:self.useAnimation];
}
}
- (void)handleMinShowTimer:(NSTimer *)theTimer {
[self hideUsingAnimation:self.useAnimation];
}
- (void)handleHideTimer:(NSTimer *)timer {
[self hideAnimated:[timer.userInfo boolValue]];
}
#pragma mark - View Hierrarchy
- (void)didMoveToSuperview {
[self updateForCurrentOrientationAnimated:NO];
}
#pragma mark - Internal show & hide operations
- (void)showUsingAnimation:(BOOL)animated {
// Cancel any previous animations
[self.bezelView.layer removeAllAnimations];
[self.backgroundView.layer removeAllAnimations];
// Cancel any scheduled hideDelayed: calls
[self.hideDelayTimer invalidate];
self.showStarted = [NSDate date];
self.alpha = 1.f;
// Needed in case we hide and re-show with the same NSProgress object attached.
[self setNSProgressDisplayLinkEnabled:YES];
if (animated) {
[self animateIn:YES withType:self.animationType completion:NULL];
} else {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
self.bezelView.alpha = self.opacity;
#pragma clang diagnostic pop
self.backgroundView.alpha = 1.f;
}
}
- (void)hideUsingAnimation:(BOOL)animated {
if (animated && self.showStarted) {
self.showStarted = nil;
[self animateIn:NO withType:self.animationType completion:^(BOOL finished) {
[self done];
}];
} else {
self.showStarted = nil;
self.bezelView.alpha = 0.f;
self.backgroundView.alpha = 1.f;
[self done];
}
}
- (void)animateIn:(BOOL)animatingIn withType:(MBProgressHUDAnimation)type completion:(void(^)(BOOL finished))completion {
// Automatically determine the correct zoom animation type
if (type == MBProgressHUDAnimationZoom) {
type = animatingIn ? MBProgressHUDAnimationZoomIn : MBProgressHUDAnimationZoomOut;
}
CGAffineTransform small = CGAffineTransformMakeScale(0.5f, 0.5f);
CGAffineTransform large = CGAffineTransformMakeScale(1.5f, 1.5f);
// Set starting state
UIView *bezelView = self.bezelView;
if (animatingIn && bezelView.alpha == 0.f && type == MBProgressHUDAnimationZoomIn) {
bezelView.transform = small;
} else if (animatingIn && bezelView.alpha == 0.f && type == MBProgressHUDAnimationZoomOut) {
bezelView.transform = large;
}
// Perform animations
dispatch_block_t animations = ^{
if (animatingIn) {
bezelView.transform = CGAffineTransformIdentity;
} else if (!animatingIn && type == MBProgressHUDAnimationZoomIn) {
bezelView.transform = large;
} else if (!animatingIn && type == MBProgressHUDAnimationZoomOut) {
bezelView.transform = small;
}
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
bezelView.alpha = animatingIn ? self.opacity : 0.f;
#pragma clang diagnostic pop
self.backgroundView.alpha = animatingIn ? 1.f : 0.f;
};
// Spring animations are nicer, but only available on iOS 7+
#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 70000 || TARGET_OS_TV
if (kCFCoreFoundationVersionNumber >= kCFCoreFoundationVersionNumber_iOS_7_0) {
[UIView animateWithDuration:0.3 delay:0. usingSpringWithDamping:1.f initialSpringVelocity:0.f options:UIViewAnimationOptionBeginFromCurrentState animations:animations completion:completion];
return;
}
#endif
[UIView animateWithDuration:0.3 delay:0. options:UIViewAnimationOptionBeginFromCurrentState animations:animations completion:completion];
}
- (void)done {
// Cancel any scheduled hideDelayed: calls
[self.hideDelayTimer invalidate];
[self setNSProgressDisplayLinkEnabled:NO];
if (self.hasFinished) {
self.alpha = 0.0f;
if (self.removeFromSuperViewOnHide) {
[self removeFromSuperview];
}
}
MBProgressHUDCompletionBlock completionBlock = self.completionBlock;
if (completionBlock) {
completionBlock();
}
id<MBProgressHUDDelegate> delegate = self.delegate;
if ([delegate respondsToSelector:@selector(hudWasHidden:)]) {
[delegate performSelector:@selector(hudWasHidden:) withObject:self];
}
}
#pragma mark - UI
- (void)setupViews {
UIColor *defaultColor = self.contentColor;
MBBackgroundView *backgroundView = [[MBBackgroundView alloc] initWithFrame:self.bounds];
backgroundView.style = MBProgressHUDBackgroundStyleSolidColor;
backgroundView.backgroundColor = [UIColor clearColor];
backgroundView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
backgroundView.alpha = 0.f;
[self addSubview:backgroundView];
_backgroundView = backgroundView;
MBBackgroundView *bezelView = [MBBackgroundView new];
bezelView.translatesAutoresizingMaskIntoConstraints = NO;
bezelView.layer.cornerRadius = 5.f;
bezelView.alpha = 0.f;
[self addSubview:bezelView];
_bezelView = bezelView;
[self updateBezelMotionEffects];
UILabel *label = [UILabel new];
label.adjustsFontSizeToFitWidth = NO;
label.textAlignment = NSTextAlignmentCenter;
label.textColor = defaultColor;
label.font = [UIFont boldSystemFontOfSize:MBDefaultLabelFontSize];
label.opaque = NO;
label.backgroundColor = [UIColor clearColor];
_label = label;
UILabel *detailsLabel = [UILabel new];
detailsLabel.adjustsFontSizeToFitWidth = NO;
detailsLabel.textAlignment = NSTextAlignmentCenter;
detailsLabel.textColor = defaultColor;
detailsLabel.numberOfLines = 0;
detailsLabel.font = [UIFont boldSystemFontOfSize:MBDefaultDetailsLabelFontSize];
detailsLabel.opaque = NO;
detailsLabel.backgroundColor = [UIColor clearColor];
_detailsLabel = detailsLabel;
UIButton *button = [MBProgressHUDRoundedButton buttonWithType:UIButtonTypeCustom];
button.titleLabel.textAlignment = NSTextAlignmentCenter;
button.titleLabel.font = [UIFont boldSystemFontOfSize:MBDefaultDetailsLabelFontSize];
[button setTitleColor:defaultColor forState:UIControlStateNormal];
_button = button;
for (UIView *view in @[label, detailsLabel, button]) {
view.translatesAutoresizingMaskIntoConstraints = NO;
[view setContentCompressionResistancePriority:998.f forAxis:UILayoutConstraintAxisHorizontal];
[view setContentCompressionResistancePriority:998.f forAxis:UILayoutConstraintAxisVertical];
[bezelView addSubview:view];
}
UIView *topSpacer = [UIView new];
topSpacer.translatesAutoresizingMaskIntoConstraints = NO;
topSpacer.hidden = YES;
[bezelView addSubview:topSpacer];
_topSpacer = topSpacer;
UIView *bottomSpacer = [UIView new];
bottomSpacer.translatesAutoresizingMaskIntoConstraints = NO;
bottomSpacer.hidden = YES;
[bezelView addSubview:bottomSpacer];
_bottomSpacer = bottomSpacer;
}
- (void)updateIndicators {
UIView *indicator = self.indicator;
BOOL isActivityIndicator = [indicator isKindOfClass:[UIActivityIndicatorView class]];
BOOL isRoundIndicator = [indicator isKindOfClass:[MBRoundProgressView class]];
MBProgressHUDMode mode = self.mode;
if (mode == MBProgressHUDModeIndeterminate) {
if (!isActivityIndicator) {
// Update to indeterminate indicator
[indicator removeFromSuperview];
indicator = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteLarge];
[(UIActivityIndicatorView *)indicator startAnimating];
[self.bezelView addSubview:indicator];
}
}
else if (mode == MBProgressHUDModeDeterminateHorizontalBar) {
// Update to bar determinate indicator
[indicator removeFromSuperview];
indicator = [[MBBarProgressView alloc] init];
[self.bezelView addSubview:indicator];
}
else if (mode == MBProgressHUDModeDeterminate || mode == MBProgressHUDModeAnnularDeterminate) {
if (!isRoundIndicator) {
// Update to determinante indicator
[indicator removeFromSuperview];
indicator = [[MBRoundProgressView alloc] init];
[self.bezelView addSubview:indicator];
}
if (mode == MBProgressHUDModeAnnularDeterminate) {
[(MBRoundProgressView *)indicator setAnnular:YES];
}
}
else if (mode == MBProgressHUDModeCustomView && self.customView != indicator) {
// Update custom view indicator
[indicator removeFromSuperview];
indicator = self.customView;
[self.bezelView addSubview:indicator];
}
else if (mode == MBProgressHUDModeText) {
[indicator removeFromSuperview];
indicator = nil;
}
indicator.translatesAutoresizingMaskIntoConstraints = NO;
self.indicator = indicator;
if ([indicator respondsToSelector:@selector(setProgress:)]) {
[(id)indicator setValue:@(self.progress) forKey:@"progress"];
}
[indicator setContentCompressionResistancePriority:998.f forAxis:UILayoutConstraintAxisHorizontal];
[indicator setContentCompressionResistancePriority:998.f forAxis:UILayoutConstraintAxisVertical];
[self updateViewsForColor:self.contentColor];
[self setNeedsUpdateConstraints];
}
- (void)updateViewsForColor:(UIColor *)color {
if (!color) return;
self.label.textColor = color;
self.detailsLabel.textColor = color;
[self.button setTitleColor:color forState:UIControlStateNormal];
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
if (self.activityIndicatorColor) {
color = self.activityIndicatorColor;
}
#pragma clang diagnostic pop
// UIAppearance settings are prioritized. If they are preset the set color is ignored.
UIView *indicator = self.indicator;
if ([indicator isKindOfClass:[UIActivityIndicatorView class]]) {
UIActivityIndicatorView *appearance = nil;
#if __IPHONE_OS_VERSION_MIN_REQUIRED < 90000
appearance = [UIActivityIndicatorView appearanceWhenContainedIn:[MBProgressHUD class], nil];
#else
// For iOS 9+
appearance = [UIActivityIndicatorView appearanceWhenContainedInInstancesOfClasses:@[[MBProgressHUD class]]];
#endif
if (appearance.color == nil) {
((UIActivityIndicatorView *)indicator).color = color;
}
} else if ([indicator isKindOfClass:[MBRoundProgressView class]]) {
MBRoundProgressView *appearance = nil;
#if __IPHONE_OS_VERSION_MIN_REQUIRED < 90000
appearance = [MBRoundProgressView appearanceWhenContainedIn:[MBProgressHUD class], nil];
#else
appearance = [MBRoundProgressView appearanceWhenContainedInInstancesOfClasses:@[[MBProgressHUD class]]];
#endif
if (appearance.progressTintColor == nil) {
((MBRoundProgressView *)indicator).progressTintColor = color;
}
if (appearance.backgroundTintColor == nil) {
((MBRoundProgressView *)indicator).backgroundTintColor = [color colorWithAlphaComponent:0.1];
}
} else if ([indicator isKindOfClass:[MBBarProgressView class]]) {
MBBarProgressView *appearance = nil;
#if __IPHONE_OS_VERSION_MIN_REQUIRED < 90000
appearance = [MBBarProgressView appearanceWhenContainedIn:[MBProgressHUD class], nil];
#else
appearance = [MBBarProgressView appearanceWhenContainedInInstancesOfClasses:@[[MBProgressHUD class]]];
#endif
if (appearance.progressColor == nil) {
((MBBarProgressView *)indicator).progressColor = color;
}
if (appearance.lineColor == nil) {
((MBBarProgressView *)indicator).lineColor = color;
}
} else {
#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 70000 || TARGET_OS_TV
if ([indicator respondsToSelector:@selector(setTintColor:)]) {
[indicator setTintColor:color];
}
#endif
}
}
- (void)updateBezelMotionEffects {
#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 70000 || TARGET_OS_TV
MBBackgroundView *bezelView = self.bezelView;
if (![bezelView respondsToSelector:@selector(addMotionEffect:)]) return;
if (self.defaultMotionEffectsEnabled) {
CGFloat effectOffset = 10.f;
UIInterpolatingMotionEffect *effectX = [[UIInterpolatingMotionEffect alloc] initWithKeyPath:@"center.x" type:UIInterpolatingMotionEffectTypeTiltAlongHorizontalAxis];
effectX.maximumRelativeValue = @(effectOffset);
effectX.minimumRelativeValue = @(-effectOffset);
UIInterpolatingMotionEffect *effectY = [[UIInterpolatingMotionEffect alloc] initWithKeyPath:@"center.y" type:UIInterpolatingMotionEffectTypeTiltAlongVerticalAxis];
effectY.maximumRelativeValue = @(effectOffset);
effectY.minimumRelativeValue = @(-effectOffset);
UIMotionEffectGroup *group = [[UIMotionEffectGroup alloc] init];
group.motionEffects = @[effectX, effectY];
[bezelView addMotionEffect:group];
} else {
NSArray *effects = [bezelView motionEffects];
for (UIMotionEffect *effect in effects) {
[bezelView removeMotionEffect:effect];
}
}
#endif
}
#pragma mark - Layout
- (void)updateConstraints {
UIView *bezel = self.bezelView;
UIView *topSpacer = self.topSpacer;
UIView *bottomSpacer = self.bottomSpacer;
CGFloat margin = self.margin;
NSMutableArray *bezelConstraints = [NSMutableArray array];
NSDictionary *metrics = @{@"margin": @(margin)};
NSMutableArray *subviews = [NSMutableArray arrayWithObjects:self.topSpacer, self.label, self.detailsLabel, self.button, self.bottomSpacer, nil];
if (self.indicator) [subviews insertObject:self.indicator atIndex:1];
// Remove existing constraints
[self removeConstraints:self.constraints];
[topSpacer removeConstraints:topSpacer.constraints];
[bottomSpacer removeConstraints:bottomSpacer.constraints];
if (self.bezelConstraints) {
[bezel removeConstraints:self.bezelConstraints];
self.bezelConstraints = nil;
}
// Center bezel in container (self), applying the offset if set
CGPoint offset = self.offset;
NSMutableArray *centeringConstraints = [NSMutableArray array];
[centeringConstraints addObject:[NSLayoutConstraint constraintWithItem:bezel attribute:NSLayoutAttributeCenterX relatedBy:NSLayoutRelationEqual toItem:self attribute:NSLayoutAttributeCenterX multiplier:1.f constant:offset.x]];
[centeringConstraints addObject:[NSLayoutConstraint constraintWithItem:bezel attribute:NSLayoutAttributeCenterY relatedBy:NSLayoutRelationEqual toItem:self attribute:NSLayoutAttributeCenterY multiplier:1.f constant:offset.y]];
[self applyPriority:998.f toConstraints:centeringConstraints];
[self addConstraints:centeringConstraints];
// Ensure minimum side margin is kept
NSMutableArray *sideConstraints = [NSMutableArray array];
[sideConstraints addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:@"|-(>=margin)-[bezel]-(>=margin)-|" options:0 metrics:metrics views:NSDictionaryOfVariableBindings(bezel)]];
[sideConstraints addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-(>=margin)-[bezel]-(>=margin)-|" options:0 metrics:metrics views:NSDictionaryOfVariableBindings(bezel)]];
[self applyPriority:999.f toConstraints:sideConstraints];
[self addConstraints:sideConstraints];
// Minimum bezel size, if set
CGSize minimumSize = self.minSize;
if (!CGSizeEqualToSize(minimumSize, CGSizeZero)) {
NSMutableArray *minSizeConstraints = [NSMutableArray array];
[minSizeConstraints addObject:[NSLayoutConstraint constraintWithItem:bezel attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationGreaterThanOrEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1.f constant:minimumSize.width]];
[minSizeConstraints addObject:[NSLayoutConstraint constraintWithItem:bezel attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationGreaterThanOrEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1.f constant:minimumSize.height]];
[self applyPriority:997.f toConstraints:minSizeConstraints];
[bezelConstraints addObjectsFromArray:minSizeConstraints];
}
// Square aspect ratio, if set
if (self.square) {
NSLayoutConstraint *square = [NSLayoutConstraint constraintWithItem:bezel attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:bezel attribute:NSLayoutAttributeWidth multiplier:1.f constant:0];
square.priority = 997.f;
[bezelConstraints addObject:square];
}
// Top and bottom spacing
[topSpacer addConstraint:[NSLayoutConstraint constraintWithItem:topSpacer attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationGreaterThanOrEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1.f constant:margin]];
[bottomSpacer addConstraint:[NSLayoutConstraint constraintWithItem:bottomSpacer attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationGreaterThanOrEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1.f constant:margin]];
// Top and bottom spaces should be equal
[bezelConstraints addObject:[NSLayoutConstraint constraintWithItem:topSpacer attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:bottomSpacer attribute:NSLayoutAttributeHeight multiplier:1.f constant:0.f]];
// Layout subviews in bezel
NSMutableArray *paddingConstraints = [NSMutableArray new];
[subviews enumerateObjectsUsingBlock:^(UIView *view, NSUInteger idx, BOOL *stop) {
// Center in bezel
[bezelConstraints addObject:[NSLayoutConstraint constraintWithItem:view attribute:NSLayoutAttributeCenterX relatedBy:NSLayoutRelationEqual toItem:bezel attribute:NSLayoutAttributeCenterX multiplier:1.f constant:0.f]];
// Ensure the minimum edge margin is kept
[bezelConstraints addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:@"|-(>=margin)-[view]-(>=margin)-|" options:0 metrics:metrics views:NSDictionaryOfVariableBindings(view)]];
// Element spacing
if (idx == 0) {
// First, ensure spacing to bezel edge
[bezelConstraints addObject:[NSLayoutConstraint constraintWithItem:view attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:bezel attribute:NSLayoutAttributeTop multiplier:1.f constant:0.f]];
} else if (idx == subviews.count - 1) {
// Last, ensure spacing to bezel edge
[bezelConstraints addObject:[NSLayoutConstraint constraintWithItem:view attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationEqual toItem:bezel attribute:NSLayoutAttributeBottom multiplier:1.f constant:0.f]];
}
if (idx > 0) {
// Has previous
NSLayoutConstraint *padding = [NSLayoutConstraint constraintWithItem:view attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:subviews[idx - 1] attribute:NSLayoutAttributeBottom multiplier:1.f constant:0.f];
[bezelConstraints addObject:padding];
[paddingConstraints addObject:padding];
}
}];
[bezel addConstraints:bezelConstraints];
self.bezelConstraints = bezelConstraints;
self.paddingConstraints = [paddingConstraints copy];
[self updatePaddingConstraints];
[super updateConstraints];
}
- (void)layoutSubviews {
// There is no need to update constraints if they are going to
// be recreated in [super layoutSubviews] due to needsUpdateConstraints being set.
// This also avoids an issue on iOS 8, where updatePaddingConstraints
// would trigger a zombie object access.
if (!self.needsUpdateConstraints) {
[self updatePaddingConstraints];
}
[super layoutSubviews];
}
- (void)updatePaddingConstraints {
// Set padding dynamically, depending on whether the view is visible or not
__block BOOL hasVisibleAncestors = NO;
[self.paddingConstraints enumerateObjectsUsingBlock:^(NSLayoutConstraint *padding, NSUInteger idx, BOOL *stop) {
UIView *firstView = (UIView *)padding.firstItem;
UIView *secondView = (UIView *)padding.secondItem;
BOOL firstVisible = !firstView.hidden && !CGSizeEqualToSize(firstView.intrinsicContentSize, CGSizeZero);
BOOL secondVisible = !secondView.hidden && !CGSizeEqualToSize(secondView.intrinsicContentSize, CGSizeZero);
// Set if both views are visible or if there's a visible view on top that doesn't have padding
// added relative to the current view yet
padding.constant = (firstVisible && (secondVisible || hasVisibleAncestors)) ? MBDefaultPadding : 0.f;
hasVisibleAncestors |= secondVisible;
}];
}
- (void)applyPriority:(UILayoutPriority)priority toConstraints:(NSArray *)constraints {
for (NSLayoutConstraint *constraint in constraints) {
constraint.priority = priority;
}
}
#pragma mark - Properties
- (void)setMode:(MBProgressHUDMode)mode {
if (mode != _mode) {
_mode = mode;
[self updateIndicators];
}
}
- (void)setCustomView:(UIView *)customView {
if (customView != _customView) {
_customView = customView;
if (self.mode == MBProgressHUDModeCustomView) {
[self updateIndicators];
}
}
}
- (void)setOffset:(CGPoint)offset {
if (!CGPointEqualToPoint(offset, _offset)) {
_offset = offset;
[self setNeedsUpdateConstraints];
}
}
- (void)setMargin:(CGFloat)margin {
if (margin != _margin) {
_margin = margin;
[self setNeedsUpdateConstraints];
}
}
- (void)setMinSize:(CGSize)minSize {
if (!CGSizeEqualToSize(minSize, _minSize)) {
_minSize = minSize;
[self setNeedsUpdateConstraints];
}
}
- (void)setSquare:(BOOL)square {
if (square != _square) {
_square = square;
[self setNeedsUpdateConstraints];
}
}
- (void)setProgressObjectDisplayLink:(CADisplayLink *)progressObjectDisplayLink {
if (progressObjectDisplayLink != _progressObjectDisplayLink) {
[_progressObjectDisplayLink invalidate];
_progressObjectDisplayLink = progressObjectDisplayLink;
[_progressObjectDisplayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];
}
}
- (void)setProgressObject:(NSProgress *)progressObject {
if (progressObject != _progressObject) {
_progressObject = progressObject;
[self setNSProgressDisplayLinkEnabled:YES];
}
}
- (void)setProgress:(float)progress {
if (progress != _progress) {
_progress = progress;
UIView *indicator = self.indicator;
if ([indicator respondsToSelector:@selector(setProgress:)]) {
[(id)indicator setValue:@(self.progress) forKey:@"progress"];
}
}
}
- (void)setContentColor:(UIColor *)contentColor {
if (contentColor != _contentColor && ![contentColor isEqual:_contentColor]) {
_contentColor = contentColor;
[self updateViewsForColor:contentColor];
}
}
- (void)setDefaultMotionEffectsEnabled:(BOOL)defaultMotionEffectsEnabled {
if (defaultMotionEffectsEnabled != _defaultMotionEffectsEnabled) {
_defaultMotionEffectsEnabled = defaultMotionEffectsEnabled;
[self updateBezelMotionEffects];
}
}
#pragma mark - NSProgress
- (void)setNSProgressDisplayLinkEnabled:(BOOL)enabled {
// We're using CADisplayLink, because NSProgress can change very quickly and observing it may starve the main thread,
// so we're refreshing the progress only every frame draw
if (enabled && self.progressObject) {
// Only create if not already active.
if (!self.progressObjectDisplayLink) {
self.progressObjectDisplayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(updateProgressFromProgressObject)];
}
} else {
self.progressObjectDisplayLink = nil;
}
}
- (void)updateProgressFromProgressObject {
self.progress = self.progressObject.fractionCompleted;
}
#pragma mark - Notifications
- (void)registerForNotifications {
#if !TARGET_OS_TV
NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
[nc addObserver:self selector:@selector(statusBarOrientationDidChange:)
name:UIApplicationDidChangeStatusBarOrientationNotification object:nil];
#endif
}
- (void)unregisterFromNotifications {
#if !TARGET_OS_TV
NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
[nc removeObserver:self name:UIApplicationDidChangeStatusBarOrientationNotification object:nil];
#endif
}
#if !TARGET_OS_TV
- (void)statusBarOrientationDidChange:(NSNotification *)notification {
UIView *superview = self.superview;
if (!superview) {
return;
} else {
[self updateForCurrentOrientationAnimated:YES];
}
}
#endif
- (void)updateForCurrentOrientationAnimated:(BOOL)animated {
// Stay in sync with the superview in any case
if (self.superview) {
self.frame = self.superview.bounds;
}
// Not needed on iOS 8+, compile out when the deployment target allows,
// to avoid sharedApplication problems on extension targets
#if __IPHONE_OS_VERSION_MIN_REQUIRED < 80000
// Only needed pre iOS 8 when added to a window
BOOL iOS8OrLater = kCFCoreFoundationVersionNumber >= kCFCoreFoundationVersionNumber_iOS_8_0;
if (iOS8OrLater || ![self.superview isKindOfClass:[UIWindow class]]) return;
// Make extension friendly. Will not get called on extensions (iOS 8+) due to the above check.
// This just ensures we don't get a warning about extension-unsafe API.
Class UIApplicationClass = NSClassFromString(@"UIApplication");
if (!UIApplicationClass || ![UIApplicationClass respondsToSelector:@selector(sharedApplication)]) return;
UIApplication *application = [UIApplication performSelector:@selector(sharedApplication)];
UIInterfaceOrientation orientation = application.statusBarOrientation;
CGFloat radians = 0;
if (UIInterfaceOrientationIsLandscape(orientation)) {
radians = orientation == UIInterfaceOrientationLandscapeLeft ? -(CGFloat)M_PI_2 : (CGFloat)M_PI_2;
// Window coordinates differ!
self.bounds = CGRectMake(0, 0, self.bounds.size.height, self.bounds.size.width);
} else {
radians = orientation == UIInterfaceOrientationPortraitUpsideDown ? (CGFloat)M_PI : 0.f;
}
if (animated) {
[UIView animateWithDuration:0.3 animations:^{
self.transform = CGAffineTransformMakeRotation(radians);
}];
} else {
self.transform = CGAffineTransformMakeRotation(radians);
}
#endif
}
@end
@implementation MBRoundProgressView
#pragma mark - Lifecycle
- (id)init {
return [self initWithFrame:CGRectMake(0.f, 0.f, 37.f, 37.f)];
}
- (id)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
self.backgroundColor = [UIColor clearColor];
self.opaque = NO;
_progress = 0.f;
_annular = NO;
_progressTintColor = [[UIColor alloc] initWithWhite:1.f alpha:1.f];
_backgroundTintColor = [[UIColor alloc] initWithWhite:1.f alpha:.1f];
}
return self;
}
#pragma mark - Layout
- (CGSize)intrinsicContentSize {
return CGSizeMake(37.f, 37.f);
}
#pragma mark - Properties
- (void)setProgress:(float)progress {
if (progress != _progress) {
_progress = progress;
[self setNeedsDisplay];
}
}
- (void)setProgressTintColor:(UIColor *)progressTintColor {
NSAssert(progressTintColor, @"The color should not be nil.");
if (progressTintColor != _progressTintColor && ![progressTintColor isEqual:_progressTintColor]) {
_progressTintColor = progressTintColor;
[self setNeedsDisplay];
}
}
- (void)setBackgroundTintColor:(UIColor *)backgroundTintColor {
NSAssert(backgroundTintColor, @"The color should not be nil.");
if (backgroundTintColor != _backgroundTintColor && ![backgroundTintColor isEqual:_backgroundTintColor]) {
_backgroundTintColor = backgroundTintColor;
[self setNeedsDisplay];
}
}
#pragma mark - Drawing
- (void)drawRect:(CGRect)rect {
CGContextRef context = UIGraphicsGetCurrentContext();
BOOL isPreiOS7 = kCFCoreFoundationVersionNumber < kCFCoreFoundationVersionNumber_iOS_7_0;
if (_annular) {
// Draw background
CGFloat lineWidth = isPreiOS7 ? 5.f : 2.f;
UIBezierPath *processBackgroundPath = [UIBezierPath bezierPath];
processBackgroundPath.lineWidth = lineWidth;
processBackgroundPath.lineCapStyle = kCGLineCapButt;
CGPoint center = CGPointMake(CGRectGetMidX(self.bounds), CGRectGetMidY(self.bounds));
CGFloat radius = (self.bounds.size.width - lineWidth)/2;
CGFloat startAngle = - ((float)M_PI / 2); // 90 degrees
CGFloat endAngle = (2 * (float)M_PI) + startAngle;
[processBackgroundPath addArcWithCenter:center radius:radius startAngle:startAngle endAngle:endAngle clockwise:YES];
[_backgroundTintColor set];
[processBackgroundPath stroke];
// Draw progress
UIBezierPath *processPath = [UIBezierPath bezierPath];
processPath.lineCapStyle = isPreiOS7 ? kCGLineCapRound : kCGLineCapSquare;
processPath.lineWidth = lineWidth;
endAngle = (self.progress * 2 * (float)M_PI) + startAngle;
[processPath addArcWithCenter:center radius:radius startAngle:startAngle endAngle:endAngle clockwise:YES];
[_progressTintColor set];
[processPath stroke];
} else {
// Draw background
CGFloat lineWidth = 2.f;
CGRect allRect = self.bounds;
CGRect circleRect = CGRectInset(allRect, lineWidth/2.f, lineWidth/2.f);
CGPoint center = CGPointMake(CGRectGetMidX(self.bounds), CGRectGetMidY(self.bounds));
[_progressTintColor setStroke];
[_backgroundTintColor setFill];
CGContextSetLineWidth(context, lineWidth);
if (isPreiOS7) {
CGContextFillEllipseInRect(context, circleRect);
}
CGContextStrokeEllipseInRect(context, circleRect);
// 90 degrees
CGFloat startAngle = - ((float)M_PI / 2.f);
// Draw progress
if (isPreiOS7) {
CGFloat radius = (CGRectGetWidth(self.bounds) / 2.f) - lineWidth;
CGFloat endAngle = (self.progress * 2.f * (float)M_PI) + startAngle;
[_progressTintColor setFill];
CGContextMoveToPoint(context, center.x, center.y);
CGContextAddArc(context, center.x, center.y, radius, startAngle, endAngle, 0);
CGContextClosePath(context);
CGContextFillPath(context);
} else {
UIBezierPath *processPath = [UIBezierPath bezierPath];
processPath.lineCapStyle = kCGLineCapButt;
processPath.lineWidth = lineWidth * 2.f;
CGFloat radius = (CGRectGetWidth(self.bounds) / 2.f) - (processPath.lineWidth / 2.f);
CGFloat endAngle = (self.progress * 2.f * (float)M_PI) + startAngle;
[processPath addArcWithCenter:center radius:radius startAngle:startAngle endAngle:endAngle clockwise:YES];
// Ensure that we don't get color overlapping when _progressTintColor alpha < 1.f.
CGContextSetBlendMode(context, kCGBlendModeCopy);
[_progressTintColor set];
[processPath stroke];
}
}
}
@end
@implementation MBBarProgressView
#pragma mark - Lifecycle
- (id)init {
return [self initWithFrame:CGRectMake(.0f, .0f, 120.0f, 20.0f)];
}
- (id)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
_progress = 0.f;
_lineColor = [UIColor whiteColor];
_progressColor = [UIColor whiteColor];
_progressRemainingColor = [UIColor clearColor];
self.backgroundColor = [UIColor clearColor];
self.opaque = NO;
}
return self;
}
#pragma mark - Layout
- (CGSize)intrinsicContentSize {
BOOL isPreiOS7 = kCFCoreFoundationVersionNumber < kCFCoreFoundationVersionNumber_iOS_7_0;
return CGSizeMake(120.f, isPreiOS7 ? 20.f : 10.f);
}
#pragma mark - Properties
- (void)setProgress:(float)progress {
if (progress != _progress) {
_progress = progress;
[self setNeedsDisplay];
}
}
- (void)setProgressColor:(UIColor *)progressColor {
NSAssert(progressColor, @"The color should not be nil.");
if (progressColor != _progressColor && ![progressColor isEqual:_progressColor]) {
_progressColor = progressColor;
[self setNeedsDisplay];
}
}
- (void)setProgressRemainingColor:(UIColor *)progressRemainingColor {
NSAssert(progressRemainingColor, @"The color should not be nil.");
if (progressRemainingColor != _progressRemainingColor && ![progressRemainingColor isEqual:_progressRemainingColor]) {
_progressRemainingColor = progressRemainingColor;
[self setNeedsDisplay];
}
}
#pragma mark - Drawing
- (void)drawRect:(CGRect)rect {
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetLineWidth(context, 2);
CGContextSetStrokeColorWithColor(context,[_lineColor CGColor]);
CGContextSetFillColorWithColor(context, [_progressRemainingColor CGColor]);
// Draw background and Border
CGFloat radius = (rect.size.height / 2) - 2;
CGContextMoveToPoint(context, 2, rect.size.height/2);
CGContextAddArcToPoint(context, 2, 2, radius + 2, 2, radius);
CGContextAddArcToPoint(context, rect.size.width - 2, 2, rect.size.width - 2, rect.size.height / 2, radius);
CGContextAddArcToPoint(context, rect.size.width - 2, rect.size.height - 2, rect.size.width - radius - 2, rect.size.height - 2, radius);
CGContextAddArcToPoint(context, 2, rect.size.height - 2, 2, rect.size.height/2, radius);
CGContextDrawPath(context, kCGPathFillStroke);
CGContextSetFillColorWithColor(context, [_progressColor CGColor]);
radius = radius - 2;
CGFloat amount = self.progress * rect.size.width;
// Progress in the middle area
if (amount >= radius + 4 && amount <= (rect.size.width - radius - 4)) {
CGContextMoveToPoint(context, 4, rect.size.height/2);
CGContextAddArcToPoint(context, 4, 4, radius + 4, 4, radius);
CGContextAddLineToPoint(context, amount, 4);
CGContextAddLineToPoint(context, amount, radius + 4);
CGContextMoveToPoint(context, 4, rect.size.height/2);
CGContextAddArcToPoint(context, 4, rect.size.height - 4, radius + 4, rect.size.height - 4, radius);
CGContextAddLineToPoint(context, amount, rect.size.height - 4);
CGContextAddLineToPoint(context, amount, radius + 4);
CGContextFillPath(context);
}
// Progress in the right arc
else if (amount > radius + 4) {
CGFloat x = amount - (rect.size.width - radius - 4);
CGContextMoveToPoint(context, 4, rect.size.height/2);
CGContextAddArcToPoint(context, 4, 4, radius + 4, 4, radius);
CGContextAddLineToPoint(context, rect.size.width - radius - 4, 4);
CGFloat angle = -acos(x/radius);
if (isnan(angle)) angle = 0;
CGContextAddArc(context, rect.size.width - radius - 4, rect.size.height/2, radius, M_PI, angle, 0);
CGContextAddLineToPoint(context, amount, rect.size.height/2);
CGContextMoveToPoint(context, 4, rect.size.height/2);
CGContextAddArcToPoint(context, 4, rect.size.height - 4, radius + 4, rect.size.height - 4, radius);
CGContextAddLineToPoint(context, rect.size.width - radius - 4, rect.size.height - 4);
angle = acos(x/radius);
if (isnan(angle)) angle = 0;
CGContextAddArc(context, rect.size.width - radius - 4, rect.size.height/2, radius, -M_PI, angle, 1);
CGContextAddLineToPoint(context, amount, rect.size.height/2);
CGContextFillPath(context);
}
// Progress is in the left arc
else if (amount < radius + 4 && amount > 0) {
CGContextMoveToPoint(context, 4, rect.size.height/2);
CGContextAddArcToPoint(context, 4, 4, radius + 4, 4, radius);
CGContextAddLineToPoint(context, radius + 4, rect.size.height/2);
CGContextMoveToPoint(context, 4, rect.size.height/2);
CGContextAddArcToPoint(context, 4, rect.size.height - 4, radius + 4, rect.size.height - 4, radius);
CGContextAddLineToPoint(context, radius + 4, rect.size.height/2);
CGContextFillPath(context);
}
}
@end
@interface MBBackgroundView ()
#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 80000 || TARGET_OS_TV
@property UIVisualEffectView *effectView;
#endif
#if !TARGET_OS_TV
@property UIToolbar *toolbar;
#endif
@end
@implementation MBBackgroundView
#pragma mark - Lifecycle
- (instancetype)initWithFrame:(CGRect)frame {
if ((self = [super initWithFrame:frame])) {
if (kCFCoreFoundationVersionNumber >= kCFCoreFoundationVersionNumber_iOS_7_0) {
_style = MBProgressHUDBackgroundStyleBlur;
#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 80000 || TARGET_OS_TV
_blurEffectStyle = UIBlurEffectStyleLight;
#endif
if (kCFCoreFoundationVersionNumber >= kCFCoreFoundationVersionNumber_iOS_8_0) {
_color = [UIColor colorWithWhite:0.8f alpha:0.6f];
} else {
_color = [UIColor colorWithWhite:0.95f alpha:0.6f];
}
} else {
_style = MBProgressHUDBackgroundStyleSolidColor;
_color = [[UIColor blackColor] colorWithAlphaComponent:0.8];
}
self.clipsToBounds = YES;
[self updateForBackgroundStyle];
}
return self;
}
#pragma mark - Layout
- (CGSize)intrinsicContentSize {
// Smallest size possible. Content pushes against this.
return CGSizeZero;
}
#pragma mark - Appearance
- (void)setStyle:(MBProgressHUDBackgroundStyle)style {
if (style == MBProgressHUDBackgroundStyleBlur && kCFCoreFoundationVersionNumber < kCFCoreFoundationVersionNumber_iOS_7_0) {
style = MBProgressHUDBackgroundStyleSolidColor;
}
if (_style != style) {
_style = style;
[self updateForBackgroundStyle];
}
}
- (void)setColor:(UIColor *)color {
NSAssert(color, @"The color should not be nil.");
if (color != _color && ![color isEqual:_color]) {
_color = color;
[self updateViewsForColor:color];
}
}
#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 80000 || TARGET_OS_TV
- (void)setBlurEffectStyle:(UIBlurEffectStyle)blurEffectStyle {
if (_blurEffectStyle == blurEffectStyle) {
return;
}
_blurEffectStyle = blurEffectStyle;
[self updateForBackgroundStyle];
}
#endif
///////////////////////////////////////////////////////////////////////////////////////////
#pragma mark - Views
- (void)updateForBackgroundStyle {
MBProgressHUDBackgroundStyle style = self.style;
if (style == MBProgressHUDBackgroundStyleBlur) {
#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 80000 || TARGET_OS_TV
if (kCFCoreFoundationVersionNumber >= kCFCoreFoundationVersionNumber_iOS_8_0) {
UIBlurEffect *effect = [UIBlurEffect effectWithStyle:self.blurEffectStyle];
UIVisualEffectView *effectView = [[UIVisualEffectView alloc] initWithEffect:effect];
[self addSubview:effectView];
effectView.frame = self.bounds;
effectView.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth;
self.backgroundColor = self.color;
self.layer.allowsGroupOpacity = NO;
self.effectView = effectView;
} else {
#endif
#if !TARGET_OS_TV
UIToolbar *toolbar = [[UIToolbar alloc] initWithFrame:CGRectInset(self.bounds, -100.f, -100.f)];
toolbar.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth;
toolbar.barTintColor = self.color;
toolbar.translucent = YES;
[self addSubview:toolbar];
self.toolbar = toolbar;
#endif
#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 80000 || TARGET_OS_TV
}
#endif
} else {
#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 80000 || TARGET_OS_TV
if (kCFCoreFoundationVersionNumber >= kCFCoreFoundationVersionNumber_iOS_8_0) {
[self.effectView removeFromSuperview];
self.effectView = nil;
} else {
#endif
#if !TARGET_OS_TV
[self.toolbar removeFromSuperview];
self.toolbar = nil;
#endif
#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 80000 || TARGET_OS_TV
}
#endif
self.backgroundColor = self.color;
}
}
- (void)updateViewsForColor:(UIColor *)color {
if (self.style == MBProgressHUDBackgroundStyleBlur) {
if (kCFCoreFoundationVersionNumber >= kCFCoreFoundationVersionNumber_iOS_8_0) {
self.backgroundColor = self.color;
} else {
#if !TARGET_OS_TV
self.toolbar.barTintColor = color;
#endif
}
} else {
self.backgroundColor = self.color;
}
}
@end
@implementation MBProgressHUD (Deprecated)
#pragma mark - Class
+ (NSUInteger)hideAllHUDsForView:(UIView *)view animated:(BOOL)animated {
NSArray *huds = [MBProgressHUD allHUDsForView:view];
for (MBProgressHUD *hud in huds) {
hud.removeFromSuperViewOnHide = YES;
[hud hideAnimated:animated];
}
return [huds count];
}
+ (NSArray *)allHUDsForView:(UIView *)view {
NSMutableArray *huds = [NSMutableArray array];
NSArray *subviews = view.subviews;
for (UIView *aView in subviews) {
if ([aView isKindOfClass:self]) {
[huds addObject:aView];
}
}
return [NSArray arrayWithArray:huds];
}
#pragma mark - Lifecycle
- (id)initWithWindow:(UIWindow *)window {
return [self initWithView:window];
}
#pragma mark - Show & hide
- (void)show:(BOOL)animated {
[self showAnimated:animated];
}
- (void)hide:(BOOL)animated {
[self hideAnimated:animated];
}
- (void)hide:(BOOL)animated afterDelay:(NSTimeInterval)delay {
[self hideAnimated:animated afterDelay:delay];
}
#pragma mark - Threading
- (void)showWhileExecuting:(SEL)method onTarget:(id)target withObject:(id)object animated:(BOOL)animated {
[self showAnimated:animated whileExecutingBlock:^{
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
// Start executing the requested task
[target performSelector:method withObject:object];
#pragma clang diagnostic pop
}];
}
- (void)showAnimated:(BOOL)animated whileExecutingBlock:(dispatch_block_t)block {
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
[self showAnimated:animated whileExecutingBlock:block onQueue:queue completionBlock:NULL];
}
- (void)showAnimated:(BOOL)animated whileExecutingBlock:(dispatch_block_t)block completionBlock:(void (^)(void))completion {
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
[self showAnimated:animated whileExecutingBlock:block onQueue:queue completionBlock:completion];
}
- (void)showAnimated:(BOOL)animated whileExecutingBlock:(dispatch_block_t)block onQueue:(dispatch_queue_t)queue {
[self showAnimated:animated whileExecutingBlock:block onQueue:queue completionBlock:NULL];
}
- (void)showAnimated:(BOOL)animated whileExecutingBlock:(dispatch_block_t)block onQueue:(dispatch_queue_t)queue completionBlock:(nullable MBProgressHUDCompletionBlock)completion {
self.taskInProgress = YES;
self.completionBlock = completion;
dispatch_async(queue, ^(void) {
block();
dispatch_async(dispatch_get_main_queue(), ^(void) {
[self cleanUp];
});
});
[self showAnimated:animated];
}
- (void)cleanUp {
self.taskInProgress = NO;
[self hideAnimated:self.useAnimation];
}
#pragma mark - Labels
- (NSString *)labelText {
return self.label.text;
}
- (void)setLabelText:(NSString *)labelText {
MBMainThreadAssert();
self.label.text = labelText;
}
- (UIFont *)labelFont {
return self.label.font;
}
- (void)setLabelFont:(UIFont *)labelFont {
MBMainThreadAssert();
self.label.font = labelFont;
}
- (UIColor *)labelColor {
return self.label.textColor;
}
- (void)setLabelColor:(UIColor *)labelColor {
MBMainThreadAssert();
self.label.textColor = labelColor;
}
- (NSString *)detailsLabelText {
return self.detailsLabel.text;
}
- (void)setDetailsLabelText:(NSString *)detailsLabelText {
MBMainThreadAssert();
self.detailsLabel.text = detailsLabelText;
}
- (UIFont *)detailsLabelFont {
return self.detailsLabel.font;
}
- (void)setDetailsLabelFont:(UIFont *)detailsLabelFont {
MBMainThreadAssert();
self.detailsLabel.font = detailsLabelFont;
}
- (UIColor *)detailsLabelColor {
return self.detailsLabel.textColor;
}
- (void)setDetailsLabelColor:(UIColor *)detailsLabelColor {
MBMainThreadAssert();
self.detailsLabel.textColor = detailsLabelColor;
}
- (CGFloat)opacity {
return _opacity;
}
- (void)setOpacity:(CGFloat)opacity {
MBMainThreadAssert();
_opacity = opacity;
}
- (UIColor *)color {
return self.bezelView.color;
}
- (void)setColor:(UIColor *)color {
MBMainThreadAssert();
self.bezelView.color = color;
}
- (CGFloat)yOffset {
return self.offset.y;
}
- (void)setYOffset:(CGFloat)yOffset {
MBMainThreadAssert();
self.offset = CGPointMake(self.offset.x, yOffset);
}
- (CGFloat)xOffset {
return self.offset.x;
}
- (void)setXOffset:(CGFloat)xOffset {
MBMainThreadAssert();
self.offset = CGPointMake(xOffset, self.offset.y);
}
- (CGFloat)cornerRadius {
return self.bezelView.layer.cornerRadius;
}
- (void)setCornerRadius:(CGFloat)cornerRadius {
MBMainThreadAssert();
self.bezelView.layer.cornerRadius = cornerRadius;
}
- (BOOL)dimBackground {
MBBackgroundView *backgroundView = self.backgroundView;
UIColor *dimmedColor = [UIColor colorWithWhite:0.f alpha:.2f];
return backgroundView.style == MBProgressHUDBackgroundStyleSolidColor && [backgroundView.color isEqual:dimmedColor];
}
- (void)setDimBackground:(BOOL)dimBackground {
MBMainThreadAssert();
self.backgroundView.style = MBProgressHUDBackgroundStyleSolidColor;
self.backgroundView.color = dimBackground ? [UIColor colorWithWhite:0.f alpha:.2f] : [UIColor clearColor];
}
- (CGSize)size {
return self.bezelView.frame.size;
}
- (UIColor *)activityIndicatorColor {
return _activityIndicatorColor;
}
- (void)setActivityIndicatorColor:(UIColor *)activityIndicatorColor {
if (activityIndicatorColor != _activityIndicatorColor) {
_activityIndicatorColor = activityIndicatorColor;
UIActivityIndicatorView *indicator = (UIActivityIndicatorView *)self.indicator;
if ([indicator isKindOfClass:[UIActivityIndicatorView class]]) {
[indicator setColor:activityIndicatorColor];
}
}
}
@end
@implementation MBProgressHUDRoundedButton
#pragma mark - Lifecycle
- (instancetype)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
CALayer *layer = self.layer;
layer.borderWidth = 1.f;
}
return self;
}
#pragma mark - Layout
- (void)layoutSubviews {
[super layoutSubviews];
// Fully rounded corners
CGFloat height = CGRectGetHeight(self.bounds);
self.layer.cornerRadius = ceil(height / 2.f);
}
- (CGSize)intrinsicContentSize {
// Only show if we have associated control events
if (self.allControlEvents == 0) return CGSizeZero;
CGSize size = [super intrinsicContentSize];
// Add some side padding
size.width += 20.f;
return size;
}
#pragma mark - Color
- (void)setTitleColor:(UIColor *)color forState:(UIControlState)state {
[super setTitleColor:color forState:state];
// Update related colors
[self setHighlighted:self.highlighted];
self.layer.borderColor = color.CGColor;
}
- (void)setHighlighted:(BOOL)highlighted {
[super setHighlighted:highlighted];
UIColor *baseColor = [self titleColorForState:UIControlStateSelected];
self.backgroundColor = highlighted ? [baseColor colorWithAlphaComponent:0.1f] : [UIColor clearColor];
}
@end
# MBProgressHUD
[![Build Status](https://travis-ci.org/matej/MBProgressHUD.svg?branch=master)](https://travis-ci.org/matej/MBProgressHUD) [![codecov.io](https://codecov.io/github/matej/MBProgressHUD/coverage.svg?branch=master)](https://codecov.io/github/matej/MBProgressHUD?branch=master)
[![Carthage compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](https://github.com/Carthage/Carthage#adding-frameworks-to-an-application) [![CocoaPods compatible](https://img.shields.io/cocoapods/v/MBProgressHUD.svg?style=flat)](https://cocoapods.org/pods/MBProgressHUD) [![License: MIT](https://img.shields.io/cocoapods/l/MBProgressHUD.svg?style=flat)](http://opensource.org/licenses/MIT)
`MBProgressHUD` is an iOS drop-in class that displays a translucent HUD with an indicator and/or labels while work is being done in a background thread. The HUD is meant as a replacement for the undocumented, private `UIKit` `UIProgressHUD` with some additional features.
[![](https://raw.githubusercontent.com/wiki/matej/MBProgressHUD/Screenshots/1-small.png)](https://raw.githubusercontent.com/wiki/matej/MBProgressHUD/Screenshots/1.png)
[![](https://raw.githubusercontent.com/wiki/matej/MBProgressHUD/Screenshots/2-small.png)](https://raw.githubusercontent.com/wiki/matej/MBProgressHUD/Screenshots/2.png)
[![](https://raw.githubusercontent.com/wiki/matej/MBProgressHUD/Screenshots/3-small.png)](https://raw.githubusercontent.com/wiki/matej/MBProgressHUD/Screenshots/3.png)
[![](https://raw.githubusercontent.com/wiki/matej/MBProgressHUD/Screenshots/4-small.png)](https://raw.githubusercontent.com/wiki/matej/MBProgressHUD/Screenshots/4.png)
[![](https://raw.githubusercontent.com/wiki/matej/MBProgressHUD/Screenshots/5-small.png)](https://raw.githubusercontent.com/wiki/matej/MBProgressHUD/Screenshots/5.png)
[![](https://raw.githubusercontent.com/wiki/matej/MBProgressHUD/Screenshots/6-small.png)](https://raw.githubusercontent.com/wiki/matej/MBProgressHUD/Screenshots/6.png)
[![](https://raw.githubusercontent.com/wiki/matej/MBProgressHUD/Screenshots/7-small.png)](https://raw.githubusercontent.com/wiki/matej/MBProgressHUD/Screenshots/7.png)
**NOTE:** The class has recently undergone a major rewrite. The old version is available in the [legacy](https://github.com/jdg/MBProgressHUD/tree/legacy) branch, should you need it.
## Requirements
`MBProgressHUD` works on iOS 6+ and requires ARC to build. It depends on the following Apple frameworks, which should already be included with most Xcode templates:
* Foundation.framework
* UIKit.framework
* CoreGraphics.framework
You will need the latest developer tools in order to build `MBProgressHUD`. Old Xcode versions might work, but compatibility will not be explicitly maintained.
## Adding MBProgressHUD to your project
### CocoaPods
[CocoaPods](http://cocoapods.org) is the recommended way to add MBProgressHUD to your project.
1. Add a pod entry for MBProgressHUD to your Podfile `pod 'MBProgressHUD', '~> 1.1.0'`
2. Install the pod(s) by running `pod install`.
3. Include MBProgressHUD wherever you need it with `#import "MBProgressHUD.h"`.
### Carthage
1. Add MBProgressHUD to your Cartfile. e.g., `github "jdg/MBProgressHUD" ~> 1.1.0`
2. Run `carthage update`
3. Follow the rest of the [standard Carthage installation instructions](https://github.com/Carthage/Carthage#adding-frameworks-to-an-application) to add MBProgressHUD to your project.
### Source files
Alternatively you can directly add the `MBProgressHUD.h` and `MBProgressHUD.m` source files to your project.
1. Download the [latest code version](https://github.com/matej/MBProgressHUD/archive/master.zip) or add the repository as a git submodule to your git-tracked project.
2. Open your project in Xcode, then drag and drop `MBProgressHUD.h` and `MBProgressHUD.m` onto your project (use the "Product Navigator view"). Make sure to select Copy items when asked if you extracted the code archive outside of your project.
3. Include MBProgressHUD wherever you need it with `#import "MBProgressHUD.h"`.
### Static library
You can also add MBProgressHUD as a static library to your project or workspace.
1. Download the [latest code version](https://github.com/matej/MBProgressHUD/downloads) or add the repository as a git submodule to your git-tracked project.
2. Open your project in Xcode, then drag and drop `MBProgressHUD.xcodeproj` onto your project or workspace (use the "Product Navigator view").
3. Select your target and go to the Build phases tab. In the Link Binary With Libraries section select the add button. On the sheet find and add `libMBProgressHUD.a`. You might also need to add `MBProgressHUD` to the Target Dependencies list.
4. Include MBProgressHUD wherever you need it with `#import <MBProgressHUD/MBProgressHUD.h>`.
## Usage
The main guideline you need to follow when dealing with MBProgressHUD while running long-running tasks is keeping the main thread work-free, so the UI can be updated promptly. The recommended way of using MBProgressHUD is therefore to set it up on the main thread and then spinning the task, that you want to perform, off onto a new thread.
```objective-c
[MBProgressHUD showHUDAddedTo:self.view animated:YES];
dispatch_async(dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{
// Do something...
dispatch_async(dispatch_get_main_queue(), ^{
[MBProgressHUD hideHUDForView:self.view animated:YES];
});
});
```
You can add the HUD on any view or window. It is however a good idea to avoid adding the HUD to certain `UIKit` views with complex view hierarchies - like `UITableView` or `UICollectionView`. Those can mutate their subviews in unexpected ways and thereby break HUD display.
If you need to configure the HUD you can do this by using the MBProgressHUD reference that showHUDAddedTo:animated: returns.
```objective-c
MBProgressHUD *hud = [MBProgressHUD showHUDAddedTo:self.view animated:YES];
hud.mode = MBProgressHUDModeAnnularDeterminate;
hud.label.text = @"Loading";
[self doSomethingInBackgroundWithProgressCallback:^(float progress) {
hud.progress = progress;
} completionCallback:^{
[hud hideAnimated:YES];
}];
```
You can also use a `NSProgress` object and MBProgressHUD will update itself when there is progress reported through that object.
```objective-c
MBProgressHUD *hud = [MBProgressHUD showHUDAddedTo:self.view animated:YES];
hud.mode = MBProgressHUDModeAnnularDeterminate;
hud.label.text = @"Loading";
NSProgress *progress = [self doSomethingInBackgroundCompletion:^{
[hud hideAnimated:YES];
}];
hud.progressObject = progress;
```
Keep in mind that UI updates, inclining calls to MBProgressHUD should always be done on the main thread.
If you need to run your long-running task in the main thread, you should perform it with a slight delay, so UIKit will have enough time to update the UI (i.e., draw the HUD) before you block the main thread with your task.
```objective-c
[MBProgressHUD showHUDAddedTo:self.view animated:YES];
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, 0.01 * NSEC_PER_SEC);
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
// Do something...
[MBProgressHUD hideHUDForView:self.view animated:YES];
});
```
You should be aware that any HUD updates issued inside the above block won't be displayed until the block completes.
For more examples, including how to use MBProgressHUD with asynchronous operations such as NSURLConnection, take a look at the bundled demo project. Extensive API documentation is provided in the header file (MBProgressHUD.h).
## License
This code is distributed under the terms and conditions of the [MIT license](LICENSE).
## Change-log
A brief summary of each MBProgressHUD release can be found in the [CHANGELOG](CHANGELOG.mdown).
......@@ -2,7 +2,10 @@ PODS:
- BDOpenSDKKit (1.0.0)
- DouyinOpenSDK (1.4.1):
- BDOpenSDKKit (~> 1.0.0)
- GMCache (1.0.1):
- TMCache (= 2.1.0)
- GMFoundation (1.0.3)
- GMJSONModel (1.7.4)
- GMKit (1.1.6):
- GMKit/Category (= 1.1.6)
- GMKit/Color (= 1.1.6)
......@@ -37,16 +40,25 @@ PODS:
- GMKit/Protocol (1.1.6):
- Masonry
- SDWebImage
- GMPhobos (1.3.5):
- GMCache
- GMKit
- GMShareSDK (0.1.5):
- DouyinOpenSDK
- GMFoundation
- GMJSONModel
- GMKit
- GMPhobos
- Masonry
- MBProgressHUD
- WechatOpenSDK
- WeiboSDK
- Masonry (1.1.0)
- MBProgressHUD (1.1.0)
- SDWebImage (5.4.0):
- SDWebImage/Core (= 5.4.0)
- SDWebImage/Core (5.4.0)
- TMCache (2.1.0)
- WechatOpenSDK (1.8.6)
- WeiboSDK (3.1.3)
......@@ -55,13 +67,18 @@ DEPENDENCIES:
SPEC REPOS:
"git@git.wanmeizhensuo.com:gengmeiios/GMSpecs.git":
- GMCache
- GMFoundation
- GMJSONModel
- GMKit
- GMPhobos
https://github.com/cocoapods/specs.git:
- BDOpenSDKKit
- DouyinOpenSDK
- Masonry
- MBProgressHUD
- SDWebImage
- TMCache
- WechatOpenSDK
- WeiboSDK
......@@ -72,11 +89,16 @@ EXTERNAL SOURCES:
SPEC CHECKSUMS:
BDOpenSDKKit: 3fb530ce73f85a7d6ee69e7fd3d9158444c5bd09
DouyinOpenSDK: 5ba83de22963ba7a3ba70c8ff11dfcb2885ecc2b
GMCache: b78d8e46db864405e91d226ce640cc80d966c611
GMFoundation: 2bdf7cddf02e5251503274c9158ac1c851f2b8da
GMJSONModel: 5e81a98de668e9f93cf6ff77869f77b0d1a806be
GMKit: 2573350637f4d4e200c8cf3426b7b96a924af15e
GMShareSDK: 2d22c8133f1ad18f26cb7aad16a7bdb2193c1d04
GMPhobos: 1e2d68c456b69bf156276d7242877498107474db
GMShareSDK: 116ca081261a2cc1b2b1fbb9e2acae65dd9ffb46
Masonry: 678fab65091a9290e40e2832a55e7ab731aad201
MBProgressHUD: e7baa36a220447d8aeb12769bf0585582f3866d9
SDWebImage: 5bf6aec6481ae2a062bdc59f9d6c1d1e552090e0
TMCache: 95ebcc9b3c7e90fb5fd8fc3036cba3aa781c9bed
WechatOpenSDK: 368ae03b72ee3ea1328c4f11326fbb5d2721d118
WeiboSDK: acb067053668102cf07d01aa7604350162c2e466
......
This source diff could not be displayed because it is too large. You can view the blob instead.
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [2013] [Tumblr, Inc.]
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.
# TMCache
## Fast parallel object cache for iOS and OS X.
[![Build Status](https://img.shields.io/travis/tumblr/TMCache.svg?style=flat)](https://travis-ci.org/tumblr/XExtensionItem)
[![Version](http://img.shields.io/cocoapods/v/TMCache.svg?style=flat)](http://cocoapods.org/?q=XExtensionItem)
[![Platform](http://img.shields.io/cocoapods/p/TMCache.svg?style=flat)]()
[![License](http://img.shields.io/cocoapods/l/TMCache.svg?style=flat)](https://github.com/tumblr/XExtensionItem/blob/master/LICENSE)
[TMCache](TMCache/TMCache.h) is a key/value store designed for persisting temporary objects that are expensive to reproduce, such as downloaded data or the results of slow processing. It is comprised of two self-similar stores, one in memory ([TMMemoryCache](TMCache/TMMemoryCache.h)) and one on disk ([TMDiskCache](TMCache/TMDiskCache.h)), all backed by GCD and safe to access from multiple threads simultaneously. On iOS, `TMMemoryCache` will clear itself when the app receives a memory warning or goes into the background. Objects stored in `TMDiskCache` remain until you trim the cache yourself, either manually or by setting a byte or age limit.
`TMCache` and `TMDiskCache` accept any object conforming to [NSCoding](https://developer.apple.com/library/ios/#documentation/Cocoa/Reference/Foundation/Protocols/NSCoding_Protocol/Reference/Reference.html). Put things in like this:
```objective-c
UIImage *img = [[UIImage alloc] initWithData:data scale:[[UIScreen mainScreen] scale]];
[[TMCache sharedCache] setObject:img forKey:@"image" block:nil]; // returns immediately
```
Get them back out like this:
```objective-c
[[TMCache sharedCache] objectForKey:@"image"
block:^(TMCache *cache, NSString *key, id object) {
UIImage *image = (UIImage *)object;
NSLog(@"image scale: %f", image.scale);
}];
```
`TMMemoryCache` allows for concurrent reads and serialized writes, while `TMDiskCache` serializes disk access across all instances in the app to increase performance and prevent file contention. `TMCache` coordinates them so that objects added to memory are available immediately to other threads while being written to disk safely in the background. Both caches are public properties of `TMCache`, so it's easy to manipulate one or the other separately if necessary.
Collections work too. Thanks to the magic of `NSKeyedArchiver`, objects repeated in a collection only occupy the space of one on disk:
```objective-c
NSArray *images = @[ image, image, image ];
[[TMCache sharedCache] setObject:images forKey:@"images"];
NSLog(@"3 for the price of 1: %d", [[[TMCache sharedCache] diskCache] byteCount]);
```
## Installation
### Manually
[Download the latest tag](https://github.com/tumblr/TMCache/tags) and drag the `TMCache` folder into your Xcode project.
Install the docs by double clicking the `.docset` file under `docs/`, or view them online at [cocoadocs.org](http://cocoadocs.org/docsets/TMCache/)
### Git Submodule
git submodule add https://github.com/tumblr/TMCache.git
git submodule update --init
### CocoaPods
Add [TMCache](http://cocoapods.org/?q=name%3ATMCache) to your `Podfile` and run `pod install`.
## Requirements
__TMCache__ requires iOS 5.0 or OS X 10.7 and greater.
## Contributing
Please see [CONTRIBUTING.md](https://github.com/tumblr/XExtensionItem/blob/master/CONTRIBUTING.md) for information on how to help out.
## Contact
[Bryan Irace](mailto:bryan@tumblr.com)
## License
Copyright 2013 Tumblr, Inc.
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](LICENSE.txt) for the specific language governing permissions and limitations under the License.
/**
`TMCache` is a thread safe key/value store designed for persisting temporary objects that are expensive to
reproduce, such as downloaded data or the results of slow processing. It is comprised of two self-similar
stores, one in memory (<TMMemoryCache>) and one on disk (<TMDiskCache>).
`TMCache` itself actually does very little; its main function is providing a front end for a common use case:
a small, fast memory cache that asynchronously persists itself to a large, slow disk cache. When objects are
removed from the memory cache in response to an "apocalyptic" event they remain in the disk cache and are
repopulated in memory the next time they are accessed. `TMCache` also does the tedious work of creating a
dispatch group to wait for both caches to finish their operations without blocking each other.
The parallel caches are accessible as public properties (<memoryCache> and <diskCache>) and can be manipulated
separately if necessary. See the docs for <TMMemoryCache> and <TMDiskCache> for more details.
*/
#import <Foundation/Foundation.h>
#import "TMDiskCache.h"
#import "TMMemoryCache.h"
@class TMCache;
typedef void (^TMCacheBlock)(TMCache *cache);
typedef void (^TMCacheObjectBlock)(TMCache *cache, NSString *key, id object);
@interface TMCache : NSObject
#pragma mark -
/// @name Core
/**
The name of this cache, used to create the <diskCache> and also appearing in stack traces.
*/
@property (readonly) NSString *name;
/**
A concurrent queue on which blocks passed to the asynchronous access methods are run.
*/
@property (readonly) dispatch_queue_t queue;
/**
Synchronously retrieves the total byte count of the <diskCache> on the shared disk queue.
*/
@property (readonly) NSUInteger diskByteCount;
/**
The underlying disk cache, see <TMDiskCache> for additional configuration and trimming options.
*/
@property (readonly) TMDiskCache *diskCache;
/**
The underlying memory cache, see <TMMemoryCache> for additional configuration and trimming options.
*/
@property (readonly) TMMemoryCache *memoryCache;
#pragma mark -
/// @name Initialization
/**
A shared cache.
@result The shared singleton cache instance.
*/
+ (instancetype)sharedCache;
/**
Multiple instances with the same name are allowed and can safely access
the same data on disk thanks to the magic of seriality. Also used to create the <diskCache>.
@see name
@param name The name of the cache.
@result A new cache with the specified name.
*/
- (instancetype)initWithName:(NSString *)name;
/**
The designated initializer. Multiple instances with the same name are allowed and can safely access
the same data on disk thanks to the magic of seriality. Also used to create the <diskCache>.
@see name
@param name The name of the cache.
@param rootPath The path of the cache on disk.
@result A new cache with the specified name.
*/
- (instancetype)initWithName:(NSString *)name rootPath:(NSString *)rootPath;
#pragma mark -
/// @name Asynchronous Methods
/**
Retrieves the object for the specified key. This method returns immediately and executes the passed
block after the object is available, potentially in parallel with other blocks on the <queue>.
@param key The key associated with the requested object.
@param block A block to be executed concurrently when the object is available.
*/
- (void)objectForKey:(NSString *)key block:(TMCacheObjectBlock)block;
/**
Stores an object in the cache for the specified key. This method returns immediately and executes the
passed block after the object has been stored, potentially in parallel with other blocks on the <queue>.
@param object An object to store in the cache.
@param key A key to associate with the object. This string will be copied.
@param block A block to be executed concurrently after the object has been stored, or nil.
*/
- (void)setObject:(id <NSCoding>)object forKey:(NSString *)key block:(TMCacheObjectBlock)block;
/**
Removes the object for the specified key. This method returns immediately and executes the passed
block after the object has been removed, potentially in parallel with other blocks on the <queue>.
@param key The key associated with the object to be removed.
@param block A block to be executed concurrently after the object has been removed, or nil.
*/
- (void)removeObjectForKey:(NSString *)key block:(TMCacheObjectBlock)block;
/**
Removes all objects from the cache that have not been used since the specified date. This method returns immediately and
executes the passed block after the cache has been trimmed, potentially in parallel with other blocks on the <queue>.
@param date Objects that haven't been accessed since this date are removed from the cache.
@param block A block to be executed concurrently after the cache has been trimmed, or nil.
*/
- (void)trimToDate:(NSDate *)date block:(TMCacheBlock)block;
/**
Removes all objects from the cache.This method returns immediately and executes the passed block after the
cache has been cleared, potentially in parallel with other blocks on the <queue>.
@param block A block to be executed concurrently after the cache has been cleared, or nil.
*/
- (void)removeAllObjects:(TMCacheBlock)block;
#pragma mark -
/// @name Synchronous Methods
/**
Retrieves the object for the specified key. This method blocks the calling thread until the object is available.
@see objectForKey:block:
@param key The key associated with the object.
@result The object for the specified key.
*/
- (id)objectForKey:(NSString *)key;
/**
Stores an object in the cache for the specified key. This method blocks the calling thread until the object has been set.
@see setObject:forKey:block:
@param object An object to store in the cache.
@param key A key to associate with the object. This string will be copied.
*/
- (void)setObject:(id <NSCoding>)object forKey:(NSString *)key;
/**
Removes the object for the specified key. This method blocks the calling thread until the object
has been removed.
@param key The key associated with the object to be removed.
*/
- (void)removeObjectForKey:(NSString *)key;
/**
Removes all objects from the cache that have not been used since the specified date.
This method blocks the calling thread until the cache has been trimmed.
@param date Objects that haven't been accessed since this date are removed from the cache.
*/
- (void)trimToDate:(NSDate *)date;
/**
Removes all objects from the cache. This method blocks the calling thread until the cache has been cleared.
*/
- (void)removeAllObjects;
@end
#import "TMCache.h"
NSString * const TMCachePrefix = @"com.tumblr.TMCache";
NSString * const TMCacheSharedName = @"TMCacheShared";
@interface TMCache ()
#if OS_OBJECT_USE_OBJC
@property (strong, nonatomic) dispatch_queue_t queue;
#else
@property (assign, nonatomic) dispatch_queue_t queue;
#endif
@end
@implementation TMCache
#pragma mark - Initialization -
#if !OS_OBJECT_USE_OBJC
- (void)dealloc
{
dispatch_release(_queue);
_queue = nil;
}
#endif
- (instancetype)initWithName:(NSString *)name
{
return [self initWithName:name rootPath:[NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) objectAtIndex:0]];
}
- (instancetype)initWithName:(NSString *)name rootPath:(NSString *)rootPath
{
if (!name)
return nil;
if (self = [super init]) {
_name = [name copy];
NSString *queueName = [[NSString alloc] initWithFormat:@"%@.%p", TMCachePrefix, self];
_queue = dispatch_queue_create([queueName UTF8String], DISPATCH_QUEUE_CONCURRENT);
_diskCache = [[TMDiskCache alloc] initWithName:_name rootPath:rootPath];
_memoryCache = [[TMMemoryCache alloc] init];
}
return self;
}
- (NSString *)description
{
return [[NSString alloc] initWithFormat:@"%@.%@.%p", TMCachePrefix, _name, self];
}
+ (instancetype)sharedCache
{
static id cache;
static dispatch_once_t predicate;
dispatch_once(&predicate, ^{
cache = [[self alloc] initWithName:TMCacheSharedName];
});
return cache;
}
#pragma mark - Public Asynchronous Methods -
- (void)objectForKey:(NSString *)key block:(TMCacheObjectBlock)block
{
if (!key || !block)
return;
__weak TMCache *weakSelf = self;
dispatch_async(_queue, ^{
TMCache *strongSelf = weakSelf;
if (!strongSelf)
return;
__weak TMCache *weakSelf = strongSelf;
[strongSelf->_memoryCache objectForKey:key block:^(TMMemoryCache *cache, NSString *key, id object) {
TMCache *strongSelf = weakSelf;
if (!strongSelf)
return;
if (object) {
[strongSelf->_diskCache fileURLForKey:key block:^(TMDiskCache *cache, NSString *key, id <NSCoding> object, NSURL *fileURL) {
// update the access time on disk
}];
__weak TMCache *weakSelf = strongSelf;
dispatch_async(strongSelf->_queue, ^{
TMCache *strongSelf = weakSelf;
if (strongSelf)
block(strongSelf, key, object);
});
} else {
__weak TMCache *weakSelf = strongSelf;
[strongSelf->_diskCache objectForKey:key block:^(TMDiskCache *cache, NSString *key, id <NSCoding> object, NSURL *fileURL) {
TMCache *strongSelf = weakSelf;
if (!strongSelf)
return;
[strongSelf->_memoryCache setObject:object forKey:key block:nil];
__weak TMCache *weakSelf = strongSelf;
dispatch_async(strongSelf->_queue, ^{
TMCache *strongSelf = weakSelf;
if (strongSelf)
block(strongSelf, key, object);
});
}];
}
}];
});
}
- (void)setObject:(id <NSCoding>)object forKey:(NSString *)key block:(TMCacheObjectBlock)block
{
if (!key || !object)
return;
dispatch_group_t group = nil;
TMMemoryCacheObjectBlock memBlock = nil;
TMDiskCacheObjectBlock diskBlock = nil;
if (block) {
group = dispatch_group_create();
dispatch_group_enter(group);
dispatch_group_enter(group);
memBlock = ^(TMMemoryCache *cache, NSString *key, id object) {
dispatch_group_leave(group);
};
diskBlock = ^(TMDiskCache *cache, NSString *key, id <NSCoding> object, NSURL *fileURL) {
dispatch_group_leave(group);
};
}
[_memoryCache setObject:object forKey:key block:memBlock];
[_diskCache setObject:object forKey:key block:diskBlock];
if (group) {
__weak TMCache *weakSelf = self;
dispatch_group_notify(group, _queue, ^{
TMCache *strongSelf = weakSelf;
if (strongSelf)
block(strongSelf, key, object);
});
#if !OS_OBJECT_USE_OBJC
dispatch_release(group);
#endif
}
}
- (void)removeObjectForKey:(NSString *)key block:(TMCacheObjectBlock)block
{
if (!key)
return;
dispatch_group_t group = nil;
TMMemoryCacheObjectBlock memBlock = nil;
TMDiskCacheObjectBlock diskBlock = nil;
if (block) {
group = dispatch_group_create();
dispatch_group_enter(group);
dispatch_group_enter(group);
memBlock = ^(TMMemoryCache *cache, NSString *key, id object) {
dispatch_group_leave(group);
};
diskBlock = ^(TMDiskCache *cache, NSString *key, id <NSCoding> object, NSURL *fileURL) {
dispatch_group_leave(group);
};
}
[_memoryCache removeObjectForKey:key block:memBlock];
[_diskCache removeObjectForKey:key block:diskBlock];
if (group) {
__weak TMCache *weakSelf = self;
dispatch_group_notify(group, _queue, ^{
TMCache *strongSelf = weakSelf;
if (strongSelf)
block(strongSelf, key, nil);
});
#if !OS_OBJECT_USE_OBJC
dispatch_release(group);
#endif
}
}
- (void)removeAllObjects:(TMCacheBlock)block
{
dispatch_group_t group = nil;
TMMemoryCacheBlock memBlock = nil;
TMDiskCacheBlock diskBlock = nil;
if (block) {
group = dispatch_group_create();
dispatch_group_enter(group);
dispatch_group_enter(group);
memBlock = ^(TMMemoryCache *cache) {
dispatch_group_leave(group);
};
diskBlock = ^(TMDiskCache *cache) {
dispatch_group_leave(group);
};
}
[_memoryCache removeAllObjects:memBlock];
[_diskCache removeAllObjects:diskBlock];
if (group) {
__weak TMCache *weakSelf = self;
dispatch_group_notify(group, _queue, ^{
TMCache *strongSelf = weakSelf;
if (strongSelf)
block(strongSelf);
});
#if !OS_OBJECT_USE_OBJC
dispatch_release(group);
#endif
}
}
- (void)trimToDate:(NSDate *)date block:(TMCacheBlock)block
{
if (!date)
return;
dispatch_group_t group = nil;
TMMemoryCacheBlock memBlock = nil;
TMDiskCacheBlock diskBlock = nil;
if (block) {
group = dispatch_group_create();
dispatch_group_enter(group);
dispatch_group_enter(group);
memBlock = ^(TMMemoryCache *cache) {
dispatch_group_leave(group);
};
diskBlock = ^(TMDiskCache *cache) {
dispatch_group_leave(group);
};
}
[_memoryCache trimToDate:date block:memBlock];
[_diskCache trimToDate:date block:diskBlock];
if (group) {
__weak TMCache *weakSelf = self;
dispatch_group_notify(group, _queue, ^{
TMCache *strongSelf = weakSelf;
if (strongSelf)
block(strongSelf);
});
#if !OS_OBJECT_USE_OBJC
dispatch_release(group);
#endif
}
}
#pragma mark - Public Synchronous Accessors -
- (NSUInteger)diskByteCount
{
__block NSUInteger byteCount = 0;
dispatch_sync([TMDiskCache sharedQueue], ^{
byteCount = self.diskCache.byteCount;
});
return byteCount;
}
#pragma mark - Public Synchronous Methods -
- (id)objectForKey:(NSString *)key
{
if (!key)
return nil;
__block id objectForKey = nil;
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
[self objectForKey:key block:^(TMCache *cache, NSString *key, id object) {
objectForKey = object;
dispatch_semaphore_signal(semaphore);
}];
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
#if !OS_OBJECT_USE_OBJC
dispatch_release(semaphore);
#endif
return objectForKey;
}
- (void)setObject:(id <NSCoding>)object forKey:(NSString *)key
{
if (!object || !key)
return;
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
[self setObject:object forKey:key block:^(TMCache *cache, NSString *key, id object) {
dispatch_semaphore_signal(semaphore);
}];
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
#if !OS_OBJECT_USE_OBJC
dispatch_release(semaphore);
#endif
}
- (void)removeObjectForKey:(NSString *)key
{
if (!key)
return;
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
[self removeObjectForKey:key block:^(TMCache *cache, NSString *key, id object) {
dispatch_semaphore_signal(semaphore);
}];
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
#if !OS_OBJECT_USE_OBJC
dispatch_release(semaphore);
#endif
}
- (void)trimToDate:(NSDate *)date
{
if (!date)
return;
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
[self trimToDate:date block:^(TMCache *cache) {
dispatch_semaphore_signal(semaphore);
}];
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
#if !OS_OBJECT_USE_OBJC
dispatch_release(semaphore);
#endif
}
- (void)removeAllObjects
{
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
[self removeAllObjects:^(TMCache *cache) {
dispatch_semaphore_signal(semaphore);
}];
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
#if !OS_OBJECT_USE_OBJC
dispatch_release(semaphore);
#endif
}
@end
// HC SVNT DRACONES
//
// TMCacheBackgroundTaskManager.h
// TMCache
//
// Created by Bryan Irace on 4/24/15.
// Copyright (c) 2015 Tumblr. All rights reserved.
//
#if __IPHONE_OS_VERSION_MIN_REQUIRED >= __IPHONE_4_0
#import <UIKit/UIKit.h>
#else
typedef NSUInteger UIBackgroundTaskIdentifier;
#endif
/**
A protocol that classes who can begin and end background tasks can conform to. This protocol provides an abstraction in
order to avoid referencing `+ [UIApplication sharedApplication]` from within an iOS application extension.
*/
@protocol TMCacheBackgroundTaskManager <NSObject>
/**
Marks the beginning of a new long-running background task.
@return A unique identifier for the new background task. You must pass this value to the `endBackgroundTask:` method to
mark the end of this task. This method returns `UIBackgroundTaskInvalid` if running in the background is not possible.
*/
- (UIBackgroundTaskIdentifier)beginBackgroundTask;
/**
Marks the end of a specific long-running background task.
@param identifier An identifier returned by the `beginBackgroundTaskWithExpirationHandler:` method.
*/
- (void)endBackgroundTask:(UIBackgroundTaskIdentifier)identifier;
@end
/**
`TMDiskCache` is a thread safe key/value store backed by the file system. It accepts any object conforming
to the `NSCoding` protocol, which includes the basic Foundation data types and collection classes and also
many UIKit classes, notably `UIImage`. All work is performed on a serial queue shared by all instances in
the app, and archiving is handled by `NSKeyedArchiver`. This is a particular advantage for `UIImage` because
it skips `UIImagePNGRepresentation()` and retains information like scale and orientation.
The designated initializer for `TMDiskCache` is <initWithName:>. The <name> string is used to create a directory
under Library/Caches that scopes disk access for any instance sharing this name. Multiple instances with the
same name are allowed because all disk access is serialized on the <sharedQueue>. The <name> also appears in
stack traces and return value for `description:`.
Unless otherwise noted, all properties and methods are safe to access from any thread at any time. All blocks
will cause the queue to wait, making it safe to access and manipulate the actual cache files on disk for the
duration of the block. In addition, the <sharedQueue> can be set to target an existing serial I/O queue, should
your app already have one.
Because this cache is bound by disk I/O it can be much slower than <TMMemoryCache>, although values stored in
`TMDiskCache` persist after application relaunch. Using <TMCache> is recommended over using `TMDiskCache`
by itself, as it adds a fast layer of additional memory caching while still writing to disk.
All access to the cache is dated so the that the least-used objects can be trimmed first. Setting an optional
<ageLimit> will trigger a GCD timer to periodically to trim the cache with <trimToDate:>.
*/
#import <Foundation/Foundation.h>
@class TMDiskCache;
@protocol TMCacheBackgroundTaskManager;
typedef void (^TMDiskCacheBlock)(TMDiskCache *cache);
typedef void (^TMDiskCacheObjectBlock)(TMDiskCache *cache, NSString *key, id <NSCoding> object, NSURL *fileURL);
@interface TMDiskCache : NSObject
#pragma mark -
/// @name Core
/**
The name of this cache, used to create a directory under Library/Caches and also appearing in stack traces.
*/
@property (readonly) NSString *name;
/**
The URL of the directory used by this cache, usually `Library/Caches/com.tumblr.TMDiskCache.(name)`
@warning Do not interact with files under this URL except on the <sharedQueue>.
*/
@property (readonly) NSURL *cacheURL;
/**
The total number of bytes used on disk, as reported by `NSURLTotalFileAllocatedSizeKey`.
@warning This property is technically safe to access from any thread, but it reflects the value *right now*,
not taking into account any pending operations. In most cases this value should only be read from a block on the
<sharedQueue>, which will ensure its accuracy and prevent it from changing during the lifetime of the block.
For example:
// some background thread, not a block already running on the shared queue
dispatch_sync([TMDiskCache sharedQueue], ^{
NSLog(@"accurate, unchanging byte count: %d", [[TMDiskCache sharedCache] byteCount]);
});
*/
@property (readonly) NSUInteger byteCount;
/**
The maximum number of bytes allowed on disk. This value is checked every time an object is set, if the written
size exceeds the limit a trim call is queued. Defaults to `0.0`, meaning no practical limit.
@warning Do not read this property on the <sharedQueue> (including asynchronous method blocks).
*/
@property (assign) NSUInteger byteLimit;
/**
The maximum number of seconds an object is allowed to exist in the cache. Setting this to a value
greater than `0.0` will start a recurring GCD timer with the same period that calls <trimToDate:>.
Setting it back to `0.0` will stop the timer. Defaults to `0.0`, meaning no limit.
@warning Do not read this property on the <sharedQueue> (including asynchronous method blocks).
*/
@property (assign) NSTimeInterval ageLimit;
#pragma mark -
/// @name Event Blocks
/**
A block to be executed just before an object is added to the cache. The queue waits during execution.
*/
@property (copy) TMDiskCacheObjectBlock willAddObjectBlock;
/**
A block to be executed just before an object is removed from the cache. The queue waits during execution.
*/
@property (copy) TMDiskCacheObjectBlock willRemoveObjectBlock;
/**
A block to be executed just before all objects are removed from the cache as a result of <removeAllObjects:>.
The queue waits during execution.
*/
@property (copy) TMDiskCacheBlock willRemoveAllObjectsBlock;
/**
A block to be executed just after an object is added to the cache. The queue waits during execution.
*/
@property (copy) TMDiskCacheObjectBlock didAddObjectBlock;
/**
A block to be executed just after an object is removed from the cache. The queue waits during execution.
*/
@property (copy) TMDiskCacheObjectBlock didRemoveObjectBlock;
/**
A block to be executed just after all objects are removed from the cache as a result of <removeAllObjects:>.
The queue waits during execution.
*/
@property (copy) TMDiskCacheBlock didRemoveAllObjectsBlock;
#pragma mark -
/// @name Initialization
/**
A shared cache.
@result The shared singleton cache instance.
*/
+ (instancetype)sharedCache;
/**
A shared serial queue, used by all instances of this class. Use `dispatch_set_target_queue` to integrate
this queue with an exisiting serial I/O queue.
@result The shared singleton queue instance.
*/
+ (dispatch_queue_t)sharedQueue;
/**
Empties the trash with `DISPATCH_QUEUE_PRIORITY_BACKGROUND`. Does not block the <sharedQueue>.
*/
+ (void)emptyTrash;
/**
Multiple instances with the same name are allowed and can safely access
the same data on disk thanks to the magic of seriality.
@see name
@param name The name of the cache.
@result A new cache with the specified name.
*/
- (instancetype)initWithName:(NSString *)name;
/**
The designated initializer. Multiple instances with the same name are allowed and can safely access
the same data on disk thanks to the magic of seriality.
@see name
@param name The name of the cache.
@param rootPath The path of the cache.
@result A new cache with the specified name.
*/
- (instancetype)initWithName:(NSString *)name rootPath:(NSString *)rootPath;
#pragma mark -
/// @name Asynchronous Methods
/**
Retrieves the object for the specified key. This method returns immediately and executes the passed
block as soon as the object is available on the serial <sharedQueue>.
@warning The fileURL is only valid for the duration of this block, do not use it after the block ends.
@param key The key associated with the requested object.
@param block A block to be executed serially when the object is available.
*/
- (void)objectForKey:(NSString *)key block:(TMDiskCacheObjectBlock)block;
/**
Retrieves the fileURL for the specified key without actually reading the data from disk. This method
returns immediately and executes the passed block as soon as the object is available on the serial
<sharedQueue>.
@warning Access is protected for the duration of the block, but to maintain safe disk access do not
access this fileURL after the block has ended. Do all work on the <sharedQueue>.
@param key The key associated with the requested object.
@param block A block to be executed serially when the file URL is available.
*/
- (void)fileURLForKey:(NSString *)key block:(TMDiskCacheObjectBlock)block;
/**
Stores an object in the cache for the specified key. This method returns immediately and executes the
passed block as soon as the object has been stored.
@param object An object to store in the cache.
@param key A key to associate with the object. This string will be copied.
@param block A block to be executed serially after the object has been stored, or nil.
*/
- (void)setObject:(id <NSCoding>)object forKey:(NSString *)key block:(TMDiskCacheObjectBlock)block;
/**
Removes the object for the specified key. This method returns immediately and executes the passed block
as soon as the object has been removed.
@param key The key associated with the object to be removed.
@param block A block to be executed serially after the object has been removed, or nil.
*/
- (void)removeObjectForKey:(NSString *)key block:(TMDiskCacheObjectBlock)block;
/**
Removes all objects from the cache that have not been used since the specified date.
This method returns immediately and executes the passed block as soon as the cache has been trimmed.
@param date Objects that haven't been accessed since this date are removed from the cache.
@param block A block to be executed serially after the cache has been trimmed, or nil.
*/
- (void)trimToDate:(NSDate *)date block:(TMDiskCacheBlock)block;
/**
Removes objects from the cache, largest first, until the cache is equal to or smaller than the specified byteCount.
This method returns immediately and executes the passed block as soon as the cache has been trimmed.
@param byteCount The cache will be trimmed equal to or smaller than this size.
@param block A block to be executed serially after the cache has been trimmed, or nil.
*/
- (void)trimToSize:(NSUInteger)byteCount block:(TMDiskCacheBlock)block;
/**
Removes objects from the cache, ordered by date (least recently used first), until the cache is equal to or smaller
than the specified byteCount. This method returns immediately and executes the passed block as soon as the cache has
been trimmed.
@param byteCount The cache will be trimmed equal to or smaller than this size.
@param block A block to be executed serially after the cache has been trimmed, or nil.
*/
- (void)trimToSizeByDate:(NSUInteger)byteCount block:(TMDiskCacheBlock)block;
/**
Removes all objects from the cache. This method returns immediately and executes the passed block as soon as the
cache has been cleared.
@param block A block to be executed serially after the cache has been cleared, or nil.
*/
- (void)removeAllObjects:(TMDiskCacheBlock)block;
/**
Loops through all objects in the cache (reads and writes are suspended during the enumeration). Data is not actually
read from disk, the `object` parameter of the block will be `nil` but the `fileURL` will be available.
This method returns immediately.
@param block A block to be executed for every object in the cache.
@param completionBlock An optional block to be executed after the enumeration is complete.
*/
- (void)enumerateObjectsWithBlock:(TMDiskCacheObjectBlock)block completionBlock:(TMDiskCacheBlock)completionBlock;
#pragma mark -
/// @name Synchronous Methods
/**
Retrieves the object for the specified key. This method blocks the calling thread until the
object is available.
@see objectForKey:block:
@param key The key associated with the object.
@result The object for the specified key.
*/
- (id <NSCoding>)objectForKey:(NSString *)key;
/**
Retrieves the file URL for the specified key. This method blocks the calling thread until the
url is available. Do not use this URL anywhere but on the <sharedQueue>. This method probably
shouldn't even exist, just use the asynchronous one.
@see fileURLForKey:block:
@param key The key associated with the object.
@result The file URL for the specified key.
*/
- (NSURL *)fileURLForKey:(NSString *)key;
/**
Stores an object in the cache for the specified key. This method blocks the calling thread until
the object has been stored.
@see setObject:forKey:block:
@param object An object to store in the cache.
@param key A key to associate with the object. This string will be copied.
*/
- (void)setObject:(id <NSCoding>)object forKey:(NSString *)key;
/**
Removes the object for the specified key. This method blocks the calling thread until the object
has been removed.
@param key The key associated with the object to be removed.
*/
- (void)removeObjectForKey:(NSString *)key;
/**
Removes all objects from the cache that have not been used since the specified date.
This method blocks the calling thread until the cache has been trimmed.
@param date Objects that haven't been accessed since this date are removed from the cache.
*/
- (void)trimToDate:(NSDate *)date;
/**
Removes objects from the cache, largest first, until the cache is equal to or smaller than the
specified byteCount. This method blocks the calling thread until the cache has been trimmed.
@param byteCount The cache will be trimmed equal to or smaller than this size.
*/
- (void)trimToSize:(NSUInteger)byteCount;
/**
Removes objects from the cache, ordered by date (least recently used first), until the cache is equal to or
smaller than the specified byteCount. This method blocks the calling thread until the cache has been trimmed.
@param byteCount The cache will be trimmed equal to or smaller than this size.
*/
- (void)trimToSizeByDate:(NSUInteger)byteCount;
/**
Removes all objects from the cache. This method blocks the calling thread until the cache has been cleared.
*/
- (void)removeAllObjects;
/**
Loops through all objects in the cache (reads and writes are suspended during the enumeration). Data is not actually
read from disk, the `object` parameter of the block will be `nil` but the `fileURL` will be available.
This method blocks the calling thread until all objects have been enumerated.
@param block A block to be executed for every object in the cache.
@warning Do not call this method within the event blocks (<didRemoveObjectBlock>, etc.)
Instead use the asynchronous version, <enumerateObjectsWithBlock:completionBlock:>.
*/
- (void)enumerateObjectsWithBlock:(TMDiskCacheObjectBlock)block;
#pragma mark -
/// @name Background Tasks
/**
Set a global manager to be used for setting up/tearing down any background tasks needed by TMCache.
@param backgroundTaskManager Background task manager.
*/
+ (void)setBackgroundTaskManager:(id <TMCacheBackgroundTaskManager>)backgroundTaskManager;
@end
#import "TMDiskCache.h"
#import "TMCacheBackgroundTaskManager.h"
#if __IPHONE_OS_VERSION_MIN_REQUIRED >= __IPHONE_4_0
#import <UIKit/UIKit.h>
#endif
#define TMDiskCacheError(error) if (error) { NSLog(@"%@ (%d) ERROR: %@", \
[[NSString stringWithUTF8String:__FILE__] lastPathComponent], \
__LINE__, [error localizedDescription]); }
static id <TMCacheBackgroundTaskManager> TMCacheBackgroundTaskManager;
NSString * const TMDiskCachePrefix = @"com.tumblr.TMDiskCache";
NSString * const TMDiskCacheSharedName = @"TMDiskCacheShared";
@interface TMDiskCache ()
@property (assign) NSUInteger byteCount;
@property (strong, nonatomic) NSURL *cacheURL;
@property (assign, nonatomic) dispatch_queue_t queue;
@property (strong, nonatomic) NSMutableDictionary *dates;
@property (strong, nonatomic) NSMutableDictionary *sizes;
@end
@implementation TMDiskCache
@synthesize willAddObjectBlock = _willAddObjectBlock;
@synthesize willRemoveObjectBlock = _willRemoveObjectBlock;
@synthesize willRemoveAllObjectsBlock = _willRemoveAllObjectsBlock;
@synthesize didAddObjectBlock = _didAddObjectBlock;
@synthesize didRemoveObjectBlock = _didRemoveObjectBlock;
@synthesize didRemoveAllObjectsBlock = _didRemoveAllObjectsBlock;
@synthesize byteLimit = _byteLimit;
@synthesize ageLimit = _ageLimit;
#pragma mark - Initialization -
- (instancetype)initWithName:(NSString *)name
{
return [self initWithName:name rootPath:[NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) objectAtIndex:0]];
}
- (instancetype)initWithName:(NSString *)name rootPath:(NSString *)rootPath
{
if (!name)
return nil;
if (self = [super init]) {
_name = [name copy];
_queue = [TMDiskCache sharedQueue];
_willAddObjectBlock = nil;
_willRemoveObjectBlock = nil;
_willRemoveAllObjectsBlock = nil;
_didAddObjectBlock = nil;
_didRemoveObjectBlock = nil;
_didRemoveAllObjectsBlock = nil;
_byteCount = 0;
_byteLimit = 0;
_ageLimit = 0.0;
_dates = [[NSMutableDictionary alloc] init];
_sizes = [[NSMutableDictionary alloc] init];
NSString *pathComponent = [[NSString alloc] initWithFormat:@"%@.%@", TMDiskCachePrefix, _name];
_cacheURL = [NSURL fileURLWithPathComponents:@[ rootPath, pathComponent ]];
__weak TMDiskCache *weakSelf = self;
dispatch_async(_queue, ^{
TMDiskCache *strongSelf = weakSelf;
[strongSelf createCacheDirectory];
[strongSelf initializeDiskProperties];
});
}
return self;
}
- (NSString *)description
{
return [[NSString alloc] initWithFormat:@"%@.%@.%p", TMDiskCachePrefix, _name, self];
}
+ (instancetype)sharedCache
{
static id cache;
static dispatch_once_t predicate;
dispatch_once(&predicate, ^{
cache = [[self alloc] initWithName:TMDiskCacheSharedName];
});
return cache;
}
+ (dispatch_queue_t)sharedQueue
{
static dispatch_queue_t queue;
static dispatch_once_t predicate;
dispatch_once(&predicate, ^{
queue = dispatch_queue_create([TMDiskCachePrefix UTF8String], DISPATCH_QUEUE_SERIAL);
});
return queue;
}
#pragma mark - Private Methods -
- (NSURL *)encodedFileURLForKey:(NSString *)key
{
if (![key length])
return nil;
return [_cacheURL URLByAppendingPathComponent:[self encodedString:key]];
}
- (NSString *)keyForEncodedFileURL:(NSURL *)url
{
NSString *fileName = [url lastPathComponent];
if (!fileName)
return nil;
return [self decodedString:fileName];
}
- (NSString *)encodedString:(NSString *)string
{
if (![string length])
return @"";
CFStringRef static const charsToEscape = CFSTR(".:/");
CFStringRef escapedString = CFURLCreateStringByAddingPercentEscapes(kCFAllocatorDefault,
(__bridge CFStringRef)string,
NULL,
charsToEscape,
kCFStringEncodingUTF8);
return (__bridge_transfer NSString *)escapedString;
}
- (NSString *)decodedString:(NSString *)string
{
if (![string length])
return @"";
CFStringRef unescapedString = CFURLCreateStringByReplacingPercentEscapesUsingEncoding(kCFAllocatorDefault,
(__bridge CFStringRef)string,
CFSTR(""),
kCFStringEncodingUTF8);
return (__bridge_transfer NSString *)unescapedString;
}
#pragma mark - Private Trash Methods -
+ (dispatch_queue_t)sharedTrashQueue
{
static dispatch_queue_t trashQueue;
static dispatch_once_t predicate;
dispatch_once(&predicate, ^{
NSString *queueName = [[NSString alloc] initWithFormat:@"%@.trash", TMDiskCachePrefix];
trashQueue = dispatch_queue_create([queueName UTF8String], DISPATCH_QUEUE_SERIAL);
dispatch_set_target_queue(trashQueue, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0));
});
return trashQueue;
}
+ (NSURL *)sharedTrashURL
{
static NSURL *sharedTrashURL;
static dispatch_once_t predicate;
dispatch_once(&predicate, ^{
sharedTrashURL = [[[NSURL alloc] initFileURLWithPath:NSTemporaryDirectory()] URLByAppendingPathComponent:TMDiskCachePrefix isDirectory:YES];
if (![[NSFileManager defaultManager] fileExistsAtPath:[sharedTrashURL path]]) {
NSError *error = nil;
[[NSFileManager defaultManager] createDirectoryAtURL:sharedTrashURL
withIntermediateDirectories:YES
attributes:nil
error:&error];
TMDiskCacheError(error);
}
});
return sharedTrashURL;
}
+(BOOL)moveItemAtURLToTrash:(NSURL *)itemURL
{
if (![[NSFileManager defaultManager] fileExistsAtPath:[itemURL path]])
return NO;
NSError *error = nil;
NSString *uniqueString = [[NSProcessInfo processInfo] globallyUniqueString];
NSURL *uniqueTrashURL = [[TMDiskCache sharedTrashURL] URLByAppendingPathComponent:uniqueString];
BOOL moved = [[NSFileManager defaultManager] moveItemAtURL:itemURL toURL:uniqueTrashURL error:&error];
TMDiskCacheError(error);
return moved;
}
+ (void)emptyTrash
{
UIBackgroundTaskIdentifier taskID = [TMCacheBackgroundTaskManager beginBackgroundTask];
dispatch_async([self sharedTrashQueue], ^{
NSError *error = nil;
NSArray *trashedItems = [[NSFileManager defaultManager] contentsOfDirectoryAtURL:[self sharedTrashURL]
includingPropertiesForKeys:nil
options:0
error:&error];
TMDiskCacheError(error);
for (NSURL *trashedItemURL in trashedItems) {
NSError *error = nil;
[[NSFileManager defaultManager] removeItemAtURL:trashedItemURL error:&error];
TMDiskCacheError(error);
}
[TMCacheBackgroundTaskManager endBackgroundTask:taskID];
});
}
#pragma mark - Private Queue Methods -
- (BOOL)createCacheDirectory
{
if ([[NSFileManager defaultManager] fileExistsAtPath:[_cacheURL path]])
return NO;
NSError *error = nil;
BOOL success = [[NSFileManager defaultManager] createDirectoryAtURL:_cacheURL
withIntermediateDirectories:YES
attributes:nil
error:&error];
TMDiskCacheError(error);
return success;
}
- (void)initializeDiskProperties
{
NSUInteger byteCount = 0;
NSArray *keys = @[ NSURLContentModificationDateKey, NSURLTotalFileAllocatedSizeKey ];
NSError *error = nil;
NSArray *files = [[NSFileManager defaultManager] contentsOfDirectoryAtURL:_cacheURL
includingPropertiesForKeys:keys
options:NSDirectoryEnumerationSkipsHiddenFiles
error:&error];
TMDiskCacheError(error);
for (NSURL *fileURL in files) {
NSString *key = [self keyForEncodedFileURL:fileURL];
error = nil;
NSDictionary *dictionary = [fileURL resourceValuesForKeys:keys error:&error];
TMDiskCacheError(error);
NSDate *date = [dictionary objectForKey:NSURLContentModificationDateKey];
if (date && key)
[_dates setObject:date forKey:key];
NSNumber *fileSize = [dictionary objectForKey:NSURLTotalFileAllocatedSizeKey];
if (fileSize) {
[_sizes setObject:fileSize forKey:key];
byteCount += [fileSize unsignedIntegerValue];
}
}
if (byteCount > 0)
self.byteCount = byteCount; // atomic
}
- (BOOL)setFileModificationDate:(NSDate *)date forURL:(NSURL *)fileURL
{
if (!date || !fileURL) {
return NO;
}
NSError *error = nil;
BOOL success = [[NSFileManager defaultManager] setAttributes:@{ NSFileModificationDate: date }
ofItemAtPath:[fileURL path]
error:&error];
TMDiskCacheError(error);
if (success) {
NSString *key = [self keyForEncodedFileURL:fileURL];
if (key) {
[_dates setObject:date forKey:key];
}
}
return success;
}
- (BOOL)removeFileAndExecuteBlocksForKey:(NSString *)key
{
NSURL *fileURL = [self encodedFileURLForKey:key];
if (!fileURL || ![[NSFileManager defaultManager] fileExistsAtPath:[fileURL path]])
return NO;
if (_willRemoveObjectBlock)
_willRemoveObjectBlock(self, key, nil, fileURL);
BOOL trashed = [TMDiskCache moveItemAtURLToTrash:fileURL];
if (!trashed)
return NO;
[TMDiskCache emptyTrash];
NSNumber *byteSize = [_sizes objectForKey:key];
if (byteSize)
self.byteCount = _byteCount - [byteSize unsignedIntegerValue]; // atomic
[_sizes removeObjectForKey:key];
[_dates removeObjectForKey:key];
if (_didRemoveObjectBlock)
_didRemoveObjectBlock(self, key, nil, fileURL);
return YES;
}
- (void)trimDiskToSize:(NSUInteger)trimByteCount
{
if (_byteCount <= trimByteCount)
return;
NSArray *keysSortedBySize = [_sizes keysSortedByValueUsingSelector:@selector(compare:)];
for (NSString *key in [keysSortedBySize reverseObjectEnumerator]) { // largest objects first
[self removeFileAndExecuteBlocksForKey:key];
if (_byteCount <= trimByteCount)
break;
}
}
- (void)trimDiskToSizeByDate:(NSUInteger)trimByteCount
{
if (_byteCount <= trimByteCount)
return;
NSArray *keysSortedByDate = [_dates keysSortedByValueUsingSelector:@selector(compare:)];
for (NSString *key in keysSortedByDate) { // oldest objects first
[self removeFileAndExecuteBlocksForKey:key];
if (_byteCount <= trimByteCount)
break;
}
}
- (void)trimDiskToDate:(NSDate *)trimDate
{
NSArray *keysSortedByDate = [_dates keysSortedByValueUsingSelector:@selector(compare:)];
for (NSString *key in keysSortedByDate) { // oldest files first
NSDate *accessDate = [_dates objectForKey:key];
if (!accessDate)
continue;
if ([accessDate compare:trimDate] == NSOrderedAscending) { // older than trim date
[self removeFileAndExecuteBlocksForKey:key];
} else {
break;
}
}
}
- (void)trimToAgeLimitRecursively
{
if (_ageLimit == 0.0)
return;
NSDate *date = [[NSDate alloc] initWithTimeIntervalSinceNow:-_ageLimit];
[self trimDiskToDate:date];
__weak TMDiskCache *weakSelf = self;
dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(_ageLimit * NSEC_PER_SEC));
dispatch_after(time, _queue, ^(void) {
TMDiskCache *strongSelf = weakSelf;
[strongSelf trimToAgeLimitRecursively];
});
}
#pragma mark - Public Asynchronous Methods -
- (void)objectForKey:(NSString *)key block:(TMDiskCacheObjectBlock)block
{
NSDate *now = [[NSDate alloc] init];
if (!key || !block)
return;
__weak TMDiskCache *weakSelf = self;
dispatch_async(_queue, ^{
TMDiskCache *strongSelf = weakSelf;
if (!strongSelf)
return;
NSURL *fileURL = [strongSelf encodedFileURLForKey:key];
id <NSCoding> object = nil;
if ([[NSFileManager defaultManager] fileExistsAtPath:[fileURL path]]) {
@try {
object = [NSKeyedUnarchiver unarchiveObjectWithFile:[fileURL path]];
}
@catch (NSException *exception) {
NSError *error = nil;
[[NSFileManager defaultManager] removeItemAtPath:[fileURL path] error:&error];
TMDiskCacheError(error);
}
[strongSelf setFileModificationDate:now forURL:fileURL];
}
block(strongSelf, key, object, fileURL);
});
}
- (void)fileURLForKey:(NSString *)key block:(TMDiskCacheObjectBlock)block
{
NSDate *now = [[NSDate alloc] init];
if (!key || !block)
return;
__weak TMDiskCache *weakSelf = self;
dispatch_async(_queue, ^{
TMDiskCache *strongSelf = weakSelf;
if (!strongSelf)
return;
NSURL *fileURL = [strongSelf encodedFileURLForKey:key];
if ([[NSFileManager defaultManager] fileExistsAtPath:[fileURL path]]) {
[strongSelf setFileModificationDate:now forURL:fileURL];
} else {
fileURL = nil;
}
block(strongSelf, key, nil, fileURL);
});
}
- (void)setObject:(id <NSCoding>)object forKey:(NSString *)key block:(TMDiskCacheObjectBlock)block
{
NSDate *now = [[NSDate alloc] init];
if (!key || !object)
return;
UIBackgroundTaskIdentifier taskID = [TMCacheBackgroundTaskManager beginBackgroundTask];
__weak TMDiskCache *weakSelf = self;
dispatch_async(_queue, ^{
TMDiskCache *strongSelf = weakSelf;
if (!strongSelf) {
[TMCacheBackgroundTaskManager endBackgroundTask:taskID];
return;
}
NSURL *fileURL = [strongSelf encodedFileURLForKey:key];
if (strongSelf->_willAddObjectBlock)
strongSelf->_willAddObjectBlock(strongSelf, key, object, fileURL);
BOOL written = [NSKeyedArchiver archiveRootObject:object toFile:[fileURL path]];
if (written) {
[strongSelf setFileModificationDate:now forURL:fileURL];
NSError *error = nil;
NSDictionary *values = [fileURL resourceValuesForKeys:@[ NSURLTotalFileAllocatedSizeKey ] error:&error];
TMDiskCacheError(error);
NSNumber *diskFileSize = [values objectForKey:NSURLTotalFileAllocatedSizeKey];
if (diskFileSize) {
NSNumber *oldEntry = [strongSelf->_sizes objectForKey:key];
if ([oldEntry isKindOfClass:[NSNumber class]]){
strongSelf.byteCount = strongSelf->_byteCount - [oldEntry unsignedIntegerValue];
}
[strongSelf->_sizes setObject:diskFileSize forKey:key];
strongSelf.byteCount = strongSelf->_byteCount + [diskFileSize unsignedIntegerValue]; // atomic
}
if (strongSelf->_byteLimit > 0 && strongSelf->_byteCount > strongSelf->_byteLimit)
[strongSelf trimToSizeByDate:strongSelf->_byteLimit block:nil];
} else {
fileURL = nil;
}
if (strongSelf->_didAddObjectBlock)
strongSelf->_didAddObjectBlock(strongSelf, key, object, written ? fileURL : nil);
if (block)
block(strongSelf, key, object, fileURL);
[TMCacheBackgroundTaskManager endBackgroundTask:taskID];
});
}
- (void)removeObjectForKey:(NSString *)key block:(TMDiskCacheObjectBlock)block
{
if (!key)
return;
UIBackgroundTaskIdentifier taskID = [TMCacheBackgroundTaskManager beginBackgroundTask];
__weak TMDiskCache *weakSelf = self;
dispatch_async(_queue, ^{
TMDiskCache *strongSelf = weakSelf;
if (!strongSelf) {
[TMCacheBackgroundTaskManager endBackgroundTask:taskID];
return;
}
NSURL *fileURL = [strongSelf encodedFileURLForKey:key];
[strongSelf removeFileAndExecuteBlocksForKey:key];
if (block)
block(strongSelf, key, nil, fileURL);
[TMCacheBackgroundTaskManager endBackgroundTask:taskID];
});
}
- (void)trimToSize:(NSUInteger)trimByteCount block:(TMDiskCacheBlock)block
{
if (trimByteCount == 0) {
[self removeAllObjects:block];
return;
}
UIBackgroundTaskIdentifier taskID = [TMCacheBackgroundTaskManager beginBackgroundTask];
__weak TMDiskCache *weakSelf = self;
dispatch_async(_queue, ^{
TMDiskCache *strongSelf = weakSelf;
if (!strongSelf) {
[TMCacheBackgroundTaskManager endBackgroundTask:taskID];
return;
}
[strongSelf trimDiskToSize:trimByteCount];
if (block)
block(strongSelf);
[TMCacheBackgroundTaskManager endBackgroundTask:taskID];
});
}
- (void)trimToDate:(NSDate *)trimDate block:(TMDiskCacheBlock)block
{
if (!trimDate)
return;
if ([trimDate isEqualToDate:[NSDate distantPast]]) {
[self removeAllObjects:block];
return;
}
UIBackgroundTaskIdentifier taskID = [TMCacheBackgroundTaskManager beginBackgroundTask];
__weak TMDiskCache *weakSelf = self;
dispatch_async(_queue, ^{
TMDiskCache *strongSelf = weakSelf;
if (!strongSelf) {
[TMCacheBackgroundTaskManager endBackgroundTask:taskID];
return;
}
[strongSelf trimDiskToDate:trimDate];
if (block)
block(strongSelf);
[TMCacheBackgroundTaskManager endBackgroundTask:taskID];
});
}
- (void)trimToSizeByDate:(NSUInteger)trimByteCount block:(TMDiskCacheBlock)block
{
if (trimByteCount == 0) {
[self removeAllObjects:block];
return;
}
UIBackgroundTaskIdentifier taskID = [TMCacheBackgroundTaskManager beginBackgroundTask];
__weak TMDiskCache *weakSelf = self;
dispatch_async(_queue, ^{
TMDiskCache *strongSelf = weakSelf;
if (!strongSelf) {
[TMCacheBackgroundTaskManager endBackgroundTask:taskID];
return;
}
[strongSelf trimDiskToSizeByDate:trimByteCount];
if (block)
block(strongSelf);
[TMCacheBackgroundTaskManager endBackgroundTask:taskID];
});
}
- (void)removeAllObjects:(TMDiskCacheBlock)block
{
UIBackgroundTaskIdentifier taskID = [TMCacheBackgroundTaskManager beginBackgroundTask];
__weak TMDiskCache *weakSelf = self;
dispatch_async(_queue, ^{
TMDiskCache *strongSelf = weakSelf;
if (!strongSelf) {
[TMCacheBackgroundTaskManager endBackgroundTask:taskID];
return;
}
if (strongSelf->_willRemoveAllObjectsBlock)
strongSelf->_willRemoveAllObjectsBlock(strongSelf);
[TMDiskCache moveItemAtURLToTrash:strongSelf->_cacheURL];
[TMDiskCache emptyTrash];
[strongSelf createCacheDirectory];
[strongSelf->_dates removeAllObjects];
[strongSelf->_sizes removeAllObjects];
strongSelf.byteCount = 0; // atomic
if (strongSelf->_didRemoveAllObjectsBlock)
strongSelf->_didRemoveAllObjectsBlock(strongSelf);
if (block)
block(strongSelf);
[TMCacheBackgroundTaskManager endBackgroundTask:taskID];
});
}
- (void)enumerateObjectsWithBlock:(TMDiskCacheObjectBlock)block completionBlock:(TMDiskCacheBlock)completionBlock
{
if (!block)
return;
UIBackgroundTaskIdentifier taskID = [TMCacheBackgroundTaskManager beginBackgroundTask];
__weak TMDiskCache *weakSelf = self;
dispatch_async(_queue, ^{
TMDiskCache *strongSelf = weakSelf;
if (!strongSelf) {
[TMCacheBackgroundTaskManager endBackgroundTask:taskID];
return;
}
NSArray *keysSortedByDate = [strongSelf->_dates keysSortedByValueUsingSelector:@selector(compare:)];
for (NSString *key in keysSortedByDate) {
NSURL *fileURL = [strongSelf encodedFileURLForKey:key];
block(strongSelf, key, nil, fileURL);
}
if (completionBlock)
completionBlock(strongSelf);
[TMCacheBackgroundTaskManager endBackgroundTask:taskID];
});
}
#pragma mark - Public Synchronous Methods -
- (id <NSCoding>)objectForKey:(NSString *)key
{
if (!key)
return nil;
__block id <NSCoding> objectForKey = nil;
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
[self objectForKey:key block:^(TMDiskCache *cache, NSString *key, id <NSCoding> object, NSURL *fileURL) {
objectForKey = object;
dispatch_semaphore_signal(semaphore);
}];
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
#if !OS_OBJECT_USE_OBJC
dispatch_release(semaphore);
#endif
return objectForKey;
}
- (NSURL *)fileURLForKey:(NSString *)key
{
if (!key)
return nil;
__block NSURL *fileURLForKey = nil;
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
[self fileURLForKey:key block:^(TMDiskCache *cache, NSString *key, id <NSCoding> object, NSURL *fileURL) {
fileURLForKey = fileURL;
dispatch_semaphore_signal(semaphore);
}];
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
#if !OS_OBJECT_USE_OBJC
dispatch_release(semaphore);
#endif
return fileURLForKey;
}
- (void)setObject:(id <NSCoding>)object forKey:(NSString *)key
{
if (!object || !key)
return;
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
[self setObject:object forKey:key block:^(TMDiskCache *cache, NSString *key, id <NSCoding> object, NSURL *fileURL) {
dispatch_semaphore_signal(semaphore);
}];
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
#if !OS_OBJECT_USE_OBJC
dispatch_release(semaphore);
#endif
}
- (void)removeObjectForKey:(NSString *)key
{
if (!key)
return;
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
[self removeObjectForKey:key block:^(TMDiskCache *cache, NSString *key, id <NSCoding> object, NSURL *fileURL) {
dispatch_semaphore_signal(semaphore);
}];
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
#if !OS_OBJECT_USE_OBJC
dispatch_release(semaphore);
#endif
}
- (void)trimToSize:(NSUInteger)byteCount
{
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
[self trimToSize:byteCount block:^(TMDiskCache *cache) {
dispatch_semaphore_signal(semaphore);
}];
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
#if !OS_OBJECT_USE_OBJC
dispatch_release(semaphore);
#endif
}
- (void)trimToDate:(NSDate *)date
{
if (!date)
return;
if ([date isEqualToDate:[NSDate distantPast]]) {
[self removeAllObjects];
return;
}
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
[self trimToDate:date block:^(TMDiskCache *cache) {
dispatch_semaphore_signal(semaphore);
}];
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
#if !OS_OBJECT_USE_OBJC
dispatch_release(semaphore);
#endif
}
- (void)trimToSizeByDate:(NSUInteger)byteCount
{
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
[self trimToSizeByDate:byteCount block:^(TMDiskCache *cache) {
dispatch_semaphore_signal(semaphore);
}];
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
#if !OS_OBJECT_USE_OBJC
dispatch_release(semaphore);
#endif
}
- (void)removeAllObjects
{
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
[self removeAllObjects:^(TMDiskCache *cache) {
dispatch_semaphore_signal(semaphore);
}];
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
#if !OS_OBJECT_USE_OBJC
dispatch_release(semaphore);
#endif
}
- (void)enumerateObjectsWithBlock:(TMDiskCacheObjectBlock)block
{
if (!block)
return;
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
[self enumerateObjectsWithBlock:block completionBlock:^(TMDiskCache *cache) {
dispatch_semaphore_signal(semaphore);
}];
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
#if !OS_OBJECT_USE_OBJC
dispatch_release(semaphore);
#endif
}
#pragma mark - Public Thread Safe Accessors -
- (TMDiskCacheObjectBlock)willAddObjectBlock
{
__block TMDiskCacheObjectBlock block = nil;
dispatch_sync(_queue, ^{
block = _willAddObjectBlock;
});
return block;
}
- (void)setWillAddObjectBlock:(TMDiskCacheObjectBlock)block
{
__weak TMDiskCache *weakSelf = self;
dispatch_async(_queue, ^{
TMDiskCache *strongSelf = weakSelf;
if (!strongSelf)
return;
strongSelf->_willAddObjectBlock = [block copy];
});
}
- (TMDiskCacheObjectBlock)willRemoveObjectBlock
{
__block TMDiskCacheObjectBlock block = nil;
dispatch_sync(_queue, ^{
block = _willRemoveObjectBlock;
});
return block;
}
- (void)setWillRemoveObjectBlock:(TMDiskCacheObjectBlock)block
{
__weak TMDiskCache *weakSelf = self;
dispatch_async(_queue, ^{
TMDiskCache *strongSelf = weakSelf;
if (!strongSelf)
return;
strongSelf->_willRemoveObjectBlock = [block copy];
});
}
- (TMDiskCacheBlock)willRemoveAllObjectsBlock
{
__block TMDiskCacheBlock block = nil;
dispatch_sync(_queue, ^{
block = _willRemoveAllObjectsBlock;
});
return block;
}
- (void)setWillRemoveAllObjectsBlock:(TMDiskCacheBlock)block
{
__weak TMDiskCache *weakSelf = self;
dispatch_async(_queue, ^{
TMDiskCache *strongSelf = weakSelf;
if (!strongSelf)
return;
strongSelf->_willRemoveAllObjectsBlock = [block copy];
});
}
- (TMDiskCacheObjectBlock)didAddObjectBlock
{
__block TMDiskCacheObjectBlock block = nil;
dispatch_sync(_queue, ^{
block = _didAddObjectBlock;
});
return block;
}
- (void)setDidAddObjectBlock:(TMDiskCacheObjectBlock)block
{
__weak TMDiskCache *weakSelf = self;
dispatch_async(_queue, ^{
TMDiskCache *strongSelf = weakSelf;
if (!strongSelf)
return;
strongSelf->_didAddObjectBlock = [block copy];
});
}
- (TMDiskCacheObjectBlock)didRemoveObjectBlock
{
__block TMDiskCacheObjectBlock block = nil;
dispatch_sync(_queue, ^{
block = _didRemoveObjectBlock;
});
return block;
}
- (void)setDidRemoveObjectBlock:(TMDiskCacheObjectBlock)block
{
__weak TMDiskCache *weakSelf = self;
dispatch_async(_queue, ^{
TMDiskCache *strongSelf = weakSelf;
if (!strongSelf)
return;
strongSelf->_didRemoveObjectBlock = [block copy];
});
}
- (TMDiskCacheBlock)didRemoveAllObjectsBlock
{
__block TMDiskCacheBlock block = nil;
dispatch_sync(_queue, ^{
block = _didRemoveAllObjectsBlock;
});
return block;
}
- (void)setDidRemoveAllObjectsBlock:(TMDiskCacheBlock)block
{
__weak TMDiskCache *weakSelf = self;
dispatch_async(_queue, ^{
TMDiskCache *strongSelf = weakSelf;
if (!strongSelf)
return;
strongSelf->_didRemoveAllObjectsBlock = [block copy];
});
}
- (NSUInteger)byteLimit
{
__block NSUInteger byteLimit = 0;
dispatch_sync(_queue, ^{
byteLimit = _byteLimit;
});
return byteLimit;
}
- (void)setByteLimit:(NSUInteger)byteLimit
{
__weak TMDiskCache *weakSelf = self;
dispatch_barrier_async(_queue, ^{
TMDiskCache *strongSelf = weakSelf;
if (!strongSelf)
return;
strongSelf->_byteLimit = byteLimit;
if (byteLimit > 0)
[strongSelf trimDiskToSizeByDate:byteLimit];
});
}
- (NSTimeInterval)ageLimit
{
__block NSTimeInterval ageLimit = 0.0;
dispatch_sync(_queue, ^{
ageLimit = _ageLimit;
});
return ageLimit;
}
- (void)setAgeLimit:(NSTimeInterval)ageLimit
{
__weak TMDiskCache *weakSelf = self;
dispatch_barrier_async(_queue, ^{
TMDiskCache *strongSelf = weakSelf;
if (!strongSelf)
return;
strongSelf->_ageLimit = ageLimit;
[strongSelf trimToAgeLimitRecursively];
});
}
#pragma mark - Background Tasks -
+ (void)setBackgroundTaskManager:(id <TMCacheBackgroundTaskManager>)backgroundTaskManager {
TMCacheBackgroundTaskManager = backgroundTaskManager;
}
@end
/**
`TMMemoryCache` is a fast, thread safe key/value store similar to `NSCache`. On iOS it will clear itself
automatically to reduce memory usage when the app receives a memory warning or goes into the background.
Access is natively asynchronous. Every method accepts a callback block that runs on a concurrent
<queue>, with cache writes protected by GCD barriers. Synchronous variations are provided.
All access to the cache is dated so the that the least-used objects can be trimmed first. Setting an
optional <ageLimit> will trigger a GCD timer to periodically to trim the cache to that age.
Objects can optionally be set with a "cost", which could be a byte count or any other meaningful integer.
Setting a <costLimit> will automatically keep the cache below that value with <trimToCostByDate:>.
Values will not persist after application relaunch or returning from the background. See <TMCache> for
a memory cache backed by a disk cache.
*/
#import <Foundation/Foundation.h>
@class TMMemoryCache;
typedef void (^TMMemoryCacheBlock)(TMMemoryCache *cache);
typedef void (^TMMemoryCacheObjectBlock)(TMMemoryCache *cache, NSString *key, id object);
@interface TMMemoryCache : NSObject
#pragma mark -
/// @name Core
/**
A concurrent queue on which all work is done. It is exposed here so that it can be set to target some
other queue, such as a global concurrent queue with a priority other than the default.
*/
@property (readonly) dispatch_queue_t queue;
/**
The total accumulated cost.
*/
@property (readonly) NSUInteger totalCost;
/**
The maximum cost allowed to accumulate before objects begin to be removed with <trimToCostByDate:>.
*/
@property (assign) NSUInteger costLimit;
/**
The maximum number of seconds an object is allowed to exist in the cache. Setting this to a value
greater than `0.0` will start a recurring GCD timer with the same period that calls <trimToDate:>.
Setting it back to `0.0` will stop the timer. Defaults to `0.0`.
*/
@property (assign) NSTimeInterval ageLimit;
/**
When `YES` on iOS the cache will remove all objects when the app receives a memory warning.
Defaults to `YES`.
*/
@property (assign) BOOL removeAllObjectsOnMemoryWarning;
/**
When `YES` on iOS the cache will remove all objects when the app enters the background.
Defaults to `YES`.
*/
@property (assign) BOOL removeAllObjectsOnEnteringBackground;
#pragma mark -
/// @name Event Blocks
/**
A block to be executed just before an object is added to the cache. This block will be excuted within
a barrier, i.e. all reads and writes are suspended for the duration of the block.
*/
@property (copy) TMMemoryCacheObjectBlock willAddObjectBlock;
/**
A block to be executed just before an object is removed from the cache. This block will be excuted
within a barrier, i.e. all reads and writes are suspended for the duration of the block.
*/
@property (copy) TMMemoryCacheObjectBlock willRemoveObjectBlock;
/**
A block to be executed just before all objects are removed from the cache as a result of <removeAllObjects:>.
This block will be excuted within a barrier, i.e. all reads and writes are suspended for the duration of the block.
*/
@property (copy) TMMemoryCacheBlock willRemoveAllObjectsBlock;
/**
A block to be executed just after an object is added to the cache. This block will be excuted within
a barrier, i.e. all reads and writes are suspended for the duration of the block.
*/
@property (copy) TMMemoryCacheObjectBlock didAddObjectBlock;
/**
A block to be executed just after an object is removed from the cache. This block will be excuted
within a barrier, i.e. all reads and writes are suspended for the duration of the block.
*/
@property (copy) TMMemoryCacheObjectBlock didRemoveObjectBlock;
/**
A block to be executed just after all objects are removed from the cache as a result of <removeAllObjects:>.
This block will be excuted within a barrier, i.e. all reads and writes are suspended for the duration of the block.
*/
@property (copy) TMMemoryCacheBlock didRemoveAllObjectsBlock;
/**
A block to be executed upon receiving a memory warning (iOS only) potentially in parallel with other blocks on the <queue>.
This block will be executed regardless of the value of <removeAllObjectsOnMemoryWarning>. Defaults to `nil`.
*/
@property (copy) TMMemoryCacheBlock didReceiveMemoryWarningBlock;
/**
A block to be executed when the app enters the background (iOS only) potentially in parallel with other blocks on the <queue>.
This block will be executed regardless of the value of <removeAllObjectsOnEnteringBackground>. Defaults to `nil`.
*/
@property (copy) TMMemoryCacheBlock didEnterBackgroundBlock;
#pragma mark -
/// @name Shared Cache
/**
A shared cache.
@result The shared singleton cache instance.
*/
+ (instancetype)sharedCache;
#pragma mark -
/// @name Asynchronous Methods
/**
Retrieves the object for the specified key. This method returns immediately and executes the passed
block after the object is available, potentially in parallel with other blocks on the <queue>.
@param key The key associated with the requested object.
@param block A block to be executed concurrently when the object is available.
*/
- (void)objectForKey:(NSString *)key block:(TMMemoryCacheObjectBlock)block;
/**
Stores an object in the cache for the specified key. This method returns immediately and executes the
passed block after the object has been stored, potentially in parallel with other blocks on the <queue>.
@param object An object to store in the cache.
@param key A key to associate with the object. This string will be copied.
@param block A block to be executed concurrently after the object has been stored, or nil.
*/
- (void)setObject:(id)object forKey:(NSString *)key block:(TMMemoryCacheObjectBlock)block;
/**
Stores an object in the cache for the specified key and the specified cost. If the cost causes the total
to go over the <costLimit> the cache is trimmed (oldest objects first). This method returns immediately
and executes the passed block after the object has been stored, potentially in parallel with other blocks
on the <queue>.
@param object An object to store in the cache.
@param key A key to associate with the object. This string will be copied.
@param cost An amount to add to the <totalCost>.
@param block A block to be executed concurrently after the object has been stored, or nil.
*/
- (void)setObject:(id)object forKey:(NSString *)key withCost:(NSUInteger)cost block:(TMMemoryCacheObjectBlock)block;
/**
Removes the object for the specified key. This method returns immediately and executes the passed
block after the object has been removed, potentially in parallel with other blocks on the <queue>.
@param key The key associated with the object to be removed.
@param block A block to be executed concurrently after the object has been removed, or nil.
*/
- (void)removeObjectForKey:(NSString *)key block:(TMMemoryCacheObjectBlock)block;
/**
Removes all objects from the cache that have not been used since the specified date.
This method returns immediately and executes the passed block after the cache has been trimmed,
potentially in parallel with other blocks on the <queue>.
@param date Objects that haven't been accessed since this date are removed from the cache.
@param block A block to be executed concurrently after the cache has been trimmed, or nil.
*/
- (void)trimToDate:(NSDate *)date block:(TMMemoryCacheBlock)block;
/**
Removes objects from the cache, costliest objects first, until the <totalCost> is below the specified
value. This method returns immediately and executes the passed block after the cache has been trimmed,
potentially in parallel with other blocks on the <queue>.
@param cost The total accumulation allowed to remain after the cache has been trimmed.
@param block A block to be executed concurrently after the cache has been trimmed, or nil.
*/
- (void)trimToCost:(NSUInteger)cost block:(TMMemoryCacheBlock)block;
/**
Removes objects from the cache, ordered by date (least recently used first), until the <totalCost> is below
the specified value. This method returns immediately and executes the passed block after the cache has been
trimmed, potentially in parallel with other blocks on the <queue>.
@param cost The total accumulation allowed to remain after the cache has been trimmed.
@param block A block to be executed concurrently after the cache has been trimmed, or nil.
*/
- (void)trimToCostByDate:(NSUInteger)cost block:(TMMemoryCacheBlock)block;
/**
Removes all objects from the cache. This method returns immediately and executes the passed block after
the cache has been cleared, potentially in parallel with other blocks on the <queue>.
@param block A block to be executed concurrently after the cache has been cleared, or nil.
*/
- (void)removeAllObjects:(TMMemoryCacheBlock)block;
/**
Loops through all objects in the cache within a memory barrier (reads and writes are suspended during the enumeration).
This method returns immediately.
@param block A block to be executed for every object in the cache.
@param completionBlock An optional block to be executed concurrently when the enumeration is complete.
*/
- (void)enumerateObjectsWithBlock:(TMMemoryCacheObjectBlock)block completionBlock:(TMMemoryCacheBlock)completionBlock;
#pragma mark -
/// @name Synchronous Methods
/**
Retrieves the object for the specified key. This method blocks the calling thread until the
object is available.
@see objectForKey:block:
@param key The key associated with the object.
@result The object for the specified key.
*/
- (id)objectForKey:(NSString *)key;
/**
Stores an object in the cache for the specified key. This method blocks the calling thread until the object
has been set.
@see setObject:forKey:block:
@param object An object to store in the cache.
@param key A key to associate with the object. This string will be copied.
*/
- (void)setObject:(id)object forKey:(NSString *)key;
/**
Stores an object in the cache for the specified key and the specified cost. If the cost causes the total
to go over the <costLimit> the cache is trimmed (oldest objects first). This method blocks the calling thread
until the object has been stored.
@param object An object to store in the cache.
@param key A key to associate with the object. This string will be copied.
@param cost An amount to add to the <totalCost>.
*/
- (void)setObject:(id)object forKey:(NSString *)key withCost:(NSUInteger)cost;
/**
Removes the object for the specified key. This method blocks the calling thread until the object
has been removed.
@param key The key associated with the object to be removed.
*/
- (void)removeObjectForKey:(NSString *)key;
/**
Removes all objects from the cache that have not been used since the specified date.
This method blocks the calling thread until the cache has been trimmed.
@param date Objects that haven't been accessed since this date are removed from the cache.
*/
- (void)trimToDate:(NSDate *)date;
/**
Removes objects from the cache, costliest objects first, until the <totalCost> is below the specified
value. This method blocks the calling thread until the cache has been trimmed.
@param cost The total accumulation allowed to remain after the cache has been trimmed.
*/
- (void)trimToCost:(NSUInteger)cost;
/**
Removes objects from the cache, ordered by date (least recently used first), until the <totalCost> is below
the specified value. This method blocks the calling thread until the cache has been trimmed.
@param cost The total accumulation allowed to remain after the cache has been trimmed.
*/
- (void)trimToCostByDate:(NSUInteger)cost;
/**
Removes all objects from the cache. This method blocks the calling thread until the cache has been cleared.
*/
- (void)removeAllObjects;
/**
Loops through all objects in the cache within a memory barrier (reads and writes are suspended during the enumeration).
This method blocks the calling thread until all objects have been enumerated.
@param block A block to be executed for every object in the cache.
@warning Do not call this method within the event blocks (<didReceiveMemoryWarningBlock>, etc.)
Instead use the asynchronous version, <enumerateObjectsWithBlock:completionBlock:>.
*/
- (void)enumerateObjectsWithBlock:(TMMemoryCacheObjectBlock)block;
/**
Handle a memory warning.
*/
- (void)handleMemoryWarning __deprecated_msg("This happens automatically in TMCache 2.1. There’s no longer a need to call it directly.");
/**
Handle the application having been backgrounded.
*/
- (void)handleApplicationBackgrounding __deprecated_msg("This happens automatically in TMCache 2.1. There’s no longer a need to call it directly.");
@end
#import "TMMemoryCache.h"
#if __IPHONE_OS_VERSION_MIN_REQUIRED >= __IPHONE_4_0
#import <UIKit/UIKit.h>
#endif
NSString * const TMMemoryCachePrefix = @"com.tumblr.TMMemoryCache";
@interface TMMemoryCache ()
#if OS_OBJECT_USE_OBJC
@property (strong, nonatomic) dispatch_queue_t queue;
#else
@property (assign, nonatomic) dispatch_queue_t queue;
#endif
@property (strong, nonatomic) NSMutableDictionary *dictionary;
@property (strong, nonatomic) NSMutableDictionary *dates;
@property (strong, nonatomic) NSMutableDictionary *costs;
@end
@implementation TMMemoryCache
@synthesize ageLimit = _ageLimit;
@synthesize costLimit = _costLimit;
@synthesize totalCost = _totalCost;
@synthesize willAddObjectBlock = _willAddObjectBlock;
@synthesize willRemoveObjectBlock = _willRemoveObjectBlock;
@synthesize willRemoveAllObjectsBlock = _willRemoveAllObjectsBlock;
@synthesize didAddObjectBlock = _didAddObjectBlock;
@synthesize didRemoveObjectBlock = _didRemoveObjectBlock;
@synthesize didRemoveAllObjectsBlock = _didRemoveAllObjectsBlock;
@synthesize didReceiveMemoryWarningBlock = _didReceiveMemoryWarningBlock;
@synthesize didEnterBackgroundBlock = _didEnterBackgroundBlock;
#pragma mark - Initialization -
- (void)dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver:self];
#if !OS_OBJECT_USE_OBJC
dispatch_release(_queue);
_queue = nil;
#endif
}
- (id)init
{
if (self = [super init]) {
NSString *queueName = [[NSString alloc] initWithFormat:@"%@.%p", TMMemoryCachePrefix, self];
_queue = dispatch_queue_create([queueName UTF8String], DISPATCH_QUEUE_CONCURRENT);
_dictionary = [[NSMutableDictionary alloc] init];
_dates = [[NSMutableDictionary alloc] init];
_costs = [[NSMutableDictionary alloc] init];
_willAddObjectBlock = nil;
_willRemoveObjectBlock = nil;
_willRemoveAllObjectsBlock = nil;
_didAddObjectBlock = nil;
_didRemoveObjectBlock = nil;
_didRemoveAllObjectsBlock = nil;
_didReceiveMemoryWarningBlock = nil;
_didEnterBackgroundBlock = nil;
_ageLimit = 0.0;
_costLimit = 0;
_totalCost = 0;
_removeAllObjectsOnMemoryWarning = YES;
_removeAllObjectsOnEnteringBackground = YES;
#if __IPHONE_OS_VERSION_MIN_REQUIRED >= __IPHONE_4_0
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(handleMemoryWarning)
name:UIApplicationDidReceiveMemoryWarningNotification
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(handleApplicationBackgrounding)
name:UIApplicationDidEnterBackgroundNotification
object:nil];
#endif
}
return self;
}
+ (instancetype)sharedCache
{
static id cache;
static dispatch_once_t predicate;
dispatch_once(&predicate, ^{
cache = [[self alloc] init];
});
return cache;
}
#pragma mark - Private Methods -
- (void)handleMemoryWarning
{
#if __IPHONE_OS_VERSION_MIN_REQUIRED >= __IPHONE_4_0
if (self.removeAllObjectsOnMemoryWarning)
[self removeAllObjects:nil];
__weak TMMemoryCache *weakSelf = self;
dispatch_async(_queue, ^{
TMMemoryCache *strongSelf = weakSelf;
if (!strongSelf)
return;
if (strongSelf->_didReceiveMemoryWarningBlock)
strongSelf->_didReceiveMemoryWarningBlock(strongSelf);
});
#endif
}
- (void)handleApplicationBackgrounding
{
#if __IPHONE_OS_VERSION_MIN_REQUIRED >= __IPHONE_4_0
if (self.removeAllObjectsOnEnteringBackground)
[self removeAllObjects:nil];
__weak TMMemoryCache *weakSelf = self;
dispatch_async(_queue, ^{
TMMemoryCache *strongSelf = weakSelf;
if (!strongSelf)
return;
if (strongSelf->_didEnterBackgroundBlock)
strongSelf->_didEnterBackgroundBlock(strongSelf);
});
#endif
}
- (void)removeObjectAndExecuteBlocksForKey:(NSString *)key
{
id object = [_dictionary objectForKey:key];
NSNumber *cost = [_costs objectForKey:key];
if (_willRemoveObjectBlock)
_willRemoveObjectBlock(self, key, object);
if (cost)
_totalCost -= [cost unsignedIntegerValue];
[_dictionary removeObjectForKey:key];
[_dates removeObjectForKey:key];
[_costs removeObjectForKey:key];
if (_didRemoveObjectBlock)
_didRemoveObjectBlock(self, key, nil);
}
- (void)trimMemoryToDate:(NSDate *)trimDate
{
NSArray *keysSortedByDate = [_dates keysSortedByValueUsingSelector:@selector(compare:)];
for (NSString *key in keysSortedByDate) { // oldest objects first
NSDate *accessDate = [_dates objectForKey:key];
if (!accessDate)
continue;
if ([accessDate compare:trimDate] == NSOrderedAscending) { // older than trim date
[self removeObjectAndExecuteBlocksForKey:key];
} else {
break;
}
}
}
- (void)trimToCostLimit:(NSUInteger)limit
{
if (_totalCost <= limit)
return;
NSArray *keysSortedByCost = [_costs keysSortedByValueUsingSelector:@selector(compare:)];
for (NSString *key in [keysSortedByCost reverseObjectEnumerator]) { // costliest objects first
[self removeObjectAndExecuteBlocksForKey:key];
if (_totalCost <= limit)
break;
}
}
- (void)trimToCostLimitByDate:(NSUInteger)limit
{
if (_totalCost <= limit)
return;
NSArray *keysSortedByDate = [_dates keysSortedByValueUsingSelector:@selector(compare:)];
for (NSString *key in keysSortedByDate) { // oldest objects first
[self removeObjectAndExecuteBlocksForKey:key];
if (_totalCost <= limit)
break;
}
}
- (void)trimToAgeLimitRecursively
{
if (_ageLimit == 0.0)
return;
NSDate *date = [[NSDate alloc] initWithTimeIntervalSinceNow:-_ageLimit];
[self trimMemoryToDate:date];
__weak TMMemoryCache *weakSelf = self;
dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(_ageLimit * NSEC_PER_SEC));
dispatch_after(time, _queue, ^(void){
TMMemoryCache *strongSelf = weakSelf;
if (!strongSelf)
return;
__weak TMMemoryCache *weakSelf = strongSelf;
dispatch_barrier_async(strongSelf->_queue, ^{
TMMemoryCache *strongSelf = weakSelf;
[strongSelf trimToAgeLimitRecursively];
});
});
}
#pragma mark - Public Asynchronous Methods -
- (void)objectForKey:(NSString *)key block:(TMMemoryCacheObjectBlock)block
{
NSDate *now = [[NSDate alloc] init];
if (!key || !block)
return;
__weak TMMemoryCache *weakSelf = self;
dispatch_async(_queue, ^{
TMMemoryCache *strongSelf = weakSelf;
if (!strongSelf)
return;
id object = [strongSelf->_dictionary objectForKey:key];
if (object) {
__weak TMMemoryCache *weakSelf = strongSelf;
dispatch_barrier_async(strongSelf->_queue, ^{
TMMemoryCache *strongSelf = weakSelf;
if (strongSelf)
[strongSelf->_dates setObject:now forKey:key];
});
}
block(strongSelf, key, object);
});
}
- (void)setObject:(id)object forKey:(NSString *)key block:(TMMemoryCacheObjectBlock)block
{
[self setObject:object forKey:key withCost:0 block:block];
}
- (void)setObject:(id)object forKey:(NSString *)key withCost:(NSUInteger)cost block:(TMMemoryCacheObjectBlock)block
{
NSDate *now = [[NSDate alloc] init];
if (!key || !object)
return;
__weak TMMemoryCache *weakSelf = self;
dispatch_barrier_async(_queue, ^{
TMMemoryCache *strongSelf = weakSelf;
if (!strongSelf)
return;
if (strongSelf->_willAddObjectBlock)
strongSelf->_willAddObjectBlock(strongSelf, key, object);
[strongSelf->_dictionary setObject:object forKey:key];
[strongSelf->_dates setObject:now forKey:key];
[strongSelf->_costs setObject:@(cost) forKey:key];
_totalCost += cost;
if (strongSelf->_didAddObjectBlock)
strongSelf->_didAddObjectBlock(strongSelf, key, object);
if (strongSelf->_costLimit > 0)
[strongSelf trimToCostByDate:strongSelf->_costLimit block:nil];
if (block) {
__weak TMMemoryCache *weakSelf = strongSelf;
dispatch_async(strongSelf->_queue, ^{
TMMemoryCache *strongSelf = weakSelf;
if (strongSelf)
block(strongSelf, key, object);
});
}
});
}
- (void)removeObjectForKey:(NSString *)key block:(TMMemoryCacheObjectBlock)block
{
if (!key)
return;
__weak TMMemoryCache *weakSelf = self;
dispatch_barrier_async(_queue, ^{
TMMemoryCache *strongSelf = weakSelf;
if (!strongSelf)
return;
[strongSelf removeObjectAndExecuteBlocksForKey:key];
if (block) {
__weak TMMemoryCache *weakSelf = strongSelf;
dispatch_async(strongSelf->_queue, ^{
TMMemoryCache *strongSelf = weakSelf;
if (strongSelf)
block(strongSelf, key, nil);
});
}
});
}
- (void)trimToDate:(NSDate *)trimDate block:(TMMemoryCacheBlock)block
{
if (!trimDate)
return;
if ([trimDate isEqualToDate:[NSDate distantPast]]) {
[self removeAllObjects:block];
return;
}
__weak TMMemoryCache *weakSelf = self;
dispatch_barrier_async(_queue, ^{
TMMemoryCache *strongSelf = weakSelf;
if (!strongSelf)
return;
[strongSelf trimMemoryToDate:trimDate];
if (block) {
__weak TMMemoryCache *weakSelf = strongSelf;
dispatch_async(strongSelf->_queue, ^{
TMMemoryCache *strongSelf = weakSelf;
if (strongSelf)
block(strongSelf);
});
}
});
}
- (void)trimToCost:(NSUInteger)cost block:(TMMemoryCacheBlock)block
{
__weak TMMemoryCache *weakSelf = self;
dispatch_barrier_async(_queue, ^{
TMMemoryCache *strongSelf = weakSelf;
if (!strongSelf)
return;
[strongSelf trimToCostLimit:cost];
if (block) {
__weak TMMemoryCache *weakSelf = strongSelf;
dispatch_async(strongSelf->_queue, ^{
TMMemoryCache *strongSelf = weakSelf;
if (strongSelf)
block(strongSelf);
});
}
});
}
- (void)trimToCostByDate:(NSUInteger)cost block:(TMMemoryCacheBlock)block
{
__weak TMMemoryCache *weakSelf = self;
dispatch_barrier_async(_queue, ^{
TMMemoryCache *strongSelf = weakSelf;
if (!strongSelf)
return;
[strongSelf trimToCostLimitByDate:cost];
if (block) {
__weak TMMemoryCache *weakSelf = strongSelf;
dispatch_async(strongSelf->_queue, ^{
TMMemoryCache *strongSelf = weakSelf;
if (strongSelf)
block(strongSelf);
});
}
});
}
- (void)removeAllObjects:(TMMemoryCacheBlock)block
{
__weak TMMemoryCache *weakSelf = self;
dispatch_barrier_async(_queue, ^{
TMMemoryCache *strongSelf = weakSelf;
if (!strongSelf)
return;
if (strongSelf->_willRemoveAllObjectsBlock)
strongSelf->_willRemoveAllObjectsBlock(strongSelf);
[strongSelf->_dictionary removeAllObjects];
[strongSelf->_dates removeAllObjects];
[strongSelf->_costs removeAllObjects];
strongSelf->_totalCost = 0;
if (strongSelf->_didRemoveAllObjectsBlock)
strongSelf->_didRemoveAllObjectsBlock(strongSelf);
if (block) {
__weak TMMemoryCache *weakSelf = strongSelf;
dispatch_async(strongSelf->_queue, ^{
TMMemoryCache *strongSelf = weakSelf;
if (strongSelf)
block(strongSelf);
});
}
});
}
- (void)enumerateObjectsWithBlock:(TMMemoryCacheObjectBlock)block completionBlock:(TMMemoryCacheBlock)completionBlock
{
if (!block)
return;
__weak TMMemoryCache *weakSelf = self;
dispatch_barrier_async(_queue, ^{
TMMemoryCache *strongSelf = weakSelf;
if (!strongSelf)
return;
NSArray *keysSortedByDate = [strongSelf->_dates keysSortedByValueUsingSelector:@selector(compare:)];
for (NSString *key in keysSortedByDate) {
block(strongSelf, key, [strongSelf->_dictionary objectForKey:key]);
}
if (completionBlock) {
__weak TMMemoryCache *weakSelf = strongSelf;
dispatch_async(strongSelf->_queue, ^{
TMMemoryCache *strongSelf = weakSelf;
if (strongSelf)
completionBlock(strongSelf);
});
}
});
}
#pragma mark - Public Synchronous Methods -
- (id)objectForKey:(NSString *)key
{
if (!key)
return nil;
__block id objectForKey = nil;
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
[self objectForKey:key block:^(TMMemoryCache *cache, NSString *key, id object) {
objectForKey = object;
dispatch_semaphore_signal(semaphore);
}];
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
#if !OS_OBJECT_USE_OBJC
dispatch_release(semaphore);
#endif
return objectForKey;
}
- (void)setObject:(id)object forKey:(NSString *)key
{
[self setObject:object forKey:key withCost:0];
}
- (void)setObject:(id)object forKey:(NSString *)key withCost:(NSUInteger)cost
{
if (!object || !key)
return;
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
[self setObject:object forKey:key withCost:cost block:^(TMMemoryCache *cache, NSString *key, id object) {
dispatch_semaphore_signal(semaphore);
}];
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
#if !OS_OBJECT_USE_OBJC
dispatch_release(semaphore);
#endif
}
- (void)removeObjectForKey:(NSString *)key
{
if (!key)
return;
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
[self removeObjectForKey:key block:^(TMMemoryCache *cache, NSString *key, id object) {
dispatch_semaphore_signal(semaphore);
}];
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
#if !OS_OBJECT_USE_OBJC
dispatch_release(semaphore);
#endif
}
- (void)trimToDate:(NSDate *)date
{
if (!date)
return;
if ([date isEqualToDate:[NSDate distantPast]]) {
[self removeAllObjects];
return;
}
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
[self trimToDate:date block:^(TMMemoryCache *cache) {
dispatch_semaphore_signal(semaphore);
}];
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
#if !OS_OBJECT_USE_OBJC
dispatch_release(semaphore);
#endif
}
- (void)trimToCost:(NSUInteger)cost
{
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
[self trimToCost:cost block:^(TMMemoryCache *cache) {
dispatch_semaphore_signal(semaphore);
}];
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
#if !OS_OBJECT_USE_OBJC
dispatch_release(semaphore);
#endif
}
- (void)trimToCostByDate:(NSUInteger)cost
{
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
[self trimToCostByDate:cost block:^(TMMemoryCache *cache) {
dispatch_semaphore_signal(semaphore);
}];
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
#if !OS_OBJECT_USE_OBJC
dispatch_release(semaphore);
#endif
}
- (void)removeAllObjects
{
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
[self removeAllObjects:^(TMMemoryCache *cache) {
dispatch_semaphore_signal(semaphore);
}];
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
#if !OS_OBJECT_USE_OBJC
dispatch_release(semaphore);
#endif
}
- (void)enumerateObjectsWithBlock:(TMMemoryCacheObjectBlock)block
{
if (!block)
return;
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
[self enumerateObjectsWithBlock:block completionBlock:^(TMMemoryCache *cache) {
dispatch_semaphore_signal(semaphore);
}];
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
#if !OS_OBJECT_USE_OBJC
dispatch_release(semaphore);
#endif
}
#pragma mark - Public Thread Safe Accessors -
- (TMMemoryCacheObjectBlock)willAddObjectBlock
{
__block TMMemoryCacheObjectBlock block = nil;
dispatch_sync(_queue, ^{
block = self->_willAddObjectBlock;
});
return block;
}
- (void)setWillAddObjectBlock:(TMMemoryCacheObjectBlock)block
{
__weak TMMemoryCache *weakSelf = self;
dispatch_barrier_async(_queue, ^{
TMMemoryCache *strongSelf = weakSelf;
if (!strongSelf)
return;
strongSelf->_willAddObjectBlock = [block copy];
});
}
- (TMMemoryCacheObjectBlock)willRemoveObjectBlock
{
__block TMMemoryCacheObjectBlock block = nil;
dispatch_sync(_queue, ^{
block = _willRemoveObjectBlock;
});
return block;
}
- (void)setWillRemoveObjectBlock:(TMMemoryCacheObjectBlock)block
{
__weak TMMemoryCache *weakSelf = self;
dispatch_barrier_async(_queue, ^{
TMMemoryCache *strongSelf = weakSelf;
if (!strongSelf)
return;
strongSelf->_willRemoveObjectBlock = [block copy];
});
}
- (TMMemoryCacheBlock)willRemoveAllObjectsBlock
{
__block TMMemoryCacheBlock block = nil;
dispatch_sync(_queue, ^{
block = _willRemoveAllObjectsBlock;
});
return block;
}
- (void)setWillRemoveAllObjectsBlock:(TMMemoryCacheBlock)block
{
__weak TMMemoryCache *weakSelf = self;
dispatch_barrier_async(_queue, ^{
TMMemoryCache *strongSelf = weakSelf;
if (!strongSelf)
return;
strongSelf->_willRemoveAllObjectsBlock = [block copy];
});
}
- (TMMemoryCacheObjectBlock)didAddObjectBlock
{
__block TMMemoryCacheObjectBlock block = nil;
dispatch_sync(_queue, ^{
block = _didAddObjectBlock;
});
return block;
}
- (void)setDidAddObjectBlock:(TMMemoryCacheObjectBlock)block
{
__weak TMMemoryCache *weakSelf = self;
dispatch_barrier_async(_queue, ^{
TMMemoryCache *strongSelf = weakSelf;
if (!strongSelf)
return;
strongSelf->_didAddObjectBlock = [block copy];
});
}
- (TMMemoryCacheObjectBlock)didRemoveObjectBlock
{
__block TMMemoryCacheObjectBlock block = nil;
dispatch_sync(_queue, ^{
block = _didRemoveObjectBlock;
});
return block;
}
- (void)setDidRemoveObjectBlock:(TMMemoryCacheObjectBlock)block
{
__weak TMMemoryCache *weakSelf = self;
dispatch_barrier_async(_queue, ^{
TMMemoryCache *strongSelf = weakSelf;
if (!strongSelf)
return;
strongSelf->_didRemoveObjectBlock = [block copy];
});
}
- (TMMemoryCacheBlock)didRemoveAllObjectsBlock
{
__block TMMemoryCacheBlock block = nil;
dispatch_sync(_queue, ^{
block = _didRemoveAllObjectsBlock;
});
return block;
}
- (void)setDidRemoveAllObjectsBlock:(TMMemoryCacheBlock)block
{
__weak TMMemoryCache *weakSelf = self;
dispatch_barrier_async(_queue, ^{
TMMemoryCache *strongSelf = weakSelf;
if (!strongSelf)
return;
strongSelf->_didRemoveAllObjectsBlock = [block copy];
});
}
- (TMMemoryCacheBlock)didReceiveMemoryWarningBlock
{
__block TMMemoryCacheBlock block = nil;
dispatch_sync(_queue, ^{
block = _didReceiveMemoryWarningBlock;
});
return block;
}
- (void)setDidReceiveMemoryWarningBlock:(TMMemoryCacheBlock)block
{
__weak TMMemoryCache *weakSelf = self;
dispatch_barrier_async(_queue, ^{
TMMemoryCache *strongSelf = weakSelf;
if (!strongSelf)
return;
strongSelf->_didReceiveMemoryWarningBlock = [block copy];
});
}
- (TMMemoryCacheBlock)didEnterBackgroundBlock
{
__block TMMemoryCacheBlock block = nil;
dispatch_sync(_queue, ^{
block = _didEnterBackgroundBlock;
});
return block;
}
- (void)setDidEnterBackgroundBlock:(TMMemoryCacheBlock)block
{
__weak TMMemoryCache *weakSelf = self;
dispatch_barrier_async(_queue, ^{
TMMemoryCache *strongSelf = weakSelf;
if (!strongSelf)
return;
strongSelf->_didEnterBackgroundBlock = [block copy];
});
}
- (NSTimeInterval)ageLimit
{
__block NSTimeInterval ageLimit = 0.0;
dispatch_sync(_queue, ^{
ageLimit = _ageLimit;
});
return ageLimit;
}
- (void)setAgeLimit:(NSTimeInterval)ageLimit
{
__weak TMMemoryCache *weakSelf = self;
dispatch_barrier_async(_queue, ^{
TMMemoryCache *strongSelf = weakSelf;
if (!strongSelf)
return;
strongSelf->_ageLimit = ageLimit;
[strongSelf trimToAgeLimitRecursively];
});
}
- (NSUInteger)costLimit
{
__block NSUInteger costLimit = 0;
dispatch_sync(_queue, ^{
costLimit = _costLimit;
});
return costLimit;
}
- (void)setCostLimit:(NSUInteger)costLimit
{
__weak TMMemoryCache *weakSelf = self;
dispatch_barrier_async(_queue, ^{
TMMemoryCache *strongSelf = weakSelf;
if (!strongSelf)
return;
strongSelf->_costLimit = costLimit;
if (costLimit > 0)
[strongSelf trimToCostLimitByDate:costLimit];
});
}
- (NSUInteger)totalCost
{
__block NSUInteger cost = 0;
dispatch_sync(_queue, ^{
cost = _totalCost;
});
return cost;
}
@end
#import <Foundation/Foundation.h>
@interface PodsDummy_GMCache : NSObject
@end
@implementation PodsDummy_GMCache
@end
#ifdef __OBJC__
#import <UIKit/UIKit.h>
#else
#ifndef FOUNDATION_EXPORT
#if defined(__cplusplus)
#define FOUNDATION_EXPORT extern "C"
#else
#define FOUNDATION_EXPORT extern
#endif
#endif
#endif
CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/GMCache
GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
HEADER_SEARCH_PATHS = $(inherited) "${PODS_ROOT}/Headers/Private" "${PODS_ROOT}/Headers/Private/GMCache" "${PODS_ROOT}/Headers/Public" "${PODS_ROOT}/Headers/Public/GMCache" "${PODS_ROOT}/Headers/Public/TMCache"
PODS_BUILD_DIR = ${BUILD_DIR}
PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
PODS_ROOT = ${SRCROOT}
PODS_TARGET_SRCROOT = ${PODS_ROOT}/GMCache
PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier}
SKIP_INSTALL = YES
#import <Foundation/Foundation.h>
@interface PodsDummy_GMJSONModel : NSObject
@end
@implementation PodsDummy_GMJSONModel
@end
#ifdef __OBJC__
#import <UIKit/UIKit.h>
#else
#ifndef FOUNDATION_EXPORT
#if defined(__cplusplus)
#define FOUNDATION_EXPORT extern "C"
#else
#define FOUNDATION_EXPORT extern
#endif
#endif
#endif
CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/GMJSONModel
GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
HEADER_SEARCH_PATHS = $(inherited) "${PODS_ROOT}/Headers/Private" "${PODS_ROOT}/Headers/Private/GMJSONModel" "${PODS_ROOT}/Headers/Public" "${PODS_ROOT}/Headers/Public/GMJSONModel"
PODS_BUILD_DIR = ${BUILD_DIR}
PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
PODS_ROOT = ${SRCROOT}
PODS_TARGET_SRCROOT = ${PODS_ROOT}/GMJSONModel
PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier}
SKIP_INSTALL = YES
#import <Foundation/Foundation.h>
@interface PodsDummy_GMPhobos : NSObject
@end
@implementation PodsDummy_GMPhobos
@end
#ifdef __OBJC__
#import <UIKit/UIKit.h>
#else
#ifndef FOUNDATION_EXPORT
#if defined(__cplusplus)
#define FOUNDATION_EXPORT extern "C"
#else
#define FOUNDATION_EXPORT extern
#endif
#endif
#endif
CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/GMPhobos
GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
HEADER_SEARCH_PATHS = $(inherited) "${PODS_ROOT}/Headers/Private" "${PODS_ROOT}/Headers/Private/GMPhobos" "${PODS_ROOT}/Headers/Public" "${PODS_ROOT}/Headers/Public/GMCache" "${PODS_ROOT}/Headers/Public/GMKit" "${PODS_ROOT}/Headers/Public/GMPhobos" "${PODS_ROOT}/Headers/Public/Masonry" "${PODS_ROOT}/Headers/Public/SDWebImage" "${PODS_ROOT}/Headers/Public/TMCache"
PODS_BUILD_DIR = ${BUILD_DIR}
PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
PODS_ROOT = ${SRCROOT}
PODS_TARGET_SRCROOT = ${PODS_ROOT}/GMPhobos
PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier}
SKIP_INSTALL = YES
CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/GMShareSDK
FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_ROOT}/../../GMShareSDK/Frameworks"
GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
HEADER_SEARCH_PATHS = $(inherited) "${PODS_ROOT}/Headers/Private" "${PODS_ROOT}/Headers/Private/GMShareSDK" "${PODS_ROOT}/Headers/Public" "${PODS_ROOT}/Headers/Public/DouyinOpenSDK" "${PODS_ROOT}/Headers/Public/GMFoundation" "${PODS_ROOT}/Headers/Public/GMKit" "${PODS_ROOT}/Headers/Public/GMShareSDK" "${PODS_ROOT}/Headers/Public/Masonry" "${PODS_ROOT}/Headers/Public/SDWebImage" "${PODS_ROOT}/Headers/Public/WechatOpenSDK" "${PODS_ROOT}/Headers/Public/WeiboSDK"
HEADER_SEARCH_PATHS = $(inherited) "${PODS_ROOT}/Headers/Private" "${PODS_ROOT}/Headers/Private/GMShareSDK" "${PODS_ROOT}/Headers/Public" "${PODS_ROOT}/Headers/Public/DouyinOpenSDK" "${PODS_ROOT}/Headers/Public/GMCache" "${PODS_ROOT}/Headers/Public/GMFoundation" "${PODS_ROOT}/Headers/Public/GMJSONModel" "${PODS_ROOT}/Headers/Public/GMKit" "${PODS_ROOT}/Headers/Public/GMPhobos" "${PODS_ROOT}/Headers/Public/GMShareSDK" "${PODS_ROOT}/Headers/Public/MBProgressHUD" "${PODS_ROOT}/Headers/Public/Masonry" "${PODS_ROOT}/Headers/Public/SDWebImage" "${PODS_ROOT}/Headers/Public/TMCache" "${PODS_ROOT}/Headers/Public/WechatOpenSDK" "${PODS_ROOT}/Headers/Public/WeiboSDK"
PODS_BUILD_DIR = ${BUILD_DIR}
PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
PODS_ROOT = ${SRCROOT}
......
#import <Foundation/Foundation.h>
@interface PodsDummy_MBProgressHUD : NSObject
@end
@implementation PodsDummy_MBProgressHUD
@end
#ifdef __OBJC__
#import <UIKit/UIKit.h>
#else
#ifndef FOUNDATION_EXPORT
#if defined(__cplusplus)
#define FOUNDATION_EXPORT extern "C"
#else
#define FOUNDATION_EXPORT extern
#endif
#endif
#endif
CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/MBProgressHUD
GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
HEADER_SEARCH_PATHS = $(inherited) "${PODS_ROOT}/Headers/Private" "${PODS_ROOT}/Headers/Private/MBProgressHUD" "${PODS_ROOT}/Headers/Public" "${PODS_ROOT}/Headers/Public/MBProgressHUD"
PODS_BUILD_DIR = ${BUILD_DIR}
PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
PODS_ROOT = ${SRCROOT}
PODS_TARGET_SRCROOT = ${PODS_ROOT}/MBProgressHUD
PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier}
SKIP_INSTALL = YES
......@@ -9,6 +9,29 @@ Copyright 2019 bytedance.com. All rights reserved.
Copyright 2019 bytedance.com. All rights reserved.
## GMCache
Copyright (c) 2016 wangyang <wangyang@wanmeizhensuo.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
## GMFoundation
Copyright (c) 2016 licong <1240690490@qq.com>
......@@ -32,6 +55,28 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
## GMJSONModel
Copyright (c) 2012-2016 Marin Todorov and JSONModel contributors
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
## GMKit
Copyright (c) 2016 北京更美互动信息科技有限公司
......@@ -39,6 +84,29 @@ Copyright (c) 2016 北京更美互动信息科技有限公司
仅限北京更美互动信息科技有限公司内部使用
## GMPhobos
Copyright (c) 2016 licong <1240690490@qq.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
## GMShareSDK
Copyright (c) 2019 Q14 <qiaojinzhu@igengmei.com>
......@@ -62,6 +130,28 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
## MBProgressHUD
Copyright © 2009-2016 Matej Bukovinski
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
## Masonry
Copyright (c) 2011-2012 Masonry Team - https://github.com/Masonry
......@@ -108,6 +198,211 @@ THE SOFTWARE.
## TMCache
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [2013] [Tumblr, Inc.]
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.
## WechatOpenSDK
Copyright 2019 tencent.com. All rights reserved.
......
......@@ -32,6 +32,35 @@
<key>Type</key>
<string>PSGroupSpecifier</string>
</dict>
<dict>
<key>FooterText</key>
<string>Copyright (c) 2016 wangyang &lt;wangyang@wanmeizhensuo.com&gt;
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
</string>
<key>License</key>
<string>MIT</string>
<key>Title</key>
<string>GMCache</string>
<key>Type</key>
<string>PSGroupSpecifier</string>
</dict>
<dict>
<key>FooterText</key>
<string>Copyright (c) 2016 licong &lt;1240690490@qq.com&gt;
......@@ -61,6 +90,34 @@ THE SOFTWARE.
<key>Type</key>
<string>PSGroupSpecifier</string>
</dict>
<dict>
<key>FooterText</key>
<string>Copyright (c) 2012-2016 Marin Todorov and JSONModel contributors
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
</string>
<key>License</key>
<string>MIT</string>
<key>Title</key>
<string>GMJSONModel</string>
<key>Type</key>
<string>PSGroupSpecifier</string>
</dict>
<dict>
<key>FooterText</key>
<string>Copyright (c) 2016 北京更美互动信息科技有限公司
......@@ -74,6 +131,35 @@ THE SOFTWARE.
<key>Type</key>
<string>PSGroupSpecifier</string>
</dict>
<dict>
<key>FooterText</key>
<string>Copyright (c) 2016 licong &lt;1240690490@qq.com&gt;
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
</string>
<key>License</key>
<string>MIT</string>
<key>Title</key>
<string>GMPhobos</string>
<key>Type</key>
<string>PSGroupSpecifier</string>
</dict>
<dict>
<key>FooterText</key>
<string>Copyright (c) 2019 Q14 &lt;qiaojinzhu@igengmei.com&gt;
......@@ -103,6 +189,34 @@ THE SOFTWARE.
<key>Type</key>
<string>PSGroupSpecifier</string>
</dict>
<dict>
<key>FooterText</key>
<string>Copyright © 2009-2016 Matej Bukovinski
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.</string>
<key>License</key>
<string>MIT</string>
<key>Title</key>
<string>MBProgressHUD</string>
<key>Type</key>
<string>PSGroupSpecifier</string>
</dict>
<dict>
<key>FooterText</key>
<string>Copyright (c) 2011-2012 Masonry Team - https://github.com/Masonry
......@@ -161,6 +275,217 @@ THE SOFTWARE.
<key>Type</key>
<string>PSGroupSpecifier</string>
</dict>
<dict>
<key>FooterText</key>
<string> Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [2013] [Tumblr, Inc.]
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.
</string>
<key>License</key>
<string>Apache 2.0</string>
<key>Title</key>
<string>TMCache</string>
<key>Type</key>
<string>PSGroupSpecifier</string>
</dict>
<dict>
<key>FooterText</key>
<string>Copyright 2019 tencent.com. All rights reserved.
......
FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_ROOT}/../../GMShareSDK/Frameworks"
GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
HEADER_SEARCH_PATHS = $(inherited) "${PODS_ROOT}/Headers/Public" "${PODS_ROOT}/Headers/Public/DouyinOpenSDK" "${PODS_ROOT}/Headers/Public/GMFoundation" "${PODS_ROOT}/Headers/Public/GMKit" "${PODS_ROOT}/Headers/Public/GMShareSDK" "${PODS_ROOT}/Headers/Public/Masonry" "${PODS_ROOT}/Headers/Public/SDWebImage" "${PODS_ROOT}/Headers/Public/WechatOpenSDK" "${PODS_ROOT}/Headers/Public/WeiboSDK"
LIBRARY_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/GMFoundation" "${PODS_CONFIGURATION_BUILD_DIR}/GMKit" "${PODS_CONFIGURATION_BUILD_DIR}/GMShareSDK" "${PODS_CONFIGURATION_BUILD_DIR}/Masonry" "${PODS_CONFIGURATION_BUILD_DIR}/SDWebImage" "${PODS_ROOT}/BDOpenSDKKit/BDOpenSDKKit" "${PODS_ROOT}/DouyinOpenSDK/DouyinOpenSDK" "${PODS_ROOT}/WechatOpenSDK/WeChatSDK1.8.6.1" "${PODS_ROOT}/WeiboSDK/libWeiboSDK"
OTHER_LDFLAGS = $(inherited) -ObjC -l"BDOpenSDKKit" -l"DouyinOpenSDK" -l"GMFoundation" -l"GMKit" -l"GMShareSDK" -l"Masonry" -l"SDWebImage" -l"WeChatSDK" -l"WeiboSDK" -l"c++" -l"sqlite3" -l"sqlite3.0" -l"z" -framework "CoreGraphics" -framework "CoreLocation" -framework "CoreTelephony" -framework "CoreText" -framework "Foundation" -framework "ImageIO" -framework "MapKit" -framework "Photos" -framework "QuartzCore" -framework "Security" -framework "SystemConfiguration" -framework "TencentOpenAPI" -framework "UIKit" -framework "WebKit"
HEADER_SEARCH_PATHS = $(inherited) "${PODS_ROOT}/Headers/Public" "${PODS_ROOT}/Headers/Public/DouyinOpenSDK" "${PODS_ROOT}/Headers/Public/GMCache" "${PODS_ROOT}/Headers/Public/GMFoundation" "${PODS_ROOT}/Headers/Public/GMJSONModel" "${PODS_ROOT}/Headers/Public/GMKit" "${PODS_ROOT}/Headers/Public/GMPhobos" "${PODS_ROOT}/Headers/Public/GMShareSDK" "${PODS_ROOT}/Headers/Public/MBProgressHUD" "${PODS_ROOT}/Headers/Public/Masonry" "${PODS_ROOT}/Headers/Public/SDWebImage" "${PODS_ROOT}/Headers/Public/TMCache" "${PODS_ROOT}/Headers/Public/WechatOpenSDK" "${PODS_ROOT}/Headers/Public/WeiboSDK"
LIBRARY_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/GMCache" "${PODS_CONFIGURATION_BUILD_DIR}/GMFoundation" "${PODS_CONFIGURATION_BUILD_DIR}/GMJSONModel" "${PODS_CONFIGURATION_BUILD_DIR}/GMKit" "${PODS_CONFIGURATION_BUILD_DIR}/GMPhobos" "${PODS_CONFIGURATION_BUILD_DIR}/GMShareSDK" "${PODS_CONFIGURATION_BUILD_DIR}/MBProgressHUD" "${PODS_CONFIGURATION_BUILD_DIR}/Masonry" "${PODS_CONFIGURATION_BUILD_DIR}/SDWebImage" "${PODS_CONFIGURATION_BUILD_DIR}/TMCache" "${PODS_ROOT}/BDOpenSDKKit/BDOpenSDKKit" "${PODS_ROOT}/DouyinOpenSDK/DouyinOpenSDK" "${PODS_ROOT}/WechatOpenSDK/WeChatSDK1.8.6.1" "${PODS_ROOT}/WeiboSDK/libWeiboSDK"
OTHER_LDFLAGS = $(inherited) -ObjC -l"BDOpenSDKKit" -l"DouyinOpenSDK" -l"GMCache" -l"GMFoundation" -l"GMJSONModel" -l"GMKit" -l"GMPhobos" -l"GMShareSDK" -l"MBProgressHUD" -l"Masonry" -l"SDWebImage" -l"TMCache" -l"WeChatSDK" -l"WeiboSDK" -l"c++" -l"sqlite3" -l"sqlite3.0" -l"z" -framework "CoreGraphics" -framework "CoreLocation" -framework "CoreTelephony" -framework "CoreText" -framework "Foundation" -framework "ImageIO" -framework "MapKit" -framework "Photos" -framework "QuartzCore" -framework "Security" -framework "SystemConfiguration" -framework "TencentOpenAPI" -framework "UIKit" -framework "WebKit" -weak_framework "UIKit"
PODS_BUILD_DIR = ${BUILD_DIR}
PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
PODS_PODFILE_DIR_PATH = ${SRCROOT}/.
......
FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_ROOT}/../../GMShareSDK/Frameworks"
GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
HEADER_SEARCH_PATHS = $(inherited) "${PODS_ROOT}/Headers/Public" "${PODS_ROOT}/Headers/Public/DouyinOpenSDK" "${PODS_ROOT}/Headers/Public/GMFoundation" "${PODS_ROOT}/Headers/Public/GMKit" "${PODS_ROOT}/Headers/Public/GMShareSDK" "${PODS_ROOT}/Headers/Public/Masonry" "${PODS_ROOT}/Headers/Public/SDWebImage" "${PODS_ROOT}/Headers/Public/WechatOpenSDK" "${PODS_ROOT}/Headers/Public/WeiboSDK"
LIBRARY_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/GMFoundation" "${PODS_CONFIGURATION_BUILD_DIR}/GMKit" "${PODS_CONFIGURATION_BUILD_DIR}/GMShareSDK" "${PODS_CONFIGURATION_BUILD_DIR}/Masonry" "${PODS_CONFIGURATION_BUILD_DIR}/SDWebImage" "${PODS_ROOT}/BDOpenSDKKit/BDOpenSDKKit" "${PODS_ROOT}/DouyinOpenSDK/DouyinOpenSDK" "${PODS_ROOT}/WechatOpenSDK/WeChatSDK1.8.6.1" "${PODS_ROOT}/WeiboSDK/libWeiboSDK"
OTHER_LDFLAGS = $(inherited) -ObjC -l"BDOpenSDKKit" -l"DouyinOpenSDK" -l"GMFoundation" -l"GMKit" -l"GMShareSDK" -l"Masonry" -l"SDWebImage" -l"WeChatSDK" -l"WeiboSDK" -l"c++" -l"sqlite3" -l"sqlite3.0" -l"z" -framework "CoreGraphics" -framework "CoreLocation" -framework "CoreTelephony" -framework "CoreText" -framework "Foundation" -framework "ImageIO" -framework "MapKit" -framework "Photos" -framework "QuartzCore" -framework "Security" -framework "SystemConfiguration" -framework "TencentOpenAPI" -framework "UIKit" -framework "WebKit"
HEADER_SEARCH_PATHS = $(inherited) "${PODS_ROOT}/Headers/Public" "${PODS_ROOT}/Headers/Public/DouyinOpenSDK" "${PODS_ROOT}/Headers/Public/GMCache" "${PODS_ROOT}/Headers/Public/GMFoundation" "${PODS_ROOT}/Headers/Public/GMJSONModel" "${PODS_ROOT}/Headers/Public/GMKit" "${PODS_ROOT}/Headers/Public/GMPhobos" "${PODS_ROOT}/Headers/Public/GMShareSDK" "${PODS_ROOT}/Headers/Public/MBProgressHUD" "${PODS_ROOT}/Headers/Public/Masonry" "${PODS_ROOT}/Headers/Public/SDWebImage" "${PODS_ROOT}/Headers/Public/TMCache" "${PODS_ROOT}/Headers/Public/WechatOpenSDK" "${PODS_ROOT}/Headers/Public/WeiboSDK"
LIBRARY_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/GMCache" "${PODS_CONFIGURATION_BUILD_DIR}/GMFoundation" "${PODS_CONFIGURATION_BUILD_DIR}/GMJSONModel" "${PODS_CONFIGURATION_BUILD_DIR}/GMKit" "${PODS_CONFIGURATION_BUILD_DIR}/GMPhobos" "${PODS_CONFIGURATION_BUILD_DIR}/GMShareSDK" "${PODS_CONFIGURATION_BUILD_DIR}/MBProgressHUD" "${PODS_CONFIGURATION_BUILD_DIR}/Masonry" "${PODS_CONFIGURATION_BUILD_DIR}/SDWebImage" "${PODS_CONFIGURATION_BUILD_DIR}/TMCache" "${PODS_ROOT}/BDOpenSDKKit/BDOpenSDKKit" "${PODS_ROOT}/DouyinOpenSDK/DouyinOpenSDK" "${PODS_ROOT}/WechatOpenSDK/WeChatSDK1.8.6.1" "${PODS_ROOT}/WeiboSDK/libWeiboSDK"
OTHER_LDFLAGS = $(inherited) -ObjC -l"BDOpenSDKKit" -l"DouyinOpenSDK" -l"GMCache" -l"GMFoundation" -l"GMJSONModel" -l"GMKit" -l"GMPhobos" -l"GMShareSDK" -l"MBProgressHUD" -l"Masonry" -l"SDWebImage" -l"TMCache" -l"WeChatSDK" -l"WeiboSDK" -l"c++" -l"sqlite3" -l"sqlite3.0" -l"z" -framework "CoreGraphics" -framework "CoreLocation" -framework "CoreTelephony" -framework "CoreText" -framework "Foundation" -framework "ImageIO" -framework "MapKit" -framework "Photos" -framework "QuartzCore" -framework "Security" -framework "SystemConfiguration" -framework "TencentOpenAPI" -framework "UIKit" -framework "WebKit" -weak_framework "UIKit"
PODS_BUILD_DIR = ${BUILD_DIR}
PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
PODS_PODFILE_DIR_PATH = ${SRCROOT}/.
......
FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_ROOT}/../../GMShareSDK/Frameworks"
GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
HEADER_SEARCH_PATHS = $(inherited) "${PODS_ROOT}/Headers/Public" "${PODS_ROOT}/Headers/Public/DouyinOpenSDK" "${PODS_ROOT}/Headers/Public/GMFoundation" "${PODS_ROOT}/Headers/Public/GMKit" "${PODS_ROOT}/Headers/Public/GMShareSDK" "${PODS_ROOT}/Headers/Public/Masonry" "${PODS_ROOT}/Headers/Public/SDWebImage" "${PODS_ROOT}/Headers/Public/WechatOpenSDK" "${PODS_ROOT}/Headers/Public/WeiboSDK"
HEADER_SEARCH_PATHS = $(inherited) "${PODS_ROOT}/Headers/Public" "${PODS_ROOT}/Headers/Public/DouyinOpenSDK" "${PODS_ROOT}/Headers/Public/GMCache" "${PODS_ROOT}/Headers/Public/GMFoundation" "${PODS_ROOT}/Headers/Public/GMJSONModel" "${PODS_ROOT}/Headers/Public/GMKit" "${PODS_ROOT}/Headers/Public/GMPhobos" "${PODS_ROOT}/Headers/Public/GMShareSDK" "${PODS_ROOT}/Headers/Public/MBProgressHUD" "${PODS_ROOT}/Headers/Public/Masonry" "${PODS_ROOT}/Headers/Public/SDWebImage" "${PODS_ROOT}/Headers/Public/TMCache" "${PODS_ROOT}/Headers/Public/WechatOpenSDK" "${PODS_ROOT}/Headers/Public/WeiboSDK"
OTHER_LDFLAGS = $(inherited) -l"c++" -l"sqlite3" -l"sqlite3.0" -l"z" -framework "CoreGraphics" -framework "CoreLocation" -framework "CoreTelephony" -framework "CoreText" -framework "Foundation" -framework "ImageIO" -framework "MapKit" -framework "Photos" -framework "QuartzCore" -framework "Security" -framework "SystemConfiguration" -framework "UIKit" -framework "WebKit"
PODS_BUILD_DIR = ${BUILD_DIR}
PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
......
FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_ROOT}/../../GMShareSDK/Frameworks"
GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
HEADER_SEARCH_PATHS = $(inherited) "${PODS_ROOT}/Headers/Public" "${PODS_ROOT}/Headers/Public/DouyinOpenSDK" "${PODS_ROOT}/Headers/Public/GMFoundation" "${PODS_ROOT}/Headers/Public/GMKit" "${PODS_ROOT}/Headers/Public/GMShareSDK" "${PODS_ROOT}/Headers/Public/Masonry" "${PODS_ROOT}/Headers/Public/SDWebImage" "${PODS_ROOT}/Headers/Public/WechatOpenSDK" "${PODS_ROOT}/Headers/Public/WeiboSDK"
HEADER_SEARCH_PATHS = $(inherited) "${PODS_ROOT}/Headers/Public" "${PODS_ROOT}/Headers/Public/DouyinOpenSDK" "${PODS_ROOT}/Headers/Public/GMCache" "${PODS_ROOT}/Headers/Public/GMFoundation" "${PODS_ROOT}/Headers/Public/GMJSONModel" "${PODS_ROOT}/Headers/Public/GMKit" "${PODS_ROOT}/Headers/Public/GMPhobos" "${PODS_ROOT}/Headers/Public/GMShareSDK" "${PODS_ROOT}/Headers/Public/MBProgressHUD" "${PODS_ROOT}/Headers/Public/Masonry" "${PODS_ROOT}/Headers/Public/SDWebImage" "${PODS_ROOT}/Headers/Public/TMCache" "${PODS_ROOT}/Headers/Public/WechatOpenSDK" "${PODS_ROOT}/Headers/Public/WeiboSDK"
OTHER_LDFLAGS = $(inherited) -l"c++" -l"sqlite3" -l"sqlite3.0" -l"z" -framework "CoreGraphics" -framework "CoreLocation" -framework "CoreTelephony" -framework "CoreText" -framework "Foundation" -framework "ImageIO" -framework "MapKit" -framework "Photos" -framework "QuartzCore" -framework "Security" -framework "SystemConfiguration" -framework "UIKit" -framework "WebKit"
PODS_BUILD_DIR = ${BUILD_DIR}
PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
......
#import <Foundation/Foundation.h>
@interface PodsDummy_TMCache : NSObject
@end
@implementation PodsDummy_TMCache
@end
#ifdef __OBJC__
#import <UIKit/UIKit.h>
#else
#ifndef FOUNDATION_EXPORT
#if defined(__cplusplus)
#define FOUNDATION_EXPORT extern "C"
#else
#define FOUNDATION_EXPORT extern
#endif
#endif
#endif
CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/TMCache
GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
HEADER_SEARCH_PATHS = $(inherited) "${PODS_ROOT}/Headers/Private" "${PODS_ROOT}/Headers/Private/TMCache" "${PODS_ROOT}/Headers/Public" "${PODS_ROOT}/Headers/Public/TMCache"
PODS_BUILD_DIR = ${BUILD_DIR}
PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
PODS_ROOT = ${SRCROOT}
PODS_TARGET_SRCROOT = ${PODS_ROOT}/TMCache
PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier}
SKIP_INSTALL = YES
......@@ -38,10 +38,12 @@ TODO: Add long description of the pod here.
s.dependency 'WeiboSDK'
s.dependency 'DouyinOpenSDK'
s.dependency 'GMKit'
# s.dependency 'MCTencentOpenAPI'
s.dependency 'MBProgressHUD'
# s.dependency 'GMBase'
s.dependency 'GMJSONModel'
s.dependency 'GMPhobos'
s.dependency 'GMFoundation'
s.dependency 'Masonry'
s.frameworks = ['Photos']
# s.libraries = 'xml2', 'z', 'c++', 'sqlite3'
......
//
// ALPlatformView.h
// GMAlpha
//
// Created by Mikasa on 2019/1/10.
// Copyright © 2019 Gengmei. All rights reserved.
//
#import <GMThirdPartyDefine.h>
@interface ALPlatformView : UIView
@property (nonatomic, copy) void(^sharePlatformClick)(GMSharePlatform platform);
@end
//
// ALPlatformView.m
// GMAlpha
//
// Created by Mikasa on 2019/1/10.
// Copyright © 2019 Gengmei. All rights reserved.
//
#import "ALPlatformView.h"
#import "ALShareButton.h"
#import "GMShareSDK.h"
#import <GMKit/GMLabel.h>
#import <GMKit/UIColor+GMTheme.h>
#import <GMFoundation/NSString+GM.h>
#import <Masonry/Masonry.h>
#import <GMKit/GMButton.h>
#import <GMKit/Constant.h>
#import <MBProgressHUD/MBProgressHUD.h>
@interface ALPlatformView()
@property (nonatomic, strong) NSArray *platformArray;
@end
@implementation ALPlatformView
- (instancetype)initWithFrame:(CGRect)frame {
if (self = [super initWithFrame:frame]) {
[self addDefaultPlatform];
}
return self;
}
// 添加
- (void)addDefaultPlatform {
self.platformArray = @[@(GMSharePlatformWechatSession),
@(GMSharePlatformWechatTimeline),
@(GMSharePlatformQQFriend),
@(GMSharePlatformSinaWeibo)];
[self addPlatformTypeSubView];
}
#pragma mark - addSinInTypeSubView
- (void)addPlatformTypeSubView {
for (NSNumber *type in self.platformArray) {
[self addSinInType:[type integerValue]];
}
NSInteger count = self.subviews.count;
CGFloat space = (MAINSCREEN_WIDTH - (self.subviews.count * kALShareButtonW))/(self.subviews.count +1);
__weak __typeof(self)weakSelf = self;
for (int i = 0; i < count; i++) {
ALShareButton *button = self.subviews[i];
if (i == 0) {
[button mas_makeConstraints:^(MASConstraintMaker *make) {
make.centerY.mas_equalTo(weakSelf.mas_centerY);
make.left.mas_equalTo(space);
make.size.mas_equalTo(CGSizeMake(kALShareButtonW, kALShareButtonH));
}];
} else {
ALShareButton *prebutton = self.subviews[i-1];
[button mas_makeConstraints:^(MASConstraintMaker *make) {
make.centerY.mas_equalTo(weakSelf.mas_centerY);
make.size.mas_equalTo(CGSizeMake(kALShareButtonW, kALShareButtonH));
make.left.mas_equalTo(prebutton.mas_right).offset(space);
}];
}
}
}
- (void)addSinInType:(GMSharePlatform)type {
NSString *iconString = @"";
NSString *title = @"";
switch (type) {
case GMSharePlatformWechatSession:
{
iconString = @"share_weixin_session";
title = @"微信";
}
break;
case GMSharePlatformSinaWeibo:
{
iconString = @"share_sina_weibo";
title = @"微博";
}
break;
case GMSharePlatformWechatTimeline:
{
iconString = @"share_weixin_timeline";
title = @"朋友圈";
}
break;
case GMSharePlatformQQFriend:
{
iconString = @"share_qq";
title = @"QQ";
}
break;
default:
break;
}
ALShareButton *button = [[ALShareButton alloc] init];
button.tag = 1000 + type;
[button setImage:[UIImage imageNamed:iconString] forState:UIControlStateNormal];
[button setImage:[UIImage imageNamed:iconString] forState:UIControlStateHighlighted];
[button setTitle:title forState:UIControlStateNormal];
[button addTarget:self action:@selector(sinInTypeClick:) forControlEvents:UIControlEventTouchUpInside];
[self addSubview:button];
}
// 其他平台
- (void)sinInTypeClick:(GMButton *)sender {
GMSharePlatform platform = sender.tag - 1000;
if (![GMShareSDK isClientInstalled:platform] &&
(platform == GMSharePlatformWechatTimeline | platform == GMSharePlatformWechatSession)) {
//QQ和新浪支持web分享,只有微信不支持,这里优化下提示语
[self toast:@"您未安装微信客户端"];
return;
}
if (self.sharePlatformClick) {
self.sharePlatformClick(platform);
}
}
/** * @brief 分享结果提示*/
- (void)toast:(NSString *)text{
// [_hud hide:YES];
MBProgressHUD *hudProgrss = [MBProgressHUD showHUDAddedTo:[UIApplication sharedApplication].keyWindow animated:YES];
hudProgrss.mode = MBProgressHUDModeText;
hudProgrss.userInteractionEnabled = NO;
hudProgrss.labelText = text;
hudProgrss.removeFromSuperViewOnHide = YES;
[hudProgrss hide:YES afterDelay:1.0];
}
@end
//
// ALShareButton.h
// GMAlpha
//
// Created by Mikasa on 2019/1/10.
// Copyright © 2019 Gengmei. All rights reserved.
//
#define kShareImageWH 45
#define kALShareButtonW 48
#define kALShareButtonH (kShareImageWH + 19 + 5)
@interface ALShareButton : UIButton
@end
//
// ALShareButton.m
// GMAlpha
//
// Created by Mikasa on 2019/1/10.
// Copyright © 2019 Gengmei. All rights reserved.
//
#import "ALShareButton.h"
#import <GMKit/UIColor+GMTheme.h>
#import <GMKit/GMFont.h>
@implementation ALShareButton
- (instancetype)initWithFrame:(CGRect)frame {
if (self = [super initWithFrame:frame]) {
[self setTitleColor:RGBCOLOR_HEX(0x323232) forState:UIControlStateNormal];
self.titleLabel.font = [UIFont gmFont:13];
self.titleLabel.textAlignment = NSTextAlignmentCenter;
}
return self;
}
- (CGRect)imageRectForContentRect:(CGRect)contentRect {
return CGRectMake((kALShareButtonW - kShareImageWH)/2, 0, kShareImageWH, kShareImageWH);
}
- (CGRect)titleRectForContentRect:(CGRect)contentRect {
return CGRectMake(0, kShareImageWH + 5, kALShareButtonW, 19);
}
@end
//
// ALShareTopicCardView.h
// GMAlpha
//
// Created by Mikasa on 2019/2/25.
// Copyright © 2019 Gengmei. All rights reserved.
//
#import <GMKit/GMLabel.h>
#import <GMKit/GMImageView.h>
#import <GMKit/GMView.h>
@interface ALShareTopicCardView : GMView
@property (nonatomic, strong) GMImageView *cardImageView;
@property (nonatomic, strong) GMLabel *cardTitleLabel;
@end
//
// ALShareTopicCardView.m
// GMAlpha
//
// Created by Mikasa on 2019/2/25.
// Copyright © 2019 Gengmei. All rights reserved.
//
#import "ALShareTopicCardView.h"
#import <GMKit/GMFont.h>
#import <GMKit/Constant.h>
#import <GMKit/UIColor+GMTheme.h>
#define kTopicCardViewW (MAINSCREEN_WIDTH - 30)
@interface ALShareTopicCardView ()
@end
@implementation ALShareTopicCardView
- (GMImageView *)cardImageView {
if (!_cardImageView) {
GMImageView *cardImageView = [GMImageView new];
cardImageView.contentMode = UIViewContentModeScaleAspectFit;
_cardImageView = cardImageView;
}
return _cardImageView;
}
- (GMLabel *)cardTitleLabel {
if (!_cardTitleLabel) {
GMLabel *cardTitleLabel = [GMLabel new];
cardTitleLabel.font = [UIFont gmFont:15];
cardTitleLabel.textColor = RGBCOLOR_HEX(0x323232);
cardTitleLabel.numberOfLines = 3;
_cardTitleLabel = cardTitleLabel;
}
return _cardTitleLabel;
}
- (void)setup {
[super setup];
self.backgroundColor = UIColor.whiteColor;
[self addSubview:self.cardImageView];
[self addSubview:self.cardTitleLabel];
[self addSetupContraints];
}
- (void)addSetupContraints {
CGFloat cardImageViewWH = (kTopicCardViewW - 20);
[self.cardImageView mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.top.mas_equalTo(10);
make.height.width.mas_equalTo(cardImageViewWH);
}];
[self.cardTitleLabel mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.mas_equalTo(_cardImageView.mas_bottom).offset(10);
make.left.mas_equalTo(16);
make.right.mas_equalTo(-16);
make.bottom.mas_equalTo(-18);
}];
}
@end
//
// GMDiaryShareInfoView.h
// Gengmei
//
// Created by yefengming on 2018/12/20.
// Copyright © 2018年 更美互动信息科技有限公司. All rights reserved.
//
#import <GMKit/GMLabel.h>
#import <GMKit/GMButton.h>
@interface GMDiaryShareInfoView : UIView
@property (nonatomic, strong) GMLabel *diaryNameLabel;
@property (nonatomic, strong) GMButton *shareButton;
@property (nonatomic, copy) void (^shareClickedBlcok)(void);
@end
//
// GMDiaryShareInfoView.m
// Gengmei
//
// Created by yefengming on 2018/12/20.
// Copyright © 2018年 更美互动信息科技有限公司. All rights reserved.
//
#import "GMDiaryShareInfoView.h"
#import <GMKit/UIColor+GMTheme.h>
#import <Masonry/Masonry.h>
#import <GMKit/Constant.h>
@implementation GMDiaryShareInfoView
- (instancetype)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
self.backgroundColor = [UIColor whiteColor];
self.diaryNameLabel = [GMLabel labelWithTextColor:RGBCOLOR_HEX(0x333333) fontSize:15];
[self addSubview:self.diaryNameLabel];
self.shareButton = [[GMButton alloc] init];
[self.shareButton setImage:[UIImage imageNamed:@"share_diary"] forState:UIControlStateNormal];
[self.shareButton setImage:[UIImage imageNamed:@"share_diary"] forState:UIControlStateHighlighted];
[self.shareButton addTarget:self action:@selector(shareButtonClick:) forControlEvents:UIControlEventTouchUpInside];
[self addSubview:self.shareButton];
[self.diaryNameLabel mas_remakeConstraints:^(MASConstraintMaker *make) {
make.left.mas_equalTo(20);
make.height.mas_equalTo(15);
make.centerY.mas_equalTo(self.shareButton);
make.width.mas_equalTo(MAINSCREEN_WIDTH - 99);
}];
[self.shareButton mas_remakeConstraints:^(MASConstraintMaker *make) {
make.right.mas_equalTo(-20);
make.top.mas_equalTo(14);
}];
}
return self;
}
- (void)shareButtonClick:(UIButton *)sender {
if (self.shareClickedBlcok) {
self.shareClickedBlcok();
}
}
@end
//
// GMDiaryShareView.h
// Gengmei
//
// Created by yefengming on 2018/12/19.
// Copyright © 2018年 更美互动信息科技有限公司. All rights reserved.
//
#import "GMShareTopView.h"
#import "GMShareSDK.h"
#import "WMShareObject.h"
@class GMShareView;
@protocol GMDiaryShareViewDelegate <NSObject>
@optional
/** @brief 定制分享定制内容*/
- (NSMutableDictionary *)fetchSharePublishContent:(GMSharePlatform)shareType;
/** 分享成功 */
- (void)shareResponseSuccess;
/** @brief 分享日记的shareObject*/
- (void)shareObject:(WMShareObject *)object;
@end
@interface GMDiaryShareView : GMView
/**
连续签到,补签任务
*/
@property (nonatomic, strong) GMShareTopView *topView;
@property (nonatomic, weak) id<GMDiaryShareViewDelegate> delegate;
/** @brief 显示分享界面*/
- (void)showWithShareArr:(NSMutableArray *)shareArr;
/** @brief 隐藏分享界面。最后会remove super view*/
- (void)hideShareView;
@end
//
// GMDiaryShareView.m
// Gengmei
//
// Created by yefengming on 2018/12/19.
// Copyright © 2018年 更美互动信息科技有限公司. All rights reserved.
//
#import "GMDiaryShareView.h"
#import "GMDiaryShareInfoView.h"
#import "WMShareObject.h"
#import <MBProgressHUD/MBProgressHUD.h>
#import <GMPhobos/Phobos.h>
#import <GMPhobos/UIResponder+PhobosPV.h>
#import <GMKit/UIColor+GMTheme.h>
#import <GMKit/Constant.h>
#import <GMKit/GMFont.h>
#import <GMKit/Constant.h>
#import <GMKit/UIView+LineWithAutolayout.h>
#import <GMKit/GMCollectionView.h>
#import <GMFoundation/NSString+GM.h>
#import <GMKit/UIView+SafeArea.h>
#import <GMKit/GMSafeValue.h>
#import <GMKit/UIView+Layout.h>
@interface GMDiaryShareView ()
@property (nonatomic, strong) GMView *bgView;
@property (nonatomic, strong) GMView *alphaView;
@property (nonatomic, strong) GMButton *closeButton;
@property (nonatomic, strong) GMView *infoView;
@property (nonatomic, strong) MBProgressHUD *hud;
@property (nonatomic, strong) GMView *bottomView;
@property (nonatomic, copy) NSString *shareUrl;
@end
@implementation GMDiaryShareView
- (instancetype)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
_alphaView = [[GMView alloc]init];
_alphaView.backgroundColor = [UIColor blackColor];
_alphaView.alpha = 0.5;
[self addSubview:_alphaView];
_bgView = [[GMView alloc]init];
[self addSubview:_bgView];
_closeButton = [GMButton buttonWithType:UIButtonTypeCustom];
_closeButton.backgroundColor = [UIColor whiteColor];
[_closeButton setTitleColor:UIColor.headlineText forState:UIControlStateNormal];
[_closeButton setTitle:@"取消" forState: UIControlStateNormal];
_closeButton.titleLabel.font = [UIFont gmFont:16];
[_closeButton addTarget:self action:@selector(closeButtonClicked:) forControlEvents:UIControlEventTouchUpInside];
[_bgView addSubview:_closeButton];
[_closeButton addTopLine];
_topView = [[GMShareTopView alloc] init];
[_bgView addSubview:_topView];
_infoView = [[GMView alloc] init];
[_bgView addSubview:_infoView];
_bottomView = [[GMView alloc] init];
_bottomView.backgroundColor = [UIColor whiteColor];
[_bgView addSubview:_bottomView];
[_alphaView mas_makeConstraints:^(MASConstraintMaker *make) {
make.edges.mas_equalTo(0);
}];
[_bgView mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.right.mas_equalTo(0);
make.bottom.mas_equalTo(0);
}];
[_closeButton mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.equalTo(_infoView.mas_bottom);
make.left.right.mas_equalTo(0);
make.height.mas_equalTo(45);
make.bottom.mas_equalTo(-UIView.safeAreaInsetsBottom);
}];
[_bottomView mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.right.bottom.mas_equalTo(0);
make.height.mas_equalTo(UIView.safeAreaInsetsBottom);
}];
[_infoView mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.right.mas_equalTo(0);
make.bottom.mas_equalTo(_closeButton.mas_top);
make.height.mas_equalTo(150);
}];
[_topView mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.right.mas_equalTo(0);
make.bottom.mas_equalTo(_infoView.mas_top).offset(11);
make.top.mas_equalTo(0);
}];
[_topView updateSubtitleConstraintsWithBottom:-30];
UITapGestureRecognizer *recognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(closeButtonClicked:)];
[_alphaView addGestureRecognizer:recognizer];
}
return self;
}
- (void)setDiaryArr:(NSMutableArray *)diaryArr {
for (int i = 0; i < diaryArr.count; i++) {
GMDiaryShareObject *object = diaryArr[i];
GMDiaryShareInfoView *view = [[GMDiaryShareInfoView alloc] initWithFrame:CGRectMake(0, 50*i, MAINSCREEN_WIDTH, 50)];
view.diaryNameLabel.text = object.title;
[_infoView addSubview:view];
__weak __typeof(self)weakSelf = self;
[view setShareClickedBlcok:^{
weakSelf.shareUrl = object.shareData.url;
[weakSelf hideShareView];
if ([weakSelf.delegate respondsToSelector:@selector(shareObject:)]) {
[weakSelf.delegate shareObject:object.shareData];
}
[weakSelf shareWithType:GMSharePlatformWechatTimeline];
}];
if (i < diaryArr.count - 1) {
[view addBottomLineWithLeft:20 right:-20];
}
}
[_infoView mas_updateConstraints:^(MASConstraintMaker *make) {
make.height.mas_equalTo(diaryArr.count * 50);
}];
}
- (void)shareWithType:(GMSharePlatform)type {
if (![GMShareSDK isClientInstalled:type]) {
[self toast:@"您未安装微信客户端"];
return;
}
//获取定制的分享内容
NSMutableDictionary *shareParams = [NSMutableDictionary dictionary];
if ([self.delegate respondsToSelector:@selector(fetchSharePublishContent:)]) {
shareParams = [self.delegate fetchSharePublishContent:GMSharePlatformWechatTimeline];
}
[self phobosClickShareWithType:type];
[[GMShareSDK shareInstance] share: type parameters:[shareParams mutableCopy] onStateChanged:^(GMShareResponseState state, NSError *error) {
switch (state) {
case GMShareResponseStateBegin:{
// debugLog(@"启动分享");
break;
}
case GMShareResponseStateSuccess:{
// debugLog(@"分享成功");
if (self) {
if (self.delegate != nil && ![self.delegate isKindOfClass:[NSNull class]]) {
if ([self.delegate respondsToSelector:@selector(shareResponseSuccess)]) {
[self.delegate shareResponseSuccess];
}
}
}
break;
}
case GMShareResponseStateFail:{
// debugLog(@"分享失败,错误描述:%@", error);
break;
}
case GMShareResponseStateCancel:{
// debugLog(@"取消分享");
break;
}
default:
break;
}
}];
}
// page_click_share_channel 埋点
- (void)phobosClickShareWithType:(GMSharePlatform)type {
if ([self.delegate isKindOfClass:[UIResponder class]]) {
UIResponder *phobosView = (UIResponder *)self.delegate;
NSDictionary *dic = @{@"page_name": SafeValue(phobosView.pageName),
@"business_id": SafeValue(phobosView.businessId),
@"url": SafeValue(self.shareUrl),
@"share_channel": [self channel:type],
@"task_type":@"diary",
@"business":@"diary_id"
};
[Phobos track:@"page_click_share_channel" attributes:dic];
}
}
- (NSString *)channel:(GMSharePlatform)type {
NSString *channel = @"";
switch (type) {
case GMSharePlatformWechatSession:
channel = @"wechat";
break;
case GMSharePlatformWechatTimeline:
channel = @"wechatline";
break;
case GMSharePlatformQQFriend:
channel = @"qq";
break;
case GMSharePlatformQQSpace:
channel = @"qzone";
break;
case GMSharePlatformSinaWeibo:
channel = @"tsina";
break;
case GMSharePlatformWechatSessionSnapShot:
channel = @"wechat_snapshot";
break;
case GMSharePlatformWechatTimelineSnapShot:
channel = @"wechatline_snapshot";
break;
case GMSharePlatformCopyLink:
channel = @"copy_url";
break;
default:
channel = @"";
break;
}
return channel;
}
/** * @brief 分享结果提示*/
- (void)toast:(NSString *)text{
[_hud hide:YES];
MBProgressHUD *hudProgrss = [MBProgressHUD showHUDAddedTo:[UIApplication sharedApplication].keyWindow animated:YES];
hudProgrss.mode = MBProgressHUDModeText;
hudProgrss.userInteractionEnabled = NO;
hudProgrss.labelText = text;
hudProgrss.removeFromSuperViewOnHide = YES;
[hudProgrss hide:YES afterDelay:1.0];
}
- (void)closeButtonClicked:(UIButton *)sender {
[self hideShareView];
}
/**
* @brief 分享界面的有关动画
添加上滑动画
usingSpringWithDamping的范围为0.0f到1.0f,数值越小「弹簧」的振动效果越明显。
initialSpringVelocity则表示初始的速度,数值越大一开始移动越快。
http://www.renfei.org/blog/ios-8-spring-animation.html
*
*/
- (void)showWithShareArr:(NSMutableArray *)shareArr {
[self setDiaryArr:shareArr];
self.frame = MAINSCREEN_FRAME;
_bgView.top = MAINSCREEN_HEIGHT; //storyBoard中在屏幕中央,重置在屏幕最底下
[[UIApplication sharedApplication].keyWindow addSubview:self];
[UIView animateWithDuration:0.25 delay:0.1 options:UIViewAnimationOptionCurveEaseInOut animations:^{
self.backgroundColor = [UIColor colorWithRed:0 green:0 blue:0 alpha:0.6];
} completion:nil];
[UIView animateWithDuration:0.25 animations:^{
_bgView.top = _bgView.height;
}];
}
- (void)hideShareView {
[UIView animateWithDuration:0.25 animations:^{
_bgView.top = MAINSCREEN_HEIGHT;
self.backgroundColor = [UIColor colorWithRed:0 green:0 blue:0 alpha:0];
} completion:^(BOOL finished) {
[self removeFromSuperview];
}];
}
/** * @brief 分享启动提示*/
- (void)showLoading:(NSString *)text
{
_hud = [MBProgressHUD showHUDAddedTo:[UIApplication sharedApplication].keyWindow animated:YES];
_hud.mode = MBProgressHUDModeIndeterminate;
_hud.userInteractionEnabled = YES;
_hud.labelText = text;
_hud.margin = 10.f;
_hud.removeFromSuperViewOnHide = YES;
[_hud show:YES];
}
- (void)hideLoading{
[_hud hide:YES];
}
@end
//
// GMShareBaseObject.h
// GMShareSDK
//
// Created by Q14 on 2019/12/24.
//
#import <GMJSONModel/JSONModel.h>
@interface GMShareBaseObject : JSONModel
@end
//
// GMShareBaseObject.m
// GMShareSDK
//
// Created by Q14 on 2019/12/24.
//
#import "GMShareBaseObject.h"
@implementation GMShareBaseObject
@end
//
// WMShareCellCollectionViewCell.h
// Gengmei
//
// Created by Sean Lee on 1/23/15.
// Copyright (c) 2015 Wanmeichuangyi. All rights reserved.
//
#import <UIKit/UIKit.h>
#import "GMThirdPartyDefine.h"
typedef void (^TransferResponseChainBlock) (void);
@class GMButton;
@interface GMShareCell : UICollectionViewCell<UIGestureRecognizerDelegate>
@property (nonatomic, strong) UIButton * shareButton;
/**
* @brief 因为使用了button来布局,这样cell的高亮处理方便些。所以这里用一个block进行响应链传递,手动执行collectionView的didselect方法
*/
@property (nonatomic, copy) TransferResponseChainBlock transferResponseChainBlock;
@property (nonatomic, assign) GMSharePlatform shareType;
@end
//
// WMShareCellCollectionViewCell.m
// Gengmei
//
// Created by Sean Lee on 1/23/15.
// Copyright (c) 2015 Wanmeichuangyi. All rights reserved.
//
#import "GMShareCell.h"
#import "GMShareFlowLayout.h"
#import <GMKit/GMFont.h>
#import <GMKit/UIColor+GMTheme.h>
#import <GMFoundation/NSString+GM.h>
@implementation GMShareCell
- (instancetype)initWithFrame:(CGRect)frame{
self = [super initWithFrame:frame];
if (self) {
_shareButton = [UIButton buttonWithType:UIButtonTypeCustom];
_shareButton.titleLabel.font = [UIFont gmFont:12];
[_shareButton setTitleColor:UIColor.auxiliaryTextLight forState:UIControlStateNormal];
[_shareButton setTitle:@"好友QQ" forState:UIControlStateNormal];
[_shareButton setImage:[UIImage imageNamed:@"share_weixin_session"] forState:UIControlStateNormal];
[_shareButton addTarget:self action:@selector(shareButtonClicked:) forControlEvents:UIControlEventTouchUpInside];
[_shareButton sizeToFit];
[self.contentView addSubview:_shareButton];
//图片是40x40
_shareButton.frame = CGRectMake(0, 0, SHARE_BUTTON_WIDTH, SHARE_BUTTON_HEIGHT);
_shareButton.userInteractionEnabled = YES;
//设置图片居上,文字居下
CGFloat spacing = 15;
CGSize imageSize = _shareButton.currentImage.size;
CGSize titleSize = [_shareButton.currentTitle sizeWithFont:[UIFont gmFont:12] boundSize:CGSizeMake(_shareButton.frame.size.width, CGFLOAT_MAX)];
CGFloat totalHeight = (imageSize.height + titleSize.height + spacing);
_shareButton.imageEdgeInsets = UIEdgeInsetsMake(- (titleSize.height), (SHARE_BUTTON_WIDTH - imageSize.width)/2, 0.0, 0.0);
_shareButton.titleEdgeInsets = UIEdgeInsetsMake(0.0, - (imageSize.width), - (totalHeight - titleSize.height),0.0);
}
return self;
}
- (void)shareButtonClicked:(UIButton *)button{
if (self.transferResponseChainBlock) {
self.transferResponseChainBlock();
}
}
@end
//
// WMShareCollectionViewFlowLayout.h
// Gengmei
//
// Created by Sean Lee on 1/23/15.
// Copyright (c) 2015 Wanmeichuangyi. All rights reserved.
//
#import <UIKit/UIKit.h>
#define SHARE_BUTTON_HEIGHT 70
#define SHARE_BUTTON_WIDTH 50
//#define COLLECTIONVIEW_HEIGHT 236
@interface GMShareFlowLayout : UICollectionViewFlowLayout
@end
//
// WMShareCollectionViewFlowLayout.m
// Gengmei
//
// Created by Sean Lee on 1/23/15.
// Copyright (c) 2015 Wanmeichuangyi. All rights reserved.
//
#import "GMShareFlowLayout.h"
#import <GMKit/Constant.h>
@implementation GMShareFlowLayout
-(void)prepareLayout{
[super prepareLayout];
self.minimumLineSpacing = 30;
self.itemSize = CGSizeMake(50, 70);
self.sectionInset = UIEdgeInsetsMake(20, 24, 20, 24);
self.minimumInteritemSpacing = floor((MAINSCREEN_WIDTH - SHARE_BUTTON_WIDTH*5 - self.sectionInset.left - self.sectionInset.right)/4);
[self setScrollDirection:UICollectionViewScrollDirectionVertical];
}
@end
//
// GMShareTopView.h
// Gengmei
//
// Created by yefengming on 2018/12/19.
// Copyright © 2018年 更美互动信息科技有限公司. All rights reserved.
//
#import <GMKit/GMView.h>
//#import <GMKit/GMButton.h>
@interface GMShareTopView : GMView
- (void)setName:(NSString *)name title:(NSString *)title subTitle:(NSString *)subTitle;
- (void)onlyTitle:(NSString *)title;
- (void)updateSubtitleConstraintsWithBottom:(CGFloat)bottom;
@end
//
// GMShareTopView.m
// Gengmei
//
// Created by yefengming on 2018/12/19.
// Copyright © 2018年 更美互动信息科技有限公司. All rights reserved.
//
#import "GMShareTopView.h"
#import <GMKit/GMLabel.h>
#import <GMKit/UIColor+GMTheme.h>
#import <GMFoundation/NSString+GM.h>
#import <GMKit/GMFont.h>
@interface GMShareTopView () {
GMLabel *_leftLabel;
GMLabel *_rightLabel;
}
@property (nonatomic, strong) GMLabel *nameLabel;
@property (nonatomic, strong) GMLabel *titleLabel;
@property (nonatomic, strong) GMLabel *subTitleLabel;
@end
@implementation GMShareTopView
- (instancetype)init
{
self = [super init];
if (self) {
self.layer.cornerRadius = 11;
self.layer.masksToBounds = YES;
self.backgroundColor = [UIColor whiteColor];
self.nameLabel = [GMLabel labelWithTextColor:RGBCOLOR_HEX(0x333333) fontSize:16];
self.nameLabel.textAlignment = NSTextAlignmentCenter;
[self addSubview:self.nameLabel];
self.titleLabel = [GMLabel labelWithTextColor:UIColor.auxiliaryTextLight fontSize:13];
self.titleLabel.textAlignment = NSTextAlignmentCenter;
[self addSubview:self.titleLabel];
self.subTitleLabel = [GMLabel labelWithTextColor:[UIColor colorWithHexString:@"FF7690"] fontSize:13];
self.subTitleLabel.textAlignment = NSTextAlignmentCenter;
[self addSubview:self.subTitleLabel];
_leftLabel = [[GMLabel alloc] init];
_leftLabel.backgroundColor = UIColor.separatorLine;
[self addSubview:_leftLabel];
_rightLabel = [[GMLabel alloc] init];
_rightLabel.backgroundColor = UIColor.separatorLine;
[self addSubview:_rightLabel];
}
return self;
}
- (void)setSubViewsConstraints {
[self.nameLabel mas_remakeConstraints:^(MASConstraintMaker *make) {
make.top.mas_equalTo(24);
make.height.mas_equalTo(16);
make.left.mas_equalTo(10);
make.right.mas_equalTo(-10);
}];
[self.titleLabel mas_remakeConstraints:^(MASConstraintMaker *make) {
make.top.mas_equalTo(self.nameLabel.mas_bottom).offset(12);
make.height.mas_equalTo(13);
make.left.right.mas_equalTo(self.nameLabel);
}];
[self.subTitleLabel mas_remakeConstraints:^(MASConstraintMaker *make) {
make.top.mas_equalTo(self.titleLabel.mas_bottom).offset(7);
make.height.mas_equalTo(13);
make.bottom.mas_equalTo(-11);
make.left.right.mas_equalTo(self.nameLabel);
}];
}
- (void)setName:(NSString *)name title:(NSString *)title subTitle:(NSString *)subTitle {
self.nameLabel.text = name;
self.titleLabel.text = title;
self.subTitleLabel.text = subTitle;
[self setSubViewsConstraints];
if (!title.isNonEmpty && !subTitle.isNonEmpty) {
[self.nameLabel mas_updateConstraints:^(MASConstraintMaker *make) {
make.bottom.mas_equalTo(-11);
}];
} else if (title.isNonEmpty && !subTitle.isNonEmpty) {
[self.titleLabel mas_updateConstraints:^(MASConstraintMaker *make) {
make.bottom.mas_equalTo(-11);
}];
} else if (!title.isNonEmpty && subTitle.isNonEmpty) {
[self.subTitleLabel mas_updateConstraints:^(MASConstraintMaker *make) {
make.top.mas_equalTo(self.nameLabel.mas_bottom).offset(11);
}];
}
}
- (void)onlyTitle:(NSString *)title {
self.titleLabel.text = title;
self.titleLabel.textColor = UIColor.auxiliaryTextDark;
self.titleLabel.font = [UIFont gmFont:16];
self.layer.cornerRadius = 0;
[self.titleLabel mas_remakeConstraints:^(MASConstraintMaker *make) {
make.top.mas_equalTo(28);
make.centerX.mas_equalTo(0);
make.height.mas_equalTo(16);
make.bottom.mas_equalTo(0);
}];
[_leftLabel mas_remakeConstraints:^(MASConstraintMaker *make) {
make.centerY.mas_equalTo(_titleLabel);
make.right.mas_equalTo(_titleLabel.mas_left).offset(-8);
make.height.mas_equalTo(1);
make.width.mas_equalTo(15);
}];
[_rightLabel mas_remakeConstraints:^(MASConstraintMaker *make) {
make.centerY.mas_equalTo(_titleLabel);
make.left.mas_equalTo(_titleLabel.mas_right).offset(8);
make.height.mas_equalTo(1);
make.width.mas_equalTo(15);
}];
}
- (void)updateSubtitleConstraintsWithBottom:(CGFloat)bottom {
[self.subTitleLabel mas_updateConstraints:^(MASConstraintMaker *make) {
make.bottom.mas_equalTo(bottom);
}];
}
@end
//
// GMShareVideoAlertView.h
// Gengmei
//
// Created by 朱璇 on 2019/8/3.
// Copyright © 2019 更美互动信息科技有限公司. All rights reserved.
//
NS_ASSUME_NONNULL_BEGIN
typedef void(^GMShareVideoAlertViewBlcok)();
@interface GMShareVideoAlertView : UIView
// 出现提示窗
+ (instancetype)showNoticeAtertViewWithConfirmBtnClickBlock:(GMShareVideoAlertViewBlcok)confirmBtnClickBlock;
@end
NS_ASSUME_NONNULL_END
//
// GMShareVideoAlertView.m
// Gengmei
//
// Created by 朱璇 on 2019/8/3.
// Copyright © 2019 更美互动信息科技有限公司. All rights reserved.
//
#import "GMShareVideoAlertView.h"
#import <Masonry/Masonry.h>
@interface GMShareVideoAlertView()
@property (nonatomic, copy) GMShareVideoAlertViewBlcok block;
@end
@implementation GMShareVideoAlertView
- (instancetype)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
self.backgroundColor = [UIColor colorWithWhite:0 alpha:.6];
[self setupSubviews];
}
return self;
}
- (void)setupSubviews {
UIImageView *background = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"share_wechatline_video_bg"]];
background.contentMode = UIViewContentModeScaleAspectFill;
[self addSubview:background];
CGFloat statusBarHeight = [[UIApplication sharedApplication] statusBarFrame].size.height;
[background mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.mas_equalTo(45);
make.right.mas_equalTo(-45);
make.top.mas_equalTo(93 + statusBarHeight);
make.bottom.mas_equalTo(-81 - self.safeAreaInsetsBottom);
}];
UIButton *confirmBtn = [UIButton buttonWithType:UIButtonTypeCustom];
[confirmBtn setBackgroundImage:[UIImage imageNamed:@"share_wechatline_video"] forState:UIControlStateNormal];
[confirmBtn setBackgroundImage:[UIImage imageNamed:@"share_wechatline_video"] forState:UIControlStateNormal | UIControlStateHighlighted];
[confirmBtn addTarget:self action:@selector(didClickConfirm) forControlEvents:UIControlEventTouchUpInside];
[self addSubview:confirmBtn];
[confirmBtn mas_makeConstraints:^(MASConstraintMaker *make) {
make.size.mas_equalTo(CGSizeMake(81, 35));
make.centerX.mas_equalTo(self.mas_centerX);
make.bottom.mas_equalTo(self.mas_bottom).offset(-50 - self.safeAreaInsetsBottom);
}];
}
+ (instancetype)showNoticeAtertViewWithConfirmBtnClickBlock:(GMShareVideoAlertViewBlcok)confirmBtnClickBlock {
UIView *backView = (UIView *)[[UIApplication sharedApplication] delegate];
GMShareVideoAlertView *alertView = [[GMShareVideoAlertView alloc] initWithFrame:backView.window.bounds];
alertView.block = confirmBtnClickBlock;
[backView.window addSubview:alertView];
return alertView;
}
- (void)didClickConfirm {
[self removeFromSuperview];
if (self.block) {
self.block();
}
}
- (CGFloat)safeAreaInsetsBottom {
if (@available(iOS 11.0, *)) {
return [UIApplication sharedApplication].keyWindow.safeAreaInsets.bottom;
} else {
return 0;
}
}
@end
//
// WMShareView.h
// Gengmei
//
// Created by Sean Lee on 1/22/15.
// Copyright (c) 2015 Wanmeichuangyi. All rights reserved.
//
#import <UIKit/UIKit.h>
#import "GMShareCell.h"
#import <GMThirdPartyDefine.h>
#import "GMShareTopView.h"
#import "WMShareObject.h"
@class GMShareView;
@class GMCollectionView;
@protocol GMShareViewDelegate <NSObject>
@required
/**
* @brief 在shareView add到super view前,会调用该deleage,在这个delegate里配置shareView,比如hasFav、showDelete等属性配置
*
*/
- (void)willShowShareView:(GMShareView *)shareView;
@optional
/** @brief 定制分享定制内容*/
- (NSMutableDictionary *)fetchSharePublishContent:(GMSharePlatform)shareType;
/** @brief 删除*/
- (void)deleteContent;
/** @brief 收藏*/
- (void)shareView:(GMShareView *)shareView favriteContentWithIndexPath:(NSIndexPath *)indexPath;
/** @brief 刷新H5*/
- (void)refreshWebView;
/** @brief 举报*/
- (void)showReport;
/*** @brief 复制链接*/
- (void)copyShareUrl;
/*** @brief 取消关注圈子*/
- (void)unFollowZone;
/*** @brief 邀请回复(问题详情和回复详情界面)*/
- (void)inviteAnswer;
// 如果需要动态变化businessId,就实现这个方法
- (NSString *)shareViewNeedBusinessId;
/** 分享成功 */
- (void)shareResponseSuccess;
/** 分享视频 */
- (void)shareVideo:(GMSharePlatform)shareType;
/** 取消分享 */
- (void)cancleShareView;
@end
/**
* @brief 弹出通用分享、收藏、等工具视图。可以包含的功能还有圈子的关注、取消关注、举报,刷新等。具体见属性注释。
@note
使用 shareView 初始化,但返回的并不是一个单例
*/
@interface GMShareView : GMView
@property (nonatomic, weak) id<GMShareViewDelegate> delegate;
/** @brief collectionView*/
@property (nonatomic,strong) GMCollectionView * shareCollectionView;
/** @brief 整个中间的背景View */
@property (nonatomic, strong) GMView *bgView;
/** @brief 是否收藏过 */
@property (assign, nonatomic) BOOL hasFav;
/** @brief 是否显示微信快照和朋友圈快照。默认NO*/
@property (assign, nonatomic) BOOL showScreenshot;
/** @brief 是否显示收藏项目。默认NO*/
@property (assign, nonatomic) BOOL showFavor;
/** @brief 是否显示删除帖子。 默认NO*/
@property (assign, nonatomic) BOOL showDelete;
/** @brief 是否显示举报帖子。 默认NO*/
@property (assign, nonatomic) BOOL showReport;
/** @brief 是否显示取消关注(用于圈子的取消关注)默认NO*/
@property (assign, nonatomic) BOOL showCancelFollow;
/** @brief 是否显示拷贝H5链接 默认YES*/
@property (nonatomic, assign) BOOL showCopyLink;
/** @brief 是否显示用于H5的刷新 默认YES*/
@property (nonatomic, assign) BOOL showRefresh;
/** @brief 是否显示邀请回复 默认NO*/
@property (nonatomic, assign) BOOL showInviteAnswer;
/** @brief 分享文案标题*/
@property (nonatomic, strong) NSString *shareTitle;
/** 分享url, 用于直接点击分享(没弹窗形式) */
@property (nonatomic, copy) NSString *shareUrl;
@property (nonatomic, strong) GMView *topLineView;
/**
连续签到,补签任务
*/
@property (nonatomic, strong) GMShareTopView *topView;
/**
分享:share 分享邀请好友注册:sharefor_register
*/
@property (nonatomic, copy) NSString *taskType;
/**
是否上报分享结果
*/
@property (nonatomic, assign) BOOL isReportShareResult;
/** @brief 显示分享界面*/
- (void)showWithShareUrl:(NSString *)shareUrl;
/** @brief 隐藏分享界面。最后会remove super view*/
- (void)hideShareView;
/**
* @author wangyang, 16-01-22 19:01:00
*
* @brief 调用该方法直接分享到指定平台,但仍会走 fetchSharePublishContent 这个代理方法以取得待分享的内容
* @param type 将要分享到的平台
* @since 5.9.0
*/
- (void)shareWithType:(GMSharePlatform)type;
@property (nonatomic, strong) WMShareObject *shareObject;
@end
//
// WMShareView.m
// Gengmei
//
// Created by Sean Lee on 1/22/15.
// Copyright (c) 2015 Wanmeichuangyi. All rights reserved.
//
#import "GMShareView.h"
#import <MBProgressHUD/MBProgressHUD.h>
#import <TencentOpenAPI/QQApiInterface.h>
#import "GMShareFlowLayout.h"
#import "DouyinOpenSDKApi.h"
#import <WechatOpenSDK/WXApi.h>
#import <WeiboSDK/WeiboSDK.h>
#import "GMShareVideoAlertView.h"
#import <GMShareSDK/GMShareSDK.h>
#import <GMKit/GMButton.h>
#import <GMKit/GMLabel.h>
#import <GMPhobos/Phobos.h>
#import <GMPhobos/UIResponder+PhobosPV.h>
#import <GMKit/UIColor+GMTheme.h>
#import <GMKit/Constant.h>
#import <GMKit/GMFont.h>
#import <GMKit/Constant.h>
#import <GMKit/UIView+LineWithAutolayout.h>
#import <GMKit/GMCollectionView.h>
#import <GMFoundation/NSString+GM.h>
#import <GMKit/UIView+SafeArea.h>
#import <GMKit/GMSafeValue.h>
#import <GMKit/UIView+Layout.h>
//#import <GMShareSDK/GMShareSDK-umbrella.h>
//@import GMPhobos;
static NSString *shareCellIdentifier = @"shareCellIdentifier";
@interface GMShareView ()<UICollectionViewDelegate, UICollectionViewDataSource, UIGestureRecognizerDelegate>
@property (nonatomic, strong) MBProgressHUD *hud;
@property (nonatomic, strong) NSMutableArray *titleArray;
@property (nonatomic, strong) NSMutableArray *imageArray;
@property (nonatomic, strong) NSMutableArray *shareTypeArray;
@property (nonatomic, strong) GMButton *closeButton;
@property (nonatomic, strong) GMView *bottomView;
@end
@implementation GMShareView
- (void)setup{
[super setup];
_titleArray = [@[@"微信快照",
@"朋友圈快照",
@"抖音",
@"微信好友",
@"朋友圈",
@"QQ",
@"新浪微博",
@"QQ空间",
@"邀请回复",
@"收藏",
@"删除",
@"复制链接",
@"举报",
@"刷新",
@"取消关注"] mutableCopy];
_imageArray = [@[@"share_wechat_session_snapshot",
@"share_wechat_timeline_snapshot",
@"share_douyin",
@"share_weixin_session",
@"share_weixin_timeline",
@"share_qq",
@"share_sina_weibo",
@"share_qq_space",
@"share_invite_answer",
@"share_fav_nor",
@"share_delete",
@"copy_link",
@"report",
@"refresh_web",
@"cancel_follow"] mutableCopy];
_shareTypeArray = [@[@(GMSharePlatformWechatSessionSnapShot),
@(GMSharePlatformWechatTimelineSnapShot),
@(GMSharePlatformDouyin),
@(GMSharePlatformWechatSession),
@(GMSharePlatformWechatTimeline),
@(GMSharePlatformQQFriend),
@(GMSharePlatformSinaWeibo),
@(GMSharePlatformQQSpace),
@(GMSharePlatformInviteAnswer),
@(GMSharePlatformFav),
@(GMSharePlatformDelete),
@(GMSharePlatformCopyLink),
@(GMSharePlatformReport),
@(GMSharePlatformRefresh),
@(GMSharePlatformCancelFollow)] mutableCopy];
self.showCopyLink = YES;
self.showRefresh = YES;
_bgView = [[GMView alloc]init];
[self addSubview:_bgView];
_closeButton = [GMButton buttonWithType:UIButtonTypeCustom];
_closeButton.backgroundColor = [UIColor whiteColor];
[_closeButton setTitleColor:UIColor.headlineText forState:UIControlStateNormal];
[_closeButton setTitle:@"取消" forState: UIControlStateNormal];
_closeButton.titleLabel.font = [UIFont gmFont:16];
[_closeButton addTarget:self action:@selector(closeButtonClicked:) forControlEvents:UIControlEventTouchUpInside];
[_bgView addSubview:_closeButton];
[_closeButton addTopLine];
_shareCollectionView = [[GMCollectionView alloc] initWithFrame:CGRectZero collectionViewLayout:[GMShareFlowLayout new]];
[_shareCollectionView setBackgroundColor:UIColor.whiteColor];
[_shareCollectionView setDataSource:self];
[_shareCollectionView setDelegate:self];
[_shareCollectionView setScrollEnabled:NO];
_shareCollectionView.delegate= self;
_shareCollectionView.dataSource = self;
_shareCollectionView.backgroundColor = [UIColor whiteColor];
[_shareCollectionView registerClass:[GMShareCell class] forCellWithReuseIdentifier:shareCellIdentifier];
[_bgView addSubview:_shareCollectionView];
_topLineView = [GMView new];
_topLineView.backgroundColor = UIColor.separatorLine;
[_bgView addSubview:_topLineView];
UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(hideShareView)];
tap.delegate = self;
[self addGestureRecognizer:tap];
_topView = [[GMShareTopView alloc] init];
[_bgView addSubview:_topView];
_topView.hidden = YES;
_bottomView = [[GMView alloc] init];
_bottomView.backgroundColor = [UIColor whiteColor];
[_bgView addSubview:_bottomView];
}
- (void)updateTitleArray {
if (!_showDelete) {
[_titleArray removeObject:@"删除"];
[_imageArray removeObject:@"share_delete"];
[_shareTypeArray removeObject:@(GMSharePlatformDelete)];
}
if (!_showFavor) {
[_titleArray removeObject:@"收藏"];
[_imageArray removeObject:@"share_fav_nor"];
[_shareTypeArray removeObject:@(GMSharePlatformFav)];
}
if (!_showReport) {
[_titleArray removeObject:@"举报"];
[_imageArray removeObject:@"report"];
[_shareTypeArray removeObject:@(GMSharePlatformReport)];
}
if (!_showCancelFollow) {
[_titleArray removeObject:@"取消关注"];
[_imageArray removeObject:@"quxiaoguanzhu"];
[_shareTypeArray removeObject:@(GMSharePlatformCancelFollow)];
}
if (!_showCopyLink) {
[_titleArray removeObject:@"复制链接"];
[_imageArray removeObject:@"copy_link"];
[_shareTypeArray removeObject:@(GMSharePlatformCopyLink)];
}
if (!_showRefresh) {
[_titleArray removeObject:@"刷新"];
[_imageArray removeObject:@"refresh_web"];
[_shareTypeArray removeObject:@(GMSharePlatformRefresh)];
}
// BOOL isWXAppInstalled = [GMShareSDK isClientInstalled:GMSharePlatformWechatSessionSnapShot] || [GMShareSDK isClientInstalled:GMSharePlatformWechatTimelineSnapShot] || [GMShareSDK isClientInstalled:GMSharePlatformWechat]
if (!_showScreenshot || (![WXApi isWXAppInstalled])) {
[_titleArray removeObject:@"微信快照"];
[_imageArray removeObject:@"share_wechat_session_snapshot"];
[_shareTypeArray removeObject:@(GMSharePlatformWechatSessionSnapShot)];
[_titleArray removeObject:@"朋友圈快照"];
[_imageArray removeObject:@"share_wechat_timeline_snapshot"];
[_shareTypeArray removeObject:@(GMSharePlatformWechatTimelineSnapShot)];
}
if (!_showInviteAnswer) {
[_titleArray removeObject:@"邀请回复"];
[_imageArray removeObject:@"share_invite_answer"];
[_shareTypeArray removeObject:@(GMSharePlatformInviteAnswer)];
}
if ((!_shareObject.qq.title.isNonEmpty || ((!_shareObject.qq.content.isNonEmpty) && _shareObject != nil)) || (![QQApiInterface isQQInstalled])) {
[_titleArray removeObject:@"QQ"];
[_imageArray removeObject:@"share_qq"];
[_shareTypeArray removeObject:@(GMSharePlatformQQFriend)];
}
if (!_shareObject.qqzone.title.isNonEmpty || ((!_shareObject.qqzone.content.isNonEmpty) && _shareObject != nil) || ![QQApiInterface isQQInstalled]) {
[_titleArray removeObject:@"QQ空间"];
[_imageArray removeObject:@"share_qq_space"];
[_shareTypeArray removeObject:@(GMSharePlatformQQSpace)];
}
if ((_shareObject.douyin == nil && _shareObject != nil) || ![DouyinOpenSDKApi isAppInstalled]) {
[_titleArray removeObject:@"抖音"];
[_imageArray removeObject:@"share_douyin"];
[_shareTypeArray removeObject:@(GMSharePlatformDouyin)];
}
if ((_shareObject.wechatline == nil && _shareObject != nil) || (![WXApi isWXAppInstalled])) {
[_titleArray removeObject:@"朋友圈"];
[_imageArray removeObject:@"share_weixin_timeline"];
[_shareTypeArray removeObject:@(GMSharePlatformWechatTimeline)];
}
if ((_shareObject.wechat == nil && _shareObject.wechatmini == nil && _shareObject != nil) || (![WXApi isWXAppInstalled])) {
[_titleArray removeObject:@"微信好友"];
[_imageArray removeObject:@"share_weixin_session"];
[_shareTypeArray removeObject:@(GMSharePlatformWechatSession)];
}
if ((_shareObject.weibo == nil && _shareObject != nil) || ![WeiboSDK isWeiboAppInstalled]) {
[_titleArray removeObject:@"新浪微博"];
[_imageArray removeObject:@"share_sina_weibo"];
[_shareTypeArray removeObject:@(GMSharePlatformSinaWeibo)];
}
[_topLineView mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.mas_equalTo(20);
make.right.mas_equalTo(-20);
if (_titleArray.count / 5.0 >= 1) {
make.height.mas_equalTo(ONE_PIXEL);
} else {
make.height.mas_equalTo(0);
}
make.top.mas_equalTo(_shareCollectionView).offset(217/2.0);
}];
}
- (void)updateConstraints{
[_bgView mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.right.mas_equalTo(0);
make.bottom.mas_equalTo(0);
}];
[_shareCollectionView mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.right.mas_equalTo(0);
make.height.mas_equalTo(435/2.0);
if (_topView.hidden) {
make.top.mas_equalTo(0);
}
}];
[_closeButton mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.equalTo(_shareCollectionView.mas_bottom);
make.left.right.mas_equalTo(0);
make.height.mas_equalTo(45);
make.bottom.mas_equalTo(-UIView.safeAreaInsetsBottom);
}];
[_bottomView mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.right.bottom.mas_equalTo(0);
make.height.mas_equalTo(UIView.safeAreaInsetsBottom);
}];
if (!_topView.hidden) {
[_topView mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.right.mas_equalTo(0);
make.bottom.mas_equalTo(_shareCollectionView.mas_top).offset(11);
make.top.mas_equalTo(0);
}];
[_topView updateSubtitleConstraintsWithBottom:-11];
}
[super updateConstraints];
}
#pragma mark - Collection View
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section {
[self updateTitleArray];
return _titleArray.count;
}
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath{
GMShareCell * cell = [collectionView dequeueReusableCellWithReuseIdentifier:shareCellIdentifier forIndexPath:indexPath];
cell.transferResponseChainBlock = ^{
[self collectionView:collectionView didSelectItemAtIndexPath:indexPath];
};
[cell.shareButton setTitle:_titleArray[indexPath.row] forState:UIControlStateNormal];
[cell.shareButton setImage:[UIImage imageNamed:_imageArray[indexPath.row]] forState:UIControlStateNormal];
cell.shareType = [_shareTypeArray[indexPath.row] integerValue];
if (cell.shareType == GMSharePlatformFav){
[cell.shareButton setImage:[UIImage imageNamed:@"share_fav_pre"] forState:UIControlStateSelected];
cell.shareButton.selected = _hasFav;
}
return cell;
}
- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath{
[self hideShareView];
GMShareCell * cell = (GMShareCell *) [collectionView cellForItemAtIndexPath:indexPath];
if (cell.shareType == GMSharePlatformFav) {
if ([self.delegate respondsToSelector:@selector(shareView:favriteContentWithIndexPath:)]) {
[self.delegate shareView:self favriteContentWithIndexPath:indexPath];
}
}else if (cell.shareType == GMSharePlatformDelete) {
//删除前提示用户是否需要删除
if ([self.delegate respondsToSelector:@selector(deleteContent)]) {
[self.delegate deleteContent];
}
}else if (cell.shareType == GMSharePlatformReport) {
if ([self.delegate respondsToSelector:@selector(showReport)]) {
[self.delegate showReport];
}
}else if (cell.shareType == GMSharePlatformRefresh) {
if ([self.delegate respondsToSelector:@selector(refreshWebView)]) {
[self.delegate refreshWebView];
}
}else if (cell.shareType == GMSharePlatformCopyLink) {
if ([self.delegate respondsToSelector:@selector(copyShareUrl)]) {
[self.delegate copyShareUrl];
}
[self phobosClickShareWithType:cell.shareType];
}else if (cell.shareType == GMSharePlatformCancelFollow) {
if ([self.delegate respondsToSelector:@selector(unFollowZone)]) {
[self.delegate unFollowZone];
}
}else if (cell.shareType == GMSharePlatformInviteAnswer) {
if ([self.delegate respondsToSelector:@selector(inviteAnswer)]) {
[self.delegate inviteAnswer];
}
}else {
if ([self.delegate respondsToSelector:@selector(shareVideo:)]) {
[self.delegate shareVideo:cell.shareType];
} else {
[self shareWithType:cell.shareType];
}
}
}
- (void)layoutSubviews{
[super layoutSubviews];
[_shareCollectionView layoutIfNeeded];
[_shareCollectionView mas_updateConstraints:^(MASConstraintMaker *make) {
make.height.mas_equalTo(_shareCollectionView.contentSize.height);
}];
}
#pragma mark - 公开方法
- (void)shareWithType:(GMSharePlatform)type {
if (![GMShareSDK isClientInstalled:type] && (type == GMSharePlatformWechatTimeline | type == GMSharePlatformWechatSession | type == GMSharePlatformWechatSessionSnapShot | type == GMSharePlatformWechatTimelineSnapShot)) {
//QQ和新浪支持web分享,只有微信不支持,这里优化下提示语
[self toast:@"您未安装微信客户端"];
return;
}
// 抖音
if (type == GMSharePlatformDouyin) {
if (![GMShareSDK isClientInstalled:type]) {
//判断是否安装抖音
[self toast:@"您未安装抖音"];
return;
}
if (![DouyinOpenSDKApi isAppSupportShare]) {
//判断抖音是不是支持分享
[self toast:@"您安装的抖音版本不支持分享"];
return;
}
}
// 当是分享视频,并且是分享到朋友圈时,需要弹起弹窗
if (_shareObject.videoUrl.isNonEmpty && type == GMSharePlatformWechatTimeline) {
[self phobosClickShareWithType:type]; // 添加埋点数据
[GMShareVideoAlertView showNoticeAtertViewWithConfirmBtnClickBlock:^{
// 调起微信
[WXApi openWXApp];
}];
return;
}
[self goToShareWithType:type];
}
- (void)goToShareWithType:(GMSharePlatform)type {
//获取定制的分享内容
NSMutableDictionary *shareParams = [NSMutableDictionary dictionary];
if ([self.delegate respondsToSelector:@selector(fetchSharePublishContent:)]) {
shareParams = [self.delegate fetchSharePublishContent:type];
}
[self phobosClickShareWithType:type];
[[GMShareSDK shareInstance] share: type parameters:[shareParams mutableCopy] onStateChanged:^(GMShareResponseState state, NSError *error) {
switch (state) {
case GMShareResponseStateBegin:{
// debugLog(@"启动分享");
break;
}
case GMShareResponseStateSuccess:{
// debugLog(@"分享成功");
if (self) {
if (self.delegate != nil && ![self.delegate isKindOfClass:[NSNull class]]) {
if ([self.delegate respondsToSelector:@selector(shareResponseSuccess)]) {
[self.delegate shareResponseSuccess];
}
}
}
if ([self.delegate isKindOfClass:[UIResponder class]] && self.isReportShareResult) {
UIResponder *phobosView = (UIResponder *)self.delegate;
NSDictionary *dic = @{@"page_name": SafeValue(phobosView.pageName),
@"share_channel": [self channel:type]};
[Phobos track:@"share_success_statistics" attributes:dic];
}
break;
}
case GMShareResponseStateFail:{
// debugLog(@"分享失败,错误描述:%@", error);
break;
}
case GMShareResponseStateCancel:{
// debugLog(@"取消分享");
break;
}
default:
break;
}
}];
}
- (NSString *)channel:(GMSharePlatform)type {
NSString *channel = @"";
switch (type) {
case GMSharePlatformWechatSession:
channel = @"wechat";
break;
case GMSharePlatformWechatTimeline:
channel = @"wechatline";
break;
case GMSharePlatformQQFriend:
channel = @"qq";
break;
case GMSharePlatformQQSpace:
channel = @"qzone";
break;
case GMSharePlatformSinaWeibo:
channel = @"tsina";
break;
case GMSharePlatformWechatSessionSnapShot:
channel = @"wechat_snapshot";
break;
case GMSharePlatformWechatTimelineSnapShot:
channel = @"wechatline_snapshot";
break;
case GMSharePlatformCopyLink:
channel = @"copy_url";
break;
case GMSharePlatformDouyin:
channel = @"douyin";
break;
default:
channel = @"";
break;
}
return channel;
}
- (void)dealloc {
}
/**
* @brief 分享界面的有关动画
添加上滑动画
usingSpringWithDamping的范围为0.0f到1.0f,数值越小「弹簧」的振动效果越明显。
initialSpringVelocity则表示初始的速度,数值越大一开始移动越快。
http://www.renfei.org/blog/ios-8-spring-animation.html
*
*/
- (void)showWithShareUrl:(NSString *)shareUrl {
self.shareUrl = shareUrl;
if ([self.delegate respondsToSelector:@selector(willShowShareView:)]) {
[self.delegate willShowShareView:self];
}
[self phobosClickShare];
self.frame = MAINSCREEN_FRAME;
_bgView.top = MAINSCREEN_HEIGHT; //storyBoard中在屏幕中央,重置在屏幕最底下
// [[UIApplication sharedApplication].keyWindow addSubview:self]; // 用这个方法偶尔会出现bug,用下面方法替换
[[UIApplication sharedApplication].keyWindow addSubview:self];
self.backgroundColor = UIColor.clearColor;
[UIView animateWithDuration:0.25 animations:^{
self.backgroundColor = [UIColor colorWithRed:0 green:0 blue:0 alpha:0.6];
}];
[UIView animateWithDuration:0.25 animations:^{
_bgView.top = _bgView.height;
}];
}
- (void)hideShareView{
[UIView animateWithDuration:0.25 animations:^{
_bgView.top = MAINSCREEN_HEIGHT;
self.backgroundColor = [UIColor colorWithRed:0 green:0 blue:0 alpha:0];
} completion:^(BOOL finished) {
[self removeFromSuperview];
}];
}
#pragma mark - 埋点
// 弹出分享框埋点
- (void)phobosClickShare {
if ([self.delegate isKindOfClass:[UIResponder class]]) {
NSAssert([self.shareUrl isNonEmpty], @"shareView.shareUrl 不应该为空");
UIResponder *phobosView = (UIResponder *)self.delegate;
NSString *businessId = SafeValue(phobosView.businessId);
if ([self.delegate respondsToSelector:@selector(shareViewNeedBusinessId)]) {
businessId = [self.delegate shareViewNeedBusinessId];
}
NSDictionary *dic = @{@"page_name": SafeValue(phobosView.pageName),
@"business_id": SafeValue(businessId),
@"url": SafeValue(self.shareUrl)};
[Phobos track:@"page_click_share" attributes:dic];
}
}
// page_click_share_channel 埋点
- (void)phobosClickShareWithType:(GMSharePlatform)type {
if ([self.delegate isKindOfClass:[UIResponder class]]) {
UIResponder *phobosView = (UIResponder *)self.delegate;
NSString *businessId = SafeValue(phobosView.businessId);
if ([self.delegate respondsToSelector:@selector(shareViewNeedBusinessId)]) {
businessId = [self.delegate shareViewNeedBusinessId];
}
NSAssert([self.shareUrl isNonEmpty], @"shareView.shareUrl 不应该为空");
NSDictionary *dic = @{@"page_name": SafeValue(phobosView.pageName),
@"business_id": SafeValue(businessId),
@"url": SafeValue(self.shareUrl),
@"share_channel": [self channel:type],
@"task_type":SafeValue(self.taskType),
@"business":@"channel"};
[Phobos track:@"page_click_share_channel" attributes:dic];
}
}
#pragma mark - Toast
- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer{
if ([gestureRecognizer isKindOfClass:[UITapGestureRecognizer class]]){
CGPoint point = [gestureRecognizer locationInView:self];
if (CGRectContainsPoint(_bgView.frame, point)) {
return NO;
}else{
return YES;
}
}
return YES;
}
/** * @brief 分享结果提示*/
- (void)toast:(NSString *)text{
[_hud hide:YES];
MBProgressHUD *hudProgrss = [MBProgressHUD showHUDAddedTo:[UIApplication sharedApplication].keyWindow animated:YES];
hudProgrss.mode = MBProgressHUDModeText;
hudProgrss.userInteractionEnabled = NO;
hudProgrss.labelText = text;
hudProgrss.removeFromSuperViewOnHide = YES;
[hudProgrss hide:YES afterDelay:1.0];
}
/** * @brief 分享启动提示*/
- (void)showLoading:(NSString *)text
{
_hud = [MBProgressHUD showHUDAddedTo:[UIApplication sharedApplication].keyWindow animated:YES];
_hud.mode = MBProgressHUDModeIndeterminate;
_hud.userInteractionEnabled = YES;
_hud.labelText = text;
_hud.margin = 10.f;
_hud.removeFromSuperViewOnHide = YES;
[_hud show:YES];
}
- (void)hideLoading{
[_hud hide:YES];
}
#pragma mark - Action
- (IBAction)closeButtonClicked:(id)button {
if ([self.delegate respondsToSelector:@selector(cancleShareView)]) {
[self.delegate cancleShareView];
}
[self hideShareView];
}
@end
//
// WMShareObject.h
// Gengmei
//
// Created by Sean Lee on 7/22/15.
// Copyright (c) 2015 Wanmeichuangyi. All rights reserved.
//
#import "GMShareBaseObject.h"
//#import <GMBase/GMObject.h>
@protocol WMShareBasicObject @end
@interface WMShareBasicObject : GMShareBaseObject
@property (nonatomic,strong) NSString * title;
@property (nonatomic,strong) NSString * content;
@end
@protocol GMWechatminiObject @end
@interface GMWechatminiObject : GMShareBaseObject
@property (nonatomic,strong) NSString * desc;
@property (nonatomic,strong) NSString * title;
@property (nonatomic,strong) NSString * hdImageUrl;
@property (nonatomic,strong) NSString * path;
@property (nonatomic,strong) NSString * userName;
@property (nonatomic,strong) NSString * thumbImageUrl;
@end
@protocol WMShareObject @end
@interface WMShareObject : GMShareBaseObject
@property (nonatomic,strong) NSString * image;
@property (nonatomic,strong) NSString * url;
@property (nonatomic,strong) NSString * from;
@property (nonatomic,strong) NSString * showPrice;
@property (nonatomic,strong) NSString * organizationName;
@property (nonatomic,strong) WMShareBasicObject <WMShareBasicObject>* wechat;
@property (nonatomic,strong) WMShareBasicObject <WMShareBasicObject>* wechatline;
@property (nonatomic,strong) WMShareBasicObject <WMShareBasicObject>* wechatScreenshot;
@property (nonatomic,strong) WMShareBasicObject <WMShareBasicObject>* wechatlineScreenshot;
@property (nonatomic,strong) WMShareBasicObject <WMShareBasicObject>* qq;
@property (nonatomic,strong) WMShareBasicObject <WMShareBasicObject>* qqzone;
@property (nonatomic,strong) WMShareBasicObject <WMShareBasicObject>* weibo;
@property (nonatomic,strong) GMWechatminiObject <GMWechatminiObject>* wechatmini;
@property (nonatomic,strong) WMShareBasicObject <WMShareBasicObject>* douyin; // 分享到抖音 扫脸1.5
@property (nonatomic, copy) NSString *page_name;// 埋点使用
@property (nonatomic, copy) NSString *business_id;// 埋点使用
@property (nonatomic, copy) NSString *is_like;//埋点使用
@property (nonatomic, copy) NSString *videoUrl;//视频URL(分享视频时)扫脸1.5
@end
@protocol GMDiaryShareObject @end
@interface GMDiaryShareObject : GMShareBaseObject
@property (nonatomic,strong) NSString *id;
@property (nonatomic,strong) NSString *title;
@property (nonatomic,strong) WMShareObject *shareData;
@end
@protocol GMShareConfigObject @end
@interface GMShareConfigObject : GMShareBaseObject
@property (nonatomic,strong) NSString *hideCopyLink;
@property (nonatomic,strong) NSString *hideRefresh;
@property (nonatomic,strong) NSString *hideReport;
@property (nonatomic,strong) NSString *shareCallback;
@property (nonatomic,strong) NSString *shareTip;
@property (nonatomic,strong) NSString *shareTitle;
@property (nonatomic,strong) NSString *shareInfo;
@property (nonatomic, copy) NSString *taskType;
@property (nonatomic, copy) NSString *isReportShareResult;
@end
//
// WMShareObject.m
// Gengmei
//
// Created by Sean Lee on 7/22/15.
// Copyright (c) 2015 Wanmeichuangyi. All rights reserved.
//
#import "WMShareObject.h"
@implementation WMShareBasicObject
@end
@implementation GMWechatminiObject
@end
@implementation WMShareObject
@end
@implementation GMDiaryShareObject
@end
@implementation GMShareConfigObject
@end
......@@ -431,11 +431,11 @@
- (void)wechatMiniProgramWithParameters: (NSDictionary *)parameters scene:(int)scene {
#ifndef APPSTORE
// if ([GMServerDomains.apiHost isEqualToString:@"http://ibackend.igengmei.com"] || [GMServerDomains.apiHost isEqualToString:@"http://backend.igengmei.com"] || [GMServerDomains.apiHost isEqualToString:@""]) {
[UIAlertController gm_Alert:@"当前环境,APP和小程序未绑定在同一微信开发平台账号,无法分享成功" leftTitle:@"OK" rightTitle:@"OK" leftAlterAction:^{
} rightAlertAction:^{
}];
// [UIAlertController gm_Alert:@"当前环境,APP和小程序未绑定在同一微信开发平台账号,无法分享成功" leftTitle:@"OK" rightTitle:@"OK" leftAlterAction:^{
// } rightAlertAction:^{
// }];
// [AppDelegate.visibleController presentViewController:alert animated:YES completion:nil];
return;
// return;
// }
#endif
dispatch_async(dispatch_get_global_queue(0, 0), ^{
......@@ -459,12 +459,10 @@
req.message = message;
req.scene = scene;
// 转回到主线程
dispatch_sync(dispatch_get_main_queue(), ^{
[WXApi sendReq:req completion:^(BOOL success) {
}];
});
});
}
- (void)wechatImageShareWithParameters: (NSDictionary *)parameters scene:(int)scene{
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment