1. 程式人生 > >以最少的代碼讓自定的model實現NSCoding、NSCopying協議

以最少的代碼讓自定的model實現NSCoding、NSCopying協議

key bject 根據 方法 conf imp oid 自定義 code

項目中用到了自定義的model:Person(栗子)。此model需要可以實現歸檔的功能,但是屬性非常多,且類似的model很多。如果按照常規去寫歸檔的代碼,那麽無論是寫起來還是維護起來都非常困難。

由於model繼承自NSObject,所以給NSObject添加了擴展用來實現自定義model的歸檔功能。實現思路來源於偉大的網絡和MJExtention,所以應該不算是原創吧,反正這個實現也快爛大街了。

大致為:

1:獲取當前類及父類的class及屬性名稱和類型

2:實現歸檔常規方法

3:宏定義常規方法,方便實現

最主要的方法為第一步。

NSObject (Coding).m

- (void)coding_encode:(NSCoder *)aCoder {
    
//獲取當前類屬性名稱及值 [self enumPropertyList:^(id key, id value) { //歸檔常規方法 [aCoder encodeObject:value forKey:key]; }]; } - (nullable instancetype)coding_decode:(NSCoder *)aDecoder { [self enumPropertyList:^(id key, id value) { [self setValue:[aDecoder decodeObjectForKey:key] forKey:key]; }];
return self; } - (void)enumPropertyList:(void(^)(id key, id value))emunBlock { //獲取當前類及父類 [self enumClass:^(__unsafe_unretained Class cl, BOOL *stop) { //根據獲取的類的名稱得到所有屬性相關信息 [self propertyForClass:cl finish:^(PropertyModel *pModel) { NSString *attributeTypeString = pModel.propertyType;//
此處為了方便只獲取了屬性名稱 使用PropertyModel便於擴展 NSString *name = pModel.name; //判斷當前屬性是否支持coding協議 if ([attributeTypeString hasPrefix:@"@\""]) {//對象類型都是以@"開頭 attributeTypeString = [attributeTypeString substringWithRange:NSMakeRange(2, attributeTypeString.length - 3)]; Class attributeClass = NSClassFromString(attributeTypeString); BOOL isConformCoding = class_conformsToProtocol(attributeClass, NSProtocolFromString(@"NSCoding")); NSString *message = [NSString stringWithFormat:@"model:%@ 不支持NSCoding協議",attributeTypeString]; NSAssert(isConformCoding, message); } if (emunBlock) { emunBlock(name,[self valueForKey:name]); } }]; }]; }

獲取類名及屬性名稱

NSObject (Class).m

- (void)enumClass:(void(^)(Class cl, BOOL *stop))enumBlock {
    if (!enumBlock) {
        return;
    }
    BOOL sstop = NO;
    
    Class c = self.class;
    
    while (c && !sstop) {
        
        enumBlock(c,&sstop);
        
        c = class_getSuperclass(c);
        
        
        if (isClassForFoundatation(c)) {
            break ;
        }
    }
    
    
}

此處借鑒(copy)了MJExtention實現方式,包括判斷當前類是否屬於Foundataion類型的方法,只不過我使用了函數的方式表示,純粹是想嘗試一下不同的風格。

NSSet *foundationClasses(){
    return [NSSet setWithObjects:
            [NSURL class],
            [NSDate class],
            [NSValue class],
            [NSData class],
            [NSError class],
            [NSArray class],
            [NSDictionary class],
            [NSString class],
            [NSAttributedString class],
            nil];

}

BOOL isClassForFoundatation(Class class){
    __block BOOL result = NO;
    [foundationClasses() enumerateObjectsUsingBlock:^(id  _Nonnull obj, BOOL * _Nonnull stop) {
        
        if ([class isSubclassOfClass:obj] || (class == [NSObject class])) {
            result = YES;
            *stop = YES;
        }
    }];
    
    return result;
}

以下是根據class獲取屬性

- (void)propertyForClass:(Class)cl finish:(void(^)(PropertyModel *pModel))finish {
    if (!finish) {
        return;
    }
    
    unsigned int count;

    objc_property_t *properties = class_copyPropertyList(cl, &count);
    
    for (int i = 0; i < count; i++) {
        objc_property_t p = properties[i];
        NSString *name = @(property_getName(p));
        
        NSString *attribute = @(property_getAttributes(p));
        NSRange dotLocation = [attribute rangeOfString:@","];
        NSString *attributeTypeString ;
        if (dotLocation.location == NSNotFound) {
            attributeTypeString = [attribute substringFromIndex:1];
        }else{
            attributeTypeString = [attribute substringWithRange:NSMakeRange(1, dotLocation.location - 1)];
        }
        
        PropertyModel *model = [PropertyModel new];
        model.name = name;
        model.propertyType = attributeTypeString;
        
        finish(model);
    }
    free(properties);

}

其中PropertyModel是為了方便擴展,當前是需要屬性名稱及類型。

@interface PropertyModel : NSObject

@property (nonatomic, copy) NSString *name;

@property (nonatomic, copy) NSString *propertyType;

@end

到此,核心代碼已經完畢

在.h文件中聲明宏定義就可以

@interface NSObject (Coding)
- (void)coding_encode:(NSCoder *_Nonnull)aCoder ;
- (nullable instancetype)coding_decode:(NSCoder *_Nonnull)aDecoder ;
@end



#define CodingImplmentation - (void)encodeWithCoder:(NSCoder *)aCoder {     [self coding_encode:aCoder];    }  - (nullable instancetype)initWithCoder:(NSCoder *)aDecoder {     if (self == [super init]){         [self coding_decode:aDecoder];    }    return  self;}

#define DWObjectCodingImplmentation CodingImplmentation

在自定義的model:Person.m的文件中只添加宏定義就可以自動實現歸檔功能

@implementation Person

#pragma mark - 歸檔

DWObjectCodingImplmentation

PS:不要忘記在.h文件中寫上<NSCoding>

demo驗證了繼承自Person的model可以歸檔、model中屬性嵌套model也可以正常運行。

獻上github源碼,下載下來直接使用就可以。想了一下是否要支持cocoapod或者carthage,但是代碼這麽簡單,還是算了。

如果有任何不符合規範或者遺漏的地方,請各路大神指教。

github: https://github.com/DawnWdf/NSObjectExtention

PPS:另外代碼中有實現copying協議的方法,與coding類似,不贅述。可變對象的copy協議,由於但是沒什麽可用的地方所以註釋了。有興趣的可以自己嘗試。

還有字典轉換model的方法,純粹自娛自樂,請大家忽略。

以最少的代碼讓自定的model實現NSCoding、NSCopying協議