preface

YYKit written by Ibireme is a very rich and excellent framework, such as YYText, YYMode, YYCache, YYImage and other functions are very powerful

Through the study of excellent framework, can absorb some excellent development ideas or development means, can bring no small help to the future development

I have seen the introduction of YYMode, and compared with other model transformation frameworks, its transformation efficiency is very high, close to the efficiency of handwritten code, and the code function is more centralized, reducing a lot of unnecessary code, so those who have this need can try this framework

Here is the use of YYMode and source code analysis

The use of YYModel

The biggest use of YYMode is to convert JSON data into models, so what methods are usually used

Yy_modelWithJSON // Convert JSON string into model object yy_modelWithDictionary // Convert dictionary into model objectCopy the code

We can implement some custom functionality by simply adding the above and below methods to the.m implementation (or its parent) of the model

modelCustomPropertyMapper

Customize the mapping of the corresponding value of the attribute field. You can map the attribute field of the model to the corresponding keyword of the JSON field to be parsed, which can be one or more

Note that when defining a model of another type, such as the history added below, the dictionary attribute for this field is used. Parse into this class and return

Json: {"n":"Harry Pottery", "p": 256, "ext" : {"desc" : "A book written by J.K.rowling."}, "ID" : 100010, "history" : {"name" : "modern war history", "page" : 20}} model: @interface YYBook : NSObject @property NSString *name; @property NSInteger page; @property NSString *desc; @property NSString *bookID; @property YYHistory *history; // History books, Will automatically map the history under the key value corresponding dictionary contains content @ end @ implementation YYBook + (NSDictionary *) modelCustomPropertyMapper {return @ {@ "name" : @"bookID", @"page" : @"p", @"desc" : @"ext.desc", // parse to ext.desc, @"bookID": @[@"id", @"ID", @"book_id"]}; // Resolve the matched keys in order} @endCopy the code

modelContainerPropertyGenericClass

Customize the type of the mapped object in the collection. When the property is an attribute of the collection type, you can customize the type of the object in the collection returned after parsing

@class YYShadow, YYBorder, YYAttachment; @interface YYAttributes @property NSString *name; @property NSArray *shadows; @property NSSet *borders; @property NSDictionary *attachments; @end @implementation YYAttributes + (NSDictionary *)modelContainerPropertyGenericClass { return @{@"shadows" : [YYShadow class], @"borders" : YYBorder.class, @"attachments" : @"YYAttachment" }; } @endCopy the code

modelCustomClassForDictionary

Custom returns the class of the model according to a dictionary keyword, usually used to parse objects with parent-child relationships

// the author gave the example @class YYCircle, YYRectangle, YYLine; @implementation YYShape + (Class)modelCustomClassForDictionary:(NSDictionary*)dictionary { if (dictionary[@"radius"] ! = nil) { return [YYCircle class]; } else if (dictionary[@"width"] ! = nil) { return [YYRectangle class]; } else if (dictionary[@"y2"] ! = nil) { return [YYLine class]; } else { return [self class]; } } @endCopy the code

ModelPropertyBlacklist, modelPropertyWhitelist

Set the blacklist and whitelist

Blacklist: Does not resolve the property fields of a given collection

Whitelist: Directly washes the property fields of a given collection

Yy_modelToJSONData, yy_modelToJSONString

Convert objects to jSON-type binary data and JSON-type strings, respectively

Dictionaries and normal models are converted to JSON strings of dictionary type, and arrays are converted to JSON strings of array type

Summary of usage: can you customize another development language or use the JSON model of the scene based on the above customization features

YYModel source code analysis

Above know the dictionary to model some functions, in the source code to better analysis

YYClassInfo: class YYClassInfo: class YYClassInfo

YYClassInfo

This file declares the YYEncodingType enumeration, YYClassIvarInfo, YYClassMethodInfo, YYClassPropertyInfo, YYClassInfo

Methods and properties are loaded into the rW of the class using objc_init (before main)

YYEncodingType

Define the EncodingType Bool and readonly attribute type of the method

YYClassIvarInfo

Defines the basic data structure of field IVAR, including IVAR, name, type encoding, etc

@interface YYClassIvarInfo : NSObject
@property (nonatomic, assign, readonly) Ivar ivar;              ///< ivar opaque struct
@property (nonatomic, strong, readonly) NSString *name;         ///< Ivar's name
@property (nonatomic, assign, readonly) ptrdiff_t offset;       ///< Ivar's offset
@property (nonatomic, strong, readonly) NSString *typeEncoding; ///< Ivar's type encoding
@property (nonatomic, assign, readonly) YYEncodingType type;    ///< Ivar's type
Copy the code

YYClassMethodInfo

The basic data structure of Method is defined, including Method, name, SEL, IMP, etc

@interface YYClassMethodInfo : NSObject
@property (nonatomic, assign, readonly) Method method;                  ///< method opaque struct
@property (nonatomic, strong, readonly) NSString *name;                 ///< method name
@property (nonatomic, assign, readonly) SEL sel;                        ///< method's selector
@property (nonatomic, assign, readonly) IMP imp;                        ///< method's implementation
@property (nonatomic, strong, readonly) NSString *typeEncoding;         ///< method's parameter and return types
@property (nonatomic, strong, readonly) NSString *returnTypeEncoding;   ///< return value's type
@property (nullable, nonatomic, strong, readonly) NSArray<NSString *> *argumentTypeEncodings; ///< array of arguments' type
@end
Copy the code

YYClassPropertyInfo

Defines the basic data structure of attributes, including method property, name type, ivAR name of object, setter and getter, and so on

@interface YYClassPropertyInfo : NSObject
@property (nonatomic, assign, readonly) objc_property_t property; ///< property's opaque struct
@property (nonatomic, strong, readonly) NSString *name;           ///< property's name
@property (nonatomic, assign, readonly) YYEncodingType type;      ///< property's type
@property (nonatomic, strong, readonly) NSString *typeEncoding;   ///< property's encoding value
@property (nonatomic, strong, readonly) NSString *ivarName;       ///< property's ivar name
@property (nullable, nonatomic, assign, readonly) Class cls;      ///< may be nil
@property (nullable, nonatomic, strong, readonly) NSArray<NSString *> *protocols; ///< may nil
@property (nonatomic, assign, readonly) SEL getter;               ///< getter (nonnull)
@property (nonatomic, assign, readonly) SEL setter;               ///< setter (nonnull)
@end
Copy the code

YYClassInfo

Defines the basic data structure of the class, all the data structures defined before, are prepared for this class, including class class, superclass class, metaCls metaclass, IVAR field collection, method method collection, property attribute collection, etc

@interface YYClassInfo : NSObject @property (nonatomic, assign, readonly) Class cls; ///< class object @property (nullable, nonatomic, assign, readonly) Class superCls; ///< super class object @property (nullable, nonatomic, assign, readonly) Class metaCls; ///< class's meta class object @property (nonatomic, readonly) BOOL isMeta; ///< whether this class is meta class @property (nonatomic, strong, readonly) NSString *name; ///< class name @property (nullable, nonatomic, strong, readonly) YYClassInfo *superClassInfo; ///< super class's class info @property (nullable, nonatomic, strong, readonly) NSDictionary<NSString *, YYClassIvarInfo *> *ivarInfos; ///< ivars @property (nullable, nonatomic, strong, readonly) NSDictionary<NSString *, YYClassMethodInfo *> *methodInfos;  ///< methods @property (nullable, nonatomic, strong, readonly) NSDictionary<NSString *, YYClassPropertyInfo *> *propertyInfos; @endCopy the code

YYClassInfo in the main assignment logic in the update method, of course, also recursively obtain its parent class, because all attributes of the set and get methods must be object methods, so there is no need to obtain metaclass methods, source code as shown below

- (void)_update { _ivarInfos = nil; _methodInfos = nil; _propertyInfos = nil; Class cls = self.cls; unsigned int methodCount = 0; Method *methods = class_copyMethodList(CLS, &methodCount); if (methods) { NSMutableDictionary *methodInfos = [NSMutableDictionary new]; _methodInfos = methodInfos; for (unsigned int i = 0; i < methodCount; i++) { YYClassMethodInfo *info = [[YYClassMethodInfo alloc] initWithMethod:methods[i]]; if (info.name) methodInfos[info.name] = info; } free(methods); } unsigned int propertyCount = 0; Objc_property_t *properties = class_copyPropertyList(CLS, &propertyCount); if (properties) { NSMutableDictionary *propertyInfos = [NSMutableDictionary new]; _propertyInfos = propertyInfos; for (unsigned int i = 0; i < propertyCount; i++) { YYClassPropertyInfo *info = [[YYClassPropertyInfo alloc] initWithProperty:properties[i]]; if (info.name) propertyInfos[info.name] = info; } free(properties); } unsigned int ivarCount = 0; Ivar *ivars = class_copyIvarList(CLS, &ivarCount); if (ivars) { NSMutableDictionary *ivarInfos = [NSMutableDictionary new]; _ivarInfos = ivarInfos; for (unsigned int i = 0; i < ivarCount; i++) { YYClassIvarInfo *info = [[YYClassIvarInfo alloc] initWithIvar:ivars[i]]; if (info.name) ivarInfos[info.name] = info; } free(ivars); } if (! _ivarInfos) _ivarInfos = @{}; if (! _methodInfos) _methodInfos = @{}; if (! _propertyInfos) _propertyInfos = @{}; _needUpdate = NO; }Copy the code

NSObject+YYModel

This object is the core logical class of YYModel, which starts with the commonly used yy_modelWithDictionary method

Note: yy_modelWithJSON method is also the first json content into dictionary call yy_modelWithDictionary method, the source code is shown below

+ (instancetype)yy_modelWithJSON:(id)json {
    NSDictionary *dic = [self _yy_dictionaryWithJSON:json];
    return [self yy_modelWithDictionary:dic];
}
Copy the code

There are two main logic of yy_modelWithDictionary. One is that the metaWithClass method obtains all the information of this class and metaclase, and yy_modelSetWithDictionary assigns values to all the attributes of this object

+ (instancetype)yy_modelWithDictionary:(NSDictionary *)dictionary { if (! dictionary || dictionary == (id)kCFNull) return nil; if (! [dictionary isKindOfClass:[NSDictionary class]]) return nil; Class cls = [self class]; _YYModelMeta *modelMeta = [_YYModelMeta metaWithClass:cls]; if (modelMeta->_hasCustomClassFromDictionary) { cls = [cls modelCustomClassForDictionary:dictionary] ? : cls; } NSObject *one = [cls new]; if ([one yy_modelSetWithDictionary:dictionary]) return one; return nil; }Copy the code

metaWithClass

Cache is used to store information about the class. Lock is used to block multithreading to avoid abnormal writing and reading from the cache. Finally, _YYModelMata’s initWithClass method is called to initialize class information

+ (instancetype)metaWithClass:(Class)cls { if (! cls) return nil; static CFMutableDictionaryRef cache; static dispatch_once_t onceToken; static dispatch_semaphore_t lock; dispatch_once(&onceToken, ^{ cache = CFDictionaryCreateMutable(CFAllocatorGetDefault(), 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); lock = dispatch_semaphore_create(1); }); dispatch_semaphore_wait(lock, DISPATCH_TIME_FOREVER); _YYModelMeta *meta = CFDictionaryGetValue(cache, (__bridge const void *)(cls)); dispatch_semaphore_signal(lock); if (! meta || meta->_classInfo.needUpdate) { meta = [[_YYModelMeta alloc] initWithClass:cls]; if (meta) { dispatch_semaphore_wait(lock, DISPATCH_TIME_FOREVER); CFDictionarySetValue(cache, (__bridge const void *)(cls), (__bridge const void *)(meta)); dispatch_semaphore_signal(lock); } } return meta; }Copy the code

initWithClass

The initWithClass method of _YYModelMata initializes all information about the base class

- (instancetype)initWithClass:(Class)cls {
	//里面通过runtime获取了类的方法、属性、字段等信息
    YYClassInfo *classInfo = [YYClassInfo classInfoWithClass:cls];
    if (!classInfo) return nil;
    self = [super init];
    
    // Get black list 获取黑名单,内部属性不赋值,通过给类发送消息,即查看指定类方法是否存在
    NSSet *blacklist = nil;
    if ([cls respondsToSelector:@selector(modelPropertyBlacklist)]) {
        NSArray *properties = [(id<YYModel>)cls modelPropertyBlacklist];
        if (properties) {
            blacklist = [NSSet setWithArray:properties];
        }
    }
    
    // Get white list //白名单,只给内部属性赋值
    NSSet *whitelist = nil;
    if ([cls respondsToSelector:@selector(modelPropertyWhitelist)]) {
        NSArray *properties = [(id<YYModel>)cls modelPropertyWhitelist];
        if (properties) {
            whitelist = [NSSet setWithArray:properties];
        }
    }
    
    // Get container property's generic class 自定义集合内的对象类型,根据返回的字典,以key-value形式保存到字典中
    NSDictionary *genericMapper = nil;
    if ([cls respondsToSelector:@selector(modelContainerPropertyGenericClass)]) {
        genericMapper = [(id<YYModel>)cls modelContainerPropertyGenericClass];
        if (genericMapper) {
            NSMutableDictionary *tmp = [NSMutableDictionary new];
            [genericMapper enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) {
                if (![key isKindOfClass:[NSString class]]) return;
                Class meta = object_getClass(obj);
                if (!meta) return;
                if (class_isMetaClass(meta)) {
                    tmp[key] = obj;
                } else if ([obj isKindOfClass:[NSString class]]) {
                    Class cls = NSClassFromString(obj);
                    if (cls) {
                        tmp[key] = cls;
                    }
                }
            }];
            genericMapper = tmp;
        }
    }
    
    // Create all property metas.
    //遍历当前类和父类,获取所有的属性,并通过黑白名单筛选,优先黑名单
    NSMutableDictionary *allPropertyMetas = [NSMutableDictionary new];
    YYClassInfo *curClassInfo = classInfo;
    while (curClassInfo && curClassInfo.superCls != nil) { // recursive parse super class, but ignore root class (NSObject/NSProxy)
        for (YYClassPropertyInfo *propertyInfo in curClassInfo.propertyInfos.allValues) {
            if (!propertyInfo.name) continue;
            if (blacklist && [blacklist containsObject:propertyInfo.name]) continue;
            if (whitelist && ![whitelist containsObject:propertyInfo.name]) continue;
            _YYModelPropertyMeta *meta = [_YYModelPropertyMeta metaWithClassInfo:classInfo
                                                                    propertyInfo:propertyInfo
                                                                         generic:genericMapper[propertyInfo.name]];
            if (!meta || !meta->_name) continue;
            if (!meta->_getter || !meta->_setter) continue;
            if (allPropertyMetas[meta->_name]) continue;
            allPropertyMetas[meta->_name] = meta;
        }
        curClassInfo = curClassInfo.superClassInfo;
    }
    if (allPropertyMetas.count) _allPropertyMetas = allPropertyMetas.allValues.copy;
    
    // create mapper
	//根据设定好的映射关系,拿出保存下来
    NSMutableDictionary *mapper = [NSMutableDictionary new];
    NSMutableArray *keyPathPropertyMetas = [NSMutableArray new];
    NSMutableArray *multiKeysPropertyMetas = [NSMutableArray new];
    if ([cls respondsToSelector:@selector(modelCustomPropertyMapper)]) {
        NSDictionary *customMapper = [(id <YYModel>)cls modelCustomPropertyMapper];
        [customMapper enumerateKeysAndObjectsUsingBlock:^(NSString *propertyName, NSString *mappedToKey, BOOL *stop) {
            _YYModelPropertyMeta *propertyMeta = allPropertyMetas[propertyName];
            if (!propertyMeta) return;
            [allPropertyMetas removeObjectForKey:propertyName];
            
            if ([mappedToKey isKindOfClass:[NSString class]]) {
                if (mappedToKey.length == 0) return;
                
                propertyMeta->_mappedToKey = mappedToKey;
                NSArray *keyPath = [mappedToKey componentsSeparatedByString:@"."];
                for (NSString *onePath in keyPath) {
                    if (onePath.length == 0) {
                        NSMutableArray *tmp = keyPath.mutableCopy;
                        [tmp removeObject:@""];
                        keyPath = tmp;
                        break;
                    }
                }
                if (keyPath.count > 1) {
                    propertyMeta->_mappedToKeyPath = keyPath;
                    [keyPathPropertyMetas addObject:propertyMeta];
                }
                propertyMeta->_next = mapper[mappedToKey] ?: nil;
                mapper[mappedToKey] = propertyMeta;
                
            } else if ([mappedToKey isKindOfClass:[NSArray class]]) {
                
                NSMutableArray *mappedToKeyArray = [NSMutableArray new];
                for (NSString *oneKey in ((NSArray *)mappedToKey)) {
                    if (![oneKey isKindOfClass:[NSString class]]) continue;
                    if (oneKey.length == 0) continue;
                    
                    NSArray *keyPath = [oneKey componentsSeparatedByString:@"."];
                    if (keyPath.count > 1) {
                        [mappedToKeyArray addObject:keyPath];
                    } else {
                        [mappedToKeyArray addObject:oneKey];
                    }
                    
                    if (!propertyMeta->_mappedToKey) {
                        propertyMeta->_mappedToKey = oneKey;
                        propertyMeta->_mappedToKeyPath = keyPath.count > 1 ? keyPath : nil;
                    }
                }
                if (!propertyMeta->_mappedToKey) return;
                
                propertyMeta->_mappedToKeyArray = mappedToKeyArray;
                [multiKeysPropertyMetas addObject:propertyMeta];
                
                propertyMeta->_next = mapper[mappedToKey] ?: nil;
                mapper[mappedToKey] = propertyMeta;
            }
        }];
    }
    //更新映射的key
    [allPropertyMetas enumerateKeysAndObjectsUsingBlock:^(NSString *name, _YYModelPropertyMeta *propertyMeta, BOOL *stop) {
        propertyMeta->_mappedToKey = name;
        propertyMeta->_next = mapper[name] ?: nil;
        mapper[name] = propertyMeta;
    }];
    
    if (mapper.count) _mapper = mapper;
    if (keyPathPropertyMetas) _keyPathPropertyMetas = keyPathPropertyMetas;
    if (multiKeysPropertyMetas) _multiKeysPropertyMetas = multiKeysPropertyMetas;
    
    //保存类,保存是否实现了某个方法的结果,以便于优化速度
    _classInfo = classInfo;
    _keyMappedCount = _allPropertyMetas.count;
    _nsType = YYClassGetNSType(cls);
    _hasCustomWillTransformFromDictionary = ([cls instancesRespondToSelector:@selector(modelCustomWillTransformFromDictionary:)]);
    _hasCustomTransformFromDictionary = ([cls instancesRespondToSelector:@selector(modelCustomTransformFromDictionary:)]);
    _hasCustomTransformToDictionary = ([cls instancesRespondToSelector:@selector(modelCustomTransformToDictionary:)]);
    _hasCustomClassFromDictionary = ([cls respondsToSelector:@selector(modelCustomClassForDictionary:)]);
    
    return self;
}
Copy the code

So that’s the basic preparation, and then the assignment part

yy_modelSetWithDictionary

This method is called the assignment method, which assigns JSON data to the object

- (BOOL)yy_modelSetWithDictionary:(NSDictionary *)dic { if (! dic || dic == (id)kCFNull) return NO; if (! [dic isKindOfClass:[NSDictionary class]]) return NO; _YYModelMeta *modelMeta = [_YYModelMeta metaWithClass:object_getClass(self)]; if (modelMeta->_keyMappedCount == 0) return NO; / / if the custom transformation (modelMeta - > _hasCustomWillTransformFromDictionary) = {dic [((id < YYModel >) self) modelCustomWillTransformFromDictionary:dic]; if (! [dic isKindOfClass:[NSDictionary class]]) return NO; } ModelSetContext context = {0}; context.modelMeta = (__bridge void *)(modelMeta); context.model = (__bridge void *)(self); context.dictionary = (__bridge void *)(dic); // Determine which array to access for mapping assignment based on the number of mappings for a key value. CFArrayApplyFunction is traversed each round the back of the array and perform ModelSetWithPropertyMetaArrayFunction method if (modelMeta - > _keyMappedCount > = CFDictionaryGetCount((CFDictionaryRef)dic)) { CFDictionaryApplyFunction((CFDictionaryRef)dic, ModelSetWithDictionaryFunction, &context); if (modelMeta->_keyPathPropertyMetas) { CFArrayApplyFunction((CFArrayRef)modelMeta->_keyPathPropertyMetas, CFRangeMake(0, CFArrayGetCount((CFArrayRef)modelMeta->_keyPathPropertyMetas)), ModelSetWithPropertyMetaArrayFunction, &context); } if (modelMeta->_multiKeysPropertyMetas) { CFArrayApplyFunction((CFArrayRef)modelMeta->_multiKeysPropertyMetas, CFRangeMake(0, CFArrayGetCount((CFArrayRef)modelMeta->_multiKeysPropertyMetas)), ModelSetWithPropertyMetaArrayFunction, &context); } } else { CFArrayApplyFunction((CFArrayRef)modelMeta->_allPropertyMetas, CFRangeMake(0, modelMeta->_keyMappedCount), ModelSetWithPropertyMetaArrayFunction, &context); } if (modelMeta->_hasCustomTransformFromDictionary) { return [((id<YYModel>)self) modelCustomTransformFromDictionary:dic]; } return YES; }Copy the code

ModelSetWithPropertyMetaArrayFunction

So all of the methods for assigning properties are going to go here, so we’re going to take all of the values of multiple properties, and then we’re going to call in ModelSetValueForProperty and we’re going to call the method for assigning values, and we’re going to assign values if the value exists, which means if the value doesn’t exist, we’re not going to assign values

static void ModelSetWithPropertyMetaArrayFunction(const void *_propertyMeta, void *_context) { ModelSetContext *context = _context; __unsafe_unretained NSDictionary *dictionary = (__bridge NSDictionary *)(context->dictionary); __unsafe_unretained _YYModelPropertyMeta *propertyMeta = (__bridge _YYModelPropertyMeta *)(_propertyMeta); if (! propertyMeta->_setter) return; id value = nil; If (propertyMeta->_mappedToKeyArray) {value = YYValueForMultiKeys(dictionary, propertyMeta->_mappedToKeyArray); } else if (propertyMeta->_mappedToKeyPath) { value = YYValueForKeyPath(dictionary, propertyMeta->_mappedToKeyPath); } else { value = [dictionary objectForKey:propertyMeta->_mappedToKey]; } // If (value) {__unsafe_unretained id model = (__bridge id)(context->model); // If (value) {__unsafe_unretained id model = (__bridge id)(context->model); ModelSetValueForProperty(model, value, propertyMeta); }}Copy the code

ModelSetValueForProperty

Objc_msgSend sends a message based on the value type of the attribute. Here’s an example of NSString.

case YYEncodingTypeNSString: case YYEncodingTypeNSMutableString: { if ([value isKindOfClass:[NSString class]]) { if (meta->_nsType == YYEncodingTypeNSString) { ((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, value); } else { ((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, ((NSString *)value).mutableCopy); } } else if ([value isKindOfClass:[NSNumber class]]) { ((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, (meta->_nsType == YYEncodingTypeNSString) ? ((NSNumber *)value).stringValue : ((NSNumber *)value).stringValue.mutableCopy); } else if ([value isKindOfClass:[NSData class]]) { NSMutableString *string = [[NSMutableString alloc] initWithData:value  encoding:NSUTF8StringEncoding]; ((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, string); } else if ([value isKindOfClass:[NSURL class]]) { ((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, (meta->_nsType == YYEncodingTypeNSString) ? ((NSURL *)value).absoluteString : ((NSURL *)value).absoluteString.mutableCopy); } else if ([value isKindOfClass:[NSAttributedString class]]) { ((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, (meta->_nsType == YYEncodingTypeNSString) ? ((NSAttributedString *)value).string : ((NSAttributedString *)value).string.mutableCopy); } } break;Copy the code

YYModel exploration is over here, look carefully, the logic is not complex, you can understand, write a JSON to model type of other languages

For example, javascript’s data to objects could be implemented in the same way as here, except perhaps without these classes at runtime

Finally, I wish you all a happy 2021 New Year and wish the world an early end to the epidemic!