Objective-C Runtime 一小時入門教程(下)

6.4 蒼老師的資料歸檔篇


蒼老師的資料總要整理一下吧!

創建People.h


#import <Foundation/Foundation.h>

 

@interface People : NSObject <NSCoding>

 

@property (nonatomic, copy) NSString *name; // 姓名

@property (nonatomic, strong) NSNumber *age; // 年齡

@property (nonatomic, copy) NSString *occupation; // 職業

@property (nonatomic, copy) NSString *nationality; // 國籍

 

@end


People.m


#import "People.h"

 

#if TARGET_IPHONE_SIMULATOR

#import <objc/objc-runtime.h>

#else

#import <objc/runtime.h>

#import <objc/message.h>

#endif

 

@implementation People

 

(void)encodeWithCoder:(NSCoder *)aCoder {

    unsigned int count = 0;

    Ivar *ivars = class_copyIvarList([People class], &count);

    for (NSUInteger i = 0; i < count; i ++) {

        Ivar ivar = ivars[i];

        const char *name = ivar_getName(ivar);

        NSString *key = [NSString stringWithUTF8String:name];

        id value = [self valueForKey:key];

        [aCoder encodeObject:value forKey:key];

    }

    free(ivars);

}

 

(id)initWithCoder:(NSCoder *)aDecoder {

    self = [super init];

    if (self) {

        unsigned int count = 0;

        Ivar *ivars = class_copyIvarList([People class], &count);

        for (NSUInteger i = 0; i < count; i ++) {

            Ivar ivar = ivars[i];

            const char *name = ivar_getName(ivar);

            NSString *key = [NSString stringWithUTF8String:name];

            id value = [aDecoder decodeObjectForKey:key];

            [self setValue:value forKey:key];

        }

        free(ivars);

    }

    return self;

}

 

@end


6.5 蒼老師的資料轉換篇


服務器返回了大量蒼老師的數據,手機端這邊接收後如何去轉換呢?當然是要將JSON轉換爲Model啦!

相信平時你們的項目中也用到過這些三方庫,下面我們去了解下runtime實現JSON和Model互轉。

創建People.h


#import <Foundation/Foundation.h>

 

@interface People : NSObject

 

@property (nonatomic, copy) NSString *name; // 姓名

@property (nonatomic, strong) NSNumber *age; // 年齡

@property (nonatomic, copy) NSString *occupation; // 職業

@property (nonatomic, copy) NSString *nationality; // 國籍

 

// 生成model

(instancetype)initWithDictionary:(NSDictionary *)dictionary;

 

// 轉換成字典

(NSDictionary *)covertToDictionary;

 

@end


People.m的代碼如下:


#import "People.h"

 

#if TARGET_IPHONE_SIMULATOR

#import <objc/objc-runtime.h>

#else

#import <objc/runtime.h>

#import <objc/message.h>

#endif

 

@implementation People

 

(instancetype)initWithDictionary:(NSDictionary *)dictionary

{

    self = [super init];

 

    if (self) {

        for (NSString *key in dictionary.allKeys) {

            id value = dictionary[key];

 

            SEL setter = [self propertySetterByKey:key];

            if (setter) {

                // 這裏還可以使用NSInvocation或者method_invoke,不再繼續深究了,有興趣google。

                ((void (*)(id, SEL, id))objc_msgSend)(self, setter, value);

            }

        }

    }

    return self;

}

 

(NSDictionary *)covertToDictionary

{

    unsigned int count = 0;

    objc_property_t *properties = class_copyPropertyList([self class], &count);

 

    if (count != 0) {

        NSMutableDictionary *resultDict = [<a href="http://www.jobbole.com/members/www821839432">@{}</amutableCopy];

 

        for (NSUInteger i = 0; i < count; i ++) {

            const void *propertyName = property_getName(properties[i]);

            NSString *name = [NSString stringWithUTF8String:propertyName];

 

            SEL getter = [self propertyGetterByKey:name];

            if (getter) {

                id value = ((id (*)(id, SEL))objc_msgSend)(self, getter);

                if (value) {

                    resultDict[name] = value;

                } else {

                    resultDict[name] = @"字典的key對應的value不能爲nil哦!";

                }

 

            }

        }

 

        free(properties);

 

        return resultDict;

    }

 

    free(properties);

 

    return nil;

}

 

#pragma mark - private methods

 

// 生成setter方法

(SEL)propertySetterByKey:(NSString *)key

{

    // 首字母大寫,你懂得

    NSString *propertySetterName = [NSString stringWithFormat:@"set%@:", key.capitalizedString];

 

    SEL setter = NSSelectorFromString(propertySetterName);

    if ([self respondsToSelector:setter]) {

        return setter;

    }

    return nil;

}

 

// 生成getter方法

(SEL)propertyGetterByKey:(NSString *)key

{

    SEL getter = NSSelectorFromString(key);

    if ([self respondsToSelector:getter]) {

        return getter;

    }

    return nil;

}

 

@end


main.m中運行以下代碼:


#import <Foundation/Foundation.h>

 

#if TARGET_IPHONE_SIMULATOR

#import <objc/objc-runtime.h>

#else

#import <objc/runtime.h>

#import <objc/message.h>

#endif

 

#import "People.h"

 

int main(int argc, const char * argv[]) {

    @autoreleasepool {

 

        NSDictionary *dict = @{

                               @"name" : @"蒼井空",

                               @"age"  : @18,

                               @"occupation" : @"老師",

                               @"nationality" : @"日本"

                               };

 

        // 字典轉模型

        People *cangTeacher = [[People alloc] initWithDictionary:dict];

        NSLog(@"熱烈歡迎,從%@遠道而來的%@歲的%@%@",cangTeacher.nationality,cangTeacher.age,cangTeacher.name,cangTeacher.occupation);

 

        // 模型轉字典

        NSDictionary *covertedDict = [cangTeacher covertToDictionary];

        NSLog(@"%@",covertedDict);

 

    }

    return 0;

}


最後輸出內容如下:




相信通過前面的學習,這些代碼不用寫過多的註釋你也可以看懂了,我把假設是網絡返回的蒼老師的資料轉化爲了model,然後又將model轉回字典。這是一個JSON轉Model相互轉換的一個思路,大家稍後運行Demo細細品味。


6.6 蒼老師的唱歌篇


這個實例主要是驗證一下上文《5.2 消息動態解析》


第一首:


添加sing實例方法,但是不提供方法的實現。驗證當找不到方法的實現時,動態添加方法。

創建People.h


#import <Foundation/Foundation.h>

 

@interface People : NSObject

 

@property (nonatomic, copy) NSString *name;

 

(void)sing;

 

@end


創建People.m


#import "People.h"

 

#if TARGET_IPHONE_SIMULATOR

#import <objc/objc-runtime.h>

#else

#import <objc/runtime.h>

#import <objc/message.h>

#endif

 

@implementation People

 

(BOOL)resolveInstanceMethod:(SEL)sel

{

    // 我們沒有給People類聲明sing方法,我們這裏動態添加方法

    if ([NSStringFromSelector(sel) isEqualToString:@"sing"]) {

        class_addMethod(self, sel, (IMP)otherSing, "v@:");

        return YES;

    }

    return [super resolveInstanceMethod:sel];

}

 

void otherSing(id self, SEL cmd)

{

    NSLog(@"%@ 唱歌啦!",((People *)self).name);

}


在main.m中運行以下代碼:


#import <Foundation/Foundation.h>

 

#if TARGET_IPHONE_SIMULATOR

#import <objc/objc-runtime.h>

#else

#import <objc/runtime.h>

#import <objc/message.h>

#endif

 

#import "People.h"

 

int main(int argc, const char * argv[]) {

    @autoreleasepool {

 

        People *cangTeacher = [[People alloc] init];

        cangTeacher.name = @"蒼老師";

        [cangTeacher sing];

 

    }

    return 0;

}


結果如下:



我們沒有提供蒼老師唱歌的方法實現,因此在調用此方法的時候,會調用resolveInstanceMethod方法,我們動態添加了方法。我們也可以返回No,繼續向下傳遞。(此處請返回《5.2 消息動態解析》第一步品味下)


第二首


外面的小鳥在唱歌,但是蒼老師的歌聲蓋過了小鳥,我們只能聽到蒼老師唱歌了。

這裏我們不聲明sing方法,將調用途中動態更換調用對象。

在第一首代碼的基礎上,創建Bird的model

Bird.h

#import  <Foundation/Foundation.h>

 

@interface Bird : NSObject

 

@property (nonatomic, copy) NSString *name;

 

@end


Bird.m


#import "Bird.h"

#import "People.h"

 

@implementation Bird

 

// 第一步:我們不動態添加方法,返回NO,進入第二步;

(BOOL)resolveInstanceMethod:(SEL)sel

{

    return NO;

}

 

// 第二部:我們不指定備選對象響應aSelector,進入第三步;

(id)forwardingTargetForSelector:(SEL)aSelector

{

    return nil;

}

 

// 第三步:返回方法選擇器,然後進入第四部;

(NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector

{

    if ([NSStringFromSelector(aSelector) isEqualToString:@"sing"]) {

        return [NSMethodSignature signatureWithObjCTypes:"v@:"];

    }

 

    return [super methodSignatureForSelector:aSelector];

}

 

// 第四部:這步我們修改調用對象

(void)forwardInvocation:(NSInvocation *)anInvocation

{

    // 我們改變調用對象爲People

    People *cangTeacher = [[People alloc] init];

    cangTeacher.name = @"蒼老師";

    [anInvocation invokeWithTarget:cangTeacher];

}

 

@end


main.m運行代碼如下:


#import <Foundation/Foundation.h>

 

#if TARGET_IPHONE_SIMULATOR

#import <objc/objc-runtime.h>

#else

#import <objc/runtime.h>

#import <objc/message.h>

#endif

 

#import "People.h"

#import "Bird.h"

 

int main(int argc, const char * argv[]) {

    @autoreleasepool {

 

        Bird *bird = [[Bird alloc] init];

        bird.name = @"小小鳥";

 

        ((void (*)(id, SEL))objc_msgSend)((id)bird, @selector(sing));

    }

    return 0;

}


運行結果如下:



成功更換了對象,把對象更換爲蒼老師了。(此處請返回《5.2 消息動態解析》品味)


第三首


蒼老師不想唱歌想跳舞了。

這裏我是實現不提供聲明,不修改調用對象,但是將sing方法修改爲dance方法。

創建People.h


#import <Foundation/Foundation.h>

 

@interface People : NSObject

 

@end


People.m


#import "People.h"

 

#if TARGET_IPHONE_SIMULATOR

#import <objc/objc-runtime.h>

#else

#import <objc/runtime.h>

#import <objc/message.h>

#endif

 

@implementation People

 

// 第一步:我們不動態添加方法,返回NO,進入第二步;

(BOOL)resolveInstanceMethod:(SEL)sel

{

    return NO;

}

 

// 第二部:我們不指定備選對象響應aSelector,進入第三步;

(id)forwardingTargetForSelector:(SEL)aSelector

{

    return nil;

}

 

// 第三步:返回方法選擇器,然後進入第四部;

(NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector

{

    if ([NSStringFromSelector(aSelector) isEqualToString:@"sing"]) {

        return [NSMethodSignature signatureWithObjCTypes:"v@:"];

    }

 

    return [super methodSignatureForSelector:aSelector];

}

 

// 第四部:這步我們修改調用方法

(void)forwardInvocation:(NSInvocation *)anInvocation

{

    [anInvocation setSelector:@selector(dance)];

    // 這還要指定是哪個對象的方法

    [anInvocation invokeWithTarget:self];

}

 

// 若forwardInvocation沒有實現,則會調用此方法

(void)doesNotRecognizeSelector:(SEL)aSelector

{

    NSLog(@"消息無法處理:%@", NSStringFromSelector(aSelector));

}

 

(void)dance

{

    NSLog(@"跳舞!!!come on!");

}

 

@end


在main.m中運行如下代碼:


#import <Foundation/Foundation.h>

 

#if TARGET_IPHONE_SIMULATOR

#import <objc/objc-runtime.h>

#else

#import <objc/runtime.h>

#import <objc/message.h>

#endif

 

#import "People.h"

 

int main(int argc, const char * argv[]) {

    @autoreleasepool {

 

        People *cangTeacher = [[People alloc] init];

 

        ((void(*)(id, SEL)) objc_msgSend)((id)cangTeacher, @selector(sing));

 

    }

    return 0;

}


結果如圖:


成功更換了方法,蒼老師由唱歌改爲跳舞了(此處請返回《5.2 消息動態解析》品味)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章