1. 程式人生 > >30分鐘擼出一個執行緒安全的YYModel

30分鐘擼出一個執行緒安全的YYModel

首圖震帖

震帖圖

前言

做iOS開發以來,從最開始沒有資料模型,所有資料都靠NSStringNSDictionaryNSArrray等系統基礎的物件儲存,到後來自己開始手動擼資料模型,再然後就開始接觸JSONModel,徹底脫離了枯燥的重複的動作,後來一些國產的一些優秀的資料模型庫也開始嶄露頭角,如MJExtension,如YYModel等。但別人的輪子始終是別人的,要是中途爆了胎還得去人家的店裡(Github)提出問題,等待修復,可是現實中大多數的時候時間都不允許我們這樣慢慢的等待,所以就有了這篇文章。

在這篇文章中,你可以瞭解到一些實用的Runtime技巧,一些面向物件的思想,最重要的是可以自己做出一個可以供自己擴充套件的資料模型輪子。輪子雖小但優點在於方便理解,擴充套件性強。

廢話不多說,直接進入正題。

一張圖說目標功能


功能圖解

想一想別人的輪子

要將資料模型的實現原理,先回想一下我們平時是怎麼用別人的資料模型的。

  • 首先我們需要根據服務端返回資料格式在我們一個對應的DataModel裡面將所有的引數名稱定義好,並且定義好對應的型別,如:

    @interface PersonDataModel : NSObject
    
    @property (nonatomic ,assign) NSUInteger age;
    @property (nonatomic ,copy  ) NSString *name;
    @property (nonatomic ,copy  ) NSString
    *sex; @end
  • 然後我們傳入一個NSString或者NSData之類的東西,總之最後我們將它轉化為NSDictionary,然後就有了我們需要的一個完整的資料模型。如JSONModel的使用方法:

    PersonDataModel *person = [[PersonDataModel alloc]initWithString:jsonString error:NULL];

所以就有了我們的設計思路

得出設計思路

  • 首先我們利用RuntimePersonDataModel中所有的有用資訊記錄到最重要的ClassPropertyInfo(在下面Lists中會講出有哪些需要記錄的資訊)。
  • 從而得到ClassInfo(這裡暫時用不到MethodInfoIvarInfo)。
  • 區分需要轉化的物件是NSDictionary還是NSArray
  • NSDictionary中的Key與我們剛才記錄在ClassPropertyInfo中的name進行對比。

    NSArray拆分成多個NSDictionary(或者String)做。

    暫時不支援NSArray中又是NSArray物件。

  • 將對比上的Key進行差異化賦值。

下面我們就來實現具體的步驟

Step

  • 獲取關鍵的ClassPropertyInfo資訊

    一條比較豐富的屬性長這樣:

    @property (nonatomic, strong ,setter=setGroup: ,getter=group) NSArray<Student> * group;

    可以看出這個地方對我們有用的有settergetterNSArrayStudentgroup,當然其中的nonatomicstrong也是一些有用的資訊,但我們目前姑且不談。

    關於property蘋果在<objc/runtime.h>中給了我們這些Api,如圖
    Runtime_Property

    其中name就可以通過下面這個Api得到是group

    /** 
    * Returns the name of a property.
    * 
    * @param property The property you want to inquire about.
    * 
    * @return A C string containing the property's name.
    */
    OBJC_EXPORT const char *property_getName(objc_property_t property) 
    OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0);

    其它的都可以在蘋果給我們的另外一個Api中全部獲取到

    /** 
     * Returns an array of property attributes for a property. 
     * 
     * @param property The property whose attributes you want copied.
     * @param outCount The number of attributes returned in the array.
     * 
     * @return An array of property attributes; must be free'd() by the caller. 
     */
    OBJC_EXPORT objc_property_attribute_t *property_copyAttributeList(objc_property_t property, unsigned int *outCount)
        OBJC_AVAILABLE(10.7, 4.3, 9.0, 1.0);

    而這個函式取出來的是一個關於objc_property_attribute_t的陣列,而objc_property_attribute_t是一個這樣的結構題:

    /// Defines a property attribute
    typedef struct {
        const char *name;           /**< The name of the attribute */
        const char *value;          /**< The value of the attribute (usually empty) */
    } objc_property_attribute_t;    

    這裡的這裡namevalue的定義可以參考:

    name包括N&WRGSVT

    這裡面的GS正好對應gettersetter,這兩個比較好理解,都是對應SEL的name,不過這個這個時候通過value取出來的是一個char型字串,這個要注意一下。比如getter就是"group"setter就是"setGroup:"

    T就稍稍複雜一點一些,這裡的T就是@\"NSArray<Student>"\(如果有兩個protocol則是@\"NSArray<Student><Student2>),我們可以將它分為三部分@NSArrayStudent。其中NSArray是這個屬性的ClassStudent是對應的protocols,因為protocols可能有多個,所以它是個陣列。同樣的它們也都是char型字串。

    最關鍵的是前面的@它代表這個property是個物件,具體這個char所對應的含義可以參考:
    其實在objc/runtime.h第1560行至1589行中也有對應的描述。我們將@這樣的字串單獨存入一個新定義的屬性type

    這裡有個Tip可以有效的將@\"NSArray<Student><Student2>分成NSArrayStudentStudent2這樣的陣列。

    NSString *type = @"@\"NSArray<Student><Student2>";
    NSMutableArray *values = [type componentsSeparatedByCharactersInSet:[NSCharacterSet characterSetWithCharactersInString:@"@\"<>,"]].mutableCopy;
    [values removeObject:@""];
    // 最終values = @[@"NSArray",@"Student",@"Student2"];

    到這裡關於一條Property最重要的一些資訊我們都得到了:

    • clsNSArray
    • namegroup
    • type@
    • getterSel@selector(group)
    • setterSel@selector(setGroup:)
    • protocolsStudent

    然後在補上一些能夠讓我們更方便使用的屬性,比如:

    • property:通過runtime取出的的property本身
    • isCustomPropetry:是否是系統類
    • isMutable:是否是系統類裡面的可變型別
    • superClsInfo:父類ClassInfo,如果父類為nil,則它也是nil
  • 獲取關鍵的ClassInfo資訊

    ClassInfo中對於本文的有用資訊不多,目前我們只取:

    • name:類名,如這裡的person
    • cls:類本身,如這裡的PersonDataModel
    • propetryInfos:參考第一步

      獲取關鍵的ClassPropertyInfo資訊

    • superClsInfo:父類的ClassInfo,可用一個遞迴方法實現。

      + (instancetype)classInfoWithClass:(Class)cls{
          if ( !cls ) return nil;
          ...
          if ( !classInfo ) {
              classInfo = [[DBClassInfo alloc] initWithClass:cls];
          }
          return classInfo;
      }
      
      - (instancetype)initWithClass:(Class)cls{
          if ( !cls ) return nil;
          self = [super init];
          if ( self ) {
              ...
              _superCls = class_getSuperclass(cls);
              _superClsInfo = [DBClassInfo classInfoWithClass:_superCls];
              ...
          }
          return self;
      }
      

      由於classInfoWithClass是個類方法,所以這一步一定要確保執行緒安全,具體方式可以見 Demo

  • 區分需要轉化的物件是NSDictionary還是NSArray

    • 一般入參有四種

      1. NSDate
      2. NSString
      3. NSArray
      4. NSDictionary
    • 這裡只詳細介紹NSDictionary的處理方式,因為無論是NSDate還是NSString我們最終都要轉化為NSDictionary或者NSArray,而NSArray通常情況下也是將起轉化為一個個NSDictionary的來進行相應的處理的。

      如果NSArray中是都是NSString那麼就不需要用到資料模型,如果NSArray中也是NSArray,本類暫不支援這樣的JSON格式。在本文也就不做講述

  • NSDictionary中的Key與我們剛才記錄在ClassPropertyInfo中的name進行對比。對比方式嘛就是輪詢。

    在這一步我們的目的是得到在我們DataModel中的每個ClassPropertyInfo對應的在NSDictionaryobject

    這句話讀起來可能比較繞口:所以我們舉個��:

    還是上文定義的PersonDataModel

    這個時候傳入的NSDictionary是

 { "name": "小明","age": 18,"sex": "男"}

那麼這個時候我們要找到的就是PersonDataModelnamesexClassPropertyInfo和它對應的Value

而在這個地方我們就可以做一些比較有意思的事情了,比如白名單黑名單過濾,比如屬性名稱的對映,而這些有意思的方法可以將它都歸為一個Option的協議,並將所有協議單獨歸類出一個檔案DBModelProtocol,這樣方便閱讀,也方便維護。

白名單黑名單比較好理解,就是在對應的Model裡面接受對應的名單實現是否對這個屬性進行賦值或者不賦值。具體使用類似實現以下兩個協議即可

+ (NSArray *)modelPropertyBlackList{
    return @[@"teacher",@"groupCount",@"groupArray"];
}

+ (NSArray *)modelPropertyWhiteList{
    return @[@"teacher",@"groupCount",@"groupArray"];
}

屬性名稱的對映其實就我常用的重新命名,比如伺服器返回了我們一個keyid,但id是一個隱藏的系統關鍵字,我們一個會將它重新命名為personId或者teacherId等更容易理解的屬性名稱

我們重新在PersonDataModel的基礎上定義一個TeacherDataModel的資料模型

@interface TeacherDataModel : PersonDataModel
@property (nonatomic, assign) NSUInteger teacherId;
@end

而服務端返回給我們資料模型卻是

{ "id": "110", "name": "黃衛民", "age": 38, "sex": "男"}

這個時候我們就可以在這一步進行一些差異化的對比了:

首先我們先實現協議:

+ (NSDictionary *)customKeyMapper{
    return @{@"id":@"teacherId"};
}

當我們輪詢到TeacherDataModelnameteacherIdClassPropertyInfo時取出的NSDictionary中對應keyidobject

  • 將對比上的nameDBClassPropertyInfo的和object進行差異化賦值。

    這一步是邏輯最簡單,但也是實現起來最繁瑣的一步。

    • DBClassPropertyInfo中的type可以讓我們知道這個property是什麼型別,上文有講述。
    • object轉化為對應的property的型別

      這一步我們新建一個新的檔案DBValueTransformer來幫我們做這些資料的處理,並且在這一步我們也可以插入我們的一個協議(NSString->NSDate)

    • 再利用objc_msgSend進行賦值

    進行到這已經完成對一個NSDictionary->DataModel的全過程。

小結

雖然本文只是講述了NSDictionary->DataModel的過程,沒有其他的Model功能那麼完善,如:

  • Model->Json
  • Model比較
  • 深拷貝

但我相信如果能看到這裡的同學對其他功能應該是已經可以手到擒來了。

做事之前先理清楚思路,功能點全部歸好類才能更好幫助我們完成它!

本文所有程式碼可以在這裡找到:Demo

參考

相關推薦

30分鐘一個執行安全YYModel

首圖震帖 前言 做iOS開發以來,從最開始沒有資料模型,所有資料都靠NSString,NSDictionary,NSArrray等系統基礎的物件儲存,到後來自己開始手動擼資料模型,再然後就開始接觸JSONModel,徹底脫離了枯燥的重複的動作,後來一

C++設計一個執行安全的懶漢單例模式

#incldue<iostream> #include<mutex> using namespace std; class CSingleton { public: static CSingleton* GetCSingleton() { if (_p ==

用c++11寫的一個執行安全的佇列

#ifndef __MSG__QUEUE__H__ #define __MSG__QUEUE__H__ #include <thread> #include <mutex>

C++11:基於std::queue和std::mutex構建一個執行安全的佇列

C++中的模板std::queue提供了一個佇列容器,但這個容器並不是執行緒安全的,如果在多執行緒環境下使用佇列,它是不能直接拿來用的。 基於它做一個執行緒安全的佇列也並不複雜。基本的原理就是用std::mutext訊號量對std::queue進行訪問控制,以

一個執行安全的計數器實現(java),可以讓一個變數每天從1開始遞增

前幾天工作中一段業務程式碼需要一個變數每天從1開始遞增。為此自己簡單的封裝了一個執行緒安全的計數器,可以讓一個變數每天從1開始遞增。當然了,如果專案在執行中發生重啟,即便日期還是當天,還是會從1開始重新計數。所以把計數器的值儲存在資料庫中會更靠譜,不過這不影響這

實現一個執行安全的單例模式

一、單例模式        單例模式也叫單件模式。Singleton是一個非常常用的設計模式,幾乎所有稍微大一些的程式都會使用它,所以構建一個高效的Singleton很重要。 1、單例類保證全域性只有一個唯一例項物件 2、單例類提供獲取這個唯一例項的介面。        

如何實現一個執行安全的map?

我們都知道,map是執行緒不安全的,那麼我們如何才能實現一個執行緒安全的map呢? 這裡介紹4種實現方式: 1、使用synchronized來進行約束: synchronized(obj){     value = map.get(key); } 2、使用JDK1.5版本

一個執行安全的單例模式

1. 錯誤的寫法:雖然用到了volatile,但是volatile只能保證可見性,並不保證原子性public class wrongsingleton { private static vol

一個執行安全的投票系統

有一個map,key儲存候選人名稱,value儲存該候選人的得票數。請實現一個執行緒安全的投票系統. 為了測試併發環境下的表現,

阿里面試官讓我實現一個執行安全並且可以設定過期時間的LRU快取,我蒙了!

目錄1. LRU 快取介紹2. ConcurrentLinkedQueue簡單介紹3. ReadWriteLock簡單介紹4.ScheduledExecutorService 簡單介紹5. 徒手擼一個執行緒安全的 LRU 快取5.1. 實現方法5.2. 原理5.3. put方法具體流程分析5.4. 原始碼6.

(轉)PHP執行安全與非執行安全的區別:如何選擇用哪一個

PHP執行緒安全與非執行緒安全的區別:如何選擇用哪一個? 很多時候,我們在做PHP環境配置的時候,很多人都是直接去亂下載PHP版本的,但是他不清楚:從2000年10月20日釋出的第一個Windows版的PHP3.0.17開始的都是執行緒安全的版本,直至5.2.1版本開始有Thread Safe

深入理解 GIL:如何寫高效能及執行安全的 Python 程式碼

6歲時,我有一個音樂盒。我上緊發條,音樂盒頂上的芭蕾舞女演員就會旋轉起來,同時,內部裝置發出“一閃一閃亮晶晶,滿天都是小星星”的叮鈴聲。那玩意兒肯定俗氣透了,但我喜歡那個音樂盒,我想知道它的工作原理是什麼。後來我拆開了,才看到它裡面一個簡單的裝置,機身內部鑲嵌著一個拇指大小的金

這裡實現一個基於陣列的執行安全的迴圈佇列

具體程式碼如下: #include<pthread.h> #include<iostream> using namespace std; #define QUEUESIZE 128 template<class object> cla

Request和Response的執行安全問題的另一個解決方案

Zigzag Chen的Swato框架提供了RequestAware和SessionAware介面,如果你的service需要訪問Request或Response物件,需要實現RequestAware或SessionAw

什麼叫做執行安全?看strtok函式接觸的一個名詞

比如一個 ArrayList 類,在新增一個元素的時候,它可能會有兩步來完成:1. 在 Items[Size] 的位置存放此元素;2. 增大 Size 的值。 在單執行緒執行的情況下,如果 Size = 0,新增一個元素後,此元素在位置 0,而且 Size=1; 而如

linux下的一個簡單執行安全記憶體池實現

這裡提供一個簡單執行緒安全記憶體池, 基於linux pthread 如下圖: 具體的資料結構: typedef struct LocMap{ char * point;

在 Java 的多執行中,如何去判斷給定的一個類是否是執行安全的(另外:synchronized 同步是否就一定能保證該類是執行安全的。)

同步程式碼塊和同步方法的區別:同步程式碼塊可以傳入任意物件,同步方法中 如果多個執行緒檢查的都是一個新的物件,不同的同步鎖對不同的執行緒不具有排他性,不能實現執行緒同步的效果,這時候執行緒同步就失效了。   兩者的區別主要體現在同步鎖上面。對於例項的同步方法,因為只能使用

如何寫執行安全的類和函式

       什麼是執行緒安全的類和函式,可以被多個執行緒呼叫而不會出現資料的錯亂的類和函式被叫做執行緒安全的類和函式,首先導致執行緒不安全的根本原因是我們函式中或著類中的共享成員變數(如類靜態成員變數,全域性變數),當我們的函式中或者類中有這些變數時他們都是非執行緒安全的

徒手一個類Flask微框架(二)路由及路由註冊的演變

pass nac 等價 themes 字典 lac found round wap 路由的基本概念:根據不同的訪問路徑調用不同的方法或者類from webob import Response,Request,dec from wsgiref.simple_server im

徒手一個類Flask微框架(三)根據業務進行路由分組

轉化 但是 根據 ask ice bsp rgb 註冊方法 127.0.0.1 所謂分組就是按照前綴分布映射如:/product/(\w+)/(?P<id>\d+ # 匹配/product/123123 的前綴比如什麽類別,類別下的什麽產品 等,