runtime之玩轉成員變數

来源:http://www.cnblogs.com/develop-SZT/archive/2016/04/02/5348364.html
-Advertisement-
Play Games

前言: 不鋪墊那麼多,單刀直入吧:runtime是一個C和彙編寫的動態庫,就像是一個小小的系統,將OC和C緊密關聯在一次,這個系統主要做兩件事情。 1,封裝C語言的結構體和函數,讓開發者在運行時創建,檢查或者修改類,對象和方法等2,傳遞消息,找出方法的最終執行代碼 也就是說我們寫的OC代碼在運行的時 ...


前言:

  不鋪墊那麼多,單刀直入吧:runtime是一個C和彙編寫的動態庫,就像是一個小小的系統,將OC和C緊密關聯在一次,這個系統主要做兩件事情。

1,封裝C語言的結構體和函數,讓開發者在運行時創建,檢查或者修改類,對象和方法等
2,傳遞消息,找出方法的最終執行代碼

也就是說我們寫的OC代碼在運行的時候都會轉為運行時代碼

通過runtime的學習能夠更好理解OC的這種消息發送機制,並且我也認為對runtime的學習是對深入學習iOS必不可少的坎,比如你有可能通過閱讀一些第三方框架來提高自己的編程技巧,在這些第三方框架中就會有大量運行時代碼。掌握了runtime我們能夠簡單做些什麼事情呢?

  1,遍歷對象的所有屬性
  2,動態添加/修改屬性,動態添加/修改/替換方法
  3,動態創建類/對象
  4,方法攔截使用(給方法添加一個動態實現,甚至可以講該方法重定向或者打包給lisi)

 聽起來跟黑魔法一樣。其實runtime就素有黑魔法之稱!我們就從成員變數開始我們對runtime的學習吧。

正文


 

成員變數:

  成員變數是我們在定義一個類中其中重要的成分,主要是想描述這個類實例化後具備了什麼屬性,特點,等等。就像定義了一個Person類,Person類具備了name,age,gender等各種屬性來描述這個類。舉了這個稍微符合的例子來輔助說明成員變數是幹嘛用的,但是卻還是不能說明成員變數到底本質是什麼?在runtime.h文件中的成員變數是一個指向objc_ivar類型的結構體指針:

1 /// An opaque type that represents an instance variable.
2 typedef struct objc_ivar *Ivar;

在這個objc_ivar這個結構體定義如下:

struct objc_ivar {
    char *ivar_name                                          OBJC2_UNAVAILABLE;//成員變數的名字
    char *ivar_type                                          OBJC2_UNAVAILABLE;//成員變數的類型
    int ivar_offset                                          OBJC2_UNAVAILABLE;
#ifdef __LP64__
    int space                                                OBJC2_UNAVAILABLE;
#endif
}      

對成員變數進行操作的主要有以下幾種方式:

Ivar *class_copyIvarList(Class cls, unsigned int *outCount)      //獲取所有成員變數
const char *ivar_getName(Ivar v)            //獲取某個成員變數的名字
const char *ivar_getTypeEncoding(Ivar v)   //獲取某個成員變數的類型編碼
Ivar class_getInstanceVariable(Class cls, const char *name)    //獲取某個類中指定名稱的成員變數
id object_getIvar(id obj, Ivar ivar)    //獲取某個對象中的某個成員變數的值
void object_setIvar(id obj, Ivar ivar, id value)    //設置某個對象的某個成員變數的值

下麵通過建立一個Person類來理解runtime中提供的這些函數,首先我們定義一個Person類,並且重寫它的description方法:

Person.h中:

@interface Person : NSObject
{
    NSString *clan;//族名
}
@property(nonatomic,copy)NSString *name;
@property(nonatomic,copy)NSString *gender;
@property(nonatomic,strong)NSNumber *age;
@property(nonatomic,assign)NSInteger height;
@property(nonatomic,assign)double weight;

Person.m

-(NSString *)description
{
    unsigned int outCount;
    Ivar *IvarArray = class_copyIvarList([Person class], &outCount);//獲取到Person中的所有成員變數
    for (unsigned int i = 0; i < outCount; i ++) {
        Ivar *ivar = &IvarArray[i];
        NSLog(@"第%d個成員變數:%s,類型是:%s",i,ivar_getName(*ivar),ivar_getTypeEncoding(*ivar));// 依次獲取每個成員變數並且列印成員變數名字和類型
    }
    return nil;
}

在程式入口創建Person實例類並且調用description方法可以看到列印台列印:


ivar_getTypeEncoding函數獲取到的是成員變數的類型編碼。類型編碼是蘋果對數據類型對象類型規定的另一個表現形式,比如"@"代表的是對象,":"表示的是SEL指針,"v"表示的是void。具體可以看蘋果官方文檔對類型編碼的具體規定:戳我!!!

通過runtime來給對象賦值和獲取對象的值:

Person.m中實現了兩個分別對實例化person對象賦值和取值方法:

 1 + (Person *)personWithName:(NSString *)name age:(NSNumber *)age gender:(NSString *)gender clan:(NSString *)clan
 2 {
 3     Person *p = [Person new];
 4     unsigned int outCount;
 5     Ivar *IvarArray = class_copyIvarList([Person class], &outCount);
 6     object_setIvar(p, IvarArray[0], clan);
 7     object_setIvar(p, IvarArray[1], name);
 8     object_setIvar(p, IvarArray[2], gender);
 9     object_setIvar(p, IvarArray[3], age);
10     return p;
11 }
12 
13 - (void)personGetPersonMessage
14 {
15     unsigned int outCount;
16     Ivar *IvarArray = class_copyIvarList([Person class], &outCount);
17     for (NSInteger i = 0; i < 4; i ++) {
18         NSLog(@"%s = %@",ivar_getName(IvarArray[i]),object_getIvar(self,IvarArray[i]));
19     }
20 }

在viewDidLoad中:

1 Person *person = [Person personWithName:@"張三" age:@26 gender:@"man" clan:@""];
2     [person personGetPersonMessage];

可以看到列印台列印:

成功的對Person對象進行設置值和取值操作。


 

屬性:

屬性在runtime中定義如下:

1 /// An opaque type that represents an Objective-C declared property.
2 typedef struct objc_property *objc_property_t;
3 /// Defines a property attribute
4 typedef struct {
5     const char *name;           /**< The name of the attribute */
6     const char *value;          /**< The value of the attribute (usually empty) */
7 } objc_property_attribute_t;

屬性的本質是一個指向objc_property的結構體指針。跟成員變數一樣,runtime中一樣為屬性定義了一系列對屬性的操作函數:

objc_property_t *class_copyPropertyList(Class cls, unsigned int *outCount)   //獲取所有屬性的列表
const char *property_getName(objc_property_t property) //獲取某個屬性的名字
const char *property_getAttributes(objc_property_t property)   //獲取屬性的特性描述
objc_property_attribute_t *property_copyAttributeList(objc_property_t property, unsigned int *outCount)   //獲取所有屬性的特性

獲取person實例對象中所有屬性的特性描述:

Person.m中:

1 - (void)getAttributeOfproperty
2 {
3     unsigned int outCount;
4     objc_property_t *propertyList = class_copyPropertyList([Person class], &outCount);
5     for (NSInteger i = 0; i < outCount; i ++) {
6         NSLog(@"屬性:%s,它的特性描述:%s",property_getName(propertyList[i]),property_getAttributes(propertyList[i]));
7     }
8 }

獲取屬性列表只會獲取有property屬性聲明的變數,所有當調用getAttributeOfproperty的時候列印台列印:

特性描述主要說明的是該屬性的修飾符,具體的代表意義如下:

1 屬性類型  name值:T  value:變化
2 編碼類型  name值:C(copy) &(strong) W(weak) 空(assign) 等 value:無
3 非/原子性 name值:空(atomic) N(Nonatomic)  value:無

在運行時runtime下我們可以獲取到所有的成員變數,以及類的私有變數。所有runtime的重要應用就是字典轉模型,複雜歸檔

應用1:複雜對象歸檔

複雜對象歸檔平常我們需要類遵循<NSCoding>協議,重寫協議中編碼和解碼的兩個方法,創建NSKeyarchive對象將類中的成員變數進行逐一編碼和解碼。

runtime下基本是同樣的操作,但是我們可以利用runtime提供的函數獲取變數的名字和所對應的成員變數,開啟迴圈進行快速歸檔(要記得普通情況下我們可以要逐一的寫),同樣是以Person類為例;

Person.m中:

-(instancetype)initWithCoder:(NSCoder *)aDecoder
{
    self = [super init];
    if (self) {
        unsigned int outCount;
        Ivar *ivarList = class_copyIvarList([Person class], &outCount);
        for (NSInteger i = 0; i < outCount; i ++) {
            Ivar ivar = ivarList[i];
            NSString *ivarName = [NSString
                                  stringWithUTF8String:ivar_getName(ivar)];
            [self setValue:[aDecoder decodeObjectForKey:ivarName] forKey:ivarName];
        }
    }
    return self;
}


-(void)encodeWithCoder:(NSCoder *)aCoder
{
    unsigned int outCount;
    Ivar *ivarlist = class_copyIvarList([self class], &outCount);
    for (NSInteger i = 0; i < outCount; i ++) {
        Ivar ivar = ivarlist[i];
        NSString *ivarName = [NSString stringWithUTF8String:ivar_getName(ivar)];
        [aCoder encodeObject:[self valueForKey:ivarName] forKey:ivarName];
    }
}

應用2字典轉模型:

另一個重要的應用便是字典轉模型,將字典中的數據賦值給模型中對應的屬性。大概思路是先通過class_copyPropertyList獲取到所有的屬性,再通過property_getName獲取到變數對應的名字作為key值,通過key值查看字典中是否有對應的value,若是有的話則給屬性賦值。

以上的操作都是基於對象具有的屬性通過runtime獲取屬性的一些信息,比如名字,屬性的值,屬性的特性描述等。通過runtime還可以給對象動態添加變數,也就是添加關聯。還記得分類和延展的區別嗎?延展可以為類添加屬性和方法,而分類只能為類添加方法。有個面試題:不使用繼承的方式如何給系統類添加一個公共變數?我們知道在延展裡面為類添加的變數是私有變數,外界無法訪問的。如果對runtime有瞭解的人也許就知道這是想考驗應聘人對runtime的瞭解。

runtime下提供了三個函數給我們能夠進行關聯對象的操作:

 1 void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)     //為某個類關聯某個對象
 2 id objc_getAssociatedObject(id object, const void *key)    //獲取到某個類的某個關聯對象
 3 void objc_removeAssociatedObjects(id object)          //移除已經關聯的對象
 4 參數說明:
 5 /**
 6  *  參數說明:
 7     object:要添加成員變數的對象
 8     key:添加成員變數對應的key值
 9     value:要添加的成員變數
10     policy:添加的成員變數的修飾符
11  */

我們以給NSDictionary添加一個NSString類型的公共變數funnyName為例:

在NSDictionary分類MyDict.h新增加兩個屬性其中一個字元串一個block中:

1 @property(nonatomic,copy)NSString *funnyName;
2 @property(nonatomic,copy)void(^dictAction)(NSString *str);

一般情況下如果我們只聲明瞭這些變數在外面使用的時候就會報錯,所有需要我們手動實現他們的set和get方法(可別以為是因為我們沒有實現它們的set和方法才報錯了哦,@property修飾的屬性可是會自動生成get和set方法)

那應該如何實現它們的set和get方法呢:

 1 -(void)setFunnyName:(NSString *)funnyName
 2 {
 3     objc_setAssociatedObject(self, @selector(funnyName), funnyName, OBJC_ASSOCIATION_COPY_NONATOMIC);
 4 }
 5 
 6 -(NSString *)funnyName
 7 {
 8     return objc_getAssociatedObject(self, @selector(funnyName));
 9 }
10 
11 -(void)setDictAction:(void (^)(NSString *))dictAction
12 {
13     objc_setAssociatedObject(self, @selector(dictAction), dictAction, OBJC_ASSOCIATION_COPY_NONATOMIC);
14 }
15 
16 -(void (^)(NSString *))dictAction
17 {
18     return objc_getAssociatedObject(self, @selector(dictAction));
19 }

在我們程式中就可以使用字典新增加的兩個屬性了:

 1 NSDictionary *dict = [NSDictionary new];
 2     dict.funnyName = @"SoFunny";
 3     NSLog(@"dict.funnyName = %@",dict.funnyName);
 4     void(^action)(NSString *str)  = ^(NSString *str){
 5         NSLog(@"列印了這個字元串:%@",str);
 6     };
 7     //設置block
 8     dict.dictAction = action;
 9     
10     //調用dict的action
11     dict.dictAction(@"新增加變數dicAction");

在列印台可以看見列印成功列印到我們想要的東西:


初嘗runtime,若是有什麼表述不當的地方還請指出。後續將繼續更新runtime的學習。

戳我看代碼


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 一、六中數據類型: 二、運算符和表達式 七種運算符 三、程式控制語句 註:do-while迴圈至少執行一次迴圈體;break語句可以跳出迴圈語句;continue語句可以跳過迴圈內剩餘的語句進入下一次迴圈;label語句用於為語句添加標號 ...
  • basket.js 源碼分析 一、前言 basket.js 可以用來載入js腳本並且保存到 LocalStorage 上,使我們可以更加精準地控制緩存,即使是在 http 緩存過期之後也可以使用。因此可以使我們防止不必要的重新請求 js 腳本,提升網站載入速度。 可以到 basket.js 的 "G ...
  • 【前面的話】DOM全稱是Document Object Model,即文檔對象模型。我們常說的html文檔其實就是一個DOM樹,DOM操作就是在記憶體中找到DOM樹上我們想要的DOM對象,對它的屬性進行修改,然後渲染引擎會把修改的結果重新繪製在界面上。DOM裡面涵蓋的知識點還是很多的,尤其是到後來要兼 ...
  • 因為自己在平時工作中,有些功能需要用到定時器,但是定時器並不像我們表邊上看到的那樣,所以這周末我看看書查查資料,深入研究了一下JavaScript中的定時器,那麼廢話不多說,下麵進入我們今天的正題。 大家都知道JavaScript是單線程的,所以不管是定時器還是用戶的操作都是需要線上程隊列中排隊執行 ...
  • PS快捷鍵大全(轉自UI中國PS教程) 擺脫滑鼠流就靠這張圖了!!!! 查看圖像 使用導航器查看圖像 選擇視窗--》導航器 菜單命令 使用縮放工具查看圖像 ctrl++ 以畫布大小放大圖像 ctrl + - 縮小 ctrl + 0 充滿整個畫面 使用抓手工具查看圖像 在使用除抓手工具時可以按住空格拖 ...
  • 本章講述在Android開發中,簡單的數據存儲。涉及知識主要是SharedPreferences,及多頁面切換ViewPager。 1.功能需求 做一個小應用、啟動的時候有左右引導圖、只有第一次啟動時顯示,看完以後,下一次啟動就不會再顯示了。 做兩個Activity展示引導,引導後進入主界面 點擊跳 ...
  • 在我們編碼的過程中,總會發現有一些重覆的編碼工作,可能你會不厭其煩的去copy and paste,但是你有去考慮過怎樣解決這樣的問題嗎?其實現在很多的編程IDE都能幫助我們減少一些編碼的任務,提高編碼效率。今天我們就來學習下如何在Xcode中定義一些常用的code snippets吧。 其實在編程 ...
  • 故事發生在這樣的情境上:給整個控制器添加了一個拖拽手勢,然後又在控制上的每個Cell上加了左滑清掃手勢,然後問題來了:只有拖拽手勢起作用,而左滑手勢沒有效果了,然後怎麼解決這個問題呢!先上圖: 當給整個控制器添加了拖拽手勢(UIPanGestureRecognizer),然後在控制器裡面的UITab ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...