Full Code of CoderMJLee/MJExtension for AI

master 03ceefbcb51b cached
80 files
196.3 KB
58.1k tokens
8 symbols
1 requests
Download .txt
Showing preview only (215K chars total). Download the full file or copy to clipboard to get everything.
Repository: CoderMJLee/MJExtension
Branch: master
Commit: 03ceefbcb51b
Files: 80
Total size: 196.3 KB

Directory structure:
gitextract_nyxu_r8w/

├── .github/
│   └── ISSUE_TEMPLATE/
│       ├── bug--.md
│       └── feature--.md
├── .gitignore
├── .travis.yml
├── LICENSE
├── MJExtension/
│   ├── Info.plist
│   ├── MJExtension.h
│   ├── MJExtensionConst.h
│   ├── MJExtensionConst.m
│   ├── MJFoundation.h
│   ├── MJFoundation.m
│   ├── MJProperty.h
│   ├── MJProperty.m
│   ├── MJPropertyKey.h
│   ├── MJPropertyKey.m
│   ├── MJPropertyType.h
│   ├── MJPropertyType.m
│   ├── NSObject+MJClass.h
│   ├── NSObject+MJClass.m
│   ├── NSObject+MJCoding.h
│   ├── NSObject+MJCoding.m
│   ├── NSObject+MJKeyValue.h
│   ├── NSObject+MJKeyValue.m
│   ├── NSObject+MJProperty.h
│   ├── NSObject+MJProperty.m
│   ├── NSString+MJExtension.h
│   ├── NSString+MJExtension.m
│   └── PrivacyInfo.xcprivacy
├── MJExtension.podspec
├── MJExtension.xcodeproj/
│   ├── project.pbxproj
│   └── xcshareddata/
│       └── xcschemes/
│           ├── MJExtension.xcscheme
│           └── MJExtensionDemo.xcscheme
├── MJExtensionDemo/
│   ├── AppDelegate.swift
│   ├── Assets.xcassets/
│   │   ├── AccentColor.colorset/
│   │   │   └── Contents.json
│   │   ├── AppIcon.appiconset/
│   │   │   └── Contents.json
│   │   └── Contents.json
│   ├── Base.lproj/
│   │   ├── LaunchScreen.storyboard
│   │   └── Main.storyboard
│   └── Info.plist
├── MJExtensionTests/
│   ├── CoreDataModel/
│   │   ├── MJCoreDataTestModel.xcdatamodeld/
│   │   │   └── MJCoreDataTestModel.xcdatamodel/
│   │   │       └── contents
│   │   ├── MJCoreDataTester+CoreDataClass.swift
│   │   └── MJCoreDataTester+CoreDataProperties.swift
│   ├── CoreDataTests.swift
│   ├── Info.plist
│   ├── MJExtensionTests-Bridging-Header.h
│   ├── MJExtensionTests.m
│   ├── Model/
│   │   ├── MJAd.h
│   │   ├── MJAd.m
│   │   ├── MJBag.h
│   │   ├── MJBag.m
│   │   ├── MJBaseObject.h
│   │   ├── MJBaseObject.m
│   │   ├── MJBook.h
│   │   ├── MJBook.m
│   │   ├── MJBox.h
│   │   ├── MJBox.m
│   │   ├── MJCat.h
│   │   ├── MJCat.m
│   │   ├── MJDog.h
│   │   ├── MJDog.m
│   │   ├── MJExtensionConfig.h
│   │   ├── MJExtensionConfig.m
│   │   ├── MJFrenchUser.h
│   │   ├── MJFrenchUser.m
│   │   ├── MJPerson.h
│   │   ├── MJPerson.m
│   │   ├── MJStatus.h
│   │   ├── MJStatus.m
│   │   ├── MJStatusResult.h
│   │   ├── MJStatusResult.m
│   │   ├── MJStudent.h
│   │   ├── MJStudent.m
│   │   ├── MJUser.h
│   │   └── MJUser.m
│   ├── MultiThreadTests.swift
│   ├── PrefixHeader.pch
│   ├── SwiftModel/
│   │   └── MJTester.swift
│   └── SwiftModelTests.swift
├── Package.swift
└── README.md

================================================
FILE CONTENTS
================================================

================================================
FILE: .github/ISSUE_TEMPLATE/bug--.md
================================================
---
name: Bug上报
about: 提交Bug让框架更加健壮, 务必提供 Demo
title: ''
labels: ''
assignees: ''

---

## 为了排除你自己的问题, 请写一个 Demo
最好能提供出现bug的Demo

**描述bug**
清晰简单地描述这个bug是啥

**怎么样重现这个bug**
1. 显示哪个页面
2. 点击哪个位置
3. 滚动到哪个位置
4. 发生了什么错误

**你期望的结果是什么?**
你本来期望得到的正确结果是怎样的?就是解决bug之后的结果

**截图**
如果有必要的话,请上传几张截图

**运行环境**
 - iPhoneX
 - iOS 12
 - Xcode 11


================================================
FILE: .github/ISSUE_TEMPLATE/feature--.md
================================================
---
name: Feature建议
about: 为框架提供新功能建议
title: ''
labels: ''
assignees: ''

---

**你的新功能建议是否牵扯到某个常见的问题?**
比如,在开发中,经常遇到XXX问题,目前框架的功能不足以解决这个问题

**你希望达到的效果**
比如,我希望框架能够帮助我解决XXX问题,实现XXXX那样的效果

**你能考虑到的可选实现方案**
描述一下你认为要实现这个功能,有哪些可行的一些实现方案?


================================================
FILE: .gitignore
================================================
.DS_Store

# Xcode
build/*
*.pbxuser
!default.pbxuser
*.mode1v3
!default.mode1v3
*.mode2v3
!default.mode2v3
*.perspectivev3
!default.perspectivev3
*.xcworkspace
!default.xcworkspace
*.xcuserdatad
xcuserdata
profile
*.moved-aside
*.xcuserstate
*.swo
*.swp

*.xcbkptlist


================================================
FILE: .travis.yml
================================================
language: objective-c
xcode_project: MJExtension.xcodeproj
xcode_scheme: MJExtension

env:
  global:
    - FRAMEWORK_NAME=MJExtension
before_install:
  - gem install xcpretty
  
script:
  - xcodebuild -project $FRAMEWORK_NAME.xcodeproj -scheme $FRAMEWORK_NAME build | xcpretty
  - pod lib lint

notifications:
  email: false


================================================
FILE: LICENSE
================================================
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.


================================================
FILE: MJExtension/Info.plist
================================================
<?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>$(DEVELOPMENT_LANGUAGE)</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</string>
	<key>CFBundleVersion</key>
	<string>$(CURRENT_PROJECT_VERSION)</string>
</dict>
</plist>


================================================
FILE: MJExtension/MJExtension.h
================================================
//
//  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>




================================================
FILE: MJExtension/MJExtensionConst.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 \
extern dispatch_semaphore_t mje_signalSemaphore; \
extern dispatch_once_t mje_onceTokenSemaphore; \
dispatch_once(&mje_onceTokenSemaphore, ^{ \
    mje_signalSemaphore = dispatch_semaphore_create(1); \
});

// 过期
#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


================================================
FILE: MJExtension/MJExtensionConst.m
================================================
#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

================================================
FILE: MJExtension/MJFoundation.h
================================================
//
//  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


================================================
FILE: MJExtension/MJFoundation.m
================================================
//
//  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


================================================
FILE: MJExtension/MJProperty.h
================================================
//
//  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


================================================
FILE: MJExtension/MJProperty.m
================================================
//
//  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 setPropertyKeys:@[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 setPropertyKeys:keyses forClass:c];
        }
    }
}

/** 对应着字典中的多级key */
- (void)setPropertyKeys:(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


================================================
FILE: MJExtension/MJPropertyKey.h
================================================
//
//  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


================================================
FILE: MJExtension/MJPropertyKey.m
================================================
//
//  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


================================================
FILE: MJExtension/MJPropertyType.h
================================================
//
//  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

================================================
FILE: MJExtension/MJPropertyType.m
================================================
//
//  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


================================================
FILE: MJExtension/NSObject+MJClass.h
================================================
//
//  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


================================================
FILE: MJExtension/NSObject+MJClass.m
================================================
//
//  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 {
    MJExtensionSemaphoreCreate
    MJ_LOCK(mje_signalSemaphore);
    if (block) {
        objc_setAssociatedObject(self, key, block(), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    } else {
        objc_setAssociatedObject(self, key, nil, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    }
    
    // 清空数据
    [[self mj_classDictForKey:key] removeAllObjects];
    MJ_UNLOCK(mje_signalSemaphore);
}

+ (NSMutableArray *)mj_totalObjectsWithSelector:(SEL)selector key:(const char *)key
{
    MJExtensionSemaphoreCreate
    MJ_LOCK(mje_signalSemaphore);
    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];
        }];
    }
    MJ_UNLOCK(mje_signalSemaphore);
    return array;
}
@end


================================================
FILE: MJExtension/NSObject+MJCoding.h
================================================
//
//  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

#define MJSecureCodingImplementation(CLASS, FLAG) \
@interface CLASS (MJSecureCoding) <NSSecureCoding> \
@end \
@implementation CLASS (MJSecureCoding) \
MJCodingImplementation \
+ (BOOL)supportsSecureCoding { \
return FLAG; \
} \
@end \



================================================
FILE: MJExtension/NSObject+MJCoding.m
================================================
//
//  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;
        
        // fixed `-[NSKeyedUnarchiver validateAllowedClass:forKey:] allowed unarchiving safe plist type ''NSNumber'(This will be disallowed in the future.)` warning.
        Class genericClass = [property objectClassInArrayForClass:property.srcClass];
        // If genericClass exists, property.type.typeClass would be a collection type(Array, Set, Dictionary). This scenario([obj, nil, obj, nil]) would not happened.
        NSSet *classes = [NSSet setWithObjects:NSNumber.class,
                          property.type.typeClass, genericClass, nil];
        id value = [decoder decodeObjectOfClasses:classes forKey:property.name];
        if (value == nil) { // 兼容以前的MJExtension版本
            value = [decoder decodeObjectForKey:[@"_" stringByAppendingString:property.name]];
        }
        if (value == nil) return;
        [property setValue:value forObject:self];
    }];
}
@end


================================================
FILE: MJExtension/NSObject+MJKeyValue.h
================================================
//
//  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:(NSMutableDictionary *)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


================================================
FILE: MJExtension/NSObject+MJKeyValue.m
================================================
//
//  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)mj_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 mj_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);
#ifdef DEBUG
            [exception raise];
#endif
        }
    }];
    
    // 转换完毕
    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 = [(NSManagedObject *)self entity].name;
        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);
#ifdef DEBUG
            [exception raise];
#endif
        }
    }];
    
    // 转换完毕
    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


================================================
FILE: MJExtension/NSObject+MJProperty.h
================================================
//
//  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


================================================
FILE: MJExtension/NSObject+MJProperty.m
================================================
//
//  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';

dispatch_semaphore_t mje_signalSemaphore;
dispatch_once_t mje_onceTokenSemaphore;

@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
{
    // 获得成员变量
    NSArray *cachedProperties = [self mj_properties];
    // 遍历成员变量
    BOOL stop = NO;
    for (MJProperty *property in cachedProperties) {
        enumeration(property, &stop);
        if (stop) break;
    }
}

#pragma mark - 公共方法
+ (NSArray *)mj_properties
{
    MJExtensionSemaphoreCreate
    MJ_LOCK(mje_signalSemaphore);
    NSMutableDictionary *cachedInfo = [self mj_propertyDictForKey:&MJCachedPropertiesKey];
    NSMutableArray *cachedProperties = cachedInfo[NSStringFromClass(self)];
    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);
        }];
        
        cachedInfo[NSStringFromClass(self)] = cachedProperties;
    }
    NSArray *properties = [cachedProperties copy];
    MJ_UNLOCK(mje_signalSemaphore);
    
    return properties;
}

#pragma mark - 新值配置
+ (void)mj_setupNewValueFromOldValue:(MJNewValueFromOldValue)newValueFormOldValue {
    MJExtensionSemaphoreCreate
    MJ_LOCK(mje_signalSemaphore);
    objc_setAssociatedObject(self, &MJNewValueFromOldValueKey, newValueFormOldValue, OBJC_ASSOCIATION_COPY_NONATOMIC);
    MJ_UNLOCK(mje_signalSemaphore);
}

+ (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];
    }
    
    MJExtensionSemaphoreCreate
    MJ_LOCK(mje_signalSemaphore);
    // 查看静态设置
    __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;
        }
    }];
    MJ_UNLOCK(mje_signalSemaphore);
    return newValue;
}

+ (void)mj_removeCachedProperties {
    MJExtensionSemaphoreCreate
    MJ_LOCK(mje_signalSemaphore);
    [[self mj_propertyDictForKey:&MJCachedPropertiesKey] removeAllObjects];
    MJ_UNLOCK(mje_signalSemaphore);
}

#pragma mark - array model class配置
+ (void)mj_setupObjectClassInArray:(MJObjectClassInArray)objectClassInArray
{
    [self mj_setupBlockReturnValue:objectClassInArray key:&MJObjectClassInArrayKey];
    
    [self mj_removeCachedProperties];
}

#pragma mark - key配置

+ (void)mj_setupReplacedKeyFromPropertyName:(MJReplacedKeyFromPropertyName)replacedKeyFromPropertyName {
    [self mj_setupBlockReturnValue:replacedKeyFromPropertyName key:&MJReplacedKeyFromPropertyNameKey];
    
    [self mj_removeCachedProperties];
}

+ (void)mj_setupReplacedKeyFromPropertyName121:(MJReplacedKeyFromPropertyName121)replacedKeyFromPropertyName121 {
    MJExtensionSemaphoreCreate
    MJ_LOCK(mje_signalSemaphore);
    objc_setAssociatedObject(self, &MJReplacedKeyFromPropertyName121Key, replacedKeyFromPropertyName121, OBJC_ASSOCIATION_COPY_NONATOMIC);
    
    [[self mj_propertyDictForKey:&MJCachedPropertiesKey] removeAllObjects];
    MJ_UNLOCK(mje_signalSemaphore);
}
@end
#pragma clang diagnostic pop


================================================
FILE: MJExtension/NSString+MJExtension.h
================================================
//
//  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


================================================
FILE: MJExtension/NSString+MJExtension.m
================================================
//
//  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


================================================
FILE: MJExtension/PrivacyInfo.xcprivacy
================================================
<?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>NSPrivacyTracking</key>
	<false/>
	<key>NSPrivacyTrackingDomains</key>
	<array/>
	<key>NSPrivacyAccessedAPITypes</key>
	<array/>
	<key>NSPrivacyCollectedDataTypes</key>
	<array/>
</dict>
</plist>


================================================
FILE: MJExtension.podspec
================================================
Pod::Spec.new do |s|
  s.name         = "MJExtension"
  s.version      = "3.4.2"
  s.ios.deployment_target = '12.0'
  s.osx.deployment_target = '10.13'
  s.tvos.deployment_target = '12.0'
  s.watchos.deployment_target = '4.0'
  s.summary      = "A fast and convenient conversion between JSON and model"
  s.homepage     = "https://github.com/CoderMJLee/MJExtension"
  s.license      = "MIT"
  s.author             = { "MJ Lee" => "richermj123go@vip.qq.com" }
  s.social_media_url   = "http://weibo.com/exceptions"
  s.source       = { :git => "https://github.com/CoderMJLee/MJExtension.git", :tag => s.version }
  s.source_files  = "MJExtension"
  s.resource_bundles = {
      'MJExtension' => ['MJExtension/PrivacyInfo.xcprivacy']
  }
  s.requires_arc = true
end


================================================
FILE: MJExtension.xcodeproj/project.pbxproj
================================================
// !$*UTF8*$!
{
	archiveVersion = 1;
	classes = {
	};
	objectVersion = 50;
	objects = {

/* Begin PBXBuildFile section */
		01052EAD25F872D00049EC6F /* MultiThreadTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01052EAC25F872D00049EC6F /* MultiThreadTests.swift */; };
		0107507826E88DD400AAEA10 /* MJCoreDataTestModel.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = 0107507626E88DD400AAEA10 /* MJCoreDataTestModel.xcdatamodeld */; };
		0107507B26E88EAC00AAEA10 /* MJCoreDataTester+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0107507926E88EAC00AAEA10 /* MJCoreDataTester+CoreDataClass.swift */; };
		0107507C26E88EAC00AAEA10 /* MJCoreDataTester+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0107507A26E88EAC00AAEA10 /* MJCoreDataTester+CoreDataProperties.swift */; };
		0107507E26E890C100AAEA10 /* CoreDataTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0107507D26E890C100AAEA10 /* CoreDataTests.swift */; };
		0107508726E8C2DB00AAEA10 /* MJExtension.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2D2DB9F42317DA64005A689E /* MJExtension.framework */; };
		0107508F26E8C59F00AAEA10 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0107508E26E8C59F00AAEA10 /* AppDelegate.swift */; };
		0107509626E8C5A000AAEA10 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 0107509426E8C5A000AAEA10 /* Main.storyboard */; };
		0107509826E8C5A300AAEA10 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 0107509726E8C5A300AAEA10 /* Assets.xcassets */; };
		0107509B26E8C5A300AAEA10 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 0107509926E8C5A300AAEA10 /* LaunchScreen.storyboard */; };
		0121B2CE26E9B277008EE973 /* MJCoreDataTestModel.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = 0107507626E88DD400AAEA10 /* MJCoreDataTestModel.xcdatamodeld */; };
		012254B52BA1481B007BD496 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = 012254B42BA1481B007BD496 /* PrivacyInfo.xcprivacy */; };
		0130EE80233C56D8008D2386 /* MJFrenchUser.m in Sources */ = {isa = PBXBuildFile; fileRef = 0130EE7F233C56D8008D2386 /* MJFrenchUser.m */; };
		0179886C24EFA460007F7FBC /* MJTester.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0179886B24EFA460007F7FBC /* MJTester.swift */; };
		0179887024EFA58B007F7FBC /* SwiftModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0179886F24EFA58B007F7FBC /* SwiftModelTests.swift */; };
		01F28187248F64ED008775BA /* MJCat.m in Sources */ = {isa = PBXBuildFile; fileRef = 01F28186248F64ED008775BA /* MJCat.m */; };
		2D2DB9F82317DA64005A689E /* MJExtension.h in Headers */ = {isa = PBXBuildFile; fileRef = 2D2DB9F62317DA64005A689E /* MJExtension.h */; settings = {ATTRIBUTES = (Public, ); }; };
		2D2DBA152317DB33005A689E /* NSObject+MJProperty.h in Headers */ = {isa = PBXBuildFile; fileRef = 2D2DBA012317DB32005A689E /* NSObject+MJProperty.h */; settings = {ATTRIBUTES = (Public, ); }; };
		2D2DBA162317DB33005A689E /* MJExtensionConst.m in Sources */ = {isa = PBXBuildFile; fileRef = 2D2DBA022317DB32005A689E /* MJExtensionConst.m */; };
		2D2DBA172317DB33005A689E /* NSObject+MJCoding.m in Sources */ = {isa = PBXBuildFile; fileRef = 2D2DBA032317DB32005A689E /* NSObject+MJCoding.m */; };
		2D2DBA182317DB33005A689E /* MJProperty.m in Sources */ = {isa = PBXBuildFile; fileRef = 2D2DBA042317DB32005A689E /* MJProperty.m */; };
		2D2DBA192317DB33005A689E /* MJPropertyKey.m in Sources */ = {isa = PBXBuildFile; fileRef = 2D2DBA052317DB32005A689E /* MJPropertyKey.m */; };
		2D2DBA1A2317DB33005A689E /* NSObject+MJKeyValue.m in Sources */ = {isa = PBXBuildFile; fileRef = 2D2DBA062317DB32005A689E /* NSObject+MJKeyValue.m */; };
		2D2DBA1B2317DB33005A689E /* NSObject+MJClass.h in Headers */ = {isa = PBXBuildFile; fileRef = 2D2DBA072317DB32005A689E /* NSObject+MJClass.h */; settings = {ATTRIBUTES = (Public, ); }; };
		2D2DBA1C2317DB33005A689E /* MJExtensionConst.h in Headers */ = {isa = PBXBuildFile; fileRef = 2D2DBA082317DB32005A689E /* MJExtensionConst.h */; settings = {ATTRIBUTES = (Public, ); }; };
		2D2DBA1D2317DB33005A689E /* NSObject+MJClass.m in Sources */ = {isa = PBXBuildFile; fileRef = 2D2DBA092317DB32005A689E /* NSObject+MJClass.m */; };
		2D2DBA1E2317DB33005A689E /* MJPropertyType.h in Headers */ = {isa = PBXBuildFile; fileRef = 2D2DBA0A2317DB32005A689E /* MJPropertyType.h */; settings = {ATTRIBUTES = (Public, ); }; };
		2D2DBA1F2317DB33005A689E /* NSString+MJExtension.m in Sources */ = {isa = PBXBuildFile; fileRef = 2D2DBA0B2317DB32005A689E /* NSString+MJExtension.m */; };
		2D2DBA202317DB33005A689E /* MJFoundation.m in Sources */ = {isa = PBXBuildFile; fileRef = 2D2DBA0C2317DB32005A689E /* MJFoundation.m */; };
		2D2DBA212317DB33005A689E /* NSObject+MJCoding.h in Headers */ = {isa = PBXBuildFile; fileRef = 2D2DBA0D2317DB32005A689E /* NSObject+MJCoding.h */; settings = {ATTRIBUTES = (Public, ); }; };
		2D2DBA222317DB33005A689E /* MJFoundation.h in Headers */ = {isa = PBXBuildFile; fileRef = 2D2DBA0E2317DB32005A689E /* MJFoundation.h */; settings = {ATTRIBUTES = (Public, ); }; };
		2D2DBA232317DB33005A689E /* NSObject+MJProperty.m in Sources */ = {isa = PBXBuildFile; fileRef = 2D2DBA0F2317DB32005A689E /* NSObject+MJProperty.m */; };
		2D2DBA242317DB33005A689E /* MJPropertyType.m in Sources */ = {isa = PBXBuildFile; fileRef = 2D2DBA102317DB32005A689E /* MJPropertyType.m */; };
		2D2DBA252317DB33005A689E /* MJPropertyKey.h in Headers */ = {isa = PBXBuildFile; fileRef = 2D2DBA112317DB32005A689E /* MJPropertyKey.h */; settings = {ATTRIBUTES = (Public, ); }; };
		2D2DBA262317DB33005A689E /* MJProperty.h in Headers */ = {isa = PBXBuildFile; fileRef = 2D2DBA122317DB32005A689E /* MJProperty.h */; settings = {ATTRIBUTES = (Public, ); }; };
		2D2DBA272317DB33005A689E /* NSObject+MJKeyValue.h in Headers */ = {isa = PBXBuildFile; fileRef = 2D2DBA132317DB33005A689E /* NSObject+MJKeyValue.h */; settings = {ATTRIBUTES = (Public, ); }; };
		2D2DBA282317DB33005A689E /* NSString+MJExtension.h in Headers */ = {isa = PBXBuildFile; fileRef = 2D2DBA142317DB33005A689E /* NSString+MJExtension.h */; settings = {ATTRIBUTES = (Public, ); }; };
		2D2DBA582317DBB9005A689E /* MJExtensionTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 2D2DBA572317DBB9005A689E /* MJExtensionTests.m */; };
		2D2DBA7A2317DBE0005A689E /* MJDog.m in Sources */ = {isa = PBXBuildFile; fileRef = 2D2DBA602317DBDF005A689E /* MJDog.m */; };
		2D2DBA7B2317DBE0005A689E /* MJBox.m in Sources */ = {isa = PBXBuildFile; fileRef = 2D2DBA652317DBDF005A689E /* MJBox.m */; };
		2D2DBA7C2317DBE0005A689E /* MJStudent.m in Sources */ = {isa = PBXBuildFile; fileRef = 2D2DBA662317DBDF005A689E /* MJStudent.m */; };
		2D2DBA7D2317DBE0005A689E /* MJBook.m in Sources */ = {isa = PBXBuildFile; fileRef = 2D2DBA692317DBDF005A689E /* MJBook.m */; };
		2D2DBA7E2317DBE0005A689E /* MJBag.m in Sources */ = {isa = PBXBuildFile; fileRef = 2D2DBA6A2317DBDF005A689E /* MJBag.m */; };
		2D2DBA7F2317DBE0005A689E /* MJBaseObject.m in Sources */ = {isa = PBXBuildFile; fileRef = 2D2DBA6C2317DBDF005A689E /* MJBaseObject.m */; };
		2D2DBA802317DBE0005A689E /* MJUser.m in Sources */ = {isa = PBXBuildFile; fileRef = 2D2DBA6E2317DBDF005A689E /* MJUser.m */; };
		2D2DBA812317DBE0005A689E /* MJStatusResult.m in Sources */ = {isa = PBXBuildFile; fileRef = 2D2DBA712317DBDF005A689E /* MJStatusResult.m */; };
		2D2DBA822317DBE0005A689E /* MJStatus.m in Sources */ = {isa = PBXBuildFile; fileRef = 2D2DBA722317DBDF005A689E /* MJStatus.m */; };
		2D2DBA832317DBE0005A689E /* MJAd.m in Sources */ = {isa = PBXBuildFile; fileRef = 2D2DBA732317DBDF005A689E /* MJAd.m */; };
		2D2DBA842317DBE0005A689E /* MJExtensionConfig.m in Sources */ = {isa = PBXBuildFile; fileRef = 2D2DBA752317DBDF005A689E /* MJExtensionConfig.m */; };
		2D2DBA852317DBE0005A689E /* MJPerson.m in Sources */ = {isa = PBXBuildFile; fileRef = 2D2DBA772317DBDF005A689E /* MJPerson.m */; };
/* End PBXBuildFile section */

/* Begin PBXContainerItemProxy section */
		010750A026E8C5AC00AAEA10 /* PBXContainerItemProxy */ = {
			isa = PBXContainerItemProxy;
			containerPortal = 2D2DB9C02317DA07005A689E /* Project object */;
			proxyType = 1;
			remoteGlobalIDString = 0107508B26E8C59F00AAEA10;
			remoteInfo = MJExtensionDemo;
		};
/* End PBXContainerItemProxy section */

/* Begin PBXFileReference section */
		01052EAC25F872D00049EC6F /* MultiThreadTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MultiThreadTests.swift; sourceTree = "<group>"; };
		0107507726E88DD400AAEA10 /* MJCoreDataTestModel.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = MJCoreDataTestModel.xcdatamodel; sourceTree = "<group>"; };
		0107507926E88EAC00AAEA10 /* MJCoreDataTester+CoreDataClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MJCoreDataTester+CoreDataClass.swift"; sourceTree = "<group>"; };
		0107507A26E88EAC00AAEA10 /* MJCoreDataTester+CoreDataProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MJCoreDataTester+CoreDataProperties.swift"; sourceTree = "<group>"; };
		0107507D26E890C100AAEA10 /* CoreDataTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CoreDataTests.swift; sourceTree = "<group>"; };
		0107508C26E8C59F00AAEA10 /* MJExtensionDemo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = MJExtensionDemo.app; sourceTree = BUILT_PRODUCTS_DIR; };
		0107508E26E8C59F00AAEA10 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
		0107509526E8C5A000AAEA10 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; };
		0107509726E8C5A300AAEA10 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
		0107509A26E8C5A300AAEA10 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
		0107509C26E8C5A300AAEA10 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
		012254B42BA1481B007BD496 /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xml; path = PrivacyInfo.xcprivacy; sourceTree = "<group>"; };
		0130EE7E233C56D8008D2386 /* MJFrenchUser.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MJFrenchUser.h; sourceTree = "<group>"; };
		0130EE7F233C56D8008D2386 /* MJFrenchUser.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MJFrenchUser.m; sourceTree = "<group>"; };
		0179886A24EFA460007F7FBC /* MJExtensionTests-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "MJExtensionTests-Bridging-Header.h"; sourceTree = "<group>"; };
		0179886B24EFA460007F7FBC /* MJTester.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MJTester.swift; sourceTree = "<group>"; };
		0179886F24EFA58B007F7FBC /* SwiftModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwiftModelTests.swift; sourceTree = "<group>"; };
		01F28185248F64ED008775BA /* MJCat.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MJCat.h; sourceTree = "<group>"; };
		01F28186248F64ED008775BA /* MJCat.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MJCat.m; sourceTree = "<group>"; };
		2D2DB9F42317DA64005A689E /* MJExtension.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = MJExtension.framework; sourceTree = BUILT_PRODUCTS_DIR; };
		2D2DB9F62317DA64005A689E /* MJExtension.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MJExtension.h; sourceTree = "<group>"; };
		2D2DB9F72317DA64005A689E /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
		2D2DBA012317DB32005A689E /* NSObject+MJProperty.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSObject+MJProperty.h"; sourceTree = "<group>"; };
		2D2DBA022317DB32005A689E /* MJExtensionConst.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MJExtensionConst.m; sourceTree = "<group>"; };
		2D2DBA032317DB32005A689E /* NSObject+MJCoding.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSObject+MJCoding.m"; sourceTree = "<group>"; };
		2D2DBA042317DB32005A689E /* MJProperty.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MJProperty.m; sourceTree = "<group>"; };
		2D2DBA052317DB32005A689E /* MJPropertyKey.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MJPropertyKey.m; sourceTree = "<group>"; };
		2D2DBA062317DB32005A689E /* NSObject+MJKeyValue.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSObject+MJKeyValue.m"; sourceTree = "<group>"; };
		2D2DBA072317DB32005A689E /* NSObject+MJClass.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSObject+MJClass.h"; sourceTree = "<group>"; };
		2D2DBA082317DB32005A689E /* MJExtensionConst.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MJExtensionConst.h; sourceTree = "<group>"; };
		2D2DBA092317DB32005A689E /* NSObject+MJClass.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSObject+MJClass.m"; sourceTree = "<group>"; };
		2D2DBA0A2317DB32005A689E /* MJPropertyType.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MJPropertyType.h; sourceTree = "<group>"; };
		2D2DBA0B2317DB32005A689E /* NSString+MJExtension.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSString+MJExtension.m"; sourceTree = "<group>"; };
		2D2DBA0C2317DB32005A689E /* MJFoundation.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MJFoundation.m; sourceTree = "<group>"; };
		2D2DBA0D2317DB32005A689E /* NSObject+MJCoding.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSObject+MJCoding.h"; sourceTree = "<group>"; };
		2D2DBA0E2317DB32005A689E /* MJFoundation.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MJFoundation.h; sourceTree = "<group>"; };
		2D2DBA0F2317DB32005A689E /* NSObject+MJProperty.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSObject+MJProperty.m"; sourceTree = "<group>"; };
		2D2DBA102317DB32005A689E /* MJPropertyType.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MJPropertyType.m; sourceTree = "<group>"; };
		2D2DBA112317DB32005A689E /* MJPropertyKey.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MJPropertyKey.h; sourceTree = "<group>"; };
		2D2DBA122317DB32005A689E /* MJProperty.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MJProperty.h; sourceTree = "<group>"; };
		2D2DBA132317DB33005A689E /* NSObject+MJKeyValue.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSObject+MJKeyValue.h"; sourceTree = "<group>"; };
		2D2DBA142317DB33005A689E /* NSString+MJExtension.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSString+MJExtension.h"; sourceTree = "<group>"; };
		2D2DBA552317DBB9005A689E /* MJExtensionTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = MJExtensionTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
		2D2DBA572317DBB9005A689E /* MJExtensionTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MJExtensionTests.m; sourceTree = "<group>"; };
		2D2DBA592317DBB9005A689E /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
		2D2DBA602317DBDF005A689E /* MJDog.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MJDog.m; sourceTree = "<group>"; };
		2D2DBA612317DBDF005A689E /* MJBaseObject.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MJBaseObject.h; sourceTree = "<group>"; };
		2D2DBA622317DBDF005A689E /* MJStatus.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MJStatus.h; sourceTree = "<group>"; };
		2D2DBA632317DBDF005A689E /* MJAd.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MJAd.h; sourceTree = "<group>"; };
		2D2DBA642317DBDF005A689E /* MJStatusResult.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MJStatusResult.h; sourceTree = "<group>"; };
		2D2DBA652317DBDF005A689E /* MJBox.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MJBox.m; sourceTree = "<group>"; };
		2D2DBA662317DBDF005A689E /* MJStudent.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MJStudent.m; sourceTree = "<group>"; };
		2D2DBA672317DBDF005A689E /* MJUser.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MJUser.h; sourceTree = "<group>"; };
		2D2DBA682317DBDF005A689E /* MJPerson.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MJPerson.h; sourceTree = "<group>"; };
		2D2DBA692317DBDF005A689E /* MJBook.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MJBook.m; sourceTree = "<group>"; };
		2D2DBA6A2317DBDF005A689E /* MJBag.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MJBag.m; sourceTree = "<group>"; };
		2D2DBA6B2317DBDF005A689E /* MJExtensionConfig.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MJExtensionConfig.h; sourceTree = "<group>"; };
		2D2DBA6C2317DBDF005A689E /* MJBaseObject.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MJBaseObject.m; sourceTree = "<group>"; };
		2D2DBA6D2317DBDF005A689E /* MJDog.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MJDog.h; sourceTree = "<group>"; };
		2D2DBA6E2317DBDF005A689E /* MJUser.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MJUser.m; sourceTree = "<group>"; };
		2D2DBA6F2317DBDF005A689E /* MJStudent.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MJStudent.h; sourceTree = "<group>"; };
		2D2DBA702317DBDF005A689E /* MJBox.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MJBox.h; sourceTree = "<group>"; };
		2D2DBA712317DBDF005A689E /* MJStatusResult.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MJStatusResult.m; sourceTree = "<group>"; };
		2D2DBA722317DBDF005A689E /* MJStatus.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MJStatus.m; sourceTree = "<group>"; };
		2D2DBA732317DBDF005A689E /* MJAd.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MJAd.m; sourceTree = "<group>"; };
		2D2DBA742317DBDF005A689E /* MJBag.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MJBag.h; sourceTree = "<group>"; };
		2D2DBA752317DBDF005A689E /* MJExtensionConfig.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MJExtensionConfig.m; sourceTree = "<group>"; };
		2D2DBA762317DBDF005A689E /* MJBook.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MJBook.h; sourceTree = "<group>"; };
		2D2DBA772317DBDF005A689E /* MJPerson.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MJPerson.m; sourceTree = "<group>"; };
		2D2DBA872317DCCF005A689E /* PrefixHeader.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = PrefixHeader.pch; sourceTree = "<group>"; };
/* End PBXFileReference section */

/* Begin PBXFrameworksBuildPhase section */
		0107508926E8C59F00AAEA10 /* Frameworks */ = {
			isa = PBXFrameworksBuildPhase;
			buildActionMask = 2147483647;
			files = (
			);
			runOnlyForDeploymentPostprocessing = 0;
		};
		2D2DB9F12317DA64005A689E /* Frameworks */ = {
			isa = PBXFrameworksBuildPhase;
			buildActionMask = 2147483647;
			files = (
			);
			runOnlyForDeploymentPostprocessing = 0;
		};
		2D2DBA522317DBB9005A689E /* Frameworks */ = {
			isa = PBXFrameworksBuildPhase;
			buildActionMask = 2147483647;
			files = (
				0107508726E8C2DB00AAEA10 /* MJExtension.framework in Frameworks */,
			);
			runOnlyForDeploymentPostprocessing = 0;
		};
/* End PBXFrameworksBuildPhase section */

/* Begin PBXGroup section */
		0107507526E88DA600AAEA10 /* CoreDataModel */ = {
			isa = PBXGroup;
			children = (
				0107507626E88DD400AAEA10 /* MJCoreDataTestModel.xcdatamodeld */,
				0107507926E88EAC00AAEA10 /* MJCoreDataTester+CoreDataClass.swift */,
				0107507A26E88EAC00AAEA10 /* MJCoreDataTester+CoreDataProperties.swift */,
			);
			path = CoreDataModel;
			sourceTree = "<group>";
		};
		0107508626E8C2DB00AAEA10 /* Frameworks */ = {
			isa = PBXGroup;
			children = (
			);
			name = Frameworks;
			sourceTree = "<group>";
		};
		0107508D26E8C59F00AAEA10 /* MJExtensionDemo */ = {
			isa = PBXGroup;
			children = (
				0107508E26E8C59F00AAEA10 /* AppDelegate.swift */,
				0107509426E8C5A000AAEA10 /* Main.storyboard */,
				0107509726E8C5A300AAEA10 /* Assets.xcassets */,
				0107509926E8C5A300AAEA10 /* LaunchScreen.storyboard */,
				0107509C26E8C5A300AAEA10 /* Info.plist */,
			);
			path = MJExtensionDemo;
			sourceTree = "<group>";
		};
		0179886924EFA422007F7FBC /* SwiftModel */ = {
			isa = PBXGroup;
			children = (
				0179886B24EFA460007F7FBC /* MJTester.swift */,
			);
			path = SwiftModel;
			sourceTree = "<group>";
		};
		2D2DB9BF2317DA07005A689E = {
			isa = PBXGroup;
			children = (
				2D2DB9F52317DA64005A689E /* MJExtension */,
				2D2DBA562317DBB9005A689E /* MJExtensionTests */,
				0107508D26E8C59F00AAEA10 /* MJExtensionDemo */,
				2D2DB9C92317DA07005A689E /* Products */,
				0107508626E8C2DB00AAEA10 /* Frameworks */,
			);
			sourceTree = "<group>";
		};
		2D2DB9C92317DA07005A689E /* Products */ = {
			isa = PBXGroup;
			children = (
				2D2DB9F42317DA64005A689E /* MJExtension.framework */,
				2D2DBA552317DBB9005A689E /* MJExtensionTests.xctest */,
				0107508C26E8C59F00AAEA10 /* MJExtensionDemo.app */,
			);
			name = Products;
			sourceTree = "<group>";
		};
		2D2DB9F52317DA64005A689E /* MJExtension */ = {
			isa = PBXGroup;
			children = (
				2D2DBA082317DB32005A689E /* MJExtensionConst.h */,
				2D2DBA022317DB32005A689E /* MJExtensionConst.m */,
				2D2DBA0E2317DB32005A689E /* MJFoundation.h */,
				2D2DBA0C2317DB32005A689E /* MJFoundation.m */,
				2D2DBA122317DB32005A689E /* MJProperty.h */,
				2D2DBA042317DB32005A689E /* MJProperty.m */,
				2D2DBA112317DB32005A689E /* MJPropertyKey.h */,
				2D2DBA052317DB32005A689E /* MJPropertyKey.m */,
				2D2DBA0A2317DB32005A689E /* MJPropertyType.h */,
				2D2DBA102317DB32005A689E /* MJPropertyType.m */,
				2D2DBA072317DB32005A689E /* NSObject+MJClass.h */,
				2D2DBA092317DB32005A689E /* NSObject+MJClass.m */,
				2D2DBA0D2317DB32005A689E /* NSObject+MJCoding.h */,
				2D2DBA032317DB32005A689E /* NSObject+MJCoding.m */,
				2D2DBA132317DB33005A689E /* NSObject+MJKeyValue.h */,
				2D2DBA062317DB32005A689E /* NSObject+MJKeyValue.m */,
				2D2DBA012317DB32005A689E /* NSObject+MJProperty.h */,
				2D2DBA0F2317DB32005A689E /* NSObject+MJProperty.m */,
				2D2DBA142317DB33005A689E /* NSString+MJExtension.h */,
				2D2DBA0B2317DB32005A689E /* NSString+MJExtension.m */,
				2D2DB9F62317DA64005A689E /* MJExtension.h */,
				2D2DB9F72317DA64005A689E /* Info.plist */,
				012254B42BA1481B007BD496 /* PrivacyInfo.xcprivacy */,
			);
			path = MJExtension;
			sourceTree = "<group>";
		};
		2D2DBA562317DBB9005A689E /* MJExtensionTests */ = {
			isa = PBXGroup;
			children = (
				0179886A24EFA460007F7FBC /* MJExtensionTests-Bridging-Header.h */,
				0179886924EFA422007F7FBC /* SwiftModel */,
				0107507526E88DA600AAEA10 /* CoreDataModel */,
				2D2DBA5F2317DBDF005A689E /* Model */,
				2D2DBA572317DBB9005A689E /* MJExtensionTests.m */,
				0179886F24EFA58B007F7FBC /* SwiftModelTests.swift */,
				01052EAC25F872D00049EC6F /* MultiThreadTests.swift */,
				0107507D26E890C100AAEA10 /* CoreDataTests.swift */,
				2D2DBA592317DBB9005A689E /* Info.plist */,
				2D2DBA872317DCCF005A689E /* PrefixHeader.pch */,
			);
			path = MJExtensionTests;
			sourceTree = "<group>";
		};
		2D2DBA5F2317DBDF005A689E /* Model */ = {
			isa = PBXGroup;
			children = (
				2D2DBA632317DBDF005A689E /* MJAd.h */,
				2D2DBA732317DBDF005A689E /* MJAd.m */,
				2D2DBA742317DBDF005A689E /* MJBag.h */,
				2D2DBA6A2317DBDF005A689E /* MJBag.m */,
				2D2DBA612317DBDF005A689E /* MJBaseObject.h */,
				2D2DBA6C2317DBDF005A689E /* MJBaseObject.m */,
				2D2DBA762317DBDF005A689E /* MJBook.h */,
				2D2DBA692317DBDF005A689E /* MJBook.m */,
				2D2DBA702317DBDF005A689E /* MJBox.h */,
				2D2DBA652317DBDF005A689E /* MJBox.m */,
				01F28185248F64ED008775BA /* MJCat.h */,
				01F28186248F64ED008775BA /* MJCat.m */,
				2D2DBA6D2317DBDF005A689E /* MJDog.h */,
				2D2DBA602317DBDF005A689E /* MJDog.m */,
				2D2DBA6B2317DBDF005A689E /* MJExtensionConfig.h */,
				2D2DBA752317DBDF005A689E /* MJExtensionConfig.m */,
				2D2DBA682317DBDF005A689E /* MJPerson.h */,
				2D2DBA772317DBDF005A689E /* MJPerson.m */,
				2D2DBA622317DBDF005A689E /* MJStatus.h */,
				2D2DBA722317DBDF005A689E /* MJStatus.m */,
				2D2DBA642317DBDF005A689E /* MJStatusResult.h */,
				2D2DBA712317DBDF005A689E /* MJStatusResult.m */,
				2D2DBA662317DBDF005A689E /* MJStudent.m */,
				2D2DBA6F2317DBDF005A689E /* MJStudent.h */,
				2D2DBA672317DBDF005A689E /* MJUser.h */,
				2D2DBA6E2317DBDF005A689E /* MJUser.m */,
				0130EE7E233C56D8008D2386 /* MJFrenchUser.h */,
				0130EE7F233C56D8008D2386 /* MJFrenchUser.m */,
			);
			path = Model;
			sourceTree = "<group>";
		};
/* End PBXGroup section */

/* Begin PBXHeadersBuildPhase section */
		2D2DB9EF2317DA64005A689E /* Headers */ = {
			isa = PBXHeadersBuildPhase;
			buildActionMask = 2147483647;
			files = (
				2D2DBA272317DB33005A689E /* NSObject+MJKeyValue.h in Headers */,
				2D2DBA222317DB33005A689E /* MJFoundation.h in Headers */,
				2D2DBA1B2317DB33005A689E /* NSObject+MJClass.h in Headers */,
				2D2DBA282317DB33005A689E /* NSString+MJExtension.h in Headers */,
				2D2DBA212317DB33005A689E /* NSObject+MJCoding.h in Headers */,
				2D2DBA1C2317DB33005A689E /* MJExtensionConst.h in Headers */,
				2D2DBA262317DB33005A689E /* MJProperty.h in Headers */,
				2D2DBA1E2317DB33005A689E /* MJPropertyType.h in Headers */,
				2D2DBA252317DB33005A689E /* MJPropertyKey.h in Headers */,
				2D2DBA152317DB33005A689E /* NSObject+MJProperty.h in Headers */,
				2D2DB9F82317DA64005A689E /* MJExtension.h in Headers */,
			);
			runOnlyForDeploymentPostprocessing = 0;
		};
/* End PBXHeadersBuildPhase section */

/* Begin PBXNativeTarget section */
		0107508B26E8C59F00AAEA10 /* MJExtensionDemo */ = {
			isa = PBXNativeTarget;
			buildConfigurationList = 0107509D26E8C5A300AAEA10 /* Build configuration list for PBXNativeTarget "MJExtensionDemo" */;
			buildPhases = (
				0107508826E8C59F00AAEA10 /* Sources */,
				0107508926E8C59F00AAEA10 /* Frameworks */,
				0107508A26E8C59F00AAEA10 /* Resources */,
			);
			buildRules = (
			);
			dependencies = (
			);
			name = MJExtensionDemo;
			productName = MJExtensionDemo;
			productReference = 0107508C26E8C59F00AAEA10 /* MJExtensionDemo.app */;
			productType = "com.apple.product-type.application";
		};
		2D2DB9F32317DA64005A689E /* MJExtension */ = {
			isa = PBXNativeTarget;
			buildConfigurationList = 2D2DB9FD2317DA64005A689E /* Build configuration list for PBXNativeTarget "MJExtension" */;
			buildPhases = (
				2D2DB9EF2317DA64005A689E /* Headers */,
				2D2DB9F02317DA64005A689E /* Sources */,
				2D2DB9F12317DA64005A689E /* Frameworks */,
				2D2DB9F22317DA64005A689E /* Resources */,
			);
			buildRules = (
			);
			dependencies = (
			);
			name = MJExtension;
			productName = MJExtension;
			productReference = 2D2DB9F42317DA64005A689E /* MJExtension.framework */;
			productType = "com.apple.product-type.framework";
		};
		2D2DBA542317DBB9005A689E /* MJExtensionTests */ = {
			isa = PBXNativeTarget;
			buildConfigurationList = 2D2DBA5C2317DBB9005A689E /* Build configuration list for PBXNativeTarget "MJExtensionTests" */;
			buildPhases = (
				2D2DBA512317DBB9005A689E /* Sources */,
				2D2DBA522317DBB9005A689E /* Frameworks */,
				2D2DBA532317DBB9005A689E /* Resources */,
			);
			buildRules = (
			);
			dependencies = (
				010750A126E8C5AC00AAEA10 /* PBXTargetDependency */,
			);
			name = MJExtensionTests;
			productName = MJExtensionTests;
			productReference = 2D2DBA552317DBB9005A689E /* MJExtensionTests.xctest */;
			productType = "com.apple.product-type.bundle.unit-test";
		};
/* End PBXNativeTarget section */

/* Begin PBXProject section */
		2D2DB9C02317DA07005A689E /* Project object */ = {
			isa = PBXProject;
			attributes = {
				LastSwiftUpdateCheck = 1300;
				LastUpgradeCheck = 1030;
				ORGANIZATIONNAME = "MJ Lee";
				TargetAttributes = {
					0107508B26E8C59F00AAEA10 = {
						CreatedOnToolsVersion = 13.0;
					};
					2D2DB9F32317DA64005A689E = {
						CreatedOnToolsVersion = 10.3;
					};
					2D2DBA542317DBB9005A689E = {
						CreatedOnToolsVersion = 10.3;
						LastSwiftMigration = 1200;
						TestTargetID = 0107508B26E8C59F00AAEA10;
					};
				};
			};
			buildConfigurationList = 2D2DB9C32317DA07005A689E /* Build configuration list for PBXProject "MJExtension" */;
			compatibilityVersion = "Xcode 9.3";
			developmentRegion = en;
			hasScannedForEncodings = 0;
			knownRegions = (
				en,
				Base,
			);
			mainGroup = 2D2DB9BF2317DA07005A689E;
			productRefGroup = 2D2DB9C92317DA07005A689E /* Products */;
			projectDirPath = "";
			projectRoot = "";
			targets = (
				2D2DB9F32317DA64005A689E /* MJExtension */,
				2D2DBA542317DBB9005A689E /* MJExtensionTests */,
				0107508B26E8C59F00AAEA10 /* MJExtensionDemo */,
			);
		};
/* End PBXProject section */

/* Begin PBXResourcesBuildPhase section */
		0107508A26E8C59F00AAEA10 /* Resources */ = {
			isa = PBXResourcesBuildPhase;
			buildActionMask = 2147483647;
			files = (
				0107509B26E8C5A300AAEA10 /* LaunchScreen.storyboard in Resources */,
				0107509826E8C5A300AAEA10 /* Assets.xcassets in Resources */,
				0107509626E8C5A000AAEA10 /* Main.storyboard in Resources */,
			);
			runOnlyForDeploymentPostprocessing = 0;
		};
		2D2DB9F22317DA64005A689E /* Resources */ = {
			isa = PBXResourcesBuildPhase;
			buildActionMask = 2147483647;
			files = (
				012254B52BA1481B007BD496 /* PrivacyInfo.xcprivacy in Resources */,
			);
			runOnlyForDeploymentPostprocessing = 0;
		};
		2D2DBA532317DBB9005A689E /* Resources */ = {
			isa = PBXResourcesBuildPhase;
			buildActionMask = 2147483647;
			files = (
			);
			runOnlyForDeploymentPostprocessing = 0;
		};
/* End PBXResourcesBuildPhase section */

/* Begin PBXSourcesBuildPhase section */
		0107508826E8C59F00AAEA10 /* Sources */ = {
			isa = PBXSourcesBuildPhase;
			buildActionMask = 2147483647;
			files = (
				0121B2CE26E9B277008EE973 /* MJCoreDataTestModel.xcdatamodeld in Sources */,
				0107508F26E8C59F00AAEA10 /* AppDelegate.swift in Sources */,
			);
			runOnlyForDeploymentPostprocessing = 0;
		};
		2D2DB9F02317DA64005A689E /* Sources */ = {
			isa = PBXSourcesBuildPhase;
			buildActionMask = 2147483647;
			files = (
				2D2DBA172317DB33005A689E /* NSObject+MJCoding.m in Sources */,
				2D2DBA232317DB33005A689E /* NSObject+MJProperty.m in Sources */,
				2D2DBA182317DB33005A689E /* MJProperty.m in Sources */,
				2D2DBA162317DB33005A689E /* MJExtensionConst.m in Sources */,
				2D2DBA1F2317DB33005A689E /* NSString+MJExtension.m in Sources */,
				2D2DBA242317DB33005A689E /* MJPropertyType.m in Sources */,
				2D2DBA1D2317DB33005A689E /* NSObject+MJClass.m in Sources */,
				2D2DBA1A2317DB33005A689E /* NSObject+MJKeyValue.m in Sources */,
				2D2DBA192317DB33005A689E /* MJPropertyKey.m in Sources */,
				2D2DBA202317DB33005A689E /* MJFoundation.m in Sources */,
			);
			runOnlyForDeploymentPostprocessing = 0;
		};
		2D2DBA512317DBB9005A689E /* Sources */ = {
			isa = PBXSourcesBuildPhase;
			buildActionMask = 2147483647;
			files = (
				2D2DBA812317DBE0005A689E /* MJStatusResult.m in Sources */,
				2D2DBA7C2317DBE0005A689E /* MJStudent.m in Sources */,
				2D2DBA822317DBE0005A689E /* MJStatus.m in Sources */,
				2D2DBA832317DBE0005A689E /* MJAd.m in Sources */,
				0107507B26E88EAC00AAEA10 /* MJCoreDataTester+CoreDataClass.swift in Sources */,
				2D2DBA802317DBE0005A689E /* MJUser.m in Sources */,
				0179887024EFA58B007F7FBC /* SwiftModelTests.swift in Sources */,
				0130EE80233C56D8008D2386 /* MJFrenchUser.m in Sources */,
				01F28187248F64ED008775BA /* MJCat.m in Sources */,
				0179886C24EFA460007F7FBC /* MJTester.swift in Sources */,
				2D2DBA852317DBE0005A689E /* MJPerson.m in Sources */,
				0107507C26E88EAC00AAEA10 /* MJCoreDataTester+CoreDataProperties.swift in Sources */,
				2D2DBA7B2317DBE0005A689E /* MJBox.m in Sources */,
				2D2DBA582317DBB9005A689E /* MJExtensionTests.m in Sources */,
				2D2DBA7E2317DBE0005A689E /* MJBag.m in Sources */,
				2D2DBA842317DBE0005A689E /* MJExtensionConfig.m in Sources */,
				2D2DBA7F2317DBE0005A689E /* MJBaseObject.m in Sources */,
				2D2DBA7D2317DBE0005A689E /* MJBook.m in Sources */,
				0107507E26E890C100AAEA10 /* CoreDataTests.swift in Sources */,
				0107507826E88DD400AAEA10 /* MJCoreDataTestModel.xcdatamodeld in Sources */,
				2D2DBA7A2317DBE0005A689E /* MJDog.m in Sources */,
				01052EAD25F872D00049EC6F /* MultiThreadTests.swift in Sources */,
			);
			runOnlyForDeploymentPostprocessing = 0;
		};
/* End PBXSourcesBuildPhase section */

/* Begin PBXTargetDependency section */
		010750A126E8C5AC00AAEA10 /* PBXTargetDependency */ = {
			isa = PBXTargetDependency;
			target = 0107508B26E8C59F00AAEA10 /* MJExtensionDemo */;
			targetProxy = 010750A026E8C5AC00AAEA10 /* PBXContainerItemProxy */;
		};
/* End PBXTargetDependency section */

/* Begin PBXVariantGroup section */
		0107509426E8C5A000AAEA10 /* Main.storyboard */ = {
			isa = PBXVariantGroup;
			children = (
				0107509526E8C5A000AAEA10 /* Base */,
			);
			name = Main.storyboard;
			sourceTree = "<group>";
		};
		0107509926E8C5A300AAEA10 /* LaunchScreen.storyboard */ = {
			isa = PBXVariantGroup;
			children = (
				0107509A26E8C5A300AAEA10 /* Base */,
			);
			name = LaunchScreen.storyboard;
			sourceTree = "<group>";
		};
/* End PBXVariantGroup section */

/* Begin XCBuildConfiguration section */
		0107509E26E8C5A300AAEA10 /* Debug */ = {
			isa = XCBuildConfiguration;
			buildSettings = {
				ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
				ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
				CLANG_CXX_LANGUAGE_STANDARD = "gnu++17";
				CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
				CODE_SIGN_ENTITLEMENTS = MJExtensionDemo/MJExtensionDemo.entitlements;
				CODE_SIGN_STYLE = Automatic;
				CURRENT_PROJECT_VERSION = 1;
				DEVELOPMENT_TEAM = "";
				GENERATE_INFOPLIST_FILE = YES;
				INFOPLIST_FILE = MJExtensionDemo/Info.plist;
				INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
				INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen;
				INFOPLIST_KEY_UIMainStoryboardFile = Main;
				INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
				INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
				LD_RUNPATH_SEARCH_PATHS = (
					"$(inherited)",
					"@executable_path/Frameworks",
				);
				MARKETING_VERSION = 1.0;
				PRODUCT_BUNDLE_IDENTIFIER = com.mj.MJExtension.MJExtensionDemo;
				PRODUCT_NAME = "$(TARGET_NAME)";
				SUPPORTS_MACCATALYST = NO;
				SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
				SWIFT_EMIT_LOC_STRINGS = YES;
				SWIFT_OPTIMIZATION_LEVEL = "-Onone";
				SWIFT_VERSION = 5.0;
				TARGETED_DEVICE_FAMILY = "1,2";
			};
			name = Debug;
		};
		0107509F26E8C5A300AAEA10 /* Release */ = {
			isa = XCBuildConfiguration;
			buildSettings = {
				ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
				ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
				CLANG_CXX_LANGUAGE_STANDARD = "gnu++17";
				CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
				CODE_SIGN_ENTITLEMENTS = MJExtensionDemo/MJExtensionDemo.entitlements;
				CODE_SIGN_STYLE = Automatic;
				CURRENT_PROJECT_VERSION = 1;
				DEVELOPMENT_TEAM = "";
				GENERATE_INFOPLIST_FILE = YES;
				INFOPLIST_FILE = MJExtensionDemo/Info.plist;
				INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
				INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen;
				INFOPLIST_KEY_UIMainStoryboardFile = Main;
				INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
				INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
				LD_RUNPATH_SEARCH_PATHS = (
					"$(inherited)",
					"@executable_path/Frameworks",
				);
				MARKETING_VERSION = 1.0;
				PRODUCT_BUNDLE_IDENTIFIER = com.mj.MJExtension.MJExtensionDemo;
				PRODUCT_NAME = "$(TARGET_NAME)";
				SUPPORTS_MACCATALYST = NO;
				SWIFT_COMPILATION_MODE = wholemodule;
				SWIFT_EMIT_LOC_STRINGS = YES;
				SWIFT_OPTIMIZATION_LEVEL = "-O";
				SWIFT_VERSION = 5.0;
				TARGETED_DEVICE_FAMILY = "1,2";
			};
			name = Release;
		};
		2D2DB9E72317DA08005A689E /* Debug */ = {
			isa = XCBuildConfiguration;
			buildSettings = {
				ALWAYS_SEARCH_USER_PATHS = NO;
				CLANG_ANALYZER_NONNULL = YES;
				CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
				CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
				CLANG_CXX_LIBRARY = "libc++";
				CLANG_ENABLE_MODULES = YES;
				CLANG_ENABLE_OBJC_ARC = YES;
				CLANG_ENABLE_OBJC_WEAK = YES;
				CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
				CLANG_WARN_BOOL_CONVERSION = YES;
				CLANG_WARN_COMMA = YES;
				CLANG_WARN_CONSTANT_CONVERSION = YES;
				CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
				CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
				CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
				CLANG_WARN_EMPTY_BODY = YES;
				CLANG_WARN_ENUM_CONVERSION = YES;
				CLANG_WARN_INFINITE_RECURSION = YES;
				CLANG_WARN_INT_CONVERSION = YES;
				CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
				CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
				CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
				CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
				CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
				CLANG_WARN_STRICT_PROTOTYPES = YES;
				CLANG_WARN_SUSPICIOUS_MOVE = YES;
				CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
				CLANG_WARN_UNREACHABLE_CODE = YES;
				CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
				CODE_SIGN_IDENTITY = "iPhone Developer";
				COPY_PHASE_STRIP = NO;
				DEBUG_INFORMATION_FORMAT = dwarf;
				ENABLE_STRICT_OBJC_MSGSEND = YES;
				ENABLE_TESTABILITY = YES;
				GCC_C_LANGUAGE_STANDARD = gnu11;
				GCC_DYNAMIC_NO_PIC = NO;
				GCC_NO_COMMON_BLOCKS = YES;
				GCC_OPTIMIZATION_LEVEL = 0;
				GCC_PREPROCESSOR_DEFINITIONS = (
					"DEBUG=1",
					"$(inherited)",
				);
				GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
				GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
				GCC_WARN_UNDECLARED_SELECTOR = YES;
				GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
				GCC_WARN_UNUSED_FUNCTION = YES;
				GCC_WARN_UNUSED_VARIABLE = YES;
				IPHONEOS_DEPLOYMENT_TARGET = 12.4;
				MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
				MTL_FAST_MATH = YES;
				ONLY_ACTIVE_ARCH = YES;
				SDKROOT = iphoneos;
				SWIFT_VERSION = 5.0;
			};
			name = Debug;
		};
		2D2DB9E82317DA08005A689E /* Release */ = {
			isa = XCBuildConfiguration;
			buildSettings = {
				ALWAYS_SEARCH_USER_PATHS = NO;
				CLANG_ANALYZER_NONNULL = YES;
				CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
				CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
				CLANG_CXX_LIBRARY = "libc++";
				CLANG_ENABLE_MODULES = YES;
				CLANG_ENABLE_OBJC_ARC = YES;
				CLANG_ENABLE_OBJC_WEAK = YES;
				CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
				CLANG_WARN_BOOL_CONVERSION = YES;
				CLANG_WARN_COMMA = YES;
				CLANG_WARN_CONSTANT_CONVERSION = YES;
				CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
				CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
				CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
				CLANG_WARN_EMPTY_BODY = YES;
				CLANG_WARN_ENUM_CONVERSION = YES;
				CLANG_WARN_INFINITE_RECURSION = YES;
				CLANG_WARN_INT_CONVERSION = YES;
				CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
				CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
				CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
				CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
				CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
				CLANG_WARN_STRICT_PROTOTYPES = YES;
				CLANG_WARN_SUSPICIOUS_MOVE = YES;
				CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
				CLANG_WARN_UNREACHABLE_CODE = YES;
				CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
				CODE_SIGN_IDENTITY = "iPhone Developer";
				COPY_PHASE_STRIP = NO;
				DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
				ENABLE_NS_ASSERTIONS = NO;
				ENABLE_STRICT_OBJC_MSGSEND = YES;
				GCC_C_LANGUAGE_STANDARD = gnu11;
				GCC_NO_COMMON_BLOCKS = YES;
				GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
				GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
				GCC_WARN_UNDECLARED_SELECTOR = YES;
				GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
				GCC_WARN_UNUSED_FUNCTION = YES;
				GCC_WARN_UNUSED_VARIABLE = YES;
				IPHONEOS_DEPLOYMENT_TARGET = 12.4;
				MTL_ENABLE_DEBUG_INFO = NO;
				MTL_FAST_MATH = YES;
				SDKROOT = iphoneos;
				SWIFT_VERSION = 5.0;
				VALIDATE_PRODUCT = YES;
			};
			name = Release;
		};
		2D2DB9FE2317DA64005A689E /* Debug */ = {
			isa = XCBuildConfiguration;
			buildSettings = {
				CODE_SIGN_IDENTITY = "";
				CODE_SIGN_STYLE = Automatic;
				CURRENT_PROJECT_VERSION = 1;
				DEFINES_MODULE = YES;
				DYLIB_COMPATIBILITY_VERSION = 1;
				DYLIB_CURRENT_VERSION = 1;
				DYLIB_INSTALL_NAME_BASE = "@rpath";
				INFOPLIST_FILE = MJExtension/Info.plist;
				INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
				IPHONEOS_DEPLOYMENT_TARGET = 10.0;
				LD_RUNPATH_SEARCH_PATHS = (
					"$(inherited)",
					"@executable_path/Frameworks",
					"@loader_path/Frameworks",
				);
				PRODUCT_BUNDLE_IDENTIFIER = com.mj.MJExtension;
				PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
				SKIP_INSTALL = YES;
				SUPPORTS_MACCATALYST = NO;
				TARGETED_DEVICE_FAMILY = "1,2";
				VERSIONING_SYSTEM = "apple-generic";
				VERSION_INFO_PREFIX = "";
			};
			name = Debug;
		};
		2D2DB9FF2317DA64005A689E /* Release */ = {
			isa = XCBuildConfiguration;
			buildSettings = {
				CODE_SIGN_IDENTITY = "";
				CODE_SIGN_STYLE = Automatic;
				CURRENT_PROJECT_VERSION = 1;
				DEFINES_MODULE = YES;
				DYLIB_COMPATIBILITY_VERSION = 1;
				DYLIB_CURRENT_VERSION = 1;
				DYLIB_INSTALL_NAME_BASE = "@rpath";
				INFOPLIST_FILE = MJExtension/Info.plist;
				INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
				IPHONEOS_DEPLOYMENT_TARGET = 10.0;
				LD_RUNPATH_SEARCH_PATHS = (
					"$(inherited)",
					"@executable_path/Frameworks",
					"@loader_path/Frameworks",
				);
				PRODUCT_BUNDLE_IDENTIFIER = com.mj.MJExtension;
				PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
				SKIP_INSTALL = YES;
				SUPPORTS_MACCATALYST = NO;
				TARGETED_DEVICE_FAMILY = "1,2";
				VERSIONING_SYSTEM = "apple-generic";
				VERSION_INFO_PREFIX = "";
			};
			name = Release;
		};
		2D2DBA5D2317DBB9005A689E /* Debug */ = {
			isa = XCBuildConfiguration;
			buildSettings = {
				ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
				CLANG_ENABLE_MODULES = YES;
				CODE_SIGN_STYLE = Automatic;
				DEVELOPMENT_TEAM = "";
				GCC_PRECOMPILE_PREFIX_HEADER = YES;
				GCC_PREFIX_HEADER = MJExtensionTests/PrefixHeader.pch;
				INFOPLIST_FILE = MJExtensionTests/Info.plist;
				LD_RUNPATH_SEARCH_PATHS = (
					"$(inherited)",
					"@executable_path/Frameworks",
					"@loader_path/Frameworks",
				);
				PRODUCT_BUNDLE_IDENTIFIER = com.mj.MJExtensionTests;
				PRODUCT_NAME = "$(TARGET_NAME)";
				SWIFT_OBJC_BRIDGING_HEADER = "MJExtensionTests/MJExtensionTests-Bridging-Header.h";
				SWIFT_OPTIMIZATION_LEVEL = "-Onone";
				TARGETED_DEVICE_FAMILY = "1,2";
				TEST_HOST = "$(BUILT_PRODUCTS_DIR)/MJExtensionDemo.app/MJExtensionDemo";
			};
			name = Debug;
		};
		2D2DBA5E2317DBB9005A689E /* Release */ = {
			isa = XCBuildConfiguration;
			buildSettings = {
				ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
				CLANG_ENABLE_MODULES = YES;
				CODE_SIGN_STYLE = Automatic;
				DEVELOPMENT_TEAM = "";
				GCC_PRECOMPILE_PREFIX_HEADER = YES;
				GCC_PREFIX_HEADER = MJExtensionTests/PrefixHeader.pch;
				INFOPLIST_FILE = MJExtensionTests/Info.plist;
				LD_RUNPATH_SEARCH_PATHS = (
					"$(inherited)",
					"@executable_path/Frameworks",
					"@loader_path/Frameworks",
				);
				PRODUCT_BUNDLE_IDENTIFIER = com.mj.MJExtensionTests;
				PRODUCT_NAME = "$(TARGET_NAME)";
				SWIFT_OBJC_BRIDGING_HEADER = "MJExtensionTests/MJExtensionTests-Bridging-Header.h";
				TARGETED_DEVICE_FAMILY = "1,2";
				TEST_HOST = "$(BUILT_PRODUCTS_DIR)/MJExtensionDemo.app/MJExtensionDemo";
			};
			name = Release;
		};
/* End XCBuildConfiguration section */

/* Begin XCConfigurationList section */
		0107509D26E8C5A300AAEA10 /* Build configuration list for PBXNativeTarget "MJExtensionDemo" */ = {
			isa = XCConfigurationList;
			buildConfigurations = (
				0107509E26E8C5A300AAEA10 /* Debug */,
				0107509F26E8C5A300AAEA10 /* Release */,
			);
			defaultConfigurationIsVisible = 0;
			defaultConfigurationName = Release;
		};
		2D2DB9C32317DA07005A689E /* Build configuration list for PBXProject "MJExtension" */ = {
			isa = XCConfigurationList;
			buildConfigurations = (
				2D2DB9E72317DA08005A689E /* Debug */,
				2D2DB9E82317DA08005A689E /* Release */,
			);
			defaultConfigurationIsVisible = 0;
			defaultConfigurationName = Release;
		};
		2D2DB9FD2317DA64005A689E /* Build configuration list for PBXNativeTarget "MJExtension" */ = {
			isa = XCConfigurationList;
			buildConfigurations = (
				2D2DB9FE2317DA64005A689E /* Debug */,
				2D2DB9FF2317DA64005A689E /* Release */,
			);
			defaultConfigurationIsVisible = 0;
			defaultConfigurationName = Release;
		};
		2D2DBA5C2317DBB9005A689E /* Build configuration list for PBXNativeTarget "MJExtensionTests" */ = {
			isa = XCConfigurationList;
			buildConfigurations = (
				2D2DBA5D2317DBB9005A689E /* Debug */,
				2D2DBA5E2317DBB9005A689E /* Release */,
			);
			defaultConfigurationIsVisible = 0;
			defaultConfigurationName = Release;
		};
/* End XCConfigurationList section */

/* Begin XCVersionGroup section */
		0107507626E88DD400AAEA10 /* MJCoreDataTestModel.xcdatamodeld */ = {
			isa = XCVersionGroup;
			children = (
				0107507726E88DD400AAEA10 /* MJCoreDataTestModel.xcdatamodel */,
			);
			currentVersion = 0107507726E88DD400AAEA10 /* MJCoreDataTestModel.xcdatamodel */;
			path = MJCoreDataTestModel.xcdatamodeld;
			sourceTree = "<group>";
			versionGroupType = wrapper.xcdatamodel;
		};
/* End XCVersionGroup section */
	};
	rootObject = 2D2DB9C02317DA07005A689E /* Project object */;
}


================================================
FILE: MJExtension.xcodeproj/xcshareddata/xcschemes/MJExtension.xcscheme
================================================
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
   LastUpgradeVersion = "1030"
   version = "1.3">
   <BuildAction
      parallelizeBuildables = "YES"
      buildImplicitDependencies = "YES">
      <BuildActionEntries>
         <BuildActionEntry
            buildForTesting = "YES"
            buildForRunning = "YES"
            buildForProfiling = "YES"
            buildForArchiving = "YES"
            buildForAnalyzing = "YES">
            <BuildableReference
               BuildableIdentifier = "primary"
               BlueprintIdentifier = "2D2DB9F32317DA64005A689E"
               BuildableName = "MJExtension.framework"
               BlueprintName = "MJExtension"
               ReferencedContainer = "container:MJExtension.xcodeproj">
            </BuildableReference>
         </BuildActionEntry>
      </BuildActionEntries>
   </BuildAction>
   <TestAction
      buildConfiguration = "Debug"
      selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
      selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
      shouldUseLaunchSchemeArgsEnv = "YES">
      <Testables>
         <TestableReference
            skipped = "NO">
            <BuildableReference
               BuildableIdentifier = "primary"
               BlueprintIdentifier = "2D2DBA542317DBB9005A689E"
               BuildableName = "MJExtensionTests.xctest"
               BlueprintName = "MJExtensionTests"
               ReferencedContainer = "container:MJExtension.xcodeproj">
            </BuildableReference>
         </TestableReference>
      </Testables>
   </TestAction>
   <LaunchAction
      buildConfiguration = "Debug"
      selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
      selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
      launchStyle = "0"
      useCustomWorkingDirectory = "NO"
      ignoresPersistentStateOnLaunch = "NO"
      debugDocumentVersioning = "YES"
      debugServiceExtension = "internal"
      allowLocationSimulation = "YES">
      <MacroExpansion>
         <BuildableReference
            BuildableIdentifier = "primary"
            BlueprintIdentifier = "2D2DB9F32317DA64005A689E"
            BuildableName = "MJExtension.framework"
            BlueprintName = "MJExtension"
            ReferencedContainer = "container:MJExtension.xcodeproj">
         </BuildableReference>
      </MacroExpansion>
   </LaunchAction>
   <ProfileAction
      buildConfiguration = "Release"
      shouldUseLaunchSchemeArgsEnv = "YES"
      savedToolIdentifier = ""
      useCustomWorkingDirectory = "NO"
      debugDocumentVersioning = "YES">
      <MacroExpansion>
         <BuildableReference
            BuildableIdentifier = "primary"
            BlueprintIdentifier = "2D2DB9F32317DA64005A689E"
            BuildableName = "MJExtension.framework"
            BlueprintName = "MJExtension"
            ReferencedContainer = "container:MJExtension.xcodeproj">
         </BuildableReference>
      </MacroExpansion>
   </ProfileAction>
   <AnalyzeAction
      buildConfiguration = "Debug">
   </AnalyzeAction>
   <ArchiveAction
      buildConfiguration = "Release"
      revealArchiveInOrganizer = "YES">
   </ArchiveAction>
</Scheme>


================================================
FILE: MJExtension.xcodeproj/xcshareddata/xcschemes/MJExtensionDemo.xcscheme
================================================
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
   LastUpgradeVersion = "1300"
   version = "1.3">
   <BuildAction
      parallelizeBuildables = "YES"
      buildImplicitDependencies = "YES">
      <BuildActionEntries>
         <BuildActionEntry
            buildForTesting = "YES"
            buildForRunning = "YES"
            buildForProfiling = "YES"
            buildForArchiving = "YES"
            buildForAnalyzing = "YES">
            <BuildableReference
               BuildableIdentifier = "primary"
               BlueprintIdentifier = "0107508B26E8C59F00AAEA10"
               BuildableName = "MJExtensionDemo.app"
               BlueprintName = "MJExtensionDemo"
               ReferencedContainer = "container:MJExtension.xcodeproj">
            </BuildableReference>
         </BuildActionEntry>
      </BuildActionEntries>
   </BuildAction>
   <TestAction
      buildConfiguration = "Debug"
      selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
      selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
      shouldUseLaunchSchemeArgsEnv = "YES">
      <Testables>
         <TestableReference
            skipped = "NO">
            <BuildableReference
               BuildableIdentifier = "primary"
               BlueprintIdentifier = "2D2DBA542317DBB9005A689E"
               BuildableName = "MJExtensionTests.xctest"
               BlueprintName = "MJExtensionTests"
               ReferencedContainer = "container:MJExtension.xcodeproj">
            </BuildableReference>
         </TestableReference>
      </Testables>
   </TestAction>
   <LaunchAction
      buildConfiguration = "Debug"
      selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
      selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
      launchStyle = "0"
      useCustomWorkingDirectory = "NO"
      ignoresPersistentStateOnLaunch = "NO"
      debugDocumentVersioning = "YES"
      debugServiceExtension = "internal"
      allowLocationSimulation = "YES">
      <BuildableProductRunnable
         runnableDebuggingMode = "0">
         <BuildableReference
            BuildableIdentifier = "primary"
            BlueprintIdentifier = "0107508B26E8C59F00AAEA10"
            BuildableName = "MJExtensionDemo.app"
            BlueprintName = "MJExtensionDemo"
            ReferencedContainer = "container:MJExtension.xcodeproj">
         </BuildableReference>
      </BuildableProductRunnable>
      <CommandLineArguments>
         <CommandLineArgument
            argument = "-com.apple.CoreData.SQLDebug 4"
            isEnabled = "YES">
         </CommandLineArgument>
         <CommandLineArgument
            argument = "-com.apple.CoreData.ConcurrencyDebug 1"
            isEnabled = "YES">
         </CommandLineArgument>
      </CommandLineArguments>
      <EnvironmentVariables>
         <EnvironmentVariable
            key = "SQLITE_AUTO_TRACE"
            value = "1"
            isEnabled = "NO">
         </EnvironmentVariable>
         <EnvironmentVariable
            key = "SQLITE_ENABLE_FILE_ASSERSIONS"
            value = "1"
            isEnabled = "YES">
         </EnvironmentVariable>
         <EnvironmentVariable
            key = "SQLITE_ENABLE_THREAD_ASSERTIONS"
            value = "1"
            isEnabled = "YES">
         </EnvironmentVariable>
      </EnvironmentVariables>
   </LaunchAction>
   <ProfileAction
      buildConfiguration = "Release"
      shouldUseLaunchSchemeArgsEnv = "YES"
      savedToolIdentifier = ""
      useCustomWorkingDirectory = "NO"
      debugDocumentVersioning = "YES">
      <BuildableProductRunnable
         runnableDebuggingMode = "0">
         <BuildableReference
            BuildableIdentifier = "primary"
            BlueprintIdentifier = "0107508B26E8C59F00AAEA10"
            BuildableName = "MJExtensionDemo.app"
            BlueprintName = "MJExtensionDemo"
            ReferencedContainer = "container:MJExtension.xcodeproj">
         </BuildableReference>
      </BuildableProductRunnable>
   </ProfileAction>
   <AnalyzeAction
      buildConfiguration = "Debug">
   </AnalyzeAction>
   <ArchiveAction
      buildConfiguration = "Release"
      revealArchiveInOrganizer = "YES">
   </ArchiveAction>
</Scheme>


================================================
FILE: MJExtensionDemo/AppDelegate.swift
================================================
//
//  AppDelegate.swift
//  MJExtensionDemo
//
//  Created by Frank on 2021/9/8.
//  Copyright © 2021 MJ Lee. All rights reserved.
//

import UIKit

@main
class AppDelegate: UIResponder, UIApplicationDelegate {
    
    var window: UIWindow?

    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        // Override point for customization after application launch.
        return true
    }

}


class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view.
    }


}


================================================
FILE: MJExtensionDemo/Assets.xcassets/AccentColor.colorset/Contents.json
================================================
{
  "colors" : [
    {
      "idiom" : "universal"
    }
  ],
  "info" : {
    "author" : "xcode",
    "version" : 1
  }
}


================================================
FILE: MJExtensionDemo/Assets.xcassets/AppIcon.appiconset/Contents.json
================================================
{
  "images" : [
    {
      "idiom" : "iphone",
      "size" : "20x20",
      "scale" : "2x"
    },
    {
      "idiom" : "iphone",
      "size" : "20x20",
      "scale" : "3x"
    },
    {
      "idiom" : "iphone",
      "size" : "29x29",
      "scale" : "2x"
    },
    {
      "idiom" : "iphone",
      "size" : "29x29",
      "scale" : "3x"
    },
    {
      "idiom" : "iphone",
      "size" : "40x40",
      "scale" : "2x"
    },
    {
      "idiom" : "iphone",
      "size" : "40x40",
      "scale" : "3x"
    },
    {
      "idiom" : "iphone",
      "size" : "60x60",
      "scale" : "2x"
    },
    {
      "idiom" : "iphone",
      "size" : "60x60",
      "scale" : "3x"
    },
    {
      "idiom" : "ipad",
      "size" : "20x20",
      "scale" : "1x"
    },
    {
      "idiom" : "ipad",
      "size" : "20x20",
      "scale" : "2x"
    },
    {
      "idiom" : "ipad",
      "size" : "29x29",
      "scale" : "1x"
    },
    {
      "idiom" : "ipad",
      "size" : "29x29",
      "scale" : "2x"
    },
    {
      "idiom" : "ipad",
      "size" : "40x40",
      "scale" : "1x"
    },
    {
      "idiom" : "ipad",
      "size" : "40x40",
      "scale" : "2x"
    },
    {
      "idiom" : "ipad",
      "size" : "76x76",
      "scale" : "1x"
    },
    {
      "idiom" : "ipad",
      "size" : "76x76",
      "scale" : "2x"
    },
    {
      "idiom" : "ipad",
      "size" : "83.5x83.5",
      "scale" : "2x"
    },
    {
      "idiom" : "ios-marketing",
      "size" : "1024x1024",
      "scale" : "1x"
    }
  ],
  "info" : {
    "version" : 1,
    "author" : "xcode"
  }
}

================================================
FILE: MJExtensionDemo/Assets.xcassets/Contents.json
================================================
{
  "info" : {
    "version" : 1,
    "author" : "xcode"
  }
}

================================================
FILE: MJExtensionDemo/Base.lproj/LaunchScreen.storyboard
================================================
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="13122.16" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="01J-lp-oVM">
    <dependencies>
        <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="13104.12"/>
        <capability name="Safe area layout guides" minToolsVersion="9.0"/>
        <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
    </dependencies>
    <scenes>
        <!--View Controller-->
        <scene sceneID="EHf-IW-A2E">
            <objects>
                <viewController id="01J-lp-oVM" sceneMemberID="viewController">
                    <view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3">
                        <rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
                        <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
                        <color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
                        <viewLayoutGuide key="safeArea" id="6Tk-OE-BBY"/>
                    </view>
                </viewController>
                <placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/>
            </objects>
            <point key="canvasLocation" x="53" y="375"/>
        </scene>
    </scenes>
</document>


================================================
FILE: MJExtensionDemo/Base.lproj/Main.storyboard
================================================
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="18122" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="BYZ-38-t0r">
    <device id="retina6_1" orientation="portrait" appearance="light"/>
    <dependencies>
        <deployment identifier="iOS"/>
        <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="18093"/>
        <capability name="Safe area layout guides" minToolsVersion="9.0"/>
        <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
    </dependencies>
    <scenes>
        <!--View Controller-->
        <scene sceneID="tne-QT-ifu">
            <objects>
                <viewController id="BYZ-38-t0r" customClass="ViewController" customModule="MJExtensionDemo" customModuleProvider="target" sceneMemberID="viewController">
                    <view key="view" contentMode="scaleToFill" id="8bC-Xf-vdC">
                        <rect key="frame" x="0.0" y="0.0" width="414" height="896"/>
                        <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
                        <subviews>
                            <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Find examples in UnitTest files." textAlignment="center" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Trl-sJ-gBw">
                                <rect key="frame" x="36.5" y="373" width="341" height="160"/>
                                <constraints>
                                    <constraint firstAttribute="height" constant="160" id="Lql-eA-d6R"/>
                                    <constraint firstAttribute="width" constant="341" id="tRA-XS-XJl"/>
                                </constraints>
                                <fontDescription key="fontDescription" type="system" pointSize="20"/>
                                <nil key="textColor"/>
                                <nil key="highlightedColor"/>
                            </label>
                        </subviews>
                        <viewLayoutGuide key="safeArea" id="6Tk-OE-BBY"/>
                        <color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
                        <constraints>
                            <constraint firstItem="Trl-sJ-gBw" firstAttribute="centerX" secondItem="6Tk-OE-BBY" secondAttribute="centerX" id="B4p-lP-hbx"/>
                            <constraint firstItem="Trl-sJ-gBw" firstAttribute="centerY" secondItem="6Tk-OE-BBY" secondAttribute="centerY" id="SPS-KU-5BS"/>
                        </constraints>
                    </view>
                </viewController>
                <placeholder placeholderIdentifier="IBFirstResponder" id="dkx-z0-nzr" sceneMemberID="firstResponder"/>
            </objects>
            <point key="canvasLocation" x="137.68115942028987" y="137.94642857142856"/>
        </scene>
    </scenes>
</document>


================================================
FILE: MJExtensionDemo/Info.plist
================================================
<?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>UILaunchStoryboardName</key>
	<string>LaunchScreen</string>
	<key>UIMainStoryboardFile</key>
	<string>Main</string>
	<key>UISupportedInterfaceOrientations</key>
	<array>
		<string>UIInterfaceOrientationPortrait</string>
		<string>UIInterfaceOrientationPortraitUpsideDown</string>
		<string>UIInterfaceOrientationLandscapeLeft</string>
		<string>UIInterfaceOrientationLandscapeRight</string>
	</array>
</dict>
</plist>


================================================
FILE: MJExtensionTests/CoreDataModel/MJCoreDataTestModel.xcdatamodeld/MJCoreDataTestModel.xcdatamodel/contents
================================================
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="19197" systemVersion="20G95" minimumToolsVersion="Automatic" sourceLanguage="Swift" userDefinedModelVersionIdentifier="">
    <entity name="MJCDTester" representedClassName="MJCoreDataTester" syncable="YES">
        <attribute name="age" optional="YES" attributeType="Integer 16" defaultValueString="0" usesScalarValueType="YES"/>
        <attribute name="identifier" optional="YES" attributeType="String"/>
        <attribute name="isJuan" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
        <attribute name="name" optional="YES" attributeType="String"/>
    </entity>
    <elements>
        <element name="MJCDTester" positionX="-63" positionY="-18" width="128" height="89"/>
    </elements>
</model>

================================================
FILE: MJExtensionTests/CoreDataModel/MJCoreDataTester+CoreDataClass.swift
================================================
//
//  MJCoreDataTester+CoreDataClass.swift
//  MJCoreDataTester
//
//  Created by Frank on 2021/9/8.
//  Copyright © 2021 MJ Lee. All rights reserved.
//
//

import Foundation
import CoreData

@objc(MJCoreDataTester)
@objcMembers
public class MJCoreDataTester: NSManagedObject {

}


================================================
FILE: MJExtensionTests/CoreDataModel/MJCoreDataTester+CoreDataProperties.swift
================================================
//
//  MJCoreDataTester+CoreDataProperties.swift
//  MJCoreDataTester
//
//  Created by Frank on 2021/9/8.
//  Copyright © 2021 MJ Lee. All rights reserved.
//
//

import Foundation
import CoreData


extension MJCoreDataTester {

    @nonobjc public class func fetchRequest() -> NSFetchRequest<MJCoreDataTester> {
        return NSFetchRequest<MJCoreDataTester>(entityName: "MJCDTester")
    }

    @NSManaged public var name: String?
    @NSManaged public var age: Int16
    @NSManaged public var identifier: String?
    @NSManaged public var isJuan: Bool
}


================================================
FILE: MJExtensionTests/CoreDataTests.swift
================================================
//
//  CoreDataTests.swift
//  CoreDataTests
//
//  Created by Frank on 2021/9/8.
//  Copyright © 2021 MJ Lee. All rights reserved.
//

import XCTest
import CoreData

class CoreDataTests: XCTestCase {
    var container: NSPersistentContainer!
    var context: NSManagedObjectContext!
    
    override func setUpWithError() throws {
        try super.setUpWithError()
        
        let container = NSPersistentContainer(name: "MJCoreDataTestModel")
        // Test in memory. This is important.
        container.persistentStoreDescriptions.first?.url = URL(fileURLWithPath: "/dev/null")
        container.loadPersistentStores { description, error in
            XCTAssertNil(error)
        }
        self.container = container
        context = container.newBackgroundContext()
    }
    
    struct Values {
        static let testJSONObject: [String : Any] = [
            "isJuan": true,
            "identifier": "7355608",
            "age": 24,
            "name": "Niko"
        ]
        
        static var name: String {
            testJSONObject["name"] as! String
        }
        static var isJuan: Bool {
            testJSONObject["isJuan"] as! Bool
        }
        static var age: Int {
            testJSONObject["age"] as! Int
        }
        static var identifier: String {
            testJSONObject["identifier"] as! String
        }
    }

    func testConversions() {
        json2CoreDataObject()
        
        coreDataObject2JSON()
    }
    
    func json2CoreDataObject() {
        context.performAndWait {
            guard let tester = MJCoreDataTester.mj_object(withKeyValues: Values.testJSONObject, context: context) else {
                XCTAssert(false, "conversion to core data object failed")
                return
            }
            
            XCTAssert(tester.isJuan == Values.isJuan)
            XCTAssert(tester.identifier == Values.identifier)
            XCTAssert(tester.name == Values.name)
            XCTAssert(tester.age == Values.age)
        }
    }
    
    func coreDataObject2JSON() {
        context.performAndWait {
            let coreDataObject =  NSEntityDescription.insertNewObject(forEntityName: MJCoreDataTester.entity().name!, into: context) as! MJCoreDataTester
            coreDataObject.name = Values.name
            coreDataObject.age = Int16(Values.age)
            coreDataObject.isJuan = Values.isJuan
            coreDataObject.identifier = Values.identifier
            
            guard let dict = coreDataObject.mj_keyValues() else {
                XCTAssert(false, "conversion to keyValues failed")
                return
            }
            
            XCTAssert(dict["isJuan"] as! Bool == Values.isJuan)
            XCTAssert(dict["identifier"] as? String == Values.identifier)
            XCTAssert(dict["name"] as? String == Values.name)
            XCTAssert(dict["age"] as! Int == Values.age)
        }
    }

}


================================================
FILE: MJExtensionTests/Info.plist
================================================
<?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>$(DEVELOPMENT_LANGUAGE)</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>BNDL</string>
	<key>CFBundleShortVersionString</key>
	<string>1.0</string>
	<key>CFBundleVersion</key>
	<string>1</string>
</dict>
</plist>


================================================
FILE: MJExtensionTests/MJExtensionTests-Bridging-Header.h
================================================
//
//  Use this file to import your target's public headers that you would like to expose to Swift.
//

#import "MJExtension.h"

#import "MJUser.h"
#import "MJCat.h"


================================================
FILE: MJExtensionTests/MJExtensionTests.m
================================================
//
//  MJExtensionTests.m
//  MJExtensionTests
//
//  Created by Frank on 2019/3/25.
//  Copyright © 2019 MJExtension. All rights reserved.
//

#import <XCTest/XCTest.h>
#import "MJUser.h"
#import "MJAd.h"
#import "MJStatus.h"
#import "MJStudent.h"
#import "MJStatusResult.h"
#import "MJBag.h"
#import "MJDog.h"
#import "MJBook.h"
#import "MJBox.h"
#import <CoreData/CoreData.h>
#import "MJFrenchUser.h"
#import "MJCat.h"
#import <MJExtensionTests-Swift.h>
#import "MJPerson.h"

@interface MJExtensionTests : XCTestCase

@end

@implementation MJExtensionTests

#pragma mark 类型不匹配类型 -> 模型
- (void)testJSON2ModelUnmatched {
    // 1.定义一个字典
    NSDictionary *dict = @{
                           @"name" : @[],
                           @"icon" : @"lufy.png",
                           @"age" : @"2147483647",
                           @"age2": @"4294967295",
                           @"height" : @1.55,
                           @"money" : @"100.7777777",
                           @"sex" : @(SexFemale),
                           @"gay" : @"1",
                           @"speed" : @"120.5",
                           @"identifier" : @"9223372036854775807",
                           @"identifier2" : @"18446744073709551615",
                           @"price" : @"20.3",
                           @"rich" : @"2",
                           @"collect" : @"40个",
                           @"alien": @"yr Joking"
                           };
    
    // 2.将字典转为MJUser模型
    MJUser *user = [MJUser mj_objectWithKeyValues:dict];
    
    // 3.检测
    XCTAssert(!user.name);
    XCTAssert([user.icon isEqual:@"lufy.png"]);
    XCTAssert(user.age == INT_MAX);
    XCTAssert(user.age2 == UINT_MAX);
    XCTAssert([user.height isEqualToNumber:@(1.55)]);
    XCTAssert([user.money compare:[NSDecimalNumber decimalNumberWithString:@"100.7777777"]] == NSOrderedSame);
    XCTAssert(user.sex == SexFemale);
    XCTAssert(user.gay);
    XCTAssert(user.speed == 120);
    XCTAssert(user.identifier == LONG_LONG_MAX);
    XCTAssert(user.identifier2 == ULONG_LONG_MAX);
    XCTAssert(user.price == 20.3);
    XCTAssert(user.rich);
    XCTAssert(user.collect == 40);
    XCTAssert(!user.alien);
}

#pragma mark 简单的字典 -> 模型
- (void)testJSON2Model {
    // 1.定义一个字典
    NSDictionary *dict = @{
                           @"name" : @"Jack",
                           @"icon" : @"lufy.png",
                           @"age" : @"2147483647",
                           @"age2": @"4294967295",
                           @"height" : @1.55,
                           @"money" : @"100.7777777",
                           @"sex" : @(SexFemale),
                           @"gay" : @"1",
                           @"speed" : @"120.5",
                           @"identifier" : @"9223372036854775807",
                           @"identifier2" : @"18446744073709551615",
                           @"price" : @"20.3",
                           @"rich" : @"2",
                           @"collect" : @"40个",
                           @"alien": @"yr Joking"
                           };
    
    // 2.将字典转为MJUser模型
    MJUser *user = [MJUser mj_objectWithKeyValues:dict];
    
    // 3.检测
    XCTAssert([user.name isEqual:@"Jack"]);
    XCTAssert([user.icon isEqual:@"lufy.png"]);
    XCTAssert(user.age == INT_MAX);
    XCTAssert(user.age2 == UINT_MAX);
    XCTAssert([user.height isEqualToNumber:@(1.55)]);
    XCTAssert([user.money compare:[NSDecimalNumber decimalNumberWithString:@"100.7777777"]] == NSOrderedSame);
    XCTAssert(user.sex == SexFemale);
    XCTAssert(user.gay);
    XCTAssert(user.speed == 120);
    XCTAssert(user.identifier == LONG_LONG_MAX);
    XCTAssert(user.identifier2 == ULONG_LONG_MAX);
    XCTAssert(user.price == 20.3);
    XCTAssert(user.rich);
    XCTAssert(user.collect == 40);
    XCTAssert(!user.alien);
}
// 特殊模型, 数字为法语逗号为小数点的分隔符情况
- (void)testJSON2NumberModel {
    // 1.定义一个字典
    NSDictionary *dict = @{
                           @"age" : @"20",
                           @"height" : @1.55,
                           @"money" : @"100,9",
                           @"gay" : @"",
                           @"speed" : @"120,5",
                           @"identifier" : @"3443623624362",
                           @"price" : @"20,3",
                           @"like" : @"20个",
                           @"collect" : @"收藏5",
                           @"rich" : @"hehe",
                           };
    
    // 2.将字典转为MJFrenchUser模型
    MJFrenchUser *user = [MJFrenchUser mj_objectWithKeyValues:dict];
    
    XCTAssert(user.age == 20);
    XCTAssert(user.height.doubleValue == 1.55);
    XCTAssert(user.money.doubleValue == 100.9);
    XCTAssert(user.gay == NO);
    XCTAssert(user.speed == 120);
    XCTAssert(user.identifier == 3443623624362);
    XCTAssert(user.price == 20.3);
    XCTAssert(user.like == 20);
    XCTAssert(user.collect == 0);
    XCTAssert(user.rich == NO);
}

#pragma mark JSON字符串 -> 模型
- (void)testJSONString2Model {
    // 1.定义一个JSON字符串
    NSString *jsonString = @"{\"name\":\"Jack\", \"icon\":\"lufy.png\", \"age\":20, \"height\":333333.7}";
    
    // 2.将JSON字符串转为MJUser模型
    MJUser *user = [MJUser mj_objectWithKeyValues:jsonString];
    
    // 3.检测
    XCTAssert([user.name isEqual:@"Jack"]);
    XCTAssert([user.icon isEqual:@"lufy.png"]);
    XCTAssert(user.age == 20);
    XCTAssert(user.height.doubleValue == 333333.7);
}

#pragma mark 复杂的字典 -> 模型 (模型里面包含了模型)
- (void)testNestedModel {
    // 1.定义一个字典
    NSDictionary *dict = @{
                           @"text" : @"是啊,今天天气确实不错!",
                           
                           @"user" : @{
                                   @"name" : @"Jack",
                                   @"icon" : @"lufy.png"
                                   },
                           
                           @"retweetedStatus" : @{
                                   @"text" : @"今天天气真不错!",
                                   
                                   @"user" : @{
                                           @"name" : @"Rose",
                                           @"icon" : @"nami.png"
                                           }
                                   }
                           };
    
    // 2.将字典转为Status模型
    MJStatus *status = [MJStatus mj_objectWithKeyValues:dict];
    
    // 3.检测status的属性
    NSString *text = status.text;
    NSString *name = status.user.name;
    NSString *icon = status.user.icon;
    XCTAssert([text isEqual:@"是啊,今天天气确实不错!"]);
    XCTAssert([name isEqual:@"Jack"]);
    XCTAssert([icon isEqual:@"lufy.png"]);
    
    // 4.检测status.retweetedStatus的属性
    NSString *text2 = status.retweetedStatus.text;
    NSString *name2 = status.retweetedStatus.user.name;
    NSString *icon2 = status.retweetedStatus.user.icon;
    XCTAssert([text2 isEqual:@"今天天气真不错!"]);
    XCTAssert([name2 isEqual:@"Rose"]);
    XCTAssert([icon2 isEqual:@"nami.png"]);
}

#pragma mark 复杂的字典 -> 模型 (模型的数组属性里面又装着模型)
- (void)testNestedModelArray {
    // 1.定义一个字典
    NSDictionary *dict = @{
                           @"statuses" : @[
                                   @{
                                       @"text" : @"今天天气真不错!",
                                       
                                       @"user" : @{
                                               @"name" : @"Rose",
                                               @"icon" : @"nami.png"
                                               }
                                       },
                                   
                                   @{
                                       @"text" : @"明天去旅游了",
                                       
                                       @"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",
                           @"previousCursor" : @"13476589",
                           @"nextCursor" : @"13476599"
                           };
    
    // 2.将字典转为MJStatusResult模型
    MJStatusResult *result = [MJStatusResult mj_objectWithKeyValues:dict];
    
    // 3.检测MJStatusResult模型的简单属性
    XCTAssert(result.totalNumber.intValue == 2014);
    XCTAssert(result.previousCursor == 13476589);
    XCTAssert(result.nextCursor == 13476599);
    
    // 4.检测statuses数组中的模型属性
    XCTAssert([result.statuses[0].text isEqual:@"今天天气真不错!"]);
    XCTAssert([result.statuses[0].user.name isEqual:@"Rose"]);
    XCTAssert([result.statuses[0].user.icon isEqual:@"nami.png"]);
    
    XCTAssert([result.statuses[1].text isEqual:@"明天去旅游了"]);
    XCTAssert([result.statuses[1].user.name isEqual:@"Jack"]);
    XCTAssert([result.statuses[1].user.icon isEqual:@"lufy.png"]);
    
    // 5.检测ads数组中的模型属性
    XCTAssert([result.ads[0].image isEqual:@"ad01.png"]);
    XCTAssert([result.ads[0].url.absoluteString isEqual:@"http://www.ad01.com"]);
    XCTAssert([result.ads[1].image isEqual:@"ad02.png"]);
    XCTAssert([result.ads[1].url.absoluteString isEqual:@"http://www.ad02.com"]);
}

#pragma mark KeyMapping
// key替换,比如ID和id。多级映射,比如 oldName 和 name.oldName
- (void)testKeyMapping {
    // 1.定义一个字典
    NSDictionary *dict = @{
                           @"id" : @"20",
                           @"desciption" : @"好孩子",
                           @"name" : @{
                                   @"newName" : @"lufy",
                                   @"oldName" : @"kitty",
                                   @"info" : @[
                                           @"test-data",
                                           @{@"nameChangedTime" : @"2013-08-07"}
                                           ]
                                   },
                           @"other" : @{
                                   @"bag" : @{
                                           @"name" : @"小书包",
                                           @"price" : @100.7
                                           }
                                   }
                           };
    
    // 2.将字典转为MJStudent模型
    MJStudent *stu = [MJStudent mj_objectWithKeyValues:dict];
    
    // 3.检测MJStudent模型的属性
    XCTAssert([stu.ID isEqual:@"20"]);
    XCTAssert([stu.desc isEqual:@"好孩子"]);
    XCTAssert([stu.otherName isEqual:@"lufy"]);
    XCTAssert([stu.nowName isEqual:@"lufy"]);
    XCTAssert([stu.oldName isEqual:@"kitty"]);
    XCTAssert([stu.nameChangedTime isEqual:@"2013-08-07"]);
    XCTAssert([stu.bag.name isEqual:@"小书包"]);
    XCTAssert(stu.bag.price == 100.7);
}

#pragma mark 字典数组 -> 模型数组
- (void)testJSONArray2ModelArray {
    // 1.定义一个字典数组
    NSArray *dictArray = @[
                           @{
                               @"name" : @"Jack",
                               @"icon" : @"lufy.png",
                               },
                           @{
                               @"name" : @"Rose",
                               @"icon" : @"nami.png",
                               }
                           ];
    
    // 2.将字典数组转为MJUser模型数组
    NSArray<MJUser *> *users = [MJUser mj_objectArrayWithKeyValuesArray:dictArray];
    
    // 3.检测users数组中的MJUser模型属性
    XCTAssert([users[0].name isEqual:@"Jack"]);
    XCTAssert([users[0].icon isEqual:@"lufy.png"]);
    XCTAssert([users[1].name isEqual:@"Rose"]);
    XCTAssert([users[1].icon isEqual:@"nami.png"]);
}

#pragma mark 模型 -> 字典
- (void)testModel2JSON {
    // 1.新建模型
    MJUser *user = [[MJUser alloc] init];
    user.name = @"Jack";
    user.icon = @"lufy.png";
    
    MJStatus *status = [[MJStatus alloc] init];
    status.user = user;
    status.text = @"今天的心情不错!";
    
    // 2.将模型转为字典
    NSDictionary *statusDict = status.mj_keyValues;
    MJExtensionLog(@"%@", statusDict);
    
    MJExtensionLog(@"%@", [status mj_keyValuesWithKeys:@[@"text"]]);
    
    // 3.新建多级映射的模型
    MJStudent *stu = [[MJStudent alloc] init];
    stu.ID = @"123";
    stu.oldName = @"rose";
    stu.nowName = @"jack";
    stu.desc = @"handsome";
    stu.nameChangedTime = @"2018-09-08";
    stu.books = @[@"Good book", @"Red book"];
    stu.isAthlete = YES;
    
    MJBag *bag = [[MJBag alloc] init];
    bag.name = @"小书包";
    bag.price = 205;
    stu.bag = bag;
    
    NSDictionary *stuDict = stu.mj_keyValues;
    MJExtensionLog(@"%@", stuDict);
    MJExtensionLog(@"%@", [stu mj_keyValuesWithIgnoredKeys:@[@"bag", @"oldName", @"nowName"]]);
    MJExtensionLog(@"%@", stu.mj_JSONString);
    
    [MJStudent mj_referenceReplacedKeyWhenCreatingKeyValues:NO];
    MJExtensionLog(@"\n模型转字典时,字典的key参考replacedKeyFromPropertyName等方法:\n%@", stu.mj_keyValues);
}

#pragma mark 模型数组 -> 字典数组
- (void)testModelArray2JSONArray {
    // 1.新建模型数组
    MJUser *user1 = [[MJUser alloc] init];
    user1.name = @"Jack";
    user1.icon = @"lufy.png";
    
    MJUser *user2 = [[MJUser alloc] init];
    user2.name = @"Rose";
    user2.icon = @"nami.png";
    NSArray *userArray = @[user1, user2];
    
    // 2.将模型数组转为字典数组
    NSArray *dictArray = [MJUser mj_keyValuesArrayWithObjectArray:userArray];
    MJExtensionLog(@"%@", dictArray);
}

#pragma mark NSNull相关的测试
- (void)testNull {
    NSNull *null = [NSNull null];
    id obj2 = [null mj_keyValues];
    MJExtensionLog(@"%@", obj2);
    
    MJUser *user1 = [[MJUser alloc] init];
    user1.name = @"user1";
    MJUser *user2 = [[MJUser alloc] init];
    user2.name = @"user2";
    NSArray *users = @[user1, [NSNull null], user2];
    NSArray *usersDictArr = [MJUser mj_keyValuesArrayWithObjectArray:users];
    MJExtensionLog(@"%@", usersDictArr);
    NSString *str = [usersDictArr mj_JSONObject];
    MJExtensionLog(@"%@", str);
    
    
    NSArray *dictArray = @[
                           @{
                               @"name" : @"Jack",
                               @"icon" : @"lufy.png",
                               },
                           [NSNull null],
                           @{
                               @"name" : @"Rose",
                               @"icon" : @"nami.png",
                               }
                           ];
    
    NSArray *userArray = [MJUser mj_objectArrayWithKeyValuesArray:dictArray];
    MJExtensionLog(@"%@", userArray);
    
    
    NSDictionary *dic = @{
                          @"name": [NSNull null],
                          @"icon": @"lufy.png"
                          };
    MJUser *testNull = [MJUser mj_objectWithKeyValues:dic];
    MJExtensionLog(@"%@", testNull);
    
    
    NSDictionary *catDict = @{
        @"name": @"Tom",
        @"address": [NSNull null],
        @"nicknames": @[
                @"Jerry's Heart",
                [NSNull null],
                @"Cowboy Tom",
        ]
    };
    MJCat *cat = [MJCat mj_objectWithKeyValues:catDict];
    MJExtensionLog(@"%@", cat);
}

#pragma mark NSCoding示例
- (void)testCoding {
    // 创建模型
    MJBag *bag = [[MJBag alloc] init];
    bag.name = @"Red bag";
    bag.price = 200.8;
    bag.isBig = YES;
    bag.weight = 200;
    
    NSString *file = [NSTemporaryDirectory() stringByAppendingPathComponent:@"bag.data"];
    
    NSError *error = nil;
    // 归档
    NSData *data = [NSKeyedArchiver archivedDataWithRootObject:bag requiringSecureCoding:YES error:&error];
    [data writeToFile:file atomically:true];

    // 解档
    NSData *readData = [NSFileManager.defaultManager contentsAtPath:file];
    error = nil;
    MJBag *decodedBag = [NSKeyedUnarchiver unarchivedObjectOfClass:MJBag.class fromData:readData error:&error];
    MJExtensionLog(@"name=%@, price=%f", decodedBag.name, decodedBag.price);
}

- (void)testCodingModelArrayProperty {
    // 有 NSArray 属性 模型
    MJPerson *person = [[MJPerson alloc] init];
    person.name = @"boy1";
    person.isVIP = YES;
    
    MJPerson *friend1 = [[MJPerson alloc] init];
    friend1.name = @"friend1";
    friend1.isVIP = YES;
    
    MJPerson *friend2 = [[MJPerson alloc] init];
    friend2.name = @"friend2";
    friend2.isVIP = NO;

    person.friends = @[friend1, friend2];
    person.books = @[@"book1", @"book2"];
    
    NSString *file = [NSTemporaryDirectory() stringByAppendingPathComponent:@"person.data"];
    NSError *error = nil;
    // 归档
    NSData *data = [NSKeyedArchiver archivedDataWithRootObject:person
                                         requiringSecureCoding:YES
                                                         error:&error];
    BOOL write = [data writeToFile:file atomically:true];
    XCTAssert(write);
    
    // 解档
    NSData *readData = [NSFileManager.defaultManager contentsAtPath:file];
    error = nil;
    MJPerson *decodedPerson = [NSKeyedUnarchiver unarchivedObjectOfClass:MJPerson.class
                                                                  fromData:readData
                                                                     error:&error];
    XCTAssert(decodedPerson.friends.count == 2);
    XCTAssert(decodedPerson.books.count == 2);
}

#pragma mark  统一转换属性名(比如驼峰转下划线)
- (void)testReplacedKeyFromPropertyName121 {
    // 1.定义一个字典
    NSDictionary *dict = @{
                           @"nick_name" : @"旺财",
                           @"sale_price" : @"10.5",
                           @"run_speed" : @"100.9"
                           };
    
    // 2.将字典转为MJUser模型
    MJDog *dog = [MJDog mj_objectWithKeyValues:dict];
    
    // 3.检测MJUser模型的属性
    XCTAssert([dog.nickName isEqual:@"旺财"]);
    XCTAssert(dog.salePrice == 10.5);
    XCTAssert(dog.runSpeed == 100.9);
}

#pragma mark 过滤字典的值(比如字符串日期处理为NSDate、字符串nil处理为@"")
- (void)testNewValueFromOldValue {
    // JSON -> Object
    // 1.定义一个字典
    NSDictionary *dict = @{
                           @"name" : @"5分钟突破iOS开发",
                           @"publishedTime" : @"2011-09-10"
                           };
    
    // 2.将字典转为MJUser模型
    MJBook *book = [MJBook mj_objectWithKeyValues:dict];
    
    // 3.检测MJUser模型的属性
    XCTAssert([book.name isEqual:@"5分钟突破iOS开发"]);
    NSDateFormatter *fmt = [[NSDateFormatter alloc] init];
    fmt.dateFormat = @"yyyy-MM-dd";
    XCTAssert([[fmt stringFromDate:book.publishedTime] isEqual:@"2011-09-10"]);
    
    //Object -> JSON
    NSDictionary *bookDict = [book mj_keyValues];
    
    XCTAssert([bookDict[@"name"] isEqualToString:@"5分钟突破iOS开发"]);
    XCTAssert([bookDict[@"publishedTime"] isEqualToString:@"2011-09-10"]);
}

#pragma mark 使用MJExtensionLog打印模型的所有属性
- (void)testLogAllProperties {
    MJUser *user = [[MJUser alloc] init];
    user.name = @"MJ";
    user.age = 10;
    user.sex = SexMale;
    user.icon = @"test.png";
    
    MJExtensionLog(@"%@", user);
}
@end


================================================
FILE: MJExtensionTests/Model/MJAd.h
================================================
//
//  MJAd.h
//  MJExtensionExample
//
//  Created by MJ Lee on 15/1/5.
//  Copyright (c) 2015年 小码哥. All rights reserved.
//  广告模型

#import <Foundation/Foundation.h>

@interface MJAd : NSObject
/** 广告图片 */
@property (copy, nonatomic) NSString *image;
/** 广告url */
@property (strong, nonatomic) NSURL *url;
@end


================================================
FILE: MJExtensionTests/Model/MJAd.m
================================================
//
//  MJAd.m
//  MJExtensionExample
//
//  Created by MJ Lee on 15/1/5.
//  Copyright (c) 2015年 小码哥. All rights reserved.
//

#import "MJAd.h"

@implementation MJAd

@end


================================================
FILE: MJExtensionTests/Model/MJBag.h
================================================
//
//  MJBag.h
//  MJExtensionExample
//
//  Created by MJ Lee on 15/1/28.
//  Copyright (c) 2015年 小码哥. All rights reserved.
//

#import <Foundation/Foundation.h>

@interface MJBag : NSObject
@property (copy, nonatomic) NSString *name;
@property (assign, nonatomic) double price;
@property (nonatomic) BOOL isBig;
@property (nonatomic) NSInteger weight;
@end


================================================
FILE: MJExtensionTests/Model/MJBag.m
================================================
//
//  MJBag.m
//  MJExtensionExample
//
//  Created by MJ Lee on 15/1/28.
//  Copyright (c) 2015年 小码哥. All rights reserved.
//

#import "MJBag.h"

@import MJExtension;

// NSSecureCoding实现
MJSecureCodingImplementation(MJBag, YES)

@implementation MJBag

//+ (NSArray *)mj_ignoredCodingPropertyNames
//{
//    return @[@"name"];
//}
@end


================================================
FILE: MJExtensionTests/Model/MJBaseObject.h
================================================
//
//  MJBaseObject.h
//  MJExtensionExample
//
//  Created by MJ Lee on 15/7/18.
//  Copyright (c) 2015年 小码哥. All rights reserved.
//

#import <Foundation/Foundation.h>

@interface MJBaseObject : NSObject
@property (copy, nonatomic) NSString *name;
@end


================================================
FILE: MJExtensionTests/Model/MJBaseObject.m
================================================
//
//  MJBaseObject.m
//  MJExtensionExample
//
//  Created by MJ Lee on 15/7/18.
//  Copyright (c) 2015年 小码哥. All rights reserved.
//

#import "MJBaseObject.h"

@implementation MJBaseObject

@end


================================================
FILE: MJExtensionTests/Model/MJBook.h
================================================
//
//  Book.h
//  MJExtensionExample
//
//  Created by MJ Lee on 15/6/7.
//  Copyright (c) 2015年 小码哥. All rights reserved.
//

#import <Foundation/Foundation.h>
@class MJBox;

@interface MJBook : NSObject
@property (copy, nonatomic) NSString *name;
@property (copy, nonatomic) NSString *publisher;
@property (strong, nonatomic) NSDate *publishedTime;
@property (strong, nonatomic) MJBox *box;
@end


================================================
FILE: MJExtensionTests/Model/MJBook.m
================================================
//
//  Book.m
//  MJExtensionExample
//
//  Created by MJ Lee on 15/6/7.
//  Copyright (c) 2015年 小码哥. All rights reserved.
//

#import "MJBook.h"
//#import "MJExtension.h"

@implementation MJBook
//- (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;
//}

- (void)mj_objectDidConvertToKeyValues:(NSMutableDictionary *)keyValues {
    NSDateFormatter *fmt = [[NSDateFormatter alloc] init];
    fmt.dateFormat = @"yyy-MM-dd";
    // should use sharedFormatter for better performance
    keyValues[@"publishedTime"] = [fmt stringFromDate:self.publishedTime];
}
@end


================================================
FILE: MJExtensionTests/Model/MJBox.h
================================================
//
//  MJBox.h
//  MJExtensionExample
//
//  Created by MJ Lee on 15/6/10.
//  Copyright (c) 2015年 小码哥. All rights reserved.
//

#import <Foundation/Foundation.h>

@interface MJBox : NSObject
@property (assign, nonatomic) double weight;
@property (assign, nonatomic) int size;
@end


================================================
FILE: MJExtensionTests/Model/MJBox.m
================================================
//
//  MJBox.m
//  MJExtensionExample
//
//  Created by MJ Lee on 15/6/10.
//  Copyright (c) 2015年 小码哥. All rights reserved.
//

#import "MJBox.h"

@import MJExtension;

@implementation MJBox
MJExtensionCodingImplementation
@end


================================================
FILE: MJExtensionTests/Model/MJCat.h
================================================
//
//  MJCat.h
//  MJExtensionTests
//
//  Created by Frank on 2020/6/9.
//  Copyright © 2020 MJ Lee. All rights reserved.
//

#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

@interface MJCat : NSObject <MJKeyValue>

@property (nonatomic, copy, nullable) NSString *name;
@property (nonatomic, copy, nullable) NSArray<NSString *> *nicknames;
@property (nonatomic, copy, nullable) NSString *address;
@property (nonatomic, copy, nullable) NSString *identifier;

@end

NS_ASSUME_NONNULL_END


================================================
FILE: MJExtensionTests/Model/MJCat.m
================================================
//
//  MJCat.m
//  MJExtensionTests
//
//  Created by Frank on 2020/6/9.
//  Copyright © 2020 MJ Lee. All rights reserved.
//

#import "MJCat.h"

@implementation MJCat

+ (NSDictionary *)mj_objectClassInArray {
    return @{
        @"nicknames" : NSString.class
    };
}

@end


================================================
FILE: MJExtensionTests/Model/MJDog.h
================================================
//
//  MJDog.h
//  MJExtensionExample
//
//  Created by MJ Lee on 15/6/7.
//  Copyright (c) 2015年 小码哥. All rights reserved.
//

#import <Foundation/Foundation.h>

@interface MJDog : NSObject
@property (copy, nonatomic) NSString *nickName;
@property (assign, nonatomic) double salePrice;
@property (assign, nonatomic) double runSpeed;
@end


================================================
FILE: MJExtensionTests/Model/MJDog.m
================================================
//
//  MJDog.m
//  MJExtensionExample
//
//  Created by MJ Lee on 15/6/7.
//  Copyright (c) 2015年 小码哥. All rights reserved.
//

#import "MJDog.h"
//#import "MJExtension.h"

@implementation MJDog
//+ (NSString *)mj_replacedKeyFromPropertyName121:(NSString *)propertyName
//{
//    // nickName -> nick_name
//    return [propertyName underlineFromCamel];
//}
@end


================================================
FILE: MJExtensionTests/Model/MJExtensionConfig.h
================================================
//
//  MJExtensionConfig.h
//  MJExtensionExample
//
//  Created by MJ Lee on 15/4/22.
//  Copyright (c) 2015年 小码哥. All rights reserved.
//

#import <Foundation/Foundation.h>

@interface MJExtensionConfig : NSObject

@end


================================================
FILE: MJExtensionTests/Model/MJExtensionConfig.m
================================================
//
//  MJExtensionConfig.m
//  MJExtensionExample
//
//  Created by MJ Lee on 15/4/22.
//  Copyright (c) 2015年 小码哥. All rights reserved.
//

#import "MJExtensionConfig.h"
#import "MJBag.h"
#import "MJUser.h"
#import "MJStatusResult.h"
#import "MJStudent.h"
#import "MJDog.h"
#import "MJBook.h"

@import MJExtension;

@implementation MJExtensionConfig
/**
 *  这个方法会在MJExtensionConfig加载进内存时调用一次
 */
+ (void)load
{
#pragma mark 如果使用NSObject来调用这些方法,代表所有继承自NSObject的类都会生效
#pragma mark NSObject中的ID属性对应着字典中的id
    [NSObject mj_setupReplacedKeyFromPropertyName:^NSDictionary *{
        return @{
                 @"ID" : @"id"
                 };
    }];
    
#pragma mark MJUser类的只有name、icon属性参与字典转模型
//    [MJUser mj_setupAllowedPropertyNames:^NSArray *{
//        return @[@"name", @"icon"];
//    }];
    // 相当于在MJUser.m中实现了+(NSArray *)mj_allowedPropertyNames方法
    
#pragma mark MJBag类中的name属性不参与归档
    [MJBag mj_setupIgnoredCodingPropertyNames:^NSArray *{
        return @[@"name"];
    }];
    // 相当于在MJBag.m中实现了+(NSArray *)mj_ignoredCodingPropertyNames方法
    
#pragma mark MJBag类中只有price属性参与归档
//    [MJBag mj_setupAllowedCodingPropertyNames:^NSArray *{
//        return @[@"price"];
//    }];
    // 相当于在MJBag.m中实现了+(NSArray *)mj_allowedCodingPropertyNames方法
    
#pragma mark MJStatusResult类中的statuses数组中存放的是MJStatus模型
#pragma mark MJStatusResult类中的ads数组中存放的是MJAd模型
    [MJStatusResult mj_setupObjectClassInArray:^NSDictionary *{
        return @{
                 @"statuses" : @"MJStatus", // @"statuses" : [MJStatus class],
                 @"ads" : @"MJAd" // @"ads" : [MJAd class]
                 };
    }];
    // 相当于在MJStatusResult.m中实现了+(NSDictionary *)mj_objectClassInArray方法
    
#pragma mark MJStudent中的desc属性对应着字典中的desciption
#pragma mark ....
    [MJStudent mj_setupReplacedKeyFromPropertyName:^NSDictionary *{
        return @{
                 @"desc" : @"desciption",
                 @"oldName" : @"name.oldName",
                 @"nowName" : @"name.newName",
                 @"otherName" : @[@"otherName", @"name.newName", @"name.oldName"],
                 @"nameChangedTime" : @"name.info[1].nameChangedTime",
                 @"bag" : @"other.bag"
                 };
    }];
    // 相当于在MJStudent.m中实现了+(NSDictionary *)mj_replacedKeyFromPropertyName方法
    
#pragma mark MJDog的所有驼峰属性转成下划线key去字典中取值
    [MJDog mj_setupReplacedKeyFromPropertyName121:^NSString *(NSString *propertyName) {
        return [propertyName mj_underlineFromCamel];
    }];
    // 相当于在MJDog.m中实现了+(NSDictionary *)mj_replacedKeyFromPropertyName121:方法
    
#pragma mark MJBook的日期处理、字符串nil值处理
    [MJBook mj_setupNewValueFromOldValue:^id(id object, id oldValue, MJProperty *property) {
        if ([property.name isEqualToString:@"publisher"]) {
            if (oldValue == nil || [oldValue isKindOfClass:[NSNull class]]) return @"";
        } else if (property.type.typeClass == [NSDate class]) {
            NSDateFormatter *fmt = [[NSDateFormatter alloc] init];
            fmt.dateFormat = @"yyyy-MM-dd";
            return [fmt dateFromString:oldValue];
        }
            
        return oldValue;
    }];
    // 相当于在MJBook.中实现了- (id)mj_newValueFromOldValue:property:方法
}
@end


================================================
FILE: MJExtensionTests/Model/MJFrenchUser.h
================================================
//
//  MJFrenchUser.h
//  MJExtensionTests
//
//  Created by Frank on 2019/9/26.
//  Copyright © 2019 MJ Lee. All rights reserved.
//

#import "MJUser.h"

NS_ASSUME_NONNULL_BEGIN

@interface MJFrenchUser : MJUser

@end

NS_ASSUME_NONNULL_END


================================================
FILE: MJExtensionTests/Model/MJFrenchUser.m
================================================
//
//  MJFrenchUser.m
//  MJExtensionTests
//
//  Created by Frank on 2019/9/26.
//  Copyright © 2019 MJ Lee. All rights reserved.
//

#import "MJFrenchUser.h"

@implementation MJFrenchUser

+ (NSLocale *)mj_numberLocale {
    return [NSLocale localeWithLocaleIdentifier:@"fr_FR"];
}

@end


================================================
FILE: MJExtensionTests/Model/MJPerson.h
================================================
//
//  MJPerson.h
//  MJExtensionTests
//
//  Created by MJ Lee on 2019/8/29.
//  Copyright © 2019 MJExtension. All rights reserved.
//

#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

@interface MJPerson : NSObject
@property (copy, nonatomic) NSString *name;
@property (nonatomic) BOOL isVIP;
@property (strong, nonatomic) NSArray<MJPerson *> *friends;
@property (strong, nonatomic) NSArray<NSString *> *books;
@end

NS_ASSUME_NONNULL_END


================================================
FILE: MJExtensionTests/Model/MJPerson.m
================================================
//
//  MJPerson.m
//  MJExtensionTests
//
//  Created by MJ Lee on 2019/8/29.
//  Copyright © 2019 MJExtension. All rights reserved.
//

#import "MJPerson.h"
#import <MJExtension/MJExtension.h>

// NSSecureCoding实现
MJSecureCodingImplementation(MJPerson, YES)

@implementation MJPerson
+ (NSDictionary *)mj_objectClassInArray {
    return @{@"friends": [MJPerson class],
             @"books": [NSString class]};
}
@end


================================================
FILE: MJExtensionTests/Model/MJStatus.h
================================================
//
//  MJStatus.h
//  字典与模型的互转
//
//  Created by MJ Lee on 14-5-21.
//  Copyright (c) 2014年 小码哥. All rights reserved.
//  微博模型

#import <Foundation/Foundation.h>
@class MJUser;

@interface MJStatus : NSObject
/** 微博文本内容 */
@property (copy, nonatomic) NSString *text;
/** 微博作者 */
@property (strong, nonatomic) MJUser *user;
/** 转发的微博 */
@property (strong, nonatomic) MJStatus *retweetedStatus;
@end

================================================
FILE: MJExtensionTests/Model/MJStatus.m
================================================
//
//  MJStatus.m
//  字典与模型的互转
//
//  Created by MJ Lee on 14-5-21.
//  Copyright (c) 2014年 小码哥. All rights reserved.
//

#import "MJStatus.h"

@implementation MJStatus

@end


================================================
FILE: MJExtensionTests/Model/MJStatusResult.h
================================================
//
//  MJStatusResult.h
//  字典与模型的互转
//
//  Created by MJ Lee on 14-5-21.
//  Copyright (c) 2014年 小码哥. All rights reserved.
//  微博结果(用来表示大批量的微博数据)

#import "MJBaseObject.h"
@class MJStatus, MJAd;

@interface MJStatusResult : MJBaseObject
/** 存放着某一页微博数据(里面都是Status模型) */
@property (strong, nonatomic) NSMutableArray<MJStatus *> *statuses;
/** 存放着一堆的广告数据(里面都是MJAd模型) */
@property (strong, nonatomic) NSArray<MJAd *> *ads;
/** 总数 */
@property (strong, nonatomic) NSNumber *totalNumber;
/** 上一页的游标 */
@property (assign, nonatomic) long long previousCursor;
/** 下一页的游标 */
@property (assign, nonatomic) long long nextCursor;
@end


================================================
FILE: MJExtensionTests/Model/MJStatusResult.m
================================================
//
//  MJStatusResult.m
//  字典与模型的互转
//
//  Created by MJ Lee on 14-5-21.
//  Copyright (c) 2014年 小码哥. All rights reserved.
//

#import "MJStatusResult.h"

@implementation MJStatusResult
+ (NSDictionary *)mj_objectClassInArray
{
    return @{
             @"statuses" : @"MJStatus",
             @"ads" : @"MJAd"
             };
}
@end


================================================
FILE: MJExtensionTests/Model/MJStudent.h
================================================
//
//  MJStudent.h
//  MJExtensionExample
//
//  Created by MJ Lee on 15/1/5.
//  Copyright (c) 2015年 小码哥. All rights reserved.
//

#import <Foundation/Foundation.h>
@class MJBag;

@interface MJStudent : NSObject
@property (copy, nonatomic) NSString *ID;
@property (copy, nonatomic) NSString *otherName;
@property (copy, nonatomic) NSString *nowName;
@property (copy, nonatomic) NSString *oldName;
@property (copy, nonatomic) NSString *nameChangedTime;
@property (copy, nonatomic) NSString *desc;
@property (strong, nonatomic) MJBag *bag;
@property (strong, nonatomic) NSArray *books;

/** 32 bit bug */
@property (nonatomic) BOOL isAthlete;
@end


================================================
FILE: MJExtensionTests/Model/MJStudent.m
================================================
//
//  MJStudent.m
//  MJExtensionExample
//
//  Created by MJ Lee on 15/1/5.
//  Copyright (c) 2015年 小码哥. All rights reserved.
//

#import "MJStudent.h"

@implementation MJStudent
//+ (NSDictionary *)mj_replacedKeyFromPropertyName
//{
//    return @{@"ID" : @"id",
//             @"desc" : @"desciption",
//             @"oldName" : @"name.oldName",
//             @"nowName" : @"name.newName",
//             @"nameChangedTime" : @"name.info[1].nameChangedTime",
//             @"bag" : @"other.bag"
//             };
//}
@end


================================================
FILE: MJExtensionTests/Model/MJUser.h
================================================
//
//  MJUser.h
//  字典与模型的互转
//
//  Created by MJ Lee on 14-5-21.
//  Copyright (c) 2014年 小码哥. All rights reserved.
//  用户模型

#import <Foundation/Foundation.h>

typedef enum {
    SexMale,
    SexFemale
} Sex;

@interface MJUser : NSObject
/** 名称 */
@property (copy, nonatomic) NSString *name;
/** 头像 */
@property (copy, nonatomic) NSString *icon;
/** 年龄 */
@property (assign, nonatomic) int age;
@property (assign, nonatomic) unsigned int age2;
/** 身高 */
@property (strong, nonatomic) NSNumber *height;
/** 财富 */
@property (strong, nonatomic) NSDecimalNumber *money;
/** 性别 */
@property (assign, nonatomic) Sex sex;
/** 同性恋 */
@property (assign, nonatomic, getter=isGay) BOOL gay;
/** 速度 */
@property (assign, nonatomic) NSInteger speed;
/** 标识 */
@property (assign, nonatomic) long long identifier;
@property (assign, nonatomic) unsigned long long identifier2;
/** 价格 */
@property (assign, nonatomic) double price;
/** 赞 */
@property (assign, nonatomic) int like;
/** 收藏 */
@property (assign, nonatomic) int collect;
/** 富有 */
@property (assign, nonatomic) BOOL rich;

/** 一定为 NO, 用来测试无效数据 @"alien": @"yr Joking"  */
@property (assign, nonatomic) BOOL alien;

@end


================================================
FILE: MJExtensionTests/Model/MJUser.m
================================================
//
//  MJUser.m
//  字典与模型的互转
//
//  Created by MJ Lee on 14-5-21.
//  Copyright (c) 2014年 小码哥. All rights reserved.
//

#import "MJUser.h"

@import MJExtension;

@implementation MJUser
//+ (NSArray *)mj_allowedPropertyNames
//{
//    return @[@"name", @"icon"];
//}
MJExtensionLogAllProperties
@end


================================================
FILE: MJExtensionTests/MultiThreadTests.swift
================================================
//
//  MultiThreadTests.swift
//  MJExtensionTests
//
//  Created by Frank on 2021/3/10.
//  Copyright © 2021 MJ Lee. All rights reserved.
//

import XCTest

class MultiThreadTests: XCTestCase {
    private func testerJSON(_ id: Int) -> [String: Any] {
        return
            [
                "isSpecialAgent": true,
                "identifier": "\(id)",
                "age": 22,
                "name": "Juan"
            ]
    }
    
    private func catJSON(_ id: Int) -> [String: Any] {
        return
            [
                "name": "Tom",
                "identifier": "\(id)",
                "nicknames" : [
                    "Jerry's Heart",
                    "Cowboy Tom",
                ],
            ]
    }
    
    func testMultiThread() throws {
        let concurrentQueue = DispatchQueue.init(label: "MJExtension.MultiThread.UnitTests", qos: .default, attributes: [.concurrent], autoreleaseFrequency: .inherit)
        let expectation4Tester = self.expectation(description: "Tester conversion successs")
        let expectation4Cat = self.expectation(description: "Cat conversion successs")
        
        for id in 0..<100 {
            concurrentQueue.async {
                sleep(1)
                let testerDict = self.testerJSON(id)
                guard let _ = MJTester.mj_object(withKeyValues: testerDict) else {
                    XCTAssert(false, "conversion failed")
                    return
                }
                print("tester: \(id)")
//                XCTAssert(tester.isSpecialAgent)
//                XCTAssert(tester.identifier == testerDict["identifier"] as? String)
//                XCTAssert(tester.age == testerDict["age"] as! Int)
//                XCTAssert(tester.name == testerDict["name"] as? String)
                
                if id == 99 {
                    expectation4Tester.fulfill()
                }
            }
            
            concurrentQueue.async {
                sleep(1)
                let catDict = self.catJSON(id)
                guard let _ = MJCat.mj_object(withKeyValues: catDict) else {
                    XCTAssert(false, "convertion failed")
                    return
                }
                print("cat: \(id)")
//                cat.nicknames?.forEach({ (nickname) in
//                    XCTAssert((catDict["nicknames"] as! [String]?)?.contains(nickname) ?? false)
//                })
//                XCTAssert(cat.identifier == catDict["identifier"] as? String)
//                XCTAssert(cat.name == catDict["name"] as? String)
                
                if id == 99 {
                    expectation4Cat.fulfill()
                }
            }
            
            concurrentQueue.async {
                sleep(1)
                MJTester.mj_setupAllowedPropertyNames { () -> [Any]? in
                    ["name", "identifier"]
                }
                print("change allowPropertyNames: (name, identifier) \(id)")
            }
            
            concurrentQueue.async {
                sleep(1)
                MJTester.mj_setupAllowedPropertyNames { () -> [Any]? in
                    ["name"]
                }
                print("change allowPropertyNames: (name) \(id)")
            }
            
            concurrentQueue.async {
                sleep(1)
                MJTester.mj_setupAllowedPropertyNames { () -> [Any]? in
                    ["isSpecialAgent", "age"]
                }
                print("change allowPropertyNames: (isSpecialAgent, age) \(id)")
            }
            
            concurrentQueue.async {
                sleep(1)
                MJUser.mj_setupAllowedPropertyNames { () -> [Any]? in
                    ["name", "nicknames"]
                }
                print("change allowPropertyNames: (name, nicknames) \(id)")
            }
        }
        
        waitForExpectations(timeout: 15, handler: nil)
    }
}


================================================
FILE: MJExtensionTests/PrefixHeader.pch
================================================
//
//  PrefixHeader.pch
//  MJExtensionTests
//
//  Created by MJ Lee on 2019/8/29.
//  Copyright © 2019 MJ Lee. All rights reserved.
//

#ifndef PrefixHeader_pch
#define PrefixHeader_pch

@import MJExtension;

#endif /* PrefixHeader_pch */


================================================
FILE: MJExtensionTests/SwiftModel/MJTester.swift
================================================
//
//  MJTester.swift
//  MJExtensionTests
//
//  Created by Frank on 2020/8/21.
//  Copyright © 2020 MJ Lee. All rights reserved.
//

import Foundation
import MJExtension

@objc(MJTester)
@objcMembers
class MJTester: NSObject {
    // make sure to use `dynamic` attribute for basic type & must use as Non-Optional & must set initial value
    dynamic var isSpecialAgent: Bool = false
    dynamic var age: Int = 0
    
    var name: String?
    var identifier: String?
}


================================================
FILE: MJExtensionTests/SwiftModelTests.swift
================================================
//
//  SwiftModelTests.swift
//  MJExtensionTests
//
//  Created by Frank on 2020/8/21.
//  Copyright © 2020 MJ Lee. All rights reserved.
//

import XCTest

class SwiftModelTests: XCTestCase {
    override class func setUp() {
        MJTester.mj_setupAllowedPropertyNames {
            nil
        }
        MJUser.mj_setupAllowedPropertyNames {
            nil
        }
    }
    
    // MARK: 🌈 Use Swift model
    func testNormalModel() throws {
        let testerDict: [String: Any] = [
            "isSpecialAgent": true,
            "identifier": "007",
            "age": 22,
            "name": "Juan"
        ]
        
        guard let tester = MJTester.mj_object(withKeyValues: testerDict) else {
            XCTAssert(false, "conversion failed")
            return
        }
        XCTAssert(tester.isSpecialAgent)
        XCTAssert(tester.identifier == testerDict["identifier"] as? String)
        XCTAssert(tester.age == testerDict["age"] as! Int)
        XCTAssert(tester.name == testerDict["name"] as? String)
    }

    // MARK: 🌈 Use Objective-C model code
    func testOBJCModel() throws {
        let userDict: [String: Any] = [
            "rich": true,
            "name": "007",
            "age": 22,
            "price": "1.5"
        ]
        
        guard let user = MJUser.mj_object(withKeyValues: userDict) else {
            XCTAssert(false, "conversion failed")
            return
        }
        XCTAssert(user.rich)
        XCTAssert(user.price == Double(userDict["price"] as! String))
        XCTAssert(user.age == userDict["age"] as! Int)
        XCTAssert(user.name == userDict["name"] as? String)
    }
}


================================================
FILE: Package.swift
================================================
// swift-tools-version:5.3

import PackageDescription

let package = Package(
    name: "MJExtension",
    products: [
        .library(name: "MJExtension", targets: ["MJExtension"]),
    ],
    dependencies: [],
    targets: [
        .target(
            name: "MJExtension",
            dependencies: [],
            path: "MJExtension",
            exclude: ["Info.plist"],
            resources: [.copy("PrivacyInfo.xcprivacy")],
            publicHeadersPath: ".",
            cxxSettings: [
                .headerSearchPath("."),
            ]
        ),
        // Mixed languages are not supported now. Go MJExtension project to see tests.
//        .testTarget(
//            name: "MJExtensionTests",
//            dependencies: ["MJExtension"],
//            path: "MJExtensionTests",
//            exclude: ["Info.plist"]
//        )
    ]
)


================================================
FILE: README.md
================================================
MJExtension
===
[![SPM supported](https://img.shields.io/badge/SPM-supported-4BC51D.svg?style=flat)](https://github.com/apple/swift-package-manager)
[![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)](https://cocoapods.org/pods/MJExtension)
![Platform](https://img.shields.io/cocoapods/p/MJExtension.svg?style=flat)

- A fast, convenient and nonintrusive conversion framework between JSON and model.
- 转换速度快、使用简单方便的字典转模型框架

[📜✍🏻**Release Notes**: more details](https://github.com/CoderMJLee/MJExtension/releases)

## Contents

* [Getting Started 【开始使用】](#Getting_Started)
	* [Features 【能做什么】](#Features)
	* [Installation 【安装】](#Installation)
* [Examples 【示例】](#Examples)
  * [Usage in Swift](#usage_in_swift)
  * [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)
  * [Secure Coding](#SecureCoding)
  * [Camel -> underline](#Camel_underline)
  * [NSString -> NSDate, nil -> @""](#NSString_NSDate)
  * [NSDate -> NSString](#NSDate_NSString)
  * [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 / SecureCoding(归档和解档)

### <a id="Installation"></a> Installation【安装】

#### CocoaPods【使用CocoaPods】

```ruby
pod 'MJExtension'
```

#### Carthage

```ruby
github "CoderMJLee/MJExtension"
```

#### Swift Package Manager

Released from [`3.4.0`](https://github.com/CoderMJLee/MJExtension/releases/)

#### Manually【手动导入】

- Drag all source files under folder `MJExtension` to your project.【将`MJExtension`文件夹中的所有源代码拽入项目中】
- Import the main header file:`#import "MJExtension.h"`【导入主头文件:`#import "MJExtension.h"`】

## <a id="Examples"></a> Examples【示例】

**Add `MJKeyValue` protocol to your model if needed【如果有需要, 请在模型中加入 `MJKeyValue` 协议】**

### <a id="usage_in_swift"></a> Usage in Swift [关于在Swift中使用MJExtension] ‼️

> Example: 
>
> - [Model - MJTester.swift](MJExtensionTests/SwiftModel/MJTester.swift)
>
> - [Usage - SwiftModelTests.swift](MJExtensionTests/SwiftModelTests.swift)

```swift
@objc(MJTester)
@objcMembers
class MJTester: NSObject {
    // make sure to use `dynamic` attribute for basic type & must use as Non-Optional & must set initial value
    dynamic var isSpecialAgent: Bool = false
    dynamic var age: Int = 0
    
    var name: String?
    var identifier: String?
}
```

1.  `@objc` or `@objcMembers` attributes should be added to class or property for declaration of Objc accessibility [在 Swift4 之后, 请在属性前加 `@objc` 修饰或在类前增加 `@objcMembers`. 以保证 Swift 的属性能够暴露给 Objc 使用. ]
2.  If you let `Bool` & `Int` as property type, make sure that using `dynamic` to attribute it. It must be `Non-Optional` type and assign `a default value`.[如果要使用 `Bool` 和 `Int` 等 Swfit 专用基本类型, 请使用 `dynamic` 关键字修饰, 类型为 `Non-Optional`, 並且给定初始值.]

> 纯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)
>   - 已经对各种常用的数据场景进行了大量的单元测试
>   - 简单易用、功能丰富、转换快速

### <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

/*************
Download .txt
gitextract_nyxu_r8w/

├── .github/
│   └── ISSUE_TEMPLATE/
│       ├── bug--.md
│       └── feature--.md
├── .gitignore
├── .travis.yml
├── LICENSE
├── MJExtension/
│   ├── Info.plist
│   ├── MJExtension.h
│   ├── MJExtensionConst.h
│   ├── MJExtensionConst.m
│   ├── MJFoundation.h
│   ├── MJFoundation.m
│   ├── MJProperty.h
│   ├── MJProperty.m
│   ├── MJPropertyKey.h
│   ├── MJPropertyKey.m
│   ├── MJPropertyType.h
│   ├── MJPropertyType.m
│   ├── NSObject+MJClass.h
│   ├── NSObject+MJClass.m
│   ├── NSObject+MJCoding.h
│   ├── NSObject+MJCoding.m
│   ├── NSObject+MJKeyValue.h
│   ├── NSObject+MJKeyValue.m
│   ├── NSObject+MJProperty.h
│   ├── NSObject+MJProperty.m
│   ├── NSString+MJExtension.h
│   ├── NSString+MJExtension.m
│   └── PrivacyInfo.xcprivacy
├── MJExtension.podspec
├── MJExtension.xcodeproj/
│   ├── project.pbxproj
│   └── xcshareddata/
│       └── xcschemes/
│           ├── MJExtension.xcscheme
│           └── MJExtensionDemo.xcscheme
├── MJExtensionDemo/
│   ├── AppDelegate.swift
│   ├── Assets.xcassets/
│   │   ├── AccentColor.colorset/
│   │   │   └── Contents.json
│   │   ├── AppIcon.appiconset/
│   │   │   └── Contents.json
│   │   └── Contents.json
│   ├── Base.lproj/
│   │   ├── LaunchScreen.storyboard
│   │   └── Main.storyboard
│   └── Info.plist
├── MJExtensionTests/
│   ├── CoreDataModel/
│   │   ├── MJCoreDataTestModel.xcdatamodeld/
│   │   │   └── MJCoreDataTestModel.xcdatamodel/
│   │   │       └── contents
│   │   ├── MJCoreDataTester+CoreDataClass.swift
│   │   └── MJCoreDataTester+CoreDataProperties.swift
│   ├── CoreDataTests.swift
│   ├── Info.plist
│   ├── MJExtensionTests-Bridging-Header.h
│   ├── MJExtensionTests.m
│   ├── Model/
│   │   ├── MJAd.h
│   │   ├── MJAd.m
│   │   ├── MJBag.h
│   │   ├── MJBag.m
│   │   ├── MJBaseObject.h
│   │   ├── MJBaseObject.m
│   │   ├── MJBook.h
│   │   ├── MJBook.m
│   │   ├── MJBox.h
│   │   ├── MJBox.m
│   │   ├── MJCat.h
│   │   ├── MJCat.m
│   │   ├── MJDog.h
│   │   ├── MJDog.m
│   │   ├── MJExtensionConfig.h
│   │   ├── MJExtensionConfig.m
│   │   ├── MJFrenchUser.h
│   │   ├── MJFrenchUser.m
│   │   ├── MJPerson.h
│   │   ├── MJPerson.m
│   │   ├── MJStatus.h
│   │   ├── MJStatus.m
│   │   ├── MJStatusResult.h
│   │   ├── MJStatusResult.m
│   │   ├── MJStudent.h
│   │   ├── MJStudent.m
│   │   ├── MJUser.h
│   │   └── MJUser.m
│   ├── MultiThreadTests.swift
│   ├── PrefixHeader.pch
│   ├── SwiftModel/
│   │   └── MJTester.swift
│   └── SwiftModelTests.swift
├── Package.swift
└── README.md
Download .txt
SYMBOL INDEX (8 symbols across 4 files)

FILE: MJExtension/MJPropertyKey.h
  type MJPropertyKeyType (line 11) | typedef enum {

FILE: MJExtension/NSObject+MJClass.h
  type NSArray (line 17) | typedef NSArray * (^MJAllowedPropertyNames)(void);
  type NSArray (line 19) | typedef NSArray * (^MJAllowedCodingPropertyNames)(void);
  type NSArray (line 22) | typedef NSArray * (^MJIgnoredPropertyNames)(void);
  type NSArray (line 24) | typedef NSArray * (^MJIgnoredCodingPropertyNames)(void);

FILE: MJExtension/NSObject+MJProperty.h
  type NSDictionary (line 23) | typedef NSDictionary * (^MJReplacedKeyFromPropertyName)(void);
  type NSDictionary (line 26) | typedef NSDictionary * (^MJObjectClassInArray)(void);

FILE: MJExtensionTests/Model/MJUser.h
  type Sex (line 11) | typedef enum {
Condensed preview — 80 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (218K chars).
[
  {
    "path": ".github/ISSUE_TEMPLATE/bug--.md",
    "chars": 326,
    "preview": "---\nname: Bug上报\nabout: 提交Bug让框架更加健壮, 务必提供 Demo\ntitle: ''\nlabels: ''\nassignees: ''\n\n---\n\n## 为了排除你自己的问题, 请写一个 Demo\n最好能提供出现"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/feature--.md",
    "chars": 233,
    "preview": "---\nname: Feature建议\nabout: 为框架提供新功能建议\ntitle: ''\nlabels: ''\nassignees: ''\n\n---\n\n**你的新功能建议是否牵扯到某个常见的问题?**\n比如,在开发中,经常遇到XXX问"
  },
  {
    "path": ".gitignore",
    "chars": 269,
    "preview": ".DS_Store\n\n# Xcode\nbuild/*\n*.pbxuser\n!default.pbxuser\n*.mode1v3\n!default.mode1v3\n*.mode2v3\n!default.mode2v3\n*.perspectiv"
  },
  {
    "path": ".travis.yml",
    "chars": 325,
    "preview": "language: objective-c\nxcode_project: MJExtension.xcodeproj\nxcode_scheme: MJExtension\n\nenv:\n  global:\n    - FRAMEWORK_NAM"
  },
  {
    "path": "LICENSE",
    "chars": 1104,
    "preview": "Copyright (c) 2013-2019 MJExtension (https://github.com/CoderMJLee/MJExtension)\n\nPermission is hereby granted, free of c"
  },
  {
    "path": "MJExtension/Info.plist",
    "chars": 726,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/P"
  },
  {
    "path": "MJExtension/MJExtension.h",
    "chars": 716,
    "preview": "//\n//  MJExtension.h\n//  MJExtension\n//\n//  Created by mj on 14-1-15.\n//  Copyright (c) 2014年 小码哥. All rights reserved.\n"
  },
  {
    "path": "MJExtension/MJExtensionConst.h",
    "chars": 2879,
    "preview": "\n#ifndef __MJExtensionConst__H__\n#define __MJExtensionConst__H__\n\n#import <Foundation/Foundation.h>\n\n#ifndef MJ_LOCK\n#de"
  },
  {
    "path": "MJExtension/MJExtensionConst.m",
    "chars": 861,
    "preview": "#ifndef __MJExtensionConst__M__\n#define __MJExtensionConst__M__\n\n#import <Foundation/Foundation.h>\n\n/**\n *  成员变量类型(属性类型)"
  },
  {
    "path": "MJExtension/MJFoundation.h",
    "chars": 318,
    "preview": "//\n//  MJFoundation.h\n//  MJExtensionExample\n//\n//  Created by MJ Lee on 14/7/16.\n//  Copyright (c) 2014年 小码哥. All right"
  },
  {
    "path": "MJExtension/MJFoundation.m",
    "chars": 2284,
    "preview": "//\n//  MJFoundation.m\n//  MJExtensionExample\n//\n//  Created by MJ Lee on 14/7/16.\n//  Copyright (c) 2014年 小码哥. All right"
  },
  {
    "path": "MJExtension/MJProperty.h",
    "chars": 1253,
    "preview": "//\n//  MJProperty.h\n//  MJExtensionExample\n//\n//  Created by MJ Lee on 15/4/17.\n//  Copyright (c) 2015年 小码哥. All rights "
  },
  {
    "path": "MJExtension/MJProperty.m",
    "chars": 6292,
    "preview": "//\n//  MJProperty.m\n//  MJExtensionExample\n//\n//  Created by MJ Lee on 15/4/17.\n//  Copyright (c) 2015年 小码哥. All rights "
  },
  {
    "path": "MJExtension/MJPropertyKey.h",
    "chars": 582,
    "preview": "//\n//  MJPropertyKey.h\n//  MJExtensionExample\n//\n//  Created by MJ Lee on 15/8/11.\n//  Copyright (c) 2015年 小码哥. All righ"
  },
  {
    "path": "MJExtension/MJPropertyKey.m",
    "chars": 640,
    "preview": "//\n//  MJPropertyKey.m\n//  MJExtensionExample\n//\n//  Created by MJ Lee on 15/8/11.\n//  Copyright (c) 2015年 小码哥. All righ"
  },
  {
    "path": "MJExtension/MJPropertyType.h",
    "chars": 923,
    "preview": "//\n//  MJPropertyType.h\n//  MJExtension\n//\n//  Created by mj on 14-1-15.\n//  Copyright (c) 2014年 小码哥. All rights reserve"
  },
  {
    "path": "MJExtension/MJPropertyType.m",
    "chars": 2115,
    "preview": "//\n//  MJPropertyType.m\n//  MJExtension\n//\n//  Created by mj on 14-1-15.\n//  Copyright (c) 2014年 小码哥. All rights reserve"
  },
  {
    "path": "MJExtension/NSObject+MJClass.h",
    "chars": 2125,
    "preview": "//\n//  NSObject+MJClass.h\n//  MJExtensionExample\n//\n//  Created by MJ Lee on 15/8/11.\n//  Copyright (c) 2015年 小码哥. All r"
  },
  {
    "path": "MJExtension/NSObject+MJClass.m",
    "chars": 5478,
    "preview": "//\n//  NSObject+MJClass.m\n//  MJExtensionExample\n//\n//  Created by MJ Lee on 15/8/11.\n//  Copyright (c) 2015年 小码哥. All r"
  },
  {
    "path": "MJExtension/NSObject+MJCoding.h",
    "chars": 1143,
    "preview": "//\n//  NSObject+MJCoding.h\n//  MJExtension\n//\n//  Created by mj on 14-1-15.\n//  Copyright (c) 2014年 小码哥. All rights rese"
  },
  {
    "path": "MJExtension/NSObject+MJCoding.m",
    "chars": 2421,
    "preview": "//\n//  NSObject+MJCoding.m\n//  MJExtension\n//\n//  Created by mj on 14-1-15.\n//  Copyright (c) 2014年 小码哥. All rights rese"
  },
  {
    "path": "MJExtension/NSObject+MJKeyValue.h",
    "chars": 4441,
    "preview": "//\n//  NSObject+MJKeyValue.h\n//  MJExtension\n//\n//  Created by mj on 13-8-24.\n//  Copyright (c) 2013年 小码哥. All rights re"
  },
  {
    "path": "MJExtension/NSObject+MJKeyValue.m",
    "chars": 20703,
    "preview": "//\n//  NSObject+MJKeyValue.m\n//  MJExtension\n//\n//  Created by mj on 13-8-24.\n//  Copyright (c) 2013年 小码哥. All rights re"
  },
  {
    "path": "MJExtension/NSObject+MJProperty.h",
    "chars": 1839,
    "preview": "//\n//  NSObject+MJProperty.h\n//  MJExtensionExample\n//\n//  Created by MJ Lee on 15/4/17.\n//  Copyright (c) 2015年 小码哥. Al"
  },
  {
    "path": "MJExtension/NSObject+MJProperty.m",
    "chars": 8583,
    "preview": "//\n//  NSObject+MJProperty.m\n//  MJExtensionExample\n//\n//  Created by MJ Lee on 15/4/17.\n//  Copyright (c) 2015年 小码哥. Al"
  },
  {
    "path": "MJExtension/NSString+MJExtension.h",
    "chars": 545,
    "preview": "//\n//  NSString+MJExtension.h\n//  MJExtensionExample\n//\n//  Created by MJ Lee on 15/6/7.\n//  Copyright (c) 2015年 小码哥. Al"
  },
  {
    "path": "MJExtension/NSString+MJExtension.m",
    "chars": 2725,
    "preview": "//\n//  NSString+MJExtension.m\n//  MJExtensionExample\n//\n//  Created by MJ Lee on 15/6/7.\n//  Copyright (c) 2015年 小码哥. Al"
  },
  {
    "path": "MJExtension/PrivacyInfo.xcprivacy",
    "chars": 373,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/P"
  },
  {
    "path": "MJExtension.podspec",
    "chars": 764,
    "preview": "Pod::Spec.new do |s|\n  s.name         = \"MJExtension\"\n  s.version      = \"3.4.2\"\n  s.ios.deployment_target = '12.0'\n  s."
  },
  {
    "path": "MJExtension.xcodeproj/project.pbxproj",
    "chars": 48546,
    "preview": "// !$*UTF8*$!\n{\n\tarchiveVersion = 1;\n\tclasses = {\n\t};\n\tobjectVersion = 50;\n\tobjects = {\n\n/* Begin PBXBuildFile section *"
  },
  {
    "path": "MJExtension.xcodeproj/xcshareddata/xcschemes/MJExtension.xcscheme",
    "chars": 3222,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Scheme\n   LastUpgradeVersion = \"1030\"\n   version = \"1.3\">\n   <BuildAction\n      "
  },
  {
    "path": "MJExtension.xcodeproj/xcshareddata/xcschemes/MJExtensionDemo.xcscheme",
    "chars": 4256,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Scheme\n   LastUpgradeVersion = \"1300\"\n   version = \"1.3\">\n   <BuildAction\n      "
  },
  {
    "path": "MJExtensionDemo/AppDelegate.swift",
    "chars": 664,
    "preview": "//\n//  AppDelegate.swift\n//  MJExtensionDemo\n//\n//  Created by Frank on 2021/9/8.\n//  Copyright © 2021 MJ Lee. All right"
  },
  {
    "path": "MJExtensionDemo/Assets.xcassets/AccentColor.colorset/Contents.json",
    "chars": 123,
    "preview": "{\n  \"colors\" : [\n    {\n      \"idiom\" : \"universal\"\n    }\n  ],\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  }"
  },
  {
    "path": "MJExtensionDemo/Assets.xcassets/AppIcon.appiconset/Contents.json",
    "chars": 1590,
    "preview": "{\n  \"images\" : [\n    {\n      \"idiom\" : \"iphone\",\n      \"size\" : \"20x20\",\n      \"scale\" : \"2x\"\n    },\n    {\n      \"idiom\""
  },
  {
    "path": "MJExtensionDemo/Assets.xcassets/Contents.json",
    "chars": 62,
    "preview": "{\n  \"info\" : {\n    \"version\" : 1,\n    \"author\" : \"xcode\"\n  }\n}"
  },
  {
    "path": "MJExtensionDemo/Base.lproj/LaunchScreen.storyboard",
    "chars": 1658,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n<document type=\"com.apple.InterfaceBuilder3.CocoaTouch.Storyboard"
  },
  {
    "path": "MJExtensionDemo/Base.lproj/Main.storyboard",
    "chars": 3326,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<document type=\"com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB\" version=\"3"
  },
  {
    "path": "MJExtensionDemo/Info.plist",
    "chars": 595,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/P"
  },
  {
    "path": "MJExtensionTests/CoreDataModel/MJCoreDataTestModel.xcdatamodeld/MJCoreDataTestModel.xcdatamodel/contents",
    "chars": 879,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\n<model type=\"com.apple.IDECoreDataModeler.DataModel\" documentVer"
  },
  {
    "path": "MJExtensionTests/CoreDataModel/MJCoreDataTester+CoreDataClass.swift",
    "chars": 283,
    "preview": "//\n//  MJCoreDataTester+CoreDataClass.swift\n//  MJCoreDataTester\n//\n//  Created by Frank on 2021/9/8.\n//  Copyright © 20"
  },
  {
    "path": "MJExtensionTests/CoreDataModel/MJCoreDataTester+CoreDataProperties.swift",
    "chars": 559,
    "preview": "//\n//  MJCoreDataTester+CoreDataProperties.swift\n//  MJCoreDataTester\n//\n//  Created by Frank on 2021/9/8.\n//  Copyright"
  },
  {
    "path": "MJExtensionTests/CoreDataTests.swift",
    "chars": 2924,
    "preview": "//\n//  CoreDataTests.swift\n//  CoreDataTests\n//\n//  Created by Frank on 2021/9/8.\n//  Copyright © 2021 MJ Lee. All right"
  },
  {
    "path": "MJExtensionTests/Info.plist",
    "chars": 701,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/P"
  },
  {
    "path": "MJExtensionTests/MJExtensionTests-Bridging-Header.h",
    "chars": 166,
    "preview": "//\n//  Use this file to import your target's public headers that you would like to expose to Swift.\n//\n\n#import \"MJExten"
  },
  {
    "path": "MJExtensionTests/MJExtensionTests.m",
    "chars": 19305,
    "preview": "//\n//  MJExtensionTests.m\n//  MJExtensionTests\n//\n//  Created by Frank on 2019/3/25.\n//  Copyright © 2019 MJExtension. A"
  },
  {
    "path": "MJExtensionTests/Model/MJAd.h",
    "chars": 312,
    "preview": "//\n//  MJAd.h\n//  MJExtensionExample\n//\n//  Created by MJ Lee on 15/1/5.\n//  Copyright (c) 2015年 小码哥. All rights reserve"
  },
  {
    "path": "MJExtensionTests/Model/MJAd.m",
    "chars": 172,
    "preview": "//\n//  MJAd.m\n//  MJExtensionExample\n//\n//  Created by MJ Lee on 15/1/5.\n//  Copyright (c) 2015年 小码哥. All rights reserve"
  },
  {
    "path": "MJExtensionTests/Model/MJBag.h",
    "chars": 359,
    "preview": "//\n//  MJBag.h\n//  MJExtensionExample\n//\n//  Created by MJ Lee on 15/1/28.\n//  Copyright (c) 2015年 小码哥. All rights reser"
  },
  {
    "path": "MJExtensionTests/Model/MJBag.m",
    "chars": 338,
    "preview": "//\n//  MJBag.m\n//  MJExtensionExample\n//\n//  Created by MJ Lee on 15/1/28.\n//  Copyright (c) 2015年 小码哥. All rights reser"
  },
  {
    "path": "MJExtensionTests/Model/MJBaseObject.h",
    "chars": 255,
    "preview": "//\n//  MJBaseObject.h\n//  MJExtensionExample\n//\n//  Created by MJ Lee on 15/7/18.\n//  Copyright (c) 2015年 小码哥. All right"
  },
  {
    "path": "MJExtensionTests/Model/MJBaseObject.m",
    "chars": 197,
    "preview": "//\n//  MJBaseObject.m\n//  MJExtensionExample\n//\n//  Created by MJ Lee on 15/7/18.\n//  Copyright (c) 2015年 小码哥. All right"
  },
  {
    "path": "MJExtensionTests/Model/MJBook.h",
    "chars": 398,
    "preview": "//\n//  Book.h\n//  MJExtensionExample\n//\n//  Created by MJ Lee on 15/6/7.\n//  Copyright (c) 2015年 小码哥. All rights reserve"
  },
  {
    "path": "MJExtensionTests/Model/MJBook.m",
    "chars": 945,
    "preview": "//\n//  Book.m\n//  MJExtensionExample\n//\n//  Created by MJ Lee on 15/6/7.\n//  Copyright (c) 2015年 小码哥. All rights reserve"
  },
  {
    "path": "MJExtensionTests/Model/MJBox.h",
    "chars": 282,
    "preview": "//\n//  MJBox.h\n//  MJExtensionExample\n//\n//  Created by MJ Lee on 15/6/10.\n//  Copyright (c) 2015年 小码哥. All rights reser"
  },
  {
    "path": "MJExtensionTests/Model/MJBox.m",
    "chars": 229,
    "preview": "//\n//  MJBox.m\n//  MJExtensionExample\n//\n//  Created by MJ Lee on 15/6/10.\n//  Copyright (c) 2015年 小码哥. All rights reser"
  },
  {
    "path": "MJExtensionTests/Model/MJCat.h",
    "chars": 499,
    "preview": "//\n//  MJCat.h\n//  MJExtensionTests\n//\n//  Created by Frank on 2020/6/9.\n//  Copyright © 2020 MJ Lee. All rights reserve"
  },
  {
    "path": "MJExtensionTests/Model/MJCat.m",
    "chars": 278,
    "preview": "//\n//  MJCat.m\n//  MJExtensionTests\n//\n//  Created by Frank on 2020/6/9.\n//  Copyright © 2020 MJ Lee. All rights reserve"
  },
  {
    "path": "MJExtensionTests/Model/MJDog.h",
    "chars": 339,
    "preview": "//\n//  MJDog.h\n//  MJExtensionExample\n//\n//  Created by MJ Lee on 15/6/7.\n//  Copyright (c) 2015年 小码哥. All rights reserv"
  },
  {
    "path": "MJExtensionTests/Model/MJDog.m",
    "chars": 362,
    "preview": "//\n//  MJDog.m\n//  MJExtensionExample\n//\n//  Created by MJ Lee on 15/6/7.\n//  Copyright (c) 2015年 小码哥. All rights reserv"
  },
  {
    "path": "MJExtensionTests/Model/MJExtensionConfig.h",
    "chars": 222,
    "preview": "//\n//  MJExtensionConfig.h\n//  MJExtensionExample\n//\n//  Created by MJ Lee on 15/4/22.\n//  Copyright (c) 2015年 小码哥. All "
  },
  {
    "path": "MJExtensionTests/Model/MJExtensionConfig.m",
    "chars": 3182,
    "preview": "//\n//  MJExtensionConfig.m\n//  MJExtensionExample\n//\n//  Created by MJ Lee on 15/4/22.\n//  Copyright (c) 2015年 小码哥. All "
  },
  {
    "path": "MJExtensionTests/Model/MJFrenchUser.h",
    "chars": 242,
    "preview": "//\n//  MJFrenchUser.h\n//  MJExtensionTests\n//\n//  Created by Frank on 2019/9/26.\n//  Copyright © 2019 MJ Lee. All rights"
  },
  {
    "path": "MJExtensionTests/Model/MJFrenchUser.m",
    "chars": 290,
    "preview": "//\n//  MJFrenchUser.m\n//  MJExtensionTests\n//\n//  Created by Frank on 2019/9/26.\n//  Copyright © 2019 MJ Lee. All rights"
  },
  {
    "path": "MJExtensionTests/Model/MJPerson.h",
    "chars": 452,
    "preview": "//\n//  MJPerson.h\n//  MJExtensionTests\n//\n//  Created by MJ Lee on 2019/8/29.\n//  Copyright © 2019 MJExtension. All righ"
  },
  {
    "path": "MJExtensionTests/Model/MJPerson.m",
    "chars": 419,
    "preview": "//\n//  MJPerson.m\n//  MJExtensionTests\n//\n//  Created by MJ Lee on 2019/8/29.\n//  Copyright © 2019 MJExtension. All righ"
  },
  {
    "path": "MJExtensionTests/Model/MJStatus.h",
    "chars": 397,
    "preview": "//\n//  MJStatus.h\n//  字典与模型的互转\n//\n//  Created by MJ Lee on 14-5-21.\n//  Copyright (c) 2014年 小码哥. All rights reserved.\n//"
  },
  {
    "path": "MJExtensionTests/Model/MJStatus.m",
    "chars": 175,
    "preview": "//\n//  MJStatus.m\n//  字典与模型的互转\n//\n//  Created by MJ Lee on 14-5-21.\n//  Copyright (c) 2014年 小码哥. All rights reserved.\n//"
  },
  {
    "path": "MJExtensionTests/Model/MJStatusResult.h",
    "chars": 624,
    "preview": "//\n//  MJStatusResult.h\n//  字典与模型的互转\n//\n//  Created by MJ Lee on 14-5-21.\n//  Copyright (c) 2014年 小码哥. All rights reserv"
  },
  {
    "path": "MJExtensionTests/Model/MJStatusResult.m",
    "chars": 336,
    "preview": "//\n//  MJStatusResult.m\n//  字典与模型的互转\n//\n//  Created by MJ Lee on 14-5-21.\n//  Copyright (c) 2014年 小码哥. All rights reserv"
  },
  {
    "path": "MJExtensionTests/Model/MJStudent.h",
    "chars": 647,
    "preview": "//\n//  MJStudent.h\n//  MJExtensionExample\n//\n//  Created by MJ Lee on 15/1/5.\n//  Copyright (c) 2015年 小码哥. All rights re"
  },
  {
    "path": "MJExtensionTests/Model/MJStudent.m",
    "chars": 529,
    "preview": "//\n//  MJStudent.m\n//  MJExtensionExample\n//\n//  Created by MJ Lee on 15/1/5.\n//  Copyright (c) 2015年 小码哥. All rights re"
  },
  {
    "path": "MJExtensionTests/Model/MJUser.h",
    "chars": 1167,
    "preview": "//\n//  MJUser.h\n//  字典与模型的互转\n//\n//  Created by MJ Lee on 14-5-21.\n//  Copyright (c) 2014年 小码哥. All rights reserved.\n//  "
  },
  {
    "path": "MJExtensionTests/Model/MJUser.m",
    "chars": 299,
    "preview": "//\n//  MJUser.m\n//  字典与模型的互转\n//\n//  Created by MJ Lee on 14-5-21.\n//  Copyright (c) 2014年 小码哥. All rights reserved.\n//\n\n"
  },
  {
    "path": "MJExtensionTests/MultiThreadTests.swift",
    "chars": 3949,
    "preview": "//\n//  MultiThreadTests.swift\n//  MJExtensionTests\n//\n//  Created by Frank on 2021/3/10.\n//  Copyright © 2021 MJ Lee. Al"
  },
  {
    "path": "MJExtensionTests/PrefixHeader.pch",
    "chars": 241,
    "preview": "//\n//  PrefixHeader.pch\n//  MJExtensionTests\n//\n//  Created by MJ Lee on 2019/8/29.\n//  Copyright © 2019 MJ Lee. All rig"
  },
  {
    "path": "MJExtensionTests/SwiftModel/MJTester.swift",
    "chars": 471,
    "preview": "//\n//  MJTester.swift\n//  MJExtensionTests\n//\n//  Created by Frank on 2020/8/21.\n//  Copyright © 2020 MJ Lee. All rights"
  },
  {
    "path": "MJExtensionTests/SwiftModelTests.swift",
    "chars": 1650,
    "preview": "//\n//  SwiftModelTests.swift\n//  MJExtensionTests\n//\n//  Created by Frank on 2020/8/21.\n//  Copyright © 2020 MJ Lee. All"
  },
  {
    "path": "Package.swift",
    "chars": 856,
    "preview": "// swift-tools-version:5.3\n\nimport PackageDescription\n\nlet package = Package(\n    name: \"MJExtension\",\n    products: [\n "
  },
  {
    "path": "README.md",
    "chars": 18093,
    "preview": "MJExtension\n===\n[![SPM supported](https://img.shields.io/badge/SPM-supported-4BC51D.svg?style=flat)](https://github.com/"
  }
]

About this extraction

This page contains the full source code of the CoderMJLee/MJExtension GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 80 files (196.3 KB), approximately 58.1k tokens, and a symbol index with 8 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!