Commit d76697b6 authored by jz's avatar jz

add view & model

parent 1c2272a7
...@@ -326,9 +326,13 @@ ...@@ -326,9 +326,13 @@
"${PODS_ROOT}/Target Support Files/Pods-GMAI_Example/Pods-GMAI_Example-frameworks.sh", "${PODS_ROOT}/Target Support Files/Pods-GMAI_Example/Pods-GMAI_Example-frameworks.sh",
"${BUILT_PRODUCTS_DIR}/AFNetworking/AFNetworking.framework", "${BUILT_PRODUCTS_DIR}/AFNetworking/AFNetworking.framework",
"${BUILT_PRODUCTS_DIR}/Alamofire/Alamofire.framework", "${BUILT_PRODUCTS_DIR}/Alamofire/Alamofire.framework",
"${BUILT_PRODUCTS_DIR}/EVReflection/EVReflection.framework",
"${BUILT_PRODUCTS_DIR}/GM-Swift-Observable/GM_Swift_Observable.framework",
"${BUILT_PRODUCTS_DIR}/GMAI/GMAI.framework", "${BUILT_PRODUCTS_DIR}/GMAI/GMAI.framework",
"${BUILT_PRODUCTS_DIR}/GMBase/GMBase.framework", "${BUILT_PRODUCTS_DIR}/GMBase/GMBase.framework",
"${BUILT_PRODUCTS_DIR}/GMBaseSwift/GMBaseSwift.framework",
"${BUILT_PRODUCTS_DIR}/GMCache/GMCache.framework", "${BUILT_PRODUCTS_DIR}/GMCache/GMCache.framework",
"${BUILT_PRODUCTS_DIR}/GMFoundation/GMFoundation.framework",
"${BUILT_PRODUCTS_DIR}/GMHud/GMHud.framework", "${BUILT_PRODUCTS_DIR}/GMHud/GMHud.framework",
"${BUILT_PRODUCTS_DIR}/GMJSONModel/GMJSONModel.framework", "${BUILT_PRODUCTS_DIR}/GMJSONModel/GMJSONModel.framework",
"${BUILT_PRODUCTS_DIR}/GMKit/GMKit.framework", "${BUILT_PRODUCTS_DIR}/GMKit/GMKit.framework",
...@@ -336,9 +340,12 @@ ...@@ -336,9 +340,12 @@
"${BUILT_PRODUCTS_DIR}/GMNetworking/GMNetworking.framework", "${BUILT_PRODUCTS_DIR}/GMNetworking/GMNetworking.framework",
"${BUILT_PRODUCTS_DIR}/GMPhobos/GMPhobos.framework", "${BUILT_PRODUCTS_DIR}/GMPhobos/GMPhobos.framework",
"${BUILT_PRODUCTS_DIR}/GMRefresh/GMRefresh.framework", "${BUILT_PRODUCTS_DIR}/GMRefresh/GMRefresh.framework",
"${BUILT_PRODUCTS_DIR}/GMRouter/GMRouter.framework",
"${BUILT_PRODUCTS_DIR}/MBProgressHUD/MBProgressHUD.framework", "${BUILT_PRODUCTS_DIR}/MBProgressHUD/MBProgressHUD.framework",
"${BUILT_PRODUCTS_DIR}/MJExtension/MJExtension.framework",
"${BUILT_PRODUCTS_DIR}/MJRefresh/MJRefresh.framework", "${BUILT_PRODUCTS_DIR}/MJRefresh/MJRefresh.framework",
"${BUILT_PRODUCTS_DIR}/Masonry/Masonry.framework", "${BUILT_PRODUCTS_DIR}/Masonry/Masonry.framework",
"${BUILT_PRODUCTS_DIR}/Qiniu/Qiniu.framework",
"${BUILT_PRODUCTS_DIR}/SDWebImage/SDWebImage.framework", "${BUILT_PRODUCTS_DIR}/SDWebImage/SDWebImage.framework",
"${BUILT_PRODUCTS_DIR}/SnapKit/SnapKit.framework", "${BUILT_PRODUCTS_DIR}/SnapKit/SnapKit.framework",
"${BUILT_PRODUCTS_DIR}/TMCache/TMCache.framework", "${BUILT_PRODUCTS_DIR}/TMCache/TMCache.framework",
...@@ -348,9 +355,13 @@ ...@@ -348,9 +355,13 @@
outputPaths = ( outputPaths = (
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/AFNetworking.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/AFNetworking.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Alamofire.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Alamofire.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/EVReflection.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/GM_Swift_Observable.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/GMAI.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/GMAI.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/GMBase.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/GMBase.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/GMBaseSwift.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/GMCache.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/GMCache.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/GMFoundation.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/GMHud.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/GMHud.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/GMJSONModel.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/GMJSONModel.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/GMKit.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/GMKit.framework",
...@@ -358,9 +369,12 @@ ...@@ -358,9 +369,12 @@
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/GMNetworking.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/GMNetworking.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/GMPhobos.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/GMPhobos.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/GMRefresh.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/GMRefresh.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/GMRouter.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/MBProgressHUD.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/MBProgressHUD.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/MJExtension.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/MJRefresh.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/MJRefresh.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Masonry.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Masonry.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Qiniu.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SDWebImage.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SDWebImage.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SnapKit.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SnapKit.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/TMCache.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/TMCache.framework",
......
...@@ -7,10 +7,16 @@ source 'git@git.wanmeizhensuo.com:gengmeiios/GMSpecs.git' ...@@ -7,10 +7,16 @@ source 'git@git.wanmeizhensuo.com:gengmeiios/GMSpecs.git'
target 'GMAI_Example' do target 'GMAI_Example' do
pod 'GMAI', :path => '../' pod 'GMAI', :path => '../'
pod 'GMBaseSwift', '3.3.7'
# pod 'EVReflection', '5.10.0'
target 'GMAI_Tests' do target 'GMAI_Tests' do
inherit! :search_paths inherit! :search_paths
end end
end end
pre_install do |installer|
# workaround for https://github.com/CocoaPods/CocoaPods/issues/3289
Pod::Installer::Xcode::TargetValidator.send(:define_method, :verify_no_static_framework_transitive_dependencies) {}
end
...@@ -15,9 +15,20 @@ PODS: ...@@ -15,9 +15,20 @@ PODS:
- AFNetworking/UIKit (3.1.0): - AFNetworking/UIKit (3.1.0):
- AFNetworking/NSURLSession - AFNetworking/NSURLSession
- Alamofire (4.7.0) - Alamofire (4.7.0)
- EVReflection (5.10.0):
- EVReflection/Core (= 5.10.0)
- EVReflection/Core (5.10.0)
- GM-Swift-Observable (4.0.1)
- GMAI (0.1.0): - GMAI (0.1.0):
- EVReflection
- GMBase - GMBase
- GMBaseSwift
- GMCache
- GMFoundation
- GMNetworking - GMNetworking
- GMRouter
- Qiniu
- SDWebImage
- GMBase (1.1.5): - GMBase (1.1.5):
- GMHud - GMHud
- GMJSONModel - GMJSONModel
...@@ -28,8 +39,17 @@ PODS: ...@@ -28,8 +39,17 @@ PODS:
- MBProgressHUD - MBProgressHUD
- SDWebImage - SDWebImage
- "UITableView+FDTemplateLayoutCell (= 1.4)" - "UITableView+FDTemplateLayoutCell (= 1.4)"
- GMBaseSwift (3.3.7):
- EVReflection (= 5.10.0)
- GM-Swift-Observable
- GMHud
- GMNetworking
- GMPhobos
- GMRefresh
- SnapKit (= 4.0.0)
- GMCache (1.0.1): - GMCache (1.0.1):
- TMCache (= 2.1.0) - TMCache (= 2.1.0)
- GMFoundation (1.0.5)
- GMHud (1.0.3): - GMHud (1.0.3):
- MBProgressHUD (= 0.9.2) - MBProgressHUD (= 0.9.2)
- GMJSONModel (1.7.4) - GMJSONModel (1.7.4)
...@@ -84,23 +104,31 @@ PODS: ...@@ -84,23 +104,31 @@ PODS:
- GMRefresh (1.0.4): - GMRefresh (1.0.4):
- GMPhobos - GMPhobos
- MJRefresh - MJRefresh
- GMRouter (0.1.5):
- MJExtension
- Masonry (1.1.0) - Masonry (1.1.0)
- MBProgressHUD (0.9.2) - MBProgressHUD (0.9.2)
- MJExtension (3.2.1)
- MJRefresh (3.3.1) - MJRefresh (3.3.1)
- Qiniu (7.3.0)
- SDWebImage (5.4.2): - SDWebImage (5.4.2):
- SDWebImage/Core (= 5.4.2) - SDWebImage/Core (= 5.4.2)
- SDWebImage/Core (5.4.2) - SDWebImage/Core (5.4.2)
- SnapKit (4.2.0) - SnapKit (4.0.0)
- TMCache (2.1.0) - TMCache (2.1.0)
- "UITableView+FDTemplateLayoutCell (1.4)" - "UITableView+FDTemplateLayoutCell (1.4)"
DEPENDENCIES: DEPENDENCIES:
- GMAI (from `../`) - GMAI (from `../`)
- GMBaseSwift (= 3.3.7)
SPEC REPOS: SPEC REPOS:
"git@git.wanmeizhensuo.com:gengmeiios/GMSpecs.git": "git@git.wanmeizhensuo.com:gengmeiios/GMSpecs.git":
- GM-Swift-Observable
- GMBase - GMBase
- GMBaseSwift
- GMCache - GMCache
- GMFoundation
- GMHud - GMHud
- GMJSONModel - GMJSONModel
- GMKit - GMKit
...@@ -108,12 +136,16 @@ SPEC REPOS: ...@@ -108,12 +136,16 @@ SPEC REPOS:
- GMNetworking - GMNetworking
- GMPhobos - GMPhobos
- GMRefresh - GMRefresh
- GMRouter
https://github.com/cocoapods/specs.git: https://github.com/cocoapods/specs.git:
- AFNetworking - AFNetworking
- Alamofire - Alamofire
- EVReflection
- Masonry - Masonry
- MBProgressHUD - MBProgressHUD
- MJExtension
- MJRefresh - MJRefresh
- Qiniu
- SDWebImage - SDWebImage
- SnapKit - SnapKit
- TMCache - TMCache
...@@ -126,9 +158,13 @@ EXTERNAL SOURCES: ...@@ -126,9 +158,13 @@ EXTERNAL SOURCES:
SPEC CHECKSUMS: SPEC CHECKSUMS:
AFNetworking: 5e0e199f73d8626b11e79750991f5d173d1f8b67 AFNetworking: 5e0e199f73d8626b11e79750991f5d173d1f8b67
Alamofire: 907e0a98eb68cdb7f9d1f541a563d6ac5dc77b25 Alamofire: 907e0a98eb68cdb7f9d1f541a563d6ac5dc77b25
GMAI: d13c3aef135a5fa2075123173cb20f1eb650596d EVReflection: 1abc1a81927ab0d30170238cf9b79bff489e9728
GM-Swift-Observable: 756d8fc13638b9faa68cb10266b2ffb47a911595
GMAI: 6dbdb0af8d3239093438c761e84f6ed88b5a53ad
GMBase: 2b77f591a35a589ed331c490af093b68b0fc73be GMBase: 2b77f591a35a589ed331c490af093b68b0fc73be
GMBaseSwift: 168898c825ef16ffbf8bd261cb4b295cfb492bd2
GMCache: b78d8e46db864405e91d226ce640cc80d966c611 GMCache: b78d8e46db864405e91d226ce640cc80d966c611
GMFoundation: 3b621bde6b0661ae61393b55691974f570f46208
GMHud: 18d41f4900a204f27be14e9504fcee2060ae3b2c GMHud: 18d41f4900a204f27be14e9504fcee2060ae3b2c
GMJSONModel: 5e81a98de668e9f93cf6ff77869f77b0d1a806be GMJSONModel: 5e81a98de668e9f93cf6ff77869f77b0d1a806be
GMKit: 09fe863069d9750c89fae2939770b08fc74b9027 GMKit: 09fe863069d9750c89fae2939770b08fc74b9027
...@@ -136,14 +172,17 @@ SPEC CHECKSUMS: ...@@ -136,14 +172,17 @@ SPEC CHECKSUMS:
GMNetworking: 592b9b71f2a7d92203483276158ce3139ac789d2 GMNetworking: 592b9b71f2a7d92203483276158ce3139ac789d2
GMPhobos: 1e2d68c456b69bf156276d7242877498107474db GMPhobos: 1e2d68c456b69bf156276d7242877498107474db
GMRefresh: c01ff8de5ada92e1362602fb6991f99124b7dbe3 GMRefresh: c01ff8de5ada92e1362602fb6991f99124b7dbe3
GMRouter: 808430d1275e92809eb1180ed3cb89525295da31
Masonry: 678fab65091a9290e40e2832a55e7ab731aad201 Masonry: 678fab65091a9290e40e2832a55e7ab731aad201
MBProgressHUD: 1569cf7ace17a8bac47aabfbb8580a49690386d1 MBProgressHUD: 1569cf7ace17a8bac47aabfbb8580a49690386d1
MJExtension: 635f2c663dcb1bf76fa4b715b2570a5710aec545
MJRefresh: eeda70fbf0ad277f3178cef1cd0c3532591d6237 MJRefresh: eeda70fbf0ad277f3178cef1cd0c3532591d6237
Qiniu: a2fb1f87b40f52bc1f386d93c6d48aab09472eae
SDWebImage: 4ca2dc4eefae4224bea8f504251cda485a363745 SDWebImage: 4ca2dc4eefae4224bea8f504251cda485a363745
SnapKit: fe8a619752f3f27075cc9a90244d75c6c3f27e2a SnapKit: a42d492c16e80209130a3379f73596c3454b7694
TMCache: 95ebcc9b3c7e90fb5fd8fc3036cba3aa781c9bed TMCache: 95ebcc9b3c7e90fb5fd8fc3036cba3aa781c9bed
"UITableView+FDTemplateLayoutCell": 234e1582bcc4e18461af91155123bb96538ed030 "UITableView+FDTemplateLayoutCell": 234e1582bcc4e18461af91155123bb96538ed030
PODFILE CHECKSUM: a3a39944abac09536207b087a26344c6d8478f6b PODFILE CHECKSUM: 841939c0a048be2edb77418fd6de6e35a738768b
COCOAPODS: 1.7.4 COCOAPODS: 1.7.4
MIT 3 License
Copyright (c) 2015, EVICT B.V.
All rights reserved.
http://evict.nl, mailto://edwin@evict.nl
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* Neither the name of EVICT B.V. nor the
names of its contributors may be used to endorse or promote products
derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
# EVReflection
<!---
[![Circle CI](https://img.shields.io/circleci/project/evermeer/EVReflection.svg?style=flat)](https://circleci.com/gh/evermeer/EVReflection)
[![Build Status](https://travis-ci.org/evermeer/EVReflection.svg?style=flat)](https://travis-ci.org/evermeer/EVReflection)
-->
[![Issues](https://img.shields.io/github/issues-raw/evermeer/EVReflection.svg?style=flat)](https://github.com/evermeer/EVReflection/issues)
[![Coverage](https://img.shields.io/badge/coverage-80%25-yellow.svg?style=flat)](https://raw.githubusercontent.com/evermeer/EVReflection/master/UnitTests/coverage.png)
[![Documentation](https://img.shields.io/badge/documented-97%25-green.svg?style=flat)](http://cocoadocs.org/docsets/EVReflection/3.7.0/)
[![Stars](https://img.shields.io/github/stars/evermeer/EVReflection.svg?style=flat)](https://github.com/evermeer/EVReflection/stargazers)
[![Awesome](https://cdn.rawgit.com/sindresorhus/awesome/d7305f38d29fed78fa85652e3a63e154dd8e8829/media/badge.svg)](https://github.com/matteocrippa/awesome-swift#json)
[![Downloads](https://img.shields.io/cocoapods/dt/EVReflection.svg?style=flat)](https://cocoapods.org/pods/EVReflection)
[![Version](https://img.shields.io/cocoapods/v/EVReflection.svg?style=flat)](http://cocoadocs.org/docsets/EVReflection)
[![Carthage compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](https://github.com/Carthage/Carthage)
[![Language](https://img.shields.io/badge/language-swift%204%20&%205-f48041.svg?style=flat)](https://developer.apple.com/swift)
[![Platform](https://img.shields.io/cocoapods/p/EVReflection.svg?style=flat)](http://cocoadocs.org/docsets/EVReflection)
[![License](https://img.shields.io/cocoapods/l/EVReflection.svg?style=flat)](http://cocoadocs.org/docsets/EVReflection)
[![Git](https://img.shields.io/badge/GitHub-evermeer-blue.svg?style=flat)](https://github.com/evermeer)
[![Twitter](https://img.shields.io/badge/twitter-@evermeer-blue.svg?style=flat)](http://twitter.com/evermeer)
[![LinkedIn](https://img.shields.io/badge/linkedin-Edwin%20Vermeer-blue.svg?style=flat)](http://nl.linkedin.com/in/evermeer/en)
[![Website](https://img.shields.io/badge/website-evict.nl-blue.svg?style=flat)](http://evict.nl)
[![eMail](https://img.shields.io/badge/email-edwin@evict.nl-blue.svg?style=flat)](mailto:edwin@evict.nl?SUBJECT=About%20EVReflection)
# General information
If you have a question and don't want to create an issue, then we can [![Join the chat at https://gitter.im/evermeer/EVReflection](https://badges.gitter.im/evermeer/EVReflection.svg)](https://gitter.im/evermeer/EVReflection?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
At this moment the master branch is tested with Swift 4.2 and 5.0 beta If you want to continue using EVReflection in an older version, then use the corresponding branch.
Run the unit tests to see EVReflection in action.
EVReflection is used in [EVCloudKitDao](https://github.com/evermeer/EVCloudKitDao) and [EVWordPressAPI](https://github.com/evermeer/EVWordPressAPI)
In most cases EVReflection is very easy to use. Just take a look the section [It's easy to use](https://github.com/evermeer/EVReflection#its-easy-to-use). But if you do want to do non standard specific things, then EVReflection will offer you an extensive range of functionality.
### Available extensions
There are extension available for using EVReflection with [XMLDictionairy](https://github.com/nicklockwood/XMLDictionary), [Realm](https://realm.io), [CloudKit](https://developer.apple.com/library/content/documentation/DataManagement/Conceptual/CloudKitQuickStart/Introduction/Introduction.html), [Alamofire](https://github.com/Alamofire/Alamofire) and [Moya](https://github.com/Moya/Moya) with [RxSwift](https://github.com/ReactiveX/RxSwift) or [ReactiveSwift](https://github.com/ReactiveSwift/ReactiveSwift)
- [XML](https://github.com/evermeer/EVReflection/tree/master/Source/XML)
- [CloudKit](https://github.com/evermeer/EVReflection/tree/master/Source/CloudKit)
- [CoreData](https://github.com/evermeer/EVReflection/tree/master/Source/CoreData)
- [Realm](https://github.com/evermeer/EVReflection/tree/master/Source/Realm)
- [Alamofire](https://github.com/evermeer/EVReflection/tree/master/Source/Alamofire)
- [AlamofireXML](https://github.com/evermeer/EVReflection/tree/master/Source/XML)
- [Moya](https://github.com/evermeer/EVReflection/tree/master/Source/Alamofire/Moya)
- [MoyaXML](https://github.com/evermeer/EVReflection/tree/master/Source/Alamofire/Moya/XML)
- [MoyaRxSwift](https://github.com/evermeer/EVReflection/tree/master/Source/Alamofire/Moya/RxSwift)
- [MoyaRxSwiftXML](https://github.com/evermeer/EVReflection/tree/master/Source/Alamofire/Moya/RxSwift/XML)
- [MoyaReactiveSwift](https://github.com/evermeer/EVReflection/tree/master/Source/Alamofire/Moya/ReactiveSwift)
- [MoyaReactiveSwiftXML](https://github.com/evermeer/EVReflection/tree/master/Source/Alamofire/Moya/ReactiveSwift/XML)
All these extens can be installed by adding something like this in your podfile:
```
pod 'EVReflection/MoyaRxSwift'
```
For Carthage there is not (yet) an extension for all above extension. If needed, please let me know. For carthage you can use:
```
github "evermeer/EVReflection"
```
## Index
- [Main features of EVReflection](https://github.com/evermeer/EVReflection#main-features-of-evreflection)
- [It's easy to use](https://github.com/evermeer/EVReflection#its-easy-to-use)
- [If you have XML instead of JSON](https://github.com/evermeer/EVReflection#if-you-have-xml-instead-of-json)
- [Using EVReflection in your own App](https://github.com/evermeer/EVReflection#using-evreflection-in-your-own-app)
- [More Sample code](https://github.com/evermeer/EVReflection#more-sample-code)
- [Extending existing objects](https://github.com/evermeer/EVReflection#extending-existing-objects)
- [Conversion options](https://github.com/evermeer/EVReflection#conversion-options)
- [Automatic keyword mapping for Swift keywords](https://github.com/evermeer/EVReflection#automatic-keyword-mapping-for-swift-keywords)
- [Automatic keyword mapping PascalCase or camelCase to snake_case](https://github.com/evermeer/EVReflection#automatic-keyword-mapping-pascalcase-or-camelcase-to-snake_case)
- [Custom keyword mapping](https://github.com/evermeer/EVReflection#custom-keyword-mapping)
- [Custom property converters](https://github.com/evermeer/EVReflection#custom-property-converters)
- [Custom object converter](https://github.com/evermeer/EVReflection#custom-object-converter)
- [Custom type converters](https://github.com/evermeer/EVReflection#custom-type-converter)
- [Encoding and Decoding](https://github.com/evermeer/EVReflection#encoding-and-decoding)
- [Skip the serialization or deserialization of specific values](https://github.com/evermeer/EVReflection#skip-the-serialization-or-deserialization-of-specific-values)
- [Property validators](https://github.com/evermeer/EVReflection#property-validators)
- [Print options](https://github.com/evermeer/EVReflection#print-options)
- [Deserialization class level validations](https://github.com/evermeer/EVReflection#deserialization-class-level-validations)
- [What to do when you use object inheritance](https://github.com/evermeer/EVReflection#what-to-do-when-you-use-object-inheritance)
- [Known issues](https://github.com/evermeer/EVReflection#known-issues)
- [License](https://github.com/evermeer/EVReflection#license)
- [My other libraries](https://github.com/evermeer/EVReflection#my-other-libraries)
## Main features of EVReflection:
- Parsing objects based on NSObject to and from a dictionary. (also see the XML and .plist samples!)
- Parsing objects to and from a JSON string.
- Support NSCoding function encodeWithCoder and decodeObjectWithCoder
- Supporting Printable, Hashable and Equatable while using all properties.
- Mapping objects from one type to an other
- Support for property mapping, converters, validators and key cleanup
## It's easy to use:
Defining an object. You only have to set EVObject as it's base class (or extend an NSObject with EVReflectable):
```swift
class User: EVObject {
var id: Int = 0
var name: String = ""
var friends: [User]? = []
}
```
Parsing JSON to an object:
```swift
let json:String = "{\"id\": 24, \"name\": \"Bob Jefferson\", \"friends\": [{\"id\": 29, \"name\": \"Jen Jackson\"}]}"
let user = User(json: json)
```
Parsing JSON to an array of objects:
```swift
let json:String = "[{\"id\": 27, \"name\": \"Bob Jefferson\"}, {\"id\": 29, \"name\": \"Jen Jackson\"}]"
let array = [User](json: json)
```
Parsing from and to a dictionary:
```swift
let dict = user.toDictionary()
let newUser = User(dictionary: dict)
XCTAssert(user == newUser, "Pass")
```
Saving and loading an object to and from a file:
```swift
user.saveToTemp("temp.dat")
let result = User(fileNameInTemp: "temp.dat")
XCTAssert(theObject == result, "Pass")
```
Mapping object to another type:
```swift
let administrator: Administrator = user.mapObjectTo()
```
## If you have XML instead of JSON
If you want to do the same but you have XML, then you can achieve that using the XML subspec 'pod EVReflection/XML' It is a simple way to parse XML. With that your code will look like this:
```swift
let xml = "<user><id>27</id><name>Bob</name><friends><user><id>20</id><name>Jen</name></user></friends></user>"
let user = User(xmlString: xml)
```
## Using EVReflection in your own App
'EVReflection' is available through the dependency manager [CocoaPods](http://cocoapods.org).
You do have to use cocoapods version 0.36 or later
You can just add EVReflection to your workspace by adding the following 2 lines to your Podfile:
```
use_frameworks!
pod "EVReflection"
```
You can also use the Swift2.2 or Swift2.3 version of EVReflection. You can get that version by using the podfile command:
```
use_frameworks!
pod "EVReflection"', :git => 'https://github.com/evermeer/EVReflection.git', :branch => 'Swift2.2'
```
Version 0.36 of cocoapods will make a dynamic framework of all the pods that you use. Because of that it's only supported in iOS 8.0 or later. When using a framework, you also have to add an import at the top of your swift file like this:
```swift
import EVReflection
```
If you want support for older versions than iOS 8.0, then you can also just copy the files from the pod folder to your project. You do have to use the Swift2.3 version or older. iOS 7 support is dropped from Swift 3.
Be aware that when you have your object definitions in a framework and not in your main app, then you have to let EVReflection know that it should also look in that framework for your classes. This can easilly be done by using the following one liner (for instance in the appdelegate)
```swift
EVReflection.setBundleIdentifier(YourDataObject.self)
```
## More Sample code
Clone EVReflection to your desktop to see these and more unit tests
```swift
func testEquatable() {
var theObjectA = TestObject2()
theObjectA.objectValue = "value1"
var theObjectB = TestObject2()
theObjectB.objectValue = "value1"
XCTAssert(theObjectA == theObjectB, "Pass")
theObjectB.objectValue = "value2"
XCTAssert(theObjectA != theObjectB, "Pass")
}
func testHashable() {
var theObject = TestObject2()
theObject.objectValue = "value1"
var hash1 = theObject.hash
NSLog("hash = \(hash)")
}
func testPrintable() {
var theObject = TestObject2()
theObject.objectValue = "value1"
NSLog("theObject = \(theObject)")
}
func testArrayFunctions() {
let dictionaryArray: [NSDictionary] = yourGetDictionaryArrayFunction()
let userArray = [User](dictionaryArray: dictionaryArray)
let newDictionaryArray = userArray.toDictionaryArray()
}
func testMapping() {
let player = GamePlayer()
player.name = "It's Me"
let administrator = GameAdministrator(usingValuesFrom: player)
}
```
Direct conversion from a NSDictionary (or an array of NSDictionaries) to json and back.
```swift
let dict1: NSDictionary = [
"requestId": "request",
"postcode": "1111AA",
"houseNumber": "1"
]
let json = dict1.toJsonString()
let dict2 = NSMutableDictionary(json: json)
print("dict:\n\(dict1)\n\njson:\n\(json)\n\ndict2:\n\(dict2)")
// You can do the same with arrays
let array:[NSDictionary] = [dict1, dict2]
let jsonArray = array.toJsonStringArray()
let array2 = [NSDictionary](jsonArray: jsonArray)
print("json array: \n\(jsonArray)\n\narray2:\n\(array2)")
```
This is how you can parse a .plist into an object model. See EVReflectionIssue124.swift to see it working.
```swift
if let path = Bundle(for: type(of: self)).path(forResource: "EVReflectionIssue124", ofType: "plist") {
if let data = NSDictionary(contentsOfFile: path) {
let plistObject = Wrapper(dictionary: data)
print(plistObject)
}
}
```
If you want to parse XML, then you can use the pod subxpec EVReflection/XML
```swift
let xml: String = "<data><item name=\"attrib\">itemData</item></data>"
let xmlObject = MyObject(xml: xml)
print(xmlObject)
```
## Extending existing objects:
It is possible to extend other objects with the EVReflectable protocol instead of changing the base class to EVObject. This will let you add the power of EVReflection to objects that also need another framework. In some cases you still need some aditional code. For a sample see the Realm and NSManagedObject subspecs. The most basic way to extend your objects is like this:
```swift
import EVReflection
extension MyObject : EVReflectable { }
```
## Extra information:
### Conversion options
With almost any EVReflection function you can specify what kind of conversion options should be used. This is done using an option set. You can use the following conversion options:
- None - Do not use any conversion function.
- [PropertyConverter](https://github.com/evermeer/EVReflection#custom-property-converters) : If specified the function propertyConverters on the EVObject will be called
- [PropertyMapping](https://github.com/evermeer/EVReflection#custom-keyword-mapping) : If specified the function propertyMapping on the EVObject will be called
- [SkipPropertyValue](https://github.com/evermeer/EVReflection#skip-the-serialization-or-deserialization-of-specific-values) : If specified the function skipPropertyValue on the EVObject will be called
- [KeyCleanup](https://github.com/evermeer/EVReflection#automatic-keyword-mapping-pascalcase-or-camelcase-to-snake_case) : If specified the automatic pascalCase and snake_case property key mapping will be called.
- [Encoding](https://github.com/evermeer/EVReflection#encoding-and-decoding) : For if you want class level functionality for encoding values (like base64, unicode, encription, ...)
- [Decoding](https://github.com/evermeer/EVReflection#encoding-and-decoding) : For if you want class level functionality for decoding values (like base64, unicode, encription, ...)
In EVReflection all functions will use a default conversion option specific to it's function. The following 4 default conversion types are used:
- DefaultNSCoding = [None]
- DefaultComparing = [PropertyConverter, PropertyMapping, SkipPropertyValue]
- DefaultDeserialize = [PropertyConverter, PropertyMapping, SkipPropertyValue, KeyCleanup, Decoding]
- DefaultSerialize = [PropertyConverter, PropertyMapping, SkipPropertyValue, Encoding]
If you want to change one of the default conversion types, then you can do that using something like:
```swift
ConversionOptions.DefaultNSCoding = [.PropertyMapping]
```
### Automatic keyword mapping for Swift keywords
If you have JSON fields that are Swift keywords, then prefix the property with an underscore. So the JSON value for self will be stored in the property `\_self`. At this moment the following keywords are handled:
"self", "description", "class", "deinit", "enum", "extension", "func", "import", "init", "let", "protocol", "static", "struct", "subscript", "typealias", "var", "break", "case", "continue", "default", "do", "else", "fallthrough", "if", "in", "for", "return", "switch", "where", "while", "as", "dynamicType", "is", "new", "super", "Self", "Type", "__COLUMN__", "__FILE__", "__FUNCTION__", "__LINE__", "associativity", "didSet", "get", "infix", "inout", "left", "mutating", "none", "nonmutating", "operator", "override", "postfix", "precedence", "prefix", "right", "set", "unowned", "unowned", "safe", "unowned", "unsafe", "weak", "willSet", "private", "public"
### Automatic keyword mapping PascalCase or camelCase to snake_case
When creating objects from JSON EVReflection will automatically detect if snake_case (keys are all lowercase and words are separated by an underscore) should be converted to PascalCase or camelCase property names. See [Conversion options](https://github.com/evermeer/EVReflection#conversion-options) for when this function will be called.
When exporting object to a dictionary or JSON string you will have an option to specify that you want a conversion to snake_case or not. The default is .DefaultDeserialize which will also convert to snake case.
```swift
let jsonString = myObject.toJsonString([.DefaultSerialize])
let dict = myObject.toDictionary([PropertyConverter, PropertyMapping, SkipPropertyValue])
```
### Custom keyword mapping
It's also possible to create a custom property mapping. You can define if an import should be ignored, if an export should be ignored or you can map a property name to another key name (for the dictionary and json). For this you only need to implement the propertyMapping function in the object. See [Conversion options](https://github.com/evermeer/EVReflection#conversion-options) for when this function will be called.
```swift
public class TestObject5: EVObject {
var Name: String = "" // Using the default mapping
var propertyInObject: String = "" // will be written to or read from keyInJson
var ignoredProperty: String = "" // Will not be written or read to/from json
override public func propertyMapping() -> [(keyInObject: String?, keyInResource: String?)] {
return [(keyInObject: "ignoredProperty",keyInResource: nil), (keyInObject: "propertyInObject",keyInResource: "keyInJson")]
}
}
```
### Custom property converters
You can also use your own property converters. For this you need to implement the propertyConverters function in your object. For each property you can create a custom getter and setter that will then be used by EVReflection. In the sample below the JSON texts 'Sure' and 'Nah' will be converted to true or false for the property isGreat. See [Conversion options](https://github.com/evermeer/EVReflection#conversion-options) for when this function will be called.
```swift
public class TestObject6: EVObject {
var isGreat: Bool = false
override func propertyConverters() -> [(key: String, decodeConverter: ((Any?) -> ()), encodeConverter: (() -> Any?))] {
return [
( // We want a custom converter for the field isGreat
key: "isGreat"
// isGreat will be true if the json says 'Sure'
, decodeConverter: { self.isGreat = ($0 as? String == "Sure") }
// The json will say 'Sure if isGreat is true, otherwise it will say 'Nah'
, encodeConverter: { return self.isGreat ? "Sure": "Nah"})
]
}
}
```
### Encoding and decoding
You can add generic cod to encode or decode multiple or all properties in an object. This can be used for instance for base64, unicode and encription. Here is a base64 sample:
```swift
class SimleEncodingDecodingObject : EVObject{
var firstName: String?
var lastName: String?
var street: String?
var city: String?
override func decodePropertyValue(value: Any, key: String) -> Any? {
return (value as? String)?.base64Decoded?.string ?? value
}
override func encodePropertyValue(value: Any, key: String) -> Any {
return (value as? String)?.base64Encoded.string ?? value
}
}
extension String {
var data: Data { return Data(utf8) }
var base64Encoded: Data { return data.base64EncodedData() }
var base64Decoded: Data? { return Data(base64Encoded: self) }
}
extension Data {
var string: String? { return String(data: self, encoding: .utf8) }
}
```
### Custom object converter
If you want to serialize an object to a dictionary or json but the structure should be different than the object itself, then instead of using propertyConverers, you can also convert the entire object by implementing the customConverter function. In the example below the entire object will be serialized to just a string. You could also return a dictionary that represents the custom structure or an array if the object should have been an array
```swift
override func customConverter() -> AnyObject? {
return "Object not serialized"
}
```
### Custom type converter
If you have a custom type that requires special conversion, then you can extend it with the EVCustomReflectable protocol. A good implementation for this can be found in the Realm subspec for the List type. The converter is implemented like this:
```swift
extension List : EVCustomReflectable {
public func constructWith(value: Any?) -> EVCustomReflectable {
if let array = value as? [NSDictionary] {
self.removeAll()
for dict in array {
if let element: T = EVReflection.fromDictionary(dict, anyobjectTypeString: _rlmArray.objectClassName) as? T {
self.append(element)
}
}
}
return self
}
public func toCodableValue() -> Any {
return self.enumerated().map { ($0.element as? EVReflectable)?.toDictionary() ?? NSDictionary() }
}
}
```
For the usage, please have a look at [the Realm unittest](https://github.com/evermeer/EVReflection/blob/master/UnitTests/RealmTests/RealmTests.swift)
### Skip the serialization or deserialization of specific values
When there is a need to not (de)serialize specific values like nil NSNull or empty strings you can implement the skipPropertyValue function and return true if the value needs to be skipped. See [Conversion options](https://github.com/evermeer/EVReflection#conversion-options) for when this function will be called.
```swift
class TestObjectSkipValues: EVObject {
var value1: String?
var value2: [String]?
var value3: NSNumber?
override func skipPropertyValue(value: Any, key: String) -> Bool {
if let value = value as? String where value.characters.count == 0 || value == "null" {
print("Ignoring empty string for key \(key)")
return true
} else if let value = value as? NSArray where value.count == 0 {
print("Ignoring empty NSArray for key\(key)")
return true
} else if value is NSNull {
print("Ignoring NSNull for key \(key)")
return true
}
return false
}
}
```
### Property validators
Before setting a value the value will always be validated using the standard validateValue KVO function. This means that for every property you can also create a validation function for that property. See the sample below where there is a validateName function for the name property.
```swift
enum MyValidationError: ErrorType {
case TypeError,
LengthError
}
public class GameUser: EVObject {
var name: String?
var memberSince: NSDate?
var objectIsNotAValue: TestObject?
func validateName(value:AutoreleasingUnsafeMutablePointer<AnyObject?>) throws {
if let theValue = value.memory as? String {
if theValue.lengthOfBytesUsingEncoding(NSUTF8StringEncoding) < 3 {
NSLog("Validating name is not long enough \(theValue)")
throw MyValidationError.LengthError
}
NSLog("Validating name OK \(theValue)")
} else {
NSLog("Validating name is not a string: \(value.memory)")
throw MyValidationError.TypeError
}
}
}
```
### Print options
You should be able to solve all problems with parsing your json to an object. If you get warnings and you know they don't matter and you want to stop them from printin you can suppress all print warings by calling the followin line of code:
```swift
PrintOptions.Active = .None
```
If you then want to turn on the print output, then just call:
```
PrintOptions.Active = .All
```
It's also possible to enable printing for specific warning types. Here is the line of code that is equal to setting it to .All. Just leave out the type that you want to suppress.
```
PrintOptions.Active = [.UnknownKeypath, .IncorrectKey, .ShouldExtendNSObject, .IsInvalidJson, .MissingProtocol, .MissingKey, .InvalidType, .InvalidValue, .InvalidClass, .EnumWithoutAssociatedValue]
```
### Deserialization class level validations
There is also support for class level validation when deserializing to an object. There are helper functions for making keys required or not allowed. You can also add custom messages. Here is some sample code about how you can implement such a validation
```swift
public class ValidateObject: EVObject {
var requiredKey1: String?
var requiredKey2: String?
var optionalKey1: String?
override public func initValidation(dict: NSDictionary) {
self.initMayNotContainKeys(["error"], dict: dict)
self.initMustContainKeys(["requiredKey1", "requiredKey2"], dict: dict)
if dict.valueForKey("requiredKey1") as? String == dict.valueForKey("optionalKey1") as? String {
// this could also be called in your property specific validators
self.addStatusMessage(.Custom, message: "optionalKey1 should not be the same as requiredKey1")
}
}
}
```
You could then test this validation with code like:
```swift
func testValidation() {
// Test missing required key
let json = "{\"requiredKey1\": \"Value1\"}"
let test = ValidateObject(json: json)
XCTAssertNotEqual(test.evReflectionStatus(), .None, "We should have a not .None status")
XCTAssertEqual(test.evReflectionStatuses.count, 1, "We should have 1 validation result")
for (status, message) in test.evReflectionStatuses {
print("Validation result: Status = \(status), Message = \(message)")
}
}
```
### What to do when you use object inheritance
You can deserialize json to an object that uses inheritance. When the properties are specified as the base class, then the correct specific object type will be returned by the function `getSpecificType`. See the sample code below or the unit test in EVReflectionInheritanceTests.swift
```swift
class Quz: EVObject {
var fooArray: Array<Foo> = []
var fooBar: Foo?
var fooBaz: Foo?
}
class Foo: EVObject {
var allFoo: String = "all Foo"
// What you need to do to get the correct type for when you deserialize inherited classes
override func getSpecificType(_ dict: NSDictionary) -> EVReflectable {
if dict["justBar"] != nil {
return Bar()
} else if dict["justBaz"] != nil {
return Baz()
}
return self
}
}
class Bar : Foo {
var justBar: String = "For bar only"
}
class Baz: Foo {
var justBaz: String = "For baz only"
}
```
### Known issues
EVReflection is trying to handle all types. With some types there are limitations in Swift. So far there is a workaround for any of these limitations. Here is an overview:
#### It's not possible in Swift to use .setObjectForKey for:
- nullable type fields like Int?
- properties based on an enum
- an Array of nullable objects like [MyObject?]
- a Set like Set<MyObject>
- generic properties like var myVal:T = T()
- structs like CGRect or CGPoint
For all these issues there are workarounds. The easiest workaround is just using a difrent type like:
- Instead of an Int? you could use NSNumber?
- Instead of [MyObject?] use [MyObject]
- Instead of Set<MyObject> use [MyObject]
- Instead of 'var status: StatysType' use 'var status:Int' and save the rawValue
- Instead of a generic property use a specific property that can hold the data (a dictionary?)
- Instead of using a struct, create your own object model for that struct
If you want to keep on using the same type, You can override the setValue forUndefinedKey in the object itself. See WorkaroundsTests.swift and WorkaroundSwiftGenericsTests.swift to see the workaround for all these types in action.
#### Generic properties
For generic properties the protocol EVGenericsKVC is required. see WorkaroundSwiftGenericsTests.swift
#### Arrays with nullable objects or Set's
For arrays with nullable objects or Set's like [MyObj?] or Set<MyObj> the protocol EVArrayConvertable is required. see WorkaroundsTests.swift
#### Swift Dictionaries
For Swift Dictionaries (and not NSDictionary) the protocol EVDictionaryConvertable is required. See WorkaroundsTests.swift
## License
EVReflection is available under the MIT 3 license. See the LICENSE file for more info.
## My other libraries:
Also see my other public source iOS libraries:
- [EVReflection](https://github.com/evermeer/EVReflection) - Reflection based (Dictionary, CKRecord, JSON and XML) object mapping with extensions for Alamofire and Moya with RxSwift or ReactiveSwift
- [EVCloudKitDao](https://github.com/evermeer/EVCloudKitDao) - Simplified access to Apple's CloudKit
- [EVFaceTracker](https://github.com/evermeer/EVFaceTracker) - Calculate the distance and angle of your device with regards to your face in order to simulate a 3D effect
- [EVURLCache](https://github.com/evermeer/EVURLCache) - a NSURLCache subclass for handling all web requests that use NSURLReques
- [AlamofireOauth2](https://github.com/evermeer/AlamofireOauth2) - A swift implementation of OAuth2 using Alamofire
- [EVWordPressAPI](https://github.com/evermeer/EVWordPressAPI) - Swift Implementation of the WordPress (Jetpack) API using AlamofireOauth2, AlomofireJsonToObjects and EVReflection (work in progress)
- [PassportScanner](https://github.com/evermeer/PassportScanner) - Scan the MRZ code of a passport and extract the firstname, lastname, passport number, nationality, date of birth, expiration date and personal numer.
- [AttributedTextView](https://github.com/evermeer/AttributedTextView) - Easiest way to create an attributed UITextView with support for multiple links (url, hashtags, mentions).
## Evolution of EVReflection (Gource Visualization)
[![Evolution of EVReflection (Gource Visualization)](https://img.youtube.com/vi/FIETlttIFh8/0.jpg)](https://www.youtube.com/watch?v=FIETlttIFh8)
//
// ConversionOptions.swift
// EVReflection
//
// Created by Edwin Vermeer on 9/5/16.
// Copyright © 2015 evict. All rights reserved.
//
/**
For specifying what conversion options should be executed
*/
public struct ConversionOptions: OptionSet, CustomStringConvertible {
/// The numeric representation of the options
public let rawValue: Int
/**
Initialize with a raw value
- parameter rawValue: the numeric representation
- returns: The ConversionOptions
*/
public init(rawValue: Int) { self.rawValue = rawValue }
/// No conversion options
public static let None = ConversionOptions(rawValue: 0)
/// Execute property converters
public static let PropertyConverter = ConversionOptions(rawValue: 1)
/// Execute property mapping
public static let PropertyMapping = ConversionOptions(rawValue: 2)
/// Skip specific property values
public static let SkipPropertyValue = ConversionOptions(rawValue: 4)
/// Do a key cleanup (CameCase, snake_case)
public static let KeyCleanup = ConversionOptions(rawValue: 8)
/// Execute the decoding function for all values
public static let Decoding = ConversionOptions(rawValue: 16)
/// Execute an encoding function on all values
public static let Encoding = ConversionOptions(rawValue: 32)
// Just for bein able to show all
public static var All: ConversionOptions = [PropertyConverter, PropertyMapping, SkipPropertyValue, KeyCleanup, Decoding, Encoding]
/// Default used for NSCoding
public static var DefaultNSCoding: ConversionOptions = [None]
/// Default used for comparing / hashing functions
public static var DefaultComparing: ConversionOptions = [PropertyConverter, PropertyMapping, SkipPropertyValue]
/// Default used for deserialization
public static var DefaultDeserialize: ConversionOptions = [PropertyConverter, PropertyMapping, SkipPropertyValue, KeyCleanup, Decoding]
/// Default used for serialization
public static var DefaultSerialize: ConversionOptions = [PropertyConverter, PropertyMapping, SkipPropertyValue, Encoding]
/// Get a nice description of the ConversionOptions
public var description: String {
let strings = ["PropertyConverter", "PropertyMapping", "SkipPropertyValue", "KeyCleanup", "Decoding", "Encoding"]
var members = [String]()
for (flag, string) in strings.enumerated() where contains(ConversionOptions(rawValue:1<<(flag + 1))) {
members.append(string)
}
if members.count == 0 {
members.append("None")
}
return members.description
}
}
//
// DeserializationStatus.swift
// EVReflection
//
// Created by Edwin Vermeer on 9/5/16.
// Copyright © 2015 evict. All rights reserved.
//
/**
Type of status messages after deserialization
*/
public struct DeserializationStatus: OptionSet, CustomStringConvertible {
/// The numeric representation of the options
public let rawValue: Int
/**
Initialize with a raw value
- parameter rawValue: the numeric representation
- returns: the DeserializationStatus
*/
public init(rawValue: Int) { self.rawValue = rawValue }
/// No status message
public static let None = DeserializationStatus(rawValue: 0)
/// Incorrect key error
public static let IncorrectKey = DeserializationStatus(rawValue: 1)
/// Missing key error
public static let MissingKey = DeserializationStatus(rawValue: 2)
/// Invalid type error
public static let InvalidType = DeserializationStatus(rawValue: 4)
/// Invalid value error
public static let InvalidValue = DeserializationStatus(rawValue: 8)
/// Invalid class error
public static let InvalidClass = DeserializationStatus(rawValue: 16)
/// Missing protocol error
public static let MissingProtocol = DeserializationStatus(rawValue: 32)
/// Custom status message
public static let Custom = DeserializationStatus(rawValue: 64)
/// Get a nice description of the DeserializationStatus
public var description: String {
let strings = ["IncorrectKey", "MissingKey", "InvalidType", "InvalidValue", "InvalidClass", "MissingProtocol", "Custom"]
var members = [String]()
for (flag, string) in strings.enumerated() where contains(DeserializationStatus(rawValue:1<<(flag))) {
members.append(string)
}
if members.count == 0 {
members.append("None")
}
return members.description
}
}
//
// ArrayExtension.swift
// EVReflection
//
// Created by Edwin Vermeer on 9/2/15.
// Copyright © 2015 evict. All rights reserved.
//
import Foundation
/**
Extending Array with an some EVReflection functions where the elements can be of type NSObject
*/
public extension Array where Element: NSObject {
/**
Initialize an array based on a json string
- parameter json: The json string
- parameter conversionOptions: Option set for the various conversion options.
*/
init(json: String?, conversionOptions: ConversionOptions = .DefaultDeserialize, forKeyPath: String? = nil) {
self.init()
let arrayTypeInstance = getArrayTypeInstance(self)
let newArray = EVReflection.arrayFromJson(type: arrayTypeInstance, json: json, conversionOptions: conversionOptions, forKeyPath: forKeyPath)
for item in newArray {
self.append(item)
}
}
/**
Initialize an array based on a json string
- parameter json: The json string
- parameter conversionOptions: Option set for the various conversion options.
*/
init(data: Data?, conversionOptions: ConversionOptions = .DefaultDeserialize, forKeyPath: String? = nil) {
self.init()
let arrayTypeInstance = getArrayTypeInstance(self)
let newArray = EVReflection.arrayFromData(nil, type:arrayTypeInstance, data: data, conversionOptions: conversionOptions, forKeyPath: forKeyPath)
for item in newArray {
self.append(item)
}
}
/**
Initialize an array based on a dictionary
- parameter json: The json string
- parameter conversionOptions: Option set for the various conversion options.
*/
init(dictionaryArray: [NSDictionary], conversionOptions: ConversionOptions = .DefaultDeserialize) {
self.init()
for item in dictionaryArray {
let arrayTypeInstance = getArrayTypeInstance(self)
EVReflection.setPropertiesfromDictionary(item, anyObject: arrayTypeInstance)
self.append(arrayTypeInstance)
}
}
/**
Initialize an array based on a dictionary
- parameter json: The json string
- parameter conversionOptions: Option set for the various conversion options.
*/
init(dictionary: NSDictionary, forKeyPath: String, conversionOptions: ConversionOptions = .DefaultDeserialize) {
self.init()
guard let dictionaryArray = dictionary.value(forKeyPath: forKeyPath) as? [NSDictionary] else {
evPrint(.UnknownKeypath, "ERROR: The forKeyPath '\(forKeyPath)' resulted in an empty array")
return
}
for item in dictionaryArray {
let arrayTypeInstance = getArrayTypeInstance(self)
EVReflection.setPropertiesfromDictionary(item, anyObject: arrayTypeInstance)
self.append(arrayTypeInstance)
}
}
/**
Get the type of the object where this array is for
- parameter arr: this array
- returns: The object type
*/
func getArrayTypeInstance<T: NSObject>(_ arr: Array<T>) -> T {
return arr.getTypeInstance()
}
/**
Get the type of the object where this array is for
- returns: The object type
*/
func getTypeInstance<T: NSObject>(
) -> T {
let nsobjectype: NSObject.Type = T.self
let nsobject: NSObject = nsobjectype.init()
if let obj = nsobject as? T {
return obj
}
// Could not instantiate array item instance.
return T()
}
/**
Get the string representation of the type of the object where this array is for
- returns: The object type
*/
func getTypeAsString() -> String {
let item = self.getTypeInstance()
return NSStringFromClass(type(of:item))
}
}
/**
Extending Array with an some EVReflection functions where the elements can be of type EVReflectable
*/
public extension Array where Element: EVReflectable {
/**
Convert this array to a json string
- parameter conversionOptions: Option set for the various conversion options.
- parameter prettyPrinted: Define if you want enters and indents
- returns: The json string
*/
func toJsonString(_ conversionOptions: ConversionOptions = .DefaultSerialize, prettyPrinted: Bool = false) -> String {
return "[\n" + self.map({($0).toJsonString(conversionOptions, prettyPrinted: prettyPrinted)}).joined(separator: ", \n") + "\n]"
}
/**
Convert this array to a json data
- parameter conversionOptions: Option set for the various conversion options.
- parameter prettyPrinted: Define if you want enters and indents
- parameter encoding: The string encoding defaulsts to .utf8
- returns: The json data
*/
func toJsonData(_ conversionOptions: ConversionOptions = .DefaultSerialize, prettyPrinted: Bool = false, encoding: String.Encoding = .utf8) -> Data {
return self.toJsonString(conversionOptions, prettyPrinted: prettyPrinted).data(using: encoding) ?? Data()
}
/**
Returns the dictionary representation of this array.
- parameter conversionOptions: Option set for the various conversion options.
- returns: The array of dictionaries
*/
func toDictionaryArray(_ conversionOptions: ConversionOptions = .DefaultSerialize) -> NSArray {
return self.map({($0).toDictionary(conversionOptions)}) as NSArray
}
}
/**
Extending Array with an some EVReflection functions where the elements can be of type NSObject
*/
public extension Array where Element: NSDictionary {
/**
Initialize a dictionary array based on a json string
- parameter json: The json string
*/
init(jsonArray: String) {
self.init()
let dictArray = EVReflection.dictionaryArrayFromJson(jsonArray)
for item in dictArray {
self.append(item as! Element)
}
}
/**
Initialize a dictionary array based on a json string
- parameter json: The json string
*/
init(dataArray: Data) {
self.init(jsonArray: String(data: dataArray, encoding: .utf8) ?? "")
}
/**
Convert this array to a json string
- parameter conversionOptions: Option set for the various conversion options.
- returns: The json string
*/
func toJsonStringArray(prettyPrinted: Bool = false) -> String {
let jsonArray: [String] = self.map { ($0 as NSDictionary).toJsonString(prettyPrinted: prettyPrinted) as String }
return "[\n" + jsonArray.joined(separator: ", \n") + "\n]"
}
}
public extension NSArray {
func nestedArrayMap<T>(_ element: (NSDictionary)->T) -> [[T]] {
return (self.map {
(($0 as? NSArray)?.map {
element($0 as? NSDictionary ?? NSDictionary())
}) ?? []
})
}
func doubleNestedArrayMap<T>(_ element: (NSDictionary)->T) -> [[[T]]] {
return (self.map {
(($0 as? NSArray)?.nestedArrayMap { element($0) }) ?? [[]]
})
}
func tripleNestedArrayMap<T>(_ element: (NSDictionary)->T) -> [[[[T]]]] {
return (self.map {
(($0 as? NSArray)?.doubleNestedArrayMap { element($0) }) ?? [[[]]]
})
}
func quadrupleNestedArrayMap<T>(_ element: (NSDictionary)->T) -> [[[[[T]]]]] {
return (self.map {
(($0 as? NSArray)?.tripleNestedArrayMap { element($0) }) ?? [[[[]]]]
})
}
func quintupleNestedArrayMap<T>(_ element: (NSDictionary)->T) -> [[[[[[T]]]]]] {
return (self.map {
(($0 as? NSArray)?.quadrupleNestedArrayMap { element($0) }) ?? [[[[[]]]]]
})
}
func sextupleNestedArrayMap<T>(_ element: (NSDictionary)->T) -> [[[[[[[T]]]]]]] {
return (self.map {
(($0 as? NSArray)?.quintupleNestedArrayMap { element($0) }) ?? [[[[[[]]]]]]
})
}
// If you need deeper nesting, whell, then you probably see the pattern above that you need to implement :-)
// just name them septuple, octuple, nonuple and decuple
// I'm not sure how far swift can handle it, but you should not want something like that.
}
//
// EVCustomReflectable.swift
// EVReflection
//
// Created by Edwin Vermeer on 27/10/2016.
// Copyright © 2016 evict. All rights reserved.
//
import Foundation
// Protocol that can be used for sub objects to define that parsing will be done in the parent using the 'setValue forKey' function
public protocol EVCustomReflectable {
static func constructWith(value: Any?) -> EVCustomReflectable?
func constructWith(value: Any?) -> EVCustomReflectable?
func toCodableValue() -> Any
}
//
// DictionaryExtension.swift
// EVReflection
//
// Created by Edwin Vermeer on 9/2/15.
// Copyright © 2015 evict. All rights reserved.
//
import Foundation
/**
Dictionary extension for creating a json strin from an array of enum values
*/
public extension NSMutableDictionary {
/**
Initialize a Dictionary based on a json string
*/
convenience init(json: String) {
self.init()
let jsonDict = EVReflection.dictionaryFromJson(json)
for (key, value) in jsonDict {
self[key] = value
}
}
/**
Initialize a Dictionary based on json data
*/
convenience init(data: Data) {
self.init(json: String(data: data, encoding: .utf8) ?? "")
}
}
public extension NSDictionary {
/**
Create a json string based on this dictionary
- parameter prettyPrinted: compact of pretty printed
*/
func toJsonString(prettyPrinted: Bool = false) -> String {
let data = self.toJsonData(prettyPrinted: prettyPrinted)
return String(data: data, encoding: .utf8) ?? ""
}
/**
Create a json data based on this dictionary
- parameter prettyPrinted: compact of pretty printed
*/
func toJsonData(prettyPrinted: Bool = false) -> Data {
do {
if prettyPrinted {
return try JSONSerialization.data(withJSONObject: self, options: .prettyPrinted)
}
return try JSONSerialization.data(withJSONObject: self, options: [])
} catch { }
return Data()
}
}
public extension NSMutableDictionary {
/**
Merge a 2nd dictionary into this one
- parameter dictionary: The 2nd dictionary that will be merged into this one
*/
func unionInPlace(dictionary: NSDictionary) {
for (key, value) in dictionary {
self[key] = value
}
}
/**
Merge a sequence into this dictionary
- parameter dictionary: The sequence that will be merged into this dictionary
*/
func unionInPlace<S: Sequence>(sequence: S) where
S.Iterator.Element == (Key,Value) {
for (key, value) in sequence {
self[key] = value
}
}
}
//
// EVObject.swift
//
// Created by Edwin Vermeer on 5/2/15.
// Copyright (c) 2015 evict. All rights reserved.
//
import Foundation
/**
Object that implements EVReflectable and NSCoding. Use this object as your base class
instead of NSObject and you wil automatically have support for all these protocols.
*/
@objcMembers
open class EVObject: NSObject, NSCoding, EVReflectable {
// These are redundant in Swift 2+: CustomDebugStringConvertible, CustomStringConvertible, Hashable, Equatable
/**
Implementation of the setValue forUndefinedKey so that we can catch exceptions for when we use an optional Type like Int? in our object. Instead of using Int? you should use NSNumber?
This method is in EVObject and not in NSObject extension because you would get the error: method conflicts with previous declaration with the same Objective-C selector
- parameter value: The value that you wanted to set
- parameter key: The name of the property that you wanted to set
*/
open override func setValue(_ value: Any!, forUndefinedKey key: String) {
if let kvc = self as? EVGenericsKVC {
kvc.setGenericValue(value as AnyObject?, forUndefinedKey: key)
} else {
self.addStatusMessage(.IncorrectKey, message: "The class '\(EVReflection.swiftStringFromClass(self))' is not key value coding-compliant for the key '\(key)'")
evPrint(.IncorrectKey, "\nWARNING: The class '\(EVReflection.swiftStringFromClass(self))' is not key value coding-compliant for the key '\(key)'\n There is no support for optional type, array of optionals or enum properties.\nAs a workaround you can implement the function 'setValue forUndefinedKey' for this. See the unit tests for more information\n")
}
}
/**
Implementation of the NSObject isEqual comparisson method
This method is in EVObject and not in NSObject extension because you would get the error: method conflicts with previous declaration with the same Objective-C selector
- parameter object: The object where you want to compare with
- returns: Returns true if the object is the same otherwise false
*/
open override func isEqual(_ object: Any?) -> Bool { // for isEqual:
if let obj = object as? EVObject {
return EVReflection.areEqual(self, rhs: obj)
}
return false
}
/**
Returns the pritty description of this object
- returns: The pritty description
*/
open override var description: String {
get {
return EVReflection.description(self, prettyPrinted: true)
}
}
/**
Returns the pritty description of this object
- returns: The pritty description
*/
open override var debugDescription: String {
get {
return EVReflection.description(self, prettyPrinted: true)
}
}
/**
This basic init override is needed so we can use EVObject as a base class.
*/
public required override init() {
super.init()
}
/**
Decode any object
This method is in EVObject and not in NSObject because you would get the error: Initializer requirement 'init(coder:)' can
only be satisfied by a `required` initializer in the definition of non-final class 'NSObject'
-parameter coder: The NSCoder that will be used for decoding the object.
*/
public convenience required init?(coder: NSCoder) {
self.init()
EVReflection.decodeObjectWithCoder(self, aDecoder: coder, conversionOptions: .DefaultNSCoding)
}
/**
Encode this object using a NSCoder
- parameter aCoder: The NSCoder that will be used for encoding the object
*/
open func encode(with aCoder: NSCoder) {
EVReflection.encodeWithCoder(self , aCoder: aCoder, conversionOptions: .DefaultNSCoding)
}
//MARK - Default implementation of protocol functions that we can override
/**
By default there is no aditional validation. Override this function to add your own class level validation rules
- parameter dict: The dictionary with keys where the initialisation is called with
*/
open func initValidation(_ dict: NSDictionary) {
}
/**
Override this method when you want custom property mapping.
This method is in EVObject and not in extension of NSObject because a functions from extensions cannot be overwritten yet
- returns: Return an array with value pairs of the object property name and json key name.
*/
open func propertyMapping() -> [(keyInObject: String?, keyInResource: String?)] {
return []
}
/**
Override this method when you want custom property value conversion
This method is in EVObject and not in extension of NSObject because a functions from extensions cannot be overwritten yet
- returns: Returns an array where each item is a combination of the folowing 3 values: A string for the property name where the custom conversion is for, a setter function and a getter function.
*/
open func propertyConverters() -> [(key: String, decodeConverter: ((Any?)->()), encodeConverter: (() -> Any?))] {
return []
}
/**
You can add general value decoding to an object when you implement this function. You can for instance use it to base64 decode, url decode, html decode, unicode, etc.
- parameter value: The value that we will be decoded
- parameter key: The key for the value
- returns: The decoded value
*/
open func decodePropertyValue(value: Any, key: String) -> Any? {
return value
}
/**
You can add general value encoding to an object when you implement this function. You can for instance use it to base64 encode, url encode, html encode, unicode, etc.
- parameter value: The value that we will be encoded
- parameter key: The key for the value
- returns: The encoded value.
*/
open func encodePropertyValue(value: Any, key: String) -> Any {
return value
}
/**
This is a general functon where you can filter for specific values (like nil or empty string) when creating a dictionary
- parameter value: The value that we will test
- parameter key: The key for the value
- returns: True if the value needs to be ignored.
*/
open func skipPropertyValue(_ value: Any, key: String) -> Bool {
return false
}
/**
When a property is declared as a base type for multiple inherited classes, then this function will let you pick the right specific type based on the suplied dictionary.
- parameter dict: The dictionary for the specific type
- returns: The specific type
*/
open func getSpecificType(_ dict: NSDictionary) -> EVReflectable? {
return nil
}
/**
Return a custom object for the object
- returns: The custom object (single value, dictionary or array)
*/
open func customConverter() -> AnyObject? {
return nil
}
}
//
// EVReflectable.swift
// EVReflection
//
// Created by Edwin Vermeer on 27/10/2016.
// Copyright © 2016 evict. All rights reserved.
//
import Foundation
// MARK: - Protocol with the overridable functions. All functionality is added to this in the extension below.
public protocol EVReflectable: class, NSObjectProtocol {
/**
By default there is no aditional validation. Override this function to add your own class level validation rules
- parameter dict: The dictionary with keys where the initialisation is called with
*/
func initValidation(_ dict: NSDictionary)
/**
Override this method when you want custom property mapping.
This method is in EVObject and not in extension of NSObject because a functions from extensions cannot be overwritten yet
- returns: Return an array with value pairs of the object property name and json key name.
*/
func propertyMapping() -> [(keyInObject: String?, keyInResource: String?)]
/**
Override this method when you want custom property value conversion
This method is in EVObject and not in extension of NSObject because a functions from extensions cannot be overwritten yet
- returns: Returns an array where each item is a combination of the folowing 3 values: A string for the property name where the custom conversion is for, a setter function and a getter function.
*/
func propertyConverters() -> [(key: String, decodeConverter: ((Any?)->()), encodeConverter: (() -> Any?))]
/**
This is a general functon where you can filter for specific values (like nil or empty string) when creating a dictionary
- parameter value: The value that we will test
- parameter key: The key for the value
- returns: True if the value needs to be ignored.
*/
func skipPropertyValue(_ value: Any, key: String) -> Bool
/**
You can add general value decoding to an object when you implement this function. You can for instance use it to base64 decode, url decode, html decode, unicode, etc.
- parameter value: The value that we will be decoded
- parameter key: The key for the value
- returns: The decoded value
*/
func decodePropertyValue(value: Any, key: String) -> Any?
/**
You can add general value encoding to an object when you implement this function. You can for instance use it to base64 encode, url encode, html encode, unicode, etc.
- parameter value: The value that we will be encoded
- parameter key: The key for the value
- returns: The encoded value.
*/
func encodePropertyValue(value: Any, key: String) -> Any
/**
Get the type of this object.
- parameter dict: The dictionary for the specific type
- returns: The specific type
*/
func getType(_ dict: NSDictionary) -> EVReflectable
/**
When a property is declared as a base type for multiple inherited classes, then this function will let you pick the right specific type based on the suplied dictionary.
- parameter dict: The dictionary for the specific type
- returns: The specific type
*/
func getSpecificType(_ dict: NSDictionary) -> EVReflectable?
/**
Return a custom object for the object
- returns: The custom object that will be parsed (single value, dictionary or array)
*/
func customConverter() -> AnyObject?
/*
/**
Declaration for Equatable ==
- parameter lhs: The object at the left side of the ==
- parameter rhs: The object at the right side of the ==
- returns: True if the objects are the same, otherwise false.
*/
static func ==(lhs: EVReflectable, rhs: EVReflectable) -> Bool
/**
Delclaration for Equatable !=
- parameter lhs: The object at the left side of the ==
- parameter rhs: The object at the right side of the ==
- returns: False if the objects are the the same, otherwise true.
*/
static func !=(lhs: EVReflectable, rhs: EVReflectable) -> Bool
*/
/**
Protocol container property for the reflection statusses
*/
var evReflectionStatuses: [(DeserializationStatus, String)] { get set }
}
// MARK: - extending EVReflectable with the initialisation functions (which need NSObject)
var EVReflectableStatusesObjectKey = "EVReflectableStatuses"
extension EVReflectable where Self: NSObject {
/**
Trick for storing a property in a protocol only
*/
public var evReflectionStatuses: [(DeserializationStatus, String)] {
get {
var statuses: [(DeserializationStatus, String)]? = objc_getAssociatedObject(self, &EVReflectableStatusesObjectKey) as? [(DeserializationStatus, String)]
if let statuses = statuses {
return statuses
} else {
statuses = [(DeserializationStatus, String)]()
objc_setAssociatedObject(self, &EVReflectableStatusesObjectKey, statuses, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN)
return statuses!
}
}
set {
objc_setAssociatedObject(self, &EVReflectableStatusesObjectKey, newValue, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN)
}
}
/**
init for creating an object whith the property values of a dictionary.
- parameter dictionary: The dictionary that will be used to create this object
- parameter conversionOptions: Option set for the various conversion options.
*/
public init(dictionary: NSDictionary, conversionOptions: ConversionOptions = .DefaultDeserialize, forKeyPath: String? = nil) {
self.init()
if let v = self as? EVCustomReflectable {
let _ = v.constructWith(value: dictionary)
} else {
EVReflection.setPropertiesfromDictionary(dictionary, anyObject: self, conversionOptions: conversionOptions, forKeyPath: forKeyPath)
}
}
/**
init for creating an object whith the contents of a json string.
- parameter json: The json string that will be used to create this object
- parameter conversionOptions: Option set for the various conversion options.
*/
public init(json: String?, conversionOptions: ConversionOptions = .DefaultDeserialize, forKeyPath: String? = nil) {
self.init()
let dictionary = EVReflection.dictionaryFromJson(json)
if let v = self as? EVCustomReflectable {
let _ = v.constructWith(value: dictionary)
} else {
EVReflection.setPropertiesfromDictionary(dictionary, anyObject: self, conversionOptions: conversionOptions, forKeyPath: forKeyPath)
}
}
/**
init for creating an object whith the property values of json Data.
- parameter dictionary: The dictionary that will be used to create this object
- parameter conversionOptions: Option set for the various conversion options.
*/
public init(data: Data, conversionOptions: ConversionOptions = .DefaultDeserialize, forKeyPath: String? = nil) {
self.init()
let dictionary: NSDictionary = (((try! JSONSerialization.jsonObject(with: data, options: []) as? NSDictionary)) ?? NSDictionary())!
if let v = self as? EVCustomReflectable {
let _ = v.constructWith(value: dictionary)
} else {
EVReflection.setPropertiesfromDictionary(dictionary, anyObject: self, conversionOptions: conversionOptions, forKeyPath: forKeyPath)
}
}
/**
Initialize this object from an archived file from the temp directory
- parameter fileNameInTemp: The filename
- parameter conversionOptions: Option set for the various conversion options.
*/
public init(fileNameInTemp: String, conversionOptions: ConversionOptions = .DefaultNSCoding) {
self.init()
let filePath = (NSTemporaryDirectory() as NSString).appendingPathComponent(fileNameInTemp)
if let temp = NSKeyedUnarchiver.unarchiveObject(withFile: filePath) as? EVReflectable {
if let v = self as? EVCustomReflectable {
let dictionary = temp.toDictionary(conversionOptions)
let _ = v.constructWith(value: dictionary)
} else {
EVReflection.setPropertiesfromDictionary( temp.toDictionary(conversionOptions), anyObject: self, conversionOptions: conversionOptions)
}
}
}
/**
Initialize this object from an archived file from the documents directory
- parameter fileNameInDocuments: The filename
- parameter conversionOptions: Option set for the various conversion options.
*/
public init(fileNameInDocuments: String, conversionOptions: ConversionOptions = .DefaultNSCoding) {
self.init()
let filePath = (NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0] as NSString).appendingPathComponent(fileNameInDocuments)
if let temp = NSKeyedUnarchiver.unarchiveObject(withFile: filePath) as? EVReflectable {
if let v = self as? EVCustomReflectable {
let dictionary = temp.toDictionary(conversionOptions)
let _ = v.constructWith(value: dictionary)
} else {
EVReflection.setPropertiesfromDictionary( temp.toDictionary(conversionOptions), anyObject: self, conversionOptions: conversionOptions)
}
}
}
/**
init for creating an object whith the property values of an other object.
- parameter usingValuesFrom: The object of whicht the values will be used to create this object
- parameter conversionOptions: Option set for the various conversion options.
*/
public init(usingValuesFrom: EVReflectable, conversionOptions: ConversionOptions = .None) {
self.init()
let dict = usingValuesFrom.toDictionary()
if let v = self as? EVCustomReflectable {
let _ = v.constructWith(value: dict)
} else {
EVReflection.setPropertiesfromDictionary(dict, anyObject: self, conversionOptions: conversionOptions)
}
}
/**
Returns the hashvalue of this object
- returns: The hashvalue of this object
*/
public var hashValue: Int {
get {
return Int(EVReflection.hashValue(self))
}
}
/**
Function for returning the hash for the NSObject based functionality
- returns: The hashvalue of this object
*/
public var hash: Int {
get {
return self.hashValue
}
}
}
// MARK: - extending EVReflectable with most of EVReflection functionality
extension EVReflectable {
/**
Implementation for Equatable ==
- parameter lhs: The object at the left side of the ==
- parameter rhs: The object at the right side of the ==
- returns: True if the objects are the same, otherwise false.
*/
static public func == (lhs: EVReflectable, rhs: EVReflectable) -> Bool {
if let lhso = lhs as? NSObject, let rhso = rhs as? NSObject {
return EVReflection.areEqual(lhso, rhs: rhso)
}
return lhs.isEqual(rhs)
}
/**
Implementation for Equatable !=
- parameter lhs: The object at the left side of the ==
- parameter rhs: The object at the right side of the ==
- returns: False if the objects are the the same, otherwise true.
*/
static public func != (lhs: EVReflectable, rhs: EVReflectable) -> Bool {
if let lhso = lhs as? NSObject, let rhso = rhs as? NSObject {
return !EVReflection.areEqual(lhso, rhs: rhso)
}
return !lhs.isEqual(rhs)
}
// MARK: - extending the base implementation for the overridable functions
/**
By default there is no aditional validation. Override this function to add your own class level validation rules
- parameter dict: The dictionary with keys where the initialisation is called with
*/
public func initValidation(_ dict: NSDictionary) {
}
/**
Override this method when you want custom property mapping.
This method is in EVObject and not in extension of NSObject because a functions from extensions cannot be overwritten yet
- returns: Return an array with value pairs of the object property name and json key name.
*/
public func propertyMapping() -> [(keyInObject: String?, keyInResource: String?)] {
return []
}
/**
Override this method when you want custom property value conversion
This method is in EVObject and not in extension of NSObject because a functions from extensions cannot be overwritten yet
- returns: Returns an array where each item is a combination of the folowing 3 values: A string for the property name where the custom conversion is for, a setter function and a getter function.
*/
public func propertyConverters() -> [(key: String, decodeConverter: ((Any?)->()), encodeConverter: (() -> Any?))] {
return []
}
/**
This is a general functon where you can filter for specific values (like nil or empty string) when creating a dictionary
- parameter value: The value that we will test
- parameter key: The key for the value
- returns: True if the value needs to be ignored.
*/
public func skipPropertyValue(_ value: Any, key: String) -> Bool {
return false
}
/**
You can add general value decoding to an object when you implement this function. You can for instance use it to base64 decode, url decode, html decode, unicode, etc.
- parameter value: The value that we will be decoded
- parameter key: The key for the value
- returns: The decoded value
*/
public func decodePropertyValue(value: Any, key: String) -> Any? {
return value
}
/**
You can add general value encoding to an object when you implement this function. You can for instance use it to base64 encode, url encode, html encode, unicode, etc.
- parameter value: The value that we will be encoded
- parameter key: The key for the value
- returns: The encoded value.
*/
public func encodePropertyValue(value: Any, key: String) -> Any {
return value
}
/**
Return a custom object for the object
- returns: The custom object that will be parsed (single value, dictionary or array)
*/
public func customConverter() -> AnyObject? {
return nil
}
/**
Get the type of this object
- parameter dict: The dictionary for the specific type
- returns: The specific type
*/
public func getType(_ dict: NSDictionary) -> EVReflectable {
return self
}
/**
When a property is declared as a base type for multiple inherited classes, then this function will let you pick the right specific type based on the suplied dictionary.
- parameter dict: The dictionary for the specific type
- returns: The specific type
*/
public func getSpecificType(_ dict: NSDictionary) -> EVReflectable? {
return nil
}
// MARK: - extension methods
/**
Save this object to a file in the temp directory
- parameter fileName: The filename
- returns: Nothing
*/
@discardableResult
public func saveToTemp(_ fileName: String) -> Bool {
let filePath = (NSTemporaryDirectory() as NSString).appendingPathComponent(fileName)
return NSKeyedArchiver.archiveRootObject(self, toFile: filePath)
}
#if os(tvOS)
// Save to documents folder is not supported on tvOS
#else
/**
Save this object to a file in the documents directory
- parameter fileName: The filename
- returns: true if successfull
*/
@discardableResult
public func saveToDocuments(_ fileName: String) -> Bool {
let filePath = (NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0] as NSString).appendingPathComponent(fileName)
return NSKeyedArchiver.archiveRootObject(self, toFile: filePath)
}
#endif
/**
Returns the dictionary representation of this object.
- parameter conversionOptions: Option set for the various conversion options.
- returns: The dictionary
*/
public func toDictionary(_ conversionOptions: ConversionOptions = .DefaultSerialize) -> NSDictionary {
if let obj = self as? NSObject {
let (reflected, _) = EVReflection.toDictionary(obj, conversionOptions: conversionOptions)
return reflected
}
evPrint(.ShouldExtendNSObject, "ERROR: You should only extend object with EVReflectable that are derived from NSObject!")
return NSDictionary()
}
/**
Convert this object to a json string
- parameter conversionOptions: Option set for the various conversion options.
- returns: The json string
*/
public func toJsonString(_ conversionOptions: ConversionOptions = .DefaultSerialize, prettyPrinted: Bool = false) -> String {
let data = self.toJsonData(conversionOptions, prettyPrinted: prettyPrinted)
return String(data: data, encoding: .utf8) ?? "{}"
}
/**
Convert this object to a json Data
- parameter conversionOptions: Option set for the various conversion options.
- returns: The json Data
*/
public func toJsonData(_ conversionOptions: ConversionOptions = .DefaultSerialize, prettyPrinted: Bool = false) -> Data {
var dict: NSDictionary
// Custom or standard toDictionary
if let v = self as? EVCustomReflectable {
dict = v.toCodableValue() as? NSDictionary ?? NSDictionary()
} else {
dict = self.toDictionary(conversionOptions)
}
if let v = self as? NSObject {
dict = EVReflection.convertDictionaryForJsonSerialization(dict, theObject: v)
}
do {
if prettyPrinted {
return try JSONSerialization.data(withJSONObject: dict, options: .prettyPrinted)
}
return try JSONSerialization.data(withJSONObject: dict, options: [])
} catch { }
return Data()
}
/**
method for instantiating an array from a json string.
- parameter json: The json string
- parameter conversionOptions: Option set for the various conversion options.
- returns: An array of objects
*/
public static func arrayFromJson<T>(_ json: String?, conversionOptions: ConversionOptions = .DefaultDeserialize) -> [T] where T:NSObject {
return EVReflection.arrayFromJson(type: T(), json: json, conversionOptions: conversionOptions)
}
/**
Auto map an opbject to an object of an other type.
Properties with the same name will be mapped automattically.
Automattic cammpelCase, PascalCase, snake_case conversion
Supports propperty mapping and conversion when using EVReflectable as a protocol
- parameter conversionOptions: Option set for the various conversion options.
- returns: The targe object with the mapped values
*/
public func mapObjectTo<T>(_ conversionOptions: ConversionOptions = .DefaultDeserialize) -> T where T:NSObject {
let nsobjectype: NSObject.Type = T.self as NSObject.Type
let nsobject: NSObject = nsobjectype.init()
let dict = self.toDictionary()
let result = EVReflection.setPropertiesfromDictionary(dict, anyObject: nsobject, conversionOptions: conversionOptions)
return result as? T ?? T()
}
/**
Get the type for a given property name or `nil` if there aren't any properties matching said name.
- parameter propertyName: The property name
- returns: The type for the property
*/
public func typeForKey(_ propertyName: String) -> Any.Type? {
let mirror = Mirror(reflecting: self)
return typeForKey(propertyName, mirror: mirror)
}
/**
get the type of a property
- parameter propertyName: The property key
- parameter mirror: The mirror of this object
- returns: The type of the property
*/
fileprivate func typeForKey(_ propertyName: String, mirror: Mirror) -> Any.Type? {
for (label, value) in mirror.children {
if propertyName == label {
return Mirror(reflecting: value).subjectType
}
}
guard let superclassMirror = mirror.superclassMirror else {
return nil
}
return typeForKey(propertyName, mirror: superclassMirror)
}
/**
Convert a Swift dictionary to a NSDictionary.
- parameter key: Key of the property that is the dictionary. Can be used when overriding this function
- parameter dict: The Swift dictionary
- returns: The dictionary converted to a NSDictionary
*/
public func convertDictionary(_ key: String, dict: Any) -> NSDictionary {
let returnDict = NSMutableDictionary()
for (key, value) in dict as? NSDictionary ?? NSDictionary() {
returnDict[key as? String ?? ""] = value
}
return returnDict
}
// MARK: - extending serialization status functions
/**
Validation function that you will probably call from the initValidation function. This function will make sure
the passed on keys are not in the dictionary used for initialisation.
The result of this validation is stored in evReflectionStatus.
- parameter keys: The fields that may not be in the dictionary (like an error key)
- parameter dict: The dictionary that is passed on from the initValidation function
*/
public func initMayNotContainKeys(_ keys: [String], dict: NSDictionary) {
for key in keys {
if dict[key] != nil {
addStatusMessage(.IncorrectKey, message: "Invalid key: \(key)")
}
}
}
/**
Validation function that you will probably call from the initValidation function. This function will make sure
the passed on keys are in the dictionary used for initialisation.
The result of this validation is stored in evReflectionStatus.
- parameter keys: The fields that may not be in the dictionary (like an error key)
- parameter dict: The dictionary that is passed on from the initValidation function
*/
public func initMustContainKeys(_ keys: [String], dict: NSDictionary) {
for key in keys {
if dict[key] == nil {
addStatusMessage(.MissingKey, message: "Missing key: \(key)")
}
}
}
/**
Return a merged status out of the status array
- returns: the deserialization status for the object
*/
public func evReflectionStatus() -> DeserializationStatus {
var status: DeserializationStatus = .None
for (s, _) in self.evReflectionStatuses {
status = [status, s]
}
return status
}
/**
function for adding a new status message to the evReflectionStatus array
- parameter type: A string to specify the message type
- parameter message: The message for the status.
*/
public func addStatusMessage(_ type: DeserializationStatus, message: String) {
self.evReflectionStatuses.append((type, message))
}
}
//
// EVReflection.swift
//
// Created by Edwin Vermeer on 28-09-14.
// Copyright (c) 2014 EVICT BV. All rights reserved.
//
import Foundation
/**
Reflection methods
*/
final public class EVReflection {
// MARK: - From and to Dictrionary parsing
/**
Create an object from a dictionary
- parameter dictionary: The dictionary that will be converted to an object
- parameter anyobjectTypeString: The string representation of the object type that will be created
- parameter conversionOptions: Option set for the various conversion options.
- returns: The object that is created from the dictionary
*/
public class func fromDictionary(_ dictionary: NSDictionary, anyobjectTypeString: String, conversionOptions: ConversionOptions = .DefaultDeserialize) -> NSObject? {
if var nsobject = swiftClassFromString(anyobjectTypeString) {
if let evResult = nsobject as? EVReflectable {
if let type = evResult.getType(dictionary) as? NSObject {
nsobject = type
}
if let specific = evResult.getSpecificType(dictionary) as? NSObject {
nsobject = specific
} else if let evResult = nsobject as? EVGenericsKVC {
nsobject = evResult.getGenericType()
}
}
nsobject = setPropertiesfromDictionary(dictionary, anyObject: nsobject, conversionOptions: conversionOptions)
return nsobject
}
return nil
}
/**
Set object properties from a dictionary
- parameter dictionary: The dictionary that will be converted to an object
- parameter anyObject: The object where the properties will be set
- parameter conversionOptions: Option set for the various conversion options.
- returns: The object that is created from the dictionary
*/
@discardableResult
public class func setPropertiesfromDictionary<T>(_ dictionary: NSDictionary, anyObject: T, conversionOptions: ConversionOptions = .DefaultDeserialize, forKeyPath: String? = nil) -> T where T: NSObject {
guard let dict = ((forKeyPath == nil) ? dictionary : dictionary.value(forKeyPath: forKeyPath!) as? NSDictionary) else {
evPrint(.UnknownKeypath, "ERROR: The forKeyPath '\(forKeyPath ?? "")' did not return a dictionary")
return anyObject
}
(anyObject as? EVReflectable)?.initValidation(dict)
let (keyMapping, _, types) = getKeyMapping(anyObject, dictionary: dict, conversionOptions: .None)
for (k, v) in dict {
let keyInObject: String? = (keyMapping.first { $0.keyInResource == k as? String })?.keyInObject
if keyInObject != nil {
let original: Any? = getValue(anyObject, key: keyInObject!)
let dictKey: String = cleanupKey(anyObject, key: k as? String ?? "", tryMatch: types) ?? ""
let valid : Bool
let dictValue : Any?
if conversionOptions.contains(.PropertyConverter) && (anyObject as? EVReflectable)?.propertyConverters().filter({$0.key == keyInObject}).first != nil {
valid = false
dictValue = nil
} else {
(dictValue, valid) = dictionaryAndArrayConversion(anyObject, key: keyInObject!, fieldType: types[dictKey] as? String ?? types[keyInObject!] as? String, original: original, theDictValue: v as Any?, conversionOptions: conversionOptions)
}
if var value: Any = valid ? dictValue : (v as Any) {
if let type: String = types[k as! String] as? String {
let t: AnyClass? = swiftClassTypeFromString(type)
if let c = t as? EVCustomReflectable.Type {
if let v = c.constructWith(value: value) {
value = v
}
}
}
setObjectValue(anyObject, key: keyInObject!, theValue: value, typeInObject: types[keyInObject!] as? String, valid: valid, conversionOptions: conversionOptions)
}
}
}
return anyObject
}
public class func getValue(_ fromObject: NSObject, key: String) -> Any? {
return (Mirror(reflecting: fromObject).children.filter({$0.0 == key}).first)?.value
}
/**
Based on an object and a dictionary create a keymapping plus a dictionary of properties plus a dictionary of types
- parameter anyObject: the object for the mapping
- parameter dictionary: the dictionary that has to be mapped
- parameter conversionOptions: Option set for the various conversion options.
- returns: The mapping, keys and values of all properties to items in a dictionary
*/
fileprivate static func getKeyMapping<T>(_ anyObject: T, dictionary: NSDictionary, conversionOptions: ConversionOptions = .DefaultDeserialize) -> (keyMapping: [(keyInObject: String?, keyInResource: String?)], properties: NSDictionary, types: NSDictionary) where T: NSObject {
let (properties, types) = toDictionary(anyObject, conversionOptions: conversionOptions, isCachable: true)
var keyMapping: [(keyInObject: String?, keyInResource: String?)] = []
if let reflectable = anyObject as? EVReflectable {
keyMapping = reflectable.propertyMapping()
}
// Add the mapping from the keys in the object.
for (objectKey, _) in properties {
if (keyMapping.first { $0.keyInObject == objectKey as? String }) == nil {
if let dictKey = cleanupKey(anyObject, key: objectKey as? String ?? "", tryMatch: dictionary) {
keyMapping.append((objectKey as? String, dictKey))
} else {
keyMapping.append((objectKey as? String, objectKey as? String))
}
}
}
// Also add the unknown mapping, these have to be handled in setValue forUndefinedKey
for item in dictionary {
var isAdded = false
if (keyMapping.first { $0.keyInResource == (item.key as? String ?? "") }) == nil {
if let reflectable = anyObject as? EVReflectable {
if let mapping = reflectable.propertyMapping().filter({$0.keyInResource == item.key as? String}).first {
keyMapping.append(mapping)
isAdded = true
}
}
if !isAdded {
keyMapping.append((item.key as? String, item.key as? String))
}
}
}
return (keyMapping, properties, types)
}
fileprivate static let properiesCache = NSCache<NSString, NSDictionary>()
fileprivate static let typesCache = NSCache<NSString, NSDictionary>()
/**
Convert an object to a dictionary while cleaning up the keys
- parameter theObject: The object that will be converted to a dictionary
- parameter conversionOptions: Option set for the various conversion options.
- returns: The dictionary that is created from theObject plus a dictionary of propery types.
*/
public class func toDictionary(_ theObject: NSObject, conversionOptions: ConversionOptions = .DefaultSerialize, isCachable: Bool = false, parents: [NSObject] = []) -> (NSDictionary, NSDictionary) {
var pdict: NSDictionary?
var tdict: NSDictionary?
var i = 1
for parent in parents {
if parent === theObject {
pdict = NSMutableDictionary()
pdict!.setValue("\(i)", forKey: "_EVReflection_parent_")
tdict = NSMutableDictionary()
tdict!.setValue("NSString", forKey: "_EVReflection_parent_")
return (pdict!, tdict!)
}
i = i + 1
}
var theParents = parents
theParents.append(theObject)
var p: NSDictionary = NSDictionary()
var t: NSDictionary = NSDictionary()
let key: NSString = "\(swiftStringFromClass(theObject)).\(conversionOptions.rawValue)" as NSString
if isCachable, let cachedVersionProperty = properiesCache.object(forKey: key), let cachedVersionTypes = typesCache.object(forKey: key) {
p = cachedVersionProperty
t = cachedVersionTypes
} else {
let reflected = Mirror(reflecting: theObject)
var (properties, types) = reflectedSub(theObject, reflected: reflected, conversionOptions: conversionOptions, isCachable: isCachable, parents: theParents)
if conversionOptions.contains(.KeyCleanup) {
(properties, types) = cleanupKeysAndValues(theObject, properties:properties, types:types)
}
p = properties
t = types
if isCachable {
properiesCache.setObject(p, forKey: key)
typesCache.setObject(t, forKey: key)
}
}
return (p, t)
}
// MARK: - From and to JSON parsing
/**
Return a dictionary representation for the json string
- parameter json: The json string that will be converted
- returns: The dictionary representation of the json
*/
public class func dictionaryFromJson(_ json: String?) -> NSDictionary {
let result = NSMutableDictionary()
if json == nil {
evPrint(.IsInvalidJson, "ERROR: nil is not valid json!")
} else if let jsonData = json!.data(using: String.Encoding.utf8) {
do {
if let jsonDic = try JSONSerialization.jsonObject(with: jsonData, options: JSONSerialization.ReadingOptions.mutableContainers) as? NSDictionary {
return jsonDic
}
} catch {
evPrint(.IsInvalidJson, "ERROR: Invalid json! \(error.localizedDescription)")
}
}
return result
}
/**
Return an array of dictionaries as the representation for the json string
- parameter json: The json string that will be converted
- returns: The dictionary representation of the json
*/
public class func dictionaryArrayFromJson(_ json: String?) -> [NSDictionary] {
let result = [NSDictionary]()
if json == nil {
evPrint(.IsInvalidJson, "ERROR: nil is not valid json!")
} else if let jsonData = json!.data(using: String.Encoding.utf8) {
do {
if let jsonDic = try JSONSerialization.jsonObject(with: jsonData, options: JSONSerialization.ReadingOptions.mutableContainers) as? [NSDictionary] {
return jsonDic
}
} catch {
evPrint(.IsInvalidJson, "ERROR: Invalid json! \(error.localizedDescription)")
}
}
return result
}
/**
Return an array representation for the json string
- parameter type: An instance of the type where the array will be created of.
- parameter json: The json string that will be converted
- parameter conversionOptions: Option set for the various conversion options.
- returns: The array of dictionaries representation of the json
*/
public class func arrayFromData<T>(_ theObject: NSObject? = nil, type: T, data: Data?, conversionOptions: ConversionOptions = .DefaultDeserialize, forKeyPath: String? = nil) -> [T] {
var result = [T]()
if data == nil {
evPrint(.IsInvalidJson, "ERROR: json data is nil!")
return result
}
do {
var serialized = try JSONSerialization.jsonObject(with: data!, options: JSONSerialization.ReadingOptions.mutableContainers)
if serialized is NSDictionary {
if forKeyPath == nil {
evPrint(.IsInvalidJson, "ERROR: The root of the json is an object and not an array. Specify a forKeyPath to get an item as an array")
return result
} else {
serialized = (serialized as! NSDictionary).value(forKeyPath: forKeyPath!) as? [NSDictionary] ?? []
}
}
if let jsonDic: [Dictionary<String, AnyObject>] = serialized as? [Dictionary<String, AnyObject>] {
let nsobjectype: NSObject.Type? = T.self as? NSObject.Type
if nsobjectype == nil {
evPrint(.ShouldExtendNSObject, "ERROR: EVReflection can only be used with types with NSObject as it's minimal base type")
return result
}
result = jsonDic.map({
let nsobject: NSObject = nsobjectype!.init()
return (setPropertiesfromDictionary($0 as NSDictionary, anyObject: nsobject, conversionOptions: conversionOptions) as? T)!
})
}
} catch {
evPrint(.IsInvalidJson, "ERROR: Invalid json! \(error.localizedDescription)")
}
return result
}
/**
Return an array representation for the json string
- parameter type: An instance of the type where the array will be created of.
- parameter json: The json string that will be converted
- parameter conversionOptions: Option set for the various conversion options.
- returns: The array of dictionaries representation of the json
*/
public class func arrayFromJson<T>(type: T, json: String?, conversionOptions: ConversionOptions = .DefaultDeserialize, forKeyPath: String? = nil) -> [T] {
let result = [T]()
if json == nil {
evPrint(.IsInvalidJson, "ERROR: nil is not valid json!")
return result
}
guard let data = json!.data(using: String.Encoding.utf8) else {
evPrint(.IsInvalidJson, "ERROR: Could not get Data from json string using utf8 encoding")
return result
}
return arrayFromData(type: type, data: data, conversionOptions: conversionOptions, forKeyPath: forKeyPath)
}
/**
Return a Json string representation of this object
- parameter theObject: The object that will be loged
- parameter conversionOptions: Option set for the various conversion options.
- returns: The string representation of the object
*/
public class func toJsonString(_ theObject: NSObject, conversionOptions: ConversionOptions = .DefaultSerialize, prettyPrinted: Bool = false) -> String {
let data = toJsonData(theObject, conversionOptions: conversionOptions, prettyPrinted: prettyPrinted)
return String(data: data, encoding: .utf8) ?? ""
}
/**
Return a Json Data representation of this object
- parameter theObject: The object that will be loged
- parameter conversionOptions: Option set for the various conversion options.
- returns: The Data representation of the object
*/
public class func toJsonData(_ theObject: NSObject, conversionOptions: ConversionOptions = .DefaultSerialize, prettyPrinted: Bool = false) -> Data {
var dict: NSDictionary
// Custom or standard toDictionary
if let v = theObject as? EVCustomReflectable {
dict = v.toCodableValue() as? NSDictionary ?? NSDictionary()
} else {
let (dictionary, _) = EVReflection.toDictionary(theObject, conversionOptions: conversionOptions)
dict = dictionary
}
dict = convertDictionaryForJsonSerialization(dict, theObject: theObject)
do {
if prettyPrinted {
return try JSONSerialization.data(withJSONObject: dict, options: .prettyPrinted)
}
return try JSONSerialization.data(withJSONObject: dict, options: [])
} catch { }
return Data()
}
// MARK: - Adding functionality to objects
/**
Dump the content of this object to the output
- parameter theObject: The object that will be loged
*/
public class func logObject(_ theObject: EVReflectable, prettyPrinted: Bool = true) {
NSLog(description(theObject, prettyPrinted: prettyPrinted))
}
/**
Return a string representation of this object
- parameter theObject: The object that will be loged
- parameter conversionOptions: Option set for the various conversion options.
- returns: The string representation of the object
*/
public class func description(_ theObject: EVReflectable, conversionOptions: ConversionOptions = .DefaultSerialize, prettyPrinted: Bool = true) -> String {
if let obj = theObject as? NSObject {
return "\(swiftStringFromClass(obj)) = \(theObject.toJsonString(prettyPrinted: prettyPrinted))"
}
evPrint(.ShouldExtendNSObject, "ERROR: \(String(reflecting: theObject)) should have NSObject as it's base type.")
return "\(String(reflecting: theObject))"
}
/**
Create a hashvalue for the object
- parameter theObject: The object for what you want a hashvalue
- returns: the hashvalue for the object
*/
public class func hashValue(_ theObject: NSObject) -> Int {
let (hasKeys, _) = toDictionary(theObject, conversionOptions: .DefaultComparing)
return Int(hasKeys.map {$1}.reduce(0) {(31 &* $0) &+ ($1 as AnyObject).hash})
}
/**
Encode any object
- parameter theObject: The object that we want to encode.
- parameter aCoder: The NSCoder that will be used for encoding the object.
- parameter conversionOptions: Option set for the various conversion options.
*/
public class func encodeWithCoder(_ theObject: NSObject, aCoder: NSCoder, conversionOptions: ConversionOptions = .DefaultNSCoding) {
let (hasKeys, _) = toDictionary(theObject, conversionOptions: conversionOptions)
for (key, value) in hasKeys {
aCoder.encode(value, forKey: key as? String ?? "")
}
}
/**
Decode any object
- parameter theObject: The object that we want to decode.
- parameter aDecoder: The NSCoder that will be used for decoding the object.
- parameter conversionOptions: Option set for the various conversion options.
*/
public class func decodeObjectWithCoder(_ theObject: NSObject, aDecoder: NSCoder, conversionOptions: ConversionOptions = .DefaultNSCoding) {
let (hasKeys, _) = toDictionary(theObject, conversionOptions: conversionOptions, isCachable: true)
let dict = NSMutableDictionary()
for (key, _) in hasKeys {
if aDecoder.containsValue(forKey: (key as? String)!) {
let newValue: AnyObject? = aDecoder.decodeObject(forKey: (key as? String)!) as AnyObject?
if !(newValue is NSNull) {
dict[(key as? String)!] = newValue
}
}
}
EVReflection.setPropertiesfromDictionary(dict, anyObject: theObject, conversionOptions: conversionOptions)
}
/**
Compare all fields of 2 objects
- parameter lhs: The first object for the comparisson
- parameter rhs: The second object for the comparisson
- returns: true if the objects are the same, otherwise false
*/
public class func areEqual(_ lhs: NSObject, rhs: NSObject) -> Bool {
if swiftStringFromClass(lhs) != swiftStringFromClass(rhs) {
return false
}
let (lhsdict, _) = toDictionary(lhs, conversionOptions: .DefaultComparing)
let (rhsdict, _) = toDictionary(rhs, conversionOptions: .DefaultComparing)
return dictionariesAreEqual(lhsdict, rhsdict: rhsdict)
}
/**
Compare 2 dictionaries
- parameter lhsdict: Compare this dictionary
- parameter rhsdict: Compare with this dictionary
- returns: Are the dictionaries equal or not
*/
public class func dictionariesAreEqual(_ lhsdict: NSDictionary, rhsdict: NSDictionary) -> Bool {
for (key, value) in rhsdict {
if let compareTo = lhsdict[(key as? String)!] {
if let dateCompareTo = compareTo as? Date, let dateValue = value as? Date {
let t1 = Int64(dateCompareTo.timeIntervalSince1970)
let t2 = Int64(dateValue.timeIntervalSince1970)
if t1 != t2 {
return false
}
} else if let array = compareTo as? NSArray, let arr = value as? NSArray {
if arr.count != array.count {
return false
}
for (index, arrayValue) in array.enumerated() {
if arrayValue as? NSDictionary != nil {
if !dictionariesAreEqual((arrayValue as? NSDictionary)!, rhsdict: (arr[index] as? NSDictionary)!) {
return false
}
} else {
if !(arrayValue as AnyObject).isEqual(arr[index]) {
return false
}
}
}
} else if !(compareTo as AnyObject).isEqual(value) {
return false
}
}
}
return true
}
// MARK: - Reflection helper functions
/**
Get the app name from the 'Bundle name' and if that's empty, then from the 'Bundle identifier' otherwise we assume it's a EVReflection unit test and use that bundle identifier
- parameter forObject: Pass an object to this method if you know a class from the bundele where you want the name for.
- returns: A cleaned up name of the app.
*/
public class func getCleanAppName(_ forObject: NSObject? = nil) -> String {
// if an object was specified, then always use the bundle name of that class
if forObject != nil {
return nameForBundle(Bundle(for: type(of: forObject!)))
}
// If no object was specified but an identifier was set, then use that identifier.
if EVReflection.bundleIdentifier != nil {
return EVReflection.bundleIdentifier!
}
// use the bundle name from the main bundle, if that's not set use the identifier
return nameForBundle(Bundle.main)
}
/**
Get the app name from the 'Bundle name' and if that's empty, then from the 'Bundle identifier' otherwise we assume it's a EVReflection unit test and use that bundle identifier
- parameter aClass: Pass an AnyClass to this method if you know a class from the bundele where you want the name for.
- returns: A cleaned up name of the app.
*/
public class func getCleanAppName(_ aClass: AnyClass?) -> String {
// if an object was specified, then always use the bundle name of that class
if aClass != nil {
return nameForBundle(Bundle(for: aClass!))
}
// If no object was specified but an identifier was set, then use that identifier.
if EVReflection.bundleIdentifier != nil {
return EVReflection.bundleIdentifier!
}
// use the bundle name from the main bundle, if that's not set use the identifier
return nameForBundle(Bundle.main)
}
/// Variable that can be set using setBundleIdentifier
fileprivate static var bundleIdentifier: String? = nil
/// Variable that can be set using setBundleIdentifiers
fileprivate static var bundleIdentifiers: [String]? = nil
/**
This method can be used in unit tests to force the bundle where classes can be found
- parameter forClass: The class that will be used to find the appName for in which we can find classes by string.
*/
public class func setBundleIdentifier(_ forClass: AnyClass) {
EVReflection.bundleIdentifier = nameForBundle(Bundle(for:forClass))
}
/**
This method can be used in unit tests to force the bundle where classes can be found
- parameter identifier: The identifier that will be used.
*/
public class func setBundleIdentifier(_ identifier: String) {
EVReflection.bundleIdentifier = identifier
}
/**
This method can be used in project where models are split between multiple modules.
- parameter classes: classes that that will be used to find the appName for in which we can find classes by string.
*/
public class func setBundleIdentifiers(_ classes: Array<AnyClass>) {
bundleIdentifiers = []
for aClass in classes {
bundleIdentifiers?.append(nameForBundle(Bundle(for: aClass)))
}
}
/**
This method can be used in project where models are split between multiple modules.
- parameter identifiers: The array of identifiers that will be used.
*/
public class func setBundleIdentifiers(_ identifiers: Array<String>) {
bundleIdentifiers = []
for identifier in identifiers {
bundleIdentifiers?.append(identifier)
}
}
fileprivate static func nameForBundle(_ bundle: Bundle) -> String {
// get the bundle name from what is set in the infoDictionary
var appName = bundle.infoDictionary?[kCFBundleExecutableKey as String] as? String ?? ""
// If it was not set, then use the bundleIdentifier (which is the same as kCFBundleIdentifierKey)
if appName == "" {
appName = bundle.bundleIdentifier ?? ""
appName = appName.split(whereSeparator: {$0 == "."}).map({ String($0) }).last ?? ""
}
// First character may not be a number
if appName.prefix(1) >= "0" && appName.prefix(1) <= "9" {
appName = "_" + String(appName.dropFirst())
}
// Clean up special characters
return appName.components(separatedBy: illegalCharacterSet).joined(separator: "_")
}
/// This dateformatter will be used when a conversion from string to NSDate is required
fileprivate static var dateFormatter: DateFormatter? = nil
/**
This function can be used to force using an alternat dateformatter for converting String to NSDate
- parameter formatter: The new DateFormatter
*/
public class func setDateFormatter(_ formatter: DateFormatter?) {
dateFormatter = formatter
}
/**
This function is used for getting the dateformatter and defaulting to a standard if it's not set
- returns: The dateformatter
*/
fileprivate class func getDateFormatter() -> DateFormatter {
if let formatter = dateFormatter {
return formatter
}
dateFormatter = DateFormatter()
dateFormatter!.locale = Locale(identifier: "en_US_POSIX")
dateFormatter!.timeZone = TimeZone(secondsFromGMT: 0)
dateFormatter!.dateFormat = "yyyy'-'MM'-'dd' 'HH':'mm':'ssZ"
return dateFormatter!
}
/**
Get the swift Class type from a string
- parameter className: The string representation of the class (name of the bundle dot name of the class)
- returns: The Class type
*/
public class func swiftClassTypeFromString(_ className: String) -> AnyClass? {
if let c = NSClassFromString(className) {
return c
}
// The default did not work. try a combi of appname and classname
if className.range(of: ".", options: NSString.CompareOptions.caseInsensitive) == nil {
let appName = getCleanAppName()
if let c = NSClassFromString("\(appName).\(className)") {
return c
}
}
if let bundleIdentifiers = bundleIdentifiers {
for aBundle in bundleIdentifiers {
if let existingClass = NSClassFromString("\(aBundle).\(className)") {
return existingClass
}
}
}
return nil
}
/**
Get the swift Class from a string
- parameter className: The string representation of the class (name of the bundle dot name of the class)
- returns: The Class type
*/
public class func swiftClassFromString(_ className: String) -> NSObject? {
return (swiftClassTypeFromString(className) as? NSObject.Type)?.init()
}
/**
Get the class name as a string from a swift class
- parameter theObject: An object for whitch the string representation of the class will be returned
- returns: The string representation of the class (name of the bundle dot name of the class)
*/
public class func swiftStringFromClass(_ theObject: NSObject) -> String {
return NSStringFromClass(type(of: theObject)).replacingOccurrences(of: getCleanAppName(theObject) + ".", with: "", options: NSString.CompareOptions.caseInsensitive, range: nil)
}
/**
Get the class name as a string from a swift class
- parameter aClass: An AnyClass for whitch the string representation of the class will be returned
- returns: The string representation of the class (name of the bundle dot name of the class)
*/
public class func swiftStringFromClass(_ aClass: AnyClass) -> String {
return NSStringFromClass(aClass).replacingOccurrences(of: getCleanAppName(aClass) + ".", with: "", options: NSString.CompareOptions.caseInsensitive, range: nil)
}
/**
Helper function to convert an Any to AnyObject
- parameter parentObject: Only needs to be set to the object that has this property when the value is from a property that is an array of optional values
- parameter key: Only needs to be set to the name of the property when the value is from a property that is an array of optional values
- parameter anyValue: Something of type Any is converted to a type NSObject
- parameter conversionOptions: Option set for the various conversion options.
- returns: The value where the Any is converted to AnyObject plus the type of that value as a string
*/
public class func valueForAny(_ parentObject: Any? = nil, key: String? = nil, anyValue: Any, conversionOptions: ConversionOptions = .DefaultDeserialize, isCachable: Bool = false, parents: [NSObject] = []) -> (value: AnyObject, type: String, isObject: Bool) {
var theValue = anyValue
var valueType: String = ""
var mi: Mirror = Mirror(reflecting: theValue)
if mi.displayStyle == .optional {
if mi.children.count == 1 {
theValue = mi.children.first!.value
mi = Mirror(reflecting: theValue)
valueType = String(reflecting:type(of: theValue))
} else if mi.children.count == 0 {
valueType = String(reflecting:type(of: theValue))
var subtype: String = String(valueType[(valueType.components(separatedBy: "<") [0] + "<").endIndex...])
subtype = String(subtype[..<subtype.index(before: subtype.endIndex)])
valueType = convertToInternalSwiftRepresentation(type: subtype)
return (NSNull(), valueType, false)
}
}
if mi.displayStyle == .class {
valueType = String(reflecting:type(of: theValue))
} else if mi.displayStyle == .enum {
valueType = String(reflecting:type(of: theValue))
if let value = theValue as? EVRaw {
theValue = value.anyRawValue
} else if let value = theValue as? EVAssociated {
//let (enumValue, enumType, _) = valueForAny(theValue, key: value.associated.label, anyValue: value.associated.value as Any, conversionOptions: conversionOptions, isCachable: isCachable, parents: parents)
valueType = "Array<Any>"
theValue = value.associated.values
} else if valueType.hasPrefix("Swift.ImplicitlyUnwrappedOptional<") { // Implicitly Unwrapped Optionals are actually fancy enums
var subtype: String = String(valueType[(valueType.components(separatedBy: "<") [0] + "<").endIndex...])
subtype = String(subtype[..<subtype.index(before: subtype.endIndex)])
valueType = convertToInternalSwiftRepresentation(type: subtype)
if mi.children.count == 0 {
return (NSNull(), valueType, false)
}
theValue = mi.children.first?.value ?? theValue
let (val, _, _) = valueForAnyDetail(parentObject, key: key, theValue: theValue, valueType: valueType)
return (val, valueType, false)
} else {
theValue = "\(theValue)"
}
} else if mi.displayStyle == .collection {
valueType = String(reflecting: type(of:theValue))
if valueType.hasPrefix("Swift.Array<Swift.Optional<") {
if let arrayConverter = parentObject as? EVArrayConvertable {
let convertedValue = arrayConverter.convertArray(key!, array: theValue)
return (convertedValue, valueType, false)
}
(parentObject as? EVReflectable)?.addStatusMessage(.MissingProtocol, message: "An object with a property of type Array with optional objects should implement the EVArrayConvertable protocol. type = \(valueType) for key \(key ?? "")")
evPrint(.MissingProtocol, "WARNING: An object with a property of type Array with optional objects should implement the EVArrayConvertable protocol. type = \(valueType) for key \(key ?? "")")
return (NSNull(), "NSNull", false)
}
} else if mi.displayStyle == .dictionary {
valueType = String(reflecting: type(of:theValue))
if let dictionaryConverter = parentObject as? EVReflectable {
let convertedValue = dictionaryConverter.convertDictionary(key!, dict: theValue)
return (convertedValue, valueType, false)
}
} else if mi.displayStyle == .set {
valueType = String(reflecting: type(of:theValue))
if valueType.hasPrefix("Swift.Set<") {
if let arrayConverter = parentObject as? EVArrayConvertable {
let convertedValue = arrayConverter.convertArray(key!, array: theValue)
return (convertedValue, valueType, false)
}
(parentObject as? EVReflectable)?.addStatusMessage(.MissingProtocol, message: "An object with a property of type Set should implement the EVArrayConvertable protocol. type = \(valueType) for key \(key ?? "")")
evPrint(.MissingProtocol, "WARNING: An object with a property of type Set should implement the EVArrayConvertable protocol. type = \(valueType) for key \(key ?? "")")
return (NSNull(), "NSNull", false)
}
} else if mi.displayStyle == .struct {
valueType = String(reflecting: type(of:theValue))
if valueType.contains("Dictionary") {
if let dictionaryConverter = parentObject as? EVReflectable {
let convertedValue = dictionaryConverter.convertDictionary(key!, dict: theValue)
return (convertedValue, valueType, false)
}
} else if valueType == "Foundation.Date" {
return (theValue as! NSDate, "NSDate", false)
} else if valueType == "Foundation.Data" {
return (theValue as! NSData, "NSData", false)
}
let structAsDict = convertStructureToDictionary(theValue, conversionOptions: conversionOptions, isCachable: isCachable, parents: parents)
return (structAsDict, "Struct", false)
} else {
valueType = String(reflecting: type(of:theValue))
}
valueType = convertToInternalSwiftRepresentation(type: valueType)
return valueForAnyDetail(parentObject, key: key, theValue: theValue, valueType: valueType)
}
public class func convertToInternalSwiftRepresentation(type: String) -> String {
if type.split(separator: "<").count > 1 {
// Remove the Array or Set prefix
let prefix = type.split(separator: "<") [0] + "<"
var subtype = String(type[prefix.endIndex...])
subtype = String(subtype[..<subtype.index(before: subtype.endIndex)])
return prefix + convertToInternalSwiftRepresentation(type: subtype) + ">"
}
if type.contains(".") {
var parts = type.components(separatedBy: ".")
if parts.count == 2 {
return parts[1]
}
let c = String(repeating:"C", count: parts.count - 1)
var rv = "_Tt\(c)\(parts[0].count)\(parts[0])"
parts.remove(at: 0)
for part in parts {
rv = "\(rv)\(part.count)\(part)"
}
return rv
}
return type
}
public class func valueForAnyDetail(_ parentObject: Any? = nil, key: String? = nil, theValue: Any, valueType: String) -> (value: AnyObject, type: String, isObject: Bool) {
if theValue is NSNumber {
return (theValue as! NSNumber, "NSNumber", false)
}
if theValue is Int64 {
return (NSNumber(value: theValue as! Int64), "NSNumber", false)
}
if theValue is UInt64 {
return (NSNumber(value: theValue as! UInt64), "NSNumber", false)
}
if theValue is Int32 {
return (NSNumber(value: theValue as! Int32), "NSNumber", false)
}
if theValue is UInt32 {
return (NSNumber(value: theValue as! UInt32), "NSNumber", false)
}
if theValue is Int16 {
return (NSNumber(value: theValue as! Int16), "NSNumber", false)
}
if theValue is UInt16 {
return (NSNumber(value: theValue as! UInt16), "NSNumber", false)
}
if theValue is Int8 {
return (NSNumber(value: theValue as! Int8), "NSNumber", false)
}
if theValue is UInt8 {
return (NSNumber(value: theValue as! UInt8), "NSNumber", false)
}
if theValue is NSString {
return (theValue as! NSString, "NSString", false)
}
if theValue is Date {
return (theValue as AnyObject, "NSDate", false)
}
if theValue is UUID {
return ((theValue as! UUID).uuidString as AnyObject, "NSString", false)
}
if theValue is Array<Any> {
return (theValue as AnyObject, valueType, false)
}
if theValue is EVCustomReflectable {
let value: AnyObject = (theValue as! EVCustomReflectable).toCodableValue() as AnyObject
return (value, valueType, false)
}
if theValue is EVReflectable && theValue is NSObject {
if valueType.contains("<") {
return (theValue as! EVReflectable, swiftStringFromClass(theValue as! NSObject), true)
}
return (theValue as! EVReflectable, valueType, true)
}
if theValue is NSObject {
if valueType.contains("<") {
return (theValue as! NSObject, swiftStringFromClass(theValue as! NSObject), true)
}
if valueType != "_SwiftValue" {
// isObject is false to prevent parsing of objects like CKRecord, CKRecordId and other objects.
return (theValue as! NSObject, valueType, false)
}
}
if valueType.hasPrefix("Swift.Array<") && parentObject is EVArrayConvertable {
return ((parentObject as! EVArrayConvertable).convertArray(key ?? "_unknownKey", array: theValue), valueType, false)
}
(parentObject as? EVReflectable)?.addStatusMessage(.InvalidType, message: "valueForAny unkown type \(valueType) for value: \(theValue).")
evPrint(.InvalidType, "ERROR: valueForAny unkown type \(valueType) for key: \(key ?? "") and value: \(theValue).")
return (NSNull(), "NSNull", false)
}
fileprivate static func convertStructureToDictionary(_ theValue: Any, conversionOptions: ConversionOptions, isCachable: Bool, parents: [NSObject] = []) -> NSDictionary {
let reflected = Mirror(reflecting: theValue)
let (addProperties, _) = reflectedSub(theValue, reflected: reflected, conversionOptions: conversionOptions, isCachable: isCachable, parents: parents)
return addProperties
}
/**
Try to set a value of a property with automatic String to and from Number conversion
- parameter anyObject: the object where the value will be set
- parameter key: the name of the property
- parameter theValue: the value that will be set
- parameter typeInObject: the type of the value
- parameter valid: False if a vaue is expected and a dictionary
- parameter conversionOptions: Option set for the various conversion options.
*/
public static func setObjectValue<T>(_ anyObject: T, key: String, theValue: Any?, typeInObject: String? = nil, valid: Bool, conversionOptions: ConversionOptions = .DefaultDeserialize, parents: [NSObject] = []) where T: NSObject {
guard var value = theValue , (value as? NSNull) == nil else {
return
}
if conversionOptions.contains(.PropertyConverter) {
if let (_, propertySetter, _) = (anyObject as? EVReflectable)?.propertyConverters().filter({$0.0 == key}).first {
propertySetter(value)
return
}
}
if conversionOptions.contains(.Decoding), let ro = anyObject as? EVReflectable {
if let v = ro.decodePropertyValue(value: value, key: key) {
value = v
}
}
// Let us put a number into a string property by taking it's stringValue
let (_, type, _) = valueForAny("", key: key, anyValue: value, conversionOptions: conversionOptions, isCachable: false, parents: parents)
if (typeInObject == "String" || typeInObject == "NSString") && type == "NSNumber" {
if let convertedValue = value as? NSNumber {
value = convertedValue.stringValue as AnyObject
}
} else if typeInObject == "NSNumber" && (type == "String" || type == "NSString") {
if let convertedValue = (value as? String)?.lowercased() {
if convertedValue == "true" || convertedValue == "yes" {
value = 1 as AnyObject
} else if convertedValue == "false" || convertedValue == "no" {
value = 0 as AnyObject
} else {
value = NSNumber(value: Double(convertedValue) ?? 0 as Double)
}
}
} else if typeInObject == "UUID" && (type == "String" || type == "NSString") {
value = UUID(uuidString: value as? String ?? "") as AnyObject? ?? UUID() as AnyObject
} else if typeInObject == "NSURL" && (type == "String" || type == "NSString") {
value = NSURL(string: value as? String ?? "")! as AnyObject
} else if (typeInObject == "NSDate" || typeInObject == "Date") && (type == "String" || type == "NSString") {
if let convertedValue = value as? String {
if let date = getDateFormatter().date(from: convertedValue) {
value = date as AnyObject
} else if let date = Date(fromDateTimeString: convertedValue) {
value = date as AnyObject
} else {
(anyObject as? EVReflectable)?.addStatusMessage(.InvalidValue, message: "The dateformatter returend nil for value \(convertedValue)")
evPrint(.InvalidValue, "WARNING: The dateformatter returend nil for value \(convertedValue)")
return
}
}
} else if typeInObject == "AnyObject" {
}
if !(value is NSArray) && (typeInObject ?? "").contains("Swift.Array") {
value = NSArray(array: [value])
}
if typeInObject == "Struct" {
anyObject.setValue(value, forUndefinedKey: key)
} else {
if !valid {
anyObject.setValue(theValue, forUndefinedKey: key)
return
}
// Call your own object validators that comply to the format: validate<Key>:Error:
do {
if !(value is NSNull) {
var setValue: AnyObject? = value as AnyObject?
let validateFunction = "validate" + key.prefix(1).uppercased() + key.dropFirst() + ":error:"
if (anyObject as AnyObject).responds(to: Selector(validateFunction)) {
try anyObject.validateValue(&setValue, forKey: key)
}
anyObject.setValue(setValue, forKey: key)
}
} catch _ {
(anyObject as? EVReflectable)?.addStatusMessage(.InvalidValue, message: "Not a valid value for object `\(NSStringFromClass(Swift.type(of: (anyObject as AnyObject))))`, type `\(type)`, key `\(key)`, value `\(value)`")
evPrint(.InvalidValue, "INFO: Not a valid value for object `\(NSStringFromClass(Swift.type(of: (anyObject as AnyObject))))`, type `\(type)`, key `\(key)`, value `\(value)`")
}
/* TODO: Do I dare? ... For nullable types like Int? we could use this instead of the workaround.
// Asign pointerToField based on specific type
// Look up the ivar, and it's offset
let ivar: Ivar = class_getInstanceVariable(anyObject.dynamicType, key)
let fieldOffset = ivar_getOffset(ivar)
// Pointer arithmetic to get a pointer to the field
let pointerToInstance = unsafeAddressOf(anyObject)
let pointerToField = UnsafeMutablePointer<Int?>(pointerToInstance + fieldOffset)
// Set the value using the pointer
pointerToField.memory = value!
*/
}
}
// MARK: - Private helper functions
/**
Create a dictionary of all property - key mappings
- parameter theObject: the object for what we want the mapping
- parameter properties: dictionairy of all the properties
- parameter types: dictionairy of all property types.
- returns: dictionairy of the property mappings
*/
fileprivate class func cleanupKeysAndValues(_ theObject: NSObject, properties: NSDictionary, types: NSDictionary) -> (NSDictionary, NSDictionary) {
let newProperties = NSMutableDictionary()
let newTypes = NSMutableDictionary()
for (key, _) in properties {
if let newKey = cleanupKey(theObject, key: (key as? String)!, tryMatch: nil) {
newProperties[newKey] = properties[(key as? String)!]
newTypes[newKey] = types[(key as? String)!]
}
}
return (newProperties, newTypes)
}
/**
Try to map a property name to a json/dictionary key by applying some rules like property mapping, snake case conversion or swift keyword fix.
- parameter anyObject: the object where the key is part of
- parameter key: the key to clean up
- parameter tryMatch: dictionary of keys where a mach will be tried to
- returns: the cleaned up key
*/
fileprivate class func cleanupKey(_ anyObject: NSObject, key: String, tryMatch: NSDictionary?) -> String? {
var newKey: String = key
if tryMatch?[newKey] != nil {
return newKey
}
// Step 1 - clean up keywords
if newKey.first == "_" {
if keywords.contains(String(newKey[newKey.index(newKey.startIndex, offsetBy: 1)...])) {
newKey = String(newKey[newKey.index(newKey.startIndex, offsetBy: 1)...])
if tryMatch?[newKey] != nil {
return newKey
}
}
}
// Step 2 - replace illegal characters
if let t = tryMatch {
for (key, _) in t {
var k = key
if let kIsString = k as? String {
k = processIllegalCharacters(kIsString)
}
if k as? String == newKey {
return key as? String
}
}
}
// Step 3 - from CmelCase or pascalCase
newKey = CamelCaseToPascalCase(newKey)
if tryMatch?[newKey] != nil {
return newKey
}
// Step 4 - from PascalCase or camelCase
newKey = PascalCaseToCamelCase(newKey)
if tryMatch?[newKey] != nil {
return newKey
}
// Step 5 - from camelCase to snakeCase
newKey = camelCaseToUnderscores(newKey)
if tryMatch?[newKey] != nil {
return newKey
}
if tryMatch != nil {
return nil
}
return newKey
}
/// Character that will be replaced by _ from the keys in a dictionary / json
fileprivate static let illegalCharacterSet = CharacterSet(charactersIn: " -&%#@!$^*()<>?.,:;")
/// processIllegalCharacters Cache
fileprivate static var processIllegalCharactersCache = NSCache<NSString, NSString>()
/**
Replace illegal characters to an underscore
- parameter input: key
- returns: processed string with illegal characters converted to underscores
*/
internal static func processIllegalCharacters(_ input: String) -> String {
var p: NSString = ""
if let cachedVersion = processIllegalCharactersCache.object(forKey: input as NSString) {
// use the cached version
p = cachedVersion
} else {
// create it from scratch then store in the cache
p = input.components(separatedBy: illegalCharacterSet).joined(separator: "_") as NSString
processIllegalCharactersCache.setObject(p, forKey: input as NSString)
}
return p as String
}
/// camelCaseToUnderscoresCache Cache
fileprivate static var camelCaseToUnderscoresCache = NSCache<NSString, NSString>()
/**
Convert a CamelCase to Underscores
- parameter input: the CamelCase string
- returns: the underscore string
*/
internal static func camelCaseToUnderscores(_ input: String) -> String {
if input.count == 0 {
return input
}
var p: NSString = ""
if let cachedVersion = camelCaseToUnderscoresCache.object(forKey: input as NSString) {
p = cachedVersion
} else {
var output: String = String(input.first!).lowercased()
let uppercase: CharacterSet = CharacterSet.uppercaseLetters
for character in input[input.index(input.startIndex, offsetBy: 1)...] {
if uppercase.contains(UnicodeScalar(String(character).utf16.first!)!) {
output += "_\(String(character).lowercased())"
} else {
output += "\(String(character))"
}
}
p = output as NSString
camelCaseToUnderscoresCache.setObject(p, forKey: input as NSString)
}
return p as String
}
/**
Convert a CamelCase to pascalCase
- parameter input: the CamelCase string
- returns: the pascalCase string
*/
internal static func PascalCaseToCamelCase(_ input: String) -> String {
if input.count > 1 {
return String(describing: input.first!).lowercased() + input[input.index(after: input.startIndex)...]
}
return input.lowercased()
}
/**
Convert a PascalCase to camelCase
- parameter input: the CamelCase string
- returns: the pascalCase string
*/
internal static func CamelCaseToPascalCase(_ input: String) -> String {
if input.count > 1 {
return String(describing: input.first!).uppercased() + input[input.index(after: input.startIndex)...]
}
return input.uppercased()
}
/// List of swift keywords for cleaning up keys
fileprivate static let keywords = ["self", "description", "class", "deinit", "enum", "extension", "func", "import", "init", "let", "protocol", "static", "struct", "subscript", "typealias", "var", "break", "case", "continue", "default", "do", "else", "fallthrough", "if", "in", "for", "return", "switch", "where", "while", "as", "dynamicType", "is", "new", "super", "Self", "Type", "__COLUMN__", "__FILE__", "__FUNCTION__", "__LINE__", "associativity", "didSet", "get", "infix", "inout", "left", "mutating", "none", "nonmutating", "operator", "override", "postfix", "precedence", "prefix", "right", "set", "unowned", "unowned", "safe", "unowned", "unsafe", "weak", "willSet", "private", "public", "internal", "zone"]
fileprivate static func arrayConversion(_ anyObject: NSObject, key: String, fieldType: String?, original: Any?, theDictValue: Any?, conversionOptions: ConversionOptions = .DefaultDeserialize) -> NSArray {
//Swift.Array<Swift.Array<Swift.Array<A81>>>
let dictValue: NSArray? = theDictValue as? NSArray
if fieldType?.hasPrefix("Swift.Array<Swift.Array<") ?? false && theDictValue is NSArray {
evPrint(.UseWorkaround, "TODO: You have to implement a workaround for double nested arrays. See https://github.com/evermeer/EVReflection/issues/212")
for item in dictValue! {
evPrint(.UseWorkaround, "TODO: Have to convert here... NSArray to \(fieldType ?? "") \(item)")
}
}
return dictValue!
}
/**
Convert a value in the dictionary to the correct type for the object
- parameter anyObject: The object where this dictionary is a property
- parameter key: The property name that is the dictionary
- parameter fieldType: type of the field in object
- parameter original: the original value
- parameter theDictValue: the value from the dictionary
- parameter conversionOptions: Option set for the various conversion options.
- returns: The converted value plus a boolean indicating if it's an object
*/
fileprivate static func dictionaryAndArrayConversion(_ anyObject: NSObject, key: String, fieldType: String?, original: Any?, theDictValue: Any?, conversionOptions: ConversionOptions = .DefaultDeserialize) -> (Any?, Bool) {
var dictValue = theDictValue
var valid = true
if let type = fieldType {
if type.hasPrefix("Swift.Array<") && dictValue is NSArray {
dictValue = arrayConversion(anyObject, key: key, fieldType: fieldType, original: original, theDictValue: theDictValue, conversionOptions: conversionOptions)
}
if type.hasPrefix("Swift.Array<") && dictValue as? NSDictionary != nil {
if (dictValue as? NSDictionary)?.count == 1 {
// XMLDictionary fix
let onlyElement = (dictValue as? NSDictionary)?.makeIterator().next()
//let t: String = ((onlyElement?.key as? String) ?? "")
if onlyElement?.value as? NSArray != nil && type.hasPrefix("Swift.Array<") { // && type.lowercased().hasSuffix("\(t)>")
dictValue = onlyElement?.value as? NSArray
dictValue = dictArrayToObjectArray(anyObject, key: key, type: type, array: (dictValue as? [NSDictionary] as NSArray?) ?? [NSDictionary]() as NSArray, conversionOptions: conversionOptions) as NSArray
} else {
// Single object array fix
var array: [NSDictionary] = [NSDictionary]()
array.append(dictValue as? NSDictionary ?? NSDictionary())
dictValue = dictArrayToObjectArray(anyObject, key: key, type: type, array: array as NSArray, conversionOptions: conversionOptions) as NSArray
}
} else {
// Single object array fix
var array: [NSDictionary] = [NSDictionary]()
array.append(dictValue as? NSDictionary ?? NSDictionary())
dictValue = dictArrayToObjectArray(anyObject, key: key, type: type, array: array as NSArray, conversionOptions: conversionOptions) as NSArray
}
} else if let _ = type.range(of: "_NativeDictionaryStorageOwner"), let dict = dictValue as? NSDictionary, let org = anyObject as? EVReflectable {
dictValue = org.convertDictionary(key, dict: dict)
} else if type != "NSDictionary" && type != "__NSDictionary0" && type != "AnyObject" && dictValue as? NSDictionary != nil { //TODO this too? && original is NSObject
let (dict, isValid) = dictToObject(type, original: original as? NSObject, dict: dictValue as? NSDictionary ?? NSDictionary(), conversionOptions: conversionOptions)
dictValue = dict ?? dictValue
valid = isValid
} else if type.range(of: "<NSDictionary>") == nil && type.range(of: "<AnyObject>") == nil && dictValue as? [NSDictionary] != nil {
// Array of objects
if !(original is EVCustomReflectable) {
dictValue = dictArrayToObjectArray(anyObject, key: key, type: type, array: dictValue as? [NSDictionary] as NSArray? ?? [NSDictionary]() as NSArray, conversionOptions: conversionOptions) as NSArray
}
} else if dictValue is String && original is NSObject && original is EVReflectable {
// fixing the conversion from XML without properties
let (dict, isValid) = dictToObject(type, original:original as? NSObject, dict: ["__text": dictValue as? String ?? ""], conversionOptions: conversionOptions)
dictValue = dict ?? dictValue
valid = isValid
} else if !type.hasPrefix("Swift.Array<") && !type.hasPrefix("Swift.Set<") {
if let array = dictValue as? NSArray {
if anyObject is EVCustomReflectable {
return (array, true)
}
if let org = anyObject as? EVReflectable {
org.addStatusMessage(.InvalidType, message: "Did not expect an array for \(key). Will use the first item instead.")
evPrint(.InvalidType, "WARNING: Did not expect an array for \(key). Will use the first item instead.")
}
if array.count > 0 {
return (array[0] as AnyObject?, true)
}
return (NSNull(), true)
}
}
} else {
}
return (dictValue, valid)
}
/**
Set sub object properties from a dictionary
- parameter type: The object type that will be created
- parameter original: The original value in the object which is used to create a return object
- parameter dict: The dictionary that will be converted to an object
- parameter conversionOptions: Option set for the various conversion options.
- returns: The object that is created from the dictionary
*/
fileprivate class func dictToObject<T>(_ type: String, original: T?, dict: NSDictionary, conversionOptions: ConversionOptions = .DefaultDeserialize) -> (T?, Bool) where T: NSObject {
if var returnObject = original {
if type != "NSNumber" && type != "NSString" && type != "NSDate" && type != "Struct" && type.contains("Dictionary<") == false {
returnObject = setPropertiesfromDictionary(dict, anyObject: returnObject, conversionOptions: conversionOptions)
} else {
if type.contains("Dictionary<") == false && type != "Struct" {
(original as? EVReflectable)?.addStatusMessage(.InvalidClass, message: "Cannot set values on type \(type) from dictionary \(dict)")
evPrint(.InvalidClass, "WARNING: Cannot set values on type \(type) from dictionary \(dict)")
}
return (returnObject, false)
}
return (returnObject, true)
}
var useType = type
if type.hasPrefix("Swift.Optional<") {
var subtype: String = String(type[(type.components(separatedBy: "<") [0] + "<").endIndex...])
subtype = String(subtype[..<subtype.index(before: subtype.endIndex)])
useType = subtype
}
if var returnObject: NSObject = swiftClassFromString(useType) {
if let evResult = returnObject as? EVReflectable {
if let type = evResult.getType(dict) as? NSObject {
returnObject = type
}
if let specific = evResult.getSpecificType(dict) as? NSObject {
returnObject = specific
} else if let evResult = returnObject as? EVGenericsKVC {
returnObject = evResult.getGenericType()
}
}
returnObject = setPropertiesfromDictionary(dict, anyObject: returnObject, conversionOptions: conversionOptions)
return (returnObject as? T, true)
}
if useType != "Struct" {
(original as? EVReflectable)?.addStatusMessage(.InvalidClass, message: "Could not create an instance for type \(type)\ndict:\(dict)")
evPrint(.InvalidClass, "ERROR: Could not create an instance for type \(useType)\ndict:\(dict)")
}
return (nil, false)
}
/**
Create an Array of objects from an array of dictionaries
- parameter type: The object type that will be created
- parameter array: The array of dictionaries that will be converted to the array of objects
- parameter conversionOptions: Option set for the various conversion options.
- returns: The array of objects that is created from the array of dictionaries
*/
fileprivate class func dictArrayToObjectArray(_ anyObject: NSObject, key: String, type: String, array: NSArray, conversionOptions: ConversionOptions = .DefaultDeserialize) -> NSArray {
var subtype = ""
if type.components(separatedBy: "<").count > 1 {
// Remove the Array prefix
subtype = String(type[(type.components(separatedBy: "<") [0] + "<").endIndex...])
subtype = String(subtype[..<subtype.index(before: subtype.endIndex)])
// Remove the optional prefix from the subtype
if subtype.hasPrefix("Optional<") {
subtype = String(subtype[(subtype.components(separatedBy: "<") [0] + "<").endIndex...])
subtype = String(subtype[..<subtype.index(before: subtype.endIndex)])
}
}
var result: [NSObject] = Mirror(reflecting: anyObject).children.filter { $0.label == key }.first?.value as? [NSObject] ?? [NSObject]()
result.removeAll()
for item in array {
let org = getTypeFor(anyObject: anyObject, key: key, type: subtype, item: item)
let (arrayObject, valid) = dictToObject(subtype, original:org, dict: item as? NSDictionary ?? NSDictionary(), conversionOptions: conversionOptions)
if arrayObject != nil && valid {
result.append(arrayObject!)
}
}
return result as NSArray
}
fileprivate class func getTypeFor(anyObject: NSObject, key: String, type: String, item: Any) -> NSObject? {
var org = swiftClassFromString(type)
if let evResult = org as? EVReflectable {
if let type = evResult.getType(item as? NSDictionary ?? NSDictionary()) as? NSObject {
org = type
}
if let specific = evResult.getSpecificType(item as? NSDictionary ?? NSDictionary()) as? NSObject {
org = specific
} else if let evResult = anyObject as? EVGenericsKVC {
org = evResult.getGenericType()
}
}
return org
}
/**
for parsing an object to a dictionary. including properties from it's super class (recursive)
- parameter theObject: The object as is
- parameter reflected: The object parsed using the reflect method.
- parameter conversionOptions: Option set for the various conversion options.
- returns: The dictionary that is created from the object plus an dictionary of property types.
*/
fileprivate class func reflectedSub(_ theObject: Any, reflected: Mirror, conversionOptions: ConversionOptions = .DefaultDeserialize, isCachable: Bool, parents: [NSObject] = []) -> (NSDictionary, NSDictionary) {
let propertiesDictionary = NSMutableDictionary()
let propertiesTypeDictionary = NSMutableDictionary()
// First add the super class propperties
if let superReflected = reflected.superclassMirror {
let (addProperties, addPropertiesTypes) = reflectedSub(theObject, reflected: superReflected, conversionOptions: conversionOptions, isCachable: isCachable, parents: parents)
for (k, v) in addProperties {
if k as? String != "evReflectionStatuses" {
propertiesDictionary.setValue(v, forKey: k as? String ?? "")
propertiesTypeDictionary[k as? String ?? ""] = addPropertiesTypes[k as? String ?? ""]
}
}
}
for property in reflected.children {
if let originalKey: String = property.label {
var skipThisKey = false
var mapKey = originalKey
if mapKey.contains(".") {
mapKey = mapKey.components(separatedBy: ".")[0] // remover the .storage for lazy properties
}
if originalKey == "evReflectionStatuses" {
skipThisKey = true
}
if conversionOptions.contains(.PropertyMapping) {
if let reflectable = theObject as? EVReflectable {
if let mapping = reflectable.propertyMapping().filter({$0.keyInObject == originalKey}).first {
if mapping.keyInResource == nil {
skipThisKey = true
} else {
mapKey = mapping.keyInResource!
}
}
}
}
if !skipThisKey {
var value = property.value
// Convert the Any value to a NSObject value
var (unboxedValue, valueType, isObject) = valueForAny(theObject, key: originalKey, anyValue: value, conversionOptions: conversionOptions, isCachable: isCachable, parents: parents)
if let v = value as? EVCustomReflectable {
unboxedValue = v.toCodableValue() as AnyObject
valueType = String(describing: type(of: v))
isObject = false
}
if conversionOptions.contains(.Encoding), let ro = theObject as? EVReflectable {
unboxedValue = ro.encodePropertyValue(value: unboxedValue, key: originalKey) as AnyObject
}
if conversionOptions.contains(.PropertyConverter) {
// If there is a properyConverter, then use the result of that instead.
if let (_, _, propertyGetter) = (theObject as? EVReflectable)?.propertyConverters().filter({$0.0 == originalKey}).first {
value = propertyGetter() as Any
let (unboxedValue2, _, _) = valueForAny(theObject, key: originalKey, anyValue: value, conversionOptions: conversionOptions, isCachable: isCachable, parents: parents)
unboxedValue = unboxedValue2
}
}
if isObject {
if let obj = unboxedValue as? EVReflectable {
if let json = obj.customConverter() {
unboxedValue = json as AnyObject
} else {
// sub objects will be added as a dictionary itself.
let (dict, _) = toDictionary(unboxedValue as? NSObject ?? NSObject(), conversionOptions: conversionOptions, isCachable: isCachable, parents: parents)
unboxedValue = dict
}
} else {
// sub objects will be added as a dictionary itself.
let (dict, _) = toDictionary(unboxedValue as? NSObject ?? NSObject(), conversionOptions: conversionOptions, isCachable: isCachable, parents: parents)
unboxedValue = dict
}
} else if let array = unboxedValue as? [NSObject] {
var item: Any
if array.count > 0 {
item = array[0]
// Workaround for bug https://bugs.swift.org/browse/SR-3083
if let possibleEnumArray = unboxedValue as? [Any] {
let possibleEnum = possibleEnumArray[0]
if type(of: item) != type(of: possibleEnum) {
item = possibleEnum
var newArray: [AnyObject] = []
for anEnum in possibleEnumArray {
let (value, _, _) = valueForAny(anyValue: anEnum)
newArray.append(value)
}
unboxedValue = newArray as AnyObject
}
}
} else {
item = array.getArrayTypeInstance(array)
}
let (_, _, isObject) = valueForAny(anyValue: item, conversionOptions: conversionOptions, isCachable: isCachable, parents: parents)
if isObject {
// If the items are objects, than add a dictionary of each to the array
var tempValue = [NSDictionary]()
for av in array {
let (dict, _) = toDictionary(av, conversionOptions: conversionOptions, isCachable: isCachable, parents: parents)
tempValue.append(dict)
}
unboxedValue = tempValue as AnyObject
}
}
if conversionOptions.contains(.SkipPropertyValue) {
if let reflectable = theObject as? EVReflectable {
if !reflectable.skipPropertyValue(unboxedValue, key: mapKey) {
propertiesDictionary.setValue(unboxedValue, forKey: mapKey)
propertiesTypeDictionary[mapKey] = valueType
}
} else {
propertiesDictionary.setValue(unboxedValue, forKey: mapKey)
propertiesTypeDictionary[mapKey] = valueType
}
} else {
propertiesDictionary.setValue(unboxedValue, forKey: mapKey)
propertiesTypeDictionary[mapKey] = valueType
}
}
}
}
return (propertiesDictionary, propertiesTypeDictionary)
}
/**
Clean up dictionary so that it can be converted to json
- parameter dict: The dictionairy that
- returns: The cleaned up dictionairy
*/
internal class func convertDictionaryForJsonSerialization(_ dict: NSDictionary, theObject: NSObject) -> NSDictionary {
let dict2: NSMutableDictionary = NSMutableDictionary()
for (key, value) in dict {
dict2.setValue(convertValueForJsonSerialization(value as AnyObject, theObject: theObject), forKey: key as? String ?? "")
}
return dict2
}
/**
Clean up a value so that it can be converted to json
- parameter value: The value to be converted
- returns: The converted value
*/
fileprivate class func convertValueForJsonSerialization(_ value: Any, theObject: NSObject) -> AnyObject {
switch value {
case let stringValue as NSString:
return stringValue
case let numberValue as NSNumber:
return numberValue
case let nullValue as NSNull:
return nullValue
case let arrayValue as NSArray:
let tempArray: NSMutableArray = NSMutableArray()
for value in arrayValue {
tempArray.add(convertValueForJsonSerialization(value as Any, theObject: theObject))
}
return tempArray
case let date as Date:
return getDateFormatter().string(from: date) as NSString
case let reflectable as EVCustomReflectable:
return convertDictionaryForJsonSerialization(reflectable.toCodableValue() as? NSDictionary ?? NSDictionary(), theObject: theObject)
case let reflectable as EVReflectable:
return convertDictionaryForJsonSerialization(reflectable.toDictionary(), theObject: theObject)
case let ok as NSDictionary:
return convertDictionaryForJsonSerialization(ok, theObject: theObject)
case let d as Data:
return d.base64EncodedString() as AnyObject
default:
(theObject as? EVReflectable)?.addStatusMessage(.InvalidType, message: "Unexpected type while converting value for JsonSerialization: \(value)")
evPrint(.InvalidType, "ERROR: Unexpected type while converting value for JsonSerialization: \(value)")
return "\(value)" as AnyObject
}
}
}
extension Date {
public init?(fromDateTimeString: String) {
let pattern = "\\\\?/Date\\((\\d+)(([+-]\\d{2})(\\d{2}))?\\)\\\\?/"
let regex = try! NSRegularExpression(pattern: pattern)
let match: NSRange = regex.rangeOfFirstMatch(in: fromDateTimeString, range: NSRange(location: 0, length: fromDateTimeString.utf16.count))
var dateString: String = ""
if match.location == NSNotFound {
dateString = fromDateTimeString
} else {
dateString = (fromDateTimeString as NSString).substring(with: match) // Extract milliseconds
}
let substrings = dateString.components(separatedBy: CharacterSet.decimalDigits.inverted)
guard let timeStamp = (substrings.compactMap { Double($0) }.first) else { return nil }
self.init(timeIntervalSince1970: timeStamp / 1000.0) // Create Date from timestamp
}
}
//
// EVWorkaroundHelpers.swift
// EVReflection
//
// Created by Edwin Vermeer on 2/7/16.
// Copyright © 2016 evict. All rights reserved.
//
import Foundation
/**
Protocol for the workaround when using generics. See WorkaroundSwiftGenericsTests.swift
*/
public protocol EVGenericsKVC {
/**
Implement this protocol in a class with generic properties so that we can still use a standard mechanism for setting property values.
*/
func setGenericValue(_ value: AnyObject!, forUndefinedKey key: String)
/**
Add a function so that we can get an instance of T
*/
func getGenericType() -> NSObject
}
/**
Protocol for the workaround when using an enum with a rawValue of an undefined type
*/
public protocol EVRaw {
/**
For implementing a function that will return the rawValue for a non sepecific enum
*/
var anyRawValue: Any { get }
}
/**
Default implementation for getting the rawValue for any other type
*/
public extension EVRaw where Self: RawRepresentable {
var anyRawValue: Any {
get {
return rawValue as Any
}
}
}
/**
Protocol for the workaround when using an array with nullable values
*/
public protocol EVArrayConvertable {
/**
For implementing a function for converting a generic array to a specific array.
*/
func convertArray(_ key: String, array: Any) -> NSArray
}
/**
Add a property to an enum to get the associated value
*/
public protocol EVAssociated {
}
/**
The implrementation of the protocol for getting the associated value
*/
public extension EVAssociated {
/**
Easy access to the associated value of an enum.
:returns: The label of the enum plus the associated value
*/
var associated: (label: String, value: Any?, values: [Any]) {
get {
let mirror = Mirror(reflecting: self)
if mirror.displayStyle == .enum {
if let associated = mirror.children.first {
let values = Mirror(reflecting: associated.value).children
var valuesArray = [Any]()
for item in values {
valuesArray.append(item.value)
}
return (associated.label!, associated.value, valuesArray)
}
print("WARNING: Enum option of \(self) does not have an associated value")
return ("\(self)", nil, [])
}
print("WARNING: You can only extend an enum with the EnumExtension")
return ("\(self)", nil, [])
}
}
}
/**
Dictionary extension for creating a dictionary from an array of enum values
*/
public extension Dictionary {
/**
Create a dictionairy based on all associated values of an enum array
- parameter associated: array of dictionairy values which have an associated value
*/
init<T: EVAssociated>(associated: [T]?) {
self.init()
if associated != nil {
for myEnum in associated! {
self[(myEnum.associated.label as? Key)!] = myEnum.associated.value as? Value
}
}
}
}
//
// PrintOptions.swift
// EVReflection
//
// Created by Edwin Vermeer on 9/5/16.
// Copyright © 2015 evict. All rights reserved.
//
/**
For specifying what should be printed
*/
public struct PrintOptions: OptionSet, CustomStringConvertible {
/// The numeric representation of the options
public let rawValue: Int
/**
Initialize with a raw value
- parameter rawValue: the numeric representation
- returns: The Print options
*/
public init(rawValue: Int) { self.rawValue = rawValue }
/// No print
public static let None = PrintOptions(rawValue: 0)
/// print array init uknown keypath
public static let UnknownKeypath = PrintOptions(rawValue: 1)
/// print EIncorrectKey
public static let IncorrectKey = PrintOptions(rawValue: 2)
/// print should extend an NSObject
public static let ShouldExtendNSObject = PrintOptions(rawValue: 4)
/// print invalid json
public static let IsInvalidJson = PrintOptions(rawValue: 8)
/// print Missing protocol error
public static let MissingProtocol = PrintOptions(rawValue: 16)
/// print Missing key error
public static let MissingKey = PrintOptions(rawValue: 32)
/// print Invalid type error
public static let InvalidType = PrintOptions(rawValue: 64)
/// print Invalid value error
public static let InvalidValue = PrintOptions(rawValue: 128)
/// print Invalid class error
public static let InvalidClass = PrintOptions(rawValue: 256)
/// print enum without associated value
public static let EnumWithoutAssociatedValue = PrintOptions(rawValue: 512)
/// print enum without associated value
public static let UseWorkaround = PrintOptions(rawValue: 1024)
/// All the options
public static var All: PrintOptions = [UnknownKeypath, IncorrectKey, ShouldExtendNSObject, IsInvalidJson, MissingProtocol, MissingKey, InvalidType, InvalidValue, InvalidClass, EnumWithoutAssociatedValue, UseWorkaround]
/// The active print options
public static var Active: PrintOptions = All
/// Get a nice description of the PrintOptions
public var description: String {
let strings = ["UnknownKeypath", "IncorrectKey", "ShouldExtendNSObject", "IsInvalidJson", "MissingProtocol", "MissingKey", "InvalidType", "InvalidValue", "InvalidClass", "EnumWithoutAssociatedValue", "UseWorkaround"]
var members = [String]()
for (flag, string) in strings.enumerated() where contains(PrintOptions(rawValue:1<<(flag + 1))) {
members.append(string)
}
if members.count == 0 {
members.append("None")
}
return members.description
}
}
public func evPrint(_ options: PrintOptions, _ value: String) {
if PrintOptions.Active.contains(options) {
print("🌀 \(value)")
}
}
//
// Event.swift
// Observable-Swift
//
// Created by Leszek Ślażyński on 21/06/14.
// Copyright (c) 2014 Leszek Ślażyński. All rights reserved.
//
// Events are implemented as structs, what has both advantages and disadvantages
// Notably they are copied when inside other value types, and mutated on add/remove/notify
// If you require a reference type for Event, use EventReference<T> instead
/// A struct representing a collection of subscriptions with means to add, remove and notify them.
public struct Event<T>: UnownableEvent {
public typealias ValueType = T
public typealias SubscriptionType = EventSubscription<T>
public typealias HandlerType = SubscriptionType.HandlerType
public private(set) var subscriptions = [SubscriptionType]()
public init() { }
public mutating func notify(_ value: T) {
subscriptions = subscriptions.filter { $0.valid() }
for subscription in subscriptions {
subscription.handler(value)
}
}
@discardableResult
public mutating func add(_ subscription: SubscriptionType) -> SubscriptionType {
subscriptions.append(subscription)
return subscription
}
@discardableResult
public mutating func add(_ handler: @escaping HandlerType) -> SubscriptionType {
return add(SubscriptionType(owner: nil, handler: handler))
}
public mutating func remove(_ subscription: SubscriptionType) {
var newsubscriptions = [SubscriptionType]()
var first = true
for existing in subscriptions {
if first && existing === subscription {
first = false
} else {
newsubscriptions.append(existing)
}
}
subscriptions = newsubscriptions
}
public mutating func removeAll() {
subscriptions.removeAll()
}
@discardableResult
public mutating func add(owner: AnyObject, _ handler: @escaping HandlerType) -> SubscriptionType {
return add(SubscriptionType(owner: owner, handler: handler))
}
public mutating func unshare() {
// _subscriptions.unshare()
}
}
@discardableResult
public func += <T: UnownableEvent> (event: inout T, handler: @escaping (T.ValueType) -> ()) -> EventSubscription<T.ValueType> {
return event.add(handler)
}
@discardableResult
public func += <T: OwnableEvent> (event: T, handler: @escaping (T.ValueType) -> ()) -> EventSubscription<T.ValueType> {
var e = event
return e.add(handler)
}
public func -= <T: UnownableEvent> (event: inout T, subscription: EventSubscription<T.ValueType>) {
return event.remove(subscription)
}
public func -= <T: OwnableEvent> (event: T, subscription: EventSubscription<T.ValueType>) {
var e = event
return e.remove(subscription)
}
//
// EventReference.swift
// Observable-Swift
//
// Created by Leszek Ślażyński on 23/06/14.
// Copyright (c) 2014 Leszek Ślażyński. All rights reserved.
//
/// A class enclosing an Event struct. Thus exposing it as a reference type.
open class EventReference<T>: OwnableEvent {
public typealias ValueType = T
public typealias SubscriptionType = EventSubscription<T>
public typealias HandlerType = EventSubscription<T>.HandlerType
public private(set) var event: Event<T>
open func notify(_ value: T) {
event.notify(value)
}
@discardableResult
open func add(_ subscription: SubscriptionType) -> SubscriptionType {
return event.add(subscription)
}
@discardableResult
open func add(_ handler: @escaping (T) -> ()) -> EventSubscription<T> {
return event.add(handler)
}
open func remove(_ subscription: SubscriptionType) {
return event.remove(subscription)
}
open func removeAll() {
event.removeAll()
}
@discardableResult
open func add(owner: AnyObject, _ handler: @escaping HandlerType) -> SubscriptionType {
return event.add(owner: owner, handler)
}
public convenience init() {
self.init(event: Event<T>())
}
public init(event: Event<T>) {
self.event = event
}
}
//
// EventSubscription.swift
// Observable-Swift
//
// Created by Leszek Ślażyński on 21/06/14.
// Copyright (c) 2014 Leszek Ślażyński. All rights reserved.
//
// Implemented as a class, so it can be compared using === and !==.
// There is no way for event to get notified when the owner was deallocated,
// therefore it will be invalidated only upon next attempt to trigger.
// Event subscriptions are neither freed nor removed from events upon invalidation.
// Events remove invalidated subscriptions themselves when firing.
// Invalidation immediately frees handler and owned objects.
/// A class representing a subscription for `Event<T>`.
public class EventSubscription<T> {
public typealias HandlerType = (T) -> ()
private var _valid: () -> Bool
/// Handler to be caled when value changes.
public private(set) var handler: HandlerType
/// array of owned objects
private var _owned = [AnyObject]()
/// When invalid subscription is to be notified, it is removed instead.
public func valid() -> Bool {
if !_valid() {
invalidate()
return false
} else {
return true
}
}
/// Marks the event for removal, frees the handler and owned objects
public func invalidate() {
_valid = { false }
handler = { _ in () }
_owned = []
}
/// Init with a handler and an optional owner.
/// If owner is present, valid() is tied to its lifetime.
public init(owner o: AnyObject?, handler h: @escaping HandlerType) {
if o == nil {
_valid = { true }
} else {
_valid = { [weak o] in o != nil }
}
handler = h
}
/// Add an object to be owned while the event is not invalidated
public func addOwnedObject(_ o: AnyObject) {
_owned.append(o)
}
/// Remove object from owned objects
public func removeOwnedObject(_ o: AnyObject) {
_owned = _owned.filter{ $0 !== o }
}
}
//
// Observable.swift
// Observable-Swift
//
// Created by Leszek Ślażyński on 20/06/14.
// Copyright (c) 2014 Leszek Ślażyński. All rights reserved.
//
/// A struct representing information associated with value change event.
public struct ValueChange<T> {
public let oldValue: T
public let newValue: T
public init(_ o: T, _ n: T) {
oldValue = o
newValue = n
}
}
// Implemented as a struct in order to have desired value and mutability sementics.
/// A struct representing an observable value.
public struct Observable<T>: UnownableObservable {
public typealias ValueType = T
public private(set) var beforeChange = EventReference<ValueChange<T>>()
public private(set) var afterChange = EventReference<ValueChange<T>>()
public var value : T {
willSet { beforeChange.notify(ValueChange(value, newValue)) }
didSet { afterChange.notify(ValueChange(oldValue, value)) }
}
public mutating func unshare(removeSubscriptions: Bool) {
if removeSubscriptions {
beforeChange = EventReference<ValueChange<T>>()
afterChange = EventReference<ValueChange<T>>()
} else {
var beforeEvent = beforeChange.event
beforeEvent.unshare()
beforeChange = EventReference<ValueChange<T>>(event: beforeEvent)
var afterEvent = afterChange.event
afterEvent.unshare()
afterChange = EventReference<ValueChange<T>>(event: afterEvent)
}
}
public init(_ v : T) {
value = v
}
}
//
// Chaining.swift
// Observable-Swift
//
// Created by Leszek Ślażyński on 23/06/14.
// Copyright (c) 2014 Leszek Ślażyński. All rights reserved.
//
public class ObservableChainingProxy<O1: AnyObservable, O2: AnyObservable>: OwnableObservable {
public typealias ValueType = O2.ValueType?
public var value: ValueType { return nil }
private weak var _beforeChange: EventReference<ValueChange<ValueType>>? = nil
private weak var _afterChange: EventReference<ValueChange<ValueType>>? = nil
public var beforeChange: EventReference<ValueChange<ValueType>> {
if let event = _beforeChange {
return event
} else {
let event = OwningEventReference<ValueChange<ValueType>>()
event.owned = self
_beforeChange = event
return event
}
}
public var afterChange: EventReference<ValueChange<ValueType>> {
if let event = _afterChange {
return event
} else {
let event = OwningEventReference<ValueChange<ValueType>>()
event.owned = self
_afterChange = event
return event
}
}
private let base: O1
private let path: (O1.ValueType) -> O2?
private func targetChangeToValueChange(_ vc: ValueChange<O2.ValueType>) -> ValueChange<ValueType> {
let oldValue = Optional.some(vc.oldValue)
let newValue = Optional.some(vc.newValue)
return ValueChange(oldValue, newValue)
}
private func objectChangeToValueChange(_ oc: ValueChange<O1.ValueType>) -> ValueChange<ValueType> {
let oldValue = path(oc.oldValue)?.value
let newValue = path(oc.newValue)?.value
return ValueChange(oldValue, newValue)
}
init(base: O1, path: @escaping (O1.ValueType) -> O2?) {
self.base = base
self.path = path
let beforeSubscription = EventSubscription(owner: self) { [weak self] in
self!.beforeChange.notify(self!.targetChangeToValueChange($0))
}
let afterSubscription = EventSubscription(owner: self) { [weak self] in
self!.afterChange.notify(self!.targetChangeToValueChange($0))
}
base.beforeChange.add(owner: self) { [weak self] oc in
let oldTarget = path(oc.oldValue)
oldTarget?.beforeChange.remove(beforeSubscription)
oldTarget?.afterChange.remove(afterSubscription)
self!.beforeChange.notify(self!.objectChangeToValueChange(oc))
}
base.afterChange.add(owner: self) { [weak self] oc in
self!.afterChange.notify(self!.objectChangeToValueChange(oc))
let newTarget = path(oc.newValue)
newTarget?.beforeChange.add(beforeSubscription)
newTarget?.afterChange.add(afterSubscription)
}
}
public func to<O3: AnyObservable>(path f: @escaping (O2.ValueType) -> O3?) -> ObservableChainingProxy<ObservableChainingProxy<O1, O2>, O3> {
func cascadeNil(_ oOrNil: ValueType) -> O3? {
if let o = oOrNil {
return f(o)
} else {
return nil
}
}
return ObservableChainingProxy<ObservableChainingProxy<O1, O2>, O3>(base: self, path: cascadeNil)
}
public func to<O3: AnyObservable>(path f: @escaping (O2.ValueType) -> O3) -> ObservableChainingProxy<ObservableChainingProxy<O1, O2>, O3> {
func cascadeNil(_ oOrNil: ValueType) -> O3? {
if let o = oOrNil {
return f(o)
} else {
return nil
}
}
return ObservableChainingProxy<ObservableChainingProxy<O1, O2>, O3>(base: self, path: cascadeNil)
}
}
public struct ObservableChainingBase<O1: AnyObservable> {
fileprivate let base: O1
public func to<O2: AnyObservable>(_ path: @escaping (O1.ValueType) -> O2?) -> ObservableChainingProxy<O1, O2> {
return ObservableChainingProxy(base: base, path: path)
}
public func to<O2: AnyObservable>(_ path: @escaping (O1.ValueType) -> O2) -> ObservableChainingProxy<O1, O2> {
return ObservableChainingProxy(base: base, path: { .some(path($0)) })
}
}
public func chain<O: AnyObservable>(_ o: O) -> ObservableChainingBase<O> {
return ObservableChainingBase(base: o)
}
public func / <O1, O2, O3: AnyObservable> (o: ObservableChainingProxy<O1, O2>, f: @escaping (O2.ValueType) -> O3?) -> ObservableChainingProxy<ObservableChainingProxy<O1, O2>, O3> {
return o.to(path: f)
}
public func / <O1: AnyObservable, O2: AnyObservable> (o: O1, f: @escaping (O1.ValueType) -> O2?) -> ObservableChainingProxy<O1, O2> {
return ObservableChainingProxy(base: o, path: f)
}
//
// ObservableProxy.swift
// Observable-Swift
//
// Created by Leszek Ślażyński on 24/06/14.
// Copyright (c) 2014 Leszek Ślażyński. All rights reserved.
//
// two generic parameters are needed to be able to override `value` in `ObservableReference<T>`
open class ObservableProxy<T, O: AnyObservable> : OwnableObservable where O.ValueType == T {
public typealias ValueType = T
public private(set) var beforeChange = EventReference<ValueChange<T>>()
public private(set) var afterChange = EventReference<ValueChange<T>>()
// private storage in case subclasses override value with a setter
private var _value: T
open var value: T {
return _value
}
public init(_ o: O) {
self._value = o.value
o.beforeChange.add(owner: self) { [weak self] change in
self!.beforeChange.notify(change)
}
o.afterChange.add(owner: self) { [weak self] change in
let nV = change.newValue
self!._value = nV
self!.afterChange.notify(change)
}
}
}
public func proxy <O: AnyObservable> (_ o: O) -> ObservableProxy<O.ValueType, O> {
return ObservableProxy(o)
}
//
// ObservableReference.swift
// Observable-Swift
//
// Created by Leszek Ślażyński on 21/06/14.
// Copyright (c) 2014 Leszek Ślażyński. All rights reserved.
//
public class ObservableReference<T> : ObservableProxy<T, Observable<T>>, WritableObservable {
public typealias ValueType = T
private var storage: Observable<T>
public override var value: T {
get { return storage.value }
set { storage.value = newValue }
}
public init(_ v : T) {
storage = Observable(v)
super.init(storage)
}
}
//
// OwningEventReference.swift
// Observable-Swift
//
// Created by Leszek Ślażyński on 28/06/14.
// Copyright (c) 2014 Leszek Ślażyński. All rights reserved.
//
/// A subclass of event reference allowing it to own other object[s].
/// Additionally, the reference makes added events own itself.
/// This retain cycle allows owned objects to live as long as valid subscriptions exist.
public class OwningEventReference<T>: EventReference<T> {
internal var owned: AnyObject? = nil
public override func add(_ subscription: SubscriptionType) -> SubscriptionType {
let subscr = super.add(subscription)
if owned != nil {
subscr.addOwnedObject(self)
}
return subscr
}
public override func add(_ handler: @escaping (T) -> ()) -> EventSubscription<T> {
let subscr = super.add(handler)
if owned != nil {
subscr.addOwnedObject(self)
}
return subscr
}
public override func remove(_ subscription: SubscriptionType) {
subscription.removeOwnedObject(self)
super.remove(subscription)
}
public override func removeAll() {
for subscription in event.subscriptions {
subscription.removeOwnedObject(self)
}
super.removeAll()
}
public override func add(owner: AnyObject, _ handler: @escaping HandlerType) -> SubscriptionType {
let subscr = super.add(owner: owner, handler)
if owned != nil {
subscr.addOwnedObject(self)
}
return subscr
}
public override init(event: Event<T>) {
super.init(event: event)
}
}
//
// Protocols.swift
// Observable-Swift
//
// Created by Leszek Ślażyński on 21/06/14.
// Copyright (c) 2014 Leszek Ślażyński. All rights reserved.
//
/// Arbitrary Event.
public protocol AnyEvent {
associatedtype ValueType
/// Notify all valid subscriptions of the change. Remove invalid ones.
mutating func notify(_ value: ValueType)
/// Add an existing subscription.
@discardableResult
mutating func add(_ subscription: EventSubscription<ValueType>) -> EventSubscription<ValueType>
/// Create, add and return a subscription for given handler.
@discardableResult
mutating func add(_ handler : @escaping (ValueType) -> ()) -> EventSubscription<ValueType>
/// Remove given subscription, if present.
mutating func remove(_ subscription : EventSubscription<ValueType>)
/// Remove all subscriptions.
mutating func removeAll()
/// Create, add and return a subscription with given handler and owner.
@discardableResult
mutating func add(owner : AnyObject, _ handler : @escaping (ValueType) -> ()) -> EventSubscription<ValueType>
}
/// Event which is a value type.
public protocol UnownableEvent: AnyEvent { }
/// Event which is a reference type
public protocol OwnableEvent: AnyEvent { }
/// Arbitrary observable.
public protocol AnyObservable {
associatedtype ValueType
/// Value of the observable.
var value: ValueType { get }
/// Event fired before value is changed
var beforeChange: EventReference<ValueChange<ValueType>> { get }
/// Event fired after value is changed
var afterChange: EventReference<ValueChange<ValueType>> { get }
}
/// Observable which can be written to
public protocol WritableObservable: AnyObservable {
var value: ValueType { get set }
}
/// Observable which is a value type. Elementary observables are value types.
public protocol UnownableObservable: WritableObservable {
/// Unshares events
mutating func unshare(removeSubscriptions: Bool)
}
/// Observable which is a reference type. Compound observables are reference types.
public protocol OwnableObservable: AnyObservable {
}
// observable <- value
infix operator <-
// value = observable^
postfix operator ^
// observable ^= value
public func ^= <T : WritableObservable> (x: inout T, y: T.ValueType) {
x.value = y
}
// observable += { valuechange in ... }
@discardableResult
public func += <T : AnyObservable> (x: inout T, y: @escaping (ValueChange<T.ValueType>) -> ()) -> EventSubscription<ValueChange<T.ValueType>> {
return x.afterChange += y
}
// observable += { (old, new) in ... }
@discardableResult
public func += <T : AnyObservable> (x: inout T, y: @escaping (T.ValueType, T.ValueType) -> ()) -> EventSubscription<ValueChange<T.ValueType>> {
return x.afterChange += y
}
// observable += { new in ... }
@discardableResult
public func += <T : AnyObservable> (x: inout T, y: @escaping (T.ValueType) -> ()) -> EventSubscription<ValueChange<T.ValueType>> {
return x.afterChange += y
}
// observable -= subscription
public func -= <T : AnyObservable> (x: inout T, s: EventSubscription<ValueChange<T.ValueType>>) {
x.afterChange.remove(s)
}
// event += { (old, new) in ... }
@discardableResult
public func += <T> (event: EventReference<ValueChange<T>>, handler: @escaping (T, T) -> ()) -> EventSubscription<ValueChange<T>> {
return event.add({ handler($0.oldValue, $0.newValue) })
}
// event += { new in ... }
@discardableResult
public func += <T> (event: EventReference<ValueChange<T>>, handler: @escaping (T) -> ()) -> EventSubscription<ValueChange<T>> {
return event.add({ handler($0.newValue) })
}
// for observable values on variables
public func <- <T : WritableObservable & UnownableObservable> (x: inout T, y: T.ValueType) {
x.value = y
}
// for observable references on variables or constants
public func <- <T : WritableObservable & OwnableObservable> (x: T, y: T.ValueType) {
var z = x
z.value = y
}
public postfix func ^ <T : AnyObservable> (x: T) -> T.ValueType {
return x.value
}
//
// TupleObservable.swift
// Observable-Swift
//
// Created by Leszek Ślażyński on 20/06/14.
// Copyright (c) 2014 Leszek Ślażyński. All rights reserved.
//
public class PairObservable<O1: AnyObservable, O2: AnyObservable> : OwnableObservable {
public typealias T1 = O1.ValueType
public typealias T2 = O2.ValueType
public typealias ValueType = (T1, T2)
public private(set) var beforeChange = EventReference<ValueChange<(T1, T2)>>()
public private(set) var afterChange = EventReference<ValueChange<(T1, T2)>>()
internal var first : T1
internal var second : T2
public var value : (T1, T2) { return (first, second) }
private let _base1 : O1
private let _base2 : O2
public init (_ o1: O1, _ o2: O2) {
_base1 = o1
_base2 = o2
first = o1.value
second = o2.value
o1.beforeChange.add(owner: self) { [weak self] c1 in
let oldV = (c1.oldValue, self!.second)
let newV = (c1.newValue, self!.second)
let change = ValueChange(oldV, newV)
self!.beforeChange.notify(change)
}
o1.afterChange.add(owner: self) { [weak self] c1 in
let nV1 = c1.newValue
self!.first = nV1
let oldV = (c1.oldValue, self!.second)
let newV = (c1.newValue, self!.second)
let change = ValueChange(oldV, newV)
self!.afterChange.notify(change)
}
o2.beforeChange.add(owner: self) { [weak self] c2 in
let oldV = (self!.first, c2.oldValue)
let newV = (self!.first, c2.newValue)
let change = ValueChange(oldV, newV)
self!.beforeChange.notify(change)
}
o2.afterChange.add(owner: self) { [weak self] c2 in
let nV2 = c2.newValue
self!.second = nV2
let oldV = (self!.first, c2.oldValue)
let newV = (self!.first, c2.newValue)
let change = ValueChange(oldV, newV)
self!.afterChange.notify(change)
}
}
}
public func & <O1 : AnyObservable, O2: AnyObservable> (x: O1, y: O2) -> PairObservable<O1, O2> {
return PairObservable(x, y)
}
Copyright (c) 2018 igiu1988 <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.
# GM-Swift-Observable
[![CI Status](http://img.shields.io/travis/igiu1988/GM-Swift-Observable.svg?style=flat)](https://travis-ci.org/igiu1988/GM-Swift-Observable)
[![Version](https://img.shields.io/cocoapods/v/GM-Swift-Observable.svg?style=flat)](http://cocoapods.org/pods/GM-Swift-Observable)
[![License](https://img.shields.io/cocoapods/l/GM-Swift-Observable.svg?style=flat)](http://cocoapods.org/pods/GM-Swift-Observable)
[![Platform](https://img.shields.io/cocoapods/p/GM-Swift-Observable.svg?style=flat)](http://cocoapods.org/pods/GM-Swift-Observable)
## Example
To run the example project, clone the repo, and run `pod install` from the Example directory first.
## Requirements
## Installation
GM-Swift-Observable is available through [CocoaPods](http://cocoapods.org). To install
it, simply add the following line to your Podfile:
```ruby
pod 'GM-Swift-Observable'
```
## Author
igiu1988, wangyang@wanmeizhensuo.com
## License
GM-Swift-Observable is available under the MIT license. See the LICENSE file for more info.
//
// GMBaseController.swift
// Gengmei
//
// Created by wangyang on 16/3/21.
// Copyright © 2016年 更美互动信息科技有限公司. All rights reserved.
//
import UIKit
import GMPhobos
import GMKit
private var navigationBarAssociationKey: UInt8 = 0
open class GMBaseController: UIViewController, GMNavigationBarDelegate, GMEmptyViewDelegate {
public var emptyView = GMEmptyView(frame:CGRect(x: 0, y: 0, width: Constant.screenWidth, height: Constant.screenHeight))
/**
默认值为false,表示导航栏自动被WMBaseViewController管理,永远在所有的view最上方。
设置为true时表示因为特殊需求,开发人员需要自己控制导航,并且需要在viewDidLoad中确定navigationBar的位置
*/
public var controlNavigationByYou = false
public override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) {
super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
initController()
}
public required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
initController()
}
open func initController() {
// 在 initController 中初始化自定义导航栏有很大好处。至少可以保证视图被push之前就可以访问navigationBar,以配置title等属性
customNavigationBar()
edgesForExtendedLayout = UIRectEdge()
automaticallyAdjustsScrollViewInsets = false
}
open override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = UIColor.background
if responds(to: #selector(setter: UIViewController.edgesForExtendedLayout)) {
edgesForExtendedLayout = UIRectEdge()
}
addNavigationBar()
hideLeftButtonForRootController()
initReferer()
initRefererLink()
initReferrerTabName()
setupEmptyView()
}
open override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
// 保证即使在loading的时候,仍然可以后退
if !controlNavigationByYou {
view.bringSubview(toFront: navigationBar)
}
}
open override func didMove(toParentViewController parent: UIViewController?) {
// 作为childController使用,但不是被push到 UINavigationController时,隐藏导航栏
super.didMove(toParentViewController: parent)
if parent != nil && !(parent is UINavigationController) {
navigationBar.isHidden = true
}
}
open override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
// 导航栏隐藏在 viewWillAppear 里控制的原因是在viewDidLoad时,有可能 navigationController 与 self 并没有关系
if navigationController != nil {
navigationController!.isNavigationBarHidden = true
}
Phobos.sharedClient().onPVStart(self)
}
open override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
if !pageName.isEmpty {
Phobos.sharedClient().onPVEnd(self)
}
}
open override var preferredStatusBarStyle: UIStatusBarStyle {
if #available(iOS 13.0, *) {
return UIStatusBarStyle.darkContent
} else {
return UIStatusBarStyle.default
}
}
deinit {
NotificationCenter.default.removeObserver(self)
}
// MARK: - 导航相关
public var navigationBar: GMNavigationBar {
get {
return objc_getAssociatedObject(self, &navigationBarAssociationKey) as! GMNavigationBar
}
set(newValue) {
objc_setAssociatedObject(self, &navigationBarAssociationKey, newValue, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN)
}
}
public func customNavigationBar() {
navigationBar = GMNavigationBar()
}
public func addNavigationBar() {
navigationBar.delegate = self
view.addSubview(navigationBar)
}
public func hideLeftButtonForRootController() {
if navigationController != nil && navigationController!.viewControllers.count == 1 {
navigationBar.leftButton.isHidden = true
}
}
/// 对于没有导航栏,但是有backButton的controller,可以使用这个方法创建backButton。坐标需要指定
open func createBackButton(_ imageName: String) -> UIButton {
let button = GMBaseNavigationButton(type: .custom)
button.setImage(UIImage(named: imageName), for: .normal)
button.sizeToFit()
button.adaptiveHotAreaWidth = 70
button.addTarget(self, action: #selector(GMBaseController.backAction(_:)), for: .touchUpInside)
return button
}
open func pushViewController(_ controller: UIViewController) {
navigationController?.pushViewController(controller, animated: true)
}
open func rightButtonClicked(_ button: UIButton) {
}
open func backAction(_ button: UIButton) {
// parentViewController 是 UINavigationController 说明是极有可能是 push 进来的,需要进一步判断
if (parent?.isKind(of: UINavigationController.classForCoder())) != nil {
let navigation = (parent as! UINavigationController)
// 如果 presentingViewController 存在,表示有人 present 自己,再并上条件已经在导航器上rootViewController时,直接 dismiss 就好了
if presentingViewController != nil && navigation.viewControllers.count == 1 {
dismiss(animated: true, completion: nil)
} else {
_ = navigationController?.popViewController(animated: true)
}
} else {
dismiss(animated: true, completion: nil)
}
}
}
@objc extension GMBaseController {
// MARK: - GMEmptyView
@objc open func setupEmptyView() {
emptyView.isHidden = true
emptyView.delegate = self
view.addSubview(emptyView)
emptyView.snp.makeConstraints { (make) in
if let _ = self.parent as? UINavigationController {
make.top.equalTo(UIApplication.shared.statusBarFrame.height)
} else {
make.top.equalTo(0)
}
make.left.right.bottom.equalTo(0)
}
}
@objc open func showEmptyView(_ type: GMEmptyViewType) {
emptyView.type = type
emptyView.isHidden = false
view.bringSubview(toFront: emptyView)
}
@objc open func hideEmptyView() {
emptyView.isHidden = true
}
@objc open func emptyViewDidClickReload() {
if let me = self as? Refreshable {
me.refreshList()
}
}
}
//
// GMBaseNavigationButton.swift
// Pods
//
// Created by wangyang on 2016/10/21.
//
//
import Foundation
public class GMBaseNavigationButton: UIButton {
// 常常导航栏上的切图资源都比较小,所以需要代码来扩大其响应范围
var adaptiveHotAreaWidth: CGFloat = 0
var adaptiveHotAreaHeight: CGFloat = 0
override public func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
let widthDelta = max(adaptiveHotAreaWidth - bounds.size.width, 0)
let heightDelta = max(adaptiveHotAreaHeight - bounds.size.height, 0)
bounds = bounds.insetBy(dx: -0.5 * widthDelta, dy: -0.5 * heightDelta)
return bounds.contains(point)
}
}
//
// GMBaseObject.swift
// GengmeiDoctor
//
// Created by Thierry on 16/4/21.
// Copyright © 2016年 wanmeizhensuo. All rights reserved.
//
import Foundation
import EVReflection
open class GMBaseObject: EVObject {
/**
Override super setValue method, ignore warning
- parameter value:
- parameter key:
*/
override open func setValue(_ value: Any!, forUndefinedKey key: String) {}
override open func propertyMapping() -> [(keyInObject: String?, keyInResource: String?)] {
return [("desc", "description")]
}
}
//
// GMBaseUtil.swift
// Pods
//
// Created by wangyang on 2016/10/21.
//
//
import Foundation
import SnapKit
public extension UIView {
public var snp_safeBottom: ConstraintItem {
if #available(iOS 11.0, *) {
return safeAreaLayoutGuide.snp.bottom
} else {
return snp.bottom
}
}
}
public extension ConstraintMaker {
public func gm_bottom(to superView: UIView, height: CGFloat) {
top.equalTo(superView.snp_safeBottom).offset(-height)
bottom.equalTo(0)
}
}
//
// GMFetchViewModel.swift
// test
//
// Created by wangyang on 16/3/16.
// Copyright © 2016年 北京更美互动信息科技有限公司. All rights reserved.
//
import Foundation
import GM_Swift_Observable
import GMNetworking
public extension NSDictionary {
/// 适用于api返回的json快速取出data下的字典
public var dic: NSDictionary {
return self["data"] as! NSDictionary
}
/// 适用于api返回的json快速取出data下的数组
public var array: [NSDictionary] {
return self["data"] as! [NSDictionary]
}
}
@available(*, deprecated, message: "NetworkResult闭包不在使用,考虑重构使用该闭包的方法为更简洁的方式")
public typealias NetworkResult = (_ result: Bool, _ message: String) -> Void
@available(*, deprecated, message: "NetworkSuccess闭包不在使用,考虑重构使用该闭包的方法为更简洁的方式")
public typealias NetworkSuccess = (_ responseObject: [String: Any], _ message: String) -> Void
@available(*, deprecated, message: "NetworkFailed闭包不在使用,考虑重构使用该闭包的方法为更简洁的方式")
public typealias NetworkFailed = (_ message: String) -> Void
@available(*, deprecated, message: "NetworkEmpty闭包不在使用,考虑重构使用该闭包的方法为更简洁的方式")
public typealias NetworkEmpty = (_ errorCode: Int, _ message: String) -> Void
open class GMFetchViewModel<T>: ViewModelProtocol {
open var dataArray = [T]()
open var fetchDataResult: Observable<Int> = Observable(-1)
open var url = ""
open var startNum = 0
open var parameters = [String: Any]()
open var message = ""
public var response: GMResponse<Any>!
/// 去重被去掉的个数
open var deleteCount = 0
/**
* @author wangyang, 15-11-23 15:11:30
*
* @brief 去重字典。保存那些已经加载过的数据的 key,value 都为 YES。详见 _deduplicationKey
因为服务器返回的数据有可能是重复的,所以添加一个字典,新添加的数据的唯一标识放在这里,追加的数据要从这里检查,如果存在,就不添加
*
* @since 5.5.0
*/
open var deduplicationDic = [AnyHashable: Any]()
/**
* @author wangyang, 15-11-23 15:11:16
*
* @brief 去重时需要用到的实体属性 key。例如帖子列表页使用 id 属性来去重,那么 _deduplicationKey = @"id"
* @note 默认为 nil,如果不为 nil,就会启动去重功能
* @since 5.5.0
*/
open var deduplicationKey = ""
public required init() {
parameters = ["start_num": startNum, "count": 10 ]
}
open func buildParameters() {
parameters["start_num"] = startNum
}
open func fetchRemoteData() {
if self.url.isEmpty {
return
}
buildParameters()
GMNetworking.request(url, .get, parameters) { [weak self] (response: GMResponse<Any>) in
guard let safeSelf = self else { return }
safeSelf.response = response
if !response.isSuccess {
safeSelf.message = "哎呀!出错了!"
safeSelf.fetchDataResult.value = 2
return
}
guard let json = (try? JSONSerialization.jsonObject(with: response.responseData!, options: .allowFragments)) as? NSDictionary else {
return
}
if safeSelf.shouldClearDataForResponse(json) {
safeSelf.clearData()
}
safeSelf.message = response.message
if response.error == 0 {
safeSelf.build(json)
}
safeSelf.fetchDataResult.value = response.error
}
}
open func build(_ response: NSDictionary) {}
open func shouldClearDataForResponse(_ response: NSDictionary) -> Bool {
return startNum == 0
}
open func clearData() {
startNum = 0
dataArray.removeAll()
deduplicationDic.removeAll()
}
open func isDataArrayEmpty() -> Bool {
return dataArray.count == 0
}
open func willRefresh() {
startNum = 0
}
open func willLoadMore() {
startNum = dataArray.count + deleteCount
}
/**
给viewModel加下标访问的好处就是可以使用泛型
- parameter indexPath
- returns: 泛型
*/
open subscript(indexPath: IndexPath) -> T {
// 这种写法可以用来试探dataArray是否是二维数组,是的话,返回indexPath对应的object
if let first = dataArray[indexPath.section] as? [AnyObject] {
return first[indexPath.row] as! T
}
// 上面的代码没有成功的话,说明是一维数组
assert(dataArray.count >= indexPath.row, "indexPath.row(\(indexPath.row))不能大于dataArray.count(\(dataArray.count))")
return dataArray[indexPath.row]
}
open func deduplicate<T>(_ array: [[String: Any]], buildModelBlock: (NSDictionary) -> (T)) -> [T] {
if deduplicationKey.isEmpty {
return [T]()
}
var result = [T]()
for obj in array {
// 因为value会作为 deduplicationDic 的key,所以必须符合 Hashable 协议。
// 下面使用 NSObject 原因有两个。第一:value是从json解析来的,所以肯定是某个 NSObject;第二:NSObject 是符合 Hashable 协议的
guard let value = obj[deduplicationKey] as? NSObject else {
assert(false, "待去重的value不可以为空")
return [T]()
}
if deduplicationDic[value] == nil {
let object = buildModelBlock(obj as NSDictionary)
result.append(object)
deduplicationDic[value] = true
} else {
deleteCount += 1
}
}
return result
}
}
/// 在ListProtocol中起重要作用
public protocol ViewModelProtocol {
var url: String { get set }
var startNum: Int { get set }
var message: String { get set }
var fetchDataResult: Observable<Int> { get set }
/**
协议中的构造器要求,必须实现init方法
*/
init()
func clearData()
func isDataArrayEmpty() -> Bool
func fetchRemoteData()
func willRefresh()
func willLoadMore()
}
//
// GMListProtocol.swift
// test
//
// Created by wangyang on 16/3/15.
// Copyright © 2016年 北京更美互动信息科技有限公司. All rights reserved.
//
import UIKit
import GM_Swift_Observable
import SnapKit
import GMRefresh
import GMHud
/// 方便GMBaseController能够访问到refreshList这个方法
public protocol Refreshable {
/**
刷新UI列表
*/
func refreshList()
}
/// 任何业务controller都可以挂上这个协议以实现list controller的功能
public protocol GMListProtocol: class, Refreshable {
associatedtype ViewModelType: ViewModelProtocol // 这里需要一个实现 ViewModelType 的viewModel实例,通过这样一个泛型,可以适应不同的业务需求
var viewModel: ViewModelType { get set } // 需要controller自己实例化
/**
向controller.view中添加一个table,并根据 fetchNow 的情况来判断是否自动请求网络
- parameter tableStyle: UITableViewStyle
- parameter now: true表示立即请求风格
*/
func addTableView(style tableStyle: UITableViewStyle, fetchNow now: Bool)
func addCollectionView(_ layout: UICollectionViewLayout, fetchNow now: Bool)
/**
主动调用该方法以请求数据
*/
func fetchData()
/**
请求完网络,会执行该方法以更新非table cell、section UI
*/
func updateOtherUIData()
/**
上拉刷新时会调用
*/
func footerRefreshing()
/**
下拉刷新时会调用
*/
func headerRefreshing()
/**
设置是否需要刷新和加载更多,默认是都需要。
另外:使用方法而非属性。少些变量,多些方法,这样可以让协议更简单一些
- parameter need: 是否需要
*/
func setNeedHeaderRefresh(_ need: Bool)
func setNeedFooterRefresh(_ need: Bool)
}
private var emptyViewAssociationKey: UInt8 = 0
private var tableViewAssociationKey: UInt8 = 0
private var collectionViewAssociationKey: UInt8 = 0
private var listViewAssociationKey: UInt8 = 0
private extension UIScrollView {
func reloadList() {
(self as? UITableView)?.reloadData()
(self as? UICollectionView)?.reloadData()
}
}
public extension GMListProtocol where Self: GMBaseController {
public var tableView: UITableView {
get {
return objc_getAssociatedObject(self, &tableViewAssociationKey) as! UITableView
}
set(newValue) {
objc_setAssociatedObject(self, &tableViewAssociationKey, newValue, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN)
}
}
public var collectionView: UICollectionView {
get {
return objc_getAssociatedObject(self, &collectionViewAssociationKey) as! UICollectionView
}
set(newValue) {
objc_setAssociatedObject(self, &collectionViewAssociationKey, newValue, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN)
}
}
fileprivate var listView: UIScrollView {
get {
return objc_getAssociatedObject(self, &listViewAssociationKey) as! UIScrollView
}
set(newValue) {
objc_setAssociatedObject(self, &listViewAssociationKey, newValue, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN)
}
}
public func addTableView(style tableStyle: UITableViewStyle, fetchNow now: Bool) {
tableView = UITableView(frame: self.view.bounds, style: tableStyle)
tableView.backgroundColor = UIColor.background
tableView.separatorStyle = .none
tableView.rowHeight = UITableViewAutomaticDimension
tableView.estimatedRowHeight = 80;
tableView.sectionHeaderHeight = UITableViewAutomaticDimension
tableView.sectionFooterHeight = UITableViewAutomaticDimension
if #available(iOS 11.0, *) {
tableView.contentInsetAdjustmentBehavior = .never
}
manager(tableView, fetchNow: now)
}
public func addCollectionView(_ layout: UICollectionViewLayout, fetchNow now: Bool) {
collectionView = UICollectionView(frame: self.view.bounds, collectionViewLayout: layout)
collectionView.backgroundColor = UIColor.separatorLine
manager(collectionView, fetchNow: now)
}
fileprivate func manager(_ list: UIScrollView, fetchNow now: Bool) {
view.addSubview(list)
list.snp.makeConstraints { (make) in
make.left.equalTo(0)
make.right.equalTo(0)
make.top.equalTo(GMNavigationBar.barHeight)
make.bottom.equalTo(0)
}
listView = list
setNeedHeaderRefresh(true)
setNeedFooterRefresh(true)
addKVO()
if now {
fetchData()
}
}
public func fetchData() {
if viewModel.isDataArrayEmpty() {
showLoading(nil)
}
viewModel.fetchRemoteData()
}
fileprivate func addKVO() {
_ = viewModel.fetchDataResult.afterChange += { [weak self] in
self?.observeHandler($1)
}
}
fileprivate func observeHandler(_ newValue: Int) {
hideLoading()
if newValue == 0 {
if viewModel.isDataArrayEmpty() {
showEmptyView(.empty)
} else {
hideEmptyView()
}
} else {
showEmptyView(.exception)
}
listView.reloadList()
// 因为数据源的改变,所以应该先reloadData完UI再endRefreshing
if let mjHeader = self.listView.mj_header, !mjHeader.isHidden {
mjHeader.endRefreshing()
}
if let mjFooter = self.listView.mj_footer, !mjFooter.isHidden {
mjFooter.endRefreshing()
}
updateOtherUIData()
}
public func refreshList() {
viewModel.clearData()
listView.reloadList()
fetchData()
}
public func updateOtherUIData() {
}
public func footerRefreshing() {
}
public func headerRefreshing() {
}
public func setNeedHeaderRefresh(_ need: Bool) {
if need {
let header = GMRefreshHeader()
header.refreshingBlock = { [weak self] in
guard let strongSelf = self else { return }
strongSelf.viewModel.willRefresh()
strongSelf.fetchData()
strongSelf.headerRefreshing()
}
listView.mj_header = header
} else {
listView.mj_header?.isHidden = true
}
}
public func setNeedFooterRefresh(_ need: Bool) {
if need {
let footer = GMRefreshFooter()
footer.stateLabel?.isHidden = true
footer.isRefreshingTitleHidden = true
footer.refreshingBlock = { [weak self] in
// TODO + _deleteCount
guard let strongSelf = self else { return }
strongSelf.viewModel.willLoadMore()
strongSelf.fetchData()
strongSelf.footerRefreshing()
}
listView.mj_footer = footer
} else {
listView.mj_footer?.isHidden = true
}
}
}
//
// WYNavigationBar.swift
// Gengmei
//
// Created by wangyang on 16/5/23.
// Copyright © 2016年 更美互动信息科技有限公司. All rights reserved.
//
import UIKit
import GMKit
@objc public protocol GMNavigationBarDelegate: class {
@objc optional func backAction(_ button: UIButton)
@objc optional func rightButtonClicked(_ button: UIButton)
@objc optional func nearRightButtonClicked(_ button: UIButton)
}
@objcMembers
public class GMNavigationBar: UIView {
// MARK: - 普通属性
public let itemView = UIView(frame: CGRect(x: 0, y: 0, width: Constant.screenWidth, height: 44))
public let titleLabel = UILabel()
public let leftButton = GMBaseNavigationButton(type: .custom)
public let rightButton = GMBaseNavigationButton(type: .custom)
public let nearRightButton = GMBaseNavigationButton(type: .custom)
public weak var delegate: GMNavigationBarDelegate?
public var isBarShowShadow = true {
didSet {
if isBarShowShadow {
self.layer.shadowColor = UIColor.init(white: 0, alpha: 0.1).cgColor
} else {
self.layer.shadowColor = UIColor.clear.cgColor
}
}
}
// MARK: - 属性、属性观察器
public var titleView: UIView? {
didSet {
if let newView = titleView {
if newView != oldValue {
oldValue?.removeFromSuperview()
}
itemView.insertSubview(newView, at: 0)
} else {
oldValue?.removeFromSuperview()
}
}
}
public var title: String? {
willSet {
titleLabel.isHidden = (newValue == nil)
titleLabel.text = newValue
}
}
public var leftIcon: String? {
willSet {
setImage(newValue, for: leftButton)
}
}
public var rightIcon: String? {
willSet {
setImage(newValue, for: rightButton)
}
}
public var leftTitle: String? {
willSet {
leftButton.isHidden = false
leftButton.setTitle(newValue, for: UIControlState())
leftButton.setImage(nil, for: UIControlState())
}
}
public var rightTitle: String? {
willSet {
rightButton.isHidden = false
rightButton.setTitle(newValue, for: UIControlState())
}
}
public var nearRightIcon: String? {
willSet {
setImage(newValue, for: nearRightButton)
}
}
public var nearRightTitle: String? {
willSet {
nearRightButton.isHidden = false
nearRightButton.setTitle(newValue, for: UIControlState())
}
}
// MARK: - 方法
override public init(frame: CGRect) {
super.init(frame: CGRect(x: 0, y: 0, width: Constant.screenWidth, height: GMNavigationBar.barHeight))
setup()
}
required public init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setup()
}
public func setup() {
self.layer.masksToBounds = false
self.layer.shadowOpacity = 1.0
self.layer.shadowRadius = 2.0
self.layer.shadowColor = UIColor.init(white: 0, alpha: 0.1).cgColor
self.layer.shadowOffset = CGSize(width: 0, height: Constant.onePixel);
self.backgroundColor = UIColor.white
leftIcon = "back"
leftButton.adaptiveHotAreaWidth = 70
leftButton.setTitleColor(UIColor.headlineText, for: .normal)
leftButton.titleLabel?.font = UIFont.gmFont(14)
leftButton.addTarget(self, action: #selector(leftAction(_:)), for: .touchUpInside)
rightButton.addTarget(self, action: #selector(rightAction(_:)), for: .touchUpInside)
rightButton.isHidden = true
rightButton.setTitleColor(UIColor.headlineText, for: .normal)
rightButton.titleLabel?.font = UIFont.gmFont(14)
rightButton.adaptiveHotAreaWidth = 40
nearRightButton.isHidden = true
nearRightButton.addTarget(self, action: #selector(nearRightAction(_:)), for: .touchUpInside)
nearRightButton.setTitleColor(UIColor.headlineText, for: .normal)
nearRightButton.titleLabel?.font = UIFont.gmFont(14)
nearRightButton.adaptiveHotAreaWidth = 40
titleLabel.isHidden = true
titleLabel.textAlignment = .center
titleLabel.backgroundColor = UIColor.clear
titleLabel.textColor = UIColor.headlineText
titleLabel.font = UIFont.gmFont(18)
addSubview(itemView)
itemView.addSubview(titleLabel)
itemView.addSubview(leftButton)
itemView.addSubview(rightButton)
itemView.addSubview(nearRightButton)
itemView.snp.makeConstraints { (make) in
make.left.right.bottom.equalTo(0)
make.height.equalTo(44)
}
titleLabel.snp.makeConstraints { (make) in
make.edges.equalTo(UIEdgeInsets.zero)
}
leftButton.snp.makeConstraints { (make) in
make.left.equalTo(20)
make.centerY.equalToSuperview()
}
rightButton.snp.makeConstraints { (make) in
make.right.equalTo(-20)
make.centerY.equalToSuperview()
}
nearRightButton.snp.makeConstraints { (make) in
make.right.equalTo(rightButton.snp.left).offset(-20)
make.centerY.equalToSuperview()
}
}
public func hideAllItems() {
titleLabel.isHidden = true
leftButton.isHidden = true
rightButton.isHidden = true
nearRightButton.isHidden = true
}
override public func layoutSubviews() {
super.layoutSubviews()
titleView?.center = CGPoint(x: itemView.bounds.size.width/2, y: itemView.bounds.size.height/2)
}
}
// MARK: - 私有
@objc extension GMNavigationBar {
fileprivate func setImage(_ iconName: String?, for button: GMBaseNavigationButton) {
if let icon = iconName, let image = UIImage(named: icon) {
button.isHidden = false
button.setImage(image, for: UIControlState())
} else {
button.isHidden = true
assert(iconName != nil && iconName!.characters.count > 0, "找不到图片")
}
}
@objc fileprivate func leftAction(_ button: UIButton) {
delegate?.backAction?(button)
}
@objc fileprivate func rightAction(_ button: UIButton) {
delegate?.rightButtonClicked?(button)
}
@objc fileprivate func nearRightAction(_ button: UIButton) {
delegate?.nearRightButtonClicked?(button)
}
}
public extension GMNavigationBar {
/// 状态栏的高度
@objc public static var statusBarHeight: CGFloat {
let statusBatr:CGFloat = Constant.screenHeight/Constant.screenWidth >= 2.0 ? 44 : 20
return statusBatr;
}
/// navigationBar的高度
@objc public static let barHeight = statusBarHeight + 44
/// item的centerY
@objc public static let navigationItemCenterY = statusBarHeight + 44/2.0
}
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.
# GMBaseSwift
[![CI Status](http://img.shields.io/travis/wangyang/GMBaseSwift.svg?style=flat)](https://travis-ci.org/wangyang/GMBaseSwift)
[![Version](https://img.shields.io/cocoapods/v/GMBaseSwift.svg?style=flat)](http://cocoapods.org/pods/GMBaseSwift)
[![License](https://img.shields.io/cocoapods/l/GMBaseSwift.svg?style=flat)](http://cocoapods.org/pods/GMBaseSwift)
[![Platform](https://img.shields.io/cocoapods/p/GMBaseSwift.svg?style=flat)](http://cocoapods.org/pods/GMBaseSwift)
## Example
To run the example project, clone the repo, and run `pod install` from the Example directory first.
## Requirements
## Installation
GMBaseSwift is available through [CocoaPods](http://cocoapods.org). To install
it, simply add the following line to your Podfile:
```ruby
pod "GMBaseSwift"
```
## Author
wangyang, wangyang@wanmeizhensuo.com
## License
GMBaseSwift is available under the MIT license. See the LICENSE file for more info.
//
// Dictionary+GM.swift
// Gengmei
//
// Created by Thierry on 16/6/15.
// Copyright © 2016年 更美互动信息科技有限公司. All rights reserved.
//
import Foundation
public extension Dictionary {
func merge(_ dict: Dictionary) -> Dictionary {
var mutableCopy = self
for (key, value) in dict {
// If both dictionaries have a value for same key, the value of the other dictionary is used.
mutableCopy[key] = value
}
return mutableCopy
}
}
//
// NSDictionary+LogCategory.h
//
//
// Created by liulujie on 18/1/10.
// Copyright © 2018年 RedSoft. All rights reserved.
//
#import <Foundation/Foundation.h>
// 如果需要关掉兼容中文的打印,把下面的宏注释掉即可
#define UseLogChinese
@interface NSArray (Log)
@end
@interface NSDictionary (Log)
@end
//
// NSDictionary+LogCategory.m
//
//
// Created by liulujie on 18/1/10.
// Copyright © 2018年 RedSoft. All rights reserved.
//
#import "LogCategory.h"
#import <objc/runtime.h>
@implementation NSArray (Log)
#ifdef UseLogChinese
- (NSString *)debugDescription {
return [NSString stringWithFormat:@"<%@ %p> %@", NSStringFromClass([self class]), self, [self descriptionWithLocale:nil]];
}
- (NSString *)description {
return [NSString stringWithFormat:@"<%@ %p> %@", NSStringFromClass([self class]), self, [self descriptionWithLocale:nil]];
}
- (NSString *)descriptionWithLocale:(id)locale {
NSMutableString * string = [NSMutableString string];
[string appendString:@"[\n"];
NSUInteger count = self.count;
[self enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
NSString * temp = nil;
if ([obj respondsToSelector:@selector(descriptionWithLocale:)]) {
temp = [obj performSelector:@selector(descriptionWithLocale:) withObject:locale];
temp = [temp stringByReplacingOccurrencesOfString:@"\n" withString:@"\n\t"];
} else {
temp = [obj performSelector:@selector(description) withObject:nil];
if ([obj isKindOfClass:[NSString class]]) {
temp = [NSString stringWithFormat:@"\"%@\"", temp];
}
}
[string appendFormat:@"\t%@", temp];
if (idx+1 != count) {
[string appendString:@","];
}
[string appendString:@"\n"];
}];
[string appendString:@"]"];
return string;
}
#endif
@end
@implementation NSDictionary (Log)
#ifdef UseLogChinese
- (NSString *)debugDescription {
return [NSString stringWithFormat:@"<%@ %p> %@", NSStringFromClass([self class]), self, [self descriptionWithLocale:nil]];
}
- (NSString *)description {
return [NSString stringWithFormat:@"<%@ %p> %@", NSStringFromClass([self class]), self, [self descriptionWithLocale:nil]];
}
- (NSString *)descriptionWithLocale:(id)locale {
NSMutableString * string = [NSMutableString string];
[string appendString:@"{\n"];
NSUInteger count = self.allKeys.count;
for (id key in self.allKeys) {
NSInteger index = [self.allKeys indexOfObject:key];
id value = [self objectForKey:key];
NSString * temp = nil;
if ([value respondsToSelector:@selector(descriptionWithLocale:)]) {
temp = [value performSelector:@selector(descriptionWithLocale:) withObject:locale];
temp = [temp stringByReplacingOccurrencesOfString:@"\n" withString:@"\n\t"];
} else {
temp = [value performSelector:@selector(description) withObject:nil];
if ([value isKindOfClass:[NSString class]]) {
temp = [NSString stringWithFormat:@"\"%@\"", temp];
}
}
[string appendFormat:@"\t%@ = %@", key, temp];
if (index+1 != count) {
[string appendString:@";"];
}
[string appendString:@"\n"];
}
[string appendString:@"}"];
return string;
}
#endif
@end
//
// NSArray+GM.h
// Gengmei
//
// Created by wangyang on 2018/4/9.
// Copyright © 2018年 更美互动信息科技有限公司. All rights reserved.
//
#import <Foundation/Foundation.h>
@interface NSArray (GM)
// 循环对比数组中的元素,遇到第一个符合条件的就删除,并停止遍历
- (NSArray *)arrayFromDeleteCondition:(BOOL (^)(id object))compare;
- (NSArray *)mapObjectsUsingBlock:(id (^)(id obj, NSUInteger idx))block;
@end
@interface NSMutableArray (GM)
// 循环对比数组中的元素,遇到第一个符合条件的就删除,并停止遍历
- (void)deleteCondition:(BOOL (^)(id object))compare;
@end
//
// NSArray+GM.m
// Gengmei
//
// Created by wangyang on 2018/4/9.
// Copyright © 2018年 更美互动信息科技有限公司. All rights reserved.
//
#import "NSArray+GM.h"
@implementation NSArray (GM)
- (NSArray *)arrayFromDeleteCondition:(BOOL (^)(id object))compare {
NSMutableArray *array;
if ([self isMemberOfClass:[NSMutableArray class]]) {
array = (NSMutableArray *)self;
} else {
array = [self mutableCopy];
}
NSInteger deleteIndex = -1;
for (int i = 0; i < array.count; i++) {
if (compare(array[i])) {
deleteIndex = i;
break;
}
}
if (deleteIndex != -1) {
[array removeObjectAtIndex:deleteIndex];
}
return array;
}
// 对于不想添加到结果中的情况,就返回 nil
- (NSArray *)mapObjectsUsingBlock:(id (^)(id obj, NSUInteger idx))block {
NSMutableArray *result = [NSMutableArray array];
[self enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
id value = block(obj, idx);
if (value) {
[result addObject:value];
}
}];
return result;
}
@end
@implementation NSMutableArray (GM)
- (void)deleteCondition:(BOOL (^)(id object))compare {
NSInteger deleteIndex = -1;
for (int i = 0; i < self.count; i++) {
if (compare(self[i])) {
deleteIndex = i;
break;
}
}
if (deleteIndex != -1) {
[self removeObjectAtIndex:deleteIndex];
}
}
@end
//
// NSAttributedString+GMSize.h
// Gengmei
//
// Created by wangyang on 7/22/15.
// Copyright (c) 2015 Wanmeichuangyi. All rights reserved.
//
#import <Foundation/Foundation.h>
@interface NSAttributedString (GMSize)
+ (NSMutableAttributedString *)string:(NSString *)string font:(UIFont *)font color:(UIColor *)color shadow:(BOOL)needShadow;
/**
实际调用 [self attributedStringWithString:string trimBothEndSpace:YES trimInnerReturn:YES font:font color:color lineSpacing:0];
*/
+ (NSMutableAttributedString *)attributedStringWithString:(NSString *)string font:(UIFont *)font color:(UIColor *)color;
+ (NSMutableAttributedString *)attributedStringWithString:(NSString *)string font:(UIFont *)font color:(UIColor *)color paragraphStyle:(NSMutableParagraphStyle *)paragraphStyle;
/**
* @brief 适用于为整个 string 添加属性,其中的 paragraphStyle 为 mutable 的,可以取出来再次修改
@note 最佳使用方法: 用该方法创建 attributedString,再使用 sizeForBoundingRectSize: 方法来计算大小.
@note 默认为 TruncatingTail
*/
+ (NSMutableAttributedString *)attributedStringWithString:(NSString *)string trimBothEndSpace:(BOOL)tripSpace trimInnerReturn:(BOOL)trimReturn font:(UIFont *)font color:(UIColor *)color lineSpacing:(CGFloat)spacing;
/**
* @brief 使用该方法返回 attributedString 的大小。比如指定{320, 99},那么就会返回一个不超过该大小的 size,且宽高都经过 ceilf 处理
*
* @param maxSize 指定的最大的 string 大小。
*
* @return 经过 ceilf 处理的最大 size
*/
- (CGSize)sizeForBoundingRectSize:(CGSize)maxSize;
@end
@interface NSMutableAttributedString (GM)
- (void)addJustifiedAligment;
@end
@interface NSMutableAttributedString (Attributed)
- (NSMutableAttributedString *)addColor:(UIColor *)color;
- (NSMutableAttributedString *)addLineSpace:(NSInteger)space;
- (NSMutableAttributedString *)addShadow:(CGFloat)blur;
@end
//
// NSAttributedString+GMSize.m
// Gengmei
//
// Created by wangyang on 7/22/15.
// Copyright (c) 2015 Wanmeichuangyi. All rights reserved.
//
#import "NSAttributedString+GMSize.h"
#import "NSNull+Empty.h"
#import "NSString+GM.h"
@implementation NSAttributedString (GMSize)
+ (NSMutableAttributedString *)string:(NSString *)string font:(UIFont *)font color:(UIColor *)color shadow:(BOOL)needShadow{
if (!font) {
NSAssert(0, @"请传递一个正常的字体参数");
}
if (!color) {
NSAssert(0, @"请传递一个正常的字体参数");
}
if (![string isNonEmpty]) {
return [[NSMutableAttributedString alloc] initWithString:@"" attributes:nil];;
}
NSMutableDictionary *stringAttribute = [@{NSForegroundColorAttributeName:color,
NSFontAttributeName:font} mutableCopy];
if (needShadow) {
NSShadow *shadow = [NSShadow new];
shadow.shadowOffset = CGSizeZero;
shadow.shadowBlurRadius = 5;
stringAttribute[NSShadowAttributeName] = shadow;
}
NSMutableAttributedString *attriString = [[NSMutableAttributedString alloc] initWithString:string attributes:stringAttribute];
return attriString;
}
+ (NSMutableAttributedString *)attributedStringWithString:(NSString *)string font:(UIFont *)font color:(UIColor *)color {
return [self attributedStringWithString:string trimBothEndSpace:YES trimInnerReturn:YES font:font color:color lineSpacing:0];
}
+ (NSMutableAttributedString *)attributedStringWithString:(NSString *)string font:(UIFont *)font color:(UIColor *)color paragraphStyle:(NSMutableParagraphStyle *)paragraphStyle{
NSString *trimString = string;
trimString = [string trimBothEnd];
trimString = [trimString trimInnerReturn];
if (!trimString) {
trimString = @"";
}
NSAttributedString *attributedString = [[NSAttributedString alloc] initWithString:trimString attributes:@{NSFontAttributeName:font, NSForegroundColorAttributeName:color, NSParagraphStyleAttributeName:paragraphStyle}];
return [attributedString mutableCopy];
}
+ (NSMutableAttributedString *)attributedStringWithString:(NSString *)string trimBothEndSpace:(BOOL)tripSpace trimInnerReturn:(BOOL)trimReturn font:(UIFont *)font color:(UIColor *)color lineSpacing:(CGFloat)spacing {
NSMutableParagraphStyle *style = [[NSMutableParagraphStyle alloc] init];
style.lineSpacing = spacing;
style.lineBreakMode = NSLineBreakByTruncatingTail;
style.alignment = NSTextAlignmentLeft;
NSString *trimString = string;
if (tripSpace) {
trimString = [string trimBothEnd];
}
if (trimReturn) {
trimString = [trimString trimInnerReturn];
}
if (!trimString) {
trimString = @"";
}
NSAttributedString *attributedString = [[NSAttributedString alloc] initWithString:trimString attributes:@{NSFontAttributeName:font, NSForegroundColorAttributeName:color, NSParagraphStyleAttributeName:style}];
return [attributedString mutableCopy];
}
- (CGSize)sizeForBoundingRectSize:(CGSize)maxSize{
// mutableCopy 以防修复原来的 string
NSMutableAttributedString *string = [self mutableCopy];
if (![string.string isNonEmpty]) {
return CGSizeZero;
}
// 修改 lineBreakMode 为 NSLineBreakByWordWrapping,因为 Truncate tail 会在 boundingRectWithSize 中指定,在这里指定了,反而会导致 boundingRectWithSize 方法计算不准确
NSMutableParagraphStyle *originStyle = [self attribute:NSParagraphStyleAttributeName atIndex:0 effectiveRange:NULL];
originStyle.lineBreakMode = NSLineBreakByWordWrapping;
CGSize size = [string boundingRectWithSize:maxSize options:NSStringDrawingUsesLineFragmentOrigin | NSStringDrawingTruncatesLastVisibleLine context:[[NSStringDrawingContext alloc] init]].size;
// 修改 lineBreakMode 为 NSLineBreakByTruncatingTail,不然不显示...
originStyle.lineBreakMode = NSLineBreakByTruncatingTail;
return CGSizeMake(ceilf(size.width), ceilf(size.height));
}
@end
@implementation NSMutableAttributedString (GM)
- (void)addJustifiedAligment {
if (self.length == 0) {
return;
}
NSRange range = NSMakeRange(0, self.length);
NSDictionary *dic = [self attributesAtIndex:0 effectiveRange:&range];
NSMutableParagraphStyle *style = dic[NSParagraphStyleAttributeName];
if (!style) {
style = [[NSMutableParagraphStyle alloc] init];
}
style.alignment = NSTextAlignmentJustified;
style.firstLineHeadIndent = 0.001; // important: must have a value to make alignment work
[self addAttribute:NSBaselineOffsetAttributeName value:@0 range:range];
}
@end
@implementation NSMutableAttributedString (Attributed)
- (NSMutableAttributedString *)addColor:(UIColor *)color {
[self addAttributes:@{NSForegroundColorAttributeName: color} range:NSMakeRange(0, self.length)];
return self;
}
- (NSMutableAttributedString *)addLineSpace:(NSInteger)space {
NSMutableParagraphStyle *style = [[NSMutableParagraphStyle alloc] init];
style.lineSpacing = space;
style.lineBreakMode = NSLineBreakByTruncatingTail;
[self addAttributes:@{NSParagraphStyleAttributeName: style} range:NSMakeRange(0, self.length)];
return self;
}
- (NSMutableAttributedString *)addShadow:(CGFloat)blur {
NSShadow *shadow = [NSShadow new];
shadow.shadowOffset = CGSizeZero;
shadow.shadowBlurRadius = blur;
[self addAttributes:@{NSShadowAttributeName: shadow} range:NSMakeRange(0, self.length)];
return self;
}
@end
//
// NSDate+DateFormat.h
// Gengmei
//
// Created by Sean Lee on 4/14/15.
// Copyright (c) 2015 Wanmeichuangyi. All rights reserved.
//
#import <Foundation/Foundation.h>
@interface NSDate (DateFormat)
/**
* @author licong, 16-12-29 16:12:03
*
* 日期格式化成年月日型的字符串(eg:2015-4-14)
*
* @return 返回格式化后的字符串
*
* @since 5.8
*/
-(NSString *)dateFormatToString;
@end
//
// NSDate+DateFormat.m
// Gengmei
//
// Created by Sean Lee on 4/14/15.
// Copyright (c) 2015 Wanmeichuangyi. All rights reserved.
//
#import "NSDate+DateFormat.h"
@implementation NSDate (DateFormat)
-(NSString *)dateFormatToString {
NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
[formatter setDateFormat:@"yyyy-MM-dd"];
return [formatter stringFromDate:self];
}
@end
//
// NSDate+Category.h
// FoJiao
//
// Created by yh on 15/8/31.
// Copyright (c) 2015年 ZSC. All rights reserved.
//
#import <Foundation/Foundation.h>
#define D_MINUTE 60
#define D_HOUR 3600
#define D_DAY 86400
#define D_WEEK 604800
#define D_YEAR 31556926
@interface NSDate (Category)
- (NSString *)timeIntervalDescription;//距离当前的时间间隔描述
- (NSString *)minuteDescription;/*精确到分钟的日期描述*/
- (NSString *)formattedTime;
- (NSString *)formattedDateDescription;//格式化日期描述
- (double)timeIntervalSince1970InMilliSecond;
+ (NSDate *)dateWithTimeIntervalInMilliSecondSince1970:(double)timeIntervalInMilliSecond;
+ (NSString *)dateStringWithTimeIntervalInMilliSecondSince1970:(double)timeIntervalInMilliSecond;
+ (NSString *)dateStringWithTimeIntervalInMilliSecondSince1970:(double)timeIntervalInMilliSecond format:(NSString *)format;
+ (NSString *)formattedTimeFromTimeInterval:(long long)time;
// Relative dates from the current date
+ (NSDate *) dateTomorrow;
+ (NSDate *) dateYesterday;
+ (NSDate *) dateWithDaysFromNow: (NSInteger) days;
+ (NSDate *) dateWithDaysBeforeNow: (NSInteger) days;
+ (NSDate *) dateWithHoursFromNow: (NSInteger) dHours;
+ (NSDate *) dateWithHoursBeforeNow: (NSInteger) dHours;
+ (NSDate *) dateWithMinutesFromNow: (NSInteger) dMinutes;
+ (NSDate *) dateWithMinutesBeforeNow: (NSInteger) dMinutes;
// Comparing dates
//- (BOOL) isEqualToDateIgnoringTime: (NSDate *) aDate;
- (BOOL) isToday;
- (BOOL) isTomorrow;
- (BOOL) isYesterday;
- (BOOL) isSameWeekAsDate: (NSDate *) aDate;
- (BOOL) isThisWeek;
- (BOOL) isNextWeek;
- (BOOL) isLastWeek;
- (BOOL) isSameMonthAsDate: (NSDate *) aDate;
- (BOOL) isThisMonth;
- (BOOL) isSameYearAsDate: (NSDate *) aDate;
- (BOOL) isThisYear;
- (BOOL) isNextYear;
- (BOOL) isLastYear;
- (BOOL) isEarlierThanDate: (NSDate *) aDate;
- (BOOL) isLaterThanDate: (NSDate *) aDate;
- (BOOL) isInFuture;
- (BOOL) isInPast;
// Date roles
- (BOOL) isTypicallyWorkday;
- (BOOL) isTypicallyWeekend;
// Adjusting dates
- (NSDate *) dateByAddingDays: (NSInteger) dDays;
- (NSDate *) dateBySubtractingDays: (NSInteger) dDays;
- (NSDate *) dateByAddingHours: (NSInteger) dHours;
- (NSDate *) dateBySubtractingHours: (NSInteger) dHours;
- (NSDate *) dateByAddingMinutes: (NSInteger) dMinutes;
- (NSDate *) dateBySubtractingMinutes: (NSInteger) dMinutes;
- (NSDate *) dateAtStartOfDay;
// Retrieving intervals
- (NSInteger) minutesAfterDate: (NSDate *) aDate;
- (NSInteger) minutesBeforeDate: (NSDate *) aDate;
- (NSInteger) hoursAfterDate: (NSDate *) aDate;
- (NSInteger) hoursBeforeDate: (NSDate *) aDate;
- (NSInteger) daysAfterDate: (NSDate *) aDate;
- (NSInteger) daysBeforeDate: (NSDate *) aDate;
- (NSInteger)distanceInDaysToDate:(NSDate *)anotherDate;
// Decomposing dates
@property (readonly) NSInteger nearestHour;
@property (readonly) NSInteger hour;
@property (readonly) NSInteger minute;
@property (readonly) NSInteger seconds;
@property (readonly) NSInteger day;
@property (readonly) NSInteger month;
@property (readonly) NSInteger week;
@property (readonly) NSInteger weekday;
@property (readonly) NSInteger nthWeekday; // e.g. 2nd Tuesday of the month == 2
@property (readonly) NSInteger year;
@end
//
// NSDate+Category.m
// FoJiao
//
// Created by yh on 15/8/31.
// Copyright (c) 2015年 ZSC. All rights reserved.
//
#import "NSDate+Category.h"
#import "NSDateFormatter+Category.h"
#define DATE_COMPONENTS (NSYearCalendarUnit| NSMonthCalendarUnit | NSDayCalendarUnit | NSWeekCalendarUnit | NSHourCalendarUnit | NSMinuteCalendarUnit | NSSecondCalendarUnit | NSWeekdayCalendarUnit | NSWeekdayOrdinalCalendarUnit)
#define CURRENT_CALENDAR [NSCalendar currentCalendar]
@implementation NSDate (Category)
/*距离当前的时间间隔描述*/
- (NSString *)timeIntervalDescription {
NSTimeInterval timeInterval = -[self timeIntervalSinceNow];
if (timeInterval < 60) {
return NSLocalizedString(@"NSDateCategory.text1", @"");
} else if (timeInterval < 3600) {
return [NSString stringWithFormat:@"%.f分钟前", timeInterval / 60];
} else if (timeInterval < 86400) {
return [NSString stringWithFormat:@"%.f小时前", timeInterval / 3600];
} else if (timeInterval < 2592000) {//30天内
return [NSString stringWithFormat:@"%.f天前", timeInterval / 86400];
} else if (timeInterval < 31536000) {//30天至1年内
NSDateFormatter *dateFormatter = [NSDateFormatter dateFormatterWithFormat:@"M月d日"];
return [dateFormatter stringFromDate:self];
} else {
return [NSString stringWithFormat:@"%.f年前", timeInterval / 31536000];
}
}
/*精确到分钟的日期描述*/
- (NSString *)minuteDescription {
NSDateFormatter *dateFormatter = [NSDateFormatter dateFormatterWithFormat:@"yyyy-MM-dd"];
NSString *theDay = [dateFormatter stringFromDate:self];//日期的年月日
NSString *currentDay = [dateFormatter stringFromDate:[NSDate date]];//当前年月日
if ([theDay isEqualToString:currentDay]) {//当天
[dateFormatter setDateFormat:@"ah:mm"];
return [dateFormatter stringFromDate:self];
} else if ([[dateFormatter dateFromString:currentDay] timeIntervalSinceDate:[dateFormatter dateFromString:theDay]] == 86400) {//昨天
[dateFormatter setDateFormat:@"ah:mm"];
return [NSString stringWithFormat:@"昨天 %@", [dateFormatter stringFromDate:self]];
} else if ([[dateFormatter dateFromString:currentDay] timeIntervalSinceDate:[dateFormatter dateFromString:theDay]] < 86400 * 7) {//间隔一周内
[dateFormatter setDateFormat:@"EEEE ah:mm"];
return [dateFormatter stringFromDate:self];
} else {//以前
[dateFormatter setDateFormat:@"yyyy-MM-dd ah:mm"];
return [dateFormatter stringFromDate:self];
}
}
/*标准时间日期描述*/
- (NSString *)formattedTime {
NSDateFormatter* formatter = [[NSDateFormatter alloc]init];
[formatter setDateFormat:@"YYYY-MM-dd"];
NSString * dateNow = [formatter stringFromDate:[NSDate date]];
NSDateComponents *components = [[NSDateComponents alloc] init];
[components setDay:[[dateNow substringWithRange:NSMakeRange(8,2)] intValue]];
[components setMonth:[[dateNow substringWithRange:NSMakeRange(5,2)] intValue]];
[components setYear:[[dateNow substringWithRange:NSMakeRange(0,4)] intValue]];
NSCalendar *gregorian = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar];
NSDate *date = [gregorian dateFromComponents:components]; //今天 0点时间
NSInteger hour = [self hoursAfterDate:date];
NSDateFormatter *dateFormatter = nil;
NSString *ret = @"";
//hasAMPM==TURE为12小时制,否则为24小时制
NSString *formatStringForHours = [NSDateFormatter dateFormatFromTemplate:@"j" options:0 locale:[NSLocale currentLocale]];
NSRange containsA = [formatStringForHours rangeOfString:@"a"];
BOOL hasAMPM = containsA.location != NSNotFound;
if (!hasAMPM) { //24小时制
if (hour <= 24 && hour >= 0) {
dateFormatter = [NSDateFormatter dateFormatterWithFormat:@"HH:mm"];
}else if (hour < 0 && hour >= -24) {
dateFormatter = [NSDateFormatter dateFormatterWithFormat:@"昨天HH:mm"];
}else {
dateFormatter = [NSDateFormatter dateFormatterWithFormat:@"yyyy-MM-dd HH:mm"];
}
}else {
if (hour >= 0 && hour <= 6) {
dateFormatter = [NSDateFormatter dateFormatterWithFormat:@"凌晨hh:mm"];
}else if (hour > 6 && hour <=11 ) {
dateFormatter = [NSDateFormatter dateFormatterWithFormat:@"上午hh:mm"];
}else if (hour > 11 && hour <= 17) {
dateFormatter = [NSDateFormatter dateFormatterWithFormat:@"下午hh:mm"];
}else if (hour > 17 && hour <= 24) {
dateFormatter = [NSDateFormatter dateFormatterWithFormat:@"晚上hh:mm"];
}else if (hour < 0 && hour >= -24){
dateFormatter = [NSDateFormatter dateFormatterWithFormat:@"昨天HH:mm"];
}else {
dateFormatter = [NSDateFormatter dateFormatterWithFormat:@"yyyy-MM-dd HH:mm"];
}
}
ret = [dateFormatter stringFromDate:self];
return ret;
}
/*格式化日期描述*/
- (NSString *)formattedDateDescription {
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
[dateFormatter setDateFormat:@"yyyy-MM-dd"];
NSString *theDay = [dateFormatter stringFromDate:self];//日期的年月日
NSString *currentDay = [dateFormatter stringFromDate:[NSDate date]];//当前年月日
NSInteger timeInterval = -[self timeIntervalSinceNow];
if (timeInterval < 60) {
return @"1分钟内";
} else if (timeInterval < 3600) {//1小时内
return [NSString stringWithFormat:@"%ld分钟前", timeInterval / 60];
} else if (timeInterval < 21600) {//6小时内
return [NSString stringWithFormat:@"%ld小时前", timeInterval / 3600];
} else if ([theDay isEqualToString:currentDay]) {//当天
[dateFormatter setDateFormat:@"HH:mm"];
return [NSString stringWithFormat:@"今天 %@", [dateFormatter stringFromDate:self]];
} else if ([[dateFormatter dateFromString:currentDay] timeIntervalSinceDate:[dateFormatter dateFromString:theDay]] == 86400) {//昨天
[dateFormatter setDateFormat:@"HH:mm"];
return [NSString stringWithFormat:@"昨天 %@", [dateFormatter stringFromDate:self]];
} else {//以前
[dateFormatter setDateFormat:@"yyyy-MM-dd HH:mm"];
return [dateFormatter stringFromDate:self];
}
}
- (double)timeIntervalSince1970InMilliSecond {
double ret;
ret = [self timeIntervalSince1970] * 1000;
return ret;
}
+ (NSDate *)dateWithTimeIntervalInMilliSecondSince1970:(double)timeIntervalInMilliSecond {
NSDate *ret = nil;
double timeInterval = timeIntervalInMilliSecond;
// judge if the argument is in secconds(for former data structure).
if(timeIntervalInMilliSecond > 140000000000) {
timeInterval = timeIntervalInMilliSecond / 1000;
}
ret = [NSDate dateWithTimeIntervalSince1970:timeInterval];
return ret;
}
+ (NSString *)dateStringWithTimeIntervalInMilliSecondSince1970:(double)timeIntervalInMilliSecond format:(NSString *)format {
NSDate *date = [self dateWithTimeIntervalInMilliSecondSince1970:timeIntervalInMilliSecond];
NSDateFormatter *formatter = [NSDateFormatter defaultDateFormatter];
[formatter setDateFormat:format];
return [formatter stringFromDate:date];
}
+ (NSString *)dateStringWithTimeIntervalInMilliSecondSince1970:(double)timeIntervalInMilliSecond {
return [self dateStringWithTimeIntervalInMilliSecondSince1970:timeIntervalInMilliSecond format:@"yyyy-MM-dd HH:mm:ss"];
}
+ (NSString *)formattedTimeFromTimeInterval:(long long)time{
return [[NSDate dateWithTimeIntervalInMilliSecondSince1970:time] formattedTime];
}
#pragma mark Relative Dates
+ (NSDate *) dateWithDaysFromNow:(NSInteger)days {
// Thanks, Jim Morrison
return [[NSDate date] dateByAddingDays:days];
}
+ (NSDate *) dateWithDaysBeforeNow:(NSInteger)days {
// Thanks, Jim Morrison
return [[NSDate date] dateBySubtractingDays:days];
}
+ (NSDate *) dateTomorrow {
return [NSDate dateWithDaysFromNow:1];
}
+ (NSDate *) dateYesterday {
return [NSDate dateWithDaysBeforeNow:1];
}
+ (NSDate *) dateWithHoursFromNow:(NSInteger)dHours {
NSTimeInterval aTimeInterval = [[NSDate date] timeIntervalSinceReferenceDate] + D_HOUR * dHours;
NSDate *newDate = [NSDate dateWithTimeIntervalSinceReferenceDate:aTimeInterval];
return newDate;
}
+ (NSDate *) dateWithHoursBeforeNow:(NSInteger)dHours {
NSTimeInterval aTimeInterval = [[NSDate date] timeIntervalSinceReferenceDate] - D_HOUR * dHours;
NSDate *newDate = [NSDate dateWithTimeIntervalSinceReferenceDate:aTimeInterval];
return newDate;
}
+ (NSDate *) dateWithMinutesFromNow:(NSInteger)dMinutes {
NSTimeInterval aTimeInterval = [[NSDate date] timeIntervalSinceReferenceDate] + D_MINUTE * dMinutes;
NSDate *newDate = [NSDate dateWithTimeIntervalSinceReferenceDate:aTimeInterval];
return newDate;
}
+ (NSDate *) dateWithMinutesBeforeNow:(NSInteger)dMinutes {
NSTimeInterval aTimeInterval = [[NSDate date] timeIntervalSinceReferenceDate] - D_MINUTE * dMinutes;
NSDate *newDate = [NSDate dateWithTimeIntervalSinceReferenceDate:aTimeInterval];
return newDate;
}
#pragma mark Comparing Dates
- (BOOL) isEqualToDateIgnoringTime:(NSDate *)aDate {
NSDateComponents *components1 = [CURRENT_CALENDAR components:DATE_COMPONENTS fromDate:self];
NSDateComponents *components2 = [CURRENT_CALENDAR components:DATE_COMPONENTS fromDate:aDate];
return ((components1.year == components2.year) &&
(components1.month == components2.month) &&
(components1.day == components2.day));
}
- (BOOL)isToday {
return [self isEqualToDateIgnoringTime:[NSDate date]];
}
- (BOOL)isTomorrow {
return [self isEqualToDateIgnoringTime:[NSDate dateTomorrow]];
}
- (BOOL)isYesterday {
return [self isEqualToDateIgnoringTime:[NSDate dateYesterday]];
}
// This hard codes the assumption that a week is 7 days
- (BOOL) isSameWeekAsDate: (NSDate *) aDate {
NSDateComponents *components1 = [CURRENT_CALENDAR components:DATE_COMPONENTS fromDate:self];
NSDateComponents *components2 = [CURRENT_CALENDAR components:DATE_COMPONENTS fromDate:aDate];
// Must be same week. 12/31 and 1/1 will both be week "1" if they are in the same week
if (components1.week != components2.week) return NO;
// Must have a time interval under 1 week. Thanks @aclark
return (fabs([self timeIntervalSinceDate:aDate]) < D_WEEK);
}
- (BOOL)isThisWeek {
return [self isSameWeekAsDate:[NSDate date]];
}
- (BOOL)isNextWeek {
NSTimeInterval aTimeInterval = [[NSDate date] timeIntervalSinceReferenceDate] + D_WEEK;
NSDate *newDate = [NSDate dateWithTimeIntervalSinceReferenceDate:aTimeInterval];
return [self isSameWeekAsDate:newDate];
}
- (BOOL)isLastWeek {
NSTimeInterval aTimeInterval = [[NSDate date] timeIntervalSinceReferenceDate] - D_WEEK;
NSDate *newDate = [NSDate dateWithTimeIntervalSinceReferenceDate:aTimeInterval];
return [self isSameWeekAsDate:newDate];
}
// Thanks, mspasov
- (BOOL)isSameMonthAsDate:(NSDate *)aDate {
NSDateComponents *components1 = [CURRENT_CALENDAR components:NSYearCalendarUnit | NSMonthCalendarUnit fromDate:self];
NSDateComponents *components2 = [CURRENT_CALENDAR components:NSYearCalendarUnit | NSMonthCalendarUnit fromDate:aDate];
return ((components1.month == components2.month) &&
(components1.year == components2.year));
}
- (BOOL)isThisMonth {
return [self isSameMonthAsDate:[NSDate date]];
}
- (BOOL)isSameYearAsDate: (NSDate *)aDate {
NSDateComponents *components1 = [CURRENT_CALENDAR components:NSYearCalendarUnit fromDate:self];
NSDateComponents *components2 = [CURRENT_CALENDAR components:NSYearCalendarUnit fromDate:aDate];
return (components1.year == components2.year);
}
- (BOOL)isThisYear {
// Thanks, baspellis
return [self isSameYearAsDate:[NSDate date]];
}
- (BOOL)isNextYear {
NSDateComponents *components1 = [CURRENT_CALENDAR components:NSYearCalendarUnit fromDate:self];
NSDateComponents *components2 = [CURRENT_CALENDAR components:NSYearCalendarUnit fromDate:[NSDate date]];
return (components1.year == (components2.year + 1));
}
- (BOOL)isLastYear {
NSDateComponents *components1 = [CURRENT_CALENDAR components:NSYearCalendarUnit fromDate:self];
NSDateComponents *components2 = [CURRENT_CALENDAR components:NSYearCalendarUnit fromDate:[NSDate date]];
return (components1.year == (components2.year - 1));
}
- (BOOL)isEarlierThanDate:(NSDate *) aDate {
return ([self compare:aDate] == NSOrderedAscending);
}
- (BOOL)isLaterThanDate:(NSDate *)aDate {
return ([self compare:aDate] == NSOrderedDescending);
}
// Thanks, markrickert
- (BOOL)isInFuture {
return ([self isLaterThanDate:[NSDate date]]);
}
// Thanks, markrickert
- (BOOL)isInPast {
return ([self isEarlierThanDate:[NSDate date]]);
}
#pragma mark Roles
- (BOOL)isTypicallyWeekend {
NSDateComponents *components = [CURRENT_CALENDAR components:NSWeekdayCalendarUnit fromDate:self];
if ((components.weekday == 1) ||
(components.weekday == 7))
return YES;
return NO;
}
- (BOOL)isTypicallyWorkday {
return ![self isTypicallyWeekend];
}
#pragma mark Adjusting Dates
- (NSDate *)dateByAddingDays:(NSInteger)dDays {
NSTimeInterval aTimeInterval = [self timeIntervalSinceReferenceDate] + D_DAY * dDays;
NSDate *newDate = [NSDate dateWithTimeIntervalSinceReferenceDate:aTimeInterval];
return newDate;
}
- (NSDate *)dateBySubtractingDays:(NSInteger)dDays {
return [self dateByAddingDays: (dDays * -1)];
}
- (NSDate *)dateByAddingHours:(NSInteger)dHours {
NSTimeInterval aTimeInterval = [self timeIntervalSinceReferenceDate] + D_HOUR * dHours;
NSDate *newDate = [NSDate dateWithTimeIntervalSinceReferenceDate:aTimeInterval];
return newDate;
}
- (NSDate *)dateBySubtractingHours:(NSInteger)dHours {
return [self dateByAddingHours: (dHours * -1)];
}
- (NSDate *)dateByAddingMinutes:(NSInteger)dMinutes {
NSTimeInterval aTimeInterval = [self timeIntervalSinceReferenceDate] + D_MINUTE * dMinutes;
NSDate *newDate = [NSDate dateWithTimeIntervalSinceReferenceDate:aTimeInterval];
return newDate;
}
- (NSDate *)dateBySubtractingMinutes: (NSInteger)dMinutes {
return [self dateByAddingMinutes: (dMinutes * -1)];
}
- (NSDate *)dateAtStartOfDay {
NSDateComponents *components = [CURRENT_CALENDAR components:DATE_COMPONENTS fromDate:self];
components.hour = 0;
components.minute = 0;
components.second = 0;
return [CURRENT_CALENDAR dateFromComponents:components];
}
- (NSDateComponents *)componentsWithOffsetFromDate:(NSDate *)aDate {
NSDateComponents *dTime = [CURRENT_CALENDAR components:DATE_COMPONENTS fromDate:aDate toDate:self options:0];
return dTime;
}
#pragma mark Retrieving Intervals
- (NSInteger)minutesAfterDate:(NSDate *)aDate {
NSTimeInterval ti = [self timeIntervalSinceDate:aDate];
return (NSInteger) (ti / D_MINUTE);
}
- (NSInteger)minutesBeforeDate:(NSDate *)aDate {
NSTimeInterval ti = [aDate timeIntervalSinceDate:self];
return (NSInteger) (ti / D_MINUTE);
}
- (NSInteger) hoursAfterDate:(NSDate *)aDate {
NSTimeInterval ti = [self timeIntervalSinceDate:aDate];
return (NSInteger) (ti / D_HOUR);
}
- (NSInteger) hoursBeforeDate: (NSDate *) aDate
{
NSTimeInterval ti = [aDate timeIntervalSinceDate:self];
return (NSInteger) (ti / D_HOUR);
}
- (NSInteger)daysAfterDate:(NSDate *)aDate {
NSTimeInterval ti = [self timeIntervalSinceDate:aDate];
return (NSInteger) (ti / D_DAY);
}
- (NSInteger)daysBeforeDate:(NSDate *)aDate {
NSTimeInterval ti = [aDate timeIntervalSinceDate:self];
return (NSInteger) (ti / D_DAY);
}
// Thanks, dmitrydims
// I have not yet thoroughly tested this
- (NSInteger)distanceInDaysToDate:(NSDate *)anotherDate {
NSCalendar *gregorianCalendar = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar];
NSDateComponents *components = [gregorianCalendar components:NSDayCalendarUnit fromDate:self toDate:anotherDate options:0];
return components.day;
}
#pragma mark Decomposing Dates
- (NSInteger)nearestHour {
NSTimeInterval aTimeInterval = [[NSDate date] timeIntervalSinceReferenceDate] + D_MINUTE * 30;
NSDate *newDate = [NSDate dateWithTimeIntervalSinceReferenceDate:aTimeInterval];
NSDateComponents *components = [CURRENT_CALENDAR components:NSHourCalendarUnit fromDate:newDate];
return components.hour;
}
- (NSInteger)hour {
NSDateComponents *components = [CURRENT_CALENDAR components:DATE_COMPONENTS fromDate:self];
return components.hour;
}
- (NSInteger)minute {
NSDateComponents *components = [CURRENT_CALENDAR components:DATE_COMPONENTS fromDate:self];
return components.minute;
}
- (NSInteger)seconds {
NSDateComponents *components = [CURRENT_CALENDAR components:DATE_COMPONENTS fromDate:self];
return components.second;
}
- (NSInteger)day {
NSDateComponents *components = [CURRENT_CALENDAR components:DATE_COMPONENTS fromDate:self];
return components.day;
}
- (NSInteger)month {
NSDateComponents *components = [CURRENT_CALENDAR components:DATE_COMPONENTS fromDate:self];
return components.month;
}
- (NSInteger)week {
NSDateComponents *components = [CURRENT_CALENDAR components:DATE_COMPONENTS fromDate:self];
return components.week;
}
- (NSInteger)weekday {
NSDateComponents *components = [CURRENT_CALENDAR components:DATE_COMPONENTS fromDate:self];
return components.weekday;
}
- (NSInteger)nthWeekday { // e.g. 2nd Tuesday of the month is 2{
NSDateComponents *components = [CURRENT_CALENDAR components:DATE_COMPONENTS fromDate:self];
return components.weekdayOrdinal;
}
- (NSInteger)year {
NSDateComponents *components = [CURRENT_CALENDAR components:DATE_COMPONENTS fromDate:self];
return components.year;
}
@end
/************************************************************
* * EaseMob CONFIDENTIAL
* __________________
* Copyright (C) 2013-2014 EaseMob Technologies. All rights reserved.
*
* NOTICE: All information contained herein is, and remains
* the property of EaseMob Technologies.
* Dissemination of this information or reproduction of this material
* is strictly forbidden unless prior written permission is obtained
* from EaseMob Technologies.
*/
#import <Foundation/Foundation.h>
@interface NSDateFormatter (Category)
+ (id)dateFormatter;
+ (id)dateFormatterWithFormat:(NSString *)dateFormat;
+ (id)defaultDateFormatter;/*yyyy-MM-dd HH:mm:ss*/
@end
/************************************************************
* * EaseMob CONFIDENTIAL
* __________________
* Copyright (C) 2013-2014 EaseMob Technologies. All rights reserved.
*
* NOTICE: All information contained herein is, and remains
* the property of EaseMob Technologies.
* Dissemination of this information or reproduction of this material
* is strictly forbidden unless prior written permission is obtained
* from EaseMob Technologies.
*/
#import "NSDateFormatter+Category.h"
@implementation NSDateFormatter (Category)
+ (id)dateFormatter
{
return [[self alloc] init];
}
+ (id)dateFormatterWithFormat:(NSString *)dateFormat
{
NSDateFormatter *dateFormatter = [[self alloc] init];
dateFormatter.dateFormat = dateFormat;
return dateFormatter;
}
+ (id)defaultDateFormatter
{
return [self dateFormatterWithFormat:@"yyyy-MM-dd HH:mm:ss"];
}
@end
//
// NSDictionary+GM.h
// Gengmei
//
// Created by Mikasa on 2019/8/8.
// Copyright © 2019 更美互动信息科技有限公司. All rights reserved.
//
NS_ASSUME_NONNULL_BEGIN
@interface NSDictionary (GM)
- (BOOL)dictContainsKey:(NSString *)key;
@end
NS_ASSUME_NONNULL_END
//
// NSDictionary+GM.m
// Gengmei
//
// Created by Mikasa on 2019/8/8.
// Copyright © 2019 更美互动信息科技有限公司. All rights reserved.
//
#import "NSDictionary+GM.h"
@implementation NSDictionary (GM)
- (BOOL)dictContainsKey:(NSString *)key {
if ([self.allKeys containsObject:key]) {
return YES;
}
return NO;
}
@end
//
// NSDictionary+json.h
// Gengmei
//
// Created by Q14 on 2019/5/8.
// Copyright © 2019 更美互动信息科技有限公司. All rights reserved.
//
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface NSDictionary (json)
/**
* JSON字符串转NSDictionary
*
* @param jsonString JSON字符串
*
* @return NSDictionary
*/
+ (NSDictionary *)dictionaryWithJsonString:(NSString *)jsonString;
/**
* 字典转JSON字符串
*
* @param dic 字典
*
* @return JSON字符串
*/
+ (NSString *)dictionaryToJson:(NSDictionary *)dic;
@end
NS_ASSUME_NONNULL_END
//
// NSDictionary+json.m
// Gengmei
//
// Created by Q14 on 2019/5/8.
// Copyright © 2019 更美互动信息科技有限公司. All rights reserved.
//
#import "NSDictionary+json.h"
@implementation NSDictionary (json)
/**
* JSON字符串转NSDictionary
*
* @param jsonString JSON字符串
*
* @return NSDictionary
*/
+ (NSDictionary *)dictionaryWithJsonString:(NSString *)jsonString {
if (jsonString == nil) {
return nil;
}
NSData *jsonData = [jsonString dataUsingEncoding:NSUTF8StringEncoding];
NSError *error;
NSDictionary *dic = [NSJSONSerialization JSONObjectWithData:jsonData options:NSJSONReadingMutableContainers error:&error];
if(error) {
NSLog(@"json解析失败:%@",error);
return nil;
}
return dic;
}
/**
* 字典转JSON字符串
*
* @param dic 字典
*
* @return JSON字符串
*/
+ (NSString *)dictionaryToJson:(NSDictionary *)dic {
NSError *parseError = nil;
NSData *jsonData = [NSJSONSerialization dataWithJSONObject:dic options:NSJSONWritingPrettyPrinted error:&parseError];
return [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];
}
@end
//
// NSFileManager+FolderSize.h
// ZhengXing
//
// Created by wangyang on 3/19/15.
// Copyright (c) 2015 Wanmei Creative. All rights reserved.
//
#import <Foundation/Foundation.h>
@interface NSFileManager (FolderSize)
- (BOOL)getAllocatedSize:(unsigned long long *)size ofDirectoryAtURL:(NSURL *)directoryURL error:(NSError * __autoreleasing *)error;
@end
//
// NRFileManager.m
// NRFoundation
//
// Created by Nikolai Ruhe on 2015-02-22.
// Copyright (c) 2015 Nikolai Ruhe. All rights reserved.
//
#import "NSFileManager+FolderSize.h"
@implementation NSFileManager (FolderSize)
// This method calculates the accumulated size of a directory on the volume in bytes.
//
// As there's no simple way to get this information from the file system it has to crawl the entire hierarchy,
// accumulating the overall sum on the way. The resulting value is roughly equivalent with the amount of bytes
// that would become available on the volume if the directory would be deleted.
//
// Caveat: There are a couple of oddities that are not taken into account (like symbolic links, meta data of
// directories, hard links, ...).
- (BOOL)getAllocatedSize:(unsigned long long *)size ofDirectoryAtURL:(NSURL *)directoryURL error:(NSError * __autoreleasing *)error
{
NSParameterAssert(size != NULL);
NSParameterAssert(directoryURL != nil);
// We'll sum up content size here:
unsigned long long accumulatedSize = 0;
// prefetching some properties during traversal will speed up things a bit.
NSArray *prefetchedProperties = @[
NSURLIsRegularFileKey,
NSURLFileAllocatedSizeKey,
NSURLTotalFileAllocatedSizeKey,
];
// The error handler simply signals errors to outside code.
__block BOOL errorDidOccur = NO;
BOOL (^errorHandler)(NSURL *, NSError *) = ^(NSURL *url, NSError *localError) {
if (error != NULL)
*error = localError;
errorDidOccur = YES;
return NO;
};
// We have to enumerate all directory contents, including subdirectories.
NSDirectoryEnumerator *enumerator = [[NSFileManager defaultManager] enumeratorAtURL:directoryURL
includingPropertiesForKeys:prefetchedProperties
options:(NSDirectoryEnumerationOptions)0
errorHandler:errorHandler];
// Start the traversal:
for (NSURL *contentItemURL in enumerator) {
// Bail out on errors from the errorHandler.
if (errorDidOccur)
return NO;
// Get the type of this item, making sure we only sum up sizes of regular files.
NSNumber *isRegularFile;
if (! [contentItemURL getResourceValue:&isRegularFile forKey:NSURLIsRegularFileKey error:error])
return NO;
if (! [isRegularFile boolValue])
continue; // Ignore anything except regular files.
// To get the file's size we first try the most comprehensive value in terms of what the file may use on disk.
// This includes metadata, compression (on file system level) and block size.
NSNumber *fileSize;
if (! [contentItemURL getResourceValue:&fileSize forKey:NSURLTotalFileAllocatedSizeKey error:error])
return NO;
// In case the value is unavailable we use the fallback value (excluding meta data and compression)
// This value should always be available.
if (fileSize == nil) {
if (! [contentItemURL getResourceValue:&fileSize forKey:NSURLFileAllocatedSizeKey error:error])
return NO;
NSAssert(fileSize != nil, @"huh? NSURLFileAllocatedSizeKey should always return a value");
}
// We're good, add up the value.
accumulatedSize += [fileSize unsignedLongLongValue];
}
// Bail out on errors from the errorHandler.
if (errorDidOccur)
return NO;
// We finally got it.
*size = accumulatedSize;
return YES;
}
@end
//
// NSJSONSerialization+GM.swift
// Gengmei
//
// Created by licong on 16/8/25.
// Copyright © 2016年 更美互动信息科技有限公司. All rights reserved.
//
import Foundation
@objc public extension JSONSerialization {
class func JSONString(_ object: AnyObject) -> String? {
guard let data = try? JSONSerialization.data(withJSONObject: object, options: .prettyPrinted),
let string = String(data: data, encoding: String.Encoding.utf8) else { return nil }
return string
}
}
//
// NSMutableAttributedString+Attachment.h
// Gengmei
//
// Created by wangyang on 2018/8/7.
// Copyright © 2018年 更美互动信息科技有限公司. All rights reserved.
//
#import <Foundation/Foundation.h>
@interface NSMutableAttributedString (Attachment)
/**
需求:http://wiki.wanmeizhensuo.com/pages/viewpage.action?pageId=9767058#space-menu-link-content
一些卡片标题有置顶标识。
*/
- (void)addFeedIsTopAttachment;
/**
http://wiki.wanmeizhensuo.com/pages/viewpage.action?pageId=11609153
*/
- (void)addDiaryMatchAttachment;
/**
标题前方添加标识(默认为拼团)
*/
- (NSAttributedString *)addGroupBuyIsFrontAttachment:(NSString *)iconName;
- (NSAttributedString *)addGroupBuyIsFrontAttachment:(NSString *)iconName attachmentBounds:(CGRect)attachmentBounds;
@end
//
// NSMutableAttributedString+Attachment.m
// Gengmei
//
// Created by wangyang on 2018/8/7.
// Copyright © 2018年 更美互动信息科技有限公司. All rights reserved.
//
#import "NSMutableAttributedString+Attachment.h"
#import "NSString+GM.h"
@implementation NSMutableAttributedString (Attachment)
- (void)addFeedIsTopAttachment {
NSTextAttachment* textAttachment = [[NSTextAttachment alloc] init];
textAttachment.image = [UIImage imageNamed:@"feed_is_top"];
textAttachment.bounds = CGRectMake(0, -1, 13, 13); // 微调图片位置
NSAttributedString *imageAttachment = [NSAttributedString attributedStringWithAttachment:textAttachment];
// 给最后一个字符尾部添加字符水平间距,以符合设计要求:箭头与文字的间距为8个点
[self addAttribute:NSKernAttributeName value:@(8) range:NSMakeRange(self.string.length - 1, 1)];
[self insertAttributedString:imageAttachment atIndex:self.string.length];
}
- (void)addDiaryMatchAttachment {
NSTextAttachment* textAttachment = [[NSTextAttachment alloc] init];
textAttachment.image = [UIImage imageNamed:@"diary_match"];//diary_match//feed_is_top
textAttachment.bounds = CGRectMake(0, -2, 31, 16); // 微调图片位置
NSAttributedString *imageAttachment = [NSAttributedString attributedStringWithAttachment:textAttachment];
// 给最后一个字符尾部添加字符水平间距,以符合设计要求:箭头与文字的间距为8个点
[self addAttribute:NSKernAttributeName value:@(8) range:NSMakeRange(self.string.length - 1, 1)];
[self insertAttributedString:imageAttachment atIndex:self.string.length];
}
- (NSAttributedString *)addGroupBuyIsFrontAttachment:(NSString *)iconName {
[self addGroupBuyIsFrontAttachment:iconName attachmentBounds:CGRectMake(0, -1, 25, 13)];
// [self addAttribute:NSKernAttributeName value:@(20) range:NSMakeRange(0, 1)];
return self;
}
- (NSAttributedString *)addGroupBuyIsFrontAttachment:(NSString *)iconName attachmentBounds:(CGRect)attachmentBounds {
NSMutableAttributedString *textAttrStr = [[NSMutableAttributedString alloc] init];
NSTextAttachment* textAttachment = [[NSTextAttachment alloc] init];
textAttachment.image = [UIImage imageNamed:iconName.isNonEmpty ? iconName : @"groupBuy_icon"];
textAttachment.bounds = CGRectMake(attachmentBounds.origin.x, attachmentBounds.origin.y, attachmentBounds.size.width, attachmentBounds.size.height); // 微调图片位置
NSAttributedString *imageAttachment = [NSAttributedString attributedStringWithAttachment:textAttachment];
[textAttrStr appendAttributedString:imageAttachment];
//标签后添加空格
[textAttrStr appendAttributedString:[[NSAttributedString alloc] initWithString:@" "]];
// 给最后一个字符尾部添加字符水平间距,以符合设计要求:箭头与文字的间距为8个点
[self insertAttributedString:textAttrStr atIndex:0];
return self;
}
@end
//
// NSNull+Empty.h
// Gengmei
//
// Created by licong on 12/28/15.
// Copyright © 2015 Wanmeichuangyi. All rights reserved.
//
#import <Foundation/Foundation.h>
@interface NSNull (Empty)
- (BOOL)isNonEmpty;
/**
* @brief 我们极力避免使用 string.length 的方式来判断空字符串。但是 string.length 除了用来判断,可能还会用在其它非判断的功能上。为了避免 [NSNull length] 而引起的 crash,还是给 NSNull 添加一个 length 方法比较安全。并且在 debug 情况下打印 log,以提示使用了 length 方法
*
* @return 0
*/
- (NSUInteger)length;
@end
//
// NSNull+Empty.m
// Gengmei
//
// Created by licong on 12/28/15.
// Copyright © 2015 Wanmeichuangyi. All rights reserved.
//
#import "NSNull+Empty.h"
@implementation NSNull (Empty)
- (BOOL) isNonEmpty {
return NO;
}
- (NSUInteger)length{
NSAssert(0, @"注意:向 NSNull 发送了 length 消息");
return 0;
}
@end
//
// UIViewController+KeyboardAnimation.h
// yingshibaokaoyan
//
// Created by wangyang on 7/17/14.
// Copyright (c) 2014 com.zkyj.yingshibao.kaoyao. All rights reserved.
//
#import <UIKit/UIKit.h>
/**
* 当键盘弹出或者收起时,会执行的block。在这个block里实现需要的UI变动
*
* @param keyboardFrame 将键盘转换为视图所在坐标第后,整个键盘View的Frame
* @param duration 动画时间
* @param curve 动画效果
* @param notifcation 通知
*/
typedef void(^ShowKeyboardAnimation)(CGRect keyboardFrame, CGFloat duration, NSInteger curve, NSNotification *notifcation);
typedef void(^HideKeyboardAnimation)(CGFloat duration, NSInteger curve, NSNotification *notifcation);
/**
* 使用该类别,以方便的控制键盘事件
*/
@interface NSObject (KeyboardAnimation)
- (void)observeKeyboardForView:(UIView *)view showKeyboardAnimation:(ShowKeyboardAnimation)animation1 hideKeyboardAnimation:(HideKeyboardAnimation)animation2;
- (void)removeObservKeyboard;
@end
//
// UIViewController+KeyboardAnimation.m
// yingshibaokaoyan
//
// Created by wangyang on 7/17/14.
// Copyright (c) 2014 com.zkyj.yingshibao.kaoyao. All rights reserved.
//
#import "NSObject+KeyboardAnimation.h"
#import <objc/runtime.h>
static const char kwy_targetView;
static const char kwy_showAnimationblock;
static const char kwy_hideAnimationblock;
@interface NSObject ()
@property (nonatomic, weak) UIView *wy_targetView;
@property (nonatomic, copy) ShowKeyboardAnimation wy_showAnimationblock;
@property (nonatomic, copy) HideKeyboardAnimation wy_hideAnimationblock;
@end
@implementation NSObject (KeyboardAnimation)
#pragma mark - 属性get、set
- (ShowKeyboardAnimation)wy_showAnimationblock
{
return objc_getAssociatedObject(self, &kwy_showAnimationblock);
}
- (void)setWy_showAnimationblock:(ShowKeyboardAnimation)wy_showAnimationblock
{
objc_setAssociatedObject(self, &kwy_showAnimationblock, wy_showAnimationblock, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
- (HideKeyboardAnimation)wy_hideAnimationblock
{
return objc_getAssociatedObject(self, &kwy_hideAnimationblock);
}
- (void)setWy_hideAnimationblock:(HideKeyboardAnimation)wy_hideAnimationblock
{
objc_setAssociatedObject(self, &kwy_hideAnimationblock, wy_hideAnimationblock, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
- (UIView *)wy_targetView
{
return objc_getAssociatedObject(self, &kwy_targetView);
}
- (void)setWy_targetView:(UIView *)wy_targetView
{
objc_setAssociatedObject(self, &kwy_targetView, wy_targetView, OBJC_ASSOCIATION_ASSIGN);
}
#pragma mark - 监听
- (void)observeKeyboardForView:(UIView *)view showKeyboardAnimation:(ShowKeyboardAnimation)animation1 hideKeyboardAnimation:(HideKeyboardAnimation)animation2
{
self.wy_showAnimationblock = animation1;
self.wy_targetView = view;
self.wy_hideAnimationblock = animation2;
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(keyboardWillShow:)
name:UIKeyboardWillShowNotification
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(keyboardWillHide:)
name:UIKeyboardWillHideNotification
object:nil];
}
- (void)removeObservKeyboard
{
self.wy_showAnimationblock = nil;
self.wy_targetView = nil;
self.wy_hideAnimationblock = nil;
[[NSNotificationCenter defaultCenter] removeObserver:self name:UIKeyboardWillShowNotification object:nil];
[[NSNotificationCenter defaultCenter] removeObserver:self name:UIKeyboardWillHideNotification object:nil];
}
#pragma mark - 监听响应
- (void)keyboardWillShow:(NSNotification *)note{
// get keyboard size and loctaion
CGRect keyboardFrame;
[[note.userInfo valueForKey:UIKeyboardFrameEndUserInfoKey] getValue: &keyboardFrame];
// Need to translate the bounds to account for rotation.
keyboardFrame = [[UIApplication sharedApplication].keyWindow convertRect:keyboardFrame toView:self.wy_targetView.superview];
NSNumber *duration = [note.userInfo objectForKey:UIKeyboardAnimationDurationUserInfoKey];
NSNumber *curve = [note.userInfo objectForKey:UIKeyboardAnimationCurveUserInfoKey];
self.wy_showAnimationblock(keyboardFrame, [duration doubleValue], [curve integerValue], note);
}
- (void)keyboardWillHide:(NSNotification *)note{
NSNumber *duration = [note.userInfo objectForKey:UIKeyboardAnimationDurationUserInfoKey];
NSNumber *curve = [note.userInfo objectForKey:UIKeyboardAnimationCurveUserInfoKey];
self.wy_hideAnimationblock([duration doubleValue], [curve integerValue], note);
}
@end
//
// NSObject+GMDate.h
// Gengmei
//
// Created by lizhen on 2019/2/15.
// Copyright © 2019 更美互动信息科技有限公司. All rights reserved.
//
#include <mach/mach_time.h>
@interface NSObject (GMDate)
/** NSDate->时间戳 */
- (CGFloat)getDateTime:(NSDate *)Date;
/** NSDate->时间字符串 */
- (NSString *)getDateToString:(NSDate *)Date;
/** 时间戳->字符串 */
- (NSString *)getDateStringWithTimeStr:(float)time;
/** 字符串时间—>时间戳 */
- (CGFloat)timeStringToTimeFloat:(NSString *)time;
/** 计算两个 NSDate 相差天数 */
- (NSString *)getTimeFrom:(NSDate *)fromDate to:(NSDate *)toDate;
/** 字符串->日期 */
- (NSDate *)getDateWithString:(NSString *)str;
/** 时间戳->NSDate */
- (NSDate *)getDateWithFloat:(float)time;
uint64_t GetPIDTimeInNanoseconds(uint64_t start,uint64_t end);
@end
//
// NSObject+GMDate.m
// Gengmei
//
// Created by lizhen on 2019/2/15.
// Copyright © 2019 更美互动信息科技有限公司. All rights reserved.
//
#import "NSObject+GMDate.h"
#include <assert.h>
#include <CoreServices/CoreServices.h>
#include <mach/mach.h>
#include <unistd.h>
@implementation NSObject (GMDate)
#pragma mark - NSDate转化为时间戳
- (CGFloat)getDateTime:(NSDate *)Date {
//获取选择的时间戳
NSDate *date = [Date dateByAddingTimeInterval:0];
NSTimeInterval time = [date timeIntervalSince1970];// *1000 是精确到毫秒,不乘就是精确到秒
return time;
}
#pragma mark - NSDate->时间字符串
- (NSString *)getDateToString:(NSDate *)Date {
NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
// 设定时间格式,这里可以设置成自己需要的格式
[formatter setDateFormat:@"yyyy/MM/dd"];
NSString *time = [formatter stringFromDate:Date];
return time;
}
#pragma mark - 时间戳转字符串
- (NSString *)getDateStringWithTimeStr:(float)time {
// 时间戳转时间,时间戳为13位是精确到毫秒的,10位精确到秒
NSDate *detailDate = [NSDate dateWithTimeIntervalSince1970:time];
NSString *currentDateStr = [self getDateToString:detailDate];
return currentDateStr;
}
#pragma mark - 字符串时间—>时间戳
- (CGFloat)timeStringToTimeFloat:(NSString *)time {
// 转换为时间戳
NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
[formatter setDateFormat:@"yyyy/MM/dd"];
NSDate *date = [formatter dateFromString:time];
return [self getDateTime:date];
}
#pragma mark - 计算两个 NSDate 相差天数
- (NSString *)getTimeFrom:(NSDate *)fromDate to:(NSDate *)toDate {
//计算两个中间差值(秒)
NSTimeInterval time = [toDate timeIntervalSinceDate:fromDate];
//开始时间和结束时间的中间相差的时间
int days;
days = ((int)time)/(3600*24); //一天是24小时*3600秒
NSString *dateValue = [NSString stringWithFormat:@"%i",days];
return dateValue;
}
#pragma mark - 字符串转日期
- (NSDate *)getDateWithString:(NSString *)str {
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
dateFormatter.dateFormat = @"yyyy/MM/dd";
[dateFormatter setMonthSymbols:[NSArray arrayWithObjects:@"01",@"02",@"03",@"04",@"05",@"06",@"07",@"08",@"09",@"10",@"11",@"12", nil]];
NSDate * ValueDate = [dateFormatter dateFromString:str];
return ValueDate;
}
#pragma mark - 时间戳->NSDate
- (NSDate *)getDateWithFloat:(float)time {
if (time <= 0) {
return nil;
}
NSString *timeStr = [self getDateStringWithTimeStr:time];
NSDate *date = [self getDateWithString:timeStr];
return date;
}
uint64_t GetPIDTimeInNanoseconds(uint64_t start,uint64_t end)
{
uint64_t elapsed;
uint64_t elapsedNano;
static mach_timebase_info_data_t sTimebaseInfo;
// Start the clock.
// start = mach_absolute_time();
// Call getpid. This will produce inaccurate results because
// we're only making a single system call. For more accurate
// results you should call getpid multiple times and average
// the results.
(void) getpid();
// Stop the clock.
// end = mach_absolute_time();
// Calculate the duration.
elapsed = end - start;
// Convert to nanoseconds.
// If this is the first time we've run, get the timebase.
// We can use denom == 0 to indicate that sTimebaseInfo is
// uninitialised because it makes no sense to have a zero
// denominator is a fraction.
if ( sTimebaseInfo.denom == 0 ) {
(void) mach_timebase_info(&sTimebaseInfo);
}
// Do the maths. We hope that the multiplication doesn't
// overflow; the price you pay for working in fixed point.
elapsedNano = elapsed * sTimebaseInfo.numer / sTimebaseInfo.denom;
return elapsedNano/1000000;
}
@end
//
// NSString+Base64.h
// GMAlpha
//
// Created by ioszhb on 2019/1/18.
// Copyright © 2019 Gengmei. All rights reserved.
//
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface NSString (Base64)
- (NSString *)encodeString:(NSString *)string;
- (NSString *)decodeString:(NSString *)string;
//base64编码
- (NSString *)str_base64Encode;
//base64解码
- (NSString *)str_base64Decode;
@end
NS_ASSUME_NONNULL_END
//
// NSString+Base64.m
// GMAlpha
//
// Created by ioszhb on 2019/1/18.
// Copyright © 2019 Gengmei. All rights reserved.
//
#import "NSString+Base64.h"
@implementation NSString (Base64)
//base64编码
- (NSString *)encodeString:(NSString *)string
{
NSData *data = [string dataUsingEncoding:NSUTF8StringEncoding];
NSString *encodedStr = [data base64EncodedStringWithOptions:0];
return encodedStr;
}
//base64解码
- (NSString *)decodeString:(NSString *)string
{
NSData *data = [[NSData alloc] initWithBase64EncodedString:string options:0];
NSString *decodedStr = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
return decodedStr;
}
//base64编码
- (NSString *)str_base64Encode
{
NSData *data = [self dataUsingEncoding:NSUTF8StringEncoding];
NSString *encodedStr = [data base64EncodedStringWithOptions:0];
return encodedStr;
}
//base64解码
- (NSString *)str_base64Decode
{
NSData *data = [[NSData alloc] initWithBase64EncodedString:self options:0];
NSString *decodedStr = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
return decodedStr;
}
@end
//
// NSString+DateFormat.h
// Gengmei
//
// Created by Sean Lee on 4/15/15.
// Copyright (c) 2015 Wanmeichuangyi. All rights reserved.
//
#import <Foundation/Foundation.h>
@interface NSString (DateFormat)
/**
* @author licong, 16-12-29 16:12:14
*
* String类型的日期转为冒号式的NSDate
*
* @return 返回一个NSDate对象
*
* @since 5.8
*/
-(NSDate *)stringFormatToColonDate;
/**
* @author licong, 16-12-29 16:12:59
*
* String类型的日期转为破折号式的NSDate
*
* @return 返回一个NSDate对象
*
* @since 5.8
*/
-(NSDate *)stringformatToDashDate;
@end
//
// NSString+DateFormat.m
// Gengmei
//
// Created by Sean Lee on 4/15/15.
// Copyright (c) 2015 Wanmeichuangyi. All rights reserved.
//
#import "NSString+DateFormat.h"
@implementation NSString (DateFormat)
-(NSDate *)stringFormatToColonDate{
NSDateFormatter* formatter = [[NSDateFormatter alloc] init];
[formatter setDateFormat:@"yyyy-MM-dd"];
return [formatter dateFromString:self];
}
- (NSDate *)stringformatToDashDate{
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
[dateFormatter setDateFormat:@"yyyy:MM:dd"];
return [dateFormatter dateFromString:self];
}
@end
//
// NSString+Encrypt.h
// Gengmei
//
// Created by licong on 12/28/15.
// Copyright © 2015 Wanmeichuangyi. All rights reserved.
//
#import <Foundation/Foundation.h>
/**
* @brief
AES块长度 128 byte
加密模式CBC,cipher block chaining
数据长度不到128byte时需要填充(即no padding自定义填充)
填充方法s + (128 - len(s) % 128) * chr(128 - len(s) % 128) 将数字转成char字符拼接尾部
加密密钥 Up[K+ub%pliOnsO5UavFBd)cw5VcyHSX
初始向量需要附加在加密后的auth_user_id中 传给服务器,final auth_user_id = base64(iv+encrypted(user_id_auth))
*/
@interface NSString (Encrypt)
/**
* @brief AES256加密
*
* @param key 32位的密钥
*
* @return 返回加密后的密文
*/
- (NSString *)AES256EncryptedStringForKey:(NSString *)key;
/**
* @brief AES256解密
*
* @param key 32位的密钥
*
* @return 返回解密后的明文
*/
- (NSString *)AES256DecryptedStringForKey:(NSString *)key;
/**
* @brief MD5加密
*
* @return MD5加密后的String
*/
- (NSString *) stringFromMD5;
@end
//
// NSString+Encrypt.m
// Gengmei
//
// Created by licong on 12/28/15.
// Copyright © 2015 Wanmeichuangyi. All rights reserved.
//
#import "NSString+Encrypt.h"
#import <CommonCrypto/CommonDigest.h>
#import <CommonCrypto/CommonCryptor.h>
#import "NSString+GM.h"
@implementation NSString (Encrypt)
- (NSString *)AES256EncryptedStringForKey:(NSString *)key
{
//密钥
NSData *keyData = [key dataUsingEncoding:NSUTF8StringEncoding];
//明文数据
NSData *data = [self dataUsingEncoding:NSUTF8StringEncoding];
// Init cryptor
CCCryptorRef cryptor = NULL;
//IV:初始化16字节的随机向量
char iv[16];
for (int i = 0; i<16; i++) {
iv[i] = arc4random()%255;//一个字节长的随机数
}
//Create Cryptor
CCCryptorStatus create = CCCryptorCreateWithMode(kCCEncrypt,
kCCModeCBC, //CBC模式
kCCAlgorithmAES128, //分组密码块长度
ccNoPadding, //无填充模式
iv, // can be NULL, because null is full of zeros
keyData.bytes, //密钥
keyData.length, //密钥长度
NULL,
0,
0,
0, //这里参数只在CTR下有用,本初填0即可
&cryptor);
if (create == kCCSuccess){
size_t numBytesCrypted;
//自定义填充明文算法
NSUInteger dataLength = [data length];
int diff = 128 - (dataLength % 128);
unsigned long newSize = 0;
if(diff > 0)
newSize = dataLength + diff;
char dataPtr[newSize];
memcpy(dataPtr, [data bytes], [data length]);
for(int i = 0; i < diff; i++){
char character = diff;
dataPtr[i + dataLength] = character;
}
/*初始化加密后的data,并开辟好空间长度,查阅相关文档:对于分组密码,加密后的数据长度总是小于或者等于 加密前数据长度+单个分组密码块长度之和*/
NSMutableData *cipherData= [NSMutableData dataWithLength: sizeof(dataPtr)+kCCBlockSizeAES128];
/*Update Cryptor,得到加密后data以及我们需要的数据长度,这里可以看到cipherData的长度是小于或者等于outLength的
*/
CCCryptorStatus update = CCCryptorUpdate(cryptor,
dataPtr,
sizeof(dataPtr),
cipherData.mutableBytes,
cipherData.length,
&numBytesCrypted);
if (update == kCCSuccess){
//通过outLength截图我们需要的数据长度
cipherData.length = numBytesCrypted;
//Final Cryptor,最终生成最终的密文,装载给cipherData
CCCryptorStatus final = CCCryptorFinal(cryptor, //CCCryptorRef cryptorRef,
cipherData.mutableBytes, //void *dataOut,
cipherData.length, //size_t dataOutAvailable,
&numBytesCrypted); //size_t *dataOutMoved)
if (final == kCCSuccess){
//Release Cryptor
CCCryptorRelease(cryptor);
}
//最终结果= 初始向量+密文,这样服务器才可以拿到初始向量,用密钥解码
NSMutableData *resultData= [NSMutableData dataWithLength:0];
[resultData appendBytes:iv length:sizeof(iv)];
[resultData appendBytes:cipherData.bytes length:cipherData.length];
//最终结果再base64转码
NSString * resultStr = [resultData base64EncodedString];//[GTMBase64 stringByEncodingData:resultData];
return resultStr;
}
}
else{
NSLog(@"加密失败");
}
return nil;
}
- (NSString *)AES256DecryptedStringForKey:(NSString *)key {
//Key to Data
NSData *keyData = [key dataUsingEncoding:NSUTF8StringEncoding];
// Init cryptor
CCCryptorRef cryptor = NULL;
//IV:获取密文里的随机向量
NSData *data = [self base64DecodedData];
NSMutableData * iv = [NSMutableData dataWithBytes:data.bytes length:kCCKeySizeAES256];
// Create Cryptor
CCCryptorStatus createDecrypt = CCCryptorCreateWithMode(kCCDecrypt, // operation
kCCModeCBC, // mode CTR
kCCAlgorithmAES, // Algorithm
ccNoPadding, // padding
iv.bytes, // can be NULL, because null is full of zeros
keyData.bytes, // key
keyData.length, // keylength
NULL, //const void *tweak
0, //size_t tweakLength,
0, //int numRounds,
0, //CCModeOptions options,
&cryptor); //CCCryptorRef *cryptorRef
if (createDecrypt == kCCSuccess)
{
// Alloc Data Out
NSMutableData * realData = [NSMutableData dataWithBytes:data.bytes + 16 length:data.length - 16];
NSMutableData *cipherDataDecrypt = [NSMutableData dataWithLength:realData.length + kCCBlockSizeAES128];
//alloc number of bytes written to data Out
size_t outLengthDecrypt;
//Update Cryptor
CCCryptorStatus updateDecrypt = CCCryptorUpdate(cryptor,
realData.bytes, //const void *dataIn,
realData.length, //size_t dataInLength,
cipherDataDecrypt.mutableBytes, //void *dataOut,
cipherDataDecrypt.length, // size_t dataOutAvailable,
&outLengthDecrypt); // size_t *dataOutMoved)
if (updateDecrypt == kCCSuccess)
{
//Cut Data Out with nedded length
cipherDataDecrypt.length = outLengthDecrypt;
//Final Cryptor
CCCryptorStatus final = CCCryptorFinal(cryptor, //CCCryptorRef cryptorRef,
cipherDataDecrypt.mutableBytes, //void *dataOut,
cipherDataDecrypt.length, // size_t dataOutAvailable,
&outLengthDecrypt); // size_t *dataOutMoved)
if (final == kCCSuccess){
CCCryptorRelease(cryptor); //CCCryptorRef cryptorRef
}
// Data to String
NSMutableString* cipherFinalDecrypt = [[NSMutableString alloc] initWithData:cipherDataDecrypt encoding:NSUTF8StringEncoding];
int diff = [cipherFinalDecrypt characterAtIndex:cipherDataDecrypt.length - 1]; // 128字节的明文(填充字符)中填充字符的个数
[cipherFinalDecrypt deleteCharactersInRange:NSMakeRange(cipherFinalDecrypt.length - diff, diff)];
return cipherFinalDecrypt;
}
}
else{
NSLog(@"解密密失败");
}
return nil;
}
- (NSString *)stringFromMD5
{
if(self == nil || [self length] == 0)
return nil;
const char *value = [self 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;
}
+ (BOOL)validKey:(NSString*)key
{
if(key == nil || key.length != kCCKeySizeAES256){
return NO;
}
return YES;
}
@end
//
// NSString+GM.h
// Gengmei
//
// Created by licong on 12/28/15.
// Copyright © 2015 Wanmeichuangyi. All rights reserved.
//
#import <Foundation/Foundation.h>
/**
*引用base64 库 因不更新所以先拿过来
*
*/
@interface NSData (Base64)
+ (NSData *)dataWithBase64EncodedString:(NSString *)string;
- (NSString *)base64EncodedStringWithWrapWidth:(NSUInteger)wrapWidth;
- (NSString *)base64EncodedString;
@end
/**
* 常用且按功能块比较难划分的扩展方法
*/
@interface NSString (GM)
/**
* @author licong, 16-12-28 12:12:13
*
* 判断一个字符串是否为空(包括nil/@""/nil)
*
* @return 布尔值,YES表示字符串不为空,NO表示字符串为空
*
* @since 5.8
*/
- (BOOL)isNonEmpty;
/*!
* @author zhaiguojun, 16-04-21
*
* @brief 把字典和数组转成string
*
* 相当于调用 [NSJSONSerialization dataWithJSONObject:object options:0 error:&error];
* @return 转换后的string
*
* @since 5.9.3
*/
+ (NSString*)convertToPrettyPrintedJsonString:(id)object;
/**
把字典和数组转成简短的json string
相当于调用 [NSJSONSerialization dataWithJSONObject:object options:0 error:&error];
*/
+ (NSString *)convertToBriefJsonString:(id)object;
/**
* @author licong, 16-12-28 12:12:16
*
* 得到非空string
*
* @return 返回一个非空的字符串
*
* @since 5.8
*/
+ (NSString *)safeStringWithString:(NSString *)string;
/**
* @brief 是否为纯数字
*
* @param string 要检查的字符串
*
*/
+ (BOOL)isPureInt:(NSString *)string;
/**
* @brief 是否为手机号
*
* @param mobileNum 0~9 11位即可
*
*/
+ (BOOL)isMobileNumber:(NSString *)mobileNum;
/**
* 问题来自于服务器返回Str 我们当做int来解析会crash 古做一下兼容(目前来看 7.3.0 之前不会发生crash 7.3.0之后必crash)
*/
- (unsigned long long)unsignedLongLongValue;
/**
* @brief 是否为邮箱格式
*
* @param string 要检查的字符串
*
*/
+ (BOOL)isValidateEmail:(NSString *)email;
@end
/**
*
* 将类似URL的query参数解析成字典
*
*/
@interface NSString (paresQuery)
/**
* @author licong, 16-12-28 18:12:14
*
* 解析url中的query,转成字典格式
*
* @return 解析好的字典
*
* @since 5.8
*/
- (NSDictionary*)urlQueryToDictionary;
/**
* @author licong, 16-12-28 18:12:11
*
* 解析query字符串为字典比如:a=b&c=d
*
* @return 解析好的字典
*
* @since 5.8
*/
- (NSDictionary*)queryToDictionary;
@end
/**
*
* 根据String的Font计算String的size
*
*/
@interface NSString (calculatorSize)
/**
* @author licong, 16-12-28 18:12:38
*
* 计算String当行间距为0的时候的高度和宽度
*
* @param font String的font
* @param size String限定的size
*
* @return String实际(如果是多行,则经过折行后)的size
*
* @since 5.8
*/
- (CGSize)sizeWithFont:(UIFont *)font boundSize:(CGSize)size;
/**
* @author licong, 16-12-28 18:12:05
*
* 计算String在某个行间距下,高度和宽度
*
* @param font String的font
* @param size String限定的size
* @param lineSpacing 设定的行间距
*
* @return String在设定的行间距下,实际(如果是多行,则经过折行后)的size
*
* @since 5.8
*/
- (CGSize)sizeWithFont:(UIFont *)font boundSize:(CGSize)size lineSpacing:(CGFloat)lineSpacing;
@end
@interface NSString (URLEncoding)
/**
* @author licong, 16-12-29 16:12:45
*
* URL编码
*
* @return 编码后的URL
*
* @since 5.8
*/
- (NSString*) URLEncodedString;
/**
* @author licong, 16-12-29 16:12:08
*
* URL解码
*
* @return 解码URL
*
* @since 5.8
*/
- (NSString*) URLDecodedString;
/**
URL编码
@return 编码后的URL
*/
- (NSString *)URLEncodeString;
@end
@interface NSString (Trim)
/**
* @author licong, 16-12-28 18:12:04
*
* 去除字符串两头的空白(空格及换行)
*
* @return 无空格的字符串
*
* @since 5.8
*/
- (NSString *)trimBothEnd;
- (NSString *)trimSpace;
- (NSString *)trimInnerReturn;
- (NSString *)trimNewlinesToOne;
/**
*引用base64 库 因不更新所以先拿过来
*
*/
+ (NSString *)stringWithBase64EncodedString:(NSString *)string;
- (NSString *)base64EncodedStringWithWrapWidth:(NSUInteger)wrapWidth;
- (NSString *)base64EncodedString;
- (NSString *)base64DecodedString;
- (NSData *)base64DecodedData;
@end
//
// NSString+GM.m
// Gengmei
//
// Created by licong on 12/28/15.
// Copyright © 2015 Wanmeichuangyi. All rights reserved.
//
#import "NSString+GM.h"
#import "NSNull+Empty.h"
@implementation NSData (Base64)
+ (NSData *)dataWithBase64EncodedString:(NSString *)string
{
if (![string length]) return nil;
NSData *decoded = nil;
#if __MAC_OS_X_VERSION_MIN_REQUIRED < __MAC_10_9 || __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_7_0
if (![NSData instancesRespondToSelector:@selector(initWithBase64EncodedString:options:)])
{
decoded = [[self alloc] initWithBase64Encoding:[string stringByReplacingOccurrencesOfString:@"[^A-Za-z0-9+/=]" withString:@"" options:NSRegularExpressionSearch range:NSMakeRange(0, [string length])]];
}
else
#endif
{
decoded = [[self alloc] initWithBase64EncodedString:string options:NSDataBase64DecodingIgnoreUnknownCharacters];
}
return [decoded length]? decoded: nil;
}
- (NSString *)base64EncodedStringWithWrapWidth:(NSUInteger)wrapWidth
{
if (![self length]) return nil;
NSString *encoded = nil;
#if __MAC_OS_X_VERSION_MIN_REQUIRED < __MAC_10_9 || __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_7_0
if (![NSData instancesRespondToSelector:@selector(base64EncodedStringWithOptions:)])
{
encoded = [self base64Encoding];
}
else
#endif
{
switch (wrapWidth)
{
case 64:
{
return [self base64EncodedStringWithOptions:NSDataBase64Encoding64CharacterLineLength];
}
case 76:
{
return [self base64EncodedStringWithOptions:NSDataBase64Encoding76CharacterLineLength];
}
default:
{
encoded = [self base64EncodedStringWithOptions:(NSDataBase64EncodingOptions)0];
}
}
}
if (!wrapWidth || wrapWidth >= [encoded length])
{
return encoded;
}
wrapWidth = (wrapWidth / 4) * 4;
NSMutableString *result = [NSMutableString string];
for (NSUInteger i = 0; i < [encoded length]; i+= wrapWidth)
{
if (i + wrapWidth >= [encoded length])
{
[result appendString:[encoded substringFromIndex:i]];
break;
}
[result appendString:[encoded substringWithRange:NSMakeRange(i, wrapWidth)]];
[result appendString:@"\r\n"];
}
return result;
}
- (NSString *)base64EncodedString
{
return [self base64EncodedStringWithWrapWidth:0];
}
@end
@implementation NSString (GM)
+ (NSString *)safeStringWithString:(NSString *)string{
if ([string isNonEmpty]) {
return string;
}else{
return @"";
}
}
- (BOOL)isNonEmpty {
NSMutableCharacterSet *emptyStringSet = [[NSMutableCharacterSet alloc] init];
[emptyStringSet formUnionWithCharacterSet: [NSCharacterSet whitespaceAndNewlineCharacterSet]];
[emptyStringSet formUnionWithCharacterSet: [NSCharacterSet characterSetWithCharactersInString: @" "]];
if ([self length] == 0) {
return NO;
}
NSString* str = [self stringByTrimmingCharactersInSet:emptyStringSet];
return [str length] > 0;
}
+ (NSString *)convertToPrettyPrintedJsonString:(id)object
{
return [self convertToJsonString:object withOption:NSJSONWritingPrettyPrinted];
}
+ (NSString *)convertToBriefJsonString:(id)object
{
return [self convertToJsonString:object withOption:0];;
}
+ (NSString *)convertToJsonString:(id)object withOption:(NSJSONWritingOptions)opt {
NSString *jsonString = @"";
NSError *error;
if (!object) {
NSAssert(0, @"convertToJsonString object 是nil");
return @"";
}
NSData *jsonData = [NSJSONSerialization dataWithJSONObject:object
options:opt
error:&error];
if (!jsonData) {
NSAssert(0, @"convertToJsonString NSJSONSerialization 出现错误");
} else {
jsonString = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];
}
if (jsonString == nil) {
NSAssert(0, @"convertToJsonString jsonString 是nil");
jsonString = @"";
}
return jsonString;
}
+ (BOOL)isPureInt:(NSString *)string{
NSScanner* scan = [NSScanner scannerWithString:string];
int val;
return [scan scanInt:&val] && [scan isAtEnd];
}
+ (BOOL)isMobileNumber:(NSString *)mobileNum{
NSString *ALL = @"\\d{11}$";
NSPredicate *regextestall = [NSPredicate predicateWithFormat:@"SELF MATCHES %@", ALL];
return [regextestall evaluateWithObject:mobileNum];
}
- (unsigned long long)unsignedLongLongValue {
return self.longLongValue;
}
//利用正则表达式验证
+ (BOOL)isValidateEmail:(NSString *)email {
NSString *emailRegex = @"[A-Z0-9a-z._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,4}";
NSPredicate *emailTest = [NSPredicate predicateWithFormat:@"SELF MATCHES %@", emailRegex];
return [emailTest evaluateWithObject:email];
}
@end
@implementation NSString (paresQuery)
- (NSDictionary*)urlQueryToDictionary{
NSURL* url1 = [NSURL URLWithString:self];
if (!url1) {
return nil;
}
NSString* query = [url1 query];
return [query queryToDictionary];
}
- (NSDictionary*)queryToDictionary {
@try {
NSMutableDictionary* dict = [NSMutableDictionary dictionary];
NSArray* components = [self componentsSeparatedByString:@"&"];
for (NSString* component in components) {
NSArray* keyValue = [component componentsSeparatedByString:@"="];
if ([keyValue count] > 1) {
NSString * key = [[keyValue objectAtIndex:0] URLDecodedString];
NSString * value = [keyValue objectAtIndex:1];
//参数中依然包含超过2个“=”号(多数发生在common_webview后的url参数中),则后面的数组的元素需要拼接成一个字符串
if ([keyValue count]>2) {
for (int i=2; i<[keyValue count]; i++) {
value=[value stringByAppendingString:@"="];
value=[value stringByAppendingString:keyValue[i]];
}
}
//因为这种情况服务器和客户端都转义了一次,所以要两次反转义还原中文
while ([value rangeOfString:@"%"].length != 0) {
value = [value URLDecodedString];
}
[dict setObject: value forKey: key];
}
}
return dict;
}
@catch (NSException *exception) {}
}
@end
@implementation NSString (calculatorSize)
- (CGSize)sizeWithFont:(UIFont *)font boundSize:(CGSize)size{
return [self sizeWithFont:font boundSize:size lineSpacing:0];
}
- (CGSize)sizeWithFont:(UIFont *)font boundSize:(CGSize)size lineSpacing:(CGFloat)lineSpacing{
NSMutableParagraphStyle *paragraphStyle = [[NSMutableParagraphStyle alloc]init];
paragraphStyle.lineSpacing = lineSpacing;
NSDictionary *attributes = @{NSFontAttributeName:font, NSParagraphStyleAttributeName:paragraphStyle};
CGSize resultSize = [self boundingRectWithSize:size options:NSStringDrawingUsesLineFragmentOrigin | NSStringDrawingTruncatesLastVisibleLine attributes:attributes context:nil].size;
return CGSizeMake(ceil(resultSize.width), ceil(resultSize.height));
}
@end
@implementation NSString (URLEncoding)
- (NSString *)URLEncodedString {
NSString *result = (NSString *)CFBridgingRelease(CFURLCreateStringByAddingPercentEscapes(kCFAllocatorDefault,
(CFStringRef)self,
NULL,
CFSTR("!*'();:@&=+$,/?%#[]"),
kCFStringEncodingUTF8));
return result;
}
- (NSString*)URLDecodedString {
NSString *result = (NSString *)CFBridgingRelease(CFURLCreateStringByReplacingPercentEscapesUsingEncoding(kCFAllocatorDefault,
(CFStringRef)self,
CFSTR(""),
kCFStringEncodingUTF8));
return result;
}
- (NSString *)URLEncodeString {
NSString *encodedString = [self stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLQueryAllowedCharacterSet]];
return encodedString;
}
@end
@implementation NSString (Trim)
//去空格只局限于首尾空格
- (NSString *)trimBothEnd {
NSMutableCharacterSet* emptyStringSet = [[NSMutableCharacterSet alloc] init];
[emptyStringSet formUnionWithCharacterSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
[emptyStringSet formUnionWithCharacterSet:[NSCharacterSet characterSetWithCharactersInString:@" "]];
NSString *string = [self stringByTrimmingCharactersInSet:emptyStringSet];
return string;
}
//去掉字符串中所有的空格
- (NSString *)trimSpace {
NSString *string = [self stringByReplacingOccurrencesOfString:@" " withString:@""];
return string;
}
- (NSString *)trimInnerReturn {
NSString *string = self;
string = [self stringByReplacingOccurrencesOfString:@"\r" withString:@""];
string = [string stringByReplacingOccurrencesOfString:@"\n" withString:@""];
return string;
}
- (NSString *)trimNewlinesToOne {
if (![self isNonEmpty]) {
return @"";
}
NSError *error = nil;
NSRegularExpression *exp = [[NSRegularExpression alloc] initWithPattern:@"\n+" options:0 error:&error];
if (error != nil) {
return self;
}
NSString *result = [exp stringByReplacingMatchesInString:self options:0 range:NSMakeRange(0, self.length) withTemplate:@"\n"];
return result;
}
+ (NSString *)stringWithBase64EncodedString:(NSString *)string
{
NSData *data = [NSData dataWithBase64EncodedString:string];
if (data)
{
return [[self alloc] initWithData:data encoding:NSUTF8StringEncoding];
}
return nil;
}
- (NSString *)base64EncodedStringWithWrapWidth:(NSUInteger)wrapWidth
{
NSData *data = [self dataUsingEncoding:NSUTF8StringEncoding allowLossyConversion:YES];
return [data base64EncodedStringWithWrapWidth:wrapWidth];
}
- (NSString *)base64EncodedString
{
NSData *data = [self dataUsingEncoding:NSUTF8StringEncoding allowLossyConversion:YES];
return [data base64EncodedString];
}
- (NSString *)base64DecodedString
{
return [NSString stringWithBase64EncodedString:self];
}
- (NSData *)base64DecodedData
{
return [NSData dataWithBase64EncodedString:self];
}
@end
//
// NSString+IconFont.h
// Gengmei
//
// Created by lizhen on 2018/5/25.
// Copyright © 2018年 更美互动信息科技有限公司. All rights reserved.
//
#import <Foundation/Foundation.h>
@interface NSString (IconFont)
/**
替换字符串中指定字体为iconfont
@param oldString 要替换的字符串
@param newString 替换后的字符串
@param font 文字字体
@param textColor 文字颜色
@param lineSpace 行间距
*/
- (NSMutableAttributedString *)replaceString:(NSArray<NSString *> *)oldArray
withStrings:(NSArray<NSString *> *)newArray
font:(UIFont *)font
textColor:(UIColor *)textColor
lineSpace:(NSInteger)lineSpace;
@end
@interface NSString (IconFontFactory)
// 美购详情页中使用
- (NSMutableAttributedString *)welfareDetailNameWithFontSize:(NSInteger)size
textColor:(UIColor *)textColor;
// 其它各处的美购标题使用
- (NSMutableAttributedString *)welfareNameWithFontSize:(NSInteger)size
textColor:(UIColor *)textColor
lineSpace:(NSInteger)lineSpace;
- (NSMutableAttributedString *)customDetailNameWithFontSize:(UIFont *)font
textColor:(UIColor *)textColor
lineSpace:(NSInteger)lineSpace;
@end
//
// NSString+IconFont.m
// Gengmei
//
// Created by lizhen on 2018/5/25.
// Copyright © 2018年 更美互动信息科技有限公司. All rights reserved.
//
#import "NSString+IconFont.h"
#import "NSAttributedString+GMSize.h"
#define RGBFCOLOR_HEX(hexColor) [UIColor colorWithRed: (((hexColor >> 16) & 0xFF))/255.0f \
green: (((hexColor >> 8) & 0xFF))/255.0f \
blue: ((hexColor & 0xFF))/255.0f \
alpha: 1]
@implementation NSString (IconFont)
- (NSMutableAttributedString *)replaceString:(NSArray<NSString *> *)oldArray
withStrings:(NSArray<NSString *> *)newArray
font:(UIFont *)font
textColor:(UIColor *)textColor
lineSpace:(NSInteger)lineSpace {
NSString *contentString = [self copy];
NSMutableArray *rangeArray = [NSMutableArray array];
for (NSInteger i = 0; i < oldArray.count; i++) {
NSInteger number = [[self mutableCopy] replaceOccurrencesOfString:oldArray[i] withString:newArray[i] options:NSCaseInsensitiveSearch range:NSMakeRange(0, self.length)];
NSRange range = NSMakeRange(0, 0);
for (NSInteger j = 0; j < number; j++) {
if ([contentString containsString:oldArray[i]]) {
//记录range,为替换成icomoon
range = [contentString rangeOfString:oldArray[i]];
contentString = [contentString stringByReplacingOccurrencesOfString:oldArray[i] withString:newArray[i] options:NSCaseInsensitiveSearch range:range];
NSValue *value = [NSValue valueWithRange:range];
[rangeArray addObject:value];
}
}
}
NSDictionary *attributeDic = @{NSForegroundColorAttributeName: textColor, NSFontAttributeName: font};
NSMutableAttributedString *content = [[NSMutableAttributedString alloc] initWithString:contentString attributes:attributeDic];
for (NSInteger k = 0; k < rangeArray.count; k++) {
//通过range遍历, 替换为icomoon
[content setAttributes:@{NSFontAttributeName : [UIFont fontWithName:@"icomoon" size:font.pointSize]} range:[rangeArray[k] rangeValue]];
}
[content addLineSpace:lineSpace];
return content;
}
@end
// BRACKETS 即 brackets,中括号的意思
#define CHINESE_BRACKETS @[@"【", @"】"]
#define ICON_FONT_BRACKETS @[@"\U0000e901", @"\U0000e902"]
@implementation NSString (IconFontFactory)
- (NSMutableAttributedString *)welfareDetailNameWithFontSize:(NSInteger)size
textColor:(UIColor *)textColor {
NSMutableAttributedString *content = [self replaceString:CHINESE_BRACKETS
withStrings:ICON_FONT_BRACKETS
font:[UIFont fontWithName:@"PingFangSC-Medium" size:size]
textColor:textColor ?: RGBFCOLOR_HEX(0x282828)
lineSpace:0];
return content;
}
- (NSMutableAttributedString *)welfareNameWithFontSize:(NSInteger)size
textColor:(UIColor *)textColor
lineSpace:(NSInteger)lineSpace {
NSMutableAttributedString *content = [self replaceString:CHINESE_BRACKETS withStrings:ICON_FONT_BRACKETS font:[UIFont fontWithName:@"PingFangSC-Regular" size:size] textColor:textColor ?: RGBFCOLOR_HEX(0x282828) lineSpace:lineSpace];
return content;
}
- (NSMutableAttributedString *)customDetailNameWithFontSize:(UIFont *)font
textColor:(UIColor *)textColor
lineSpace:(NSInteger)lineSpace {
NSMutableAttributedString *content = [self replaceString:CHINESE_BRACKETS withStrings:ICON_FONT_BRACKETS font:font textColor:textColor ?: RGBFCOLOR_HEX(0x282828) lineSpace:lineSpace];
return content;
}
@end
//
// NSString+RegularString.h
// Gengmei
//
// Created by 汪俊 on 2018/2/1.
// Copyright © 2018年 更美互动信息科技有限公司. All rights reserved.
//
#import <Foundation/Foundation.h>
@interface NSString (RegularString)
- (NSString *)regularPattern:(NSArray *)keys;
/** 数量转换 */
- (NSString *)numberToString;
/** 转换
<1w,显示具体数字
>=1w,显示1w+
以每一w为单位递增2w+、3w+、4w+
*/
- (NSString *)numberToWString;
/** 转换
<=99,显示具体数字
>99,显示99+
*/
- (NSString *)numberTo99String;
@end
//
// NSString+RegularString.m
// Gengmei
//
// Created by 汪俊 on 2018/2/1.
// Copyright © 2018年 更美互动信息科技有限公司. All rights reserved.
//
#import "NSString+RegularString.h"
@implementation NSString (RegularString)
/**
关键字 正则判断
*/
- (NSString *)regularPattern:(NSArray *)keys{
NSMutableString *pattern = [[NSMutableString alloc]initWithString:@"(?i)"];
for (NSString *key in keys) {
[pattern appendFormat:@"%@|",key];
}
return pattern;
}
- (NSString *)numberToString {
NSInteger num = self.integerValue;
if (num > 0 && num < 10000) {
return self;
} else if (num >= 10000) {
return [NSString stringWithFormat:@"%ld.%.0f万", num / 10000, ceilf(num % 10000 / 1000.0)];
} else {
return @"0";
}
}
- (NSString *)numberToWString {
NSInteger num = self.integerValue;
if (num > 0 && num < 10000) {
return self;
} else if (num >= 10000) {
return [NSString stringWithFormat:@"%ldw+", num / 10000];
} else {
return @"";
}
}
- (NSString *)numberTo99String {
NSInteger num = self.integerValue;
if (num > 0 && num < 100) {
return self;
} else if (num > 99) {
return [NSString stringWithFormat:@"99+"];
} else {
return @"";
}
}
@end
//
// String+GM.swift
// Gengmei
//
// Created by wangyang on 2017/12/21.
// Copyright © 2017年 更美互动信息科技有限公司. All rights reserved.
//
import Foundation
// MARK: - 字符串截取
extension String {
// 截取字符串从开始到index
func substring(to index: Int) -> String {
guard let end_Index = validEndIndex(original: index) else {
return self
}
return String(self[startIndex..<end_Index])
}
// 截取字符串从index到结束
func substring(from index: Int) -> String {
guard let start_index = validStartIndex(original: index) else {
return self
}
return String(self[start_index..<endIndex])
}
// 切割字符串(区间范围 前闭后开)
func sliceString(_ range: CountableRange<Int>) -> String {
guard
let startIndex = validStartIndex(original: range.lowerBound),
let endIndex = validEndIndex(original: range.upperBound),
startIndex <= endIndex
else {
return ""
}
return String(self[startIndex..<endIndex])
}
// 切割字符串(区间范围 前闭后闭)
func sliceString(_ range: CountableClosedRange<Int>) -> String {
guard
let start_Index = validStartIndex(original: range.lowerBound),
let end_Index = validEndIndex(original: range.upperBound),
startIndex <= endIndex
else {
return ""
}
if endIndex.encodedOffset <= end_Index.encodedOffset {
return String(self[start_Index..<endIndex])
}
return String(self[start_Index...end_Index])
}
// 校验字符串位置 是否合理,并返回String.Index
func validIndex(original: Int) -> String.Index {
switch original {
case ...startIndex.encodedOffset : return startIndex
case endIndex.encodedOffset... : return endIndex
default : return index(startIndex, offsetBy: original)
}
}
// 校验是否是合法的起始位置
func validStartIndex(original: Int) -> String.Index? {
guard original <= endIndex.encodedOffset else { return nil }
return validIndex(original: original)
}
// 校验是否是合法的结束位置
func validEndIndex(original: Int) -> String.Index? {
guard original >= startIndex.encodedOffset else { return nil }
return validIndex(original: original)
}
}
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.
# GMFoundation
[![CI Status](http://img.shields.io/travis/licong/GMFoundation.svg?style=flat)](https://travis-ci.org/licong/GMFoundation)
[![Version](https://img.shields.io/cocoapods/v/GMFoundation.svg?style=flat)](http://cocoapods.org/pods/GMFoundation)
[![License](https://img.shields.io/cocoapods/l/GMFoundation.svg?style=flat)](http://cocoapods.org/pods/GMFoundation)
[![Platform](https://img.shields.io/cocoapods/p/GMFoundation.svg?style=flat)](http://cocoapods.org/pods/GMFoundation)
## Example
To run the example project, clone the repo, and run `pod install` from the Example directory first.
## Requirements
## Installation
GMFoundation is available through [CocoaPods](http://cocoapods.org). To install
it, simply add the following line to your Podfile:
```ruby
pod "GMFoundation"
```
## Author
licong, 1240690490@qq.com
## License
GMFoundation is available under the MIT license. See the LICENSE file for more info.
//
// GMRouter+gm.h
// GMRouter
//
// Created by Q14 on 2019/11/28.
//
#import "GMRouter.h"
#import "Target_commons.h"
NS_ASSUME_NONNULL_BEGIN
/**
* 将函数名称编码成CTMediator能解析的方法名称
*
*/
NSString *enActionFuncName(NSString *actionName);
/**
* 通过函数名称解析出类的名称
*
*/
NSString *deActionFuncName(NSString *action);
/**
* 通过SEL参数解析出类的实例
*
*/
Class getClassFromAtcion(SEL sel);
//extern NSString *const kCTMediatorClassName;
/**
* 注册自定义的创建vc函数名称
*
*/
void registerSelectorToMediator(NSString *clsName,NSString *selName);
/**
* 删除自定义的创建vc函数名称
*
*/
void removeSelectorToMediator(NSString *clsName);
@interface GMRouter (gm)
/**
* 通过vc类的名字创建vc,默认的vc创建函数为createVC:
*
* @param actionName vc类名称
*
* @param params 创建vc初始化要传递的参数
*
* @param shouldCacheTarget 是否需要缓存target,一般传NO
*
* @return vc的实例
*
*/
- (id)performAction:(NSString *)actionName params:(NSDictionary *)params shouldCacheTarget:(BOOL)shouldCacheTarget;
/**
* 通过vc类的名字创建vc
*
* @param actionName vc类名称
*
* @param dstSelName vc中实现的创建vc的函数,不要在这个方法中使用self关键字,获取当前类名则
* 通过使用getClassFromAtcion(_cmd)来获取
*
* @param params 创建vc初始化要传递的参数
*
* @param shouldCacheTarget 是否需要缓存target,一般传NO
*
* @return vc的实例
*
*/
- (id)performAction:(NSString *)actionName dstSel:(NSString *)dstSelName params:(NSDictionary *)params shouldCacheTarget:(BOOL)shouldCacheTarget;
/**
* 通过vc类的名字创建vc 兼容项目中的更美协议
*
* @param urlScheme 协议名字 * 例如gengmei://welfare_special?service_id=5930&is_new_special=0
*
*
*
* @return vc的实例
*
*/
- (id)pushScheme:(NSString *)urlScheme;
/**
* 通过vc类的名字创建vc
*
* @param urlScheme vc类名称
* 例如gengmei://welfare_special
*
* @param params 创建vc初始化要传递的参数
* {@"service_id": @"5930",@"is_new_special": @0}
*
* @return vc的实例
*
*/
- (id)pushScheme:(NSString *)urlScheme params:(NSDictionary *)params;
/**
* 初始化Map
*
*/
- (void)initializeRouteMap;
@end
NS_ASSUME_NONNULL_END
//
// GMRouter+gm.m
// GMRouter
//
// Created by Q14 on 2019/11/28.
//
#import "GMRouter+gm.h"
#import <objc/message.h>
#import <objc/runtime.h>
//nsstring
static inline BOOL verifiedString(id strlike) {
if (strlike && ![strlike isEqual:[NSNull null]] && [[strlike class] isSubclassOfClass:[NSString class]] && ((NSString*)strlike).length > 0) {
return YES;
}else{
return NO;
}
}
NSString *const GMRouterActionPrefix = @"Action_";
NSString *const GMRouterActionSuffix = @":";
NSString *const GMRouterTargetPrefix = @"Target_";
/**
增加一个魔块 需要在路由做映射
*/
NSString *const GMRouterTargetAI = @"Target_AI";
NSString *const GMRouterTargetBanking = @"Target_Banking";
NSString *const GMRouterTargetCommunity = @"Target_Community";
NSString *const GMRouterTargetWeb = @"Target_Web";
static NSMutableDictionary *routeMap = nil;
@implementation GMRouter (gm)
- (void)initializeRouteMap {
routeMap = [[NSMutableDictionary alloc] initWithCapacity:50];
NSArray *arr = @[GMRouterTargetAI, GMRouterTargetBanking, GMRouterTargetCommunity, GMRouterTargetWeb];
for (NSString *clsStr in arr) {
NSDictionary *dict = [self getMethods:clsStr];
[routeMap addEntriesFromDictionary:dict];
}
}
#pragma mark - 获取类的所有方法
// 获取所有的方法
- (NSDictionary *)getMethods:(NSString *)clsStr {
Class cls = NSClassFromString(clsStr);
NSRange range = [clsStr rangeOfString:@"Target_"];
NSString *targetValue = [clsStr substringFromIndex:range.length];
NSAssert(targetValue.length != 0, @"Target_后不能为空!请注意Target");
unsigned int count = 0;
NSMutableDictionary *dict = [NSMutableDictionary dictionary];
// 获取类的所有 Method
Method *methods = class_copyMethodList([cls class], &count);
for (NSUInteger i = 0; i < count; i ++) {
// 获取方法 Name
SEL methodSEL = method_getName(methods[i]);
const char *methodName = sel_getName(methodSEL);
NSString *name = [NSString stringWithUTF8String:methodName];
//获取到的是这样的 pushToHospitalDetail: 因此要去掉:
NSString *rangeStr = @":";
if ([name containsString:rangeStr]) {
NSRange range = [name rangeOfString:rangeStr];
name = [name substringToIndex:range.location];
}
// 获取方法的参数列表
int arguments = method_getNumberOfArguments(methods[i]);
NSString *promoteStr = [NSString stringWithFormat:@"%@-内有重复的方法名-%@", clsStr, name];
NSAssert(![dict.allKeys containsObject:name], promoteStr);
//因为消息发送的时候会有两个默认的参数(消息接受者和方法名),所以需要减去2
dict[name] = targetValue;
}
free(methods);
return dict;
}
- (id)performAction:(NSString *)actionName params:(NSDictionary *)params shouldCacheTarget:(BOOL)shouldCacheTarget{
NSString *selName = @"createVC:";
NSString *sel = [routeMap objectForKey:actionName];
if (verifiedString(sel)) selName = sel;
return [self performAction:actionName dstSel:selName params:params shouldCacheTarget:shouldCacheTarget];
}
- (id)performAction:(NSString *)actionName dstSel:(NSString *)dstSelName params:(NSDictionary *)params shouldCacheTarget:(BOOL)shouldCacheTarget{
Class class = NSClassFromString(actionName);
SEL sel = NSSelectorFromString(dstSelName);
IMP imp = [class instanceMethodForSelector:sel];
if (!imp || imp == _objc_msgForward) {
imp = [class methodForSelector:sel];
}
SEL selector = NSSelectorFromString(enActionFuncName(actionName));
Class targetCls = NSClassFromString([NSString stringWithFormat:@"%@%@",GMRouterTargetPrefix,GMRouterTargetCommons]);
if (!class_respondsToSelector(targetCls, selector)) {
BOOL flag = class_addMethod(targetCls, selector, imp, "@@:@");
if (!flag) {
return nil;
}
}
id action = [self performTarget:GMRouterTargetCommons action:actionName params:params shouldCacheTarget:shouldCacheTarget];
if (![action isKindOfClass:class]) {
Class class = NSClassFromString(@"ErrorViewController");
UIViewController *vc = [[class alloc] init];
return vc;
}else {
return [action isKindOfClass:class] ? action : nil;
}
}
- (id)pushScheme:(NSString *)urlScheme {
NSString *encodeUrlScheme = [self URLEncodeString:urlScheme];
NSURL *url = [NSURL URLWithString:encodeUrlScheme];
if (!url) {
// debugLog(@"协议出错了!");
}
NSString *host = url.host;
NSString *targetName = [routeMap objectForKey:host];
NSDictionary *params = [self getParams:encodeUrlScheme withHost:host];
host = [self getHostWithEncodeUrlScheme:encodeUrlScheme host:host];
return [self performTarget:targetName action:host params:params shouldCacheTarget:NO];
}
- (NSDictionary *)getParams:(NSString *)encodeUrlScheme withHost:(NSString *)host {
NSDictionary *params;
NSArray *array = [encodeUrlScheme componentsSeparatedByString:@"url="];
if (([host isEqualToString:@"third_webview"] || [host isEqualToString:@"common_webview"]) && array.count > 1) {
NSString *value = array[1];
//拦截所有的即将调转的url(value),如果不在白名单之中,让其使用GMThirdWebViewController加载. 必须要用while 因为url部分包含 %3A gengmei://common_webview?url=http%3A//backend.paas.env/hybrid/base_wiki/item/285
while ([value rangeOfString:@"%"].length != 0) {
value = [self URLDecodedString:value];
}
params = @{@"url":value};
} else {
params = [self urlQueryToDictionary:encodeUrlScheme];
}
return params;
}
- (NSString *)getHostWithEncodeUrlScheme:(NSString *)encodeUrlScheme host:(NSString *)host {
NSArray *array = [encodeUrlScheme componentsSeparatedByString:@"url="];
if (([host isEqualToString:@"third_webview"] || [host isEqualToString:@"common_webview"]) && array.count > 1) {
NSString *value = array[1];
while ([value rangeOfString:@"%"].length != 0) {
value = [ self URLDecodedString:value];
}
//拦截所有的即将调转的url(value),如果不在白名单之中,让其使用GMThirdWebViewController加载.
NSString *valueHost = [[NSURL URLWithString:value] host];
Class cls = NSClassFromString(@"GMServerDomains");
if ([cls respondsToSelector:@selector(allowURLHost:)]) {
BOOL isAllow = [cls performSelector:@selector(allowURLHost:) withObject:valueHost];
if (!isAllow) {
host = @"third_webview";
}
}
}
return host;
}
//-(void)
- (id)pushScheme:(NSString *)urlScheme params:(NSDictionary *)params {
NSString *encodeUrlScheme = [self URLEncodeString:urlScheme];
NSURL *url = [NSURL URLWithString:encodeUrlScheme];
if (!url) {
// debugLog(@"协议出错了!");
}
NSString *host = url.host;
NSString *targetName = [routeMap objectForKey:host];
NSDictionary *paramsDict = [self getParams:encodeUrlScheme withHost:host];
host = [self getHostWithEncodeUrlScheme:encodeUrlScheme host:host];
return [self performTarget:targetName action:host params:paramsDict shouldCacheTarget:NO];
}
#pragma mark - string to dict
- (NSDictionary*)urlQueryToDictionary:(NSString *)urlScheme {
NSURL* url1 = [NSURL URLWithString:urlScheme];
if (!url1) {
return nil;
}
NSString *query = [url1 query];
return [self queryToDictionary:query];
}
- (NSDictionary*)queryToDictionary:(NSString *)query {
@try {
NSMutableDictionary* dict = [NSMutableDictionary dictionary];
NSArray* components = [query componentsSeparatedByString:@"&"];
for (NSString* component in components) {
NSArray* keyValue = [component componentsSeparatedByString:@"="];
if ([keyValue count] > 1) {
NSString * key = [self URLDecodedString:[keyValue objectAtIndex:0]];
NSString * value = [keyValue objectAtIndex:1];
//参数中依然包含超过2个“=”号(多数发生在common_webview后的url参数中),则后面的数组的元素需要拼接成一个字符串
if ([keyValue count]>2) {
for (int i=2; i<[keyValue count]; i++) {
value=[value stringByAppendingString:@"="];
value=[value stringByAppendingString:keyValue[i]];
}
}
//因为这种情况服务器和客户端都转义了一次,所以要两次反转义还原中文
while ([value rangeOfString:@"%"].length != 0) {
value = [self URLDecodedString:value];
}
[dict setObject: value forKey: key];
}
}
return dict;
}
@catch (NSException *exception) {}
}
- (NSString*)URLDecodedString:(NSString *)urlStr {
NSString *result = (NSString *)CFBridgingRelease(CFURLCreateStringByReplacingPercentEscapesUsingEncoding(kCFAllocatorDefault,
(CFStringRef)urlStr,
CFSTR(""),
kCFStringEncodingUTF8));
return result;
}
- (NSString *)URLEncodeString:(NSString *)urlStr {
NSString *encodedString = [urlStr stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLQueryAllowedCharacterSet]];
return encodedString;
}
@end
NSString *enActionFuncName(NSString *actionName){
return [NSString stringWithFormat:@"%@:",actionName];
}
NSString *deActionFuncName(NSString *action){
// NSString *prefix = @"Action_";
// NSString *suffix = @":";TargetCommons
if ([action hasPrefix:GMRouterActionPrefix] &&
[action hasSuffix:GMRouterActionSuffix]) {
return [action substringWithRange:NSMakeRange(GMRouterActionPrefix.length, action.length - GMRouterActionPrefix.length - GMRouterActionSuffix.length)];
}
return action;
}
Class getClassFromAtcion(SEL sel){
return NSClassFromString(deActionFuncName(NSStringFromSelector(sel)));
}
void registerSelectorToMediator(NSString *clsName,NSString *selName){
if (!routeMap) {
routeMap = [[NSMutableDictionary alloc] init];
}
[routeMap setObject:selName forKey:clsName];
}
void removeSelectorToMediator(NSString *clsName){
[routeMap removeObjectForKey:clsName];
}
//
// GMRouter.h
// GMRouter
//
// Created by Q14 on 2019/11/28.
//
//#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
extern NSString * const GMRouterParamsKeySwiftTargetModuleName;
@interface GMRouter : NSObject
+ (instancetype)sharedInstance;
// 远程App调用入口 universalLink
- (id)performActionWithUrl:(NSURL *)url completion:(void(^)(NSDictionary *info))completion;
// 本地组件调用入口
- (id)performTarget:(NSString *)targetName action:(NSString *)actionName params:(NSDictionary *)params shouldCacheTarget:(BOOL)shouldCacheTarget;
- (void)releaseCachedTargetWithTargetName:(NSString *)targetName;
@end
NS_ASSUME_NONNULL_END
//
// GMRouter.m
// GMRouter
//
// Created by Q14 on 2019/11/28.
//
#import "GMRouter.h"
#import "GMRouter+gm.h"
NSString * const GMRouterParamsKeySwiftTargetModuleName = @"GMRouterParamsKeySwiftTargetModuleName";
@interface GMRouter()
@property (nonatomic, strong) NSMutableDictionary *cachedTarget;
@end
@implementation GMRouter
#pragma mark - public methods
+ (instancetype)sharedInstance {
static GMRouter *router;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
router = [[GMRouter alloc] init];
[router initializeRouteMap];
});
return router;
}
/*
scheme://[target]/[action]?[params]
url sample:
aaa://targetA/actionB?id=1234
*/
- (id)performActionWithUrl:(NSURL *)url completion:(void (^)(NSDictionary *))completion
{
NSMutableDictionary *params = [[NSMutableDictionary alloc] init];
NSString *urlString = [url query];
for (NSString *param in [urlString componentsSeparatedByString:@"&"]) {
NSArray *elts = [param componentsSeparatedByString:@"="];
if([elts count] < 2) continue;
[params setObject:[elts lastObject] forKey:[elts firstObject]];
}
// 这里这么写主要是出于安全考虑,防止黑客通过远程方式调用本地模块。这里的做法足以应对绝大多数场景,如果要求更加严苛,也可以做更加复杂的安全逻辑。
NSString *actionName = [url.path stringByReplacingOccurrencesOfString:@"/" withString:@""];
if ([actionName hasPrefix:@"native"]) {
return @(NO);
}
// 这个demo针对URL的路由处理非常简单,就只是取对应的target名字和method名字,但这已经足以应对绝大部份需求。如果需要拓展,可以在这个方法调用之前加入完整的路由逻辑
id result = [self performTarget:url.host action:actionName params:params shouldCacheTarget:NO];
if (completion) {
if (result) {
completion(@{@"result":result});
} else {
completion(nil);
}
}
return result;
}
- (id)performTarget:(NSString *)targetName action:(NSString *)actionName params:(NSDictionary *)params shouldCacheTarget:(BOOL)shouldCacheTarget
{
NSString *swiftModuleName = params[GMRouterParamsKeySwiftTargetModuleName];
// generate target
NSString *targetClassString = nil;
if (swiftModuleName.length > 0) {
targetClassString = [NSString stringWithFormat:@"%@.Target_%@", swiftModuleName, targetName];
} else {
targetClassString = [NSString stringWithFormat:@"Target_%@", targetName];
}
NSObject *target = self.cachedTarget[targetClassString];
if (target == nil) {
Class targetClass = NSClassFromString(targetClassString);
target = [[targetClass alloc] init];
}
// generate action
NSString *actionString = [NSString stringWithFormat:@"%@:", actionName];
SEL action = NSSelectorFromString(actionString);
if (target == nil) {
// 这里是处理无响应请求的地方之一,这个demo做得比较简单,如果没有可以响应的target,就直接return了。实际开发过程中是可以事先给一个固定的target专门用于在这个时候顶上,然后处理这种请求的
[self NoTargetActionResponseWithTargetString:targetClassString selectorString:actionString originParams:params];
return nil;
}
if (shouldCacheTarget) {
self.cachedTarget[targetClassString] = target;
}
if ([target respondsToSelector:action]) {
return [self safePerformAction:action target:target params:params];
} else {
// 这里是处理无响应请求的地方,如果无响应,则尝试调用对应target的notFound方法统一处理
SEL action = NSSelectorFromString(@"notFound:");
if ([target respondsToSelector:action]) {
return [self safePerformAction:action target:target params:params];
} else {
// 这里也是处理无响应请求的地方,在notFound都没有的时候,这个demo是直接return了。实际开发过程中,可以用前面提到的固定的target顶上的。
[self NoTargetActionResponseWithTargetString:targetClassString selectorString:actionString originParams:params];
[self.cachedTarget removeObjectForKey:targetClassString];
return nil;
}
}
}
- (void)releaseCachedTargetWithTargetName:(NSString *)targetName
{
NSString *targetClassString = [NSString stringWithFormat:@"Target_%@", targetName];
[self.cachedTarget removeObjectForKey:targetClassString];
}
#pragma mark - private methods
- (void)NoTargetActionResponseWithTargetString:(NSString *)targetString selectorString:(NSString *)selectorString originParams:(NSDictionary *)originParams
{
SEL action = NSSelectorFromString(@"Action_response:");
NSObject *target = [[NSClassFromString(@"Target_NoTargetAction") alloc] init];
NSMutableDictionary *params = [[NSMutableDictionary alloc] init];
params[@"originParams"] = originParams;
params[@"targetString"] = targetString;
params[@"selectorString"] = selectorString;
[self safePerformAction:action target:target params:params];
}
- (id)safePerformAction:(SEL)action target:(NSObject *)target params:(NSDictionary *)params
{
NSMethodSignature* methodSig = [target methodSignatureForSelector:action];
if(methodSig == nil) {
return nil;
}
const char* retType = [methodSig methodReturnType];
if (strcmp(retType, @encode(void)) == 0) {
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:methodSig];
[invocation setArgument:&params atIndex:2];
[invocation setSelector:action];
[invocation setTarget:target];
[invocation invoke];
return nil;
}
if (strcmp(retType, @encode(NSInteger)) == 0) {
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:methodSig];
[invocation setArgument:&params atIndex:2];
[invocation setSelector:action];
[invocation setTarget:target];
[invocation invoke];
NSInteger result = 0;
[invocation getReturnValue:&result];
return @(result);
}
if (strcmp(retType, @encode(BOOL)) == 0) {
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:methodSig];
[invocation setArgument:&params atIndex:2];
[invocation setSelector:action];
[invocation setTarget:target];
[invocation invoke];
BOOL result = 0;
[invocation getReturnValue:&result];
return @(result);
}
if (strcmp(retType, @encode(CGFloat)) == 0) {
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:methodSig];
[invocation setArgument:&params atIndex:2];
[invocation setSelector:action];
[invocation setTarget:target];
[invocation invoke];
CGFloat result = 0;
[invocation getReturnValue:&result];
return @(result);
}
if (strcmp(retType, @encode(NSUInteger)) == 0) {
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:methodSig];
[invocation setArgument:&params atIndex:2];
[invocation setSelector:action];
[invocation setTarget:target];
[invocation invoke];
NSUInteger result = 0;
[invocation getReturnValue:&result];
return @(result);
}
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
return [target performSelector:action withObject:params];
#pragma clang diagnostic pop
}
#pragma mark - getters and setters
- (NSMutableDictionary *)cachedTarget
{
if (_cachedTarget == nil) {
_cachedTarget = [[NSMutableDictionary alloc] init];
}
return _cachedTarget;
}
@end
//
// Target_commons.h
// GMRouter
//
// Created by Q14 on 2019/11/28.
//
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
extern NSString * const GMRouterTargetCommons;
@interface Target_commons : NSObject
// 自定义push方法
- (UIViewController *)push_CommonViewController:(NSString *)stringVCName params:(NSDictionary *)params;
@end
NS_ASSUME_NONNULL_END
//
// Target_commons.m
// GMRouter
//
// Created by Q14 on 2019/11/28.
//
#import "Target_commons.h"
NSString * const GMRouterTargetCommons = @"commons";
@implementation Target_commons
// 自定义push方法
- (UIViewController *)push_CommonViewController:(NSString *)stringVCName params:(NSDictionary *)params {
// 因为action是从属于ModuleA的,所以action直接可以使用ModuleA里的所有声明
Class class = NSClassFromString(stringVCName);
UIViewController *controller = [[class alloc] init];
return controller;
}
@end
//
// UIViewController+Router.h
// GMRouter
//
// Created by Q14 on 2019/11/28.
//
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
@interface UIViewController (Router)
- (id)createVC:(NSDictionary *)dict;
@end
NS_ASSUME_NONNULL_END
//
// UIViewController+Router.m
// GMRouter
//
// Created by Q14 on 2019/11/28.
//
#import "UIViewController+Router.h"
#import "GMRouter+gm.h"
@implementation UIViewController (Router)
- (id)createVC:(NSDictionary *)dict{
Class class = getClassFromAtcion(_cmd);
if (class) {
UIViewController *doc = self;
doc = [[class alloc]init];
// doc = [doc mj_setKeyValues:dict];
return doc;
}
return nil;
}
@end
Copyright (c) 2019 Q14 <qiaojinzhu@igengmei.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.
# GMRouter
[![CI Status](https://img.shields.io/travis/Q14/GMRouter.svg?style=flat)](https://travis-ci.org/Q14/GMRouter)
[![Version](https://img.shields.io/cocoapods/v/GMRouter.svg?style=flat)](https://cocoapods.org/pods/GMRouter)
[![License](https://img.shields.io/cocoapods/l/GMRouter.svg?style=flat)](https://cocoapods.org/pods/GMRouter)
[![Platform](https://img.shields.io/cocoapods/p/GMRouter.svg?style=flat)](https://cocoapods.org/pods/GMRouter)
## Example
To run the example project, clone the repo, and run `pod install` from the Example directory first.
## Requirements
## Installation
GMRouter is available through [CocoaPods](https://cocoapods.org). To install
it, simply add the following line to your Podfile:
```ruby
pod 'GMRouter'
```
## Author
Q14, qiaojinzhu@igengmei.com
## License
GMRouter is available under the MIT license. See the LICENSE file for more info.
...@@ -25,6 +25,27 @@ ...@@ -25,6 +25,27 @@
], ],
"GMNetworking": [ "GMNetworking": [
],
"EVReflection": [
],
"GMBaseSwift": [
],
"GMCache": [
],
"GMRouter": [
],
"Qiniu": [
],
"GMFoundation": [
],
"SDWebImage": [
] ]
} }
} }
{
"name": "GMBaseSwift",
"version": "3.3.7",
"summary": "GMBaseSwift.",
"homepage": "http://git.wanmeizhensuo.com/gengmeiios/GMBaseSwift",
"license": {
"type": "MIT",
"file": "LICENSE"
},
"authors": {
"wangyang": "wangyang@wanmeizhensuo.com"
},
"source": {
"git": "git@git.wanmeizhensuo.com:gengmeiios/GMBaseSwift.git",
"tag": "3.3.7"
},
"platforms": {
"ios": "8.0"
},
"source_files": "GMBaseSwift/Classes/**/*",
"dependencies": {
"GMNetworking": [
],
"GM-Swift-Observable": [
],
"EVReflection": [
"5.10.0"
],
"SnapKit": [
"4.0.0"
],
"GMRefresh": [
],
"GMPhobos": [
],
"GMHud": [
]
}
}
Copyright (c) 2013-2019 MJExtension (https://github.com/CoderMJLee/MJExtension)
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.
//
// MJExtension.h
// MJExtension
//
// Created by mj on 14-1-15.
// Copyright (c) 2014年 小码哥. All rights reserved.
//
#import <Foundation/Foundation.h>
#import "NSObject+MJCoding.h"
#import "NSObject+MJProperty.h"
#import "NSObject+MJClass.h"
#import "NSObject+MJKeyValue.h"
#import "NSString+MJExtension.h"
#import "MJExtensionConst.h"
#import "MJFoundation.h"
//! Project version number for MJExtension.
FOUNDATION_EXPORT double MJExtensionVersionNumber;
//! Project version string for MJExtension.
FOUNDATION_EXPORT const unsigned char MJExtensionVersionString[];
// In this header, you should import all the public headers of your framework using statements like #import <MJExtension/PublicHeader.h>
#ifndef __MJExtensionConst__H__
#define __MJExtensionConst__H__
#import <Foundation/Foundation.h>
#ifndef MJ_LOCK
#define MJ_LOCK(lock) dispatch_semaphore_wait(lock, DISPATCH_TIME_FOREVER);
#endif
#ifndef MJ_UNLOCK
#define MJ_UNLOCK(lock) dispatch_semaphore_signal(lock);
#endif
// 信号量
#define MJExtensionSemaphoreCreate \
static dispatch_semaphore_t signalSemaphore; \
static dispatch_once_t onceTokenSemaphore; \
dispatch_once(&onceTokenSemaphore, ^{ \
signalSemaphore = dispatch_semaphore_create(1); \
});
#define MJExtensionSemaphoreWait MJ_LOCK(signalSemaphore)
#define MJExtensionSemaphoreSignal MJ_UNLOCK(signalSemaphore)
// 过期
#define MJExtensionDeprecated(instead) NS_DEPRECATED(2_0, 2_0, 2_0, 2_0, instead)
// 构建错误
#define MJExtensionBuildError(clazz, msg) \
NSError *error = [NSError errorWithDomain:msg code:250 userInfo:nil]; \
[clazz setMj_error:error];
// 日志输出
#ifdef DEBUG
#define MJExtensionLog(...) NSLog(__VA_ARGS__)
#else
#define MJExtensionLog(...)
#endif
/**
* 断言
* @param condition 条件
* @param returnValue 返回值
*/
#define MJExtensionAssertError(condition, returnValue, clazz, msg) \
[clazz setMj_error:nil]; \
if ((condition) == NO) { \
MJExtensionBuildError(clazz, msg); \
return returnValue;\
}
#define MJExtensionAssert2(condition, returnValue) \
if ((condition) == NO) return returnValue;
/**
* 断言
* @param condition 条件
*/
#define MJExtensionAssert(condition) MJExtensionAssert2(condition, )
/**
* 断言
* @param param 参数
* @param returnValue 返回值
*/
#define MJExtensionAssertParamNotNil2(param, returnValue) \
MJExtensionAssert2((param) != nil, returnValue)
/**
* 断言
* @param param 参数
*/
#define MJExtensionAssertParamNotNil(param) MJExtensionAssertParamNotNil2(param, )
/**
* 打印所有的属性
*/
#define MJLogAllIvars \
- (NSString *)description \
{ \
return [self mj_keyValues].description; \
}
#define MJExtensionLogAllProperties MJLogAllIvars
/** 仅在 Debugger 展示所有的属性 */
#define MJImplementDebugDescription \
- (NSString *)debugDescription \
{ \
return [self mj_keyValues].debugDescription; \
}
/**
* 类型(属性类型)
*/
FOUNDATION_EXPORT NSString *const MJPropertyTypeInt;
FOUNDATION_EXPORT NSString *const MJPropertyTypeShort;
FOUNDATION_EXPORT NSString *const MJPropertyTypeFloat;
FOUNDATION_EXPORT NSString *const MJPropertyTypeDouble;
FOUNDATION_EXPORT NSString *const MJPropertyTypeLong;
FOUNDATION_EXPORT NSString *const MJPropertyTypeLongLong;
FOUNDATION_EXPORT NSString *const MJPropertyTypeChar;
FOUNDATION_EXPORT NSString *const MJPropertyTypeBOOL1;
FOUNDATION_EXPORT NSString *const MJPropertyTypeBOOL2;
FOUNDATION_EXPORT NSString *const MJPropertyTypePointer;
FOUNDATION_EXPORT NSString *const MJPropertyTypeIvar;
FOUNDATION_EXPORT NSString *const MJPropertyTypeMethod;
FOUNDATION_EXPORT NSString *const MJPropertyTypeBlock;
FOUNDATION_EXPORT NSString *const MJPropertyTypeClass;
FOUNDATION_EXPORT NSString *const MJPropertyTypeSEL;
FOUNDATION_EXPORT NSString *const MJPropertyTypeId;
#endif
#ifndef __MJExtensionConst__M__
#define __MJExtensionConst__M__
#import <Foundation/Foundation.h>
/**
* 成员变量类型(属性类型)
*/
NSString *const MJPropertyTypeInt = @"i";
NSString *const MJPropertyTypeShort = @"s";
NSString *const MJPropertyTypeFloat = @"f";
NSString *const MJPropertyTypeDouble = @"d";
NSString *const MJPropertyTypeLong = @"l";
NSString *const MJPropertyTypeLongLong = @"q";
NSString *const MJPropertyTypeChar = @"c";
NSString *const MJPropertyTypeBOOL1 = @"c";
NSString *const MJPropertyTypeBOOL2 = @"b";
NSString *const MJPropertyTypePointer = @"*";
NSString *const MJPropertyTypeIvar = @"^{objc_ivar=}";
NSString *const MJPropertyTypeMethod = @"^{objc_method=}";
NSString *const MJPropertyTypeBlock = @"@?";
NSString *const MJPropertyTypeClass = @"#";
NSString *const MJPropertyTypeSEL = @":";
NSString *const MJPropertyTypeId = @"@";
#endif
\ No newline at end of file
//
// MJFoundation.h
// MJExtensionExample
//
// Created by MJ Lee on 14/7/16.
// Copyright (c) 2014年 小码哥. All rights reserved.
//
#import <Foundation/Foundation.h>
@interface MJFoundation : NSObject
+ (BOOL)isClassFromFoundation:(Class)c;
+ (BOOL)isFromNSObjectProtocolProperty:(NSString *)propertyName;
@end
//
// MJFoundation.m
// MJExtensionExample
//
// Created by MJ Lee on 14/7/16.
// Copyright (c) 2014年 小码哥. All rights reserved.
//
#import "MJFoundation.h"
#import "MJExtensionConst.h"
#import <CoreData/CoreData.h>
#import "objc/runtime.h"
@implementation MJFoundation
+ (BOOL)isClassFromFoundation:(Class)c
{
if (c == [NSObject class] || c == [NSManagedObject class]) return YES;
static NSSet *foundationClasses;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
// 集合中没有NSObject,因为几乎所有的类都是继承自NSObject,具体是不是NSObject需要特殊判断
foundationClasses = [NSSet setWithObjects:
[NSURL class],
[NSDate class],
[NSValue class],
[NSData class],
[NSError class],
[NSArray class],
[NSDictionary class],
[NSString class],
[NSAttributedString class], nil];
});
__block BOOL result = NO;
[foundationClasses enumerateObjectsUsingBlock:^(Class foundationClass, BOOL *stop) {
if ([c isSubclassOfClass:foundationClass]) {
result = YES;
*stop = YES;
}
}];
return result;
}
+ (BOOL)isFromNSObjectProtocolProperty:(NSString *)propertyName
{
if (!propertyName) return NO;
static NSSet<NSString *> *objectProtocolPropertyNames;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
unsigned int count = 0;
objc_property_t *propertyList = protocol_copyPropertyList(@protocol(NSObject), &count);
NSMutableSet *propertyNames = [NSMutableSet setWithCapacity:count];
for (int i = 0; i < count; i++) {
objc_property_t property = propertyList[i];
NSString *propertyName = [NSString stringWithCString:property_getName(property) encoding:NSUTF8StringEncoding];
if (propertyName) {
[propertyNames addObject:propertyName];
}
}
objectProtocolPropertyNames = [propertyNames copy];
free(propertyList);
});
return [objectProtocolPropertyNames containsObject:propertyName];
}
@end
//
// MJProperty.h
// MJExtensionExample
//
// Created by MJ Lee on 15/4/17.
// Copyright (c) 2015年 小码哥. All rights reserved.
// 包装一个成员属性
#import <Foundation/Foundation.h>
#import <objc/runtime.h>
#import "MJPropertyType.h"
#import "MJPropertyKey.h"
/**
* 包装一个成员
*/
@interface MJProperty : NSObject
/** 成员属性 */
@property (nonatomic, assign) objc_property_t property;
/** 成员属性的名字 */
@property (nonatomic, readonly) NSString *name;
/** 成员属性的类型 */
@property (nonatomic, readonly) MJPropertyType *type;
/** 成员属性来源于哪个类(可能是父类) */
@property (nonatomic, assign) Class srcClass;
/**** 同一个成员属性 - 父类和子类的行为可能不一致(originKey、propertyKeys、objectClassInArray) ****/
/** 设置最原始的key */
- (void)setOriginKey:(id)originKey forClass:(Class)c;
/** 对应着字典中的多级key(里面存放的数组,数组里面都是MJPropertyKey对象) */
- (NSArray *)propertyKeysForClass:(Class)c;
/** 模型数组中的模型类型 */
- (void)setObjectClassInArray:(Class)objectClass forClass:(Class)c;
- (Class)objectClassInArrayForClass:(Class)c;
/**** 同一个成员变量 - 父类和子类的行为可能不一致(key、keys、objectClassInArray) ****/
/**
* 设置object的成员变量值
*/
- (void)setValue:(id)value forObject:(id)object;
/**
* 得到object的成员属性值
*/
- (id)valueForObject:(id)object;
/**
* 初始化
*/
+ (instancetype)cachedPropertyWithProperty:(objc_property_t)property;
@end
//
// MJProperty.m
// MJExtensionExample
//
// Created by MJ Lee on 15/4/17.
// Copyright (c) 2015年 小码哥. All rights reserved.
//
#import "MJProperty.h"
#import "MJFoundation.h"
#import "MJExtensionConst.h"
#import <objc/message.h>
#include "TargetConditionals.h"
@interface MJProperty()
@property (strong, nonatomic) NSMutableDictionary *propertyKeysDict;
@property (strong, nonatomic) NSMutableDictionary *objectClassInArrayDict;
@property (strong, nonatomic) dispatch_semaphore_t propertyKeysLock;
@property (strong, nonatomic) dispatch_semaphore_t objectClassInArrayLock;
@end
@implementation MJProperty
#pragma mark - 初始化
- (instancetype)init
{
if (self = [super init]) {
_propertyKeysDict = [NSMutableDictionary dictionary];
_objectClassInArrayDict = [NSMutableDictionary dictionary];
_propertyKeysLock = dispatch_semaphore_create(1);
_objectClassInArrayLock = dispatch_semaphore_create(1);
}
return self;
}
#pragma mark - 缓存
+ (instancetype)cachedPropertyWithProperty:(objc_property_t)property
{
MJProperty *propertyObj = objc_getAssociatedObject(self, property);
if (propertyObj == nil) {
propertyObj = [[self alloc] init];
propertyObj.property = property;
objc_setAssociatedObject(self, property, propertyObj, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
return propertyObj;
}
#pragma mark - 公共方法
- (void)setProperty:(objc_property_t)property
{
_property = property;
MJExtensionAssertParamNotNil(property);
// 1.属性名
_name = @(property_getName(property));
// 2.成员类型
NSString *attrs = @(property_getAttributes(property));
NSUInteger dotLoc = [attrs rangeOfString:@","].location;
NSString *code = nil;
NSUInteger loc = 1;
if (dotLoc == NSNotFound) { // 没有,
code = [attrs substringFromIndex:loc];
} else {
code = [attrs substringWithRange:NSMakeRange(loc, dotLoc - loc)];
}
_type = [MJPropertyType cachedTypeWithCode:code];
}
/**
* 获得成员变量的值
*/
- (id)valueForObject:(id)object
{
if (self.type.KVCDisabled) return [NSNull null];
id value = [object valueForKey:self.name];
// 32位BOOL类型转换json后成Int类型
/** https://github.com/CoderMJLee/MJExtension/issues/545 */
// 32 bit device OR 32 bit Simulator
#if defined(__arm__) || (TARGET_OS_SIMULATOR && !__LP64__)
if (self.type.isBoolType) {
value = @([(NSNumber *)value boolValue]);
}
#endif
return value;
}
/**
* 设置成员变量的值
*/
- (void)setValue:(id)value forObject:(id)object
{
if (self.type.KVCDisabled || value == nil) return;
[object setValue:value forKey:self.name];
}
/**
* 通过字符串key创建对应的keys
*/
- (NSArray *)propertyKeysWithStringKey:(NSString *)stringKey
{
if (stringKey.length == 0) return nil;
NSMutableArray *propertyKeys = [NSMutableArray array];
// 如果有多级映射
NSArray *oldKeys = [stringKey componentsSeparatedByString:@"."];
for (NSString *oldKey in oldKeys) {
NSUInteger start = [oldKey rangeOfString:@"["].location;
if (start != NSNotFound) { // 有索引的key
NSString *prefixKey = [oldKey substringToIndex:start];
NSString *indexKey = prefixKey;
if (prefixKey.length) {
MJPropertyKey *propertyKey = [[MJPropertyKey alloc] init];
propertyKey.name = prefixKey;
[propertyKeys addObject:propertyKey];
indexKey = [oldKey stringByReplacingOccurrencesOfString:prefixKey withString:@""];
}
/** 解析索引 **/
// 元素
NSArray *cmps = [[indexKey stringByReplacingOccurrencesOfString:@"[" withString:@""] componentsSeparatedByString:@"]"];
for (NSInteger i = 0; i<cmps.count - 1; i++) {
MJPropertyKey *subPropertyKey = [[MJPropertyKey alloc] init];
subPropertyKey.type = MJPropertyKeyTypeArray;
subPropertyKey.name = cmps[i];
[propertyKeys addObject:subPropertyKey];
}
} else { // 没有索引的key
MJPropertyKey *propertyKey = [[MJPropertyKey alloc] init];
propertyKey.name = oldKey;
[propertyKeys addObject:propertyKey];
}
}
return propertyKeys;
}
/** 对应着字典中的key */
- (void)setOriginKey:(id)originKey forClass:(Class)c
{
if ([originKey isKindOfClass:[NSString class]]) { // 字符串类型的key
NSArray *propertyKeys = [self propertyKeysWithStringKey:originKey];
if (propertyKeys.count) {
[self setPorpertyKeys:@[propertyKeys] forClass:c];
}
} else if ([originKey isKindOfClass:[NSArray class]]) {
NSMutableArray *keyses = [NSMutableArray array];
for (NSString *stringKey in originKey) {
NSArray *propertyKeys = [self propertyKeysWithStringKey:stringKey];
if (propertyKeys.count) {
[keyses addObject:propertyKeys];
}
}
if (keyses.count) {
[self setPorpertyKeys:keyses forClass:c];
}
}
}
/** 对应着字典中的多级key */
- (void)setPorpertyKeys:(NSArray *)propertyKeys forClass:(Class)c
{
if (propertyKeys.count == 0) return;
NSString *key = NSStringFromClass(c);
if (!key) return;
MJ_LOCK(self.propertyKeysLock);
self.propertyKeysDict[key] = propertyKeys;
MJ_UNLOCK(self.propertyKeysLock);
}
- (NSArray *)propertyKeysForClass:(Class)c
{
NSString *key = NSStringFromClass(c);
if (!key) return nil;
MJ_LOCK(self.propertyKeysLock);
NSArray *propertyKeys = self.propertyKeysDict[key];
MJ_UNLOCK(self.propertyKeysLock);
return propertyKeys;
}
/** 模型数组中的模型类型 */
- (void)setObjectClassInArray:(Class)objectClass forClass:(Class)c
{
if (!objectClass) return;
NSString *key = NSStringFromClass(c);
if (!key) return;
MJ_LOCK(self.objectClassInArrayLock);
self.objectClassInArrayDict[key] = objectClass;
MJ_UNLOCK(self.objectClassInArrayLock);
}
- (Class)objectClassInArrayForClass:(Class)c
{
NSString *key = NSStringFromClass(c);
if (!key) return nil;
MJ_LOCK(self.objectClassInArrayLock);
Class objectClass = self.objectClassInArrayDict[key];
MJ_UNLOCK(self.objectClassInArrayLock);
return objectClass;
}
@end
//
// MJPropertyKey.h
// MJExtensionExample
//
// Created by MJ Lee on 15/8/11.
// Copyright (c) 2015年 小码哥. All rights reserved.
//
#import <Foundation/Foundation.h>
typedef enum {
MJPropertyKeyTypeDictionary = 0, // 字典的key
MJPropertyKeyTypeArray // 数组的key
} MJPropertyKeyType;
/**
* 属性的key
*/
@interface MJPropertyKey : NSObject
/** key的名字 */
@property (copy, nonatomic) NSString *name;
/** key的种类,可能是@"10",可能是@"age" */
@property (assign, nonatomic) MJPropertyKeyType type;
/**
* 根据当前的key,也就是name,从object(字典或者数组)中取值
*/
- (id)valueInObject:(id)object;
@end
//
// MJPropertyKey.m
// MJExtensionExample
//
// Created by MJ Lee on 15/8/11.
// Copyright (c) 2015年 小码哥. All rights reserved.
//
#import "MJPropertyKey.h"
@implementation MJPropertyKey
- (id)valueInObject:(id)object
{
if ([object isKindOfClass:[NSDictionary class]] && self.type == MJPropertyKeyTypeDictionary) {
return object[self.name];
} else if ([object isKindOfClass:[NSArray class]] && self.type == MJPropertyKeyTypeArray) {
NSArray *array = object;
NSUInteger index = self.name.intValue;
if (index < array.count) return array[index];
return nil;
}
return nil;
}
@end
//
// MJPropertyType.h
// MJExtension
//
// Created by mj on 14-1-15.
// Copyright (c) 2014年 小码哥. All rights reserved.
// 包装一种类型
#import <Foundation/Foundation.h>
/**
* 包装一种类型
*/
@interface MJPropertyType : NSObject
/** 类型标识符 */
@property (nonatomic, copy) NSString *code;
/** 是否为id类型 */
@property (nonatomic, readonly, getter=isIdType) BOOL idType;
/** 是否为基本数字类型:int、float等 */
@property (nonatomic, readonly, getter=isNumberType) BOOL numberType;
/** 是否为BOOL类型 */
@property (nonatomic, readonly, getter=isBoolType) BOOL boolType;
/** 对象类型(如果是基本数据类型,此值为nil) */
@property (nonatomic, readonly) Class typeClass;
/** 类型是否来自于Foundation框架,比如NSString、NSArray */
@property (nonatomic, readonly, getter = isFromFoundation) BOOL fromFoundation;
/** 类型是否不支持KVC */
@property (nonatomic, readonly, getter = isKVCDisabled) BOOL KVCDisabled;
/**
* 获得缓存的类型对象
*/
+ (instancetype)cachedTypeWithCode:(NSString *)code;
@end
\ No newline at end of file
//
// MJPropertyType.m
// MJExtension
//
// Created by mj on 14-1-15.
// Copyright (c) 2014年 小码哥. All rights reserved.
//
#import "MJPropertyType.h"
#import "MJExtension.h"
#import "MJFoundation.h"
#import "MJExtensionConst.h"
@implementation MJPropertyType
+ (instancetype)cachedTypeWithCode:(NSString *)code
{
MJExtensionAssertParamNotNil2(code, nil);
static NSMutableDictionary *types;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
types = [NSMutableDictionary dictionary];
});
MJPropertyType *type = types[code];
if (type == nil) {
type = [[self alloc] init];
type.code = code;
types[code] = type;
}
return type;
}
#pragma mark - 公共方法
- (void)setCode:(NSString *)code
{
_code = code;
MJExtensionAssertParamNotNil(code);
if ([code isEqualToString:MJPropertyTypeId]) {
_idType = YES;
} else if (code.length == 0) {
_KVCDisabled = YES;
} else if (code.length > 3 && [code hasPrefix:@"@\""]) {
// 去掉@"和",截取中间的类型名称
_code = [code substringWithRange:NSMakeRange(2, code.length - 3)];
_typeClass = NSClassFromString(_code);
_fromFoundation = [MJFoundation isClassFromFoundation:_typeClass];
_numberType = [_typeClass isSubclassOfClass:[NSNumber class]];
} else if ([code isEqualToString:MJPropertyTypeSEL] ||
[code isEqualToString:MJPropertyTypeIvar] ||
[code isEqualToString:MJPropertyTypeMethod]) {
_KVCDisabled = YES;
}
// 是否为数字类型
NSString *lowerCode = _code.lowercaseString;
NSArray *numberTypes = @[MJPropertyTypeInt, MJPropertyTypeShort, MJPropertyTypeBOOL1, MJPropertyTypeBOOL2, MJPropertyTypeFloat, MJPropertyTypeDouble, MJPropertyTypeLong, MJPropertyTypeLongLong, MJPropertyTypeChar];
if ([numberTypes containsObject:lowerCode]) {
_numberType = YES;
if ([lowerCode isEqualToString:MJPropertyTypeBOOL1]
|| [lowerCode isEqualToString:MJPropertyTypeBOOL2]) {
_boolType = YES;
}
}
}
@end
//
// NSObject+MJClass.h
// MJExtensionExample
//
// Created by MJ Lee on 15/8/11.
// Copyright (c) 2015年 小码哥. All rights reserved.
//
#import <Foundation/Foundation.h>
/**
* 遍历所有类的block(父类)
*/
typedef void (^MJClassesEnumeration)(Class c, BOOL *stop);
/** 这个数组中的属性名才会进行字典和模型的转换 */
typedef NSArray * (^MJAllowedPropertyNames)(void);
/** 这个数组中的属性名才会进行归档 */
typedef NSArray * (^MJAllowedCodingPropertyNames)(void);
/** 这个数组中的属性名将会被忽略:不进行字典和模型的转换 */
typedef NSArray * (^MJIgnoredPropertyNames)(void);
/** 这个数组中的属性名将会被忽略:不进行归档 */
typedef NSArray * (^MJIgnoredCodingPropertyNames)(void);
/**
* 类相关的扩展
*/
@interface NSObject (MJClass)
/**
* 遍历所有的类
*/
+ (void)mj_enumerateClasses:(MJClassesEnumeration)enumeration;
+ (void)mj_enumerateAllClasses:(MJClassesEnumeration)enumeration;
#pragma mark - 属性白名单配置
/**
* 这个数组中的属性名才会进行字典和模型的转换
*
* @param allowedPropertyNames 这个数组中的属性名才会进行字典和模型的转换
*/
+ (void)mj_setupAllowedPropertyNames:(MJAllowedPropertyNames)allowedPropertyNames;
/**
* 这个数组中的属性名才会进行字典和模型的转换
*/
+ (NSMutableArray *)mj_totalAllowedPropertyNames;
#pragma mark - 属性黑名单配置
/**
* 这个数组中的属性名将会被忽略:不进行字典和模型的转换
*
* @param ignoredPropertyNames 这个数组中的属性名将会被忽略:不进行字典和模型的转换
*/
+ (void)mj_setupIgnoredPropertyNames:(MJIgnoredPropertyNames)ignoredPropertyNames;
/**
* 这个数组中的属性名将会被忽略:不进行字典和模型的转换
*/
+ (NSMutableArray *)mj_totalIgnoredPropertyNames;
#pragma mark - 归档属性白名单配置
/**
* 这个数组中的属性名才会进行归档
*
* @param allowedCodingPropertyNames 这个数组中的属性名才会进行归档
*/
+ (void)mj_setupAllowedCodingPropertyNames:(MJAllowedCodingPropertyNames)allowedCodingPropertyNames;
/**
* 这个数组中的属性名才会进行字典和模型的转换
*/
+ (NSMutableArray *)mj_totalAllowedCodingPropertyNames;
#pragma mark - 归档属性黑名单配置
/**
* 这个数组中的属性名将会被忽略:不进行归档
*
* @param ignoredCodingPropertyNames 这个数组中的属性名将会被忽略:不进行归档
*/
+ (void)mj_setupIgnoredCodingPropertyNames:(MJIgnoredCodingPropertyNames)ignoredCodingPropertyNames;
/**
* 这个数组中的属性名将会被忽略:不进行归档
*/
+ (NSMutableArray *)mj_totalIgnoredCodingPropertyNames;
#pragma mark - 内部使用
+ (void)mj_setupBlockReturnValue:(id (^)(void))block key:(const char *)key;
@end
//
// NSObject+MJClass.m
// MJExtensionExample
//
// Created by MJ Lee on 15/8/11.
// Copyright (c) 2015年 小码哥. All rights reserved.
//
#import "NSObject+MJClass.h"
#import "NSObject+MJCoding.h"
#import "NSObject+MJKeyValue.h"
#import "MJFoundation.h"
#import <objc/runtime.h>
static const char MJAllowedPropertyNamesKey = '\0';
static const char MJIgnoredPropertyNamesKey = '\0';
static const char MJAllowedCodingPropertyNamesKey = '\0';
static const char MJIgnoredCodingPropertyNamesKey = '\0';
@implementation NSObject (MJClass)
+ (NSMutableDictionary *)mj_classDictForKey:(const void *)key
{
static NSMutableDictionary *allowedPropertyNamesDict;
static NSMutableDictionary *ignoredPropertyNamesDict;
static NSMutableDictionary *allowedCodingPropertyNamesDict;
static NSMutableDictionary *ignoredCodingPropertyNamesDict;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
allowedPropertyNamesDict = [NSMutableDictionary dictionary];
ignoredPropertyNamesDict = [NSMutableDictionary dictionary];
allowedCodingPropertyNamesDict = [NSMutableDictionary dictionary];
ignoredCodingPropertyNamesDict = [NSMutableDictionary dictionary];
});
if (key == &MJAllowedPropertyNamesKey) return allowedPropertyNamesDict;
if (key == &MJIgnoredPropertyNamesKey) return ignoredPropertyNamesDict;
if (key == &MJAllowedCodingPropertyNamesKey) return allowedCodingPropertyNamesDict;
if (key == &MJIgnoredCodingPropertyNamesKey) return ignoredCodingPropertyNamesDict;
return nil;
}
+ (void)mj_enumerateClasses:(MJClassesEnumeration)enumeration
{
// 1.没有block就直接返回
if (enumeration == nil) return;
// 2.停止遍历的标记
BOOL stop = NO;
// 3.当前正在遍历的类
Class c = self;
// 4.开始遍历每一个类
while (c && !stop) {
// 4.1.执行操作
enumeration(c, &stop);
// 4.2.获得父类
c = class_getSuperclass(c);
if ([MJFoundation isClassFromFoundation:c]) break;
}
}
+ (void)mj_enumerateAllClasses:(MJClassesEnumeration)enumeration
{
// 1.没有block就直接返回
if (enumeration == nil) return;
// 2.停止遍历的标记
BOOL stop = NO;
// 3.当前正在遍历的类
Class c = self;
// 4.开始遍历每一个类
while (c && !stop) {
// 4.1.执行操作
enumeration(c, &stop);
// 4.2.获得父类
c = class_getSuperclass(c);
}
}
#pragma mark - 属性黑名单配置
+ (void)mj_setupIgnoredPropertyNames:(MJIgnoredPropertyNames)ignoredPropertyNames
{
[self mj_setupBlockReturnValue:ignoredPropertyNames key:&MJIgnoredPropertyNamesKey];
}
+ (NSMutableArray *)mj_totalIgnoredPropertyNames
{
return [self mj_totalObjectsWithSelector:@selector(mj_ignoredPropertyNames) key:&MJIgnoredPropertyNamesKey];
}
#pragma mark - 归档属性黑名单配置
+ (void)mj_setupIgnoredCodingPropertyNames:(MJIgnoredCodingPropertyNames)ignoredCodingPropertyNames
{
[self mj_setupBlockReturnValue:ignoredCodingPropertyNames key:&MJIgnoredCodingPropertyNamesKey];
}
+ (NSMutableArray *)mj_totalIgnoredCodingPropertyNames
{
return [self mj_totalObjectsWithSelector:@selector(mj_ignoredCodingPropertyNames) key:&MJIgnoredCodingPropertyNamesKey];
}
#pragma mark - 属性白名单配置
+ (void)mj_setupAllowedPropertyNames:(MJAllowedPropertyNames)allowedPropertyNames;
{
[self mj_setupBlockReturnValue:allowedPropertyNames key:&MJAllowedPropertyNamesKey];
}
+ (NSMutableArray *)mj_totalAllowedPropertyNames
{
return [self mj_totalObjectsWithSelector:@selector(mj_allowedPropertyNames) key:&MJAllowedPropertyNamesKey];
}
#pragma mark - 归档属性白名单配置
+ (void)mj_setupAllowedCodingPropertyNames:(MJAllowedCodingPropertyNames)allowedCodingPropertyNames
{
[self mj_setupBlockReturnValue:allowedCodingPropertyNames key:&MJAllowedCodingPropertyNamesKey];
}
+ (NSMutableArray *)mj_totalAllowedCodingPropertyNames
{
return [self mj_totalObjectsWithSelector:@selector(mj_allowedCodingPropertyNames) key:&MJAllowedCodingPropertyNamesKey];
}
#pragma mark - block和方法处理:存储block的返回值
+ (void)mj_setupBlockReturnValue:(id (^)(void))block key:(const char *)key
{
if (block) {
objc_setAssociatedObject(self, key, block(), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
} else {
objc_setAssociatedObject(self, key, nil, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
// 清空数据
MJExtensionSemaphoreCreate
MJExtensionSemaphoreWait
[[self mj_classDictForKey:key] removeAllObjects];
MJExtensionSemaphoreSignal
}
+ (NSMutableArray *)mj_totalObjectsWithSelector:(SEL)selector key:(const char *)key
{
MJExtensionSemaphoreCreate
MJExtensionSemaphoreWait
NSMutableArray *array = [self mj_classDictForKey:key][NSStringFromClass(self)];
if (array == nil) {
// 创建、存储
[self mj_classDictForKey:key][NSStringFromClass(self)] = array = [NSMutableArray array];
if ([self respondsToSelector:selector]) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
NSArray *subArray = [self performSelector:selector];
#pragma clang diagnostic pop
if (subArray) {
[array addObjectsFromArray:subArray];
}
}
[self mj_enumerateAllClasses:^(__unsafe_unretained Class c, BOOL *stop) {
NSArray *subArray = objc_getAssociatedObject(c, key);
[array addObjectsFromArray:subArray];
}];
}
MJExtensionSemaphoreSignal
return array;
}
@end
//
// NSObject+MJCoding.h
// MJExtension
//
// Created by mj on 14-1-15.
// Copyright (c) 2014年 小码哥. All rights reserved.
//
#import <Foundation/Foundation.h>
#import "MJExtensionConst.h"
/**
* Codeing协议
*/
@protocol MJCoding <NSObject>
@optional
/**
* 这个数组中的属性名才会进行归档
*/
+ (NSArray *)mj_allowedCodingPropertyNames;
/**
* 这个数组中的属性名将会被忽略:不进行归档
*/
+ (NSArray *)mj_ignoredCodingPropertyNames;
@end
@interface NSObject (MJCoding) <MJCoding>
/**
* 解码(从文件中解析对象)
*/
- (void)mj_decode:(NSCoder *)decoder;
/**
* 编码(将对象写入文件中)
*/
- (void)mj_encode:(NSCoder *)encoder;
@end
/**
归档的实现
*/
#define MJCodingImplementation \
- (id)initWithCoder:(NSCoder *)decoder \
{ \
if (self = [super init]) { \
[self mj_decode:decoder]; \
} \
return self; \
} \
\
- (void)encodeWithCoder:(NSCoder *)encoder \
{ \
[self mj_encode:encoder]; \
}
#define MJExtensionCodingImplementation MJCodingImplementation
\ No newline at end of file
//
// NSObject+MJCoding.m
// MJExtension
//
// Created by mj on 14-1-15.
// Copyright (c) 2014年 小码哥. All rights reserved.
//
#import "NSObject+MJCoding.h"
#import "NSObject+MJClass.h"
#import "NSObject+MJProperty.h"
#import "MJProperty.h"
@implementation NSObject (MJCoding)
- (void)mj_encode:(NSCoder *)encoder
{
Class clazz = [self class];
NSArray *allowedCodingPropertyNames = [clazz mj_totalAllowedCodingPropertyNames];
NSArray *ignoredCodingPropertyNames = [clazz mj_totalIgnoredCodingPropertyNames];
[clazz mj_enumerateProperties:^(MJProperty *property, BOOL *stop) {
// 检测是否被忽略
if (allowedCodingPropertyNames.count && ![allowedCodingPropertyNames containsObject:property.name]) return;
if ([ignoredCodingPropertyNames containsObject:property.name]) return;
id value = [property valueForObject:self];
if (value == nil) return;
[encoder encodeObject:value forKey:property.name];
}];
}
- (void)mj_decode:(NSCoder *)decoder
{
Class clazz = [self class];
NSArray *allowedCodingPropertyNames = [clazz mj_totalAllowedCodingPropertyNames];
NSArray *ignoredCodingPropertyNames = [clazz mj_totalIgnoredCodingPropertyNames];
[clazz mj_enumerateProperties:^(MJProperty *property, BOOL *stop) {
// 检测是否被忽略
if (allowedCodingPropertyNames.count && ![allowedCodingPropertyNames containsObject:property.name]) return;
if ([ignoredCodingPropertyNames containsObject:property.name]) return;
id value = [decoder decodeObjectForKey:property.name];
if (value == nil) { // 兼容以前的MJExtension版本
value = [decoder decodeObjectForKey:[@"_" stringByAppendingString:property.name]];
}
if (value == nil) return;
[property setValue:value forObject:self];
}];
}
@end
//
// NSObject+MJKeyValue.h
// MJExtension
//
// Created by mj on 13-8-24.
// Copyright (c) 2013年 小码哥. All rights reserved.
//
#import <Foundation/Foundation.h>
#import "MJExtensionConst.h"
#import <CoreData/CoreData.h>
#import "MJProperty.h"
/**
* KeyValue协议
*/
@protocol MJKeyValue <NSObject>
@optional
/**
* 只有这个数组中的属性名才允许进行字典和模型的转换
*/
+ (NSArray *)mj_allowedPropertyNames;
/**
* 这个数组中的属性名将会被忽略:不进行字典和模型的转换
*/
+ (NSArray *)mj_ignoredPropertyNames;
/**
* 将属性名换为其他key去字典中取值
*
* @return 字典中的key是属性名,value是从字典中取值用的key
*/
+ (NSDictionary *)mj_replacedKeyFromPropertyName;
/**
* 将属性名换为其他key去字典中取值
*
* @return 从字典中取值用的key
*/
+ (id)mj_replacedKeyFromPropertyName121:(NSString *)propertyName;
/**
* 数组中需要转换的模型类
*
* @return 字典中的key是数组属性名,value是数组中存放模型的Class(Class类型或者NSString类型)
*/
+ (NSDictionary *)mj_objectClassInArray;
/** 特殊地区在字符串格式化数字时使用 */
+ (NSLocale *)mj_numberLocale;
/**
* 旧值换新值,用于过滤字典中的值
*
* @param oldValue 旧值
*
* @return 新值
*/
- (id)mj_newValueFromOldValue:(id)oldValue property:(MJProperty *)property;
/**
* 当字典转模型完毕时调用
*/
- (void)mj_keyValuesDidFinishConvertingToObject MJExtensionDeprecated("请使用`mj_didConvertToObjectWithKeyValues:`替代");
- (void)mj_keyValuesDidFinishConvertingToObject:(NSDictionary *)keyValues MJExtensionDeprecated("请使用`mj_didConvertToObjectWithKeyValues:`替代");
- (void)mj_didConvertToObjectWithKeyValues:(NSDictionary *)keyValues;
/**
* 当模型转字典完毕时调用
*/
- (void)mj_objectDidFinishConvertingToKeyValues MJExtensionDeprecated("请使用`mj_objectDidConvertToKeyValues:`替代");
- (void)mj_objectDidConvertToKeyValues:(NSDictionary *)keyValues;
@end
@interface NSObject (MJKeyValue) <MJKeyValue>
#pragma mark - 类方法
/**
* 字典转模型过程中遇到的错误
*/
+ (NSError *)mj_error;
/**
* 模型转字典时,字典的key是否参考replacedKeyFromPropertyName等方法(父类设置了,子类也会继承下来)
*/
+ (void)mj_referenceReplacedKeyWhenCreatingKeyValues:(BOOL)reference;
#pragma mark - 对象方法
/**
* 将字典的键值对转成模型属性
* @param keyValues 字典(可以是NSDictionary、NSData、NSString)
*/
- (instancetype)mj_setKeyValues:(id)keyValues;
/**
* 将字典的键值对转成模型属性
* @param keyValues 字典(可以是NSDictionary、NSData、NSString)
* @param context CoreData上下文
*/
- (instancetype)mj_setKeyValues:(id)keyValues context:(NSManagedObjectContext *)context;
/**
* 将模型转成字典
* @return 字典
*/
- (NSMutableDictionary *)mj_keyValues;
- (NSMutableDictionary *)mj_keyValuesWithKeys:(NSArray *)keys;
- (NSMutableDictionary *)mj_keyValuesWithIgnoredKeys:(NSArray *)ignoredKeys;
/**
* 通过模型数组来创建一个字典数组
* @param objectArray 模型数组
* @return 字典数组
*/
+ (NSMutableArray *)mj_keyValuesArrayWithObjectArray:(NSArray *)objectArray;
+ (NSMutableArray *)mj_keyValuesArrayWithObjectArray:(NSArray *)objectArray keys:(NSArray *)keys;
+ (NSMutableArray *)mj_keyValuesArrayWithObjectArray:(NSArray *)objectArray ignoredKeys:(NSArray *)ignoredKeys;
#pragma mark - 字典转模型
/**
* 通过字典来创建一个模型
* @param keyValues 字典(可以是NSDictionary、NSData、NSString)
* @return 新建的对象
*/
+ (instancetype)mj_objectWithKeyValues:(id)keyValues;
/**
* 通过字典来创建一个CoreData模型
* @param keyValues 字典(可以是NSDictionary、NSData、NSString)
* @param context CoreData上下文
* @return 新建的对象
*/
+ (instancetype)mj_objectWithKeyValues:(id)keyValues context:(NSManagedObjectContext *)context;
/**
* 通过plist来创建一个模型
* @param filename 文件名(仅限于mainBundle中的文件)
* @return 新建的对象
*/
+ (instancetype)mj_objectWithFilename:(NSString *)filename;
/**
* 通过plist来创建一个模型
* @param file 文件全路径
* @return 新建的对象
*/
+ (instancetype)mj_objectWithFile:(NSString *)file;
#pragma mark - 字典数组转模型数组
/**
* 通过字典数组来创建一个模型数组
* @param keyValuesArray 字典数组(可以是NSDictionary、NSData、NSString)
* @return 模型数组
*/
+ (NSMutableArray *)mj_objectArrayWithKeyValuesArray:(id)keyValuesArray;
/**
* 通过字典数组来创建一个模型数组
* @param keyValuesArray 字典数组(可以是NSDictionary、NSData、NSString)
* @param context CoreData上下文
* @return 模型数组
*/
+ (NSMutableArray *)mj_objectArrayWithKeyValuesArray:(id)keyValuesArray context:(NSManagedObjectContext *)context;
/**
* 通过plist来创建一个模型数组
* @param filename 文件名(仅限于mainBundle中的文件)
* @return 模型数组
*/
+ (NSMutableArray *)mj_objectArrayWithFilename:(NSString *)filename;
/**
* 通过plist来创建一个模型数组
* @param file 文件全路径
* @return 模型数组
*/
+ (NSMutableArray *)mj_objectArrayWithFile:(NSString *)file;
#pragma mark - 转换为JSON
/**
* 转换为JSON Data
*/
- (NSData *)mj_JSONData;
/**
* 转换为字典或者数组
*/
- (id)mj_JSONObject;
/**
* 转换为JSON 字符串
*/
- (NSString *)mj_JSONString;
@end
//
// NSObject+MJKeyValue.m
// MJExtension
//
// Created by mj on 13-8-24.
// Copyright (c) 2013年 小码哥. All rights reserved.
//
#import "NSObject+MJKeyValue.h"
#import "NSObject+MJProperty.h"
#import "NSString+MJExtension.h"
#import "MJProperty.h"
#import "MJPropertyType.h"
#import "MJExtensionConst.h"
#import "MJFoundation.h"
#import "NSString+MJExtension.h"
#import "NSObject+MJClass.h"
@implementation NSDecimalNumber(MJKeyValue)
- (id)standardValueWithTypeCode:(NSString *)typeCode {
// 由于这里涉及到编译器问题, 暂时保留 Long, 实际上在 64 位系统上, 这 2 个精度范围相同,
// 32 位略有不同, 其余都可使用 Double 进行强转不丢失精度
if ([typeCode isEqualToString:MJPropertyTypeLongLong]) {
return @(self.longLongValue);
} else if ([typeCode isEqualToString:MJPropertyTypeLongLong.uppercaseString]) {
return @(self.unsignedLongLongValue);
} else if ([typeCode isEqualToString:MJPropertyTypeLong]) {
return @(self.longValue);
} else if ([typeCode isEqualToString:MJPropertyTypeLong.uppercaseString]) {
return @(self.unsignedLongValue);
} else {
return @(self.doubleValue);
}
}
@end
@implementation NSObject (MJKeyValue)
#pragma mark - 错误
static const char MJErrorKey = '\0';
+ (NSError *)mj_error
{
return objc_getAssociatedObject(self, &MJErrorKey);
}
+ (void)setMj_error:(NSError *)error
{
objc_setAssociatedObject(self, &MJErrorKey, error, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
#pragma mark - 模型 -> 字典时的参考
/** 模型转字典时,字典的key是否参考replacedKeyFromPropertyName等方法(父类设置了,子类也会继承下来) */
static const char MJReferenceReplacedKeyWhenCreatingKeyValuesKey = '\0';
+ (void)mj_referenceReplacedKeyWhenCreatingKeyValues:(BOOL)reference
{
objc_setAssociatedObject(self, &MJReferenceReplacedKeyWhenCreatingKeyValuesKey, @(reference), OBJC_ASSOCIATION_ASSIGN);
}
+ (BOOL)mj_isReferenceReplacedKeyWhenCreatingKeyValues
{
__block id value = objc_getAssociatedObject(self, &MJReferenceReplacedKeyWhenCreatingKeyValuesKey);
if (!value) {
[self mj_enumerateAllClasses:^(__unsafe_unretained Class c, BOOL *stop) {
value = objc_getAssociatedObject(c, &MJReferenceReplacedKeyWhenCreatingKeyValuesKey);
if (value) *stop = YES;
}];
}
return [value boolValue];
}
#pragma mark - --常用的对象--
+ (void)load
{
// 默认设置
[self mj_referenceReplacedKeyWhenCreatingKeyValues:YES];
}
#pragma mark - --公共方法--
#pragma mark - 字典 -> 模型
- (instancetype)mj_setKeyValues:(id)keyValues
{
return [self mj_setKeyValues:keyValues context:nil];
}
/**
核心代码:
*/
- (instancetype)mj_setKeyValues:(id)keyValues context:(NSManagedObjectContext *)context
{
// 获得JSON对象
keyValues = [keyValues mj_JSONObject];
MJExtensionAssertError([keyValues isKindOfClass:[NSDictionary class]], self, [self class], @"keyValues参数不是一个字典");
Class clazz = [self class];
NSArray *allowedPropertyNames = [clazz mj_totalAllowedPropertyNames];
NSArray *ignoredPropertyNames = [clazz mj_totalIgnoredPropertyNames];
NSLocale *numberLocale = nil;
if ([self.class respondsToSelector:@selector(mj_numberLocale)]) {
numberLocale = self.class.mj_numberLocale;
}
//通过封装的方法回调一个通过运行时编写的,用于返回属性列表的方法。
[clazz mj_enumerateProperties:^(MJProperty *property, BOOL *stop) {
@try {
// 0.检测是否被忽略
if (allowedPropertyNames.count && ![allowedPropertyNames containsObject:property.name]) return;
if ([ignoredPropertyNames containsObject:property.name]) return;
// 1.取出属性值
id value;
NSArray *propertyKeyses = [property propertyKeysForClass:clazz];
for (NSArray *propertyKeys in propertyKeyses) {
value = keyValues;
for (MJPropertyKey *propertyKey in propertyKeys) {
value = [propertyKey valueInObject:value];
}
if (value) break;
}
// 值的过滤
id newValue = [clazz mj_getNewValueFromObject:self oldValue:value property:property];
if (newValue != value) { // 有过滤后的新值
[property setValue:newValue forObject:self];
return;
}
// 如果没有值,就直接返回
if (!value || value == [NSNull null]) return;
// 2.复杂处理
MJPropertyType *type = property.type;
Class propertyClass = type.typeClass;
Class objectClass = [property objectClassInArrayForClass:[self class]];
// 不可变 -> 可变处理
if (propertyClass == [NSMutableArray class] && [value isKindOfClass:[NSArray class]]) {
value = [NSMutableArray arrayWithArray:value];
} else if (propertyClass == [NSMutableDictionary class] && [value isKindOfClass:[NSDictionary class]]) {
value = [NSMutableDictionary dictionaryWithDictionary:value];
} else if (propertyClass == [NSMutableString class] && [value isKindOfClass:[NSString class]]) {
value = [NSMutableString stringWithString:value];
} else if (propertyClass == [NSMutableData class] && [value isKindOfClass:[NSData class]]) {
value = [NSMutableData dataWithData:value];
}
if (!type.isFromFoundation && propertyClass) { // 模型属性
value = [propertyClass mj_objectWithKeyValues:value context:context];
} else if (objectClass) {
if (objectClass == [NSURL class] && [value isKindOfClass:[NSArray class]]) {
// string array -> url array
NSMutableArray *urlArray = [NSMutableArray array];
for (NSString *string in value) {
if (![string isKindOfClass:[NSString class]]) continue;
[urlArray addObject:string.mj_url];
}
value = urlArray;
} else { // 字典数组-->模型数组
value = [objectClass mj_objectArrayWithKeyValuesArray:value context:context];
}
} else if (propertyClass == [NSString class]) {
if ([value isKindOfClass:[NSNumber class]]) {
// NSNumber -> NSString
value = [value description];
} else if ([value isKindOfClass:[NSURL class]]) {
// NSURL -> NSString
value = [value absoluteString];
}
} else if ([value isKindOfClass:[NSString class]]) {
if (propertyClass == [NSURL class]) {
// NSString -> NSURL
// 字符串转码
value = [value mj_url];
} else if (type.isNumberType) {
NSString *oldValue = value;
// NSString -> NSDecimalNumber, 使用 DecimalNumber 来转换数字, 避免丢失精度以及溢出
NSDecimalNumber *decimalValue = [NSDecimalNumber decimalNumberWithString:oldValue
locale:numberLocale];
// 检查特殊情况
if (decimalValue == NSDecimalNumber.notANumber) {
value = @(0);
}else if (propertyClass != [NSDecimalNumber class]) {
value = [decimalValue standardValueWithTypeCode:type.code];
} else {
value = decimalValue;
}
// 如果是BOOL
if (type.isBoolType) {
// 字符串转BOOL(字符串没有charValue方法)
// 系统会调用字符串的charValue转为BOOL类型
NSString *lower = [oldValue lowercaseString];
if ([lower isEqualToString:@"yes"] || [lower isEqualToString:@"true"]) {
value = @YES;
} else if ([lower isEqualToString:@"no"] || [lower isEqualToString:@"false"]) {
value = @NO;
}
}
}
} else if ([value isKindOfClass:[NSNumber class]] && propertyClass == [NSDecimalNumber class]){
// 过滤 NSDecimalNumber类型
if (![value isKindOfClass:[NSDecimalNumber class]]) {
value = [NSDecimalNumber decimalNumberWithDecimal:[((NSNumber *)value) decimalValue]];
}
}
// 经过转换后, 最终检查 value 与 property 是否匹配
if (propertyClass && ![value isKindOfClass:propertyClass]) {
value = nil;
}
// 3.赋值
[property setValue:value forObject:self];
} @catch (NSException *exception) {
MJExtensionBuildError([self class], exception.reason);
MJExtensionLog(@"%@", exception);
}
}];
// 转换完毕
if ([self respondsToSelector:@selector(mj_didConvertToObjectWithKeyValues:)]) {
[self mj_didConvertToObjectWithKeyValues:keyValues];
}
#pragma clang diagnostic push
#pragma clang diagnostic ignored"-Wdeprecated-declarations"
if ([self respondsToSelector:@selector(mj_keyValuesDidFinishConvertingToObject)]) {
[self mj_keyValuesDidFinishConvertingToObject];
}
if ([self respondsToSelector:@selector(mj_keyValuesDidFinishConvertingToObject:)]) {
[self mj_keyValuesDidFinishConvertingToObject:keyValues];
}
#pragma clang diagnostic pop
return self;
}
+ (instancetype)mj_objectWithKeyValues:(id)keyValues
{
return [self mj_objectWithKeyValues:keyValues context:nil];
}
+ (instancetype)mj_objectWithKeyValues:(id)keyValues context:(NSManagedObjectContext *)context
{
// 获得JSON对象
keyValues = [keyValues mj_JSONObject];
MJExtensionAssertError([keyValues isKindOfClass:[NSDictionary class]], nil, [self class], @"keyValues参数不是一个字典");
if ([self isSubclassOfClass:[NSManagedObject class]] && context) {
NSString *entityName = [NSStringFromClass(self) componentsSeparatedByString:@"."].lastObject;
return [[NSEntityDescription insertNewObjectForEntityForName:entityName inManagedObjectContext:context] mj_setKeyValues:keyValues context:context];
}
return [[[self alloc] init] mj_setKeyValues:keyValues];
}
+ (instancetype)mj_objectWithFilename:(NSString *)filename
{
MJExtensionAssertError(filename != nil, nil, [self class], @"filename参数为nil");
return [self mj_objectWithFile:[[NSBundle mainBundle] pathForResource:filename ofType:nil]];
}
+ (instancetype)mj_objectWithFile:(NSString *)file
{
MJExtensionAssertError(file != nil, nil, [self class], @"file参数为nil");
return [self mj_objectWithKeyValues:[NSDictionary dictionaryWithContentsOfFile:file]];
}
#pragma mark - 字典数组 -> 模型数组
+ (NSMutableArray *)mj_objectArrayWithKeyValuesArray:(NSArray *)keyValuesArray
{
return [self mj_objectArrayWithKeyValuesArray:keyValuesArray context:nil];
}
+ (NSMutableArray *)mj_objectArrayWithKeyValuesArray:(id)keyValuesArray context:(NSManagedObjectContext *)context
{
// 如果是JSON字符串
keyValuesArray = [keyValuesArray mj_JSONObject];
// 1.判断真实性
MJExtensionAssertError([keyValuesArray isKindOfClass:[NSArray class]], nil, [self class], @"keyValuesArray参数不是一个数组");
// 如果数组里面放的是NSString、NSNumber等数据
if ([MJFoundation isClassFromFoundation:self]) return [NSMutableArray arrayWithArray:keyValuesArray];
// 2.创建数组
NSMutableArray *modelArray = [NSMutableArray array];
// 3.遍历
for (NSDictionary *keyValues in keyValuesArray) {
if ([keyValues isKindOfClass:[NSArray class]]){
[modelArray addObject:[self mj_objectArrayWithKeyValuesArray:keyValues context:context]];
} else {
id model = [self mj_objectWithKeyValues:keyValues context:context];
if (model) [modelArray addObject:model];
}
}
return modelArray;
}
+ (NSMutableArray *)mj_objectArrayWithFilename:(NSString *)filename
{
MJExtensionAssertError(filename != nil, nil, [self class], @"filename参数为nil");
return [self mj_objectArrayWithFile:[[NSBundle mainBundle] pathForResource:filename ofType:nil]];
}
+ (NSMutableArray *)mj_objectArrayWithFile:(NSString *)file
{
MJExtensionAssertError(file != nil, nil, [self class], @"file参数为nil");
return [self mj_objectArrayWithKeyValuesArray:[NSArray arrayWithContentsOfFile:file]];
}
#pragma mark - 模型 -> 字典
- (NSMutableDictionary *)mj_keyValues
{
return [self mj_keyValuesWithKeys:nil ignoredKeys:nil];
}
- (NSMutableDictionary *)mj_keyValuesWithKeys:(NSArray *)keys
{
return [self mj_keyValuesWithKeys:keys ignoredKeys:nil];
}
- (NSMutableDictionary *)mj_keyValuesWithIgnoredKeys:(NSArray *)ignoredKeys
{
return [self mj_keyValuesWithKeys:nil ignoredKeys:ignoredKeys];
}
- (NSMutableDictionary *)mj_keyValuesWithKeys:(NSArray *)keys ignoredKeys:(NSArray *)ignoredKeys
{
// 如果自己不是模型类, 那就返回自己
// 模型类过滤掉 NSNull
// 唯一一个不返回自己的
if ([self isMemberOfClass:NSNull.class]) { return nil; }
// 这里虽然返回了自己, 但是其实是有报错信息的.
// TODO: 报错机制不好, 需要重做
MJExtensionAssertError(![MJFoundation isClassFromFoundation:[self class]], (NSMutableDictionary *)self, [self class], @"不是自定义的模型类")
id keyValues = [NSMutableDictionary dictionary];
Class clazz = [self class];
NSArray *allowedPropertyNames = [clazz mj_totalAllowedPropertyNames];
NSArray *ignoredPropertyNames = [clazz mj_totalIgnoredPropertyNames];
[clazz mj_enumerateProperties:^(MJProperty *property, BOOL *stop) {
@try {
// 0.检测是否被忽略
if (allowedPropertyNames.count && ![allowedPropertyNames containsObject:property.name]) return;
if ([ignoredPropertyNames containsObject:property.name]) return;
if (keys.count && ![keys containsObject:property.name]) return;
if ([ignoredKeys containsObject:property.name]) return;
// 1.取出属性值
id value = [property valueForObject:self];
if (!value) return;
// 2.如果是模型属性
MJPropertyType *type = property.type;
Class propertyClass = type.typeClass;
if (!type.isFromFoundation && propertyClass) {
value = [value mj_keyValues];
} else if ([value isKindOfClass:[NSArray class]]) {
// 3.处理数组里面有模型的情况
value = [NSObject mj_keyValuesArrayWithObjectArray:value];
} else if (propertyClass == [NSURL class]) {
value = [value absoluteString];
}
// 4.赋值
if ([clazz mj_isReferenceReplacedKeyWhenCreatingKeyValues]) {
NSArray *propertyKeys = [[property propertyKeysForClass:clazz] firstObject];
NSUInteger keyCount = propertyKeys.count;
// 创建字典
__block id innerContainer = keyValues;
[propertyKeys enumerateObjectsUsingBlock:^(MJPropertyKey *propertyKey, NSUInteger idx, BOOL *stop) {
// 下一个属性
MJPropertyKey *nextPropertyKey = nil;
if (idx != keyCount - 1) {
nextPropertyKey = propertyKeys[idx + 1];
}
if (nextPropertyKey) { // 不是最后一个key
// 当前propertyKey对应的字典或者数组
id tempInnerContainer = [propertyKey valueInObject:innerContainer];
if (tempInnerContainer == nil || [tempInnerContainer isKindOfClass:[NSNull class]]) {
if (nextPropertyKey.type == MJPropertyKeyTypeDictionary) {
tempInnerContainer = [NSMutableDictionary dictionary];
} else {
tempInnerContainer = [NSMutableArray array];
}
if (propertyKey.type == MJPropertyKeyTypeDictionary) {
innerContainer[propertyKey.name] = tempInnerContainer;
} else {
innerContainer[propertyKey.name.intValue] = tempInnerContainer;
}
}
if ([tempInnerContainer isKindOfClass:[NSMutableArray class]]) {
NSMutableArray *tempInnerContainerArray = tempInnerContainer;
int index = nextPropertyKey.name.intValue;
while (tempInnerContainerArray.count < index + 1) {
[tempInnerContainerArray addObject:[NSNull null]];
}
}
innerContainer = tempInnerContainer;
} else { // 最后一个key
if (propertyKey.type == MJPropertyKeyTypeDictionary) {
innerContainer[propertyKey.name] = value;
} else {
innerContainer[propertyKey.name.intValue] = value;
}
}
}];
} else {
keyValues[property.name] = value;
}
} @catch (NSException *exception) {
MJExtensionBuildError([self class], exception.reason);
MJExtensionLog(@"%@", exception);
}
}];
// 转换完毕
if ([self respondsToSelector:@selector(mj_objectDidConvertToKeyValues:)]) {
[self mj_objectDidConvertToKeyValues:keyValues];
}
#pragma clang diagnostic push
#pragma clang diagnostic ignored"-Wdeprecated-declarations"
if ([self respondsToSelector:@selector(mj_objectDidFinishConvertingToKeyValues)]) {
[self mj_objectDidFinishConvertingToKeyValues];
}
#pragma clang diagnostic pop
return keyValues;
}
#pragma mark - 模型数组 -> 字典数组
+ (NSMutableArray *)mj_keyValuesArrayWithObjectArray:(NSArray *)objectArray
{
return [self mj_keyValuesArrayWithObjectArray:objectArray keys:nil ignoredKeys:nil];
}
+ (NSMutableArray *)mj_keyValuesArrayWithObjectArray:(NSArray *)objectArray keys:(NSArray *)keys
{
return [self mj_keyValuesArrayWithObjectArray:objectArray keys:keys ignoredKeys:nil];
}
+ (NSMutableArray *)mj_keyValuesArrayWithObjectArray:(NSArray *)objectArray ignoredKeys:(NSArray *)ignoredKeys
{
return [self mj_keyValuesArrayWithObjectArray:objectArray keys:nil ignoredKeys:ignoredKeys];
}
+ (NSMutableArray *)mj_keyValuesArrayWithObjectArray:(NSArray *)objectArray keys:(NSArray *)keys ignoredKeys:(NSArray *)ignoredKeys
{
// 0.判断真实性
MJExtensionAssertError([objectArray isKindOfClass:[NSArray class]], nil, [self class], @"objectArray参数不是一个数组");
// 1.创建数组
NSMutableArray *keyValuesArray = [NSMutableArray array];
for (id object in objectArray) {
if (keys) {
id convertedObj = [object mj_keyValuesWithKeys:keys];
if (!convertedObj) { continue; }
[keyValuesArray addObject:convertedObj];
} else {
id convertedObj = [object mj_keyValuesWithIgnoredKeys:ignoredKeys];
if (!convertedObj) { continue; }
[keyValuesArray addObject:convertedObj];
}
}
return keyValuesArray;
}
#pragma mark - 转换为JSON
- (NSData *)mj_JSONData
{
if ([self isKindOfClass:[NSString class]]) {
return [((NSString *)self) dataUsingEncoding:NSUTF8StringEncoding];
} else if ([self isKindOfClass:[NSData class]]) {
return (NSData *)self;
}
return [NSJSONSerialization dataWithJSONObject:[self mj_JSONObject] options:kNilOptions error:nil];
}
- (id)mj_JSONObject
{
if ([self isKindOfClass:[NSString class]]) {
return [NSJSONSerialization JSONObjectWithData:[((NSString *)self) dataUsingEncoding:NSUTF8StringEncoding] options:kNilOptions error:nil];
} else if ([self isKindOfClass:[NSData class]]) {
return [NSJSONSerialization JSONObjectWithData:(NSData *)self options:kNilOptions error:nil];
}
return self.mj_keyValues;
}
- (NSString *)mj_JSONString
{
if ([self isKindOfClass:[NSString class]]) {
return (NSString *)self;
} else if ([self isKindOfClass:[NSData class]]) {
return [[NSString alloc] initWithData:(NSData *)self encoding:NSUTF8StringEncoding];
}
return [[NSString alloc] initWithData:[self mj_JSONData] encoding:NSUTF8StringEncoding];
}
@end
//
// NSObject+MJProperty.h
// MJExtensionExample
//
// Created by MJ Lee on 15/4/17.
// Copyright (c) 2015年 小码哥. All rights reserved.
//
#import <Foundation/Foundation.h>
#import "MJExtensionConst.h"
@class MJProperty;
/**
* 遍历成员变量用的block
*
* @param property 成员的包装对象
* @param stop YES代表停止遍历,NO代表继续遍历
*/
typedef void (^MJPropertiesEnumeration)(MJProperty *property, BOOL *stop);
/** 将属性名换为其他key去字典中取值 */
typedef NSDictionary * (^MJReplacedKeyFromPropertyName)(void);
typedef id (^MJReplacedKeyFromPropertyName121)(NSString *propertyName);
/** 数组中需要转换的模型类 */
typedef NSDictionary * (^MJObjectClassInArray)(void);
/** 用于过滤字典中的值 */
typedef id (^MJNewValueFromOldValue)(id object, id oldValue, MJProperty *property);
/**
* 成员属性相关的扩展
*/
@interface NSObject (MJProperty)
#pragma mark - 遍历
/**
* 遍历所有的成员
*/
+ (void)mj_enumerateProperties:(MJPropertiesEnumeration)enumeration;
#pragma mark - 新值配置
/**
* 用于过滤字典中的值
*
* @param newValueFormOldValue 用于过滤字典中的值
*/
+ (void)mj_setupNewValueFromOldValue:(MJNewValueFromOldValue)newValueFormOldValue;
+ (id)mj_getNewValueFromObject:(__unsafe_unretained id)object oldValue:(__unsafe_unretained id)oldValue property:(__unsafe_unretained MJProperty *)property;
#pragma mark - key配置
/**
* 将属性名换为其他key去字典中取值
*
* @param replacedKeyFromPropertyName 将属性名换为其他key去字典中取值
*/
+ (void)mj_setupReplacedKeyFromPropertyName:(MJReplacedKeyFromPropertyName)replacedKeyFromPropertyName;
/**
* 将属性名换为其他key去字典中取值
*
* @param replacedKeyFromPropertyName121 将属性名换为其他key去字典中取值
*/
+ (void)mj_setupReplacedKeyFromPropertyName121:(MJReplacedKeyFromPropertyName121)replacedKeyFromPropertyName121;
#pragma mark - array model class配置
/**
* 数组中需要转换的模型类
*
* @param objectClassInArray 数组中需要转换的模型类
*/
+ (void)mj_setupObjectClassInArray:(MJObjectClassInArray)objectClassInArray;
@end
//
// NSObject+MJProperty.m
// MJExtensionExample
//
// Created by MJ Lee on 15/4/17.
// Copyright (c) 2015年 小码哥. All rights reserved.
//
#import "NSObject+MJProperty.h"
#import "NSObject+MJKeyValue.h"
#import "NSObject+MJCoding.h"
#import "NSObject+MJClass.h"
#import "MJProperty.h"
#import "MJFoundation.h"
#import <objc/runtime.h>
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wundeclared-selector"
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
static const char MJReplacedKeyFromPropertyNameKey = '\0';
static const char MJReplacedKeyFromPropertyName121Key = '\0';
static const char MJNewValueFromOldValueKey = '\0';
static const char MJObjectClassInArrayKey = '\0';
static const char MJCachedPropertiesKey = '\0';
@implementation NSObject (Property)
+ (NSMutableDictionary *)mj_propertyDictForKey:(const void *)key
{
static NSMutableDictionary *replacedKeyFromPropertyNameDict;
static NSMutableDictionary *replacedKeyFromPropertyName121Dict;
static NSMutableDictionary *newValueFromOldValueDict;
static NSMutableDictionary *objectClassInArrayDict;
static NSMutableDictionary *cachedPropertiesDict;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
replacedKeyFromPropertyNameDict = [NSMutableDictionary dictionary];
replacedKeyFromPropertyName121Dict = [NSMutableDictionary dictionary];
newValueFromOldValueDict = [NSMutableDictionary dictionary];
objectClassInArrayDict = [NSMutableDictionary dictionary];
cachedPropertiesDict = [NSMutableDictionary dictionary];
});
if (key == &MJReplacedKeyFromPropertyNameKey) return replacedKeyFromPropertyNameDict;
if (key == &MJReplacedKeyFromPropertyName121Key) return replacedKeyFromPropertyName121Dict;
if (key == &MJNewValueFromOldValueKey) return newValueFromOldValueDict;
if (key == &MJObjectClassInArrayKey) return objectClassInArrayDict;
if (key == &MJCachedPropertiesKey) return cachedPropertiesDict;
return nil;
}
#pragma mark - --私有方法--
+ (id)mj_propertyKey:(NSString *)propertyName
{
MJExtensionAssertParamNotNil2(propertyName, nil);
__block id key = nil;
// 查看有没有需要替换的key
if ([self respondsToSelector:@selector(mj_replacedKeyFromPropertyName121:)]) {
key = [self mj_replacedKeyFromPropertyName121:propertyName];
}
// 调用block
if (!key) {
[self mj_enumerateAllClasses:^(__unsafe_unretained Class c, BOOL *stop) {
MJReplacedKeyFromPropertyName121 block = objc_getAssociatedObject(c, &MJReplacedKeyFromPropertyName121Key);
if (block) {
key = block(propertyName);
}
if (key) *stop = YES;
}];
}
// 查看有没有需要替换的key
if ((!key || [key isEqual:propertyName]) && [self respondsToSelector:@selector(mj_replacedKeyFromPropertyName)]) {
key = [self mj_replacedKeyFromPropertyName][propertyName];
}
if (!key || [key isEqual:propertyName]) {
[self mj_enumerateAllClasses:^(__unsafe_unretained Class c, BOOL *stop) {
NSDictionary *dict = objc_getAssociatedObject(c, &MJReplacedKeyFromPropertyNameKey);
if (dict) {
key = dict[propertyName];
}
if (key && ![key isEqual:propertyName]) *stop = YES;
}];
}
// 2.用属性名作为key
if (!key) key = propertyName;
return key;
}
+ (Class)mj_propertyObjectClassInArray:(NSString *)propertyName
{
__block id clazz = nil;
if ([self respondsToSelector:@selector(mj_objectClassInArray)]) {
clazz = [self mj_objectClassInArray][propertyName];
}
if (!clazz) {
[self mj_enumerateAllClasses:^(__unsafe_unretained Class c, BOOL *stop) {
NSDictionary *dict = objc_getAssociatedObject(c, &MJObjectClassInArrayKey);
if (dict) {
clazz = dict[propertyName];
}
if (clazz) *stop = YES;
}];
}
// 如果是NSString类型
if ([clazz isKindOfClass:[NSString class]]) {
clazz = NSClassFromString(clazz);
}
return clazz;
}
#pragma mark - --公共方法--
+ (void)mj_enumerateProperties:(MJPropertiesEnumeration)enumeration
{
// 获得成员变量
MJExtensionSemaphoreCreate
MJExtensionSemaphoreWait
NSArray *cachedProperties = [self mj_properties];
MJExtensionSemaphoreSignal
// 遍历成员变量
BOOL stop = NO;
for (MJProperty *property in cachedProperties) {
enumeration(property, &stop);
if (stop) break;
}
}
#pragma mark - 公共方法
+ (NSMutableArray *)mj_properties
{
NSMutableArray *cachedProperties = [self mj_propertyDictForKey:&MJCachedPropertiesKey][NSStringFromClass(self)];
if (cachedProperties == nil) {
if (cachedProperties == nil) {
cachedProperties = [NSMutableArray array];
[self mj_enumerateClasses:^(__unsafe_unretained Class c, BOOL *stop) {
// 1.获得所有的成员变量
unsigned int outCount = 0;
objc_property_t *properties = class_copyPropertyList(c, &outCount);
// 2.遍历每一个成员变量
for (unsigned int i = 0; i<outCount; i++) {
MJProperty *property = [MJProperty cachedPropertyWithProperty:properties[i]];
// 过滤掉Foundation框架类里面的属性
if ([MJFoundation isClassFromFoundation:property.srcClass]) continue;
// 过滤掉`hash`, `superclass`, `description`, `debugDescription`
if ([MJFoundation isFromNSObjectProtocolProperty:property.name]) continue;
property.srcClass = c;
[property setOriginKey:[self mj_propertyKey:property.name] forClass:self];
[property setObjectClassInArray:[self mj_propertyObjectClassInArray:property.name] forClass:self];
[cachedProperties addObject:property];
}
// 3.释放内存
free(properties);
}];
[self mj_propertyDictForKey:&MJCachedPropertiesKey][NSStringFromClass(self)] = cachedProperties;
}
}
return cachedProperties;
}
#pragma mark - 新值配置
+ (void)mj_setupNewValueFromOldValue:(MJNewValueFromOldValue)newValueFormOldValue
{
objc_setAssociatedObject(self, &MJNewValueFromOldValueKey, newValueFormOldValue, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
+ (id)mj_getNewValueFromObject:(__unsafe_unretained id)object oldValue:(__unsafe_unretained id)oldValue property:(MJProperty *__unsafe_unretained)property{
// 如果有实现方法
if ([object respondsToSelector:@selector(mj_newValueFromOldValue:property:)]) {
return [object mj_newValueFromOldValue:oldValue property:property];
}
// 查看静态设置
__block id newValue = oldValue;
[self mj_enumerateAllClasses:^(__unsafe_unretained Class c, BOOL *stop) {
MJNewValueFromOldValue block = objc_getAssociatedObject(c, &MJNewValueFromOldValueKey);
if (block) {
newValue = block(object, oldValue, property);
*stop = YES;
}
}];
return newValue;
}
#pragma mark - array model class配置
+ (void)mj_setupObjectClassInArray:(MJObjectClassInArray)objectClassInArray
{
[self mj_setupBlockReturnValue:objectClassInArray key:&MJObjectClassInArrayKey];
MJExtensionSemaphoreCreate
MJExtensionSemaphoreWait
[[self mj_propertyDictForKey:&MJCachedPropertiesKey] removeAllObjects];
MJExtensionSemaphoreSignal
}
#pragma mark - key配置
+ (void)mj_setupReplacedKeyFromPropertyName:(MJReplacedKeyFromPropertyName)replacedKeyFromPropertyName
{
[self mj_setupBlockReturnValue:replacedKeyFromPropertyName key:&MJReplacedKeyFromPropertyNameKey];
MJExtensionSemaphoreCreate
MJExtensionSemaphoreWait
[[self mj_propertyDictForKey:&MJCachedPropertiesKey] removeAllObjects];
MJExtensionSemaphoreSignal
}
+ (void)mj_setupReplacedKeyFromPropertyName121:(MJReplacedKeyFromPropertyName121)replacedKeyFromPropertyName121
{
objc_setAssociatedObject(self, &MJReplacedKeyFromPropertyName121Key, replacedKeyFromPropertyName121, OBJC_ASSOCIATION_COPY_NONATOMIC);
MJExtensionSemaphoreCreate
MJExtensionSemaphoreWait
[[self mj_propertyDictForKey:&MJCachedPropertiesKey] removeAllObjects];
MJExtensionSemaphoreSignal
}
@end
#pragma clang diagnostic pop
//
// NSString+MJExtension.h
// MJExtensionExample
//
// Created by MJ Lee on 15/6/7.
// Copyright (c) 2015年 小码哥. All rights reserved.
//
#import <Foundation/Foundation.h>
#import "MJExtensionConst.h"
@interface NSString (MJExtension)
/**
* 驼峰转下划线(loveYou -> love_you)
*/
- (NSString *)mj_underlineFromCamel;
/**
* 下划线转驼峰(love_you -> loveYou)
*/
- (NSString *)mj_camelFromUnderline;
/**
* 首字母变大写
*/
- (NSString *)mj_firstCharUpper;
/**
* 首字母变小写
*/
- (NSString *)mj_firstCharLower;
- (BOOL)mj_isPureInt;
- (NSURL *)mj_url;
@end
//
// NSString+MJExtension.m
// MJExtensionExample
//
// Created by MJ Lee on 15/6/7.
// Copyright (c) 2015年 小码哥. All rights reserved.
//
#import "NSString+MJExtension.h"
@implementation NSString (MJExtension)
- (NSString *)mj_underlineFromCamel
{
if (self.length == 0) return self;
NSMutableString *string = [NSMutableString string];
for (NSUInteger i = 0; i<self.length; i++) {
unichar c = [self characterAtIndex:i];
NSString *cString = [NSString stringWithFormat:@"%c", c];
NSString *cStringLower = [cString lowercaseString];
if ([cString isEqualToString:cStringLower]) {
[string appendString:cStringLower];
} else {
[string appendString:@"_"];
[string appendString:cStringLower];
}
}
return string;
}
- (NSString *)mj_camelFromUnderline
{
if (self.length == 0) return self;
NSMutableString *string = [NSMutableString string];
NSArray *cmps = [self componentsSeparatedByString:@"_"];
for (NSUInteger i = 0; i<cmps.count; i++) {
NSString *cmp = cmps[i];
if (i && cmp.length) {
[string appendString:[NSString stringWithFormat:@"%c", [cmp characterAtIndex:0]].uppercaseString];
if (cmp.length >= 2) [string appendString:[cmp substringFromIndex:1]];
} else {
[string appendString:cmp];
}
}
return string;
}
- (NSString *)mj_firstCharLower
{
if (self.length == 0) return self;
NSMutableString *string = [NSMutableString string];
[string appendString:[NSString stringWithFormat:@"%c", [self characterAtIndex:0]].lowercaseString];
if (self.length >= 2) [string appendString:[self substringFromIndex:1]];
return string;
}
- (NSString *)mj_firstCharUpper
{
if (self.length == 0) return self;
NSMutableString *string = [NSMutableString string];
[string appendString:[NSString stringWithFormat:@"%c", [self characterAtIndex:0]].uppercaseString];
if (self.length >= 2) [string appendString:[self substringFromIndex:1]];
return string;
}
- (BOOL)mj_isPureInt
{
NSScanner *scan = [NSScanner scannerWithString:self];
int val;
return [scan scanInt:&val] && [scan isAtEnd];
}
- (NSURL *)mj_url
{
// [self stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet characterSetWithCharactersInString:@"!$&'()*+,-./:;=?@_~%#[]"]];
#pragma clang diagnostic push
#pragma clang diagnostic ignored"-Wdeprecated-declarations"
return [NSURL URLWithString:(NSString *)CFBridgingRelease(CFURLCreateStringByAddingPercentEscapes(kCFAllocatorDefault, (CFStringRef)self, (CFStringRef)@"!$&'()*+,-./:;=?@_~%#[]", NULL,kCFStringEncodingUTF8))];
#pragma clang diagnostic pop
}
@end
MJExtension
===
[![Carthage compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](https://github.com/Carthage/Carthage)
![podversion](https://img.shields.io/cocoapods/v/MJExtension.svg)
- A fast, convenient and nonintrusive conversion framework between JSON and model.
- 转换速度快、使用简单方便的字典转模型框架
## ‼️ 纯Swift版的JSON与Model转换框架已经开源上架 ‼️
- [KakaJSON](https://github.com/kakaopensource/KakaJSON)
- [中文教程](https://www.cnblogs.com/mjios/p/11352776.html)
- 如果你的项目是用Swift写的Model,墙裂推荐使用[KakaJSON](https://github.com/kakaopensource/KakaJSON)
- 已经对各种常用的数据场景进行了大量的单元测试
- 简单易用、功能丰富、转换快速
## 关于在Swift中使用MJExtension ‼️
### ‼️ 在 Swift4 之后, 请在属性前加 `@objc` 修饰. 以保证 Swift 的属性能够暴露给 Objc 使用. ‼️
### ‼️ 请勿使用 `Bool` 类型, 因为在 Swift 中并没有桥接该类型, 不能显式的对应 `BOOL`, 请使用 `NSNumber` 替代 ‼️
## Contents
* [Getting Started 【开始使用】](#Getting_Started)
* [Features 【能做什么】](#Features)
* [Installation 【安装】](#Installation)
* [Examples 【示例】](#Examples)
* [JSON -> Model](#JSON_Model)
* [JSONString -> Model](#JSONString_Model)
* [Model contains model](#Model_contains_model)
* [Model contains model-array](#Model_contains_model_array)
* [Model name - JSON key mapping](#Model_name_JSON_key_mapping)
* [JSON array -> model array](#JSON_array_model_array)
* [Model -> JSON](#Model_JSON)
* [Model array -> JSON array](#Model_array_JSON_array)
* [Core Data](#Core_Data)
* [Coding](#Coding)
* [Camel -> underline](#Camel_underline)
* [NSString -> NSDate, nil -> @""](#NSString_NSDate)
* [More use cases](#More_use_cases)
---
# <a id="Getting_Started"></a> Getting Started【开始使用】
## <a id="Features"></a> Features【能做什么】
- MJExtension是一套字典和模型之间互相转换的超轻量级框架
* `JSON` --> `Model``Core Data Model`
* `JSONString` --> `Model``Core Data Model`
* `Model``Core Data Model` --> `JSON`
* `JSON Array` --> `Model Array``Core Data Model Array`
* `JSONString` --> `Model Array``Core Data Model Array`
* `Model Array``Core Data Model Array` --> `JSON Array`
* Coding all properties of a model with only one line of code.
* 只需要一行代码,就能实现模型的所有属性进行Coding(归档和解档)
## <a id="Installation"></a> Installation【安装】
### From CocoaPods【使用CocoaPods】
```ruby
pod 'MJExtension'
```
### Manually【手动导入】
- Drag all source files under folder `MJExtension` to your project.【将`MJExtension`文件夹中的所有源代码拽入项目中】
- Import the main header file:`#import "MJExtension.h"`【导入主头文件:`#import "MJExtension.h"`
```objc
MJExtension.h
MJConst.h MJConst.m
MJFoundation.h MJFoundation.m
MJProperty.h MJProperty.m
MJType.h MJType.m
NSObject+MJCoding.h NSObject+MJCoding.m
NSObject+MJProperty.h NSObject+MJProperty.m
NSObject+MJKeyValue.h NSObject+MJKeyValue.m
```
# <a id="Examples"></a> Examples【示例】
### <a id="JSON_Model"></a> The most simple JSON -> Model【最简单的字典转模型】
```objc
typedef enum {
SexMale,
SexFemale
} Sex;
@interface User : NSObject
@property (copy, nonatomic) NSString *name;
@property (copy, nonatomic) NSString *icon;
@property (assign, nonatomic) unsigned int age;
@property (copy, nonatomic) NSString *height;
@property (strong, nonatomic) NSNumber *money;
@property (assign, nonatomic) Sex sex;
@property (assign, nonatomic, getter=isGay) BOOL gay;
@end
/***********************************************/
NSDictionary *dict = @{
@"name" : @"Jack",
@"icon" : @"lufy.png",
@"age" : @20,
@"height" : @"1.55",
@"money" : @100.9,
@"sex" : @(SexFemale),
@"gay" : @"true"
// @"gay" : @"1"
// @"gay" : @"NO"
};
// JSON -> User
User *user = [User mj_objectWithKeyValues:dict];
NSLog(@"name=%@, icon=%@, age=%zd, height=%@, money=%@, sex=%d, gay=%d", user.name, user.icon, user.age, user.height, user.money, user.sex, user.gay);
// name=Jack, icon=lufy.png, age=20, height=1.550000, money=100.9, sex=1
```
### <a id="JSONString_Model"></a> JSONString -> Model【JSON字符串转模型】
```objc
// 1.Define a JSONString
NSString *jsonString = @"{\"name\":\"Jack\", \"icon\":\"lufy.png\", \"age\":20}";
// 2.JSONString -> User
User *user = [User mj_objectWithKeyValues:jsonString];
// 3.Print user's properties
NSLog(@"name=%@, icon=%@, age=%d", user.name, user.icon, user.age);
// name=Jack, icon=lufy.png, age=20
```
### <a id="Model_contains_model"></a> Model contains model【模型中嵌套模型】
```objc
@interface Status : NSObject
@property (copy, nonatomic) NSString *text;
@property (strong, nonatomic) User *user;
@property (strong, nonatomic) Status *retweetedStatus;
@end
/***********************************************/
NSDictionary *dict = @{
@"text" : @"Agree!Nice weather!",
@"user" : @{
@"name" : @"Jack",
@"icon" : @"lufy.png"
},
@"retweetedStatus" : @{
@"text" : @"Nice weather!",
@"user" : @{
@"name" : @"Rose",
@"icon" : @"nami.png"
}
}
};
// JSON -> Status
Status *status = [Status mj_objectWithKeyValues:dict];
NSString *text = status.text;
NSString *name = status.user.name;
NSString *icon = status.user.icon;
NSLog(@"text=%@, name=%@, icon=%@", text, name, icon);
// text=Agree!Nice weather!, name=Jack, icon=lufy.png
NSString *text2 = status.retweetedStatus.text;
NSString *name2 = status.retweetedStatus.user.name;
NSString *icon2 = status.retweetedStatus.user.icon;
NSLog(@"text2=%@, name2=%@, icon2=%@", text2, name2, icon2);
// text2=Nice weather!, name2=Rose, icon2=nami.png
```
### <a id="Model_contains_model_array"></a> Model contains model-array【模型中有个数组属性,数组里面又要装着其他模型】
```objc
@interface Ad : NSObject
@property (copy, nonatomic) NSString *image;
@property (copy, nonatomic) NSString *url;
@end
@interface StatusResult : NSObject
/** Contatins status model */
@property (strong, nonatomic) NSMutableArray *statuses;
/** Contatins ad model */
@property (strong, nonatomic) NSArray *ads;
@property (strong, nonatomic) NSNumber *totalNumber;
@end
/***********************************************/
// Tell MJExtension what type of model will be contained in statuses and ads.
[StatusResult mj_setupObjectClassInArray:^NSDictionary *{
return @{
@"statuses" : @"Status",
// @"statuses" : [Status class],
@"ads" : @"Ad"
// @"ads" : [Ad class]
};
}];
// Equals: StatusResult.m implements +mj_objectClassInArray method.
NSDictionary *dict = @{
@"statuses" : @[
@{
@"text" : @"Nice weather!",
@"user" : @{
@"name" : @"Rose",
@"icon" : @"nami.png"
}
},
@{
@"text" : @"Go camping tomorrow!",
@"user" : @{
@"name" : @"Jack",
@"icon" : @"lufy.png"
}
}
],
@"ads" : @[
@{
@"image" : @"ad01.png",
@"url" : @"http://www.ad01.com"
},
@{
@"image" : @"ad02.png",
@"url" : @"http://www.ad02.com"
}
],
@"totalNumber" : @"2014"
};
// JSON -> StatusResult
StatusResult *result = [StatusResult mj_objectWithKeyValues:dict];
NSLog(@"totalNumber=%@", result.totalNumber);
// totalNumber=2014
// Printing
for (Status *status in result.statuses) {
NSString *text = status.text;
NSString *name = status.user.name;
NSString *icon = status.user.icon;
NSLog(@"text=%@, name=%@, icon=%@", text, name, icon);
}
// text=Nice weather!, name=Rose, icon=nami.png
// text=Go camping tomorrow!, name=Jack, icon=lufy.png
// Printing
for (Ad *ad in result.ads) {
NSLog(@"image=%@, url=%@", ad.image, ad.url);
}
// image=ad01.png, url=http://www.ad01.com
// image=ad02.png, url=http://www.ad02.com
```
### <a id="Model_name_JSON_key_mapping"></a> Model name - JSON key mapping【模型中的属性名和字典中的key不相同(或者需要多级映射)】
```objc
@interface Bag : NSObject
@property (copy, nonatomic) NSString *name;
@property (assign, nonatomic) double price;
@end
@interface Student : NSObject
@property (copy, nonatomic) NSString *ID;
@property (copy, nonatomic) NSString *desc;
@property (copy, nonatomic) NSString *nowName;
@property (copy, nonatomic) NSString *oldName;
@property (copy, nonatomic) NSString *nameChangedTime;
@property (strong, nonatomic) Bag *bag;
@end
/***********************************************/
// How to map
[Student mj_setupReplacedKeyFromPropertyName:^NSDictionary *{
return @{
@"ID" : @"id",
@"desc" : @"description",
@"oldName" : @"name.oldName",
@"nowName" : @"name.newName",
@"nameChangedTime" : @"name.info[1].nameChangedTime",
@"bag" : @"other.bag"
};
}];
// Equals: Student.m implements +mj_replacedKeyFromPropertyName method.
NSDictionary *dict = @{
@"id" : @"20",
@"description" : @"kids",
@"name" : @{
@"newName" : @"lufy",
@"oldName" : @"kitty",
@"info" : @[
@"test-data",
@{
@"nameChangedTime" : @"2013-08"
}
]
},
@"other" : @{
@"bag" : @{
@"name" : @"a red bag",
@"price" : @100.7
}
}
};
// JSON -> Student
Student *stu = [Student mj_objectWithKeyValues:dict];
// Printing
NSLog(@"ID=%@, desc=%@, oldName=%@, nowName=%@, nameChangedTime=%@",
stu.ID, stu.desc, stu.oldName, stu.nowName, stu.nameChangedTime);
// ID=20, desc=kids, oldName=kitty, nowName=lufy, nameChangedTime=2013-08
NSLog(@"bagName=%@, bagPrice=%f", stu.bag.name, stu.bag.price);
// bagName=a red bag, bagPrice=100.700000
```
### <a id="JSON_array_model_array"></a> JSON array -> model array【将一个字典数组转成模型数组】
```objc
NSArray *dictArray = @[
@{
@"name" : @"Jack",
@"icon" : @"lufy.png"
},
@{
@"name" : @"Rose",
@"icon" : @"nami.png"
}
];
// JSON array -> User array
NSArray *userArray = [User mj_objectArrayWithKeyValuesArray:dictArray];
// Printing
for (User *user in userArray) {
NSLog(@"name=%@, icon=%@", user.name, user.icon);
}
// name=Jack, icon=lufy.png
// name=Rose, icon=nami.png
```
### <a id="Model_JSON"></a> Model -> JSON【将一个模型转成字典】
```objc
// New model
User *user = [[User alloc] init];
user.name = @"Jack";
user.icon = @"lufy.png";
Status *status = [[Status alloc] init];
status.user = user;
status.text = @"Nice mood!";
// Status -> JSON
NSDictionary *statusDict = status.mj_keyValues;
NSLog(@"%@", statusDict);
/*
{
text = "Nice mood!";
user = {
icon = "lufy.png";
name = Jack;
};
}
*/
// More complex situation
Student *stu = [[Student alloc] init];
stu.ID = @"123";
stu.oldName = @"rose";
stu.nowName = @"jack";
stu.desc = @"handsome";
stu.nameChangedTime = @"2018-09-08";
Bag *bag = [[Bag alloc] init];
bag.name = @"a red bag";
bag.price = 205;
stu.bag = bag;
NSDictionary *stuDict = stu.mj_keyValues;
NSLog(@"%@", stuDict);
/*
{
ID = 123;
bag = {
name = "\U5c0f\U4e66\U5305";
price = 205;
};
desc = handsome;
nameChangedTime = "2018-09-08";
nowName = jack;
oldName = rose;
}
*/
```
### <a id="Model_array_JSON_array"></a> Model array -> JSON array【将一个模型数组转成字典数组】
```objc
// New model array
User *user1 = [[User alloc] init];
user1.name = @"Jack";
user1.icon = @"lufy.png";
User *user2 = [[User alloc] init];
user2.name = @"Rose";
user2.icon = @"nami.png";
NSArray *userArray = @[user1, user2];
// Model array -> JSON array
NSArray *dictArray = [User mj_keyValuesArrayWithObjectArray:userArray];
NSLog(@"%@", dictArray);
/*
(
{
icon = "lufy.png";
name = Jack;
},
{
icon = "nami.png";
name = Rose;
}
)
*/
```
### <a id="Core_Data"></a> Core Data
```objc
NSDictionary *dict = @{
@"name" : @"Jack",
@"icon" : @"lufy.png",
@"age" : @20,
@"height" : @1.55,
@"money" : @"100.9",
@"sex" : @(SexFemale),
@"gay" : @"true"
};
// This demo just provide simple steps
NSManagedObjectContext *context = nil;
User *user = [User mj_objectWithKeyValues:dict context:context];
[context save:nil];
```
### <a id="Coding"></a> Coding
```objc
#import "MJExtension.h"
@implementation Bag
// NSCoding Implementation
MJExtensionCodingImplementation
@end
/***********************************************/
// what properties not to be coded
[Bag mj_setupIgnoredCodingPropertyNames:^NSArray *{
return @[@"name"];
}];
// Equals: Bag.m implements +mj_ignoredCodingPropertyNames method.
// Create model
Bag *bag = [[Bag alloc] init];
bag.name = @"Red bag";
bag.price = 200.8;
NSString *file = [NSHomeDirectory() stringByAppendingPathComponent:@"Desktop/bag.data"];
// Encoding
[NSKeyedArchiver archiveRootObject:bag toFile:file];
// Decoding
Bag *decodedBag = [NSKeyedUnarchiver unarchiveObjectWithFile:file];
NSLog(@"name=%@, price=%f", decodedBag.name, decodedBag.price);
// name=(null), price=200.800000
```
### <a id="Camel_underline"></a> Camel -> underline【统一转换属性名(比如驼峰转下划线)】
```objc
// Dog
#import "MJExtension.h"
@implementation Dog
+ (NSString *)mj_replacedKeyFromPropertyName121:(NSString *)propertyName
{
// nickName -> nick_name
return [propertyName mj_underlineFromCamel];
}
@end
// NSDictionary
NSDictionary *dict = @{
@"nick_name" : @"旺财",
@"sale_price" : @"10.5",
@"run_speed" : @"100.9"
};
// NSDictionary -> Dog
Dog *dog = [Dog mj_objectWithKeyValues:dict];
// printing
NSLog(@"nickName=%@, scalePrice=%f runSpeed=%f", dog.nickName, dog.salePrice, dog.runSpeed);
```
### <a id="NSString_NSDate"></a> NSString -> NSDate, nil -> @""【过滤字典的值(比如字符串日期处理为NSDate、字符串nil处理为@"")】
```objc
// Book
#import "MJExtension.h"
@implementation Book
- (id)mj_newValueFromOldValue:(id)oldValue property:(MJProperty *)property
{
if ([property.name isEqualToString:@"publisher"]) {
if (oldValue == nil) return @"";
} else if (property.type.typeClass == [NSDate class]) {
NSDateFormatter *fmt = [[NSDateFormatter alloc] init];
fmt.dateFormat = @"yyyy-MM-dd";
return [fmt dateFromString:oldValue];
}
return oldValue;
}
@end
// NSDictionary
NSDictionary *dict = @{
@"name" : @"5分钟突破iOS开发",
@"publishedTime" : @"2011-09-10"
};
// NSDictionary -> Book
Book *book = [Book mj_objectWithKeyValues:dict];
// printing
NSLog(@"name=%@, publisher=%@, publishedTime=%@", book.name, book.publisher, book.publishedTime);
```
### <a id="More_use_cases"></a> More use cases【更多用法】
- Please reference to `NSObject+MJKeyValue.h` and `NSObject+MJCoding.h`
## 期待
* 如果在使用过程中遇到BUG,希望你能Issues我,谢谢(或者尝试下载最新的框架代码看看BUG修复没有)
* 如果在使用过程中发现功能不够用,希望你能Issues我,我非常想为这个框架增加更多好用的功能,谢谢
* 如果你想为MJExtension输出代码,请拼命Pull Requests我
...@@ -15,9 +15,20 @@ PODS: ...@@ -15,9 +15,20 @@ PODS:
- AFNetworking/UIKit (3.1.0): - AFNetworking/UIKit (3.1.0):
- AFNetworking/NSURLSession - AFNetworking/NSURLSession
- Alamofire (4.7.0) - Alamofire (4.7.0)
- EVReflection (5.10.0):
- EVReflection/Core (= 5.10.0)
- EVReflection/Core (5.10.0)
- GM-Swift-Observable (4.0.1)
- GMAI (0.1.0): - GMAI (0.1.0):
- EVReflection
- GMBase - GMBase
- GMBaseSwift
- GMCache
- GMFoundation
- GMNetworking - GMNetworking
- GMRouter
- Qiniu
- SDWebImage
- GMBase (1.1.5): - GMBase (1.1.5):
- GMHud - GMHud
- GMJSONModel - GMJSONModel
...@@ -28,8 +39,17 @@ PODS: ...@@ -28,8 +39,17 @@ PODS:
- MBProgressHUD - MBProgressHUD
- SDWebImage - SDWebImage
- "UITableView+FDTemplateLayoutCell (= 1.4)" - "UITableView+FDTemplateLayoutCell (= 1.4)"
- GMBaseSwift (3.3.7):
- EVReflection (= 5.10.0)
- GM-Swift-Observable
- GMHud
- GMNetworking
- GMPhobos
- GMRefresh
- SnapKit (= 4.0.0)
- GMCache (1.0.1): - GMCache (1.0.1):
- TMCache (= 2.1.0) - TMCache (= 2.1.0)
- GMFoundation (1.0.5)
- GMHud (1.0.3): - GMHud (1.0.3):
- MBProgressHUD (= 0.9.2) - MBProgressHUD (= 0.9.2)
- GMJSONModel (1.7.4) - GMJSONModel (1.7.4)
...@@ -84,23 +104,31 @@ PODS: ...@@ -84,23 +104,31 @@ PODS:
- GMRefresh (1.0.4): - GMRefresh (1.0.4):
- GMPhobos - GMPhobos
- MJRefresh - MJRefresh
- GMRouter (0.1.5):
- MJExtension
- Masonry (1.1.0) - Masonry (1.1.0)
- MBProgressHUD (0.9.2) - MBProgressHUD (0.9.2)
- MJExtension (3.2.1)
- MJRefresh (3.3.1) - MJRefresh (3.3.1)
- Qiniu (7.3.0)
- SDWebImage (5.4.2): - SDWebImage (5.4.2):
- SDWebImage/Core (= 5.4.2) - SDWebImage/Core (= 5.4.2)
- SDWebImage/Core (5.4.2) - SDWebImage/Core (5.4.2)
- SnapKit (4.2.0) - SnapKit (4.0.0)
- TMCache (2.1.0) - TMCache (2.1.0)
- "UITableView+FDTemplateLayoutCell (1.4)" - "UITableView+FDTemplateLayoutCell (1.4)"
DEPENDENCIES: DEPENDENCIES:
- GMAI (from `../`) - GMAI (from `../`)
- GMBaseSwift (= 3.3.7)
SPEC REPOS: SPEC REPOS:
"git@git.wanmeizhensuo.com:gengmeiios/GMSpecs.git": "git@git.wanmeizhensuo.com:gengmeiios/GMSpecs.git":
- GM-Swift-Observable
- GMBase - GMBase
- GMBaseSwift
- GMCache - GMCache
- GMFoundation
- GMHud - GMHud
- GMJSONModel - GMJSONModel
- GMKit - GMKit
...@@ -108,12 +136,16 @@ SPEC REPOS: ...@@ -108,12 +136,16 @@ SPEC REPOS:
- GMNetworking - GMNetworking
- GMPhobos - GMPhobos
- GMRefresh - GMRefresh
- GMRouter
https://github.com/cocoapods/specs.git: https://github.com/cocoapods/specs.git:
- AFNetworking - AFNetworking
- Alamofire - Alamofire
- EVReflection
- Masonry - Masonry
- MBProgressHUD - MBProgressHUD
- MJExtension
- MJRefresh - MJRefresh
- Qiniu
- SDWebImage - SDWebImage
- SnapKit - SnapKit
- TMCache - TMCache
...@@ -126,9 +158,13 @@ EXTERNAL SOURCES: ...@@ -126,9 +158,13 @@ EXTERNAL SOURCES:
SPEC CHECKSUMS: SPEC CHECKSUMS:
AFNetworking: 5e0e199f73d8626b11e79750991f5d173d1f8b67 AFNetworking: 5e0e199f73d8626b11e79750991f5d173d1f8b67
Alamofire: 907e0a98eb68cdb7f9d1f541a563d6ac5dc77b25 Alamofire: 907e0a98eb68cdb7f9d1f541a563d6ac5dc77b25
GMAI: d13c3aef135a5fa2075123173cb20f1eb650596d EVReflection: 1abc1a81927ab0d30170238cf9b79bff489e9728
GM-Swift-Observable: 756d8fc13638b9faa68cb10266b2ffb47a911595
GMAI: 6dbdb0af8d3239093438c761e84f6ed88b5a53ad
GMBase: 2b77f591a35a589ed331c490af093b68b0fc73be GMBase: 2b77f591a35a589ed331c490af093b68b0fc73be
GMBaseSwift: 168898c825ef16ffbf8bd261cb4b295cfb492bd2
GMCache: b78d8e46db864405e91d226ce640cc80d966c611 GMCache: b78d8e46db864405e91d226ce640cc80d966c611
GMFoundation: 3b621bde6b0661ae61393b55691974f570f46208
GMHud: 18d41f4900a204f27be14e9504fcee2060ae3b2c GMHud: 18d41f4900a204f27be14e9504fcee2060ae3b2c
GMJSONModel: 5e81a98de668e9f93cf6ff77869f77b0d1a806be GMJSONModel: 5e81a98de668e9f93cf6ff77869f77b0d1a806be
GMKit: 09fe863069d9750c89fae2939770b08fc74b9027 GMKit: 09fe863069d9750c89fae2939770b08fc74b9027
...@@ -136,14 +172,17 @@ SPEC CHECKSUMS: ...@@ -136,14 +172,17 @@ SPEC CHECKSUMS:
GMNetworking: 592b9b71f2a7d92203483276158ce3139ac789d2 GMNetworking: 592b9b71f2a7d92203483276158ce3139ac789d2
GMPhobos: 1e2d68c456b69bf156276d7242877498107474db GMPhobos: 1e2d68c456b69bf156276d7242877498107474db
GMRefresh: c01ff8de5ada92e1362602fb6991f99124b7dbe3 GMRefresh: c01ff8de5ada92e1362602fb6991f99124b7dbe3
GMRouter: 808430d1275e92809eb1180ed3cb89525295da31
Masonry: 678fab65091a9290e40e2832a55e7ab731aad201 Masonry: 678fab65091a9290e40e2832a55e7ab731aad201
MBProgressHUD: 1569cf7ace17a8bac47aabfbb8580a49690386d1 MBProgressHUD: 1569cf7ace17a8bac47aabfbb8580a49690386d1
MJExtension: 635f2c663dcb1bf76fa4b715b2570a5710aec545
MJRefresh: eeda70fbf0ad277f3178cef1cd0c3532591d6237 MJRefresh: eeda70fbf0ad277f3178cef1cd0c3532591d6237
Qiniu: a2fb1f87b40f52bc1f386d93c6d48aab09472eae
SDWebImage: 4ca2dc4eefae4224bea8f504251cda485a363745 SDWebImage: 4ca2dc4eefae4224bea8f504251cda485a363745
SnapKit: fe8a619752f3f27075cc9a90244d75c6c3f27e2a SnapKit: a42d492c16e80209130a3379f73596c3454b7694
TMCache: 95ebcc9b3c7e90fb5fd8fc3036cba3aa781c9bed TMCache: 95ebcc9b3c7e90fb5fd8fc3036cba3aa781c9bed
"UITableView+FDTemplateLayoutCell": 234e1582bcc4e18461af91155123bb96538ed030 "UITableView+FDTemplateLayoutCell": 234e1582bcc4e18461af91155123bb96538ed030
PODFILE CHECKSUM: a3a39944abac09536207b087a26344c6d8478f6b PODFILE CHECKSUM: 841939c0a048be2edb77418fd6de6e35a738768b
COCOAPODS: 1.7.4 COCOAPODS: 1.7.4
This source diff could not be displayed because it is too large. You can view the blob instead.
The MIT License (MIT)
Copyright (c) 2011-2016 Qiniu, Ltd.<sdk@qiniu.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.
//
// QNPipeline.h
// QiniuSDK
//
// Created by BaiLong on 2017/7/25.
// Copyright © 2017年 Qiniu. All rights reserved.
//
#ifndef QNPipeline_h
#define QNPipeline_h
@class QNResponseInfo;
@interface QNPipelineConfig : NSObject
/**
* 上报打点域名
*/
@property (copy, nonatomic, readonly) NSString *host;
/**
* 超时时间 单位 秒
*/
@property (assign) UInt32 timeoutInterval;
- (instancetype)initWithHost:(NSString *)host;
- (instancetype)init;
@end
/**
* 上传完成后的回调函数
*
* @param info 上下文信息,包括状态码,错误值
* @param key 上传时指定的key,原样返回
* @param resp 上传成功会返回文件信息,失败为nil; 可以通过此值是否为nil 判断上传结果
*/
typedef void (^QNPipelineCompletionHandler)(QNResponseInfo *info);
@interface QNPipeline : NSObject
- (instancetype)init:(QNPipelineConfig *)config;
- (void)pumpRepo:(NSString *)repo
event:(NSDictionary *)data
token:(NSString *)token
handler:(QNPipelineCompletionHandler)handler;
- (void)pumpRepo:(NSString *)repo
events:(NSArray<NSDictionary *> *)data
token:(NSString *)token
handler:(QNPipelineCompletionHandler)handler;
@end
#endif /* QNPipeline_h */
//
// QNPipeline.m
// QiniuSDK
//
// Created by BaiLong on 2017/7/25.
// Copyright © 2017年 Qiniu. All rights reserved.
//
#import <Foundation/Foundation.h>
#import "QNSessionManager.h"
#import "QNPipeline.h"
@implementation QNPipelineConfig
- (instancetype)init {
return [self initWithHost:@"https://pipeline.qiniu.com"];
}
- (instancetype)initWithHost:(NSString*)host {
if (self = [super init]) {
_host = host;
_timeoutInterval = 10;
}
return self;
}
@end
@interface QNPipeline ()
@property (nonatomic) id<QNHttpDelegate> httpManager;
@property (nonatomic) QNPipelineConfig* config;
+ (NSDateFormatter*)dateFormatter;
@end
static NSString* buildString(NSObject* obj) {
NSString* v;
if ([obj isKindOfClass:[NSNumber class]]) {
NSNumber* num = (NSNumber*)obj;
if (num == (void*)kCFBooleanFalse) {
v = @"false";
} else if (num == (void*)kCFBooleanTrue) {
v = @"true";
} else if (!strcmp(num.objCType, @encode(BOOL))) {
if ([num intValue] == 0) {
v = @"false";
} else {
v = @"true";
}
} else {
v = num.stringValue;
}
} else if ([obj isKindOfClass:[NSString class]]) {
v = (NSString*)obj;
v = [v stringByReplacingOccurrencesOfString:@"\n" withString:@"\\n"];
v = [v stringByReplacingOccurrencesOfString:@"\t" withString:@"\\t"];
} else if ([obj isKindOfClass:[NSDictionary class]] || [obj isKindOfClass:[NSArray class]] || [obj isKindOfClass:[NSSet class]]) {
v = [[NSString alloc] initWithData:[NSJSONSerialization dataWithJSONObject:obj options:kNilOptions error:nil] encoding:NSUTF8StringEncoding];
} else if ([obj isKindOfClass:[NSDate class]]) {
v = [[QNPipeline dateFormatter] stringFromDate:(NSDate*)obj];
} else {
v = [obj description];
}
return v;
}
static void formatPoint(NSDictionary* event, NSMutableString* buffer) {
[event enumerateKeysAndObjectsUsingBlock:^(NSString* key, NSObject* obj, BOOL* stop) {
if (obj == nil || [obj isEqual:[NSNull null]]) {
return;
}
[buffer appendString:key];
[buffer appendString:@"="];
[buffer appendString:buildString(obj)];
[buffer appendString:@"\t"];
}];
NSRange range = NSMakeRange(buffer.length - 1, 1);
[buffer replaceCharactersInRange:range withString:@"\n"];
}
static NSMutableString* formatPoints(NSArray<NSDictionary*>* events) {
NSMutableString* str = [NSMutableString new];
[events enumerateObjectsUsingBlock:^(NSDictionary* _Nonnull obj, NSUInteger idx, BOOL* _Nonnull stop) {
formatPoint(obj, str);
}];
return str;
}
@implementation QNPipeline
- (instancetype)init:(QNPipelineConfig*)config {
if (self = [super init]) {
if (config == nil) {
config = [QNPipelineConfig new];
}
_config = config;
#if (defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 70000) || (defined(__MAC_OS_X_VERSION_MAX_ALLOWED) && __MAC_OS_X_VERSION_MAX_ALLOWED >= 1090)
_httpManager = [[QNSessionManager alloc] initWithProxy:nil timeout:config.timeoutInterval urlConverter:nil];
#endif
}
return self;
}
- (void)pumpRepo:(NSString*)repo
event:(NSDictionary*)data
token:(NSString*)token
handler:(QNPipelineCompletionHandler)handler {
NSMutableString* str = [NSMutableString new];
formatPoint(data, str);
[self pumpRepo:repo string:str token:token handler:handler];
}
- (void)pumpRepo:(NSString*)repo
events:(NSArray<NSDictionary*>*)data
token:(NSString*)token
handler:(QNPipelineCompletionHandler)handler {
NSMutableString* str = formatPoints(data);
[self pumpRepo:repo string:str token:token handler:handler];
}
- (NSString*)url:(NSString*)repo {
return [NSString stringWithFormat:@"%@/v2/repos/%@/data", _config.host, repo];
}
- (void)pumpRepo:(NSString*)repo
string:(NSString*)str
token:(NSString*)token
handler:(QNPipelineCompletionHandler)handler {
NSDictionary* headers = @{ @"Authorization" : token,
@"Content-Type" : @"text/plain" };
[_httpManager post:[self url:repo] withData:[str dataUsingEncoding:NSUTF8StringEncoding] withParams:nil withHeaders:headers withTaskIdentifier:nil withCompleteBlock:^(QNResponseInfo* info, NSDictionary* resp) {
handler(info);
}
withProgressBlock:nil
withCancelBlock:nil
withAccess:nil];
}
+ (NSDateFormatter*)dateFormatter {
static NSDateFormatter* formatter = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
formatter = [NSDateFormatter new];
[formatter setLocale:[NSLocale currentLocale]];
[formatter setDateFormat:@"yyyy-MM-dd'T'HH:mm:ss.SSSXXX"];
[formatter setTimeZone:[NSTimeZone defaultTimeZone]];
});
return formatter;
}
@end
//
// QNALAssetFile.h
// QiniuSDK
//
// Created by bailong on 15/7/25.
// Copyright (c) 2015年 Qiniu. All rights reserved.
//
#import <Foundation/Foundation.h>
#import "QNFileDelegate.h"
#if defined(__IPHONE_OS_VERSION_MAX_ALLOWED)
@class ALAsset;
@interface QNALAssetFile : NSObject <QNFileDelegate>
/**
* 打开指定文件
*
* @param path 文件路径
* @param error 输出的错误信息
*
* @return 实例
*/
- (instancetype)init:(ALAsset *)asset
error:(NSError *__autoreleasing *)error;
@end
#endif
\ No newline at end of file
//
// QNALAssetFile.m
// QiniuSDK
//
// Created by bailong on 15/7/25.
// Copyright (c) 2015年 Qiniu. All rights reserved.
//
#import "QNALAssetFile.h"
#ifdef __IPHONE_OS_VERSION_MAX_ALLOWED
#import <AssetsLibrary/AssetsLibrary.h>
#import "QNResponseInfo.h"
@interface QNALAssetFile ()
@property (nonatomic) ALAsset *asset;
@property (readonly) int64_t fileSize;
@property (readonly) int64_t fileModifyTime;
@property (nonatomic, strong) NSLock *lock;
@end
@implementation QNALAssetFile
- (instancetype)init:(ALAsset *)asset
error:(NSError *__autoreleasing *)error {
if (self = [super init]) {
NSDate *createTime = [asset valueForProperty:ALAssetPropertyDate];
int64_t t = 0;
if (createTime != nil) {
t = [createTime timeIntervalSince1970];
}
_fileModifyTime = t;
_fileSize = asset.defaultRepresentation.size;
_asset = asset;
_lock = [[NSLock alloc] init];
}
return self;
}
- (NSData *)read:(long)offset
size:(long)size
error:(NSError **)error {
NSData *data = nil;
@try {
[_lock lock];
ALAssetRepresentation *rep = [self.asset defaultRepresentation];
Byte *buffer = (Byte *)malloc(size);
NSUInteger buffered = [rep getBytes:buffer fromOffset:offset length:size error:error];
data = [NSData dataWithBytesNoCopy:buffer length:buffered freeWhenDone:YES];
} @catch (NSException *exception) {
*error = [NSError errorWithDomain:NSCocoaErrorDomain code:kQNFileError userInfo:@{NSLocalizedDescriptionKey : exception.reason}];
NSLog(@"read file failed reason: %@ \n%@", exception.reason, exception.callStackSymbols);
} @finally {
[_lock unlock];
}
return data;
}
- (NSData *)readAllWithError:(NSError **)error {
return [self read:0 size:(long)_fileSize error:error];
}
- (void)close {
}
- (NSString *)path {
ALAssetRepresentation *rep = [self.asset defaultRepresentation];
return [rep url].path;
}
- (int64_t)modifyTime {
return _fileModifyTime;
}
- (int64_t)size {
return _fileSize;
}
@end
#endif
//
// QNAsyncRun.h
// QiniuSDK
//
// Created by bailong on 14/10/17.
// Copyright (c) 2014年 Qiniu. All rights reserved.
//
typedef void (^QNRun)(void);
void QNAsyncRun(QNRun run);
void QNAsyncRunInMain(QNRun run);
//
// QNAsyncRun.m
// QiniuSDK
//
// Created by bailong on 14/10/17.
// Copyright (c) 2014 Qiniu. All rights reserved.
//
#import "QNAsyncRun.h"
#import <Foundation/Foundation.h>
void QNAsyncRun(QNRun run) {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void) {
run();
});
}
void QNAsyncRunInMain(QNRun run) {
dispatch_async(dispatch_get_main_queue(), ^(void) {
run();
});
}
//
// QNCrc.h
// QiniuSDK
//
// Created by bailong on 14-9-29.
// Copyright (c) 2014年 Qiniu. All rights reserved.
//
#import <Foundation/Foundation.h>
/**
* 生成crc32 校验码
*/
@interface QNCrc32 : NSObject
/**
* 文件校验
*
* @param filePath 文件路径
* @param error 文件读取错误
*
* @return 校验码
*/
+ (UInt32)file:(NSString *)filePath
error:(NSError **)error;
/**
* 二进制字节校验
*
* @param data 二进制数据
*
* @return 校验码
*/
+ (UInt32)data:(NSData *)data;
@end
//
// QNCrc.m
// QiniuSDK
//
// Created by bailong on 14-9-29.
// Copyright (c) 2014年 Qiniu. All rights reserved.
//
#import <zlib.h>
#import "QNConfiguration.h"
#import "QNCrc32.h"
@implementation QNCrc32
+ (UInt32)data:(NSData *)data {
uLong crc = crc32(0L, Z_NULL, 0);
crc = crc32(crc, [data bytes], (uInt)[data length]);
return (UInt32)crc;
}
+ (UInt32)file:(NSString *)filePath
error:(NSError **)error {
@autoreleasepool {
NSData *data = [NSData dataWithContentsOfFile:filePath options:NSDataReadingMappedIfSafe error:error];
if (*error != nil) {
return 0;
}
int len = (int)[data length];
int count = (len + kQNBlockSize - 1) / kQNBlockSize;
uLong crc = crc32(0L, Z_NULL, 0);
for (int i = 0; i < count; i++) {
int offset = i * kQNBlockSize;
int size = (len - offset) > kQNBlockSize ? kQNBlockSize : (len - offset);
NSData *d = [data subdataWithRange:NSMakeRange(offset, (unsigned int)size)];
crc = crc32(crc, [d bytes], (uInt)[d length]);
}
return (UInt32)crc;
}
}
@end
//
// QNEtag.h
// QiniuSDK
//
// Created by bailong on 14/10/4.
// Copyright (c) 2014年 Qiniu. All rights reserved.
//
#import <Foundation/Foundation.h>
/**
* 服务器 hash etag 生成
*/
@interface QNEtag : NSObject
/**
* 文件etag
*
* @param filePath 文件路径
* @param error 输出文件读取错误
*
* @return etag
*/
+ (NSString *)file:(NSString *)filePath
error:(NSError **)error;
/**
* 二进制数据etag
*
* @param data 数据
*
* @return etag
*/
+ (NSString *)data:(NSData *)data;
@end
//
// QNEtag.m
// QiniuSDK
//
// Created by bailong on 14/10/4.
// Copyright (c) 2014年 Qiniu. All rights reserved.
//
#include <CommonCrypto/CommonCrypto.h>
#import "QNConfiguration.h"
#import "QNEtag.h"
#import "QNUrlSafeBase64.h"
@implementation QNEtag
+ (NSString *)file:(NSString *)filePath
error:(NSError **)error {
@autoreleasepool {
NSData *data = [NSData dataWithContentsOfFile:filePath options:NSDataReadingMappedIfSafe error:error];
if (error && *error) {
return 0;
}
return [QNEtag data:data];
}
}
+ (NSString *)data:(NSData *)data {
if (data == nil || [data length] == 0) {
return @"Fto5o-5ea0sNMlW_75VgGJCv2AcJ";
}
int len = (int)[data length];
int count = (len + kQNBlockSize - 1) / kQNBlockSize;
NSMutableData *retData = [NSMutableData dataWithLength:CC_SHA1_DIGEST_LENGTH + 1];
UInt8 *ret = [retData mutableBytes];
NSMutableData *blocksSha1 = nil;
UInt8 *pblocksSha1 = ret + 1;
if (count > 1) {
blocksSha1 = [NSMutableData dataWithLength:CC_SHA1_DIGEST_LENGTH * count];
pblocksSha1 = [blocksSha1 mutableBytes];
}
for (int i = 0; i < count; i++) {
int offset = i * kQNBlockSize;
int size = (len - offset) > kQNBlockSize ? kQNBlockSize : (len - offset);
NSData *d = [data subdataWithRange:NSMakeRange(offset, (unsigned int)size)];
CC_SHA1([d bytes], (CC_LONG)size, pblocksSha1 + i * CC_SHA1_DIGEST_LENGTH);
}
if (count == 1) {
ret[0] = 0x16;
} else {
ret[0] = 0x96;
CC_SHA1(pblocksSha1, (CC_LONG)CC_SHA1_DIGEST_LENGTH * count, ret + 1);
}
return [QNUrlSafeBase64 encodeData:retData];
}
@end
//
// QNFile.h
// QiniuSDK
//
// Created by bailong on 15/7/25.
// Copyright (c) 2015年 Qiniu. All rights reserved.
//
#import "QNFileDelegate.h"
#import <Foundation/Foundation.h>
@interface QNFile : NSObject <QNFileDelegate>
/**
* 打开指定文件
*
* @param path 文件路径
* @param error 输出的错误信息
*
* @return 实例
*/
- (instancetype)init:(NSString *)path
error:(NSError *__autoreleasing *)error;
@end
//
// QNFile.m
// QiniuSDK
//
// Created by bailong on 15/7/25.
// Copyright (c) 2015年 Qiniu. All rights reserved.
//
#import "QNFile.h"
#import "QNResponseInfo.h"
@interface QNFile ()
@property (nonatomic, readonly) NSString *filepath;
@property (nonatomic) NSData *data;
@property (readonly) int64_t fileSize;
@property (readonly) int64_t fileModifyTime;
@property (nonatomic) NSFileHandle *file;
@property (nonatomic) NSLock *lock;
@end
@implementation QNFile
- (instancetype)init:(NSString *)path
error:(NSError *__autoreleasing *)error {
if (self = [super init]) {
_filepath = path;
NSError *error2 = nil;
NSDictionary *fileAttr = [[NSFileManager defaultManager] attributesOfItemAtPath:path error:&error2];
if (error2 != nil) {
if (error != nil) {
*error = error2;
}
return self;
}
_fileSize = [fileAttr fileSize];
NSDate *modifyTime = fileAttr[NSFileModificationDate];
int64_t t = 0;
if (modifyTime != nil) {
t = [modifyTime timeIntervalSince1970];
}
_fileModifyTime = t;
NSFileHandle *f = nil;
NSData *d = nil;
//[NSData dataWithContentsOfFile:filePath options:NSDataReadingMappedIfSafe error:&error] 不能用在大于 200M的文件上,改用filehandle
// 参见 https://issues.apache.org/jira/browse/CB-5790
if (_fileSize > 16 * 1024 * 1024) {
f = [NSFileHandle fileHandleForReadingAtPath:path];
if (f == nil) {
if (error != nil) {
*error = [[NSError alloc] initWithDomain:path code:kQNFileError userInfo:nil];
}
return self;
}
} else {
d = [NSData dataWithContentsOfFile:path options:NSDataReadingMappedIfSafe error:&error2];
if (error2 != nil) {
if (error != nil) {
*error = error2;
}
return self;
}
}
_file = f;
_data = d;
_lock = [[NSLock alloc] init];
}
return self;
}
- (NSData *)read:(long)offset
size:(long)size
error:(NSError **)error {
NSData *data = nil;
@try {
[_lock lock];
if (_data != nil) {
data = [_data subdataWithRange:NSMakeRange(offset, (unsigned int)size)];
} else {
[_file seekToFileOffset:offset];
data = [_file readDataOfLength:size];
}
} @catch (NSException *exception) {
*error = [NSError errorWithDomain:NSCocoaErrorDomain code:kQNFileError userInfo:@{NSLocalizedDescriptionKey : exception.reason}];
NSLog(@"read file failed reason: %@ \n%@", exception.reason, exception.callStackSymbols);
} @finally {
[_lock unlock];
}
return data;
}
- (NSData *)readAllWithError:(NSError **)error {
return [self read:0 size:(long)_fileSize error:error];
}
- (void)close {
if (_file != nil) {
[_file closeFile];
}
}
- (NSString *)path {
return _filepath;
}
- (int64_t)modifyTime {
return _fileModifyTime;
}
- (int64_t)size {
return _fileSize;
}
@end
//
// QNFileDelegate.h
// QiniuSDK
//
// Created by bailong on 15/7/25.
// Copyright (c) 2015年 Qiniu. All rights reserved.
//
#import <Foundation/Foundation.h>
/**
* 文件处理接口,支持ALAsset, NSFileHandle, NSData
*/
@protocol QNFileDelegate <NSObject>
/**
* 从指定偏移读取数据
*
* @param offset 偏移地址
* @param size 大小
* @param error 错误信息
*
* @return 数据
*/
- (NSData *)read:(long)offset
size:(long)size
error:(NSError **)error;
/**
* 读取所有文件内容
*
* @return 数据
* @error 错误信息
*/
- (NSData *)readAllWithError:(NSError **)error;
/**
* 关闭文件
*
*/
- (void)close;
/**
* 文件路径
*
* @return 文件路径
*/
- (NSString *)path;
/**
* 文件修改时间
*
* @return 修改时间
*/
- (int64_t)modifyTime;
/**
* 文件大小
*
* @return 文件大小
*/
- (int64_t)size;
@end
//
// QNPHAssetFile.h
// Pods
//
// Created by 何舒 on 15/10/21.
//
//
#import <Foundation/Foundation.h>
#import "QNFileDelegate.h"
#if (defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 80000)
@class PHAsset;
@interface QNPHAssetFile : NSObject <QNFileDelegate>
/**
* 打开指定文件
*
* @param path 文件路径
* @param error 输出的错误信息
*
* @return 实例
*/
- (instancetype)init:(PHAsset *)phAsset
error:(NSError *__autoreleasing *)error;
@end
#endif
//
// QNPHAssetFile.m
// Pods
//
// Created by 何舒 on 15/10/21.
//
//
#import "QNPHAssetFile.h"
#if (defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 80000)
#import <AVFoundation/AVFoundation.h>
#import <Photos/Photos.h>
#import "QNResponseInfo.h"
@interface QNPHAssetFile ()
@property (nonatomic) PHAsset *phAsset;
@property (readonly) int64_t fileSize;
@property (readonly) int64_t fileModifyTime;
@property (nonatomic, strong) NSData *assetData;
@property (nonatomic, strong) NSURL *assetURL;
@property (nonatomic, readonly) NSString *filepath;
@property (nonatomic) NSFileHandle *file;
@property (nonatomic, strong) NSLock *lock;
@end
@implementation QNPHAssetFile
- (instancetype)init:(PHAsset *)phAsset error:(NSError *__autoreleasing *)error {
if (self = [super init]) {
NSDate *createTime = phAsset.creationDate;
int64_t t = 0;
if (createTime != nil) {
t = [createTime timeIntervalSince1970];
}
_fileModifyTime = t;
_phAsset = phAsset;
_filepath = [self getInfo];
_lock = [[NSLock alloc] init];
if (PHAssetMediaTypeVideo == self.phAsset.mediaType) {
NSError *error2 = nil;
NSDictionary *fileAttr = [[NSFileManager defaultManager] attributesOfItemAtPath:_filepath error:&error2];
if (error2 != nil) {
if (error != nil) {
*error = error2;
}
return self;
}
_fileSize = [fileAttr fileSize];
NSFileHandle *f = nil;
NSData *d = nil;
if (_fileSize > 16 * 1024 * 1024) {
f = [NSFileHandle fileHandleForReadingAtPath:_filepath];
if (f == nil) {
if (error != nil) {
*error = [[NSError alloc] initWithDomain:_filepath code:kQNFileError userInfo:nil];
}
return self;
}
} else {
d = [NSData dataWithContentsOfFile:_filepath options:NSDataReadingMappedIfSafe error:&error2];
if (error2 != nil) {
if (error != nil) {
*error = error2;
}
return self;
}
}
_file = f;
_assetData = d;
}
}
return self;
}
- (NSData *)read:(long)offset
size:(long)size
error:(NSError **)error {
NSData *data = nil;
@try {
[_lock lock];
if (_assetData != nil) {
data = [_assetData subdataWithRange:NSMakeRange(offset, (unsigned int)size)];
} else {
[_file seekToFileOffset:offset];
data = [_file readDataOfLength:size];
}
} @catch (NSException *exception) {
*error = [NSError errorWithDomain:NSCocoaErrorDomain code:kQNFileError userInfo:@{NSLocalizedDescriptionKey : exception.reason}];
NSLog(@"read file failed reason: %@ \n%@", exception.reason, exception.callStackSymbols);
} @finally {
[_lock unlock];
}
return data;
}
- (NSData *)readAllWithError:(NSError **)error {
return [self read:0 size:(long)_fileSize error:error];
}
- (void)close {
if (PHAssetMediaTypeVideo == self.phAsset.mediaType) {
if (_file != nil) {
[_file closeFile];
}
[[NSFileManager defaultManager] removeItemAtPath:_filepath error:nil];
}
}
- (NSString *)path {
return _filepath;
}
- (int64_t)modifyTime {
return _fileModifyTime;
}
- (int64_t)size {
return _fileSize;
}
- (NSString *)getInfo {
__block NSString *filePath = nil;
if (PHAssetMediaTypeImage == self.phAsset.mediaType) {
PHImageRequestOptions *options = [PHImageRequestOptions new];
options.version = PHImageRequestOptionsVersionCurrent;
options.deliveryMode = PHImageRequestOptionsDeliveryModeHighQualityFormat;
options.resizeMode = PHImageRequestOptionsResizeModeNone;
//不支持icloud上传
options.networkAccessAllowed = NO;
options.synchronous = YES;
[[PHImageManager defaultManager] requestImageDataForAsset:self.phAsset
options:options
resultHandler:^(NSData *imageData, NSString *dataUTI, UIImageOrientation orientation, NSDictionary *info) {
_assetData = imageData;
_fileSize = imageData.length;
_assetURL = [NSURL URLWithString:self.phAsset.localIdentifier];
filePath = _assetURL.path;
}];
} else if (PHAssetMediaTypeVideo == self.phAsset.mediaType) {
NSArray *assetResources = [PHAssetResource assetResourcesForAsset:self.phAsset];
PHAssetResource *resource;
for (PHAssetResource *assetRes in assetResources) {
if (assetRes.type == PHAssetResourceTypePairedVideo || assetRes.type == PHAssetResourceTypeVideo) {
resource = assetRes;
}
}
NSString *fileName = @"tempAssetVideo.mov";
if (resource.originalFilename) {
fileName = resource.originalFilename;
}
PHAssetResourceRequestOptions *options = [PHAssetResourceRequestOptions new];
//不支持icloud上传
options.networkAccessAllowed = NO;
NSString *PATH_VIDEO_FILE = [NSTemporaryDirectory() stringByAppendingPathComponent:fileName];
[[NSFileManager defaultManager] removeItemAtPath:PATH_VIDEO_FILE error:nil];
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
[[PHAssetResourceManager defaultManager] writeDataForAssetResource:resource toFile:[NSURL fileURLWithPath:PATH_VIDEO_FILE] options:options completionHandler:^(NSError *_Nullable error) {
if (error) {
filePath = nil;
} else {
filePath = PATH_VIDEO_FILE;
}
dispatch_semaphore_signal(semaphore);
}];
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
}
return filePath;
}
@end
#endif
//
// QNPHAssetResource.h
// QiniuSDK
//
// Created by 何舒 on 16/2/14.
// Copyright © 2016年 Qiniu. All rights reserved.
//
#import <Foundation/Foundation.h>
#import "QNFileDelegate.h"
#if (defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 90100)
@class PHAssetResource;
@interface QNPHAssetResource : NSObject <QNFileDelegate>
/**
* 打开指定文件
*
* @param path PHLivePhoto的PHAssetResource文件
* @param error 输出的错误信息
*
* @return 实例
*/
- (instancetype)init:(PHAssetResource *)phAssetResource
error:(NSError *__autoreleasing *)error;
@end
#endif
//
// QNPHAssetResource.m
// QiniuSDK
//
// Created by 何舒 on 16/2/14.
// Copyright © 2016年 Qiniu. All rights reserved.
//
#import "QNPHAssetResource.h"
#if (defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 90100)
#import <AVFoundation/AVFoundation.h>
#import <Photos/Photos.h>
enum {
kAMASSETMETADATA_PENDINGREADS = 1,
kAMASSETMETADATA_ALLFINISHED = 0
};
#import "QNResponseInfo.h"
@interface QNPHAssetResource ()
{
BOOL _hasGotInfo;
}
@property (nonatomic) PHAsset *phAsset;
@property (nonatomic) PHLivePhoto *phLivePhoto;
@property (nonatomic) PHAssetResource *phAssetResource;
@property (readonly) int64_t fileSize;
@property (readonly) int64_t fileModifyTime;
@property (nonatomic, strong) NSData *assetData;
@property (nonatomic, strong) NSURL *assetURL;
@property (nonatomic, strong) NSLock *lock;
@end
@implementation QNPHAssetResource
- (instancetype)init:(PHAssetResource *)phAssetResource
error:(NSError *__autoreleasing *)error {
if (self = [super init]) {
PHAsset *phasset = [PHAsset fetchAssetsWithBurstIdentifier:self.phAssetResource.assetLocalIdentifier options:nil][0];
NSDate *createTime = phasset.creationDate;
int64_t t = 0;
if (createTime != nil) {
t = [createTime timeIntervalSince1970];
}
_fileModifyTime = t;
_phAssetResource = phAssetResource;
_lock = [[NSLock alloc] init];
[self getInfo];
}
return self;
}
- (NSData *)read:(long)offset
size:(long)size
error:(NSError **)error {
NSData *data = nil;
@try {
[_lock lock];
NSRange subRange = NSMakeRange(offset, size);
if (!self.assetData) {
self.assetData = [self fetchDataFromAsset:self.phAssetResource error:error];
}
data = [self.assetData subdataWithRange:subRange];
} @catch (NSException *exception) {
*error = [NSError errorWithDomain:NSCocoaErrorDomain code:kQNFileError userInfo:@{NSLocalizedDescriptionKey : exception.reason}];
NSLog(@"read file failed reason: %@ \n%@", exception.reason, exception.callStackSymbols);
} @finally {
[_lock unlock];
}
return data;
}
- (NSData *)readAllWithError:(NSError **)error {
return [self read:0 size:(long)_fileSize error:error];
}
- (void)close {
}
- (NSString *)path {
return self.assetURL.path;
}
- (int64_t)modifyTime {
return _fileModifyTime;
}
- (int64_t)size {
return _fileSize;
}
- (void)getInfo {
if (!_hasGotInfo) {
_hasGotInfo = YES;
NSConditionLock *assetReadLock = [[NSConditionLock alloc] initWithCondition:kAMASSETMETADATA_PENDINGREADS];
NSString *pathToWrite = [NSTemporaryDirectory() stringByAppendingString:self.phAssetResource.originalFilename];
NSURL *localpath = [NSURL fileURLWithPath:pathToWrite];
PHAssetResourceRequestOptions *options = [PHAssetResourceRequestOptions new];
options.networkAccessAllowed = YES;
[[PHAssetResourceManager defaultManager] writeDataForAssetResource:self.phAssetResource toFile:localpath options:options completionHandler:^(NSError *_Nullable error) {
if (error == nil) {
AVURLAsset *urlAsset = [AVURLAsset URLAssetWithURL:localpath options:nil];
NSNumber *fileSize = nil;
[urlAsset.URL getResourceValue:&fileSize forKey:NSURLFileSizeKey error:nil];
_fileSize = [fileSize unsignedLongLongValue];
_assetURL = urlAsset.URL;
self.assetData = [NSData dataWithData:[NSData dataWithContentsOfURL:urlAsset.URL]];
} else {
NSLog(@"%@", error);
}
BOOL blHave = [[NSFileManager defaultManager] fileExistsAtPath:pathToWrite];
if (!blHave) {
return;
} else {
[[NSFileManager defaultManager] removeItemAtPath:pathToWrite error:nil];
}
[assetReadLock lock];
[assetReadLock unlockWithCondition:kAMASSETMETADATA_ALLFINISHED];
}];
[assetReadLock lockWhenCondition:kAMASSETMETADATA_ALLFINISHED];
[assetReadLock unlock];
assetReadLock = nil;
}
}
- (NSData *)fetchDataFromAsset:(PHAssetResource *)videoResource error:(NSError **)err {
__block NSData *tmpData = [NSData data];
__block NSError *innerError = *err;
NSConditionLock *assetReadLock = [[NSConditionLock alloc] initWithCondition:kAMASSETMETADATA_PENDINGREADS];
NSString *pathToWrite = [NSTemporaryDirectory() stringByAppendingString:videoResource.originalFilename];
NSURL *localpath = [NSURL fileURLWithPath:pathToWrite];
PHAssetResourceRequestOptions *options = [PHAssetResourceRequestOptions new];
options.networkAccessAllowed = YES;
[[PHAssetResourceManager defaultManager] writeDataForAssetResource:videoResource toFile:localpath options:options completionHandler:^(NSError *_Nullable error) {
if (error == nil) {
AVURLAsset *urlAsset = [AVURLAsset URLAssetWithURL:localpath options:nil];
NSData *videoData = [NSData dataWithContentsOfURL:urlAsset.URL];
tmpData = [NSData dataWithData:videoData];
} else {
innerError = error;
}
BOOL blHave = [[NSFileManager defaultManager] fileExistsAtPath:pathToWrite];
if (!blHave) {
return;
} else {
[[NSFileManager defaultManager] removeItemAtPath:pathToWrite error:nil];
}
[assetReadLock lock];
[assetReadLock unlockWithCondition:kAMASSETMETADATA_ALLFINISHED];
}];
[assetReadLock lockWhenCondition:kAMASSETMETADATA_ALLFINISHED];
[assetReadLock unlock];
assetReadLock = nil;
return tmpData;
}
@end
#endif
//
// QNSystem.h
// QiniuSDK
//
// Created by bailong on 15/10/13.
// Copyright © 2015年 Qiniu. All rights reserved.
//
#ifndef QNSystem_h
#define QNSystem_h
BOOL hasNSURLSession();
BOOL hasAts();
BOOL allowsArbitraryLoads();
BOOL isIpV6FullySupported();
#endif /* QNSystem_h */
//
// QNSystem.m
// QiniuSDK
//
// Created by bailong on 15/10/13.
// Copyright © 2015年 Qiniu. All rights reserved.
//
#import <Foundation/Foundation.h>
#if __IPHONE_OS_VERSION_MIN_REQUIRED
#import <MobileCoreServices/MobileCoreServices.h>
#import <UIKit/UIKit.h>
#else
#import <CoreServices/CoreServices.h>
#endif
BOOL hasNSURLSession() {
#if defined(__IPHONE_OS_VERSION_MAX_ALLOWED)
float sysVersion = [[[UIDevice currentDevice] systemVersion] floatValue];
if (sysVersion < 7.0) {
return NO;
}
#else
NSOperatingSystemVersion sysVersion = [[NSProcessInfo processInfo] operatingSystemVersion];
if (sysVersion.majorVersion < 10) {
return NO;
} else if (sysVersion.majorVersion == 10) {
return sysVersion.minorVersion >= 9;
}
#endif
return YES;
}
BOOL hasAts() {
#if defined(__IPHONE_OS_VERSION_MAX_ALLOWED)
float sysVersion = [[[UIDevice currentDevice] systemVersion] floatValue];
if (sysVersion < 9.0) {
return NO;
}
#else
NSOperatingSystemVersion sysVersion = [[NSProcessInfo processInfo] operatingSystemVersion];
if (sysVersion.majorVersion < 10) {
return NO;
} else if (sysVersion.majorVersion == 10) {
return sysVersion.minorVersion >= 11;
}
#endif
return YES;
}
BOOL allowsArbitraryLoads() {
if (!hasAts()) {
return YES;
}
// for unit test
NSDictionary* d = [[NSBundle mainBundle] infoDictionary];
if (d == nil || d.count == 0) {
return YES;
}
NSDictionary* sec = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"NSAppTransportSecurity"];
if (sec == nil) {
return NO;
}
NSNumber* ats = [sec objectForKey:@"NSAllowsArbitraryLoads"];
if (ats == nil) {
return NO;
}
return ats.boolValue;
}
BOOL isIpV6FullySupported() {
#if defined(__IPHONE_OS_VERSION_MAX_ALLOWED)
float sysVersion = [[[UIDevice currentDevice] systemVersion] floatValue];
if (sysVersion < 9.0) {
return NO;
}
#else
NSOperatingSystemVersion sysVersion = [[NSProcessInfo processInfo] operatingSystemVersion];
if (sysVersion.majorVersion < 10) {
return NO;
} else if (sysVersion.majorVersion == 10) {
return sysVersion.minorVersion >= 11;
}
#endif
return YES;
}
//
// QiniuSDK
//
// Created by bailong on 14-9-28.
// Copyright (c) 2014年 Qiniu. All rights reserved.
//
#import <Foundation/Foundation.h>
/**
* url safe base64 编码类, 对/ 做了处理
*/
@interface QNUrlSafeBase64 : NSObject
/**
* 字符串编码
*
* @param source 字符串
*
* @return base64 字符串
*/
+ (NSString *)encodeString:(NSString *)source;
/**
* 二进制数据编码
*
* @param source 二进制数据
*
* @return base64字符串
*/
+ (NSString *)encodeData:(NSData *)source;
/**
* 字符串解码
*
* @param base64 字符串
*
* @return 数据
*/
+ (NSData *)decodeString:(NSString *)data;
@end
//
// QiniuSDK
//
// Created by bailong on 14-9-28.
// Copyright (c) 2014年 Qiniu. All rights reserved.
//
#import <Foundation/Foundation.h>
#import "QNUrlSafeBase64.h"
#import "QN_GTM_Base64.h"
@implementation QNUrlSafeBase64
+ (NSString *)encodeString:(NSString *)sourceString {
NSData *data = [NSData dataWithBytes:[sourceString UTF8String] length:[sourceString lengthOfBytesUsingEncoding:NSUTF8StringEncoding]];
return [self encodeData:data];
}
+ (NSString *)encodeData:(NSData *)data {
return [QN_GTM_Base64 stringByWebSafeEncodingData:data padded:YES];
}
+ (NSData *)decodeString:(NSString *)data {
return [QN_GTM_Base64 webSafeDecodeString:data];
}
@end
//
// QNVersion.h
// QiniuSDK
//
// Created by bailong on 14-9-29.
// Copyright (c) 2014年 Qiniu. All rights reserved.
//
#import <Foundation/Foundation.h>
/**
* sdk 版本
*/
static const NSString *kQiniuVersion = @"7.3.0";
//
// GTMBase64.h
//
// Copyright 2006-2008 Google 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.
//
#import <Foundation/Foundation.h>
// GTMBase64
//
/// Helper for handling Base64 and WebSafeBase64 encodings
//
/// The webSafe methods use different character set and also the results aren't
/// always padded to a multiple of 4 characters. This is done so the resulting
/// data can be used in urls and url query arguments without needing any
/// encoding. You must use the webSafe* methods together, the data does not
/// interop with the RFC methods.
//
@interface QN_GTM_Base64 : NSObject
//
// Standard Base64 (RFC) handling
//
// encodeData:
//
/// Base64 encodes contents of the NSData object.
//
/// Returns:
/// A new autoreleased NSData with the encoded payload. nil for any error.
//
+ (NSData *)encodeData:(NSData *)data;
// decodeData:
//
/// Base64 decodes contents of the NSData object.
//
/// Returns:
/// A new autoreleased NSData with the decoded payload. nil for any error.
//
+ (NSData *)decodeData:(NSData *)data;
// encodeBytes:length:
//
/// Base64 encodes the data pointed at by |bytes|.
//
/// Returns:
/// A new autoreleased NSData with the encoded payload. nil for any error.
//
+ (NSData *)encodeBytes:(const void *)bytes length:(NSUInteger)length;
// decodeBytes:length:
//
/// Base64 decodes the data pointed at by |bytes|.
//
/// Returns:
/// A new autoreleased NSData with the encoded payload. nil for any error.
//
+ (NSData *)decodeBytes:(const void *)bytes length:(NSUInteger)length;
// stringByEncodingData:
//
/// Base64 encodes contents of the NSData object.
//
/// Returns:
/// A new autoreleased NSString with the encoded payload. nil for any error.
//
+ (NSString *)stringByEncodingData:(NSData *)data;
// stringByEncodingBytes:length:
//
/// Base64 encodes the data pointed at by |bytes|.
//
/// Returns:
/// A new autoreleased NSString with the encoded payload. nil for any error.
//
+ (NSString *)stringByEncodingBytes:(const void *)bytes length:(NSUInteger)length;
// decodeString:
//
/// Base64 decodes contents of the NSString.
//
/// Returns:
/// A new autoreleased NSData with the decoded payload. nil for any error.
//
+ (NSData *)decodeString:(NSString *)string;
//
// Modified Base64 encoding so the results can go onto urls.
//
// The changes are in the characters generated and also allows the result to
// not be padded to a multiple of 4.
// Must use the matching call to encode/decode, won't interop with the
// RFC versions.
//
// webSafeEncodeData:padded:
//
/// WebSafe Base64 encodes contents of the NSData object. If |padded| is YES
/// then padding characters are added so the result length is a multiple of 4.
//
/// Returns:
/// A new autoreleased NSData with the encoded payload. nil for any error.
//
+ (NSData *)webSafeEncodeData:(NSData *)data
padded:(BOOL)padded;
// webSafeDecodeData:
//
/// WebSafe Base64 decodes contents of the NSData object.
//
/// Returns:
/// A new autoreleased NSData with the decoded payload. nil for any error.
//
+ (NSData *)webSafeDecodeData:(NSData *)data;
// webSafeEncodeBytes:length:padded:
//
/// WebSafe Base64 encodes the data pointed at by |bytes|. If |padded| is YES
/// then padding characters are added so the result length is a multiple of 4.
//
/// Returns:
/// A new autoreleased NSData with the encoded payload. nil for any error.
//
+ (NSData *)webSafeEncodeBytes:(const void *)bytes
length:(NSUInteger)length
padded:(BOOL)padded;
// webSafeDecodeBytes:length:
//
/// WebSafe Base64 decodes the data pointed at by |bytes|.
//
/// Returns:
/// A new autoreleased NSData with the encoded payload. nil for any error.
//
+ (NSData *)webSafeDecodeBytes:(const void *)bytes length:(NSUInteger)length;
// stringByWebSafeEncodingData:padded:
//
/// WebSafe Base64 encodes contents of the NSData object. If |padded| is YES
/// then padding characters are added so the result length is a multiple of 4.
//
/// Returns:
/// A new autoreleased NSString with the encoded payload. nil for any error.
//
+ (NSString *)stringByWebSafeEncodingData:(NSData *)data
padded:(BOOL)padded;
// stringByWebSafeEncodingBytes:length:padded:
//
/// WebSafe Base64 encodes the data pointed at by |bytes|. If |padded| is YES
/// then padding characters are added so the result length is a multiple of 4.
//
/// Returns:
/// A new autoreleased NSString with the encoded payload. nil for any error.
//
+ (NSString *)stringByWebSafeEncodingBytes:(const void *)bytes
length:(NSUInteger)length
padded:(BOOL)padded;
// webSafeDecodeString:
//
/// WebSafe Base64 decodes contents of the NSString.
//
/// Returns:
/// A new autoreleased NSData with the decoded payload. nil for any error.
//
+ (NSData *)webSafeDecodeString:(NSString *)string;
@end
//
// GTMBase64.m
//
// Copyright 2006-2008 Google 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.
//
#import "QN_GTM_Base64.h"
static const char *kBase64EncodeChars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
static const char *kWebSafeBase64EncodeChars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
static const char kBase64PaddingChar = '=';
static const char kBase64InvalidChar = 99;
static const char kBase64DecodeChars[] = {
// This array was generated by the following code:
// #include <sys/time.h>
// #include <stdlib.h>
// #include <string.h>
// main()
// {
// static const char Base64[] =
// "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
// char *pos;
// int idx, i, j;
// printf(" ");
// for (i = 0; i < 255; i += 8) {
// for (j = i; j < i + 8; j++) {
// pos = strchr(Base64, j);
// if ((pos == NULL) || (j == 0))
// idx = 99;
// else
// idx = pos - Base64;
// if (idx == 99)
// printf(" %2d, ", idx);
// else
// printf(" %2d/*%c*/,", idx, j);
// }
// printf("\n ");
// }
// }
99, 99, 99, 99, 99, 99, 99, 99,
99, 99, 99, 99, 99, 99, 99, 99,
99, 99, 99, 99, 99, 99, 99, 99,
99, 99, 99, 99, 99, 99, 99, 99,
99, 99, 99, 99, 99, 99, 99, 99,
99, 99, 99, 62 /*+*/, 99, 99, 99, 63 /*/ */,
52 /*0*/, 53 /*1*/, 54 /*2*/, 55 /*3*/, 56 /*4*/, 57 /*5*/, 58 /*6*/, 59 /*7*/,
60 /*8*/, 61 /*9*/, 99, 99, 99, 99, 99, 99,
99, 0 /*A*/, 1 /*B*/, 2 /*C*/, 3 /*D*/, 4 /*E*/, 5 /*F*/, 6 /*G*/,
7 /*H*/, 8 /*I*/, 9 /*J*/, 10 /*K*/, 11 /*L*/, 12 /*M*/, 13 /*N*/, 14 /*O*/,
15 /*P*/, 16 /*Q*/, 17 /*R*/, 18 /*S*/, 19 /*T*/, 20 /*U*/, 21 /*V*/, 22 /*W*/,
23 /*X*/, 24 /*Y*/, 25 /*Z*/, 99, 99, 99, 99, 99,
99, 26 /*a*/, 27 /*b*/, 28 /*c*/, 29 /*d*/, 30 /*e*/, 31 /*f*/, 32 /*g*/,
33 /*h*/, 34 /*i*/, 35 /*j*/, 36 /*k*/, 37 /*l*/, 38 /*m*/, 39 /*n*/, 40 /*o*/,
41 /*p*/, 42 /*q*/, 43 /*r*/, 44 /*s*/, 45 /*t*/, 46 /*u*/, 47 /*v*/, 48 /*w*/,
49 /*x*/, 50 /*y*/, 51 /*z*/, 99, 99, 99, 99, 99,
99, 99, 99, 99, 99, 99, 99, 99,
99, 99, 99, 99, 99, 99, 99, 99,
99, 99, 99, 99, 99, 99, 99, 99,
99, 99, 99, 99, 99, 99, 99, 99,
99, 99, 99, 99, 99, 99, 99, 99,
99, 99, 99, 99, 99, 99, 99, 99,
99, 99, 99, 99, 99, 99, 99, 99,
99, 99, 99, 99, 99, 99, 99, 99,
99, 99, 99, 99, 99, 99, 99, 99,
99, 99, 99, 99, 99, 99, 99, 99,
99, 99, 99, 99, 99, 99, 99, 99,
99, 99, 99, 99, 99, 99, 99, 99,
99, 99, 99, 99, 99, 99, 99, 99,
99, 99, 99, 99, 99, 99, 99, 99,
99, 99, 99, 99, 99, 99, 99, 99,
99, 99, 99, 99, 99, 99, 99, 99};
static const char kWebSafeBase64DecodeChars[] = {
// This array was generated by the following code:
// #include <sys/time.h>
// #include <stdlib.h>
// #include <string.h>
// main()
// {
// static const char Base64[] =
// "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
// char *pos;
// int idx, i, j;
// printf(" ");
// for (i = 0; i < 255; i += 8) {
// for (j = i; j < i + 8; j++) {
// pos = strchr(Base64, j);
// if ((pos == NULL) || (j == 0))
// idx = 99;
// else
// idx = pos - Base64;
// if (idx == 99)
// printf(" %2d, ", idx);
// else
// printf(" %2d/*%c*/,", idx, j);
// }
// printf("\n ");
// }
// }
99, 99, 99, 99, 99, 99, 99, 99,
99, 99, 99, 99, 99, 99, 99, 99,
99, 99, 99, 99, 99, 99, 99, 99,
99, 99, 99, 99, 99, 99, 99, 99,
99, 99, 99, 99, 99, 99, 99, 99,
99, 99, 99, 99, 99, 62 /*-*/, 99, 99,
52 /*0*/, 53 /*1*/, 54 /*2*/, 55 /*3*/, 56 /*4*/, 57 /*5*/, 58 /*6*/, 59 /*7*/,
60 /*8*/, 61 /*9*/, 99, 99, 99, 99, 99, 99,
99, 0 /*A*/, 1 /*B*/, 2 /*C*/, 3 /*D*/, 4 /*E*/, 5 /*F*/, 6 /*G*/,
7 /*H*/, 8 /*I*/, 9 /*J*/, 10 /*K*/, 11 /*L*/, 12 /*M*/, 13 /*N*/, 14 /*O*/,
15 /*P*/, 16 /*Q*/, 17 /*R*/, 18 /*S*/, 19 /*T*/, 20 /*U*/, 21 /*V*/, 22 /*W*/,
23 /*X*/, 24 /*Y*/, 25 /*Z*/, 99, 99, 99, 99, 63 /*_*/,
99, 26 /*a*/, 27 /*b*/, 28 /*c*/, 29 /*d*/, 30 /*e*/, 31 /*f*/, 32 /*g*/,
33 /*h*/, 34 /*i*/, 35 /*j*/, 36 /*k*/, 37 /*l*/, 38 /*m*/, 39 /*n*/, 40 /*o*/,
41 /*p*/, 42 /*q*/, 43 /*r*/, 44 /*s*/, 45 /*t*/, 46 /*u*/, 47 /*v*/, 48 /*w*/,
49 /*x*/, 50 /*y*/, 51 /*z*/, 99, 99, 99, 99, 99,
99, 99, 99, 99, 99, 99, 99, 99,
99, 99, 99, 99, 99, 99, 99, 99,
99, 99, 99, 99, 99, 99, 99, 99,
99, 99, 99, 99, 99, 99, 99, 99,
99, 99, 99, 99, 99, 99, 99, 99,
99, 99, 99, 99, 99, 99, 99, 99,
99, 99, 99, 99, 99, 99, 99, 99,
99, 99, 99, 99, 99, 99, 99, 99,
99, 99, 99, 99, 99, 99, 99, 99,
99, 99, 99, 99, 99, 99, 99, 99,
99, 99, 99, 99, 99, 99, 99, 99,
99, 99, 99, 99, 99, 99, 99, 99,
99, 99, 99, 99, 99, 99, 99, 99,
99, 99, 99, 99, 99, 99, 99, 99,
99, 99, 99, 99, 99, 99, 99, 99,
99, 99, 99, 99, 99, 99, 99, 99};
// Tests a character to see if it's a whitespace character.
//
// Returns:
// YES if the character is a whitespace character.
// NO if the character is not a whitespace character.
//
BOOL QN_IsSpace(unsigned char c) {
// we use our own mapping here because we don't want anything w/ locale
// support.
static BOOL kSpaces[256] = {
0, 0, 0, 0, 0, 0, 0, 0, 0, 1, // 0-9
1, 1, 1, 1, 0, 0, 0, 0, 0, 0, // 10-19
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 20-29
0, 0, 1, 0, 0, 0, 0, 0, 0, 0, // 30-39
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 40-49
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 50-59
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 60-69
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 70-79
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 80-89
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 90-99
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 100-109
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 110-119
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 120-129
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 130-139
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 140-149
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 150-159
1, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 160-169
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 170-179
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 180-189
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 190-199
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 200-209
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 210-219
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 220-229
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 230-239
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 240-249
0, 0, 0, 0, 0, 1, // 250-255
};
return kSpaces[c];
}
// Calculate how long the data will be once it's base64 encoded.
//
// Returns:
// The guessed encoded length for a source length
//
NSUInteger QN_CalcEncodedLength(NSUInteger srcLen, BOOL padded) {
NSUInteger intermediate_result = 8 * srcLen + 5;
NSUInteger len = intermediate_result / 6;
if (padded) {
len = ((len + 3) / 4) * 4;
}
return len;
}
// Tries to calculate how long the data will be once it's base64 decoded.
// Unlike the above, this is always an upperbound, since the source data
// could have spaces and might end with the padding characters on them.
//
// Returns:
// The guessed decoded length for a source length
//
NSUInteger QN_GuessDecodedLength(NSUInteger srcLen) {
return (srcLen + 3) / 4 * 3;
}
@interface QN_GTM_Base64 (PrivateMethods)
+ (NSData *)baseEncode:(const void *)bytes
length:(NSUInteger)length
charset:(const char *)charset
padded:(BOOL)padded;
+ (NSData *)baseDecode:(const void *)bytes
length:(NSUInteger)length
charset:(const char *)charset
requirePadding:(BOOL)requirePadding;
+ (NSUInteger)baseEncode:(const char *)srcBytes
srcLen:(NSUInteger)srcLen
destBytes:(char *)destBytes
destLen:(NSUInteger)destLen
charset:(const char *)charset
padded:(BOOL)padded;
+ (NSUInteger)baseDecode:(const char *)srcBytes
srcLen:(NSUInteger)srcLen
destBytes:(char *)destBytes
destLen:(NSUInteger)destLen
charset:(const char *)charset
requirePadding:(BOOL)requirePadding;
@end
@implementation QN_GTM_Base64
//
// Standard Base64 (RFC) handling
//
+ (NSData *)encodeData:(NSData *)data {
return [self baseEncode:[data bytes]
length:[data length]
charset:kBase64EncodeChars
padded:YES];
}
+ (NSData *)decodeData:(NSData *)data {
return [self baseDecode:[data bytes]
length:[data length]
charset:kBase64DecodeChars
requirePadding:YES];
}
+ (NSData *)encodeBytes:(const void *)bytes length:(NSUInteger)length {
return [self baseEncode:bytes
length:length
charset:kBase64EncodeChars
padded:YES];
}
+ (NSData *)decodeBytes:(const void *)bytes length:(NSUInteger)length {
return [self baseDecode:bytes
length:length
charset:kBase64DecodeChars
requirePadding:YES];
}
+ (NSString *)stringByEncodingData:(NSData *)data {
NSString *result = nil;
NSData *converted = [self baseEncode:[data bytes]
length:[data length]
charset:kBase64EncodeChars
padded:YES];
if (converted) {
result = [[NSString alloc] initWithData:converted
encoding:NSASCIIStringEncoding];
}
return result;
}
+ (NSString *)stringByEncodingBytes:(const void *)bytes length:(NSUInteger)length {
NSString *result = nil;
NSData *converted = [self baseEncode:bytes
length:length
charset:kBase64EncodeChars
padded:YES];
if (converted) {
result = [[NSString alloc] initWithData:converted
encoding:NSASCIIStringEncoding];
}
return result;
}
+ (NSData *)decodeString:(NSString *)string {
NSData *result = nil;
NSData *data = [string dataUsingEncoding:NSASCIIStringEncoding];
if (data) {
result = [self baseDecode:[data bytes]
length:[data length]
charset:kBase64DecodeChars
requirePadding:YES];
}
return result;
}
//
// Modified Base64 encoding so the results can go onto urls.
//
// The changes are in the characters generated and also the result isn't
// padded to a multiple of 4.
// Must use the matching call to encode/decode, won't interop with the
// RFC versions.
//
+ (NSData *)webSafeEncodeData:(NSData *)data
padded:(BOOL)padded {
return [self baseEncode:[data bytes]
length:[data length]
charset:kWebSafeBase64EncodeChars
padded:padded];
}
+ (NSData *)webSafeDecodeData:(NSData *)data {
return [self baseDecode:[data bytes]
length:[data length]
charset:kWebSafeBase64DecodeChars
requirePadding:NO];
}
+ (NSData *)webSafeEncodeBytes:(const void *)bytes
length:(NSUInteger)length
padded:(BOOL)padded {
return [self baseEncode:bytes
length:length
charset:kWebSafeBase64EncodeChars
padded:padded];
}
+ (NSData *)webSafeDecodeBytes:(const void *)bytes length:(NSUInteger)length {
return [self baseDecode:bytes
length:length
charset:kWebSafeBase64DecodeChars
requirePadding:NO];
}
+ (NSString *)stringByWebSafeEncodingData:(NSData *)data
padded:(BOOL)padded {
NSString *result = nil;
NSData *converted = [self baseEncode:[data bytes]
length:[data length]
charset:kWebSafeBase64EncodeChars
padded:padded];
if (converted) {
result = [[NSString alloc] initWithData:converted
encoding:NSASCIIStringEncoding];
}
return result;
}
+ (NSString *)stringByWebSafeEncodingBytes:(const void *)bytes
length:(NSUInteger)length
padded:(BOOL)padded {
NSString *result = nil;
NSData *converted = [self baseEncode:bytes
length:length
charset:kWebSafeBase64EncodeChars
padded:padded];
if (converted) {
result = [[NSString alloc] initWithData:converted
encoding:NSASCIIStringEncoding];
}
return result;
}
+ (NSData *)webSafeDecodeString:(NSString *)string {
NSData *result = nil;
NSData *data = [string dataUsingEncoding:NSASCIIStringEncoding];
if (data) {
result = [self baseDecode:[data bytes]
length:[data length]
charset:kWebSafeBase64DecodeChars
requirePadding:NO];
}
return result;
}
@end
@implementation QN_GTM_Base64 (PrivateMethods)
//
// baseEncode:length:charset:padded:
//
// Does the common lifting of creating the dest NSData. it creates & sizes the
// data for the results. |charset| is the characters to use for the encoding
// of the data. |padding| controls if the encoded data should be padded to a
// multiple of 4.
//
// Returns:
// an autorelease NSData with the encoded data, nil if any error.
//
+ (NSData *)baseEncode:(const void *)bytes
length:(NSUInteger)length
charset:(const char *)charset
padded:(BOOL)padded {
// how big could it be?
NSUInteger maxLength = QN_CalcEncodedLength(length, padded);
// make space
NSMutableData *result = [NSMutableData data];
[result setLength:maxLength];
// do it
NSUInteger finalLength = [self baseEncode:bytes
srcLen:length
destBytes:[result mutableBytes]
destLen:[result length]
charset:charset
padded:padded];
if (finalLength) {
// _GTMDevAssert(finalLength == maxLength, @"how did we calc the length wrong?");
} else {
// shouldn't happen, this means we ran out of space
result = nil;
}
return result;
}
//
// baseDecode:length:charset:requirePadding:
//
// Does the common lifting of creating the dest NSData. it creates & sizes the
// data for the results. |charset| is the characters to use for the decoding
// of the data.
//
// Returns:
// an autorelease NSData with the decoded data, nil if any error.
//
//
+ (NSData *)baseDecode:(const void *)bytes
length:(NSUInteger)length
charset:(const char *)charset
requirePadding:(BOOL)requirePadding {
// could try to calculate what it will end up as
NSUInteger maxLength = QN_GuessDecodedLength(length);
// make space
NSMutableData *result = [NSMutableData data];
[result setLength:maxLength];
// do it
NSUInteger finalLength = [self baseDecode:bytes
srcLen:length
destBytes:[result mutableBytes]
destLen:[result length]
charset:charset
requirePadding:requirePadding];
if (finalLength) {
if (finalLength != maxLength) {
// resize down to how big it was
[result setLength:finalLength];
}
} else {
// either an error in the args, or we ran out of space
result = nil;
}
return result;
}
//
// baseEncode:srcLen:destBytes:destLen:charset:padded:
//
// Encodes the buffer into the larger. returns the length of the encoded
// data, or zero for an error.
// |charset| is the characters to use for the encoding
// |padded| tells if the result should be padded to a multiple of 4.
//
// Returns:
// the length of the encoded data. zero if any error.
//
+ (NSUInteger)baseEncode:(const char *)srcBytes
srcLen:(NSUInteger)srcLen
destBytes:(char *)destBytes
destLen:(NSUInteger)destLen
charset:(const char *)charset
padded:(BOOL)padded {
if (!srcLen || !destLen || !srcBytes || !destBytes) {
return 0;
}
char *curDest = destBytes;
const unsigned char *curSrc = (const unsigned char *)(srcBytes);
// Three bytes of data encodes to four characters of cyphertext.
// So we can pump through three-byte chunks atomically.
while (srcLen > 2) {
// space?
// _GTMDevAssert(destLen >= 4, @"our calc for encoded length was wrong");
curDest[0] = charset[curSrc[0] >> 2];
curDest[1] = charset[((curSrc[0] & 0x03) << 4) + (curSrc[1] >> 4)];
curDest[2] = charset[((curSrc[1] & 0x0f) << 2) + (curSrc[2] >> 6)];
curDest[3] = charset[curSrc[2] & 0x3f];
curDest += 4;
curSrc += 3;
srcLen -= 3;
destLen -= 4;
}
// now deal with the tail (<=2 bytes)
switch (srcLen) {
case 0:
// Nothing left; nothing more to do.
break;
case 1:
// One byte left: this encodes to two characters, and (optionally)
// two pad characters to round out the four-character cypherblock.
// _GTMDevAssert(destLen >= 2, @"our calc for encoded length was wrong");
curDest[0] = charset[curSrc[0] >> 2];
curDest[1] = charset[(curSrc[0] & 0x03) << 4];
curDest += 2;
destLen -= 2;
if (padded) {
// _GTMDevAssert(destLen >= 2, @"our calc for encoded length was wrong");
curDest[0] = kBase64PaddingChar;
curDest[1] = kBase64PaddingChar;
curDest += 2;
}
break;
case 2:
// Two bytes left: this encodes to three characters, and (optionally)
// one pad character to round out the four-character cypherblock.
// _GTMDevAssert(destLen >= 3, @"our calc for encoded length was wrong");
curDest[0] = charset[curSrc[0] >> 2];
curDest[1] = charset[((curSrc[0] & 0x03) << 4) + (curSrc[1] >> 4)];
curDest[2] = charset[(curSrc[1] & 0x0f) << 2];
curDest += 3;
destLen -= 3;
if (padded) {
// _GTMDevAssert(destLen >= 1, @"our calc for encoded length was wrong");
curDest[0] = kBase64PaddingChar;
curDest += 1;
}
break;
}
// return the length
return (curDest - destBytes);
}
//
// baseDecode:srcLen:destBytes:destLen:charset:requirePadding:
//
// Decodes the buffer into the larger. returns the length of the decoded
// data, or zero for an error.
// |charset| is the character decoding buffer to use
//
// Returns:
// the length of the encoded data. zero if any error.
//
+ (NSUInteger)baseDecode:(const char *)srcBytes
srcLen:(NSUInteger)srcLen
destBytes:(char *)destBytes
destLen:(NSUInteger)destLen
charset:(const char *)charset
requirePadding:(BOOL)requirePadding {
if (!srcLen || !destLen || !srcBytes || !destBytes) {
return 0;
}
int decode;
NSUInteger destIndex = 0;
int state = 0;
char ch = 0;
while (srcLen-- && (ch = *srcBytes++) != 0) {
if (QN_IsSpace(ch)) // Skip whitespace
continue;
if (ch == kBase64PaddingChar)
break;
decode = charset[(unsigned int)ch];
if (decode == kBase64InvalidChar)
return 0;
// Four cyphertext characters decode to three bytes.
// Therefore we can be in one of four states.
switch (state) {
case 0:
// We're at the beginning of a four-character cyphertext block.
// This sets the high six bits of the first byte of the
// plaintext block.
// _GTMDevAssert(destIndex < destLen, @"our calc for decoded length was wrong");
destBytes[destIndex] = decode << 2;
state = 1;
break;
case 1:
// We're one character into a four-character cyphertext block.
// This sets the low two bits of the first plaintext byte,
// and the high four bits of the second plaintext byte.
// _GTMDevAssert((destIndex+1) < destLen, @"our calc for decoded length was wrong");
destBytes[destIndex] |= decode >> 4;
destBytes[destIndex + 1] = (decode & 0x0f) << 4;
destIndex++;
state = 2;
break;
case 2:
// We're two characters into a four-character cyphertext block.
// This sets the low four bits of the second plaintext
// byte, and the high two bits of the third plaintext byte.
// However, if this is the end of data, and those two
// bits are zero, it could be that those two bits are
// leftovers from the encoding of data that had a length
// of two mod three.
// _GTMDevAssert((destIndex+1) < destLen, @"our calc for decoded length was wrong");
destBytes[destIndex] |= decode >> 2;
destBytes[destIndex + 1] = (decode & 0x03) << 6;
destIndex++;
state = 3;
break;
case 3:
// We're at the last character of a four-character cyphertext block.
// This sets the low six bits of the third plaintext byte.
// _GTMDevAssert(destIndex < destLen, @"our calc for decoded length was wrong");
destBytes[destIndex] |= decode;
destIndex++;
state = 0;
break;
}
}
// We are done decoding Base-64 chars. Let's see if we ended
// on a byte boundary, and/or with erroneous trailing characters.
if (ch == kBase64PaddingChar) { // We got a pad char
if ((state == 0) || (state == 1)) {
return 0; // Invalid '=' in first or second position
}
if (srcLen == 0) {
if (state == 2) { // We run out of input but we still need another '='
return 0;
}
// Otherwise, we are in state 3 and only need this '='
} else {
if (state == 2) { // need another '='
while ((ch = *srcBytes++) && (srcLen-- > 0)) {
if (!QN_IsSpace(ch))
break;
}
if (ch != kBase64PaddingChar) {
return 0;
}
}
// state = 1 or 2, check if all remain padding is space
while ((ch = *srcBytes++) && (srcLen-- > 0)) {
if (!QN_IsSpace(ch)) {
return 0;
}
}
}
} else {
// We ended by seeing the end of the string.
if (requirePadding) {
// If we require padding, then anything but state 0 is an error.
if (state != 0) {
return 0;
}
} else {
// Make sure we have no partial bytes lying around. Note that we do not
// require trailing '=', so states 2 and 3 are okay too.
if (state == 1) {
return 0;
}
}
}
// If then next piece of output was valid and got written to it means we got a
// very carefully crafted input that appeared valid but contains some trailing
// bits past the real length, so just toss the thing.
if ((destIndex < destLen) &&
(destBytes[destIndex] != 0)) {
return 0;
}
return destIndex;
}
@end
#import <Foundation/Foundation.h>
@class QNResponseInfo;
typedef void (^QNInternalProgressBlock)(long long totalBytesWritten, long long totalBytesExpectedToWrite);
typedef void (^QNCompleteBlock)(QNResponseInfo *info, NSDictionary *resp);
typedef BOOL (^QNCancelBlock)(void);
/**
* Http 客户端接口
*/
@protocol QNHttpDelegate <NSObject>
- (void)multipartPost:(NSString *)url
withData:(NSData *)data
withParams:(NSDictionary *)params
withFileName:(NSString *)key
withMimeType:(NSString *)mime
withTaskIdentifier:(NSString *)taskIdentifier
withCompleteBlock:(QNCompleteBlock)completeBlock
withProgressBlock:(QNInternalProgressBlock)progressBlock
withCancelBlock:(QNCancelBlock)cancelBlock
withAccess:(NSString *)access;
- (void)post:(NSString *)url
withData:(NSData *)data
withParams:(NSDictionary *)params
withHeaders:(NSDictionary *)headers
withTaskIdentifier:(NSString *)taskIdentifier
withCompleteBlock:(QNCompleteBlock)completeBlock
withProgressBlock:(QNInternalProgressBlock)progressBlock
withCancelBlock:(QNCancelBlock)cancelBlock
withAccess:(NSString *)access;
- (void)invalidateSessionWithIdentifier:(NSString *)identifier;
@end
//
// QNResponseInfo.h
// QiniuSDK
//
// Created by bailong on 14/10/2.
// Copyright (c) 2014年 Qiniu. All rights reserved.
//
#import <Foundation/Foundation.h>
/**
* 中途取消的状态码
*/
extern const int kQNRequestCancelled;
/**
* 网络错误状态码
*/
extern const int kQNNetworkError;
/**
* 错误参数状态码
*/
extern const int kQNInvalidArgument;
/**
* 0 字节文件或数据
*/
extern const int kQNZeroDataSize;
/**
* 错误token状态码
*/
extern const int kQNInvalidToken;
/**
* 读取文件错误状态码
*/
extern const int kQNFileError;
/**
* 上传完成后返回的状态信息
*/
@interface QNResponseInfo : NSObject
/**
* 状态码
*/
@property (readonly) int statusCode;
/**
* 七牛服务器生成的请求ID,用来跟踪请求信息,如果使用过程中出现问题,请反馈此ID
*/
@property (nonatomic, copy, readonly) NSString *reqId;
/**
* 七牛服务器内部跟踪记录
*/
@property (nonatomic, copy, readonly) NSString *xlog;
/**
* cdn服务器内部跟踪记录
*/
@property (nonatomic, copy, readonly) NSString *xvia;
/**
* 错误信息,出错时请反馈此记录
*/
@property (nonatomic, copy, readonly) NSError *error;
/**
* 服务器域名
*/
@property (nonatomic, copy, readonly) NSString *host;
/**
* 请求消耗的时间,单位 秒
*/
@property (nonatomic, readonly) double duration;
/**
* 服务器IP
*/
@property (nonatomic, readonly) NSString *serverIp;
/**
* 客户端id
*/
@property (nonatomic, readonly) NSString *id;
/**
* 时间戳
*/
@property (readonly) UInt64 timeStamp;
/**
* 网络类型
*/
//@property (nonatomic, readonly) NSString *networkType;
/**
* 是否取消
*/
@property (nonatomic, readonly, getter=isCancelled) BOOL canceled;
/**
* 成功的请求
*/
@property (nonatomic, readonly, getter=isOK) BOOL ok;
/**
* 是否网络错误
*/
@property (nonatomic, readonly, getter=isConnectionBroken) BOOL broken;
/**
* 是否需要重试,内部使用
*/
@property (nonatomic, readonly) BOOL couldRetry;
/**
* 是否需要换备用server,内部使用
*/
@property (nonatomic, readonly) BOOL needSwitchServer;
/**
* 是否为 七牛响应
*/
@property (nonatomic, readonly, getter=isNotQiniu) BOOL notQiniu;
/**
* 工厂函数,内部使用
*
* @return 取消的实例
*/
+ (instancetype)cancel;
/**
* 工厂函数,内部使用
*
* @param desc 错误参数描述
*
* @return 错误参数实例
*/
+ (instancetype)responseInfoWithInvalidArgument:(NSString *)desc;
/**
* 工厂函数,内部使用
*
* @param desc 错误token描述
*
* @return 错误token实例
*/
+ (instancetype)responseInfoWithInvalidToken:(NSString *)desc;
/**
* 工厂函数,内部使用
*
* @param error 错误信息
* @param host 服务器域名
* @param duration 请求完成时间,单位秒
*
* @return 网络错误实例
*/
+ (instancetype)responseInfoWithNetError:(NSError *)error
host:(NSString *)host
duration:(double)duration;
/**
* 工厂函数,内部使用
*
* @param error 错误信息
*
* @return 文件错误实例
*/
+ (instancetype)responseInfoWithFileError:(NSError *)error;
/**
* 工厂函数,内部使用
*
* @return 文件错误实例
*/
+ (instancetype)responseInfoOfZeroData:(NSString *)path;
/**
* 构造函数
*
* @param status 状态码
* @param reqId 七牛服务器请求id
* @param xlog 七牛服务器记录
* @param body 服务器返回内容
* @param host 服务器域名
* @param duration 请求完成时间,单位秒
*
* @return 实例
*/
- (instancetype)init:(int)status
withReqId:(NSString *)reqId
withXLog:(NSString *)xlog
withXVia:(NSString *)xvia
withHost:(NSString *)host
withIp:(NSString *)ip
withDuration:(double)duration
withBody:(NSData *)body;
@end
//
// QNResponseInfo.m
// QiniuSDK
//
// Created by bailong on 14/10/2.
// Copyright (c) 2014年 Qiniu. All rights reserved.
//
#import "QNResponseInfo.h"
#import "QNUserAgent.h"
#import "QNVersion.h"
const int kQNZeroDataSize = -6;
const int kQNInvalidToken = -5;
const int kQNFileError = -4;
const int kQNInvalidArgument = -3;
const int kQNRequestCancelled = -2;
const int kQNNetworkError = -1;
/**
https://developer.apple.com/library/ios/documentation/Cocoa/Reference/Foundation/Miscellaneous/Foundation_Constants/index.html#//apple_ref/doc/constant_group/URL_Loading_System_Error_Codes
NSURLErrorUnknown = -1,
NSURLErrorCancelled = -999,
NSURLErrorBadURL = -1000,
NSURLErrorTimedOut = -1001,
NSURLErrorUnsupportedURL = -1002,
NSURLErrorCannotFindHost = -1003,
NSURLErrorCannotConnectToHost = -1004,
NSURLErrorDataLengthExceedsMaximum = -1103,
NSURLErrorNetworkConnectionLost = -1005,
NSURLErrorDNSLookupFailed = -1006,
NSURLErrorHTTPTooManyRedirects = -1007,
NSURLErrorResourceUnavailable = -1008,
NSURLErrorNotConnectedToInternet = -1009,
NSURLErrorRedirectToNonExistentLocation = -1010,
NSURLErrorBadServerResponse = -1011,
NSURLErrorUserCancelledAuthentication = -1012,
NSURLErrorUserAuthenticationRequired = -1013,
NSURLErrorZeroByteResource = -1014,
NSURLErrorCannotDecodeRawData = -1015,
NSURLErrorCannotDecodeContentData = -1016,
NSURLErrorCannotParseResponse = -1017,
NSURLErrorInternationalRoamingOff = -1018,
NSURLErrorCallIsActive = -1019,
NSURLErrorDataNotAllowed = -1020,
NSURLErrorRequestBodyStreamExhausted = -1021,
NSURLErrorFileDoesNotExist = -1100,
NSURLErrorFileIsDirectory = -1101,
NSURLErrorNoPermissionsToReadFile = -1102,
NSURLErrorSecureConnectionFailed = -1200,
NSURLErrorServerCertificateHasBadDate = -1201,
NSURLErrorServerCertificateUntrusted = -1202,
NSURLErrorServerCertificateHasUnknownRoot = -1203,
NSURLErrorServerCertificateNotYetValid = -1204,
NSURLErrorClientCertificateRejected = -1205,
NSURLErrorClientCertificateRequired = -1206,
NSURLErrorCannotLoadFromNetwork = -2000,
NSURLErrorCannotCreateFile = -3000,
NSURLErrorCannotOpenFile = -3001,
NSURLErrorCannotCloseFile = -3002,
NSURLErrorCannotWriteToFile = -3003,
NSURLErrorCannotRemoveFile = -3004,
NSURLErrorCannotMoveFile = -3005,
NSURLErrorDownloadDecodingFailedMidStream = -3006,
NSURLErrorDownloadDecodingFailedToComplete = -3007
*/
static QNResponseInfo *cancelledInfo = nil;
static NSString *domain = @"qiniu.com";
@implementation QNResponseInfo
+ (instancetype)cancel {
return [[QNResponseInfo alloc] initWithCancelled];
}
+ (instancetype)responseInfoWithInvalidArgument:(NSString *)text {
return [[QNResponseInfo alloc] initWithStatus:kQNInvalidArgument errorDescription:text];
}
+ (instancetype)responseInfoWithInvalidToken:(NSString *)text {
return [[QNResponseInfo alloc] initWithStatus:kQNInvalidToken errorDescription:text];
}
+ (instancetype)responseInfoWithNetError:(NSError *)error host:(NSString *)host duration:(double)duration {
int code = kQNNetworkError;
if (error != nil) {
code = (int)error.code;
}
return [[QNResponseInfo alloc] initWithStatus:code error:error host:host duration:duration];
}
+ (instancetype)responseInfoWithFileError:(NSError *)error {
return [[QNResponseInfo alloc] initWithStatus:kQNFileError error:error];
}
+ (instancetype)responseInfoOfZeroData:(NSString *)path {
NSString *desc;
if (path == nil) {
desc = @"data size is 0";
} else {
desc = [[NSString alloc] initWithFormat:@"file %@ size is 0", path];
}
return [[QNResponseInfo alloc] initWithStatus:kQNZeroDataSize errorDescription:desc];
}
- (instancetype)initWithCancelled {
return [self initWithStatus:kQNRequestCancelled errorDescription:@"cancelled by user"];
}
- (instancetype)initWithStatus:(int)status
error:(NSError *)error {
return [self initWithStatus:status error:error host:nil duration:0];
}
- (instancetype)initWithStatus:(int)status
error:(NSError *)error
host:(NSString *)host
duration:(double)duration {
if (self = [super init]) {
_statusCode = status;
_error = error;
_host = host;
_duration = duration;
_id = [QNUserAgent sharedInstance].id;
_timeStamp = [[NSDate date] timeIntervalSince1970];
}
return self;
}
- (instancetype)initWithStatus:(int)status
errorDescription:(NSString *)text {
NSError *error = [[NSError alloc] initWithDomain:domain code:status userInfo:@{ @"error" : text }];
return [self initWithStatus:status error:error];
}
- (instancetype)init:(int)status
withReqId:(NSString *)reqId
withXLog:(NSString *)xlog
withXVia:(NSString *)xvia
withHost:(NSString *)host
withIp:(NSString *)ip
withDuration:(double)duration
withBody:(NSData *)body {
if (self = [super init]) {
_statusCode = status;
_reqId = reqId;
_xlog = xlog;
_xvia = xvia;
_host = host;
_duration = duration;
_serverIp = ip;
_id = [QNUserAgent sharedInstance].id;
_timeStamp = [[NSDate date] timeIntervalSince1970];
if (status != 200) {
if (body == nil) {
_error = [[NSError alloc] initWithDomain:domain code:_statusCode userInfo:nil];
} else {
NSError *tmp;
NSDictionary *uInfo = [NSJSONSerialization JSONObjectWithData:body options:NSJSONReadingMutableLeaves error:&tmp];
if (tmp != nil) {
// 出现错误时,如果信息是非UTF8编码会失败,返回nil
NSString *str = [[NSString alloc] initWithData:body encoding:NSUTF8StringEncoding];
if (str == nil) {
str = @"";
}
uInfo = @{ @"error" : str };
}
_error = [[NSError alloc] initWithDomain:domain code:_statusCode userInfo:uInfo];
}
} else if (body == nil || body.length == 0) {
NSDictionary *uInfo = @{ @"error" : @"no response json" };
_error = [[NSError alloc] initWithDomain:domain code:_statusCode userInfo:uInfo];
}
}
return self;
}
- (NSString *)description {
return [NSString stringWithFormat:@"<%@= id: %@, ver: %@, status: %d, requestId: %@, xlog: %@, xvia: %@, host: %@ ip: %@ duration: %f s time: %llu error: %@>", NSStringFromClass([self class]), _id, kQiniuVersion, _statusCode, _reqId, _xlog, _xvia, _host, _serverIp, _duration, _timeStamp, _error];
}
- (BOOL)isCancelled {
return _statusCode == kQNRequestCancelled || _statusCode == -999;
}
- (BOOL)isNotQiniu {
return (_statusCode >= 200 && _statusCode < 500) && _reqId == nil;
}
- (BOOL)isOK {
return _statusCode == 200 && _error == nil && _reqId != nil;
}
- (BOOL)isConnectionBroken {
// reqId is nill means the server is not qiniu
return _statusCode == kQNNetworkError || (_statusCode < -1000 && _statusCode != -1003);
}
- (BOOL)needSwitchServer {
return _statusCode == kQNNetworkError || (_statusCode < -1000 && _statusCode != -1003) || (_statusCode / 100 == 5 && _statusCode != 579);
}
- (BOOL)couldRetry {
return (_statusCode >= 500 && _statusCode < 600 && _statusCode != 579) || _statusCode == kQNNetworkError || _statusCode == 996 || _statusCode == 406 || (_statusCode == 200 && _error != nil) || _statusCode < -1000 || self.isNotQiniu;
}
@end
#import "QNHttpDelegate.h"
#import <Foundation/Foundation.h>
#import "QNConfiguration.h"
#if (defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 70000) || (defined(__MAC_OS_X_VERSION_MAX_ALLOWED) && __MAC_OS_X_VERSION_MAX_ALLOWED >= 1090)
@interface QNSessionManager : NSObject <QNHttpDelegate>
- (instancetype)initWithProxy:(NSDictionary *)proxyDict
timeout:(UInt32)timeout
urlConverter:(QNUrlConvert)converter;
- (void)multipartPost:(NSString *)url
withData:(NSData *)data
withParams:(NSDictionary *)params
withFileName:(NSString *)key
withMimeType:(NSString *)mime
withTaskIdentifier:(NSString *)taskIdentifier
withCompleteBlock:(QNCompleteBlock)completeBlock
withProgressBlock:(QNInternalProgressBlock)progressBlock
withCancelBlock:(QNCancelBlock)cancelBlock
withAccess:(NSString *)access;
- (void)post:(NSString *)url
withData:(NSData *)data
withParams:(NSDictionary *)params
withHeaders:(NSDictionary *)headers
withTaskIdentifier:(NSString *)taskIdentifier
withCompleteBlock:(QNCompleteBlock)completeBlock
withProgressBlock:(QNInternalProgressBlock)progressBlock
withCancelBlock:(QNCancelBlock)cancelBlock
withAccess:(NSString *)access;
- (void)get:(NSString *)url
withHeaders:(NSDictionary *)headers
withCompleteBlock:(QNCompleteBlock)completeBlock;
- (void)invalidateSessionWithIdentifier:(NSString *)identifier;
@end
#endif
//
// QNHttpManager.m
// QiniuSDK
//
// Created by bailong on 14/10/1.
// Copyright (c) 2014年 Qiniu. All rights reserved.
//
#import "QNAsyncRun.h"
#import "QNConfiguration.h"
#import "QNResponseInfo.h"
#import "QNSessionManager.h"
#include "QNSystem.h"
#import "QNUserAgent.h"
#if (defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 70000) || (defined(__MAC_OS_X_VERSION_MAX_ALLOWED) && __MAC_OS_X_VERSION_MAX_ALLOWED >= 1090)
@interface QNProgessDelegate : NSObject <NSURLSessionDataDelegate>
@property (nonatomic, strong) QNInternalProgressBlock progressBlock;
@property (nonatomic, strong) NSURLSessionUploadTask *task;
@property (nonatomic, strong) QNCancelBlock cancelBlock;
- (instancetype)initWithProgress:(QNInternalProgressBlock)progressBlock;
@end
static NSURL *buildUrl(NSString *host, NSNumber *port, NSString *path) {
port = port == nil ? [NSNumber numberWithInt:80] : port;
NSString *p = [[NSString alloc] initWithFormat:@"http://%@:%@%@", host, port, path];
return [[NSURL alloc] initWithString:p];
}
static BOOL needRetry(NSHTTPURLResponse *httpResponse, NSError *error) {
if (error != nil) {
return error.code < -1000;
}
if (httpResponse == nil) {
return YES;
}
int status = (int)httpResponse.statusCode;
return status >= 500 && status < 600 && status != 579;
}
@implementation QNProgessDelegate
- (instancetype)initWithProgress:(QNInternalProgressBlock)progressBlock {
if (self = [super init]) {
_progressBlock = progressBlock;
}
return self;
}
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task
didSendBodyData:(int64_t)bytesSent
totalBytesSent:(int64_t)totalBytesSent
totalBytesExpectedToSend:(int64_t)totalBytesExpectedToSend {
if (_progressBlock) {
_progressBlock(totalBytesSent, totalBytesExpectedToSend);
}
if (_cancelBlock && _cancelBlock()) {
[_task cancel];
}
}
@end
@interface QNSessionManager ()
@property UInt32 timeout;
@property (nonatomic, strong) QNUrlConvert converter;
@property bool noProxy;
@property (nonatomic, strong) NSDictionary *proxyDict;
@property (nonatomic, strong) NSOperationQueue *delegateQueue;
@property (nonatomic, strong) NSMutableArray *sessionArray;
@end
@implementation QNSessionManager
- (instancetype)initWithProxy:(NSDictionary *)proxyDict
timeout:(UInt32)timeout
urlConverter:(QNUrlConvert)converter {
if (self = [super init]) {
if (proxyDict != nil) {
_noProxy = NO;
_proxyDict = proxyDict;
} else {
_noProxy = YES;
}
_delegateQueue = [[NSOperationQueue alloc] init];
_timeout = timeout;
_converter = converter;
_sessionArray = [NSMutableArray array];
}
return self;
}
- (instancetype)init {
return [self initWithProxy:nil timeout:60 urlConverter:nil];
}
+ (QNResponseInfo *)buildResponseInfo:(NSHTTPURLResponse *)response
withError:(NSError *)error
withDuration:(double)duration
withResponse:(NSData *)body
withHost:(NSString *)host
withIp:(NSString *)ip {
QNResponseInfo *info;
if (response) {
int status = (int)[response statusCode];
NSDictionary *headers = [response allHeaderFields];
NSString *reqId = headers[@"X-Reqid"];
NSString *xlog = headers[@"X-Log"];
NSString *xvia = headers[@"X-Via"];
if (xvia == nil) {
xvia = headers[@"X-Px"];
}
if (xvia == nil) {
xvia = headers[@"Fw-Via"];
}
info = [[QNResponseInfo alloc] init:status withReqId:reqId withXLog:xlog withXVia:xvia withHost:host withIp:ip withDuration:duration withBody:body];
} else {
info = [QNResponseInfo responseInfoWithNetError:error host:host duration:duration];
}
return info;
}
- (void)sendRequest:(NSMutableURLRequest *)request
withTaskIdentifier:(NSString *)taskIdentifier
withCompleteBlock:(QNCompleteBlock)completeBlock
withProgressBlock:(QNInternalProgressBlock)progressBlock
withCancelBlock:(QNCancelBlock)cancelBlock
withAccess:(NSString *)access {
__block NSDate *startTime = [NSDate date];
NSString *domain = request.URL.host;
NSString *u = request.URL.absoluteString;
NSURL *url = request.URL;
NSArray *ips = nil;
if (_converter != nil) {
url = [[NSURL alloc] initWithString:_converter(u)];
request.URL = url;
domain = url.host;
}
[self sendRequest2:request withTaskIdentifier:taskIdentifier withCompleteBlock:completeBlock withProgressBlock:progressBlock withCancelBlock:cancelBlock withIpArray:ips withIndex:0 withDomain:domain withRetryTimes:3 withStartTime:startTime withAccess:access];
}
- (void)sendRequest2:(NSMutableURLRequest *)request
withTaskIdentifier:(NSString *)taskIdentifier
withCompleteBlock:(QNCompleteBlock)completeBlock
withProgressBlock:(QNInternalProgressBlock)progressBlock
withCancelBlock:(QNCancelBlock)cancelBlock
withIpArray:(NSArray *)ips
withIndex:(int)index
withDomain:(NSString *)domain
withRetryTimes:(int)times
withStartTime:(NSDate *)startTime
withAccess:(NSString *)access {
NSURL *url = request.URL;
__block NSString *ip = nil;
if (ips != nil) {
ip = [ips objectAtIndex:(index % ips.count)];
NSString *path = url.path;
if (path == nil || [@"" isEqualToString:path]) {
path = @"/";
}
url = buildUrl(ip, url.port, path);
[request setValue:domain forHTTPHeaderField:@"Host"];
}
request.URL = url;
[request setTimeoutInterval:_timeout];
[request setValue:[[QNUserAgent sharedInstance] getUserAgent:access] forHTTPHeaderField:@"User-Agent"];
[request setValue:nil forHTTPHeaderField:@"Accept-Language"];
if (progressBlock == nil) {
progressBlock = ^(long long totalBytesWritten, long long totalBytesExpectedToWrite) {
};
}
QNInternalProgressBlock progressBlock2 = ^(long long totalBytesWritten, long long totalBytesExpectedToWrite) {
progressBlock(totalBytesWritten, totalBytesExpectedToWrite);
};
__block QNProgessDelegate *delegate = [[QNProgessDelegate alloc] initWithProgress:nil];
delegate.progressBlock = progressBlock2;
NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
if (_proxyDict) {
configuration.connectionProxyDictionary = _proxyDict;
}
__block NSURLSession *session = [NSURLSession sessionWithConfiguration:configuration delegate:delegate delegateQueue:_delegateQueue];
if (taskIdentifier) {
[_sessionArray addObject:@{@"taskIdentifier":taskIdentifier,@"session":session}];
}
NSURLSessionUploadTask *uploadTask = [session uploadTaskWithRequest:request fromData:nil completionHandler:^(NSData *_Nullable data, NSURLResponse *_Nullable response, NSError *_Nullable error) {
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response;
double duration = [[NSDate date] timeIntervalSinceDate:startTime];
QNResponseInfo *info;
NSDictionary *resp = nil;
if (_converter != nil && _noProxy && (index + 1 < ips.count || times > 0) && needRetry(httpResponse, error)) {
[self sendRequest2:request withTaskIdentifier:taskIdentifier withCompleteBlock:completeBlock withProgressBlock:progressBlock withCancelBlock:cancelBlock withIpArray:ips withIndex:index + 1 withDomain:domain withRetryTimes:times - 1 withStartTime:startTime withAccess:access];
return;
}
if (error == nil) {
info = [QNSessionManager buildResponseInfo:httpResponse withError:nil withDuration:duration withResponse:data withHost:domain withIp:ip];
if (info.isOK) {
NSError *tmp;
resp = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableLeaves error:&tmp];
}
} else {
info = [QNSessionManager buildResponseInfo:httpResponse withError:error withDuration:duration withResponse:data withHost:domain withIp:ip];
}
delegate.task = nil;
delegate.cancelBlock = nil;
delegate.progressBlock = nil;
[self finishSession:session];
completeBlock(info, resp);
}];
delegate.task = uploadTask;
delegate.cancelBlock = cancelBlock;
[uploadTask resume];
}
- (void)multipartPost:(NSString *)url
withData:(NSData *)data
withParams:(NSDictionary *)params
withFileName:(NSString *)key
withMimeType:(NSString *)mime
withTaskIdentifier:(NSString *)taskIdentifier
withCompleteBlock:(QNCompleteBlock)completeBlock
withProgressBlock:(QNInternalProgressBlock)progressBlock
withCancelBlock:(QNCancelBlock)cancelBlock
withAccess:(NSString *)access {
NSURL *URL = [[NSURL alloc] initWithString:url];
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:URL cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:30];
request.HTTPMethod = @"POST";
NSString *boundary = @"werghnvt54wef654rjuhgb56trtg34tweuyrgf";
request.allHTTPHeaderFields = @{
@"Content-Type" : [NSString stringWithFormat:@"multipart/form-data; boundary=%@", boundary]
};
NSMutableData *postData = [[NSMutableData alloc] init];
for (NSString *paramsKey in params) {
NSString *pair = [NSString stringWithFormat:@"--%@\r\nContent-Disposition: form-data; name=\"%@\"\r\n\r\n", boundary, paramsKey];
[postData appendData:[pair dataUsingEncoding:NSUTF8StringEncoding]];
id value = [params objectForKey:paramsKey];
if ([value isKindOfClass:[NSString class]]) {
[postData appendData:[value dataUsingEncoding:NSUTF8StringEncoding]];
} else if ([value isKindOfClass:[NSData class]]) {
[postData appendData:value];
}
[postData appendData:[@"\r\n" dataUsingEncoding:NSUTF8StringEncoding]];
}
NSString *filePair = [NSString stringWithFormat:@"--%@\r\nContent-Disposition: form-data; name=\"%@\"; filename=\"%@\"\nContent-Type:%@\r\n\r\n", boundary, @"file", key, mime];
[postData appendData:[filePair dataUsingEncoding:NSUTF8StringEncoding]];
[postData appendData:data];
[postData appendData:[[NSString stringWithFormat:@"\r\n--%@--\r\n", boundary] dataUsingEncoding:NSUTF8StringEncoding]];
request.HTTPBody = postData;
[request setValue:[NSString stringWithFormat:@"%lu", (unsigned long)postData.length] forHTTPHeaderField:@"Content-Length"];
[self sendRequest:request withTaskIdentifier:taskIdentifier withCompleteBlock:completeBlock withProgressBlock:progressBlock withCancelBlock:cancelBlock
withAccess:access];
}
- (void)post:(NSString *)url
withData:(NSData *)data
withParams:(NSDictionary *)params
withHeaders:(NSDictionary *)headers
withTaskIdentifier:(NSString *)taskIdentifier
withCompleteBlock:(QNCompleteBlock)completeBlock
withProgressBlock:(QNInternalProgressBlock)progressBlock
withCancelBlock:(QNCancelBlock)cancelBlock
withAccess:(NSString *)access {
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:[[NSURL alloc] initWithString:url]];
if (headers) {
[request setAllHTTPHeaderFields:headers];
}
[request setHTTPMethod:@"POST"];
if (params) {
[request setValuesForKeysWithDictionary:params];
}
[request setHTTPBody:data];
QNAsyncRun(^{
[self sendRequest:request
withTaskIdentifier:(NSString *)taskIdentifier
withCompleteBlock:completeBlock
withProgressBlock:progressBlock
withCancelBlock:cancelBlock
withAccess:access];
});
}
- (void)get:(NSString *)url
withHeaders:(NSDictionary *)headers
withCompleteBlock:(QNCompleteBlock)completeBlock {
QNAsyncRun(^{
NSURL *URL = [NSURL URLWithString:url];
NSURLRequest *request = [NSURLRequest requestWithURL:URL];
NSURLSessionDataTask *dataTask = [[NSURLSession sharedSession] dataTaskWithRequest:request completionHandler:^(NSData *_Nullable data, NSURLResponse *_Nullable response, NSError *_Nullable error) {
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response;
NSData *s = [@"{}" dataUsingEncoding:NSUTF8StringEncoding];
NSDictionary *resp = nil;
QNResponseInfo *info;
if (error == nil) {
info = [QNSessionManager buildResponseInfo:httpResponse withError:nil withDuration:0 withResponse:s withHost:@"" withIp:@""];
if (info.isOK) {
NSError *jsonError;
id unMarshel = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingAllowFragments error:&jsonError];
if (jsonError) {
info = [QNSessionManager buildResponseInfo:httpResponse withError:jsonError withDuration:0 withResponse:s withHost:@"" withIp:@""];
} else if ([unMarshel isKindOfClass:[NSDictionary class]]) {
resp = unMarshel;
}
}
} else {
info = [QNSessionManager buildResponseInfo:httpResponse withError:error withDuration:0 withResponse:s withHost:@"" withIp:@""];
}
completeBlock(info, resp);
}];
[dataTask resume];
});
}
- (void)finishSession:(NSURLSession *)session {
for (int i = 0; i < _sessionArray.count; i++) {
NSDictionary *sessionInfo = _sessionArray[i];
if (sessionInfo[@"session"] == session) {
[session finishTasksAndInvalidate];
[_sessionArray removeObject:sessionInfo];
break;
}
}
}
- (void)invalidateSessionWithIdentifier:(NSString *)identifier {
for (int i = 0; i < _sessionArray.count; i++) {
NSDictionary *sessionInfo = _sessionArray[i];
if ([sessionInfo[@"taskIdentifier"] isEqualToString:identifier]) {
NSURLSession *session = sessionInfo[@"session"];
[session invalidateAndCancel];
[_sessionArray removeObject:sessionInfo];
}
}
}
@end
#endif
//
// QNUserAgent.h
// QiniuSDK
//
// Created by bailong on 14-9-29.
// Copyright (c) 2014年 Qiniu. All rights reserved.
//
#import <Foundation/Foundation.h>
/**
* UserAgent
*
*/
@interface QNUserAgent : NSObject
/**
* 用户id
*/
@property (copy, nonatomic, readonly) NSString *id;
/**
* UserAgent 字串
*/
- (NSString *)description;
/**
* UserAgent + AK 字串
*/
- (NSString *)getUserAgent:(NSString *)access;
/**
* 单例
*/
+ (instancetype)sharedInstance;
@end
//
// QNUserAgent.m
// QiniuSDK
//
// Created by bailong on 14-9-29.
// Copyright (c) 2014年 Qiniu. All rights reserved.
//
#import <Foundation/Foundation.h>
#if __IPHONE_OS_VERSION_MIN_REQUIRED
#import <MobileCoreServices/MobileCoreServices.h>
#import <UIKit/UIKit.h>
#else
#import <CoreServices/CoreServices.h>
#endif
#import "QNUserAgent.h"
#import "QNVersion.h"
static NSString *qn_clientId(void) {
#if __IPHONE_OS_VERSION_MIN_REQUIRED
NSString *s = [[[UIDevice currentDevice] identifierForVendor] UUIDString];
if (s == nil) {
s = @"simulator";
}
return s;
#else
long long now_timestamp = [[NSDate date] timeIntervalSince1970] * 1000;
int r = arc4random() % 1000;
return [NSString stringWithFormat:@"%lld%u", now_timestamp, r];
#endif
}
static NSString *qn_userAgent(NSString *id, NSString *ak) {
#if __IPHONE_OS_VERSION_MIN_REQUIRED
return [NSString stringWithFormat:@"QiniuObject-C/%@ (%@; iOS %@; %@; %@)", kQiniuVersion, [[UIDevice currentDevice] model], [[UIDevice currentDevice] systemVersion], id, ak];
#else
return [NSString stringWithFormat:@"QiniuObject-C/%@ (Mac OS X %@; %@; %@)", kQiniuVersion, [[NSProcessInfo processInfo] operatingSystemVersionString], id, ak];
#endif
}
@interface QNUserAgent ()
@property (nonatomic) NSString *ua;
@end
@implementation QNUserAgent
- (NSString *)description {
return _ua;
}
- (instancetype)init {
if (self = [super init]) {
_id = qn_clientId();
}
return self;
}
/**
* UserAgent
*/
- (NSString *)getUserAgent:(NSString *)access {
NSString *ak;
if (access == nil || access.length == 0) {
ak = @"-";
} else {
ak = access;
}
return qn_userAgent(_id, ak);
}
/**
* 单例
*/
+ (instancetype)sharedInstance {
static QNUserAgent *sharedInstance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedInstance = [[self alloc] init];
});
return sharedInstance;
}
@end
//
// QiniuSDK.h
// QiniuSDK
//
// Created by bailong on 14-9-28.
// Copyright (c) 2014年 Qiniu. All rights reserved.
//
#import <Foundation/Foundation.h>
#import "QNConfiguration.h"
#import "QNFileRecorder.h"
#import "QNPipeline.h"
#import "QNResponseInfo.h"
#import "QNUploadManager.h"
#import "QNUploadOption.h"
#import "QNUrlSafeBase64.h"
#import "QNUploadInfoReporter.h"
//
// QNFileRecorder.h
// QiniuSDK
//
// Created by bailong on 14/10/5.
// Copyright (c) 2014年 Qiniu. All rights reserved.
//
#import "QNRecorderDelegate.h"
#import <Foundation/Foundation.h>
/**
* 将上传记录保存到文件系统中
*/
@interface QNFileRecorder : NSObject <QNRecorderDelegate>
/**
* 用指定保存的目录进行初始化
*
* @param directory 目录
* @param error 输出的错误信息
*
* @return 实例
*/
+ (instancetype)fileRecorderWithFolder:(NSString *)directory
error:(NSError *__autoreleasing *)error;
/**
* 用指定保存的目录,以及是否进行base64编码进行初始化,
*
* @param directory 目录
* @param encode 为避免因为特殊字符或含有/,无法保存持久化记录,故用此参数指定是否要base64编码
* @param error 输出错误信息
*
* @return 实例
*/
+ (instancetype)fileRecorderWithFolder:(NSString *)directory
encodeKey:(BOOL)encode
error:(NSError *__autoreleasing *)error;
/**
* 从外部手动删除记录,如无特殊需求,不建议使用
*
* @param key 持久化记录key
* @param dir 目录
* @param encode 是否encode
*/
+ (void)removeKey:(NSString *)key
directory:(NSString *)dir
encodeKey:(BOOL)encode;
@end
//
// QNFileRecorder.m
// QiniuSDK
//
// Created by bailong on 14/10/5.
// Copyright (c) 2014年 Qiniu. All rights reserved.
//
#import "QNFileRecorder.h"
#import "QNUrlSafeBase64.h"
@interface QNFileRecorder ()
@property (copy, readonly) NSString *directory;
@property BOOL encode;
@end
@implementation QNFileRecorder
- (NSString *)pathOfKey:(NSString *)key {
return [QNFileRecorder pathJoin:key path:_directory];
}
+ (NSString *)pathJoin:(NSString *)key
path:(NSString *)path {
return [[NSString alloc] initWithFormat:@"%@/%@", path, key];
}
+ (instancetype)fileRecorderWithFolder:(NSString *)directory
error:(NSError *__autoreleasing *)perror {
return [QNFileRecorder fileRecorderWithFolder:directory encodeKey:false error:perror];
}
+ (instancetype)fileRecorderWithFolder:(NSString *)directory
encodeKey:(BOOL)encode
error:(NSError *__autoreleasing *)perror {
NSError *error;
[[NSFileManager defaultManager] createDirectoryAtPath:directory withIntermediateDirectories:YES attributes:nil error:&error];
if (error != nil) {
if (perror) {
*perror = error;
}
return nil;
}
return [[QNFileRecorder alloc] initWithFolder:directory encodeKey:encode];
}
- (instancetype)initWithFolder:(NSString *)directory encodeKey:(BOOL)encode {
if (self = [super init]) {
_directory = directory;
_encode = encode;
}
return self;
}
- (NSError *)set:(NSString *)key
data:(NSData *)value {
NSError *error;
if (_encode) {
key = [QNUrlSafeBase64 encodeString:key];
}
[value writeToFile:[self pathOfKey:key] options:NSDataWritingAtomic error:&error];
return error;
}
- (NSData *)get:(NSString *)key {
if (_encode) {
key = [QNUrlSafeBase64 encodeString:key];
}
return [NSData dataWithContentsOfFile:[self pathOfKey:key]];
}
- (NSError *)del:(NSString *)key {
NSError *error;
if (_encode) {
key = [QNUrlSafeBase64 encodeString:key];
}
[[NSFileManager defaultManager] removeItemAtPath:[self pathOfKey:key] error:&error];
return error;
}
+ (void)removeKey:(NSString *)key
directory:(NSString *)dir
encodeKey:(BOOL)encode {
if (encode) {
key = [QNUrlSafeBase64 encodeString:key];
}
NSError *error;
NSString *path = [QNFileRecorder pathJoin:key path:dir];
[[NSFileManager defaultManager] removeItemAtPath:path error:&error];
if (error) {
NSLog(@"%s,%@", __func__, error);
}
}
- (NSString *)description {
return [NSString stringWithFormat:@"<%@: %p, dir: %@>", NSStringFromClass([self class]), self, _directory];
}
@end
//
// QNRecorderDelegate.h
// QiniuSDK
//
// Created by bailong on 14/10/5.
// Copyright (c) 2014年 Qiniu. All rights reserved.
//
#import <Foundation/Foundation.h>
/**
* 为持久化上传记录,根据上传的key以及文件名 生成持久化的记录key
*
* @param uploadKey 上传的key
* @param filePath 文件名
*
* @return 根据uploadKey, filepath 算出的记录key
*/
typedef NSString * (^QNRecorderKeyGenerator)(NSString *uploadKey, NSString *filePath);
/**
* 持久化记录接口,可以实现将记录持久化到文件,数据库等
*/
@protocol QNRecorderDelegate <NSObject>
/**
* 保存记录
*
* @param key 持久化记录的key
* @param value 持久化记录上传状态信息
*
* @return 错误信息,成功为nil
*/
- (NSError *)set:(NSString *)key
data:(NSData *)value;
/**
* 取出保存的持久化上传状态信息
*
* @param key 持久化记录key
*
* @return 上传中间状态信息
*/
- (NSData *)get:(NSString *)key;
/**
* 删除持久化记录,一般在上传成功后自动调用
*
* @param key 持久化记录key
*
* @return 错误信息,成功为nil
*/
- (NSError *)del:(NSString *)key;
@end
//
// QNConcurrentResumeUpload.h
// QiniuSDK
//
// Created by WorkSpace_Sun on 2019/7/15.
// Copyright © 2019 Qiniu. All rights reserved.
//
#import <Foundation/Foundation.h>
#import "QNHttpDelegate.h"
#import "QNUploadManager.h"
#import "QNFileDelegate.h"
@class QNUpToken;
@class QNUploadOption;
@class QNConfiguration;
@interface QNConcurrentResumeUpload : NSObject
- (instancetype)initWithFile:(id<QNFileDelegate>)file
withKey:(NSString *)key
withToken:(QNUpToken *)token
withRecorder:(id<QNRecorderDelegate>)recorder
withRecorderKey:(NSString *)recorderKey
withHttpManager:(id<QNHttpDelegate>)http
withCompletionHandler:(QNUpCompletionHandler)block
withOption:(QNUploadOption *)option
withConfiguration:(QNConfiguration *)config;
- (void)run;
@end
//
// QNConcurrentResumeUpload.m
// QiniuSDK
//
// Created by WorkSpace_Sun on 2019/7/15.
// Copyright © 2019 Qiniu. All rights reserved.
//
#import "QNConcurrentResumeUpload.h"
#import "QNUploadOption.h"
#import "QNConfiguration.h"
#import "QNUpToken.h"
#import "QNResponseInfo.h"
#import "QNUrlSafeBase64.h"
#import "QNCrc32.h"
#import "QNUploadInfoReporter.h"
@interface QNConcurrentTask: NSObject
@property (nonatomic, assign) int index; // block index in the file
@property (nonatomic, assign) UInt32 size; // total size of the block
@property (atomic, assign) UInt32 uploadedSize; // uploaded size of the block
@property (nonatomic, copy) NSString *context;
@property (nonatomic, assign) BOOL isTaskCompleted;
- (instancetype)init __attribute__((unavailable("use concurrentTaskWithBlockIndex:blockSize: instead.")));
@end
@implementation QNConcurrentTask
+ (instancetype)concurrentTaskWithBlockIndex:(int)index blockSize:(UInt32)size {
return [[QNConcurrentTask alloc] initWithBlockIndex:index blockSize:size];
}
- (instancetype)initWithBlockIndex:(int)index blockSize:(UInt32)size
{
self = [super init];
if (self) {
_isTaskCompleted = NO;
_uploadedSize = 0;
_size = size;
_index = index;
}
return self;
}
@end
@interface QNConcurrentTaskQueue: NSObject
@property (nonatomic, strong) id<QNFileDelegate> file;
@property (nonatomic, assign) UInt32 totalSize; // 文件总大小
@property (nonatomic, assign) UInt32 concurrentTaskCount; // 用户设置的最大并发任务数量
@property (nonatomic, copy) NSArray *recordInfo; // 续传信息
@property (nonatomic, strong) NSMutableArray<QNConcurrentTask *> *taskQueueArray; // block 任务队列
@property (nonatomic, assign) BOOL isAllCompleted; // completed
@property (nonatomic, assign) float totalPercent; // 上传总进度
@property (nonatomic, assign) UInt32 taskQueueCount; // 实际并发任务数量
@property (atomic, assign) int nextTaskIndex; // 下一个任务的index
@property (nonatomic, assign) BOOL isConcurrentTaskError; // error
@property (nonatomic, strong) QNResponseInfo *info; // errorInfo if error
@property (nonatomic, strong) NSDictionary *resp; // errorResp if error
@property (nonatomic, strong) NSLock *lock;
- (instancetype)init __attribute__((unavailable("use taskQueueWithFile:totalSize:concurrentTaskCount:recordInfo: instead.")));
@end
@implementation QNConcurrentTaskQueue
+ (instancetype)taskQueueWithFile:(id<QNFileDelegate>)file totalSize:(UInt32)totalSize concurrentTaskCount:(UInt32)concurrentTaskCount recordInfo:(NSArray *)recordInfo {
return [[QNConcurrentTaskQueue alloc] initWithFile:file totalSize:totalSize concurrentTaskCount:concurrentTaskCount recordInfo:recordInfo];
}
- (instancetype)initWithFile:(id<QNFileDelegate>)file totalSize:(UInt32)totalSize concurrentTaskCount:(UInt32)concurrentTaskCount recordInfo:(NSArray *)recordInfo
{
self = [super init];
if (self) {
_file = file;
_totalSize = totalSize;
_concurrentTaskCount = concurrentTaskCount;
_recordInfo = recordInfo;
_taskQueueArray = [NSMutableArray array];
_isConcurrentTaskError = NO;
_totalPercent = 0;
_nextTaskIndex = 0;
_lock = [[NSLock alloc] init];
[self initTaskQueue];
}
return self;
}
- (void)initTaskQueue {
// add recover task
if (_recordInfo.count > 0) {
for (NSDictionary *info in _recordInfo) {
int block_index = [info[@"block_index"] intValue];
UInt32 block_size = [info[@"block_size"] unsignedIntValue];
NSString *context = info[@"context"];
QNConcurrentTask *recoveryTask = [QNConcurrentTask concurrentTaskWithBlockIndex:block_index blockSize:block_size];
recoveryTask.uploadedSize = block_size;
recoveryTask.context = context;
recoveryTask.isTaskCompleted = YES;
[_taskQueueArray addObject:recoveryTask];
}
}
int blockCount = _totalSize % kQNBlockSize == 0 ? _totalSize / kQNBlockSize : _totalSize / kQNBlockSize + 1;
_taskQueueCount = blockCount > _concurrentTaskCount ? _concurrentTaskCount : blockCount;
for (int i = 0; i < blockCount; i++) {
BOOL isTaskExisted = NO;
for (int j = 0; j < _taskQueueArray.count; j++) {
if (_taskQueueArray[j].index == i) {
isTaskExisted = YES;
break;
}
}
if (!isTaskExisted) {
UInt32 left = _totalSize - i * kQNBlockSize;
UInt32 blockSize = left < kQNBlockSize ? left : kQNBlockSize;
QNConcurrentTask *task = [QNConcurrentTask concurrentTaskWithBlockIndex:i blockSize:blockSize];
[_taskQueueArray addObject:task];
}
}
}
- (QNConcurrentTask *)getNextTask {
QNConcurrentTask *nextTask = nil;
[_lock lock];
while (_nextTaskIndex < _taskQueueArray.count) {
QNConcurrentTask *task = _taskQueueArray[_nextTaskIndex];
_nextTaskIndex++;
if (!task.isTaskCompleted) {
nextTask = task;
break;
}
}
[_lock unlock];
return nextTask;
}
- (void)buildErrorWithInfo:(QNResponseInfo *)info resp:(NSDictionary *)resp {
if (_isConcurrentTaskError) return;
_isConcurrentTaskError = YES;
_info = info;
_resp = resp;
}
- (BOOL)completeTask:(QNConcurrentTask *)task withContext:(NSString *)context {
task.uploadedSize = task.size;
task.context = context;
task.isTaskCompleted = YES;
[_lock lock];
BOOL hasMore = _nextTaskIndex < _taskQueueArray.count;
[_lock unlock];
return hasMore;
}
- (NSArray *)getRecordInfo {
NSMutableArray *infoArray = [NSMutableArray array];
for (QNConcurrentTask *task in _taskQueueArray) {
if (task.isTaskCompleted) {
[infoArray addObject:@{
@"block_index":@(task.index),
@"block_size":@(task.size),
@"context":task.context
}];
}
}
return infoArray;
}
- (NSArray *)getContexts {
NSArray *sortedTaskQueueArray = [_taskQueueArray sortedArrayUsingComparator:^NSComparisonResult(id _Nonnull obj1, id _Nonnull obj2) {
QNConcurrentTask *task1 = obj1;
QNConcurrentTask *task2 = obj2;
return task1.index > task2.index;
}];
NSMutableArray *contextArray = [NSMutableArray arrayWithCapacity:sortedTaskQueueArray.count];
for (QNConcurrentTask *task in sortedTaskQueueArray) {
if (task.isTaskCompleted) {
[contextArray addObject:task.context];
}
}
return contextArray;
}
- (BOOL)isAllCompleted {
BOOL isAllTaskCompleted = YES;
for (QNConcurrentTask *task in _taskQueueArray) {
if (!task.isTaskCompleted) {
isAllTaskCompleted = NO;
break;
}
}
return isAllTaskCompleted && !_isConcurrentTaskError && !_info && !_resp;
}
- (float)totalPercent {
long long totalUploadSize = 0;
for (QNConcurrentTask *task in _taskQueueArray) {
totalUploadSize += task.uploadedSize;
}
return totalUploadSize / (float)_totalSize < 0.95 ? totalUploadSize / (float)_totalSize : 0.95;
}
@end
@interface QNConcurrentResumeUpload ()
@property (nonatomic, strong) id<QNHttpDelegate> httpManager;
@property (nonatomic, copy) NSString *key;
@property (nonatomic, copy) NSDictionary *headers;
@property (nonatomic, strong) QNUploadOption *option;
@property (nonatomic, strong) QNUpToken *token;
@property (nonatomic, strong) QNUpCompletionHandler complete;
@property (nonatomic, strong) id<QNRecorderDelegate> recorder;
@property (nonatomic, copy) NSString *recorderKey;
@property (nonatomic, strong) QNConfiguration *config;
@property (nonatomic, strong) id<QNFileDelegate> file;
@property (nonatomic, copy) NSString *access; //AK
@property (nonatomic, strong) dispatch_group_t uploadGroup;
@property (nonatomic, strong) QNConcurrentTaskQueue *taskQueue;
@property (nonatomic, copy) NSString *taskIdentifier;
@property (nonatomic, assign) UInt32 size;
@property (nonatomic, assign) int64_t modifyTime;
@end
@implementation QNConcurrentResumeUpload
- (instancetype)initWithFile:(id<QNFileDelegate>)file
withKey:(NSString *)key
withToken:(QNUpToken *)token
withRecorder:(id<QNRecorderDelegate>)recorder
withRecorderKey:(NSString *)recorderKey
withHttpManager:(id<QNHttpDelegate>)http
withCompletionHandler:(QNUpCompletionHandler)block
withOption:(QNUploadOption *)option
withConfiguration:(QNConfiguration *)config {
if (self = [super init]) {
_file = file;
_size = (UInt32)[file size];
_key = key;
_recorder = recorder;
_recorderKey = recorderKey;
_modifyTime = [file modifyTime];
_option = option != nil ? option : [QNUploadOption defaultOptions];
_complete = block;
_headers = @{@"Authorization" : [NSString stringWithFormat:@"UpToken %@", token.token], @"Content-Type" : @"application/octet-stream"};
_config = config;
_token = token;
_access = token.access;
_httpManager = http;
_taskIdentifier = [[NSUUID UUID] UUIDString];
_taskQueue = [QNConcurrentTaskQueue taskQueueWithFile:file totalSize:_size concurrentTaskCount:_config.concurrentTaskCount recordInfo:[self recoveryFromRecord]];
}
return self;
}
- (void)run {
_uploadGroup = dispatch_group_create();
for (int i = 0; i < _taskQueue.taskQueueCount; i++) {
dispatch_group_enter(_uploadGroup);
dispatch_group_async(_uploadGroup, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[self putBlockWithHost:[self.config.zone up:self.token isHttps:self.config.useHttps frozenDomain:nil] taskQueue:[self.taskQueue getNextTask] retriedTimes:0];
});
}
dispatch_group_notify(_uploadGroup, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
if (self.taskQueue.isAllCompleted) {
[self makeFileWithHost:[self.config.zone up:self.token isHttps:self.config.useHttps frozenDomain:nil] retriedTimes:0];
} else {
self.complete(self.taskQueue.info, self.key, self.taskQueue.resp);
}
});
}
- (void)putBlockWithHost:(NSString *)uphost taskQueue:(QNConcurrentTask *)task retriedTimes:(int)retried {
if (_taskQueue.isConcurrentTaskError) {
dispatch_group_leave(self.uploadGroup);
return;
}
if (self.option.cancellationSignal()) {
[self invalidateTasksWithErrorInfo:[QNResponseInfo cancel] resp:nil];
dispatch_group_leave(self.uploadGroup);
return;
}
NSError *error;
NSData *data = [self.file read:task.index * kQNBlockSize size:task.size error:&error];
if (error) {
self.complete([QNResponseInfo responseInfoWithFileError:error], self.key, nil);
return;
}
UInt32 blockCrc = [QNCrc32 data:data];
QNInternalProgressBlock progressBlock = ^(long long totalBytesWritten, long long totalBytesExpectedToWrite) {
if (self.taskQueue.isConcurrentTaskError) return;
if (totalBytesWritten >= task.uploadedSize) {
task.uploadedSize = (unsigned int)totalBytesWritten;
}
self.option.progressHandler(self.key, self.taskQueue.totalPercent);
};
QNCompleteBlock completionHandler = ^(QNResponseInfo *info, NSDictionary *resp) {
[UploadInfoReporter recordWithRequestType:ReportType_mkblk
responseInfo:info
bytesSent:task.size
fileSize:self.size
token:self.token.token];
if (info.error != nil) {
if (retried >= self.config.retryMax || !info.couldRetry) {
[self invalidateTasksWithErrorInfo:info resp:resp];
dispatch_group_leave(self.uploadGroup);
return;
}
NSString *nextHost = uphost;
if (info.isConnectionBroken || info.needSwitchServer) {
nextHost = [self.config.zone up:self.token isHttps:self.config.useHttps frozenDomain:nextHost];
}
[self putBlockWithHost:nextHost taskQueue:task retriedTimes:retried + 1];
return;
}
if (resp == nil) {
[self putBlockWithHost:uphost taskQueue:task retriedTimes:retried];
return;
}
NSString *ctx = resp[@"ctx"];
NSNumber *crc = resp[@"crc32"];
if (ctx == nil || crc == nil || [crc unsignedLongValue] != blockCrc) {
[self putBlockWithHost:uphost taskQueue:task retriedTimes:retried];
return;
}
BOOL hasMore = [self.taskQueue completeTask:task withContext:ctx];
self.option.progressHandler(self.key, self.taskQueue.totalPercent);
[self record];
if (hasMore) {
[self putBlockWithHost:uphost taskQueue:[self.taskQueue getNextTask] retriedTimes:retried];
} else {
dispatch_group_leave(self.uploadGroup);
}
};
NSString *url = [[NSString alloc] initWithFormat:@"%@/mkblk/%u", uphost, (unsigned int)task.size];
[self post:url withData:data withCompleteBlock:completionHandler withProgressBlock:progressBlock];
}
- (void)makeFileWithHost:(NSString *)uphost retriedTimes:(int)retried {
NSString *mime = [[NSString alloc] initWithFormat:@"/mimeType/%@", [QNUrlSafeBase64 encodeString:self.option.mimeType]];
__block NSString *url = [[NSString alloc] initWithFormat:@"%@/mkfile/%u%@", uphost, (unsigned int)self.size, mime];
if (self.key != nil) {
NSString *keyStr = [[NSString alloc] initWithFormat:@"/key/%@", [QNUrlSafeBase64 encodeString:self.key]];
url = [NSString stringWithFormat:@"%@%@", url, keyStr];
}
[self.option.params enumerateKeysAndObjectsUsingBlock:^(NSString *key, NSString *obj, BOOL *stop) {
url = [NSString stringWithFormat:@"%@/%@/%@", url, key, [QNUrlSafeBase64 encodeString:obj]];
}];
//添加路径
NSString *fname = [[NSString alloc] initWithFormat:@"/fname/%@", [QNUrlSafeBase64 encodeString:[[_file path] lastPathComponent]]];
url = [NSString stringWithFormat:@"%@%@", url, fname];
NSArray *contextArray = [_taskQueue getContexts];
NSString *bodyStr = [contextArray componentsJoinedByString:@","];
NSMutableData *postData = [NSMutableData data];
[postData appendData:[bodyStr dataUsingEncoding:NSUTF8StringEncoding]];
QNCompleteBlock completionHandler = ^(QNResponseInfo *info, NSDictionary *resp) {
[UploadInfoReporter recordWithRequestType:ReportType_mkfile
responseInfo:info
bytesSent:self.size
fileSize:self.size
token:self.token.token];
if (info.isOK) {
[self removeRecord];
self.option.progressHandler(self.key, 1.0);
} else if (info.couldRetry && retried < self.config.retryMax) {
[self makeFileWithHost:uphost retriedTimes:retried + 1];
return;
}
self.complete(info, self.key, resp);
};
[self post:url withData:postData withCompleteBlock:completionHandler withProgressBlock:nil];
}
- (void)record {
NSString *key = self.recorderKey;
if (_recorder == nil || key == nil || [key isEqualToString:@""]) {
return;
}
NSNumber *total_size = @(self.size);
NSNumber *modify_time = [NSNumber numberWithLongLong:_modifyTime];
NSMutableDictionary *rec = [NSMutableDictionary dictionaryWithObjectsAndKeys:total_size, @"total_size", modify_time, @"modify_time", [_taskQueue getRecordInfo], @"info", nil];
NSError *error;
NSData *data = [NSJSONSerialization dataWithJSONObject:rec options:NSJSONWritingPrettyPrinted error:&error];
if (error != nil) {
NSLog(@"up record json error %@ %@", key, error);
return;
}
error = [_recorder set:key data:data];
if (error != nil) {
NSLog(@"up record set error %@ %@", key, error);
}
}
- (void)removeRecord {
if (_recorder == nil) {
return;
}
[_recorder del:self.recorderKey];
}
- (NSArray *)recoveryFromRecord {
NSString *key = self.recorderKey;
if (_recorder == nil || key == nil || [key isEqualToString:@""]) {
return nil;
}
NSData *data = [_recorder get:key];
if (data == nil) {
return nil;
}
NSError *error;
NSDictionary *recordInfo = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableLeaves error:&error];
if (error != nil) {
NSLog(@"recovery error %@ %@", key, error);
[_recorder del:self.key];
return nil;
}
NSNumber *total_size = recordInfo[@"total_size"];
NSNumber *modify_time = recordInfo[@"modify_time"];
NSArray *info = recordInfo[@"info"];
if (total_size == nil || modify_time == nil || info == nil || info.count == 0) {
return nil;
}
UInt32 size = [total_size unsignedIntValue];
if (size != self.size) {
return nil;
}
UInt32 t = [modify_time unsignedIntValue];
if (t != _modifyTime) {
NSLog(@"modify time changed %u, %llu", (unsigned int)t, _modifyTime);
return nil;
}
return info;
}
- (void)post:(NSString *)url
withData:(NSData *)data
withCompleteBlock:(QNCompleteBlock)completeBlock
withProgressBlock:(QNInternalProgressBlock)progressBlock {
[_httpManager post:url withData:data withParams:nil withHeaders:_headers withTaskIdentifier:_taskIdentifier withCompleteBlock:completeBlock withProgressBlock:progressBlock withCancelBlock:_option.cancellationSignal withAccess:_access];
}
- (void)invalidateTasksWithErrorInfo:(QNResponseInfo *)info resp:(NSDictionary *)resp {
if (_taskQueue.isConcurrentTaskError) return;
[_taskQueue buildErrorWithInfo:info resp:resp];
[_httpManager invalidateSessionWithIdentifier:_taskIdentifier];
}
@end
//
// QNConfiguration.h
// QiniuSDK
//
// Created by bailong on 15/5/21.
// Copyright (c) 2015年 Qiniu. All rights reserved.
//
#import <Foundation/Foundation.h>
#import "QNRecorderDelegate.h"
/**
* 断点上传时的分块大小
*/
extern const UInt32 kQNBlockSize;
/**
* 转换为用户需要的url
*
* @param url 上传url
*
* @return 根据上传url算出代理url
*/
typedef NSString * (^QNUrlConvert)(NSString *url);
@class QNConfigurationBuilder;
@class QNZone;
@class QNReportConfig;
/**
* Builder block
*
* @param builder builder实例
*/
typedef void (^QNConfigurationBuilderBlock)(QNConfigurationBuilder *builder);
@interface QNConfiguration : NSObject
/**
* 存储区域
*/
@property (copy, nonatomic, readonly) QNZone *zone;
/**
* 断点上传时的分片大小
*/
@property (readonly) UInt32 chunkSize;
/**
* 如果大于此值就使用断点上传,否则使用form上传
*/
@property (readonly) UInt32 putThreshold;
/**
* 上传失败的重试次数
*/
@property (readonly) UInt32 retryMax;
/**
* 超时时间 单位 秒
*/
@property (readonly) UInt32 timeoutInterval;
/**
* 是否使用 https,默认为 YES
*/
@property (nonatomic, assign, readonly) BOOL useHttps;
/**
* 是否开启并发分片上传,默认为NO
*/
@property (nonatomic, assign, readonly) BOOL useConcurrentResumeUpload;
/**
* 并发分片上传的并发任务个数,在concurrentResumeUpload为YES时有效,默认为3个
*/
@property (nonatomic, assign, readonly) UInt32 concurrentTaskCount;
@property (nonatomic, readonly) QNReportConfig *reportConfig;
@property (nonatomic, readonly) id<QNRecorderDelegate> recorder;
@property (nonatomic, readonly) QNRecorderKeyGenerator recorderKeyGen;
@property (nonatomic, readonly) NSDictionary *proxy;
@property (nonatomic, readonly) QNUrlConvert converter;
@property (readonly) BOOL disableATS;
+ (instancetype)build:(QNConfigurationBuilderBlock)block;
@end
typedef void (^QNPrequeryReturn)(int code);
@class QNUpToken;
@class QNZoneInfo;
@interface QNZone : NSObject
@property (nonatomic, strong) NSArray<NSString *> *upDomainList;
@property (nonatomic, strong) QNZoneInfo *zoneInfo;
/**
* 默认上传服务器地址列表
*/
- (void)preQuery:(QNUpToken *)token
on:(QNPrequeryReturn)ret;
- (NSString *)up:(QNUpToken *)token
isHttps:(BOOL)isHttps
frozenDomain:(NSString *)frozenDomain;
@end
@interface QNZoneInfo : NSObject
@property (readonly, nonatomic) long ttl;
@property (readonly, nonatomic) NSMutableArray<NSString *> *upDomainsList;
@property (readonly, nonatomic) NSMutableDictionary *upDomainsDic;
- (instancetype)init:(long)ttl
upDomainsList:(NSMutableArray<NSString *> *)upDomainsList
upDomainsDic:(NSMutableDictionary *)upDomainsDic;
- (QNZoneInfo *)buildInfoFromJson:(NSDictionary *)resp;
@end
@interface QNFixedZone : QNZone
/**
* zone 0 华东
*
* @return 实例
*/
+ (instancetype)zone0;
/**
* zone 1 华北
*
* @return 实例
*/
+ (instancetype)zone1;
/**
* zone 2 华南
*
* @return 实例
*/
+ (instancetype)zone2;
/**
* zone Na0 北美
*
* @return 实例
*/
+ (instancetype)zoneNa0;
/**
* zone As0 新加坡
*
* @return 实例
*/
+ (instancetype)zoneAs0;
/**
* Zone初始化方法
*
* @param upList 默认上传服务器地址列表
*
* @return Zone实例
*/
- (instancetype)initWithupDomainList:(NSArray<NSString *> *)upList;
/**
* Zone初始化方法
*
* @param upList 默认上传服务器地址列表
*
* @return Zone实例
*/
+ (instancetype)createWithHost:(NSArray<NSString *> *)upList;
- (void)preQuery:(QNUpToken *)token
on:(QNPrequeryReturn)ret;
- (NSString *)up:(QNUpToken *)token
isHttps:(BOOL)isHttps
frozenDomain:(NSString *)frozenDomain;
@end
@interface QNAutoZone : QNZone
- (NSString *)up:(QNUpToken *)token
isHttps:(BOOL)isHttps
frozenDomain:(NSString *)frozenDomain;
@end
@interface QNConfigurationBuilder : NSObject
/**
* 默认上传服务器地址
*/
@property (nonatomic, strong) QNZone *zone;
/**
* 断点上传时的分片大小
*/
@property (assign) UInt32 chunkSize;
/**
* 如果大于此值就使用断点上传,否则使用form上传
*/
@property (assign) UInt32 putThreshold;
/**
* 上传失败的重试次数
*/
@property (assign) UInt32 retryMax;
/**
* 超时时间 单位 秒
*/
@property (assign) UInt32 timeoutInterval;
/**
* 是否使用 https,默认为 YES
*/
@property (nonatomic, assign) BOOL useHttps;
/**
* 是否开启并发分片上传,默认为NO
*/
@property (nonatomic, assign) BOOL useConcurrentResumeUpload;
/**
* 并发分片上传的并发任务个数,在concurrentResumeUpload为YES时有效,默认为3个
*/
@property (nonatomic, assign) UInt32 concurrentTaskCount;
@property (nonatomic, strong) id<QNRecorderDelegate> recorder;
@property (nonatomic, strong) QNRecorderKeyGenerator recorderKeyGen;
@property (nonatomic, strong) QNReportConfig *reportConfig;
@property (nonatomic, strong) NSDictionary *proxy;
@property (nonatomic, strong) QNUrlConvert converter;
@property (assign) BOOL disableATS;
@end
//
// QNConfiguration.m
// QiniuSDK
//
// Created by bailong on 15/5/21.
// Copyright (c) 2015年 Qiniu. All rights reserved.
//
#import "QNConfiguration.h"
#import "QNResponseInfo.h"
#import "QNSessionManager.h"
#import "QNSystem.h"
#import "QNUpToken.h"
#import "QNUploadInfoReporter.h"
const UInt32 kQNBlockSize = 4 * 1024 * 1024;
@implementation QNConfiguration
+ (instancetype)build:(QNConfigurationBuilderBlock)block {
QNConfigurationBuilder *builder = [[QNConfigurationBuilder alloc] init];
block(builder);
return [[QNConfiguration alloc] initWithBuilder:builder];
}
- (instancetype)initWithBuilder:(QNConfigurationBuilder *)builder {
if (self = [super init]) {
_chunkSize = builder.chunkSize;
_putThreshold = builder.putThreshold;
_retryMax = builder.retryMax;
_timeoutInterval = builder.timeoutInterval;
_recorder = builder.recorder;
_recorderKeyGen = builder.recorderKeyGen;
_proxy = builder.proxy;
_converter = builder.converter;
_disableATS = builder.disableATS;
_zone = builder.zone;
_useHttps = builder.useHttps;
_reportConfig = builder.reportConfig;
_useConcurrentResumeUpload = builder.useConcurrentResumeUpload;
_concurrentTaskCount = builder.concurrentTaskCount;
}
return self;
}
@end
@implementation QNConfigurationBuilder
- (instancetype)init {
if (self = [super init]) {
_zone = [[QNAutoZone alloc] init];
_chunkSize = 2 * 1024 * 1024;
_putThreshold = 4 * 1024 * 1024;
_retryMax = 3;
_timeoutInterval = 60;
_reportConfig = [QNReportConfig sharedInstance];
_recorder = nil;
_recorderKeyGen = nil;
_proxy = nil;
_converter = nil;
if (hasAts() && !allowsArbitraryLoads()) {
_disableATS = NO;
} else {
_disableATS = YES;
}
_useHttps = YES;
_useConcurrentResumeUpload = NO;
_concurrentTaskCount = 3;
}
return self;
}
@end
@implementation QNZoneInfo
- (instancetype)init:(long)ttl
upDomainsList:(NSMutableArray<NSString *> *)upDomainsList
upDomainsDic:(NSMutableDictionary *)upDomainsDic {
if (self = [super init]) {
_ttl = ttl;
_upDomainsList = upDomainsList;
_upDomainsDic = upDomainsDic;
}
return self;
}
- (QNZoneInfo *)buildInfoFromJson:(NSDictionary *)resp {
long ttl = [[resp objectForKey:@"ttl"] longValue];
NSDictionary *up = [resp objectForKey:@"up"];
NSDictionary *acc = [up objectForKey:@"acc"];
NSDictionary *src = [up objectForKey:@"src"];
NSDictionary *old_acc = [up objectForKey:@"old_acc"];
NSDictionary *old_src = [up objectForKey:@"old_src"];
NSArray *urlDicList = [[NSArray alloc] initWithObjects:acc, src, old_acc, old_src, nil];
NSMutableArray *domainList = [[NSMutableArray alloc] init];
NSMutableDictionary *domainDic = [[NSMutableDictionary alloc] init];
//main
for (int i = 0; i < urlDicList.count; i++) {
if ([[urlDicList[i] allKeys] containsObject:@"main"]) {
NSArray *mainDomainList = urlDicList[i][@"main"];
for (int i = 0; i < mainDomainList.count; i++) {
[domainList addObject:mainDomainList[i]];
[domainDic setObject:[NSDate dateWithTimeIntervalSince1970:0] forKey:mainDomainList[i]];
}
}
//backup
if ([[urlDicList[i] allKeys] containsObject:@"backup"]) {
NSArray *mainDomainList = urlDicList[i][@"backup"];
for (int i = 0; i < mainDomainList.count; i++) {
[domainList addObject:mainDomainList[i]];
[domainDic setObject:[NSDate dateWithTimeIntervalSince1970:0] forKey:mainDomainList[i]];
}
}
}
return [[QNZoneInfo alloc] init:ttl upDomainsList:domainList upDomainsDic:domainDic];
}
- (void)frozenDomain:(NSString *)domain {
NSTimeInterval secondsFor10min = 10 * 60;
NSDate *tomorrow = [NSDate dateWithTimeIntervalSinceNow:secondsFor10min];
[self.upDomainsDic setObject:tomorrow forKey:domain];
}
@end
@implementation QNZone
- (instancetype)init {
self = [super init];
return self;
}
- (NSArray<NSString *> *)upDomainList:(NSString *)token {
return self.upDomainList;
}
- (NSString *)upHost:(QNZoneInfo *)zoneInfo
isHttps:(BOOL)isHttps
lastUpHost:(NSString *)lastUpHost {
NSString *upHost = nil;
NSString *upDomain = nil;
// frozen domain
if (lastUpHost) {
NSString *upLastDomain = nil;
if (isHttps) {
upLastDomain = [lastUpHost substringFromIndex:8];
} else {
upLastDomain = [lastUpHost substringFromIndex:7];
}
[zoneInfo frozenDomain:upLastDomain];
}
//get backup domain
for (NSString *backupDomain in zoneInfo.upDomainsList) {
NSDate *frozenTill = zoneInfo.upDomainsDic[backupDomain];
NSDate *now = [NSDate date];
if ([frozenTill compare:now] == NSOrderedAscending) {
upDomain = backupDomain;
break;
}
}
if (upDomain) {
[zoneInfo.upDomainsDic setObject:[NSDate dateWithTimeIntervalSince1970:0] forKey:upDomain];
} else {
//reset all the up host frozen time
for (NSString *domain in zoneInfo.upDomainsList) {
[zoneInfo.upDomainsDic setObject:[NSDate dateWithTimeIntervalSince1970:0] forKey:domain];
}
if (zoneInfo.upDomainsList.count > 0) {
upDomain = zoneInfo.upDomainsList[0];
}
}
if (upDomain) {
if (isHttps) {
upHost = [NSString stringWithFormat:@"https://%@", upDomain];
} else {
upHost = [NSString stringWithFormat:@"http://%@", upDomain];
}
}
return upHost;
}
- (NSString *)up:(QNUpToken *)token
isHttps:(BOOL)isHttps
frozenDomain:(NSString *)frozenDomain {
return nil;
}
- (void)preQuery:(QNUpToken *)token
on:(QNPrequeryReturn)ret {
ret(0);
}
@end
@interface QNFixedZone () {
NSString *server;
NSMutableDictionary *cache;
NSLock *lock;
}
@end
@implementation QNFixedZone
- (instancetype)initWithupDomainList:(NSArray<NSString *> *)upList {
if (self = [super init]) {
self.upDomainList = upList;
self.zoneInfo = [self createZoneInfo:upList];
}
return self;
}
+ (instancetype)createWithHost:(NSArray<NSString *> *)upList {
return [[QNFixedZone alloc] initWithupDomainList:upList];
}
+ (instancetype)zone0 {
static QNFixedZone *z0 = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
static const NSArray<NSString *> *uplist = nil;
if (!uplist) {
uplist = [[NSArray alloc] initWithObjects:@"upload.qiniup.com", @"upload-nb.qiniup.com",
@"upload-xs.qiniup.com", @"up.qiniup.com",
@"up-nb.qiniup.com", @"up-xs.qiniup.com",
@"upload.qbox.me", @"up.qbox.me", nil];
z0 = [QNFixedZone createWithHost:(NSArray<NSString *> *)uplist];
}
});
return z0;
}
+ (instancetype)zone1 {
static QNFixedZone *z1 = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
static const NSArray<NSString *> *uplist = nil;
if (!uplist) {
uplist = [[NSArray alloc] initWithObjects:@"upload-z1.qiniup.com", @"up-z1.qiniup.com",
@"upload-z1.qbox.me", @"up-z1.qbox.me", nil];
z1 = [QNFixedZone createWithHost:(NSArray<NSString *> *)uplist];
}
});
return z1;
}
+ (instancetype)zone2 {
static QNFixedZone *z2 = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
static const NSArray<NSString *> *uplist = nil;
if (!uplist) {
uplist = [[NSArray alloc] initWithObjects:@"upload-z2.qiniup.com", @"upload-gz.qiniup.com",
@"upload-fs.qiniup.com", @"up-z2.qiniup.com",
@"up-gz.qiniup.com", @"up-fs.qiniup.com",
@"upload-z2.qbox.me", @"up-z2.qbox.me", nil];
z2 = [QNFixedZone createWithHost:(NSArray<NSString *> *)uplist];
}
});
return z2;
}
+ (instancetype)zoneNa0 {
static QNFixedZone *zNa0 = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
static const NSArray<NSString *> *uplist = nil;
if (!uplist) {
uplist = [[NSArray alloc] initWithObjects:@"upload-na0.qiniup.com", @"up-na0.qiniup.com",
@"upload-na0.qbox.me", @"up-na0.qbox.me", nil];
zNa0 = [QNFixedZone createWithHost:(NSArray<NSString *> *)uplist];
}
});
return zNa0;
}
+ (instancetype)zoneAs0 {
static QNFixedZone *zAs0 = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
static const NSArray<NSString *> *uplist = nil;
if (!uplist) {
uplist = [[NSArray alloc] initWithObjects:@"upload-as0.qiniup.com", @"up-as0.qiniup.com",
@"upload-as0.qbox.me", @"up-as0.qbox.me", nil];
zAs0 = [QNFixedZone createWithHost:(NSArray<NSString *> *)uplist];
}
});
return zAs0;
}
- (void)preQuery:(QNUpToken *)token
on:(QNPrequeryReturn)ret {
ret(0);
}
- (QNZoneInfo *)createZoneInfo:(NSArray<NSString *> *)upDomainList {
NSMutableDictionary *upDomainDic = [[NSMutableDictionary alloc] init];
for (NSString *upDomain in upDomainList) {
[upDomainDic setValue:[NSDate dateWithTimeIntervalSince1970:0] forKey:upDomain];
}
QNZoneInfo *zoneInfo = [[QNZoneInfo alloc] init:86400 upDomainsList:(NSMutableArray<NSString *> *)upDomainList upDomainsDic:upDomainDic];
return zoneInfo;
}
- (NSString *)up:(QNUpToken *)token
isHttps:(BOOL)isHttps
frozenDomain:(NSString *)frozenDomain {
if (self.zoneInfo == nil) {
return nil;
}
return [super upHost:self.zoneInfo isHttps:isHttps lastUpHost:frozenDomain];
}
@end
@implementation QNAutoZone {
NSString *server;
NSMutableDictionary *cache;
NSLock *lock;
QNSessionManager *sesionManager;
}
- (instancetype)init{
if (self = [super init]) {
server = @"https://uc.qbox.me";
cache = [NSMutableDictionary new];
lock = [NSLock new];
sesionManager = [[QNSessionManager alloc] initWithProxy:nil timeout:10 urlConverter:nil];
}
return self;
}
- (NSString *)up:(QNUpToken *)token
isHttps:(BOOL)isHttps
frozenDomain:(NSString *)frozenDomain {
NSString *index = [token index];
[lock lock];
QNZoneInfo *info = [cache objectForKey:index];
[lock unlock];
if (info == nil) {
return nil;
}
return [super upHost:info isHttps:isHttps lastUpHost:frozenDomain];
}
- (void)preQuery:(QNUpToken *)token
on:(QNPrequeryReturn)ret {
if (token == nil) {
ret(-1);
}
[lock lock];
QNZoneInfo *info = [cache objectForKey:[token index]];
[lock unlock];
if (info != nil) {
ret(0);
return;
}
//https://uc.qbox.me/v2/query?ak=T3sAzrwItclPGkbuV4pwmszxK7Ki46qRXXGBBQz3&bucket=if-pbl
NSString *url = [NSString stringWithFormat:@"%@/v2/query?ak=%@&bucket=%@", server, token.access, token.bucket];
[sesionManager get:url withHeaders:nil withCompleteBlock:^(QNResponseInfo *info, NSDictionary *resp) {
if (!info.error) {
QNZoneInfo *info = [[[QNZoneInfo alloc] init] buildInfoFromJson:resp];
if (info == nil) {
ret(kQNInvalidToken);
} else {
[lock lock];
[cache setValue:info forKey:[token index]];
[lock unlock];
ret(0);
}
} else {
ret(kQNNetworkError);
}
}];
}
@end
//
// QNFormUpload.h
// QiniuSDK
//
// Created by bailong on 15/1/4.
// Copyright (c) 2015年 Qiniu. All rights reserved.
//
#import "QNHttpDelegate.h"
#import "QNUpToken.h"
#import "QNUploadManager.h"
#import <Foundation/Foundation.h>
@interface QNFormUpload : NSObject
- (instancetype)initWithData:(NSData *)data
withKey:(NSString *)key
withFileName:(NSString *)fileName
withToken:(QNUpToken *)token
withCompletionHandler:(QNUpCompletionHandler)block
withOption:(QNUploadOption *)option
withHttpManager:(id<QNHttpDelegate>)http
withConfiguration:(QNConfiguration *)config;
- (void)put;
@end
//
// QNFormUpload.m
// QiniuSDK
//
// Created by bailong on 15/1/4.
// Copyright (c) 2015年 Qiniu. All rights reserved.
//
#import "QNFormUpload.h"
#import "QNConfiguration.h"
#import "QNCrc32.h"
#import "QNRecorderDelegate.h"
#import "QNResponseInfo.h"
#import "QNUploadManager.h"
#import "QNUploadOption+Private.h"
#import "QNUrlSafeBase64.h"
#import "QNUploadInfoReporter.h"
@interface QNFormUpload ()
@property (nonatomic, strong) NSData *data;
@property (nonatomic, strong) id<QNHttpDelegate> httpManager;
@property (nonatomic) int retryTimes;
@property (nonatomic, strong) NSString *key;
@property (nonatomic, strong) QNUpToken *token;
@property (nonatomic, strong) QNUploadOption *option;
@property (nonatomic, strong) QNUpCompletionHandler complete;
@property (nonatomic, strong) QNConfiguration *config;
@property (nonatomic, strong) NSString *fileName;
@property (nonatomic) float previousPercent;
@property (nonatomic, strong) NSString *access; //AK
@property (nonatomic, copy) NSString *taskIdentifier;
@end
@implementation QNFormUpload
- (instancetype)initWithData:(NSData *)data
withKey:(NSString *)key
withFileName:(NSString *)fileName
withToken:(QNUpToken *)token
withCompletionHandler:(QNUpCompletionHandler)block
withOption:(QNUploadOption *)option
withHttpManager:(id<QNHttpDelegate>)http
withConfiguration:(QNConfiguration *)config {
if (self = [super init]) {
_data = data;
_key = key;
_token = token;
_option = option != nil ? option : [QNUploadOption defaultOptions];
_complete = block;
_httpManager = http;
_config = config;
_fileName = fileName != nil ? fileName : @"?";
_previousPercent = 0;
_access = token.access;
_taskIdentifier = [[NSUUID UUID] UUIDString];
}
return self;
}
- (void)put {
NSMutableDictionary *parameters = [NSMutableDictionary dictionary];
if (_key) {
parameters[@"key"] = _key;
}
parameters[@"token"] = _token.token;
[parameters addEntriesFromDictionary:_option.params];
parameters[@"crc32"] = [NSString stringWithFormat:@"%u", (unsigned int)[QNCrc32 data:_data]];
QNInternalProgressBlock p = ^(long long totalBytesWritten, long long totalBytesExpectedToWrite) {
float percent = (float)totalBytesWritten / (float)totalBytesExpectedToWrite;
if (percent > 0.95) {
percent = 0.95;
}
if (percent > _previousPercent) {
_previousPercent = percent;
} else {
percent = _previousPercent;
}
_option.progressHandler(_key, percent);
};
__block NSString *upHost = [_config.zone up:_token isHttps:_config.useHttps frozenDomain:nil];
QNCompleteBlock complete = ^(QNResponseInfo *info, NSDictionary *resp) {
[UploadInfoReporter recordWithRequestType:ReportType_form
responseInfo:info
bytesSent:(UInt32)_data.length
fileSize:(UInt32)_data.length
token:_token.token];
if (info.isOK) {
_option.progressHandler(_key, 1.0);
}
if (info.isOK || !info.couldRetry) {
_complete(info, _key, resp);
return;
}
if (_option.cancellationSignal()) {
_complete([QNResponseInfo cancel], _key, nil);
return;
}
__block NSString *nextHost = upHost;
if (info.isConnectionBroken || info.needSwitchServer) {
nextHost = [_config.zone up:_token isHttps:_config.useHttps frozenDomain:nextHost];
}
QNCompleteBlock retriedComplete = ^(QNResponseInfo *info, NSDictionary *resp) {
[UploadInfoReporter recordWithRequestType:ReportType_form
responseInfo:info
bytesSent:(UInt32)_data.length
fileSize:(UInt32)_data.length
token:_token.token];
if (info.isOK) {
_option.progressHandler(_key, 1.0);
}
if (info.isOK || !info.couldRetry) {
_complete(info, _key, resp);
return;
}
if (_option.cancellationSignal()) {
_complete([QNResponseInfo cancel], _key, nil);
return;
}
NSString *thirdHost = nextHost;
if (info.isConnectionBroken || info.needSwitchServer) {
thirdHost = [_config.zone up:_token isHttps:_config.useHttps frozenDomain:nextHost];
}
QNCompleteBlock thirdComplete = ^(QNResponseInfo *info, NSDictionary *resp) {
[UploadInfoReporter recordWithRequestType:ReportType_form
responseInfo:info
bytesSent:(UInt32)_data.length
fileSize:(UInt32)_data.length
token:_token.token];
if (info.isOK) {
_option.progressHandler(_key, 1.0);
}
_complete(info, _key, resp);
};
[_httpManager multipartPost:thirdHost
withData:_data
withParams:parameters
withFileName:_fileName
withMimeType:_option.mimeType
withTaskIdentifier:_taskIdentifier
withCompleteBlock:thirdComplete
withProgressBlock:p
withCancelBlock:_option.cancellationSignal
withAccess:_access];
};
[_httpManager multipartPost:nextHost
withData:_data
withParams:parameters
withFileName:_fileName
withMimeType:_option.mimeType
withTaskIdentifier:_taskIdentifier
withCompleteBlock:retriedComplete
withProgressBlock:p
withCancelBlock:_option.cancellationSignal
withAccess:_access];
};
[_httpManager multipartPost:upHost
withData:_data
withParams:parameters
withFileName:_fileName
withMimeType:_option.mimeType
withTaskIdentifier:_taskIdentifier
withCompleteBlock:complete
withProgressBlock:p
withCancelBlock:_option.cancellationSignal
withAccess:_access];
}
@end
//
// QNResumeUpload.h
// QiniuSDK
//
// Created by bailong on 14/10/1.
// Copyright (c) 2014年 Qiniu. All rights reserved.
//
#import "QNFileDelegate.h"
#import "QNHttpDelegate.h"
#import "QNUpToken.h"
#import "QNUploadManager.h"
#import <Foundation/Foundation.h>
@interface QNResumeUpload : NSObject
- (instancetype)initWithFile:(id<QNFileDelegate>)file
withKey:(NSString *)key
withToken:(QNUpToken *)token
withCompletionHandler:(QNUpCompletionHandler)block
withOption:(QNUploadOption *)option
withRecorder:(id<QNRecorderDelegate>)recorder
withRecorderKey:(NSString *)recorderKey
withHttpManager:(id<QNHttpDelegate>)http
withConfiguration:(QNConfiguration *)config;
- (void)run;
@end
//
// QNResumeUpload.m
// QiniuSDK
//
// Created by bailong on 14/10/1.
// Copyright (c) 2014年 Qiniu. All rights reserved.
//
#import "QNResumeUpload.h"
#import "QNConfiguration.h"
#import "QNCrc32.h"
#import "QNRecorderDelegate.h"
#import "QNResponseInfo.h"
#import "QNUploadManager.h"
#import "QNUploadOption+Private.h"
#import "QNUrlSafeBase64.h"
#import "QNUploadInfoReporter.h"
typedef void (^task)(void);
@interface QNResumeUpload ()
@property (nonatomic, strong) id<QNHttpDelegate> httpManager;
@property UInt32 size;
@property (nonatomic) int retryTimes;
@property (nonatomic, strong) NSString *key;
@property (nonatomic, strong) NSString *recorderKey;
@property (nonatomic) NSDictionary *headers;
@property (nonatomic, strong) QNUploadOption *option;
@property (nonatomic, strong) QNUpToken *token;
@property (nonatomic, strong) QNUpCompletionHandler complete;
@property (nonatomic, strong) NSMutableArray *contexts;
@property (nonatomic, assign) QNReportType reportType;
@property int64_t modifyTime;
@property (nonatomic, strong) id<QNRecorderDelegate> recorder;
@property (nonatomic, strong) QNConfiguration *config;
@property UInt32 chunkCrc;
@property (nonatomic, strong) id<QNFileDelegate> file;
//@property (nonatomic, strong) NSArray *fileAry;
@property (nonatomic) float previousPercent;
@property (nonatomic, strong) NSString *access; //AK
@property (nonatomic, copy) NSString *taskIdentifier;
- (void)makeBlock:(NSString *)uphost
offset:(UInt32)offset
blockSize:(UInt32)blockSize
chunkSize:(UInt32)chunkSize
progress:(QNInternalProgressBlock)progressBlock
complete:(QNCompleteBlock)complete;
- (void)putChunk:(NSString *)uphost
offset:(UInt32)offset
size:(UInt32)size
context:(NSString *)context
progress:(QNInternalProgressBlock)progressBlock
complete:(QNCompleteBlock)complete;
- (void)makeFile:(NSString *)uphost
complete:(QNCompleteBlock)complete;
@end
@implementation QNResumeUpload
- (instancetype)initWithFile:(id<QNFileDelegate>)file
withKey:(NSString *)key
withToken:(QNUpToken *)token
withCompletionHandler:(QNUpCompletionHandler)block
withOption:(QNUploadOption *)option
withRecorder:(id<QNRecorderDelegate>)recorder
withRecorderKey:(NSString *)recorderKey
withHttpManager:(id<QNHttpDelegate>)http
withConfiguration:(QNConfiguration *)config;
{
if (self = [super init]) {
_file = file;
_size = (UInt32)[file size];
_key = key;
NSString *tokenUp = [NSString stringWithFormat:@"UpToken %@", token.token];
_option = option != nil ? option : [QNUploadOption defaultOptions];
_complete = block;
_headers = @{@"Authorization" : tokenUp, @"Content-Type" : @"application/octet-stream"};
_recorder = recorder;
_httpManager = http;
_modifyTime = [file modifyTime];
_recorderKey = recorderKey;
_contexts = [[NSMutableArray alloc] initWithCapacity:(_size + kQNBlockSize - 1) / kQNBlockSize];
_config = config;
_token = token;
_previousPercent = 0;
_access = token.access;
_taskIdentifier = [[NSUUID UUID] UUIDString];
}
return self;
}
// save json value
//{
// "size":filesize,
// "offset":lastSuccessOffset,
// "modify_time": lastFileModifyTime,
// "contexts": contexts
//}
- (void)record:(UInt32)offset {
NSString *key = self.recorderKey;
if (offset == 0 || _recorder == nil || key == nil || [key isEqualToString:@""]) {
return;
}
NSNumber *n_size = @(self.size);
NSNumber *n_offset = @(offset);
NSNumber *n_time = [NSNumber numberWithLongLong:_modifyTime];
NSMutableDictionary *rec = [NSMutableDictionary dictionaryWithObjectsAndKeys:n_size, @"size", n_offset, @"offset", n_time, @"modify_time", _contexts, @"contexts", nil];
NSError *error;
NSData *data = [NSJSONSerialization dataWithJSONObject:rec options:NSJSONWritingPrettyPrinted error:&error];
if (error != nil) {
NSLog(@"up record json error %@ %@", key, error);
return;
}
error = [_recorder set:key data:data];
if (error != nil) {
NSLog(@"up record set error %@ %@", key, error);
}
}
- (void)removeRecord {
if (_recorder == nil) {
return;
}
[_recorder del:self.recorderKey];
}
- (UInt32)recoveryFromRecord {
NSString *key = self.recorderKey;
if (_recorder == nil || key == nil || [key isEqualToString:@""]) {
return 0;
}
NSData *data = [_recorder get:key];
if (data == nil) {
return 0;
}
NSError *error;
NSDictionary *info = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableLeaves error:&error];
if (error != nil) {
NSLog(@"recovery error %@ %@", key, error);
[_recorder del:self.key];
return 0;
}
NSNumber *n_offset = info[@"offset"];
NSNumber *n_size = info[@"size"];
NSNumber *time = info[@"modify_time"];
NSArray *contexts = info[@"contexts"];
if (n_offset == nil || n_size == nil || time == nil || contexts == nil) {
return 0;
}
UInt32 offset = [n_offset unsignedIntValue];
UInt32 size = [n_size unsignedIntValue];
if (offset > size || size != self.size) {
return 0;
}
UInt64 t = [time unsignedLongLongValue];
if (t != _modifyTime) {
NSLog(@"modify time changed %llu, %llu", t, _modifyTime);
return 0;
}
_contexts = [[NSMutableArray alloc] initWithArray:contexts copyItems:true];
return offset;
}
- (void)nextTask:(UInt32)offset retriedTimes:(int)retried host:(NSString *)host {
if (self.option.cancellationSignal()) {
self.complete([QNResponseInfo cancel], self.key, nil);
return;
}
if (offset == self.size) {
QNCompleteBlock completionHandler = ^(QNResponseInfo *info, NSDictionary *resp) {
[UploadInfoReporter recordWithRequestType:_reportType
responseInfo:info
bytesSent:_size
fileSize:_size
token:_token.token];
if (info.isOK) {
[self removeRecord];
self.option.progressHandler(self.key, 1.0);
} else if (info.couldRetry && retried < _config.retryMax) {
[self nextTask:offset retriedTimes:retried + 1 host:host];
return;
}
self.complete(info, self.key, resp);
};
[self makeFile:host complete:completionHandler];
return;
}
UInt32 chunkSize = [self calcPutSize:offset];
QNInternalProgressBlock progressBlock = ^(long long totalBytesWritten, long long totalBytesExpectedToWrite) {
float percent = (float)(offset + totalBytesWritten) / (float)self.size;
if (percent > 0.95) {
percent = 0.95;
}
if (percent > _previousPercent) {
_previousPercent = percent;
} else {
percent = _previousPercent;
}
self.option.progressHandler(self.key, percent);
};
QNCompleteBlock completionHandler = ^(QNResponseInfo *info, NSDictionary *resp) {
[UploadInfoReporter recordWithRequestType:_reportType
responseInfo:info
bytesSent:chunkSize
fileSize:_size
token:_token.token];
if (info.error != nil) {
if (info.statusCode == 701) {
[self nextTask:(offset / kQNBlockSize) * kQNBlockSize retriedTimes:0 host:host];
return;
}
if (retried >= _config.retryMax || !info.couldRetry) {
self.complete(info, self.key, resp);
return;
}
NSString *nextHost = host;
if (info.isConnectionBroken || info.needSwitchServer) {
nextHost = [_config.zone up:_token isHttps:_config.useHttps frozenDomain:nextHost];
}
[self nextTask:offset retriedTimes:retried + 1 host:nextHost];
return;
}
if (resp == nil) {
[self nextTask:offset retriedTimes:retried host:host];
return;
}
NSString *ctx = resp[@"ctx"];
NSNumber *crc = resp[@"crc32"];
if (ctx == nil || crc == nil || [crc unsignedLongValue] != _chunkCrc) {
[self nextTask:offset retriedTimes:retried host:host];
return;
}
_contexts[offset / kQNBlockSize] = ctx;
[self record:offset + chunkSize];
[self nextTask:offset + chunkSize retriedTimes:retried host:host];
};
if (offset % kQNBlockSize == 0) {
UInt32 blockSize = [self calcBlockSize:offset];
[self makeBlock:host offset:offset blockSize:blockSize chunkSize:chunkSize progress:progressBlock complete:completionHandler];
return;
}
NSString *context = _contexts[offset / kQNBlockSize];
[self putChunk:host offset:offset size:chunkSize context:context progress:progressBlock complete:completionHandler];
}
- (UInt32)calcPutSize:(UInt32)offset {
UInt32 left = self.size - offset;
return left < _config.chunkSize ? left : _config.chunkSize;
}
- (UInt32)calcBlockSize:(UInt32)offset {
UInt32 left = self.size - offset;
return left < kQNBlockSize ? left : kQNBlockSize;
}
- (void)makeBlock:(NSString *)uphost
offset:(UInt32)offset
blockSize:(UInt32)blockSize
chunkSize:(UInt32)chunkSize
progress:(QNInternalProgressBlock)progressBlock
complete:(QNCompleteBlock)complete {
_reportType = ReportType_mkblk;
NSError *error;
NSData *data = [self.file read:offset size:chunkSize error:&error];
if (error) {
self.complete([QNResponseInfo responseInfoWithFileError:error], self.key, nil);
return;
}
NSString *url = [[NSString alloc] initWithFormat:@"%@/mkblk/%u", uphost, (unsigned int)blockSize];
_chunkCrc = [QNCrc32 data:data];
[self post:url withData:data withCompleteBlock:complete withProgressBlock:progressBlock];
}
- (void)putChunk:(NSString *)uphost
offset:(UInt32)offset
size:(UInt32)size
context:(NSString *)context
progress:(QNInternalProgressBlock)progressBlock
complete:(QNCompleteBlock)complete {
_reportType = ReportType_bput;
NSError *error;
NSData *data = [self.file read:offset size:size error:&error];
if (error) {
self.complete([QNResponseInfo responseInfoWithFileError:error], self.key, nil);
return;
}
UInt32 chunkOffset = offset % kQNBlockSize;
NSString *url = [[NSString alloc] initWithFormat:@"%@/bput/%@/%u", uphost, context, (unsigned int)chunkOffset];
_chunkCrc = [QNCrc32 data:data];
[self post:url withData:data withCompleteBlock:complete withProgressBlock:progressBlock];
}
- (void)makeFile:(NSString *)uphost
complete:(QNCompleteBlock)complete {
_reportType = ReportType_mkfile;
NSString *mime = [[NSString alloc] initWithFormat:@"/mimeType/%@", [QNUrlSafeBase64 encodeString:self.option.mimeType]];
__block NSString *url = [[NSString alloc] initWithFormat:@"%@/mkfile/%u%@", uphost, (unsigned int)self.size, mime];
if (self.key != nil) {
NSString *keyStr = [[NSString alloc] initWithFormat:@"/key/%@", [QNUrlSafeBase64 encodeString:self.key]];
url = [NSString stringWithFormat:@"%@%@", url, keyStr];
}
[self.option.params enumerateKeysAndObjectsUsingBlock:^(NSString *key, NSString *obj, BOOL *stop) {
url = [NSString stringWithFormat:@"%@/%@/%@", url, key, [QNUrlSafeBase64 encodeString:obj]];
}];
//添加路径
NSString *fname = [[NSString alloc] initWithFormat:@"/fname/%@", [QNUrlSafeBase64 encodeString:[self fileBaseName]]];
url = [NSString stringWithFormat:@"%@%@", url, fname];
NSMutableData *postData = [NSMutableData data];
NSString *bodyStr = [self.contexts componentsJoinedByString:@","];
[postData appendData:[bodyStr dataUsingEncoding:NSUTF8StringEncoding]];
[self post:url withData:postData withCompleteBlock:complete withProgressBlock:nil];
}
#pragma mark - 处理文件路径
- (NSString *)fileBaseName {
return [[_file path] lastPathComponent];
}
- (void)post:(NSString *)url
withData:(NSData *)data
withCompleteBlock:(QNCompleteBlock)completeBlock
withProgressBlock:(QNInternalProgressBlock)progressBlock {
[_httpManager post:url withData:data withParams:nil withHeaders:_headers withTaskIdentifier:_taskIdentifier withCompleteBlock:completeBlock withProgressBlock:progressBlock withCancelBlock:_option.cancellationSignal withAccess:_access];
}
- (void)run {
@autoreleasepool {
UInt32 offset = [self recoveryFromRecord];
[self nextTask:offset retriedTimes:0 host:[_config.zone up:_token isHttps:_config.useHttps frozenDomain:nil]];
}
}
@end
//
// QNUpToken.h
// QiniuSDK
//
// Created by bailong on 15/6/7.
// Copyright (c) 2015年 Qiniu. All rights reserved.
//
#import <Foundation/Foundation.h>
@interface QNUpToken : NSObject
+ (instancetype)parse:(NSString *)token;
@property (copy, nonatomic, readonly) NSString *access;
@property (copy, nonatomic, readonly) NSString *bucket;
@property (copy, nonatomic, readonly) NSString *token;
@property (readonly) BOOL hasReturnUrl;
- (NSString *)index;
@end
//
// QNUpToken.m
// QiniuSDK
//
// Created by bailong on 15/6/7.
// Copyright (c) 2015年 Qiniu. All rights reserved.
//
#import "QNUrlSafeBase64.h"
#import "QNUpToken.h"
@interface QNUpToken ()
- (instancetype)init:(NSDictionary *)policy token:(NSString *)token;
@end
@implementation QNUpToken
- (instancetype)init:(NSDictionary *)policy token:(NSString *)token {
if (self = [super init]) {
_token = token;
_access = [self getAccess];
_bucket = [self getBucket:policy];
_hasReturnUrl = (policy[@"returnUrl"] != nil);
}
return self;
}
- (NSString *)getAccess {
NSRange range = [_token rangeOfString:@":" options:NSCaseInsensitiveSearch];
return [_token substringToIndex:range.location];
}
- (NSString *)getBucket:(NSDictionary *)info {
NSString *scope = [info objectForKey:@"scope"];
if (!scope || [scope isKindOfClass:[NSNull class]]) {
return @"";
}
NSRange range = [scope rangeOfString:@":"];
if (range.location == NSNotFound) {
return scope;
}
return [scope substringToIndex:range.location];
}
+ (instancetype)parse:(NSString *)token {
if (token == nil) {
return nil;
}
NSArray *array = [token componentsSeparatedByString:@":"];
if (array == nil || array.count != 3) {
return nil;
}
NSData *data = [QNUrlSafeBase64 decodeString:array[2]];
NSError *tmp = nil;
NSDictionary *dict = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableLeaves error:&tmp];
if (tmp != nil || dict[@"scope"] == nil || dict[@"deadline"] == nil) {
return nil;
}
return [[QNUpToken alloc] init:dict token:token];
}
- (NSString *)index {
return [NSString stringWithFormat:@"%@:%@", _access, _bucket];
}
@end
//
// QNUploadInfoReporter.h
// QiniuSDK
//
// Created by WorkSpace_Sun on 2019/6/24.
// Copyright © 2019 Qiniu. All rights reserved.
//
#import <Foundation/Foundation.h>
#import "QNHttpDelegate.h"
typedef NS_ENUM(NSUInteger, QNReportType) {
ReportType_form,
ReportType_mkblk,
ReportType_bput,
ReportType_mkfile,
ReportType_block
};
@interface QNReportConfig : NSObject
- (id)init __attribute__((unavailable("Use sharedInstance: instead.")));
+ (instancetype)sharedInstance;
/**
* 是否开启sdk上传信息搜集 默认为YES
*/
@property (nonatomic, assign, getter=isRecordEnable) BOOL recordEnable;
/**
* 每次上传最小时间间隔 单位:分钟
*/
@property (nonatomic, assign) uint32_t interval;
/**
* 记录文件大于 uploadThreshold 后才可能触发上传,单位:字节 默认为4 * 1024
*/
@property (nonatomic, assign) uint64_t uploadThreshold;
/**
* 记录文件最大值 要大于 uploadThreshold 单位:字节 默认为2 * 1024 * 1024
*/
@property (nonatomic, assign) uint64_t maxRecordFileSize;
/**
* 记录文件所在文件夹目录 默认为:.../沙盒/Library/Caches/com.qiniu.report
*/
@property (nonatomic, copy) NSString *recordDirectory;
/**
* 信息上报服务器地址
*/
@property (nonatomic, copy, readonly) NSString *serverURL;
/**
* 信息上报请求超时时间 单位:秒 默认为10秒
*/
@property (nonatomic, assign, readonly) NSTimeInterval timeoutInterval;
@end
#define UploadInfoReporter [QNUploadInfoReporter sharedInstance]
@interface QNUploadInfoReporter : NSObject
@property (nonatomic, assign, readonly) NSTimeInterval lastReportTime;
- (id)init __attribute__((unavailable("Use sharedInstance: instead.")));
+ (instancetype)sharedInstance;
/**
* 上报统计信息
*
* @param requestType 请求类型
* @param responseInfo 返回信息
* @param bytesSent 已发送的字节数
* @param fileSize 总字节数
* @param token 上传凭证
*
*/
- (void)recordWithRequestType:(QNReportType)requestType
responseInfo:(QNResponseInfo *)responseInfo
bytesSent:(UInt32)bytesSent
fileSize:(UInt32)fileSize
token:(NSString *)token;
/**
* 清空统计信息
*/
- (void)clean;
@end
//
// QNUploadInfoReporter.m
// QiniuSDK
//
// Created by WorkSpace_Sun on 2019/6/24.
// Copyright © 2019 Qiniu. All rights reserved.
//
#import "QNUploadInfoReporter.h"
#import "QNResponseInfo.h"
#import "QNFile.h"
#import "QNUpToken.h"
#import "QNUserAgent.h"
#import "QNAsyncRun.h"
@implementation QNReportConfig
+ (instancetype)sharedInstance {
static QNReportConfig *sharedInstance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedInstance = [[self alloc] init];
});
return sharedInstance;
}
- (instancetype)init {
self = [super init];
if (self) {
_recordEnable = YES;
_interval = 10;
_serverURL = @"https://uplog.qbox.me/log/3";
_recordDirectory = [[NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) firstObject] stringByAppendingPathComponent:@"com.qiniu.report"];
_maxRecordFileSize = 2 * 1024 * 1024;
_uploadThreshold = 4 * 1024;
_timeoutInterval = 10;
}
return self;
}
@end
static const NSString *recorderFileName = @"recorder";
static const NSString *reportTypeValueList[] = {@"form", @"mkblk", @"bput", @"mkfile", @"block"};
@interface QNUploadInfoReporter ()
@property (nonatomic, strong) QNReportConfig *config;
@property (nonatomic, assign) NSTimeInterval lastReportTime;
@property (nonatomic, strong) NSFileManager *fileManager;
@property (nonatomic, strong) NSString *recorderFilePath;
@property (nonatomic, strong) dispatch_queue_t recordQueue;
@property (nonatomic, strong) dispatch_semaphore_t semaphore;
@end
@implementation QNUploadInfoReporter
+ (instancetype)sharedInstance {
static QNUploadInfoReporter *sharedInstance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedInstance = [[self alloc] init];
});
return sharedInstance;
}
- (instancetype)init {
self = [super init];
if (self) {
_config = [QNReportConfig sharedInstance];
_lastReportTime = 0;
_recorderFilePath = [NSString stringWithFormat:@"%@/%@", _config.recordDirectory, recorderFileName];
_fileManager = [NSFileManager defaultManager];
_recordQueue = dispatch_queue_create("com.qiniu.report", DISPATCH_QUEUE_SERIAL);
}
return self;
}
- (void)clean {
if ([_fileManager fileExistsAtPath:_recorderFilePath]) {
NSError *error = nil;
[_fileManager removeItemAtPath:_recorderFilePath error:&error];
if (error) {
QNAsyncRunInMain(^{
NSLog(@"remove recorder file failed: %@", error);
});
return;
}
}
}
- (BOOL)checkReportAvailable {
if (!_config.isRecordEnable) return NO;
if (!(_config.maxRecordFileSize > _config.uploadThreshold)) {
QNAsyncRunInMain(^{
NSLog(@"maxRecordFileSize must be larger than uploadThreshold");
});
return NO;
}
return YES;
}
- (void)recordWithRequestType:(QNReportType)requestType
responseInfo:(QNResponseInfo *)responseInfo
bytesSent:(UInt32)bytesSent
fileSize:(UInt32)fileSize
token:(NSString *)token {
if (![self checkReportAvailable]) return;
NSString *nullString = @"";
NSArray *reportItems = @[
[NSString stringWithFormat:@"%d", responseInfo.statusCode] ? [NSString stringWithFormat:@"%d", responseInfo.statusCode] : nullString,
responseInfo.reqId ? responseInfo.reqId : nullString,
responseInfo.host ? responseInfo.host : nullString,
responseInfo.serverIp ? responseInfo.serverIp : nullString,
nullString,
[NSString stringWithFormat:@"%.1f", responseInfo.duration * 1000] ? [NSString stringWithFormat:@"%.1f", responseInfo.duration * 1000] : nullString,
[NSString stringWithFormat:@"%llu", responseInfo.timeStamp] ? [NSString stringWithFormat:@"%llu", responseInfo.timeStamp] : nullString,
[NSString stringWithFormat:@"%u", (unsigned int)bytesSent] ? [NSString stringWithFormat:@"%u", (unsigned int)bytesSent] : nullString,
reportTypeValueList[requestType],
[NSString stringWithFormat:@"%u", (unsigned int)fileSize] ? [NSString stringWithFormat:@"%u", (unsigned int)fileSize] : nullString
];
// 串行队列处理文件读写
dispatch_async(_recordQueue, ^{
[self innerRecordWithUploadResult:[reportItems componentsJoinedByString:@","] uploadToken:token];
});
}
- (void)innerRecordWithUploadResult:(NSString *)result uploadToken:(NSString *)token {
// 检查recorder文件夹是否存在
NSError *error = nil;
if (![_fileManager fileExistsAtPath:_config.recordDirectory]) {
[_fileManager createDirectoryAtPath:_config.recordDirectory withIntermediateDirectories:YES attributes:nil error:&error];
if (error) {
QNAsyncRunInMain(^{
NSLog(@"create record directory failed, please check record directory: %@", error.localizedDescription);
});
return;
}
}
// 拼接换行符
NSString *finalRecordInfo = [result stringByAppendingString:@"\n"];
if (![_fileManager fileExistsAtPath:_recorderFilePath]) {
// 如果recordFile不存在,创建文件并写入首行,首次不上传
[finalRecordInfo writeToFile:_recorderFilePath atomically:YES encoding:NSUTF8StringEncoding error:&error];
} else {
// recordFile存在,拼接文件内容、上传到服务器
QNFile *file = [[QNFile alloc] init:_recorderFilePath error:&error];
if (error) {
QNAsyncRunInMain(^{
NSLog(@"create QNFile with path failed: %@", error.localizedDescription);
});
return;
}
// 判断recorder文件大小是否超过maxRecordFileSize
if (file.size < _config.maxRecordFileSize) {
// 上传信息写入recorder文件
NSFileHandle *fileHandler = [NSFileHandle fileHandleForUpdatingAtPath:_recorderFilePath];
[fileHandler seekToEndOfFile];
[fileHandler writeData:[finalRecordInfo dataUsingEncoding:NSUTF8StringEncoding]];
[fileHandler closeFile];
}
// 判断是否满足上传条件:文件大于上报临界值 && (首次上传 || 距上次上传时间大于_config.interval)
NSTimeInterval currentTime = [[NSDate dateWithTimeIntervalSinceNow:0] timeIntervalSince1970];
if (file.size > _config.uploadThreshold && (_lastReportTime == 0 || currentTime - _lastReportTime > _config.interval * 60)) {
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:[NSURL URLWithString:_config.serverURL]];
[request setValue:[NSString stringWithFormat:@"UpToken %@", token] forHTTPHeaderField:@"Authorization"];
[request setValue:[[QNUserAgent sharedInstance] getUserAgent:[QNUpToken parse:token].access] forHTTPHeaderField:@"User-Agent"];
[request setHTTPMethod:@"POST"];
[request setTimeoutInterval:_config.timeoutInterval];
__block NSURLSession *session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration]];
NSURLSessionUploadTask *uploadTask = [session uploadTaskWithRequest:request fromFile:[NSURL fileURLWithPath:_recorderFilePath] completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response;
if (httpResponse.statusCode == 200) {
self.lastReportTime = [[NSDate dateWithTimeIntervalSinceNow:0] timeIntervalSince1970];
[self clean];
} else {
QNAsyncRunInMain(^{
NSLog(@"upload info report failed: %@", error.localizedDescription);
});
}
[session finishTasksAndInvalidate];
dispatch_semaphore_signal(self.semaphore);
}];
[uploadTask resume];
// 控制上传过程中,文件内容不被修改
_semaphore = dispatch_semaphore_create(0);
dispatch_semaphore_wait(_semaphore, DISPATCH_TIME_FOREVER);
}
}
}
@end
//
// QNUploader.h
// QiniuSDK
//
// Created by bailong on 14-9-28.
// Copyright (c) 2014年 Qiniu. All rights reserved.
//
#import <Foundation/Foundation.h>
#import "QNRecorderDelegate.h"
@class QNResponseInfo;
@class QNUploadOption;
@class QNConfiguration;
@class ALAsset;
@class PHAsset;
@class PHAssetResource;
/**
* 上传完成后的回调函数
*
* @param info 上下文信息,包括状态码,错误值
* @param key 上传时指定的key,原样返回
* @param resp 上传成功会返回文件信息,失败为nil; 可以通过此值是否为nil 判断上传结果
*/
typedef void (^QNUpCompletionHandler)(QNResponseInfo *info, NSString *key, NSDictionary *resp);
/**
管理上传的类,可以生成一次,持续使用,不必反复创建。
*/
@interface QNUploadManager : NSObject
/**
* 默认构造方法,没有持久化记录
*
* @return 上传管理类实例
*/
- (instancetype)init;
/**
* 使用一个持久化的记录接口进行记录的构造方法
*
* @param recorder 持久化记录接口实现
*
* @return 上传管理类实例
*/
- (instancetype)initWithRecorder:(id<QNRecorderDelegate>)recorder;
/**
* 使用持久化记录接口以及持久化key生成函数的构造方法,默认情况下使用上传存储的key, 如果key为nil或者有特殊字符比如/,建议使用自己的生成函数
*
* @param recorder 持久化记录接口实现
* @param recorderKeyGenerator 持久化记录key生成函数
*
* @return 上传管理类实例
*/
- (instancetype)initWithRecorder:(id<QNRecorderDelegate>)recorder
recorderKeyGenerator:(QNRecorderKeyGenerator)recorderKeyGenerator;
/**
* 使用配置信息生成上传实例
*
* @param config 配置信息
*
* @return 上传管理类实例
*/
- (instancetype)initWithConfiguration:(QNConfiguration *)config;
/**
* 方便使用的单例方法
*
* @param config 配置信息
*
* @return 上传管理类实例
*/
+ (instancetype)sharedInstanceWithConfiguration:(QNConfiguration *)config;
/**
* 直接上传数据
*
* @param data 待上传的数据
* @param key 上传到云存储的key,为nil时表示是由七牛生成
* @param token 上传需要的token, 由服务器生成
* @param completionHandler 上传完成后的回调函数
* @param option 上传时传入的可选参数
*/
- (void)putData:(NSData *)data
key:(NSString *)key
token:(NSString *)token
complete:(QNUpCompletionHandler)completionHandler
option:(QNUploadOption *)option;
/**
* 上传文件
*
* @param filePath 文件路径
* @param key 上传到云存储的key,为nil时表示是由七牛生成
* @param token 上传需要的token, 由服务器生成
* @param completionHandler 上传完成后的回调函数
* @param option 上传时传入的可选参数
*/
- (void)putFile:(NSString *)filePath
key:(NSString *)key
token:(NSString *)token
complete:(QNUpCompletionHandler)completionHandler
option:(QNUploadOption *)option;
/**
* 上传ALAsset文件
*
* @param alasset ALAsset文件
* @param key 上传到云存储的key,为nil时表示是由七牛生成
* @param token 上传需要的token, 由服务器生成
* @param completionHandler 上传完成后的回调函数
* @param option 上传时传入的可选参数
*/
- (void)putALAsset:(ALAsset *)asset
key:(NSString *)key
token:(NSString *)token
complete:(QNUpCompletionHandler)completionHandler
option:(QNUploadOption *)option;
/**
* 上传PHAsset文件(IOS8 andLater)
*
* @param asset PHAsset文件
* @param key 上传到云存储的key,为nil时表示是由七牛生成
* @param token 上传需要的token, 由服务器生成
* @param completionHandler 上传完成后的回调函数
* @param option 上传时传入的可选参数
*/
- (void)putPHAsset:(PHAsset *)asset
key:(NSString *)key
token:(NSString *)token
complete:(QNUpCompletionHandler)completionHandler
option:(QNUploadOption *)option;
/**
* 上传PHAssetResource文件(IOS9.1 andLater)
*
* @param asset PHAssetResource文件
* @param key 上传到云存储的key,为nil时表示是由七牛生成
* @param token 上传需要的token, 由服务器生成
* @param completionHandler 上传完成后的回调函数
* @param option 上传时传入的可选参数
*/
- (void)putPHAssetResource:(PHAssetResource *)assetResource
key:(NSString *)key
token:(NSString *)token
complete:(QNUpCompletionHandler)completionHandler
option:(QNUploadOption *)option;
@end
//
// QNUploader.h
// QiniuSDK
//
// Created by bailong on 14-9-28.
// Copyright (c) 2014年 Qiniu. All rights reserved.
//
#import <Foundation/Foundation.h>
#if __IPHONE_OS_VERSION_MIN_REQUIRED
#import "QNALAssetFile.h"
#import <AssetsLibrary/AssetsLibrary.h>
#import <MobileCoreServices/MobileCoreServices.h>
#import <UIKit/UIKit.h>
#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 80000
#import "QNPHAssetFile.h"
#import <Photos/Photos.h>
#endif
#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 90100
#import "QNPHAssetResource.h"
#endif
#else
#import <CoreServices/CoreServices.h>
#endif
#import "QNAsyncRun.h"
#import "QNConfiguration.h"
#import "QNCrc32.h"
#import "QNFile.h"
#import "QNFormUpload.h"
#import "QNResponseInfo.h"
#import "QNResumeUpload.h"
#import "QNSessionManager.h"
#import "QNSystem.h"
#import "QNUpToken.h"
#import "QNUploadManager.h"
#import "QNUploadOption+Private.h"
#import "QNConcurrentResumeUpload.h"
@interface QNUploadManager ()
@property (nonatomic) id<QNHttpDelegate> httpManager;
@property (nonatomic) QNConfiguration *config;
@end
@implementation QNUploadManager
- (instancetype)init {
return [self initWithConfiguration:nil];
}
- (instancetype)initWithRecorder:(id<QNRecorderDelegate>)recorder {
return [self initWithRecorder:recorder recorderKeyGenerator:nil];
}
- (instancetype)initWithRecorder:(id<QNRecorderDelegate>)recorder
recorderKeyGenerator:(QNRecorderKeyGenerator)recorderKeyGenerator {
QNConfiguration *config = [QNConfiguration build:^(QNConfigurationBuilder *builder) {
builder.recorder = recorder;
builder.recorderKeyGen = recorderKeyGenerator;
}];
return [self initWithConfiguration:config];
}
- (instancetype)initWithConfiguration:(QNConfiguration *)config {
if (self = [super init]) {
if (config == nil) {
config = [QNConfiguration build:^(QNConfigurationBuilder *builder){
}];
}
_config = config;
#if (defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 70000) || (defined(__MAC_OS_X_VERSION_MAX_ALLOWED) && __MAC_OS_X_VERSION_MAX_ALLOWED >= 1090)
_httpManager = [[QNSessionManager alloc] initWithProxy:config.proxy timeout:config.timeoutInterval urlConverter:config.converter];
#endif
}
return self;
}
+ (instancetype)sharedInstanceWithConfiguration:(QNConfiguration *)config {
static QNUploadManager *sharedInstance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedInstance = [[self alloc] initWithConfiguration:config];
});
return sharedInstance;
}
+ (BOOL)checkAndNotifyError:(NSString *)key
token:(NSString *)token
input:(NSObject *)input
complete:(QNUpCompletionHandler)completionHandler {
NSString *desc = nil;
if (completionHandler == nil) {
@throw [NSException exceptionWithName:NSInvalidArgumentException
reason:@"no completionHandler"
userInfo:nil];
return YES;
}
if (input == nil) {
desc = @"no input data";
} else if (token == nil || [token isEqual:[NSNull null]] || [token isEqualToString:@""]) {
desc = @"no token";
}
if (desc != nil) {
QNAsyncRunInMain(^{
completionHandler([QNResponseInfo responseInfoWithInvalidArgument:desc], key, nil);
});
return YES;
}
return NO;
}
- (void)putData:(NSData *)data
key:(NSString *)key
token:(NSString *)token
complete:(QNUpCompletionHandler)completionHandler
option:(QNUploadOption *)option {
[self putData:data fileName:nil key:key token:token complete:completionHandler option:option];
}
- (void)putData:(NSData *)data
fileName:(NSString *)fileName
key:(NSString *)key
token:(NSString *)token
complete:(QNUpCompletionHandler)completionHandler
option:(QNUploadOption *)option {
if ([QNUploadManager checkAndNotifyError:key token:token input:data complete:completionHandler]) {
return;
}
QNUpToken *t = [QNUpToken parse:token];
if (t == nil) {
QNAsyncRunInMain(^{
completionHandler([QNResponseInfo responseInfoWithInvalidToken:@"invalid token"], key, nil);
});
return;
}
[_config.zone preQuery:t on:^(int code) {
if (code != 0) {
QNAsyncRunInMain(^{
completionHandler([QNResponseInfo responseInfoWithInvalidToken:@"get zone failed"], key, nil);
});
return;
}
if ([data length] == 0) {
QNAsyncRunInMain(^{
completionHandler([QNResponseInfo responseInfoOfZeroData:nil], key, nil);
});
return;
}
QNUpCompletionHandler complete = ^(QNResponseInfo *info, NSString *key, NSDictionary *resp) {
QNAsyncRunInMain(^{
completionHandler(info, key, resp);
});
};
QNFormUpload *up = [[QNFormUpload alloc]
initWithData:data
withKey:key
withFileName:fileName
withToken:t
withCompletionHandler:complete
withOption:option
withHttpManager:_httpManager
withConfiguration:_config];
QNAsyncRun(^{
[up put];
});
}];
}
- (void)putFileInternal:(id<QNFileDelegate>)file
key:(NSString *)key
token:(NSString *)token
complete:(QNUpCompletionHandler)completionHandler
option:(QNUploadOption *)option {
@autoreleasepool {
QNUpToken *t = [QNUpToken parse:token];
if (t == nil) {
QNAsyncRunInMain(^{
completionHandler([QNResponseInfo responseInfoWithInvalidToken:@"invalid token"], key, nil);
});
return;
}
[_config.zone preQuery:t on:^(int code) {
if (code != 0) {
QNAsyncRunInMain(^{
completionHandler([QNResponseInfo responseInfoWithInvalidToken:@"get zone failed"], key, nil);
});
return;
}
QNUpCompletionHandler complete = ^(QNResponseInfo *info, NSString *key, NSDictionary *resp) {
[file close];
QNAsyncRunInMain(^{
completionHandler(info, key, resp);
});
};
if ([file size] <= _config.putThreshold) {
NSError *error;
NSData *data = [file readAllWithError:&error];
if (error) {
QNAsyncRunInMain(^{
completionHandler([QNResponseInfo responseInfoWithFileError:error], key, nil);
});
return;
}
NSString *fileName = [[file path] lastPathComponent];
[self putData:data fileName:fileName key:key token:token complete:completionHandler option:option];
return;
}
NSString *recorderKey = key;
if (_config.recorder != nil && _config.recorderKeyGen != nil) {
recorderKey = _config.recorderKeyGen(key, [file path]);
}
NSLog(@"recorder %@", _config.recorder);
if (_config.useConcurrentResumeUpload) {
QNConcurrentResumeUpload *up = [[QNConcurrentResumeUpload alloc]
initWithFile:file
withKey:key
withToken:t
withRecorder:_config.recorder
withRecorderKey:recorderKey
withHttpManager:_httpManager
withCompletionHandler:completionHandler
withOption:option
withConfiguration:_config];
QNAsyncRun(^{
[up run];
});
} else {
QNResumeUpload *up = [[QNResumeUpload alloc]
initWithFile:file
withKey:key
withToken:t
withCompletionHandler:complete
withOption:option
withRecorder:_config.recorder
withRecorderKey:recorderKey
withHttpManager:_httpManager
withConfiguration:_config];
QNAsyncRun(^{
[up run];
});
}
}];
}
}
- (void)putFile:(NSString *)filePath
key:(NSString *)key
token:(NSString *)token
complete:(QNUpCompletionHandler)completionHandler
option:(QNUploadOption *)option {
if ([QNUploadManager checkAndNotifyError:key token:token input:filePath complete:completionHandler]) {
return;
}
@autoreleasepool {
NSError *error = nil;
__block QNFile *file = [[QNFile alloc] init:filePath error:&error];
if (error) {
QNAsyncRunInMain(^{
QNResponseInfo *info = [QNResponseInfo responseInfoWithFileError:error];
completionHandler(info, key, nil);
});
return;
}
[self putFileInternal:file key:key token:token complete:completionHandler option:option];
}
}
- (void)putALAsset:(ALAsset *)asset
key:(NSString *)key
token:(NSString *)token
complete:(QNUpCompletionHandler)completionHandler
option:(QNUploadOption *)option {
#if __IPHONE_OS_VERSION_MIN_REQUIRED
if ([QNUploadManager checkAndNotifyError:key token:token input:asset complete:completionHandler]) {
return;
}
@autoreleasepool {
NSError *error = nil;
__block QNALAssetFile *file = [[QNALAssetFile alloc] init:asset error:&error];
if (error) {
QNAsyncRunInMain(^{
QNResponseInfo *info = [QNResponseInfo responseInfoWithFileError:error];
completionHandler(info, key, nil);
});
return;
}
[self putFileInternal:file key:key token:token complete:completionHandler option:option];
}
#endif
}
- (void)putPHAsset:(PHAsset *)asset
key:(NSString *)key
token:(NSString *)token
complete:(QNUpCompletionHandler)completionHandler
option:(QNUploadOption *)option {
#if (defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 80000)
if ([QNUploadManager checkAndNotifyError:key token:token input:asset complete:completionHandler]) {
return;
}
@autoreleasepool {
NSError *error = nil;
__block QNPHAssetFile *file = [[QNPHAssetFile alloc] init:asset error:&error];
if (error) {
QNAsyncRunInMain(^{
QNResponseInfo *info = [QNResponseInfo responseInfoWithFileError:error];
completionHandler(info, key, nil);
});
return;
}
[self putFileInternal:file key:key token:token complete:completionHandler option:option];
}
#endif
}
- (void)putPHAssetResource:(PHAssetResource *)assetResource
key:(NSString *)key
token:(NSString *)token
complete:(QNUpCompletionHandler)completionHandler
option:(QNUploadOption *)option {
#if (defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 90100)
if ([QNUploadManager checkAndNotifyError:key token:token input:assetResource complete:completionHandler]) {
return;
}
@autoreleasepool {
NSError *error = nil;
__block QNPHAssetResource *file = [[QNPHAssetResource alloc] init:assetResource error:&error];
if (error) {
QNAsyncRunInMain(^{
QNResponseInfo *info = [QNResponseInfo responseInfoWithFileError:error];
completionHandler(info, key, nil);
});
return;
}
[self putFileInternal:file key:key token:token complete:completionHandler option:option];
}
#endif
}
@end
//
// QNUploadOption+Private.h
// QiniuSDK
//
// Created by bailong on 14/10/5.
// Copyright (c) 2014年 Qiniu. All rights reserved.
//
#import "QNUploadOption.h"
@interface QNUploadOption (Private)
@property (nonatomic, getter=priv_isCancelled, readonly) BOOL cancelled;
@end
//
// QNUploadOption.h
// QiniuSDK
//
// Created by bailong on 14/10/4.
// Copyright (c) 2014年 Qiniu. All rights reserved.
//
#import <Foundation/Foundation.h>
/**
* 上传进度回调函数
*
* @param key 上传时指定的存储key
* @param percent 进度百分比
*/
typedef void (^QNUpProgressHandler)(NSString *key, float percent);
/**
* 上传中途取消函数
*
* @return 如果想取消,返回True, 否则返回No
*/
typedef BOOL (^QNUpCancellationSignal)(void);
/**
* 可选参数集合,此类初始化后sdk上传使用时 不会对此进行改变;如果参数没有变化以及没有使用依赖,可以重复使用。
*/
@interface QNUploadOption : NSObject
/**
* 用于服务器上传回调通知的自定义参数,参数的key必须以x: 开头
*/
@property (copy, nonatomic, readonly) NSDictionary *params;
/**
* 指定文件的mime类型
*/
@property (copy, nonatomic, readonly) NSString *mimeType;
/**
* 是否进行crc校验
*/
@property (readonly) BOOL checkCrc;
/**
* 进度回调函数
*/
@property (copy, readonly) QNUpProgressHandler progressHandler;
/**
* 中途取消函数
*/
@property (copy, readonly) QNUpCancellationSignal cancellationSignal;
/**
* 可选参数的初始化方法
*
* @param mimeType mime类型
* @param progress 进度函数
* @param params 自定义服务器回调参数
* @param check 是否进行crc检查
* @param cancellation 中途取消函数
*
* @return 可选参数类实例
*/
- (instancetype)initWithMime:(NSString *)mimeType
progressHandler:(QNUpProgressHandler)progress
params:(NSDictionary *)params
checkCrc:(BOOL)check
cancellationSignal:(QNUpCancellationSignal)cancellation;
- (instancetype)initWithProgessHandler:(QNUpProgressHandler)progress DEPRECATED_ATTRIBUTE;
- (instancetype)initWithProgressHandler:(QNUpProgressHandler)progress;
/**
* 内部使用,默认的参数实例
*
* @return 可选参数类实例
*/
+ (instancetype)defaultOptions;
@end
//
// QNUploadOption.m
// QiniuSDK
//
// Created by bailong on 14/10/4.
// Copyright (c) 2014年 Qiniu. All rights reserved.
//
#import "QNUploadOption+Private.h"
#import "QNUploadManager.h"
static NSString *mime(NSString *mimeType) {
if (mimeType == nil || [mimeType isEqualToString:@""]) {
return @"application/octet-stream";
}
return mimeType;
}
@implementation QNUploadOption
+ (NSDictionary *)filteParam:(NSDictionary *)params {
NSMutableDictionary *ret = [NSMutableDictionary dictionary];
if (params == nil) {
return ret;
}
[params enumerateKeysAndObjectsUsingBlock:^(NSString *key, NSString *obj, BOOL *stop) {
if ([key hasPrefix:@"x:"] && ![obj isEqualToString:@""]) {
ret[key] = obj;
}
}];
return ret;
}
- (instancetype)initWithProgessHandler:(QNUpProgressHandler)progress {
return [self initWithMime:nil progressHandler:progress params:nil checkCrc:NO cancellationSignal:nil];
}
- (instancetype)initWithProgressHandler:(QNUpProgressHandler)progress {
return [self initWithMime:nil progressHandler:progress params:nil checkCrc:NO cancellationSignal:nil];
}
- (instancetype)initWithMime:(NSString *)mimeType
progressHandler:(QNUpProgressHandler)progress
params:(NSDictionary *)params
checkCrc:(BOOL)check
cancellationSignal:(QNUpCancellationSignal)cancel {
if (self = [super init]) {
_mimeType = mime(mimeType);
_progressHandler = progress != nil ? progress : ^(NSString *key, float percent) {
};
_params = [QNUploadOption filteParam:params];
_checkCrc = check;
_cancellationSignal = cancel != nil ? cancel : ^BOOL() {
return NO;
};
}
return self;
}
+ (instancetype)defaultOptions {
return [[QNUploadOption alloc] initWithMime:nil progressHandler:nil params:nil checkCrc:NO cancellationSignal:nil];
}
@end
# Qiniu Resource Storage SDK for Objective-C
[![@qiniu on weibo](http://img.shields.io/badge/weibo-%40qiniutek-blue.svg)](http://weibo.com/qiniutek)
[![Software License](https://img.shields.io/badge/license-MIT-brightgreen.svg)](LICENSE.md)
[![Build Status](https://travis-ci.org/qiniu/objc-sdk.svg?branch=master)](https://travis-ci.org/qiniu/objc-sdk)
[![codecov](https://codecov.io/gh/qiniu/objc-sdk/branch/master/graph/badge.svg)](https://codecov.io/gh/qiniu/objc-sdk)
[![Latest Stable Version](http://img.shields.io/cocoapods/v/Qiniu.svg)](https://github.com/qiniu/objc-sdk/releases)
![Platform](http://img.shields.io/cocoapods/p/Qiniu.svg)
## 安装
通过 CocoaPods
```ruby
pod "Qiniu", "~> 7.2"
```
## 运行环境
| Qiniu SDK 版本 | 最低 iOS版本 | 最低 OS X 版本 | Notes |
| :--------------------------------------: | :------: | :--------: | :-----------: |
| 7.2.x | iOS 7 | OS X 10.9 | Xcode 最低版本 6. |
| 7.1.x / AFNetworking-3.x | iOS 7 | OS X 10.9 | Xcode 最低版本 6. |
| [7.0.x / AFNetworking-2.x](https://github.com/qiniu/objc-sdk/tree/7.0.x/AFNetworking-2.x) | iOS 6 | OS X 10.8 | Xcode 最低版本 5. |
| [7.x / AFNetworking-1.x](https://github.com/qiniu/objc-sdk/tree/AFNetworking-1.x) | iOS 5 | OS X 10.7 | Xcode 最低版本 5. |
| [6.x](https://github.com/qiniu/ios-sdk) | iOS 6 | None | Xcode 最低版本 5. |
## 使用方法
### 简单上传
```Objective-C
#import <QiniuSDK.h>
...
NSString *token = @"从服务端SDK获取";
QNUploadManager *upManager = [[QNUploadManager alloc] init];
NSData *data = [@"Hello, World!" dataUsingEncoding : NSUTF8StringEncoding];
[upManager putData:data key:@"hello" token:token
complete: ^(QNResponseInfo *info, NSString *key, NSDictionary *resp) {
NSLog(@"%@", info);
NSLog(@"%@", resp);
} option:[QNUploadOption defaultOptions]];
...
```
### 如使用最新版的sdk,默认自动判断上传空间。如需要指定上传区域,可以按如下方式上传:
```Objective-C
#import <QiniuSDK.h>
...
QNConfiguration *config = [QNConfiguration build:^(QNConfigurationBuilder *builder) {
builder.useHttps = NO;// 是否使用https
builder.zone = [QNFixedZone zone0];// 指定华东区域
// builder.zone = [QNFixedZone zone1];// 指定华北区域
// builder.zone = [QNFixedZone zone2];// 指定华南区域
// builder.zone = [QNFixedZone zoneNa0];// 指定北美区域
// builder.zone = [QNFixedZone zoneAs0];// 指定东南亚区域
}];
QNUploadManager *upManager = [[QNUploadManager alloc] initWithConfiguration:config];
QNUploadOption *option = [[QNUploadOption alloc] initWithProgressHandler:^(NSString *key, float percent) {
NSLog(@"progress %f", percent);
}];
NSData *data = [@"Hello, World!" dataUsingEncoding:NSUTF8StringEncoding];
NSString *token = @"从服务端SDK获取";
[upManager putData:data key:@"hello" token:token complete: ^(QNResponseInfo *info, NSString *key, NSDictionary *resp) {
NSLog(@"%@", info);
NSLog(@"%@", resp);
} option:option];
...
```
建议 QNUploadManager 创建一次重复使用, 或者使用单例方式创建.
## 测试
### 所有测试
``` bash
$ xcodebuild test -workspace QiniuSDK.xcworkspace -scheme QiniuSDK_Mac -configuration Release -destination 'platform=macOS,arch=x86_64'
```
### 指定测试
可以在单元测试上修改, 熟悉 SDK
``` bash
$ xcodebuild test -workspace QiniuSDK.xcworkspace -scheme QiniuSDK_Mac -configuration Release -destination 'platform=macOS,arch=x86_64' -only-testing:"QiniuSDK_MacTests/QNResumeUploadTest/test5M"
```
## 示例代码
* 完整的demo 见 QiniuDemo 目录下的代码
* 具体细节的一些配置 可参考 QiniuSDKTests 下面的一些单元测试,以及源代码
## 常见问题
- 如果碰到 crc 链接错误, 请把 libz.dylib 加入到项目中去
- 如果碰到 res_9_ninit 链接错误, 请把 libresolv.dylib 加入到项目中去
- 如果需要支持 iOS 5 或者支持 RestKit, 请用 AFNetworking 1.x 分支的版本
- 如果碰到其他编译错误, 请参考 CocoaPods 的 [troubleshooting](http://guides.cocoapods.org/using/troubleshooting.html)
- iOS 9+ 强制使用https,需要在project build info 添加NSAppTransportSecurity类型Dictionary。在NSAppTransportSecurity下添加NSAllowsArbitraryLoads类型Boolean,值设为YES。 具体操作可参见 http://blog.csdn.net/guoer9973/article/details/48622823
## 代码贡献
详情参考 [代码提交指南](https://github.com/qiniu/objc-sdk/blob/master/Contributing.md).
## 贡献记录
- [所有贡献者](https://github.com/qiniu/objc-sdk/contributors)
## 联系我们
- 如果需要帮助, 请提交工单 (在 portal 右侧点击咨询和建议提交工单, 或者直接向 support@qiniu.com 发送邮件)
- 如果有什么问题, 可以到问答社区提问, [问答社区](http://qiniu.segmentfault.com/)
- 更详细的文档, 见 [官方文档站](http://developer.qiniu.com/)
- 如果发现了 bug, 欢迎提交 [issue](https://github.com/qiniu/objc-sdk/issues)
- 如果有功能需求, 欢迎提交 [issue](https://github.com/qiniu/objc-sdk/issues)
- 如果要提交代码, 欢迎提交 pull request
- 欢迎关注我们的 [微信](http://www.qiniu.com/#weixin) && [微博](http://weibo.com/qiniutek), 及时获取动态信息
## 代码许可
The MIT License (MIT). 详情见 [License 文件](https://github.com/qiniu/objc-sdk/blob/master/LICENSE).
...@@ -49,23 +49,12 @@ public final class Constraint { ...@@ -49,23 +49,12 @@ public final class Constraint {
public var layoutConstraints: [LayoutConstraint] public var layoutConstraints: [LayoutConstraint]
public var isActive: Bool { public var isActive: Bool {
set { for layoutConstraint in self.layoutConstraints {
if newValue { if layoutConstraint.isActive {
activate() return true
}
else {
deactivate()
}
}
get {
for layoutConstraint in self.layoutConstraints {
if layoutConstraint.isActive {
return true
}
} }
return false
} }
return false
} }
// MARK: Initialization // MARK: Initialization
...@@ -225,12 +214,6 @@ public final class Constraint { ...@@ -225,12 +214,6 @@ public final class Constraint {
return self return self
} }
@discardableResult
public func update(priority: ConstraintPriority) -> Constraint {
self.priority = priority.value
return self
}
@available(*, deprecated:3.0, message:"Use update(offset: ConstraintOffsetTarget) instead.") @available(*, deprecated:3.0, message:"Use update(offset: ConstraintOffsetTarget) instead.")
public func updateOffset(amount: ConstraintOffsetTarget) -> Void { self.update(offset: amount) } public func updateOffset(amount: ConstraintOffsetTarget) -> Void { self.update(offset: amount) }
......
...@@ -28,9 +28,7 @@ ...@@ -28,9 +28,7 @@
#endif #endif
internal struct ConstraintAttributes : OptionSet, ExpressibleByIntegerLiteral { internal struct ConstraintAttributes : OptionSet {
typealias IntegerLiteralType = UInt
internal init(rawValue: UInt) { internal init(rawValue: UInt) {
self.rawValue = rawValue self.rawValue = rawValue
...@@ -41,13 +39,10 @@ internal struct ConstraintAttributes : OptionSet, ExpressibleByIntegerLiteral { ...@@ -41,13 +39,10 @@ internal struct ConstraintAttributes : OptionSet, ExpressibleByIntegerLiteral {
internal init(nilLiteral: ()) { internal init(nilLiteral: ()) {
self.rawValue = 0 self.rawValue = 0
} }
internal init(integerLiteral rawValue: IntegerLiteralType) {
self.init(rawValue: rawValue)
}
internal private(set) var rawValue: UInt internal private(set) var rawValue: UInt
internal static var allZeros: ConstraintAttributes { return 0 } internal static var allZeros: ConstraintAttributes { return self.init(0) }
internal static func convertFromNilLiteral() -> ConstraintAttributes { return 0 } internal static func convertFromNilLiteral() -> ConstraintAttributes { return self.init(0) }
internal var boolValue: Bool { return self.rawValue != 0 } internal var boolValue: Bool { return self.rawValue != 0 }
internal func toRaw() -> UInt { return self.rawValue } internal func toRaw() -> UInt { return self.rawValue }
...@@ -56,57 +51,57 @@ internal struct ConstraintAttributes : OptionSet, ExpressibleByIntegerLiteral { ...@@ -56,57 +51,57 @@ internal struct ConstraintAttributes : OptionSet, ExpressibleByIntegerLiteral {
// normal // normal
internal static var none: ConstraintAttributes { return 0 } internal static var none: ConstraintAttributes { return self.init(0) }
internal static var left: ConstraintAttributes { return 1 } internal static var left: ConstraintAttributes { return self.init(1) }
internal static var top: ConstraintAttributes { return 2 } internal static var top: ConstraintAttributes { return self.init(2) }
internal static var right: ConstraintAttributes { return 4 } internal static var right: ConstraintAttributes { return self.init(4) }
internal static var bottom: ConstraintAttributes { return 8 } internal static var bottom: ConstraintAttributes { return self.init(8) }
internal static var leading: ConstraintAttributes { return 16 } internal static var leading: ConstraintAttributes { return self.init(16) }
internal static var trailing: ConstraintAttributes { return 32 } internal static var trailing: ConstraintAttributes { return self.init(32) }
internal static var width: ConstraintAttributes { return 64 } internal static var width: ConstraintAttributes { return self.init(64) }
internal static var height: ConstraintAttributes { return 128 } internal static var height: ConstraintAttributes { return self.init(128) }
internal static var centerX: ConstraintAttributes { return 256 } internal static var centerX: ConstraintAttributes { return self.init(256) }
internal static var centerY: ConstraintAttributes { return 512 } internal static var centerY: ConstraintAttributes { return self.init(512) }
internal static var lastBaseline: ConstraintAttributes { return 1024 } internal static var lastBaseline: ConstraintAttributes { return self.init(1024) }
@available(iOS 8.0, OSX 10.11, *) @available(iOS 8.0, OSX 10.11, *)
internal static var firstBaseline: ConstraintAttributes { return 2048 } internal static var firstBaseline: ConstraintAttributes { return self.init(2048) }
@available(iOS 8.0, *) @available(iOS 8.0, *)
internal static var leftMargin: ConstraintAttributes { return 4096 } internal static var leftMargin: ConstraintAttributes { return self.init(4096) }
@available(iOS 8.0, *) @available(iOS 8.0, *)
internal static var rightMargin: ConstraintAttributes { return 8192 } internal static var rightMargin: ConstraintAttributes { return self.init(8192) }
@available(iOS 8.0, *) @available(iOS 8.0, *)
internal static var topMargin: ConstraintAttributes { return 16384 } internal static var topMargin: ConstraintAttributes { return self.init(16384) }
@available(iOS 8.0, *) @available(iOS 8.0, *)
internal static var bottomMargin: ConstraintAttributes { return 32768 } internal static var bottomMargin: ConstraintAttributes { return self.init(32768) }
@available(iOS 8.0, *) @available(iOS 8.0, *)
internal static var leadingMargin: ConstraintAttributes { return 65536 } internal static var leadingMargin: ConstraintAttributes { return self.init(65536) }
@available(iOS 8.0, *) @available(iOS 8.0, *)
internal static var trailingMargin: ConstraintAttributes { return 131072 } internal static var trailingMargin: ConstraintAttributes { return self.init(131072) }
@available(iOS 8.0, *) @available(iOS 8.0, *)
internal static var centerXWithinMargins: ConstraintAttributes { return 262144 } internal static var centerXWithinMargins: ConstraintAttributes { return self.init(262144) }
@available(iOS 8.0, *) @available(iOS 8.0, *)
internal static var centerYWithinMargins: ConstraintAttributes { return 524288 } internal static var centerYWithinMargins: ConstraintAttributes { return self.init(524288) }
// aggregates // aggregates
internal static var edges: ConstraintAttributes { return 15 } internal static var edges: ConstraintAttributes { return self.init(15) }
internal static var size: ConstraintAttributes { return 192 } internal static var size: ConstraintAttributes { return self.init(192) }
internal static var center: ConstraintAttributes { return 768 } internal static var center: ConstraintAttributes { return self.init(768) }
@available(iOS 8.0, *) @available(iOS 8.0, *)
internal static var margins: ConstraintAttributes { return 61440 } internal static var margins: ConstraintAttributes { return self.init(61440) }
@available(iOS 8.0, *) @available(iOS 8.0, *)
internal static var centerWithinMargins: ConstraintAttributes { return 786432 } internal static var centerWithinMargins: ConstraintAttributes { return self.init(786432) }
internal var layoutAttributes:[LayoutAttribute] { internal var layoutAttributes:[LayoutAttribute] {
var attrs = [LayoutAttribute]() var attrs = [LayoutAttribute]()
......
...@@ -171,7 +171,15 @@ public class ConstraintMaker { ...@@ -171,7 +171,15 @@ public class ConstraintMaker {
} }
internal static func makeConstraints(item: LayoutConstraintItem, closure: (_ make: ConstraintMaker) -> Void) { internal static func makeConstraints(item: LayoutConstraintItem, closure: (_ make: ConstraintMaker) -> Void) {
let constraints = prepareConstraints(item: item, closure: closure) let maker = ConstraintMaker(item: item)
closure(maker)
var constraints: [Constraint] = []
for description in maker.descriptions {
guard let constraint = description.constraint else {
continue
}
constraints.append(constraint)
}
for constraint in constraints { for constraint in constraints {
constraint.activateIfNeeded(updatingExisting: false) constraint.activateIfNeeded(updatingExisting: false)
} }
...@@ -188,7 +196,15 @@ public class ConstraintMaker { ...@@ -188,7 +196,15 @@ public class ConstraintMaker {
return return
} }
let constraints = prepareConstraints(item: item, closure: closure) let maker = ConstraintMaker(item: item)
closure(maker)
var constraints: [Constraint] = []
for description in maker.descriptions {
guard let constraint = description.constraint else {
continue
}
constraints.append(constraint)
}
for constraint in constraints { for constraint in constraints {
constraint.activateIfNeeded(updatingExisting: true) constraint.activateIfNeeded(updatingExisting: true)
} }
......
...@@ -73,13 +73,3 @@ extension CGFloat: ConstraintPriorityTarget { ...@@ -73,13 +73,3 @@ extension CGFloat: ConstraintPriorityTarget {
} }
} }
#if os(iOS) || os(tvOS)
extension UILayoutPriority: ConstraintPriorityTarget {
public var constraintPriorityTargetValue: Float {
return self.rawValue
}
}
#endif
...@@ -25,13 +25,8 @@ import Foundation ...@@ -25,13 +25,8 @@ import Foundation
#if os(iOS) || os(tvOS) #if os(iOS) || os(tvOS)
import UIKit import UIKit
#if swift(>=4.2)
typealias LayoutRelation = NSLayoutConstraint.Relation
typealias LayoutAttribute = NSLayoutConstraint.Attribute
#else
typealias LayoutRelation = NSLayoutRelation typealias LayoutRelation = NSLayoutRelation
typealias LayoutAttribute = NSLayoutAttribute typealias LayoutAttribute = NSLayoutAttribute
#endif
typealias LayoutPriority = UILayoutPriority typealias LayoutPriority = UILayoutPriority
#else #else
import AppKit import AppKit
......
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleExecutable</key>
<string>${EXECUTABLE_NAME}</string>
<key>CFBundleIdentifier</key>
<string>${PRODUCT_BUNDLE_IDENTIFIER}</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>${PRODUCT_NAME}</string>
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>5.10.0</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>${CURRENT_PROJECT_VERSION}</string>
<key>NSPrincipalClass</key>
<string></string>
</dict>
</plist>
#import <Foundation/Foundation.h>
@interface PodsDummy_EVReflection : NSObject
@end
@implementation PodsDummy_EVReflection
@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
#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
FOUNDATION_EXPORT double EVReflectionVersionNumber;
FOUNDATION_EXPORT const unsigned char EVReflectionVersionString[];
framework module EVReflection {
umbrella header "EVReflection-umbrella.h"
export *
module * { export * }
}
CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/EVReflection
GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
OTHER_LDFLAGS = $(inherited) -framework "Foundation"
OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS
PODS_BUILD_DIR = ${BUILD_DIR}
PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
PODS_ROOT = ${SRCROOT}
PODS_TARGET_SRCROOT = ${PODS_ROOT}/EVReflection
PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier}
SKIP_INSTALL = YES
SWIFT_VERSION = 4.2
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleExecutable</key>
<string>${EXECUTABLE_NAME}</string>
<key>CFBundleIdentifier</key>
<string>${PRODUCT_BUNDLE_IDENTIFIER}</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>${PRODUCT_NAME}</string>
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>4.0.1</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>${CURRENT_PROJECT_VERSION}</string>
<key>NSPrincipalClass</key>
<string></string>
</dict>
</plist>
#import <Foundation/Foundation.h>
@interface PodsDummy_GM_Swift_Observable : NSObject
@end
@implementation PodsDummy_GM_Swift_Observable
@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
#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
FOUNDATION_EXPORT double GM_Swift_ObservableVersionNumber;
FOUNDATION_EXPORT const unsigned char GM_Swift_ObservableVersionString[];
framework module GM_Swift_Observable {
umbrella header "GM-Swift-Observable-umbrella.h"
export *
module * { export * }
}
CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/GM-Swift-Observable
GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS
PODS_BUILD_DIR = ${BUILD_DIR}
PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
PODS_ROOT = ${SRCROOT}
PODS_TARGET_SRCROOT = ${PODS_ROOT}/GM-Swift-Observable
PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier}
SKIP_INSTALL = YES
...@@ -18,6 +18,9 @@ ...@@ -18,6 +18,9 @@
#import "GMFaceV3Model.h" #import "GMFaceV3Model.h"
#import "GMAIFacePopViewObject.h" #import "GMAIFacePopViewObject.h"
#import "GMFaceV3Util.h" #import "GMFaceV3Util.h"
#import "ALUpLoadManager.h"
#import "GMAIConst.h"
#import "Target_AI.h"
FOUNDATION_EXPORT double GMAIVersionNumber; FOUNDATION_EXPORT double GMAIVersionNumber;
FOUNDATION_EXPORT const unsigned char GMAIVersionString[]; FOUNDATION_EXPORT const unsigned char GMAIVersionString[];
......
CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/GMAI CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/GMAI
FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/AFNetworking" "${PODS_CONFIGURATION_BUILD_DIR}/Alamofire" "${PODS_CONFIGURATION_BUILD_DIR}/GMBase" "${PODS_CONFIGURATION_BUILD_DIR}/GMCache" "${PODS_CONFIGURATION_BUILD_DIR}/GMHud" "${PODS_CONFIGURATION_BUILD_DIR}/GMJSONModel" "${PODS_CONFIGURATION_BUILD_DIR}/GMKit" "${PODS_CONFIGURATION_BUILD_DIR}/GMNetService" "${PODS_CONFIGURATION_BUILD_DIR}/GMNetworking" "${PODS_CONFIGURATION_BUILD_DIR}/GMPhobos" "${PODS_CONFIGURATION_BUILD_DIR}/GMRefresh" "${PODS_CONFIGURATION_BUILD_DIR}/MBProgressHUD" "${PODS_CONFIGURATION_BUILD_DIR}/MJRefresh" "${PODS_CONFIGURATION_BUILD_DIR}/Masonry" "${PODS_CONFIGURATION_BUILD_DIR}/SDWebImage" "${PODS_CONFIGURATION_BUILD_DIR}/SnapKit" "${PODS_CONFIGURATION_BUILD_DIR}/TMCache" "${PODS_CONFIGURATION_BUILD_DIR}/UITableView+FDTemplateLayoutCell" FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/AFNetworking" "${PODS_CONFIGURATION_BUILD_DIR}/Alamofire" "${PODS_CONFIGURATION_BUILD_DIR}/EVReflection" "${PODS_CONFIGURATION_BUILD_DIR}/GM-Swift-Observable" "${PODS_CONFIGURATION_BUILD_DIR}/GMBase" "${PODS_CONFIGURATION_BUILD_DIR}/GMBaseSwift" "${PODS_CONFIGURATION_BUILD_DIR}/GMCache" "${PODS_CONFIGURATION_BUILD_DIR}/GMFoundation" "${PODS_CONFIGURATION_BUILD_DIR}/GMHud" "${PODS_CONFIGURATION_BUILD_DIR}/GMJSONModel" "${PODS_CONFIGURATION_BUILD_DIR}/GMKit" "${PODS_CONFIGURATION_BUILD_DIR}/GMNetService" "${PODS_CONFIGURATION_BUILD_DIR}/GMNetworking" "${PODS_CONFIGURATION_BUILD_DIR}/GMPhobos" "${PODS_CONFIGURATION_BUILD_DIR}/GMRefresh" "${PODS_CONFIGURATION_BUILD_DIR}/GMRouter" "${PODS_CONFIGURATION_BUILD_DIR}/MBProgressHUD" "${PODS_CONFIGURATION_BUILD_DIR}/MJExtension" "${PODS_CONFIGURATION_BUILD_DIR}/MJRefresh" "${PODS_CONFIGURATION_BUILD_DIR}/Masonry" "${PODS_CONFIGURATION_BUILD_DIR}/Qiniu" "${PODS_CONFIGURATION_BUILD_DIR}/SDWebImage" "${PODS_CONFIGURATION_BUILD_DIR}/SnapKit" "${PODS_CONFIGURATION_BUILD_DIR}/TMCache" "${PODS_CONFIGURATION_BUILD_DIR}/UITableView+FDTemplateLayoutCell"
GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
OTHER_LDFLAGS = $(inherited) -weak_framework "UIKit" OTHER_LDFLAGS = $(inherited) -weak_framework "UIKit"
OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS
......
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleExecutable</key>
<string>${EXECUTABLE_NAME}</string>
<key>CFBundleIdentifier</key>
<string>${PRODUCT_BUNDLE_IDENTIFIER}</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>${PRODUCT_NAME}</string>
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>3.3.7</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>${CURRENT_PROJECT_VERSION}</string>
<key>NSPrincipalClass</key>
<string></string>
</dict>
</plist>
#import <Foundation/Foundation.h>
@interface PodsDummy_GMBaseSwift : NSObject
@end
@implementation PodsDummy_GMBaseSwift
@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
#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
FOUNDATION_EXPORT double GMBaseSwiftVersionNumber;
FOUNDATION_EXPORT const unsigned char GMBaseSwiftVersionString[];
framework module GMBaseSwift {
umbrella header "GMBaseSwift-umbrella.h"
export *
module * { export * }
}
CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/GMBaseSwift
FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/Alamofire" "${PODS_CONFIGURATION_BUILD_DIR}/EVReflection" "${PODS_CONFIGURATION_BUILD_DIR}/GM-Swift-Observable" "${PODS_CONFIGURATION_BUILD_DIR}/GMCache" "${PODS_CONFIGURATION_BUILD_DIR}/GMHud" "${PODS_CONFIGURATION_BUILD_DIR}/GMKit" "${PODS_CONFIGURATION_BUILD_DIR}/GMNetworking" "${PODS_CONFIGURATION_BUILD_DIR}/GMPhobos" "${PODS_CONFIGURATION_BUILD_DIR}/GMRefresh" "${PODS_CONFIGURATION_BUILD_DIR}/MBProgressHUD" "${PODS_CONFIGURATION_BUILD_DIR}/MJRefresh" "${PODS_CONFIGURATION_BUILD_DIR}/Masonry" "${PODS_CONFIGURATION_BUILD_DIR}/SDWebImage" "${PODS_CONFIGURATION_BUILD_DIR}/SnapKit" "${PODS_CONFIGURATION_BUILD_DIR}/TMCache"
GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
OTHER_LDFLAGS = $(inherited) -weak_framework "UIKit"
OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS
PODS_BUILD_DIR = ${BUILD_DIR}
PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
PODS_ROOT = ${SRCROOT}
PODS_TARGET_SRCROOT = ${PODS_ROOT}/GMBaseSwift
PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier}
SKIP_INSTALL = YES
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleExecutable</key>
<string>${EXECUTABLE_NAME}</string>
<key>CFBundleIdentifier</key>
<string>${PRODUCT_BUNDLE_IDENTIFIER}</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>${PRODUCT_NAME}</string>
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>1.0.5</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>${CURRENT_PROJECT_VERSION}</string>
<key>NSPrincipalClass</key>
<string></string>
</dict>
</plist>
#import <Foundation/Foundation.h>
@interface PodsDummy_GMFoundation : NSObject
@end
@implementation PodsDummy_GMFoundation
@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
#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
#import "LogCategory.h"
#import "NSArray+GM.h"
#import "NSAttributedString+GMSize.h"
#import "NSDate+DateFormat.h"
#import "NSDate+Category.h"
#import "NSDateFormatter+Category.h"
#import "NSDictionary+GM.h"
#import "NSDictionary+json.h"
#import "NSFileManager+FolderSize.h"
#import "NSMutableAttributedString+Attachment.h"
#import "NSNull+Empty.h"
#import "NSObject+KeyboardAnimation.h"
#import "NSObject+GMDate.h"
#import "NSString+Base64.h"
#import "NSString+DateFormat.h"
#import "NSString+Encrypt.h"
#import "NSString+GM.h"
#import "NSString+IconFont.h"
#import "NSString+RegularString.h"
FOUNDATION_EXPORT double GMFoundationVersionNumber;
FOUNDATION_EXPORT const unsigned char GMFoundationVersionString[];
framework module GMFoundation {
umbrella header "GMFoundation-umbrella.h"
export *
module * { export * }
}
CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/GMFoundation
GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS
PODS_BUILD_DIR = ${BUILD_DIR}
PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
PODS_ROOT = ${SRCROOT}
PODS_TARGET_SRCROOT = ${PODS_ROOT}/GMFoundation
PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier}
SKIP_INSTALL = YES
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleExecutable</key>
<string>${EXECUTABLE_NAME}</string>
<key>CFBundleIdentifier</key>
<string>${PRODUCT_BUNDLE_IDENTIFIER}</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>${PRODUCT_NAME}</string>
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>0.1.5</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>${CURRENT_PROJECT_VERSION}</string>
<key>NSPrincipalClass</key>
<string></string>
</dict>
</plist>
#import <Foundation/Foundation.h>
@interface PodsDummy_GMRouter : NSObject
@end
@implementation PodsDummy_GMRouter
@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
#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
#import "GMRouter+gm.h"
#import "GMRouter.h"
#import "Target_commons.h"
#import "UIViewController+Router.h"
FOUNDATION_EXPORT double GMRouterVersionNumber;
FOUNDATION_EXPORT const unsigned char GMRouterVersionString[];
framework module GMRouter {
umbrella header "GMRouter-umbrella.h"
export *
module * { export * }
}
CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/GMRouter
FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/MJExtension"
GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
PODS_BUILD_DIR = ${BUILD_DIR}
PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
PODS_ROOT = ${SRCROOT}
PODS_TARGET_SRCROOT = ${PODS_ROOT}/GMRouter
PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier}
SKIP_INSTALL = YES
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleExecutable</key>
<string>${EXECUTABLE_NAME}</string>
<key>CFBundleIdentifier</key>
<string>${PRODUCT_BUNDLE_IDENTIFIER}</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>${PRODUCT_NAME}</string>
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>3.2.1</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>${CURRENT_PROJECT_VERSION}</string>
<key>NSPrincipalClass</key>
<string></string>
</dict>
</plist>
#import <Foundation/Foundation.h>
@interface PodsDummy_MJExtension : NSObject
@end
@implementation PodsDummy_MJExtension
@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
#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
#import "MJExtension.h"
#import "MJExtensionConst.h"
#import "MJFoundation.h"
#import "MJProperty.h"
#import "MJPropertyKey.h"
#import "MJPropertyType.h"
#import "NSObject+MJClass.h"
#import "NSObject+MJCoding.h"
#import "NSObject+MJKeyValue.h"
#import "NSObject+MJProperty.h"
#import "NSString+MJExtension.h"
FOUNDATION_EXPORT double MJExtensionVersionNumber;
FOUNDATION_EXPORT const unsigned char MJExtensionVersionString[];
framework module MJExtension {
umbrella header "MJExtension-umbrella.h"
export *
module * { export * }
}
CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/MJExtension
GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
PODS_BUILD_DIR = ${BUILD_DIR}
PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
PODS_ROOT = ${SRCROOT}
PODS_TARGET_SRCROOT = ${PODS_ROOT}/MJExtension
PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier}
SKIP_INSTALL = YES
...@@ -47,6 +47,60 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN ...@@ -47,6 +47,60 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE. THE SOFTWARE.
## EVReflection
MIT 3 License
Copyright (c) 2015, EVICT B.V.
All rights reserved.
http://evict.nl, mailto://edwin@evict.nl
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* Neither the name of EVICT B.V. nor the
names of its contributors may be used to endorse or promote products
derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
## GM-Swift-Observable
Copyright (c) 2018 igiu1988 <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.
## GMAI ## GMAI
Copyright (c) 2020 Q14 <qiaojinzhu@igengmei.com> Copyright (c) 2020 Q14 <qiaojinzhu@igengmei.com>
...@@ -93,6 +147,29 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN ...@@ -93,6 +147,29 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE. THE SOFTWARE.
## GMBaseSwift
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 ## GMCache
Copyright (c) 2016 wangyang <wangyang@wanmeizhensuo.com> Copyright (c) 2016 wangyang <wangyang@wanmeizhensuo.com>
...@@ -116,6 +193,29 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN ...@@ -116,6 +193,29 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE. THE SOFTWARE.
## GMFoundation
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.
## GMHud ## GMHud
Copyright (c) 2016 wangyang <wangyang@wanmeizhensuo.com> Copyright (c) 2016 wangyang <wangyang@wanmeizhensuo.com>
...@@ -260,6 +360,29 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN ...@@ -260,6 +360,29 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE. THE SOFTWARE.
## GMRouter
Copyright (c) 2019 Q14 <qiaojinzhu@igengmei.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.
## MBProgressHUD ## MBProgressHUD
Copyright (c) 2009-2015 Matej Bukovinski Copyright (c) 2009-2015 Matej Bukovinski
...@@ -282,6 +405,29 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, ...@@ -282,6 +405,29 @@ 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 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE. THE SOFTWARE.
## MJExtension
Copyright (c) 2013-2019 MJExtension (https://github.com/CoderMJLee/MJExtension)
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.
## MJRefresh ## MJRefresh
Copyright (c) 2013-2015 MJRefresh (https://github.com/CoderMJLee/MJRefresh) Copyright (c) 2013-2015 MJRefresh (https://github.com/CoderMJLee/MJRefresh)
...@@ -327,6 +473,31 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, ...@@ -327,6 +473,31 @@ 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 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE. THE SOFTWARE.
## Qiniu
The MIT License (MIT)
Copyright (c) 2011-2017 qiniu.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.
## SDWebImage ## SDWebImage
Copyright (c) 2009-2018 Olivier Poitrey rs@dailymotion.com Copyright (c) 2009-2018 Olivier Poitrey rs@dailymotion.com
......
...@@ -70,6 +70,72 @@ THE SOFTWARE. ...@@ -70,6 +70,72 @@ THE SOFTWARE.
<key>Type</key> <key>Type</key>
<string>PSGroupSpecifier</string> <string>PSGroupSpecifier</string>
</dict> </dict>
<dict>
<key>FooterText</key>
<string>MIT 3 License
Copyright (c) 2015, EVICT B.V.
All rights reserved.
http://evict.nl, mailto://edwin@evict.nl
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* Neither the name of EVICT B.V. nor the
names of its contributors may be used to endorse or promote products
derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL &lt;COPYRIGHT HOLDER&gt; BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
</string>
<key>License</key>
<string>MIT</string>
<key>Title</key>
<string>EVReflection</string>
<key>Type</key>
<string>PSGroupSpecifier</string>
</dict>
<dict>
<key>FooterText</key>
<string>Copyright (c) 2018 igiu1988 &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>GM-Swift-Observable</string>
<key>Type</key>
<string>PSGroupSpecifier</string>
</dict>
<dict> <dict>
<key>FooterText</key> <key>FooterText</key>
<string>Copyright (c) 2020 Q14 &lt;qiaojinzhu@igengmei.com&gt; <string>Copyright (c) 2020 Q14 &lt;qiaojinzhu@igengmei.com&gt;
...@@ -142,6 +208,35 @@ furnished to do so, subject to the following conditions: ...@@ -142,6 +208,35 @@ furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software. 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>GMBaseSwift</string>
<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 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
...@@ -157,6 +252,35 @@ THE SOFTWARE. ...@@ -157,6 +252,35 @@ THE SOFTWARE.
<key>Type</key> <key>Type</key>
<string>PSGroupSpecifier</string> <string>PSGroupSpecifier</string>
</dict> </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>GMFoundation</string>
<key>Type</key>
<string>PSGroupSpecifier</string>
</dict>
<dict> <dict>
<key>FooterText</key> <key>FooterText</key>
<string>Copyright (c) 2016 wangyang &lt;wangyang@wanmeizhensuo.com&gt; <string>Copyright (c) 2016 wangyang &lt;wangyang@wanmeizhensuo.com&gt;
...@@ -343,6 +467,35 @@ THE SOFTWARE. ...@@ -343,6 +467,35 @@ THE SOFTWARE.
<key>Type</key> <key>Type</key>
<string>PSGroupSpecifier</string> <string>PSGroupSpecifier</string>
</dict> </dict>
<dict>
<key>FooterText</key>
<string>Copyright (c) 2019 Q14 &lt;qiaojinzhu@igengmei.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>GMRouter</string>
<key>Type</key>
<string>PSGroupSpecifier</string>
</dict>
<dict> <dict>
<key>FooterText</key> <key>FooterText</key>
<string>Copyright (c) 2009-2015 Matej Bukovinski <string>Copyright (c) 2009-2015 Matej Bukovinski
...@@ -371,6 +524,35 @@ THE SOFTWARE.</string> ...@@ -371,6 +524,35 @@ THE SOFTWARE.</string>
<key>Type</key> <key>Type</key>
<string>PSGroupSpecifier</string> <string>PSGroupSpecifier</string>
</dict> </dict>
<dict>
<key>FooterText</key>
<string>Copyright (c) 2013-2019 MJExtension (https://github.com/CoderMJLee/MJExtension)
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>MJExtension</string>
<key>Type</key>
<string>PSGroupSpecifier</string>
</dict>
<dict> <dict>
<key>FooterText</key> <key>FooterText</key>
<string>Copyright (c) 2013-2015 MJRefresh (https://github.com/CoderMJLee/MJRefresh) <string>Copyright (c) 2013-2015 MJRefresh (https://github.com/CoderMJLee/MJRefresh)
...@@ -428,6 +610,37 @@ THE SOFTWARE.</string> ...@@ -428,6 +610,37 @@ THE SOFTWARE.</string>
<key>Type</key> <key>Type</key>
<string>PSGroupSpecifier</string> <string>PSGroupSpecifier</string>
</dict> </dict>
<dict>
<key>FooterText</key>
<string>The MIT License (MIT)
Copyright (c) 2011-2017 qiniu.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.
</string>
<key>License</key>
<string>MIT</string>
<key>Title</key>
<string>Qiniu</string>
<key>Type</key>
<string>PSGroupSpecifier</string>
</dict>
<dict> <dict>
<key>FooterText</key> <key>FooterText</key>
<string>Copyright (c) 2009-2018 Olivier Poitrey rs@dailymotion.com <string>Copyright (c) 2009-2018 Olivier Poitrey rs@dailymotion.com
......
...@@ -163,9 +163,13 @@ strip_invalid_archs() { ...@@ -163,9 +163,13 @@ strip_invalid_archs() {
if [[ "$CONFIGURATION" == "Debug" ]]; then if [[ "$CONFIGURATION" == "Debug" ]]; then
install_framework "${BUILT_PRODUCTS_DIR}/AFNetworking/AFNetworking.framework" install_framework "${BUILT_PRODUCTS_DIR}/AFNetworking/AFNetworking.framework"
install_framework "${BUILT_PRODUCTS_DIR}/Alamofire/Alamofire.framework" install_framework "${BUILT_PRODUCTS_DIR}/Alamofire/Alamofire.framework"
install_framework "${BUILT_PRODUCTS_DIR}/EVReflection/EVReflection.framework"
install_framework "${BUILT_PRODUCTS_DIR}/GM-Swift-Observable/GM_Swift_Observable.framework"
install_framework "${BUILT_PRODUCTS_DIR}/GMAI/GMAI.framework" install_framework "${BUILT_PRODUCTS_DIR}/GMAI/GMAI.framework"
install_framework "${BUILT_PRODUCTS_DIR}/GMBase/GMBase.framework" install_framework "${BUILT_PRODUCTS_DIR}/GMBase/GMBase.framework"
install_framework "${BUILT_PRODUCTS_DIR}/GMBaseSwift/GMBaseSwift.framework"
install_framework "${BUILT_PRODUCTS_DIR}/GMCache/GMCache.framework" install_framework "${BUILT_PRODUCTS_DIR}/GMCache/GMCache.framework"
install_framework "${BUILT_PRODUCTS_DIR}/GMFoundation/GMFoundation.framework"
install_framework "${BUILT_PRODUCTS_DIR}/GMHud/GMHud.framework" install_framework "${BUILT_PRODUCTS_DIR}/GMHud/GMHud.framework"
install_framework "${BUILT_PRODUCTS_DIR}/GMJSONModel/GMJSONModel.framework" install_framework "${BUILT_PRODUCTS_DIR}/GMJSONModel/GMJSONModel.framework"
install_framework "${BUILT_PRODUCTS_DIR}/GMKit/GMKit.framework" install_framework "${BUILT_PRODUCTS_DIR}/GMKit/GMKit.framework"
...@@ -173,9 +177,12 @@ if [[ "$CONFIGURATION" == "Debug" ]]; then ...@@ -173,9 +177,12 @@ if [[ "$CONFIGURATION" == "Debug" ]]; then
install_framework "${BUILT_PRODUCTS_DIR}/GMNetworking/GMNetworking.framework" install_framework "${BUILT_PRODUCTS_DIR}/GMNetworking/GMNetworking.framework"
install_framework "${BUILT_PRODUCTS_DIR}/GMPhobos/GMPhobos.framework" install_framework "${BUILT_PRODUCTS_DIR}/GMPhobos/GMPhobos.framework"
install_framework "${BUILT_PRODUCTS_DIR}/GMRefresh/GMRefresh.framework" install_framework "${BUILT_PRODUCTS_DIR}/GMRefresh/GMRefresh.framework"
install_framework "${BUILT_PRODUCTS_DIR}/GMRouter/GMRouter.framework"
install_framework "${BUILT_PRODUCTS_DIR}/MBProgressHUD/MBProgressHUD.framework" install_framework "${BUILT_PRODUCTS_DIR}/MBProgressHUD/MBProgressHUD.framework"
install_framework "${BUILT_PRODUCTS_DIR}/MJExtension/MJExtension.framework"
install_framework "${BUILT_PRODUCTS_DIR}/MJRefresh/MJRefresh.framework" install_framework "${BUILT_PRODUCTS_DIR}/MJRefresh/MJRefresh.framework"
install_framework "${BUILT_PRODUCTS_DIR}/Masonry/Masonry.framework" install_framework "${BUILT_PRODUCTS_DIR}/Masonry/Masonry.framework"
install_framework "${BUILT_PRODUCTS_DIR}/Qiniu/Qiniu.framework"
install_framework "${BUILT_PRODUCTS_DIR}/SDWebImage/SDWebImage.framework" install_framework "${BUILT_PRODUCTS_DIR}/SDWebImage/SDWebImage.framework"
install_framework "${BUILT_PRODUCTS_DIR}/SnapKit/SnapKit.framework" install_framework "${BUILT_PRODUCTS_DIR}/SnapKit/SnapKit.framework"
install_framework "${BUILT_PRODUCTS_DIR}/TMCache/TMCache.framework" install_framework "${BUILT_PRODUCTS_DIR}/TMCache/TMCache.framework"
...@@ -184,9 +191,13 @@ fi ...@@ -184,9 +191,13 @@ fi
if [[ "$CONFIGURATION" == "Release" ]]; then if [[ "$CONFIGURATION" == "Release" ]]; then
install_framework "${BUILT_PRODUCTS_DIR}/AFNetworking/AFNetworking.framework" install_framework "${BUILT_PRODUCTS_DIR}/AFNetworking/AFNetworking.framework"
install_framework "${BUILT_PRODUCTS_DIR}/Alamofire/Alamofire.framework" install_framework "${BUILT_PRODUCTS_DIR}/Alamofire/Alamofire.framework"
install_framework "${BUILT_PRODUCTS_DIR}/EVReflection/EVReflection.framework"
install_framework "${BUILT_PRODUCTS_DIR}/GM-Swift-Observable/GM_Swift_Observable.framework"
install_framework "${BUILT_PRODUCTS_DIR}/GMAI/GMAI.framework" install_framework "${BUILT_PRODUCTS_DIR}/GMAI/GMAI.framework"
install_framework "${BUILT_PRODUCTS_DIR}/GMBase/GMBase.framework" install_framework "${BUILT_PRODUCTS_DIR}/GMBase/GMBase.framework"
install_framework "${BUILT_PRODUCTS_DIR}/GMBaseSwift/GMBaseSwift.framework"
install_framework "${BUILT_PRODUCTS_DIR}/GMCache/GMCache.framework" install_framework "${BUILT_PRODUCTS_DIR}/GMCache/GMCache.framework"
install_framework "${BUILT_PRODUCTS_DIR}/GMFoundation/GMFoundation.framework"
install_framework "${BUILT_PRODUCTS_DIR}/GMHud/GMHud.framework" install_framework "${BUILT_PRODUCTS_DIR}/GMHud/GMHud.framework"
install_framework "${BUILT_PRODUCTS_DIR}/GMJSONModel/GMJSONModel.framework" install_framework "${BUILT_PRODUCTS_DIR}/GMJSONModel/GMJSONModel.framework"
install_framework "${BUILT_PRODUCTS_DIR}/GMKit/GMKit.framework" install_framework "${BUILT_PRODUCTS_DIR}/GMKit/GMKit.framework"
...@@ -194,9 +205,12 @@ if [[ "$CONFIGURATION" == "Release" ]]; then ...@@ -194,9 +205,12 @@ if [[ "$CONFIGURATION" == "Release" ]]; then
install_framework "${BUILT_PRODUCTS_DIR}/GMNetworking/GMNetworking.framework" install_framework "${BUILT_PRODUCTS_DIR}/GMNetworking/GMNetworking.framework"
install_framework "${BUILT_PRODUCTS_DIR}/GMPhobos/GMPhobos.framework" install_framework "${BUILT_PRODUCTS_DIR}/GMPhobos/GMPhobos.framework"
install_framework "${BUILT_PRODUCTS_DIR}/GMRefresh/GMRefresh.framework" install_framework "${BUILT_PRODUCTS_DIR}/GMRefresh/GMRefresh.framework"
install_framework "${BUILT_PRODUCTS_DIR}/GMRouter/GMRouter.framework"
install_framework "${BUILT_PRODUCTS_DIR}/MBProgressHUD/MBProgressHUD.framework" install_framework "${BUILT_PRODUCTS_DIR}/MBProgressHUD/MBProgressHUD.framework"
install_framework "${BUILT_PRODUCTS_DIR}/MJExtension/MJExtension.framework"
install_framework "${BUILT_PRODUCTS_DIR}/MJRefresh/MJRefresh.framework" install_framework "${BUILT_PRODUCTS_DIR}/MJRefresh/MJRefresh.framework"
install_framework "${BUILT_PRODUCTS_DIR}/Masonry/Masonry.framework" install_framework "${BUILT_PRODUCTS_DIR}/Masonry/Masonry.framework"
install_framework "${BUILT_PRODUCTS_DIR}/Qiniu/Qiniu.framework"
install_framework "${BUILT_PRODUCTS_DIR}/SDWebImage/SDWebImage.framework" install_framework "${BUILT_PRODUCTS_DIR}/SDWebImage/SDWebImage.framework"
install_framework "${BUILT_PRODUCTS_DIR}/SnapKit/SnapKit.framework" install_framework "${BUILT_PRODUCTS_DIR}/SnapKit/SnapKit.framework"
install_framework "${BUILT_PRODUCTS_DIR}/TMCache/TMCache.framework" install_framework "${BUILT_PRODUCTS_DIR}/TMCache/TMCache.framework"
......
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES
FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/AFNetworking" "${PODS_CONFIGURATION_BUILD_DIR}/Alamofire" "${PODS_CONFIGURATION_BUILD_DIR}/GMAI" "${PODS_CONFIGURATION_BUILD_DIR}/GMBase" "${PODS_CONFIGURATION_BUILD_DIR}/GMCache" "${PODS_CONFIGURATION_BUILD_DIR}/GMHud" "${PODS_CONFIGURATION_BUILD_DIR}/GMJSONModel" "${PODS_CONFIGURATION_BUILD_DIR}/GMKit" "${PODS_CONFIGURATION_BUILD_DIR}/GMNetService" "${PODS_CONFIGURATION_BUILD_DIR}/GMNetworking" "${PODS_CONFIGURATION_BUILD_DIR}/GMPhobos" "${PODS_CONFIGURATION_BUILD_DIR}/GMRefresh" "${PODS_CONFIGURATION_BUILD_DIR}/MBProgressHUD" "${PODS_CONFIGURATION_BUILD_DIR}/MJRefresh" "${PODS_CONFIGURATION_BUILD_DIR}/Masonry" "${PODS_CONFIGURATION_BUILD_DIR}/SDWebImage" "${PODS_CONFIGURATION_BUILD_DIR}/SnapKit" "${PODS_CONFIGURATION_BUILD_DIR}/TMCache" "${PODS_CONFIGURATION_BUILD_DIR}/UITableView+FDTemplateLayoutCell" FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/AFNetworking" "${PODS_CONFIGURATION_BUILD_DIR}/Alamofire" "${PODS_CONFIGURATION_BUILD_DIR}/EVReflection" "${PODS_CONFIGURATION_BUILD_DIR}/GM-Swift-Observable" "${PODS_CONFIGURATION_BUILD_DIR}/GMAI" "${PODS_CONFIGURATION_BUILD_DIR}/GMBase" "${PODS_CONFIGURATION_BUILD_DIR}/GMBaseSwift" "${PODS_CONFIGURATION_BUILD_DIR}/GMCache" "${PODS_CONFIGURATION_BUILD_DIR}/GMFoundation" "${PODS_CONFIGURATION_BUILD_DIR}/GMHud" "${PODS_CONFIGURATION_BUILD_DIR}/GMJSONModel" "${PODS_CONFIGURATION_BUILD_DIR}/GMKit" "${PODS_CONFIGURATION_BUILD_DIR}/GMNetService" "${PODS_CONFIGURATION_BUILD_DIR}/GMNetworking" "${PODS_CONFIGURATION_BUILD_DIR}/GMPhobos" "${PODS_CONFIGURATION_BUILD_DIR}/GMRefresh" "${PODS_CONFIGURATION_BUILD_DIR}/GMRouter" "${PODS_CONFIGURATION_BUILD_DIR}/MBProgressHUD" "${PODS_CONFIGURATION_BUILD_DIR}/MJExtension" "${PODS_CONFIGURATION_BUILD_DIR}/MJRefresh" "${PODS_CONFIGURATION_BUILD_DIR}/Masonry" "${PODS_CONFIGURATION_BUILD_DIR}/Qiniu" "${PODS_CONFIGURATION_BUILD_DIR}/SDWebImage" "${PODS_CONFIGURATION_BUILD_DIR}/SnapKit" "${PODS_CONFIGURATION_BUILD_DIR}/TMCache" "${PODS_CONFIGURATION_BUILD_DIR}/UITableView+FDTemplateLayoutCell"
GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
HEADER_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/AFNetworking/AFNetworking.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/Alamofire/Alamofire.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/GMAI/GMAI.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/GMBase/GMBase.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/GMCache/GMCache.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/GMHud/GMHud.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/GMJSONModel/GMJSONModel.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/GMKit/GMKit.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/GMNetService/GMNetService.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/GMNetworking/GMNetworking.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/GMPhobos/GMPhobos.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/GMRefresh/GMRefresh.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/MBProgressHUD/MBProgressHUD.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/MJRefresh/MJRefresh.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/Masonry/Masonry.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/SDWebImage/SDWebImage.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/SnapKit/SnapKit.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/TMCache/TMCache.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/UITableView+FDTemplateLayoutCell/UITableView_FDTemplateLayoutCell.framework/Headers" HEADER_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/AFNetworking/AFNetworking.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/Alamofire/Alamofire.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/EVReflection/EVReflection.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/GM-Swift-Observable/GM_Swift_Observable.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/GMAI/GMAI.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/GMBase/GMBase.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/GMBaseSwift/GMBaseSwift.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/GMCache/GMCache.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/GMFoundation/GMFoundation.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/GMHud/GMHud.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/GMJSONModel/GMJSONModel.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/GMKit/GMKit.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/GMNetService/GMNetService.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/GMNetworking/GMNetworking.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/GMPhobos/GMPhobos.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/GMRefresh/GMRefresh.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/GMRouter/GMRouter.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/MBProgressHUD/MBProgressHUD.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/MJExtension/MJExtension.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/MJRefresh/MJRefresh.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/Masonry/Masonry.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/Qiniu/Qiniu.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/SDWebImage/SDWebImage.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/SnapKit/SnapKit.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/TMCache/TMCache.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/UITableView+FDTemplateLayoutCell/UITableView_FDTemplateLayoutCell.framework/Headers"
LD_RUNPATH_SEARCH_PATHS = $(inherited) '@executable_path/Frameworks' '@loader_path/Frameworks' LD_RUNPATH_SEARCH_PATHS = $(inherited) '@executable_path/Frameworks' '@loader_path/Frameworks'
OTHER_LDFLAGS = $(inherited) -l"z" -framework "AFNetworking" -framework "Alamofire" -framework "CoreGraphics" -framework "CoreLocation" -framework "Foundation" -framework "GMAI" -framework "GMBase" -framework "GMCache" -framework "GMHud" -framework "GMJSONModel" -framework "GMKit" -framework "GMNetService" -framework "GMNetworking" -framework "GMPhobos" -framework "GMRefresh" -framework "ImageIO" -framework "MBProgressHUD" -framework "MJRefresh" -framework "MapKit" -framework "Masonry" -framework "MobileCoreServices" -framework "SDWebImage" -framework "Security" -framework "SnapKit" -framework "SystemConfiguration" -framework "TMCache" -framework "UIKit" -framework "UITableView_FDTemplateLayoutCell" -weak_framework "UIKit" OTHER_LDFLAGS = $(inherited) -l"z" -framework "AFNetworking" -framework "Alamofire" -framework "CoreGraphics" -framework "CoreLocation" -framework "EVReflection" -framework "Foundation" -framework "GMAI" -framework "GMBase" -framework "GMBaseSwift" -framework "GMCache" -framework "GMFoundation" -framework "GMHud" -framework "GMJSONModel" -framework "GMKit" -framework "GMNetService" -framework "GMNetworking" -framework "GMPhobos" -framework "GMRefresh" -framework "GMRouter" -framework "GM_Swift_Observable" -framework "ImageIO" -framework "MBProgressHUD" -framework "MJExtension" -framework "MJRefresh" -framework "MapKit" -framework "Masonry" -framework "MobileCoreServices" -framework "Qiniu" -framework "SDWebImage" -framework "Security" -framework "SnapKit" -framework "SystemConfiguration" -framework "TMCache" -framework "UIKit" -framework "UITableView_FDTemplateLayoutCell" -weak_framework "UIKit"
OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS
PODS_BUILD_DIR = ${BUILD_DIR} PODS_BUILD_DIR = ${BUILD_DIR}
PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
......
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES
FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/AFNetworking" "${PODS_CONFIGURATION_BUILD_DIR}/Alamofire" "${PODS_CONFIGURATION_BUILD_DIR}/GMAI" "${PODS_CONFIGURATION_BUILD_DIR}/GMBase" "${PODS_CONFIGURATION_BUILD_DIR}/GMCache" "${PODS_CONFIGURATION_BUILD_DIR}/GMHud" "${PODS_CONFIGURATION_BUILD_DIR}/GMJSONModel" "${PODS_CONFIGURATION_BUILD_DIR}/GMKit" "${PODS_CONFIGURATION_BUILD_DIR}/GMNetService" "${PODS_CONFIGURATION_BUILD_DIR}/GMNetworking" "${PODS_CONFIGURATION_BUILD_DIR}/GMPhobos" "${PODS_CONFIGURATION_BUILD_DIR}/GMRefresh" "${PODS_CONFIGURATION_BUILD_DIR}/MBProgressHUD" "${PODS_CONFIGURATION_BUILD_DIR}/MJRefresh" "${PODS_CONFIGURATION_BUILD_DIR}/Masonry" "${PODS_CONFIGURATION_BUILD_DIR}/SDWebImage" "${PODS_CONFIGURATION_BUILD_DIR}/SnapKit" "${PODS_CONFIGURATION_BUILD_DIR}/TMCache" "${PODS_CONFIGURATION_BUILD_DIR}/UITableView+FDTemplateLayoutCell" FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/AFNetworking" "${PODS_CONFIGURATION_BUILD_DIR}/Alamofire" "${PODS_CONFIGURATION_BUILD_DIR}/EVReflection" "${PODS_CONFIGURATION_BUILD_DIR}/GM-Swift-Observable" "${PODS_CONFIGURATION_BUILD_DIR}/GMAI" "${PODS_CONFIGURATION_BUILD_DIR}/GMBase" "${PODS_CONFIGURATION_BUILD_DIR}/GMBaseSwift" "${PODS_CONFIGURATION_BUILD_DIR}/GMCache" "${PODS_CONFIGURATION_BUILD_DIR}/GMFoundation" "${PODS_CONFIGURATION_BUILD_DIR}/GMHud" "${PODS_CONFIGURATION_BUILD_DIR}/GMJSONModel" "${PODS_CONFIGURATION_BUILD_DIR}/GMKit" "${PODS_CONFIGURATION_BUILD_DIR}/GMNetService" "${PODS_CONFIGURATION_BUILD_DIR}/GMNetworking" "${PODS_CONFIGURATION_BUILD_DIR}/GMPhobos" "${PODS_CONFIGURATION_BUILD_DIR}/GMRefresh" "${PODS_CONFIGURATION_BUILD_DIR}/GMRouter" "${PODS_CONFIGURATION_BUILD_DIR}/MBProgressHUD" "${PODS_CONFIGURATION_BUILD_DIR}/MJExtension" "${PODS_CONFIGURATION_BUILD_DIR}/MJRefresh" "${PODS_CONFIGURATION_BUILD_DIR}/Masonry" "${PODS_CONFIGURATION_BUILD_DIR}/Qiniu" "${PODS_CONFIGURATION_BUILD_DIR}/SDWebImage" "${PODS_CONFIGURATION_BUILD_DIR}/SnapKit" "${PODS_CONFIGURATION_BUILD_DIR}/TMCache" "${PODS_CONFIGURATION_BUILD_DIR}/UITableView+FDTemplateLayoutCell"
GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
HEADER_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/AFNetworking/AFNetworking.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/Alamofire/Alamofire.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/GMAI/GMAI.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/GMBase/GMBase.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/GMCache/GMCache.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/GMHud/GMHud.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/GMJSONModel/GMJSONModel.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/GMKit/GMKit.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/GMNetService/GMNetService.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/GMNetworking/GMNetworking.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/GMPhobos/GMPhobos.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/GMRefresh/GMRefresh.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/MBProgressHUD/MBProgressHUD.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/MJRefresh/MJRefresh.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/Masonry/Masonry.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/SDWebImage/SDWebImage.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/SnapKit/SnapKit.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/TMCache/TMCache.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/UITableView+FDTemplateLayoutCell/UITableView_FDTemplateLayoutCell.framework/Headers" HEADER_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/AFNetworking/AFNetworking.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/Alamofire/Alamofire.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/EVReflection/EVReflection.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/GM-Swift-Observable/GM_Swift_Observable.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/GMAI/GMAI.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/GMBase/GMBase.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/GMBaseSwift/GMBaseSwift.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/GMCache/GMCache.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/GMFoundation/GMFoundation.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/GMHud/GMHud.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/GMJSONModel/GMJSONModel.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/GMKit/GMKit.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/GMNetService/GMNetService.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/GMNetworking/GMNetworking.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/GMPhobos/GMPhobos.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/GMRefresh/GMRefresh.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/GMRouter/GMRouter.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/MBProgressHUD/MBProgressHUD.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/MJExtension/MJExtension.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/MJRefresh/MJRefresh.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/Masonry/Masonry.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/Qiniu/Qiniu.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/SDWebImage/SDWebImage.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/SnapKit/SnapKit.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/TMCache/TMCache.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/UITableView+FDTemplateLayoutCell/UITableView_FDTemplateLayoutCell.framework/Headers"
LD_RUNPATH_SEARCH_PATHS = $(inherited) '@executable_path/Frameworks' '@loader_path/Frameworks' LD_RUNPATH_SEARCH_PATHS = $(inherited) '@executable_path/Frameworks' '@loader_path/Frameworks'
OTHER_LDFLAGS = $(inherited) -l"z" -framework "AFNetworking" -framework "Alamofire" -framework "CoreGraphics" -framework "CoreLocation" -framework "Foundation" -framework "GMAI" -framework "GMBase" -framework "GMCache" -framework "GMHud" -framework "GMJSONModel" -framework "GMKit" -framework "GMNetService" -framework "GMNetworking" -framework "GMPhobos" -framework "GMRefresh" -framework "ImageIO" -framework "MBProgressHUD" -framework "MJRefresh" -framework "MapKit" -framework "Masonry" -framework "MobileCoreServices" -framework "SDWebImage" -framework "Security" -framework "SnapKit" -framework "SystemConfiguration" -framework "TMCache" -framework "UIKit" -framework "UITableView_FDTemplateLayoutCell" -weak_framework "UIKit" OTHER_LDFLAGS = $(inherited) -l"z" -framework "AFNetworking" -framework "Alamofire" -framework "CoreGraphics" -framework "CoreLocation" -framework "EVReflection" -framework "Foundation" -framework "GMAI" -framework "GMBase" -framework "GMBaseSwift" -framework "GMCache" -framework "GMFoundation" -framework "GMHud" -framework "GMJSONModel" -framework "GMKit" -framework "GMNetService" -framework "GMNetworking" -framework "GMPhobos" -framework "GMRefresh" -framework "GMRouter" -framework "GM_Swift_Observable" -framework "ImageIO" -framework "MBProgressHUD" -framework "MJExtension" -framework "MJRefresh" -framework "MapKit" -framework "Masonry" -framework "MobileCoreServices" -framework "Qiniu" -framework "SDWebImage" -framework "Security" -framework "SnapKit" -framework "SystemConfiguration" -framework "TMCache" -framework "UIKit" -framework "UITableView_FDTemplateLayoutCell" -weak_framework "UIKit"
OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS
PODS_BUILD_DIR = ${BUILD_DIR} PODS_BUILD_DIR = ${BUILD_DIR}
PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
......
FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/AFNetworking" "${PODS_CONFIGURATION_BUILD_DIR}/Alamofire" "${PODS_CONFIGURATION_BUILD_DIR}/GMAI" "${PODS_CONFIGURATION_BUILD_DIR}/GMBase" "${PODS_CONFIGURATION_BUILD_DIR}/GMCache" "${PODS_CONFIGURATION_BUILD_DIR}/GMHud" "${PODS_CONFIGURATION_BUILD_DIR}/GMJSONModel" "${PODS_CONFIGURATION_BUILD_DIR}/GMKit" "${PODS_CONFIGURATION_BUILD_DIR}/GMNetService" "${PODS_CONFIGURATION_BUILD_DIR}/GMNetworking" "${PODS_CONFIGURATION_BUILD_DIR}/GMPhobos" "${PODS_CONFIGURATION_BUILD_DIR}/GMRefresh" "${PODS_CONFIGURATION_BUILD_DIR}/MBProgressHUD" "${PODS_CONFIGURATION_BUILD_DIR}/MJRefresh" "${PODS_CONFIGURATION_BUILD_DIR}/Masonry" "${PODS_CONFIGURATION_BUILD_DIR}/SDWebImage" "${PODS_CONFIGURATION_BUILD_DIR}/SnapKit" "${PODS_CONFIGURATION_BUILD_DIR}/TMCache" "${PODS_CONFIGURATION_BUILD_DIR}/UITableView+FDTemplateLayoutCell" FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/AFNetworking" "${PODS_CONFIGURATION_BUILD_DIR}/Alamofire" "${PODS_CONFIGURATION_BUILD_DIR}/EVReflection" "${PODS_CONFIGURATION_BUILD_DIR}/GM-Swift-Observable" "${PODS_CONFIGURATION_BUILD_DIR}/GMAI" "${PODS_CONFIGURATION_BUILD_DIR}/GMBase" "${PODS_CONFIGURATION_BUILD_DIR}/GMBaseSwift" "${PODS_CONFIGURATION_BUILD_DIR}/GMCache" "${PODS_CONFIGURATION_BUILD_DIR}/GMFoundation" "${PODS_CONFIGURATION_BUILD_DIR}/GMHud" "${PODS_CONFIGURATION_BUILD_DIR}/GMJSONModel" "${PODS_CONFIGURATION_BUILD_DIR}/GMKit" "${PODS_CONFIGURATION_BUILD_DIR}/GMNetService" "${PODS_CONFIGURATION_BUILD_DIR}/GMNetworking" "${PODS_CONFIGURATION_BUILD_DIR}/GMPhobos" "${PODS_CONFIGURATION_BUILD_DIR}/GMRefresh" "${PODS_CONFIGURATION_BUILD_DIR}/GMRouter" "${PODS_CONFIGURATION_BUILD_DIR}/MBProgressHUD" "${PODS_CONFIGURATION_BUILD_DIR}/MJExtension" "${PODS_CONFIGURATION_BUILD_DIR}/MJRefresh" "${PODS_CONFIGURATION_BUILD_DIR}/Masonry" "${PODS_CONFIGURATION_BUILD_DIR}/Qiniu" "${PODS_CONFIGURATION_BUILD_DIR}/SDWebImage" "${PODS_CONFIGURATION_BUILD_DIR}/SnapKit" "${PODS_CONFIGURATION_BUILD_DIR}/TMCache" "${PODS_CONFIGURATION_BUILD_DIR}/UITableView+FDTemplateLayoutCell"
GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
HEADER_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/AFNetworking/AFNetworking.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/Alamofire/Alamofire.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/GMAI/GMAI.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/GMBase/GMBase.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/GMCache/GMCache.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/GMHud/GMHud.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/GMJSONModel/GMJSONModel.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/GMKit/GMKit.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/GMNetService/GMNetService.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/GMNetworking/GMNetworking.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/GMPhobos/GMPhobos.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/GMRefresh/GMRefresh.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/MBProgressHUD/MBProgressHUD.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/MJRefresh/MJRefresh.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/Masonry/Masonry.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/SDWebImage/SDWebImage.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/SnapKit/SnapKit.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/TMCache/TMCache.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/UITableView+FDTemplateLayoutCell/UITableView_FDTemplateLayoutCell.framework/Headers" HEADER_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/AFNetworking/AFNetworking.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/Alamofire/Alamofire.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/EVReflection/EVReflection.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/GM-Swift-Observable/GM_Swift_Observable.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/GMAI/GMAI.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/GMBase/GMBase.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/GMBaseSwift/GMBaseSwift.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/GMCache/GMCache.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/GMFoundation/GMFoundation.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/GMHud/GMHud.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/GMJSONModel/GMJSONModel.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/GMKit/GMKit.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/GMNetService/GMNetService.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/GMNetworking/GMNetworking.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/GMPhobos/GMPhobos.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/GMRefresh/GMRefresh.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/GMRouter/GMRouter.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/MBProgressHUD/MBProgressHUD.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/MJExtension/MJExtension.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/MJRefresh/MJRefresh.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/Masonry/Masonry.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/Qiniu/Qiniu.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/SDWebImage/SDWebImage.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/SnapKit/SnapKit.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/TMCache/TMCache.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/UITableView+FDTemplateLayoutCell/UITableView_FDTemplateLayoutCell.framework/Headers"
OTHER_LDFLAGS = $(inherited) -l"z" -framework "AFNetworking" -framework "Alamofire" -framework "CoreGraphics" -framework "CoreLocation" -framework "Foundation" -framework "GMAI" -framework "GMBase" -framework "GMCache" -framework "GMHud" -framework "GMJSONModel" -framework "GMKit" -framework "GMNetService" -framework "GMNetworking" -framework "GMPhobos" -framework "GMRefresh" -framework "ImageIO" -framework "MBProgressHUD" -framework "MJRefresh" -framework "MapKit" -framework "Masonry" -framework "MobileCoreServices" -framework "SDWebImage" -framework "Security" -framework "SnapKit" -framework "SystemConfiguration" -framework "TMCache" -framework "UIKit" -framework "UITableView_FDTemplateLayoutCell" -weak_framework "UIKit" OTHER_LDFLAGS = $(inherited) -l"z" -framework "AFNetworking" -framework "Alamofire" -framework "CoreGraphics" -framework "CoreLocation" -framework "EVReflection" -framework "Foundation" -framework "GMAI" -framework "GMBase" -framework "GMBaseSwift" -framework "GMCache" -framework "GMFoundation" -framework "GMHud" -framework "GMJSONModel" -framework "GMKit" -framework "GMNetService" -framework "GMNetworking" -framework "GMPhobos" -framework "GMRefresh" -framework "GMRouter" -framework "GM_Swift_Observable" -framework "ImageIO" -framework "MBProgressHUD" -framework "MJExtension" -framework "MJRefresh" -framework "MapKit" -framework "Masonry" -framework "MobileCoreServices" -framework "Qiniu" -framework "SDWebImage" -framework "Security" -framework "SnapKit" -framework "SystemConfiguration" -framework "TMCache" -framework "UIKit" -framework "UITableView_FDTemplateLayoutCell" -weak_framework "UIKit"
PODS_BUILD_DIR = ${BUILD_DIR} PODS_BUILD_DIR = ${BUILD_DIR}
PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
PODS_PODFILE_DIR_PATH = ${SRCROOT}/. PODS_PODFILE_DIR_PATH = ${SRCROOT}/.
......
FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/AFNetworking" "${PODS_CONFIGURATION_BUILD_DIR}/Alamofire" "${PODS_CONFIGURATION_BUILD_DIR}/GMAI" "${PODS_CONFIGURATION_BUILD_DIR}/GMBase" "${PODS_CONFIGURATION_BUILD_DIR}/GMCache" "${PODS_CONFIGURATION_BUILD_DIR}/GMHud" "${PODS_CONFIGURATION_BUILD_DIR}/GMJSONModel" "${PODS_CONFIGURATION_BUILD_DIR}/GMKit" "${PODS_CONFIGURATION_BUILD_DIR}/GMNetService" "${PODS_CONFIGURATION_BUILD_DIR}/GMNetworking" "${PODS_CONFIGURATION_BUILD_DIR}/GMPhobos" "${PODS_CONFIGURATION_BUILD_DIR}/GMRefresh" "${PODS_CONFIGURATION_BUILD_DIR}/MBProgressHUD" "${PODS_CONFIGURATION_BUILD_DIR}/MJRefresh" "${PODS_CONFIGURATION_BUILD_DIR}/Masonry" "${PODS_CONFIGURATION_BUILD_DIR}/SDWebImage" "${PODS_CONFIGURATION_BUILD_DIR}/SnapKit" "${PODS_CONFIGURATION_BUILD_DIR}/TMCache" "${PODS_CONFIGURATION_BUILD_DIR}/UITableView+FDTemplateLayoutCell" FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/AFNetworking" "${PODS_CONFIGURATION_BUILD_DIR}/Alamofire" "${PODS_CONFIGURATION_BUILD_DIR}/EVReflection" "${PODS_CONFIGURATION_BUILD_DIR}/GM-Swift-Observable" "${PODS_CONFIGURATION_BUILD_DIR}/GMAI" "${PODS_CONFIGURATION_BUILD_DIR}/GMBase" "${PODS_CONFIGURATION_BUILD_DIR}/GMBaseSwift" "${PODS_CONFIGURATION_BUILD_DIR}/GMCache" "${PODS_CONFIGURATION_BUILD_DIR}/GMFoundation" "${PODS_CONFIGURATION_BUILD_DIR}/GMHud" "${PODS_CONFIGURATION_BUILD_DIR}/GMJSONModel" "${PODS_CONFIGURATION_BUILD_DIR}/GMKit" "${PODS_CONFIGURATION_BUILD_DIR}/GMNetService" "${PODS_CONFIGURATION_BUILD_DIR}/GMNetworking" "${PODS_CONFIGURATION_BUILD_DIR}/GMPhobos" "${PODS_CONFIGURATION_BUILD_DIR}/GMRefresh" "${PODS_CONFIGURATION_BUILD_DIR}/GMRouter" "${PODS_CONFIGURATION_BUILD_DIR}/MBProgressHUD" "${PODS_CONFIGURATION_BUILD_DIR}/MJExtension" "${PODS_CONFIGURATION_BUILD_DIR}/MJRefresh" "${PODS_CONFIGURATION_BUILD_DIR}/Masonry" "${PODS_CONFIGURATION_BUILD_DIR}/Qiniu" "${PODS_CONFIGURATION_BUILD_DIR}/SDWebImage" "${PODS_CONFIGURATION_BUILD_DIR}/SnapKit" "${PODS_CONFIGURATION_BUILD_DIR}/TMCache" "${PODS_CONFIGURATION_BUILD_DIR}/UITableView+FDTemplateLayoutCell"
GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
HEADER_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/AFNetworking/AFNetworking.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/Alamofire/Alamofire.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/GMAI/GMAI.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/GMBase/GMBase.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/GMCache/GMCache.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/GMHud/GMHud.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/GMJSONModel/GMJSONModel.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/GMKit/GMKit.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/GMNetService/GMNetService.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/GMNetworking/GMNetworking.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/GMPhobos/GMPhobos.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/GMRefresh/GMRefresh.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/MBProgressHUD/MBProgressHUD.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/MJRefresh/MJRefresh.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/Masonry/Masonry.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/SDWebImage/SDWebImage.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/SnapKit/SnapKit.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/TMCache/TMCache.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/UITableView+FDTemplateLayoutCell/UITableView_FDTemplateLayoutCell.framework/Headers" HEADER_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/AFNetworking/AFNetworking.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/Alamofire/Alamofire.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/EVReflection/EVReflection.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/GM-Swift-Observable/GM_Swift_Observable.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/GMAI/GMAI.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/GMBase/GMBase.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/GMBaseSwift/GMBaseSwift.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/GMCache/GMCache.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/GMFoundation/GMFoundation.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/GMHud/GMHud.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/GMJSONModel/GMJSONModel.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/GMKit/GMKit.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/GMNetService/GMNetService.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/GMNetworking/GMNetworking.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/GMPhobos/GMPhobos.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/GMRefresh/GMRefresh.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/GMRouter/GMRouter.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/MBProgressHUD/MBProgressHUD.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/MJExtension/MJExtension.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/MJRefresh/MJRefresh.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/Masonry/Masonry.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/Qiniu/Qiniu.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/SDWebImage/SDWebImage.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/SnapKit/SnapKit.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/TMCache/TMCache.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/UITableView+FDTemplateLayoutCell/UITableView_FDTemplateLayoutCell.framework/Headers"
OTHER_LDFLAGS = $(inherited) -l"z" -framework "AFNetworking" -framework "Alamofire" -framework "CoreGraphics" -framework "CoreLocation" -framework "Foundation" -framework "GMAI" -framework "GMBase" -framework "GMCache" -framework "GMHud" -framework "GMJSONModel" -framework "GMKit" -framework "GMNetService" -framework "GMNetworking" -framework "GMPhobos" -framework "GMRefresh" -framework "ImageIO" -framework "MBProgressHUD" -framework "MJRefresh" -framework "MapKit" -framework "Masonry" -framework "MobileCoreServices" -framework "SDWebImage" -framework "Security" -framework "SnapKit" -framework "SystemConfiguration" -framework "TMCache" -framework "UIKit" -framework "UITableView_FDTemplateLayoutCell" -weak_framework "UIKit" OTHER_LDFLAGS = $(inherited) -l"z" -framework "AFNetworking" -framework "Alamofire" -framework "CoreGraphics" -framework "CoreLocation" -framework "EVReflection" -framework "Foundation" -framework "GMAI" -framework "GMBase" -framework "GMBaseSwift" -framework "GMCache" -framework "GMFoundation" -framework "GMHud" -framework "GMJSONModel" -framework "GMKit" -framework "GMNetService" -framework "GMNetworking" -framework "GMPhobos" -framework "GMRefresh" -framework "GMRouter" -framework "GM_Swift_Observable" -framework "ImageIO" -framework "MBProgressHUD" -framework "MJExtension" -framework "MJRefresh" -framework "MapKit" -framework "Masonry" -framework "MobileCoreServices" -framework "Qiniu" -framework "SDWebImage" -framework "Security" -framework "SnapKit" -framework "SystemConfiguration" -framework "TMCache" -framework "UIKit" -framework "UITableView_FDTemplateLayoutCell" -weak_framework "UIKit"
PODS_BUILD_DIR = ${BUILD_DIR} PODS_BUILD_DIR = ${BUILD_DIR}
PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
PODS_PODFILE_DIR_PATH = ${SRCROOT}/. PODS_PODFILE_DIR_PATH = ${SRCROOT}/.
......
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleExecutable</key>
<string>${EXECUTABLE_NAME}</string>
<key>CFBundleIdentifier</key>
<string>${PRODUCT_BUNDLE_IDENTIFIER}</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>${PRODUCT_NAME}</string>
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>7.3.0</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>${CURRENT_PROJECT_VERSION}</string>
<key>NSPrincipalClass</key>
<string></string>
</dict>
</plist>
#import <Foundation/Foundation.h>
@interface PodsDummy_Qiniu : NSObject
@end
@implementation PodsDummy_Qiniu
@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
#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
#import "QNPipeline.h"
#import "QNALAssetFile.h"
#import "QNAsyncRun.h"
#import "QNCrc32.h"
#import "QNEtag.h"
#import "QNFile.h"
#import "QNFileDelegate.h"
#import "QNPHAssetFile.h"
#import "QNPHAssetResource.h"
#import "QNSystem.h"
#import "QNUrlSafeBase64.h"
#import "QNVersion.h"
#import "QN_GTM_Base64.h"
#import "QNHttpDelegate.h"
#import "QNResponseInfo.h"
#import "QNSessionManager.h"
#import "QNUserAgent.h"
#import "QiniuSDK.h"
#import "QNFileRecorder.h"
#import "QNRecorderDelegate.h"
#import "QNConcurrentResumeUpload.h"
#import "QNConfiguration.h"
#import "QNFormUpload.h"
#import "QNResumeUpload.h"
#import "QNUploadInfoReporter.h"
#import "QNUploadManager.h"
#import "QNUploadOption+Private.h"
#import "QNUploadOption.h"
#import "QNUpToken.h"
FOUNDATION_EXPORT double QiniuVersionNumber;
FOUNDATION_EXPORT const unsigned char QiniuVersionString[];
framework module Qiniu {
umbrella header "Qiniu-umbrella.h"
export *
module * { export * }
}
CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/Qiniu
GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
OTHER_LDFLAGS = $(inherited) -l"z"
PODS_BUILD_DIR = ${BUILD_DIR}
PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
PODS_ROOT = ${SRCROOT}
PODS_TARGET_SRCROOT = ${PODS_ROOT}/Qiniu
PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier}
SKIP_INSTALL = YES
...@@ -15,7 +15,7 @@ ...@@ -15,7 +15,7 @@
<key>CFBundlePackageType</key> <key>CFBundlePackageType</key>
<string>FMWK</string> <string>FMWK</string>
<key>CFBundleShortVersionString</key> <key>CFBundleShortVersionString</key>
<string>4.2.0</string> <string>4.0.0</string>
<key>CFBundleSignature</key> <key>CFBundleSignature</key>
<string>????</string> <string>????</string>
<key>CFBundleVersion</key> <key>CFBundleVersion</key>
......
...@@ -36,8 +36,19 @@ TODO: Add long description of the pod here. ...@@ -36,8 +36,19 @@ TODO: Add long description of the pod here.
# 'GMAI' => ['GMAI/Assets/*.png'] # 'GMAI' => ['GMAI/Assets/*.png']
# } # }
# s.public_header_files = 'Pod/Classes/**/*.h' # s.public_header_files = 'Pod/Classes/**/*.h'
# s.frameworks = 'UIKit', 'MapKit' # s.frameworks = 'UIKit', 'MapKit'
s.dependency 'GMBase' s.dependency 'GMBase'
s.dependency 'GMNetworking' s.dependency 'GMNetworking'
s.dependency 'EVReflection'
s.dependency 'GMBaseSwift'
s.dependency 'GMCache'
s.dependency 'GMRouter'
s.dependency 'Qiniu'
s.dependency 'GMFoundation'
s.dependency 'SDWebImage'
end end
...@@ -8,6 +8,8 @@ ...@@ -8,6 +8,8 @@
//#import "ALOneImageResultViewController.h" //#import "ALOneImageResultViewController.h"
//#import "Gengmei-Swift.h" //#import "Gengmei-Swift.h"
#import "ALFaceInfoViewModel.h" #import "ALFaceInfoViewModel.h"
#import "GMAIConst.h"
#import "ALUpLoadManager.h"
//#import "ALUpLoadManager.h" //#import "ALUpLoadManager.h"
//#import "GMFaceInfoShareViewController.h" //#import "GMFaceInfoShareViewController.h"
#import "UIImage+ImageEffects.h" #import "UIImage+ImageEffects.h"
...@@ -16,7 +18,12 @@ ...@@ -16,7 +18,12 @@
#import "ALFacePointsModel.h" #import "ALFacePointsModel.h"
//#import "GMFacePointViewController.h" //#import "GMFacePointViewController.h"
#import "GMFaceV3Model.h" #import "GMFaceV3Model.h"
#import "GMAIConst.h"
@import GMRouter;
@import GMCache;
@import GMNetworking; @import GMNetworking;
@import SDWebImage;
@implementation ALFaceInfoViewModel @implementation ALFaceInfoViewModel
/** /**
获取面部点数据 获取面部点数据
...@@ -56,7 +63,7 @@ ...@@ -56,7 +63,7 @@
failBlock:(HttpFailedBlock)failBlock { failBlock:(HttpFailedBlock)failBlock {
NSDictionary *param = @{@"face_url":SafeString(faceUrl)}; NSDictionary *param = @{@"face_url":SafeString(faceUrl)};
[GMNetworking requestOCWithApi:API_ONEIMAGE_CREATE method:GMHTTPMethodPost parameters:param completion:^(GMResponseOC *response) { [GMNetworking requestOCWithApi:API_ONEIMAGE_CREATE method:GMHTTPMethodPost parameters:param completion:^(GMResponseOC *response) {
if (response.isSuccess) { if (response.isSuccess) {
if (successBlock) { if (successBlock) {
...@@ -75,8 +82,7 @@ ...@@ -75,8 +82,7 @@
面部分享页面 面部分享页面
*/ */
+ (void)jumpFaceAnalysisSharePage { + (void)jumpFaceAnalysisSharePage {
GMFaceInfoShareViewController *shareCtrl = [[GMFaceInfoShareViewController alloc] init]; [[GMRouter sharedInstance] pushScheme:@"gengmei://face_info_share"];
[AppDelegate.navigation pushViewController:shareCtrl animated:YES];
} }
/** /**
...@@ -340,12 +346,11 @@ ...@@ -340,12 +346,11 @@
} }
GMFaceV3Model *faceModel = pointModel.faces.firstObject; GMFaceV3Model *faceModel = pointModel.faces.firstObject;
faceModel.image = image; faceModel.image = image;
if (successBlock) { if (successBlock) {
successBlock(faceModel); successBlock(faceModel);
} else { } else {
GMFacePointViewController *pointVc = [[GMFacePointViewController alloc] initWithModel:faceModel]; [[GMRouter sharedInstance] pushScheme:@"gengmei://face_point"];
[vc.navigationController pushViewController:pointVc animated:YES];
} }
} }
}]; }];
...@@ -359,13 +364,14 @@ ...@@ -359,13 +364,14 @@
[Phobos track:@"popup_view" attributes:@{@"page_name":@"create_report", [Phobos track:@"popup_view" attributes:@{@"page_name":@"create_report",
@"popup_name":@"entering_fail"}]; @"popup_name":@"entering_fail"}];
[GMHudModule showWarning:@"未能识别要分析的人脸"]; // [GMHudModule showWarning:@"未能识别要分析的人脸"];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
for (UIViewController *vc in AppDelegate.navigation.childViewControllers) { #warning TODO: 待完成
if ([vc isKindOfClass:[NewFaceShotController class]]){ // for (UIViewController *vc in AppDelegate.navigation.childViewControllers) {
[AppDelegate.navigation popToViewController:vc animated:YES]; // if ([vc isKindOfClass:[NewFaceShotController class]]){
} // [AppDelegate.navigation popToViewController:vc animated:YES];
} // }
// }
}); });
} }
......
...@@ -9,6 +9,15 @@ ...@@ -9,6 +9,15 @@
import Foundation import Foundation
import UIKit import UIKit
/*
四种双眼皮类型 开扇形、平行式、新月、欧式
*/
enum DoubleEyeType: Int {
case kaishanType = 1
case pingxingType
case xinyueType
case oushiType
}
private var eyeLidTHRatioAssociationKey: UInt8 = 0 private var eyeLidTHRatioAssociationKey: UInt8 = 0
//private typealias Eyes = Face //private typealias Eyes = Face
......
...@@ -7,6 +7,7 @@ ...@@ -7,6 +7,7 @@
// //
#import "GMFaceV3Model.h" #import "GMFaceV3Model.h"
@import GMFoundation;
@implementation GMFaceCommonContentModel @implementation GMFaceCommonContentModel
@end @end
......
//
// ShareObject.swift
// Gengmei
//
// Created by Terminator on 16/5/21.
// Copyright © 2016年 更美互动信息科技有限公司. All rights reserved.
//
import UIKit
import GMBaseSwift
@objcMembers
class ShareBasicObject: GMBaseObject {
var title = ""
var content = ""
}
@objcMembers
class ShareBasicWeChatMiniObject: GMBaseObject {
var userName = ""
var path = ""
var hdImageUrl = ""
var thumbImageUrl = ""
var desc = ""
var title = ""
}
@objcMembers
class CoverObject: GMBaseObject {
var desc = ""
var image = ""
}
@objcMembers
class OthersObject: GMBaseObject {
var cityName = ""
var cover = [CoverObject]()
var title = ""
var type = ""
var voteNum = ""
var voteNumDesc = ""
var beforeDesc = ""
var afterCoverUrl = ""
var afterDesc = ""
var beforeCoverUrl = ""
}
@objcMembers
class ShareObject: GMBaseObject {
var image = ""
var url = ""
var wechat = ShareBasicObject()
var wechatline = ShareBasicObject()
var qq = ShareBasicObject()
var qqzone = ShareBasicObject()
var weibo = ShareBasicObject()
var wechatScreenshot = ShareBasicObject()
var wechatlineScreenshot = ShareBasicObject()
var wechatmini = ShareBasicWeChatMiniObject()
var others = OthersObject()
}
//@interface WMShareBasicObject : GMBaseObject
//@property (nonatomic,strong) NSString * title;
//@property (nonatomic,strong) NSString * content;
//@end
//
//
//@protocol WMShareObject @end
//@interface WMShareObject : GMBaseObject
//@property (nonatomic,strong) NSString * image;
//@property (nonatomic,strong) NSString * url;
//@property (nonatomic,strong) WMShareBasicObject <WMShareBasicObject>* wechat;
//@property (nonatomic,strong) WMShareBasicObject <WMShareBasicObject>* wechatline;
//@property (nonatomic,strong) WMShareBasicObject <WMShareBasicObject>* qq;
//@property (nonatomic,strong) WMShareBasicObject <WMShareBasicObject>* weibo;
...@@ -5,9 +5,9 @@ ...@@ -5,9 +5,9 @@
// Created by Q14 on 2019/7/1. // Created by Q14 on 2019/7/1.
// Copyright © 2019 更美互动信息科技有限公司. All rights reserved. // Copyright © 2019 更美互动信息科技有限公司. All rights reserved.
// //
#import "AppDelegate+ViewControl.h" //#import "AppDelegate+ViewControl.h"
#import "ALFaceInfoModel.h" #import "ALFaceInfoModel.h"
#import "GMScanAnimationController.h" //#import "GMScanAnimationController.h"
#import "GMFaceV3Model.h" #import "GMFaceV3Model.h"
@interface GMFaceV3Util : GMObject @interface GMFaceV3Util : GMObject
......
...@@ -9,9 +9,13 @@ ...@@ -9,9 +9,13 @@
#import "GMFaceV3Util.h" #import "GMFaceV3Util.h"
#import "ALTransFormFaceUtil.h" #import "ALTransFormFaceUtil.h"
#import "GMFaceV3Model.h" #import "GMFaceV3Model.h"
#import "GMAIConst.h"
#import "ALUpLoadManager.h" #import "ALUpLoadManager.h"
#import "GMHudModule+Alpha.h" //#import "GMHudModule+Alpha.h"
#import "GMFacePointViewController.h" //#import "GMFacePointViewController.h"
@import GMNetworking;
@import GMCache;
@import SDWebImage;
@implementation GMFaceV3Util @implementation GMFaceV3Util
/** /**
...@@ -95,7 +99,7 @@ ...@@ -95,7 +99,7 @@
[uploadImage setImage:image getTokenType:GetTokenTypeImage fileSuffix:nil block:^(NSString *path) { [uploadImage setImage:image getTokenType:GetTokenTypeImage fileSuffix:nil block:^(NSString *path) {
[weakSelf uploadOneImageToServer:path vc:vc successBlock:successBlock]; [weakSelf uploadOneImageToServer:path vc:vc successBlock:successBlock];
} failBlock:^(NSString *message) { } failBlock:^(NSString *message) {
[weakSelf showAlertScanErrorView]; // [weakSelf showAlertScanErrorView];
}]; }];
} }
...@@ -123,7 +127,7 @@ ...@@ -123,7 +127,7 @@
return; return;
}; };
ALUpLoadManager *uploadImage = [ALUpLoadManager new]; ALUpLoadManager *uploadImage = [ALUpLoadManager new];
[uploadImage setImage:image getTokenType:GetTokenTypeImage fileSuffix:nil block:^(NSString *path) { [uploadImage setImage:image getTokenType:GetTokenTypeImage fileSuffix:nil block:^(NSString *path) {
if (successBlock) { if (successBlock) {
successBlock(path); successBlock(path);
...@@ -202,10 +206,10 @@ ...@@ -202,10 +206,10 @@
+ (void)showAlertScanErrorView { + (void)showAlertScanErrorView {
[Phobos track:@"popup_view" attributes:@{@"page_name":@"create_report", [Phobos track:@"popup_view" attributes:@{@"page_name":@"create_report",
@"popup_name":@"entering_fail"}]; @"popup_name":@"entering_fail"}];
[GMHudModule showWarning:@"未能识别要分析的人脸"]; // [GMHudModule showWarning:@"未能识别要分析的人脸"];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ // dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[AppDelegate.navigation popViewControllerAnimated:YES]; // [AppDelegate.navigation popViewControllerAnimated:YES];
}); // });
} }
// 跳转到面部分析页面 // 跳转到面部分析页面
...@@ -218,13 +222,14 @@ ...@@ -218,13 +222,14 @@
} }
GMFaceV3Model *faceModel = pointModel.faces.firstObject; GMFaceV3Model *faceModel = pointModel.faces.firstObject;
faceModel.image = image; //模型转换 faceModel.image = image; //模型转换
if (successBlock) { if (successBlock) {
successBlock(faceModel); successBlock(faceModel);
} else { } else {
GMFacePointViewController *pointVc = [[GMFacePointViewController alloc] initWithModel:faceModel]; #warning TODO:
pointVc.isGene = isGene; // GMFacePointViewController *pointVc = [[GMFacePointViewController alloc] initWithModel:faceModel];
[vc.navigationController pushViewController:pointVc animated:YES]; // pointVc.isGene = isGene;
// [vc.navigationController pushViewController:pointVc animated:YES];
} }
}]; }];
} }
......
//
// ALUpLoadManager.h
// GMAlpha
//
// Created by zhouLiang on 2018/11/26.
// Copyright © 2018 Gengmei. All rights reserved.
//
typedef NS_ENUM(NSInteger , GetTokenType) {
GetTokenTypeImage = 1 ,/** 图片 */
GetTokenTypeVideo = 2 ,/** 视频文件 */
GetTokenTypeSaoFace = 3/** face扫脸相关文件 */
};
#import <Foundation/Foundation.h>
typedef void(^returnImageUrlBlock)(NSString *path);
typedef void(^returnFailBlock)(NSString *message);
@interface ALUpLoadManager : NSObject
+ (instancetype)shareInstance;
@property (nonatomic, assign) NSString *fileSuffer;
/**
* 七牛上传图片
*/
- (void)setImage:(UIImage *)image getTokenType:(GetTokenType)type fileSuffix:(NSString *)fileSuffix block:(returnImageUrlBlock)block failBlock:(returnFailBlock)failBlock;
/**
* 七牛上传文件
*/
- (void)setFile:(NSString *)file getTokenType:(GetTokenType)type block:(returnImageUrlBlock)block failBlock:(returnFailBlock)failBlock;
/**
* 七牛上传视频
*/
- (void)setData:(NSData *)data getTokenType:(GetTokenType)type key:(NSString *)key block:(returnImageUrlBlock)block failBlock:(returnFailBlock)failBlock;
/**
* getToken
*/
- (void)getToken:(GetTokenType)type block:(returnImageUrlBlock)block failBlock:(returnFailBlock)failBlock;
@end
//
// ALUpLoadManager.m
// GMAlpha
//
// Created by zhouLiang on 2018/11/26.
// Copyright © 2018 Gengmei. All rights reserved.
//
#import "ALUpLoadManager.h"
#import "GMAIConst.h"
//#import "QNUploadManager.h"
#import <Qiniu/QNUploadManager.h>
#import <CommonCrypto/CommonDigest.h>
//#import "GMPhotoTools.h"
@import GMFoundation;
@import GMNetworking;
@interface ALUpLoadManager ()
@property (nonatomic, copy) NSString *token;
@end
@implementation ALUpLoadManager
+ (instancetype)shareInstance
{
static dispatch_once_t onceToken;
static id instance = nil;
dispatch_once(&onceToken, ^{
instance = [[[self class] alloc] init];
});
return instance;
}
/**
* 七牛上传图片
*/
- (void)setImage:(UIImage *)image getTokenType:(GetTokenType)type fileSuffix:(NSString *)fileSuffix block:(returnImageUrlBlock)block failBlock:(returnFailBlock)failBlock {
if (!image) {
failBlock(@"数据不能为空");
return;
}
NSDictionary *params = @{@"token_type" : @(type)};
[GMNetworking requestOCWithApi:API_FACE_GET_TOKEN method:GMHTTPMethodPost parameters:params completion:^(GMResponseOC * response) {
if (response.isSuccess) {
//token get success
NSDictionary *dict = response.data;
NSString *token = dict[@"token"];
[self putImage:image getTokenType:type token:token fileSuffix:fileSuffix block:block failBlock:failBlock];
} else {
failBlock(response.message);
}
}];
}
// 上传图片
- (void)putImage:(UIImage *)image getTokenType:(GetTokenType)type token:(NSString *)token fileSuffix:(NSString *)fileSuffix block:(returnImageUrlBlock)block failBlock:(returnFailBlock)failBlock {
dispatch_group_t group = dispatch_group_create();
dispatch_group_enter(group);
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
QNUploadManager *upManager = [[QNUploadManager alloc] init];
NSData *imageData;
if (type == GetTokenTypeSaoFace) {
imageData = UIImageJPEGRepresentation(image,1.0);
}
else{
imageData = UIImageJPEGRepresentation(image,0.7);
}
NSString *key = [self createImagePath];
if ([fileSuffix isNonEmpty]) {
key = [NSString stringWithFormat:@"%@%@",key,fileSuffix];
}
[upManager putData:imageData key:key token:token complete:^(QNResponseInfo *info, NSString *key, NSDictionary *resp) {
if (info) {
// 上传成功
if (block != nil) {
block(key);
}
} else {
if (failBlock != nil) {
failBlock(@"上传失败");
}
//上传失败
// [AppDelegate.visibleController toast:@"上传失败"];
}
dispatch_group_leave(group);
} option:nil];
});
}
/**
* 七牛上传文件
*/
- (void)setFile:(NSString *)file getTokenType:(GetTokenType)type block:(returnImageUrlBlock)block failBlock:(returnFailBlock)failBlock
{
// if ([[GMLoginManager shareInstance] showLoginViewIfNeeded]) {
// return;
// }
if (![file isNonEmpty]) {
failBlock(@"数据不能为空");
return;
}
NSDictionary *params = @{@"token_type" : @(type)};
[GMNetworking requestOCWithApi:API_FACE_GET_TOKEN method:GMHTTPMethodPost parameters:params completion:^(GMResponseOC * response) {
if (response.isSuccess)
{
//token get success
NSDictionary *dict = response.data;
NSString *token = dict[@"token"];
dispatch_group_t group = dispatch_group_create();
dispatch_group_enter(group);
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
QNUploadManager *upManager = [[QNUploadManager alloc] init];
// NSData *imageData = UIImageJPEGRepresentation(image,.7);
NSString *key = [self createImagePath];
[upManager putFile:file key:key token:token complete:^(QNResponseInfo *info, NSString *key, NSDictionary *resp) {
if (info) {
// 上传成功
if (block != nil) {
block(key);
}
} else {
if (failBlock != nil) {
failBlock(@"上传失败");
}
//上传失败
// [AppDelegate.visibleController toast:@"上传失败"];
}
dispatch_group_leave(group);
} option:nil];
});
}
else
{
//token get fail
// [AppDelegate.visibleController toast:response.message];
}
}];
}
/**
* 七牛上传视频
*/
- (void)setData:(NSData *)data getTokenType:(GetTokenType)type key:(NSString *)key block:(returnImageUrlBlock)block failBlock:(returnFailBlock)failBlock
{
// if ([[GMLoginManager shareInstance] showLoginViewIfNeeded]) {
// return;
// }
if (!data || data.length <= 0) {
failBlock(@"数据不能为空");
return;
}
NSDictionary *params = @{@"token_type" : @(type)};
__block NSString *oldKey = key;
[GMNetworking requestOCWithApi:API_FACE_GET_TOKEN method:GMHTTPMethodPost parameters:params completion:^(GMResponseOC * response) {
if (response.isSuccess)
{
//token get success
NSDictionary *dict = response.data;
NSString *token = dict[@"token"];
dispatch_group_t group = dispatch_group_create();
dispatch_group_enter(group);
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
QNUploadManager *upManager = [[QNUploadManager alloc] init];
// NSData *imageData = UIImageJPEGRepresentation(image,.7);
__block NSString *newKey = [self createImagePath];
if (oldKey == nil) {
oldKey = newKey;
}
[upManager putData:data key:oldKey token:token complete:^(QNResponseInfo *info, NSString *key, NSDictionary *resp) {
if (info) {
// 上传成功
if (block != nil) {
block(key);
}
} else {
if (failBlock != nil) {
failBlock(@"上传失败");
}
//上传失败
// [AppDelegate.visibleController toast:@"上传失败"];
}
dispatch_group_leave(group);
} option:nil];
});
}
else
{
//token get fail
// [AppDelegate.visibleController toast:response.message];
}
}];
}
- (void)getToken:(GetTokenType)type block:(returnImageUrlBlock)block failBlock:(returnFailBlock)failBlock
{
NSDictionary *params = @{@"token_type" : @(type)};
[GMNetworking requestOCWithApi:API_FACE_GET_TOKEN method:GMHTTPMethodPost parameters:params completion:^(GMResponseOC * response) {
if (response.isSuccess)
{
//token get success
NSDictionary *dict = response.data;
NSString *token = dict[@"token"];
block(token);
}
else
{
failBlock(@"token请求失败");
//token get fail
// [AppDelegate.visibleController toast:response.message];
}
}];
}
// 生成图片或者文件的路径
- (NSString *)createImagePath {
NSString *imagePath = @"";
// 1、先取 当前时间 按照 "年/月/日/时分" 转换 作为图片路径的上半部分--> 2018/10/25/1821
NSDate* date = [NSDate dateWithTimeIntervalSinceNow:0];//获取当前时间0秒后的时间
NSTimeInterval timeNow = [date timeIntervalSince1970];
NSString *frontPart = [self getDateStringWithTimeStr:timeNow];
imagePath = [imagePath stringByAppendingString:frontPart];
// 2、取当前的时间戳 + 随机字符串(长度 32) 组成字符串,将该字符串 MD5哈希,取该哈希值的前 12位 作为图片路径的下半部分 --> caf8f8d86886
NSString *timeNowStr = [NSString stringWithFormat:@"%.0f", timeNow];
NSString *radomStr = [self getRadomString];
NSString *secondPart = [timeNowStr stringByAppendingString:radomStr];
NSString *secondPartMD5 = [[self MD5ForLower32Bate:secondPart] substringToIndex:12];
return [NSString stringWithFormat:@"%@/%@", imagePath, secondPartMD5];
}
// 时间戳转字符串
- (NSString *)getDateStringWithTimeStr:(float )time{
NSDate *detailDate=[NSDate dateWithTimeIntervalSince1970:time];
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init]; //实例化一个NSDateFormatter对象
//设定时间格式,这里可以设置成自己需要的格式
[dateFormatter setDateFormat:@"yyyy/MM/dd/HHmm"];
NSString *currentDateStr = [dateFormatter stringFromDate: detailDate];
return currentDateStr;
}
// 取随机字符串
- (NSString *)getRadomString {
NSArray *array = [[NSArray alloc] initWithObjects:@"a",@"b",@"c",@"d",@"e",@"f",@"g",@"h",@"i",@"j",@"k",@"l",@"m",@"n",@"o",@"p",@"q",@"r",@"s",@"t",@"u",@"v",@"w",@"x",@"y",@"z",@"0",@"1",@"2",@"3",@"4",@"5",@"6",@"7",@"8",@"9",nil];
NSMutableArray *randomArray = [[NSMutableArray alloc] init];
while (randomArray.count < 32) {
int r = arc4random() % [array count];
[randomArray addObject:[array objectAtIndex:r]];
}
return [randomArray componentsJoinedByString:@""];
}
// MD5加密
-(NSString *)MD5ForLower32Bate:(NSString *)str{
//要进行UTF8的转码
const char* input = [str UTF8String];
unsigned char result[CC_MD5_DIGEST_LENGTH];
CC_MD5(input, (CC_LONG)strlen(input), result);
NSMutableString *digest = [NSMutableString stringWithCapacity:CC_MD5_DIGEST_LENGTH * 2];
for (NSInteger i = 0; i < CC_MD5_DIGEST_LENGTH; i++) {
[digest appendFormat:@"%02x", result[i]];
}
return digest;
}
@end
//
// GMAIConst.h
// AFNetworking
//
// Created by Q14 on 2020/1/13.
//
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface GMAIConst : NSObject
#pragma mark - API
extern NSString * const API_ONEIMAGE_POINTS;
// OneImage 创建
extern NSString * const API_ONEIMAGE_CREATE;
extern NSString * const API_FACE_GET_TOKEN;
extern NSString * const API_ONEIMAGE_BEAUTY_IMAGE;
extern NSString * const API_FACE_RESEARCH_REPORT;
//#define API_FACE_GET_TOKEN @"/api/app/upload_token"
//#define API_ONEIMAGE_CREATE @"/api/one_image/create/v3"// OneImage 创建
//#define API_ONEIMAGE_POINTS @"/api/one_image/points/v3"
#pragma mark - Const
extern NSString * const kFace_Id;
extern NSString * const kGMGeneMemoryLastData;
extern NSString * const kGMGeneMemoryGender;
@end
NS_ASSUME_NONNULL_END
//
// GMAIConst.m
// AFNetworking
//
// Created by Q14 on 2020/1/13.
//
#import "GMAIConst.h"
@implementation GMAIConst
#pragma mark - API
NSString * const API_ONEIMAGE_POINTS = @"/api/one_image/points/v3";
NSString * const API_ONEIMAGE_CREATE = @"/api/one_image/create/v3";
NSString * const API_FACE_GET_TOKEN = @"/api/app/upload_token";
NSString * const API_ONEIMAGE_BEAUTY_IMAGE = @"/api/one_image/beauty_image/get";
NSString * const API_FACE_RESEARCH_REPORT = @"/gm_ai/fece_research/report"; // 面孔起源报告结果
//#define API_ONEIMAGE_BEAUTY_IMAGE @"/api/one_image/beauty_image/get"
//
#pragma mark - Const
NSString * const kFace_Id = @"face_Id";
NSString * const kGMGeneMemoryLastData = @"GMGeneMemoryLastData"; // 面孔起源 上次报告
//#define kGMGeneMemoryGender @"GMGeneMemoryGender"
NSString * const kGMGeneMemoryGender = @"GMGeneMemoryLastData";
@end
//
// Target_AI.h
// Gengmei
//
// Created by Q14 on 2019/12/10.
// Copyright © 2019 更美互动信息科技有限公司. All rights reserved.
//
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface Target_AI : NSObject
- (UIViewController *)scan_faceimage_result:(NSDictionary *)param;
- (UIViewController *)scan_faceimage:(NSDictionary *)param;
@end
NS_ASSUME_NONNULL_END
//
// Target_AI.m
// Gengmei
//
// Created by Q14 on 2019/12/10.
// Copyright © 2019 更美互动信息科技有限公司. All rights reserved.
//
#import "Target_AI.h"
@import GMCache;
//#import "Gengmei-Swift.h"
//#import <GMCache>
@implementation Target_AI
/** 跳转至测肤结果页面 */
- (UIViewController *)scan_faceimage_result:(NSDictionary *)param {
// 如果有测肤数据就跳转测肤结果页面
// NSString *isShow = [GMCache fetchObjectAtDocumentPathWithkey:@"GMTestSkinIsShowData"];
// if ([isShow isNonEmpty]) {
// GMTestSkinAnimationWebView *controller = [[GMTestSkinAnimationWebView alloc] init];
// [AppDelegate.navigation pushViewController:controller animated:YES];
// return controller;
// } else { // 没有测肤数据则默认跳转测肤页面
// NewFaceShotController *controller = [[NewFaceShotController alloc] init];
// controller.cameraType = GMScanAnimationTypeScanSkin;
// [AppDelegate.navigation pushViewController:controller animated:YES];
// return controller;
// }
return nil;
}
/** 跳转至OneImage页面 */
- (UIViewController *)scan_faceimage:(NSDictionary *)param {
// NewFaceShotController *controller = [[NewFaceShotController alloc] init];
// controller.cameraType = [param[@"face_skin_tab_index"] integerValue];
//// controller.haveTask = [param[@"has_task"] boolValue];
// [AppDelegate.navigation pushViewController:controller animated:YES];
return nil;
}
-(UIViewController *)face_point:(NSDictionary *)params {
// GMFacePointViewController *vc = [GMFacePointViewController allo]
return nil;
}
@end
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