一、Runtime簡介 1.1 簡單介紹 Runtime簡稱運行時。OC就是運行時機制,也就是在運行時候的一些機制,其中最主要的是消息機制; 對於C語言,函數的調用在編譯的時候會決定調用哪個函數; 對於OC的函數,屬於動態調用的過程,在編譯的時候並不能決定真正調用哪個函數,只有在真正運行的時候才會根 ...
一、Runtime簡介
1.1 簡單介紹
- Runtime簡稱運行時。OC就是運行時機制,也就是在運行時候的一些機制,其中最主要的是消息機制;
- 對於C語言,函數的調用在編譯的時候會決定調用哪個函數;
- 對於OC的函數,屬於動態調用的過程,在編譯的時候並不能決定真正調用哪個函數,只有在真正運行的時候才會根據函數的名稱找到對應的函數來調用;
- 在編譯階段,OC可以調用任何函數,即使這個函數並未實現,只要聲明過就不會報錯;在編譯階段,C語言調用未實現的函數就會報錯。
1.2 可以用來做什麼
- 在程式運行的過程中,動態的創建一個類(比如KVO的底層實現)
- 在程式運行過程中,動態地為某個類添加屬性/方法,可以用於封裝框架(想怎麼改就怎麼改),這也是我們runtime機制的主要運用方向
- 遍歷一個類中所有的成員變數(屬性)/所有方法。比如字典轉模型:利用runtime遍歷模型對象的所有屬性,根據屬性名從字典中取出對應的值,設置到模型的屬性上;還有歸檔和解檔,利用runtime遍歷模型對象的所有屬性。
二、Runtime常用的方法
// 獲取類 Class PersonClass = object_getClass([Person class]); // SEL是selector在Objc中的標示 method 是方法名 SEL oriSEL = @selector(method); // 獲取類方法 Method oriMethod = class_getClassMethod(Class cls , SEL name); // 獲取實例方法 Method insMethod = class_getInstanceMethod(Class cls, SEL name); // 添加一個新方法 BOOL addSucc = class_addMethod(xiaomingClass, oriSEL, method_getImplementation(cusMethod), method_getTypeEncoding(cusMethod)); // 替換原方法實現 class_replaceMethod(toolClass, cusSEL, method_getImplementation(oriMethod), method_getTypeEncoding(oriMethod)); // 交換兩個方法 method_exchangeImplementations(oriMethod, cusMethod); // 獲取一個類的屬性列表(返回值是一個數組) objc_property_t *propertyList = class_copyPropertyList([self class], &count); // 獲取一個類的方法列表(返回值是一個數組) Method *methodList = class_copyMethodList([self class], &count); // 獲取一個類的成員變數列表 (返回值是一個數組) Ivar *ivarList = class_copyIvarList([self class], &count); // 獲取成員變數的名字 const char *ivar_getName(Ivar v) // 獲取成員變數的類型 const char *ivar_getTypeEndcoding(Ivar v) // 獲取一個類的協議列表(返回值是一個數組) __unsafe_unretained Protocol **protocolList = class_copyProtocolList([self class], &count); // set方法 //將值value 跟對象object 關聯起來(將值value 存儲到對象object 中) //參數 object:給哪個對象設置屬性 //參數 key:一個屬性對應一個Key,將來可以通過key取出這個存儲的值,key 可以是任何類型:double、int 等,建議用char 可以節省位元組 //參數 value:給屬性設置的值 //參數policy:存儲策略 (assign 、copy 、 retain就是strong) void objc_setAssociatedObject(id object , const void *key ,id value ,objc_AssociationPolicy policy) // get方法,利用參數key將對象object中存儲的對應值取出來 id objc_getAssociatedObject(id object , const void *key)
三、Runtime相關術語的數據結構
我感覺還是有必要看一下理論知識,如果不看跳過---
3.1 SEL
它是selector(方法選擇器)在Objc中的標示(Swift中是Selector類)。Objc在相同的類中不會有命名相同的兩個方法。selector對方法名進行包裝,以便找到對應的方法實現,它的數據結構:
// 它是個映射到方法的C語言字元串,可以通過Objc編譯器命令@selector 或者Runtime 系統的 sel_registerName 函數來獲取一個 SEL 類型的方法選擇器。
typedef struct objc_selector *SEL;
3.2 id
id 是一個參數類型,它是指向某個類的實例的指針。定義如下:
// 我們可以看到 objc_object 結構體包含一個isa指針,根據isa指針就可以找到對象所屬的類。
// isa 指針在代碼運行時並不總指向實例對象所屬的類型,所以不能依靠它來確定類型,要想確定類型還是需要用對象的 -class方法。
// KVO的實現原理就是將被觀察對象的isa指針指向了一個中間動態創建的中間類,而不是真實類型。
typedef struct objc_object *id; struct objc_object {Class isa;};
3.3 Class
typedef struct objc_class *Class;
Class其實是指向objc_class 結構體的指針。objc_class 的數據結構如下:
struct objc_class { Class isa OBJC_ISA_AVAILABILITY; #if !__OBJC2_ Class super_class// 父類 const char *name // 類名 long version // 類的版本信息,預設為0
long info // 類信息,供運行期間使用的一些位標識 long instance_size // 類的實例變數大小 struct objc_ivar_list *ivars // 累的成員變數鏈表 struct objc_method_list **methodLists // 方法鏈表 struct objc_cache *cache // 方法緩存 struct objc_protocol_list *protocols // 協議鏈表 #endif } OBJC2_UNAVAILABLE;
從 objc_class 可以看到,一個運行時類中關聯了它的父類指針、類名、成員變數、方法、緩存以及附屬的協議。
其中 objc_ivar_list 和 objc_method_list 分別是成員變數列表和方法列表:
// 成員變數列表 struct objc_ivar_list { int ivar_count OBJC2_UNAVAILABLE; #ifdef __LP64__ int space OBJC2_UNAVAILABLE; #endif /* variable length structure */ struct objc_ivar ivar_list[1] OBJC2_UNAVAILABLE; } OBJC2_UNAVAILABLE; // 方法列表 struct objc_method_list { struct objc_method_list *obsolete OBJC2_UNAVAILABLE; int method_count OBJC2_UNAVAILABLE; #ifdef __LP64__ int space OBJC2_UNAVAILABLE; #endif /* variable length structure */ struct objc_method method_list[1] OBJC2_UNAVAILABLE; }
由此可見,我們可以動態修改 *methodList 的值來添加成員方法,這也是 Category 實現的原理,同樣解釋了 Category 不能添加屬性的原因。
objc_ivar_list 結構體用來存儲成員變數的列表,而 objc_ivar 則是存儲了單個成員變數的信息;同理,objc_method_list 結構體存儲著方法數組的列表,而單個方法的信息則由 objc_method 結構體存儲。
值得註意的是,objc_class 中也有一個 isa 指針,這說明 Objc 類本身也是一個對象。為了處理類和對象的關係,Runtime庫創建了一種叫做 Meta Class(元類) 的東西,類對象所屬的類就叫做元類。元類表述了類對象本身所具備的元數據。
我們所熟悉的類方法,就源自於 Meta Class。我們可以理解為類方法就是類對象的實例方法。每個類僅有一個類對象,而每個類對象僅有一個與之相關的元類。
當你發出一個類似 [NSObject alloc] (類方法)的消息時,實際上,這個消息被髮送給了一個類對象,這個類對象必須是一個元類的實例,而這個元類同時也是一個根元類的實例,所有元類的isa指針最終都指向根元類。
所以當 [NSObject alloc] 這條消息發送給類對象的時候,運行時代碼 objc_msgSend() 會去它元類中查找能夠響應消息的方法實現,如果找到了,就會對這個類對象執行方法調用。
3.4 Method
Method 是一個方法結構體的指針:
typedef struct objc_method *Method; struct objc_method { SEL method_name OBJC2_UNAVAILABLE; char *method_types // 方法的返回值,和各個參數類型的字元串描述; IMP method_imp OBJC2_UNAVAILABLE; }
objc_method 存儲了方法名,方法類型和方法實現:
- 方法名類型為SEL
- 方法類型 method_types 是個char指針,存儲方法的參數類型和返回值類型
- method_imp 指向了方法的實現,本質是一個函數指針
3.5 Ivar
Ivar 是表示成員變數的類型。
typedef struct objc_ivar *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_offset是基地址偏移位元組
3.6 IMP
IMP在objc.h中的定義是:
typedef id (*IMP)(id, SEL,...);
它就是一個函數指針,這是由編譯器生成的。當你發起一個ObjC消息之後,最終它會執行的那段代碼就是由這個函數指針指定的。而 IMP 這個函數指針就指向了這個方法的實現。
如果得到了執行某個實例某個方法的入口,我們就可以繞開消息傳遞階段,直接執行方法,這在後面Cache中會提到。
你會發現 IMP 指向的方法與 objc_msgSend 函數類型相同,參數都包含 id 和 SEL 類型。每個方法對應一個 SEL 類型的方法選擇器,而每個實例對象中的SEL對應的方法實現肯定是唯一的,通過id和 SEL 參數就能確定唯一的方法實現地址,而一個確定的方法也只有唯一的一組 id 和 SEL 參數。
3.7 Cache
Cache 定義如下:
typedef struct objc_cache *Cache struct objc_cache { unsigned int mask /* total = mask + 1 */ OBJC2_UNAVAILABLE; unsigned int occupied OBJC2_UNAVAILABLE; Method buckets[1] OBJC2_UNAVAILABLE; };
Cache 為方法調用的性能進行優化,每當實例對象接收到一個消息時,它不會直接在 isa 指針指向的類的方法列表中遍歷查找到能夠響應的方法,因為每次都要查找效率太低了,而是優先在 Cache 中查找。
Runtime 系統會把被調用的方法存到 Cache 中,如果一個方法被調用,那麼它有可能今後還會被調用,下次查找的收就會效率更高。
四、消息發送和消息轉發
4.1 消息發送
消息發送舉例:
[person read:book];
會被編譯成:
objc_msgSend(person, @selector(read:), book);
objc_msgSend的具體流程如下:
- 通過 isa 指針找到所屬的類
- 查找類的 cache 列表,如果沒有則下一步
- 查找類的方法列表
- 如果能找到與選擇子名稱相符的方法,就跳至其實現代碼
- 找不到的話,就沿著繼承體系繼續向上查找
- 如果能夠找到與選擇子名稱相符的方法,就調至其實現代碼
- 如果還沒找到,就執行 “消息轉發”
4.2 消息轉發
這裡會給接收者最後一次機會把這個方法處理了,搞不定程式就會崩潰
- (void)forwardInvocation:(NSInvocation *)invocation // invocation : 封裝了與那條尚未處理的消息相關的所有細節的對象
在這裡能做的比較現實的事就是:在觸發消息前,先以某種方式改變消息內容,比如追加另外一個參數,或是改變消息等等。實現此方法時,如果發現某調用操作不應該由本類處理,可以調用超類的同名方法,則繼承體系中的每個類都有機會處理該請求,知道 NSObject ,如果NSObject 搞不定,則還會調用 doesNotRecognizeSelector: 來拋出異常,此時你就會在控制台看到熟悉的 unrecognized selector sent to instance..1
上面這4個方法均是模板方法,開發者可以override,由runtime來調用。最常見的實現消息轉發,就是重寫3和4。
五、Runtime的作用
5.1 發送消息
- 方法調用的本質,就是讓對象發送消息;
- objc_msgSend,只有對象才能發送消息,因此以objc開頭;
- 使用消息機制的前提,必須導入 #import<objc/message.h>
- 消息機制簡單使用
// 比如創建一個Person對象,我們通常會如下寫 Person *p = [[Person alloc] init]; // 其實方法的調用就是向這個對方發送消息,所以上面的代碼底層實現的主要代碼就是: // 由於iOS5以後,蘋果不支持這樣寫,所以需要在 Build Setting 中 將 Enable Strick Checking 設置為 NO Person *p = objc_msgSend(objc_msgSend(objc_getClass("Person"),sel_registerName("alloc")),sel_registerName("init"));
消息機制的原理:對象根據方法編號SEL去映射表查找對應的方法實現。
5.2 交換方法
簡單來說就是將兩個方法實現進行交換,比如系統自帶的方法功能不夠,我們想給系統自帶的方法擴展一些功能,並且保持原有的功能,我們就可以使用Runtie,交換方法。
比如:我們想在 NSMutableArray 添加新對象的時候,進行置空判斷。
// 需求:給 addObject: 方法提供功能,當添加一個新的對象時,進行為空判斷 // 1 先弄個分類 定義一個 能添加新對象又能進行為空判斷的方法 // 2 和系統的方法進行交換 #import <objc/message.h> @implementation NSMutableArray (Category) // 載入分類到記憶體的時候調用,會比main函數先調用 +(void)load{ // 交換方法 獲取類名,如果使用[self class] 獲取的是 NSMutableArray ,因為NSMutableArray 是類簇,這個可以看看瞭解一下 Class selfClass = NSClassFromString(@"__NSArrayM"); // 獲取系統 addObject: 方法地址 SEL sel_add = @selector(addObject:); Method addObject1 = class_getInstanceMethod(selfClass, sel_add); // 獲取自定義 safeAddObject: 方法地址 SEL sel_safeAdd = @selector(safeAddObject:); Method safeAddObject2 = class_getInstanceMethod(selfClass, sel_safeAdd); // 然後交換方法地址,相當於交換實現方式 method_exchangeImplementations(addObject1,safeAddObject2); } // 我們這裡定義一個新的添加對象的方法 , 不能在分類中重寫系統方法 addObject 這樣會覆蓋系統的addObject ,並且分類中也不能使用 super - (void)safeAddObject:(id)anObject{ // 我們可以加判斷,如果對象為空 if (!anObject) { NSLog(@"對象為空,添加新對象失敗"); return; } // 這裡調用 safeAddObject 其實是調用 addObject: 因為在load 方法中,系統的方法和你自己實現的方法已經交換了 [self safeAddObject:anObject]; }
5.3 動態添加方法
如果一個類方法非常多,載入類到記憶體的時候也比較耗費資源,需要給每個方法生成映射表,可以使用動態給某個類,添加方法解決。
NSMutableArray *mArr = [NSMutableArray array]; // WCE_addObject 方法只是在分類中聲明,但是並沒有實現 [mArr performSelector:@selector(WCE_addObject)]; // 現在我們在分類中預設去實現 / 當對象調用一個未實現的對象方法,會進入這個方法,並把對應的方法列表傳過來 +(BOOL)resolveInstanceMethod:(SEL)sel{ if (sel == @selector(WCE_addObject)) { // 動態添加 WCE_addObject 方法 // 第一個參數:給哪個類添加方法 // 第二個參數:添加方法的方法編號 // 第三個參數:添加方法的函數實現(函數地址) 這裡需要轉換一下 // 第四個參數:函數的類型,(返回值+參數類型) v:void @:對象-self SEL:_cmd class_addMethod(self, @selector(WCE_addObject), (void *)WCE_addObject, "v@:"); } return [super resolveInstanceMethod:sel]; } // 預設方法都有兩個隱式參數 調用方法者 調用的方法 void WCE_addObject(id self,SEL sel){ NSLog(@"這個方法被預設執行了"); }
5.4 給分類添加屬性
給一個類聲明屬性,其實本質就是給這個類添加關聯,並不是直接把這個值的記憶體空間添加到類存空間。
比如我們想給NSMutableArr類動態添加一個wce_name屬性:
// wce_name 是動態的給NSMutableArray 添加的屬性 NSMutableArray *mArr = [NSMutableArray array]; mArr.wce_name = @"這是我的東西哦"; NSLog(@"%@",mArr.wce_name); // 下麵是具體實現 // 定義關聯的key static const char *key = "wce_name"; @implementation NSMutableArray (Property) -(NSString *)wce_name{ // 根據關聯的key,獲取關聯的值 return objc_getAssociatedObject(self, key); } -(void)setWce_name:(NSString *)wce_name{ // 第一個參數:給哪個對象添加關聯 // 第二個參數:關聯的key,通過這個key獲取 // 第三個參數:關聯的value // 第四個參數:關聯的策略 retain copy strong objc_setAssociatedObject(self, key, wce_name, OBJC_ASSOCIATION_COPY_NONATOMIC); }
5.5 獲取對象的所有屬性
比如我們想把一個字典轉化成對象,可以使用KVC,setValuesForKeysWithDictionary: ,但是必須要保證:模型中的屬性和字典中key一一對應,如果不一致,就會報錯。
所以,我們可以使用Runtime,遍歷對象中的所有屬性,根據模型的屬性名,去字典中查找key,取出對應的值,給模型的屬性賦值。建一個NSObject分類,專門字典轉模型,以後所有的模型都可以通過這個分類轉。
// 字典轉model + (instancetype)WCE_objectWithKeyValues:(NSDictionary *)dict{ NSArray *propertyList = [self WCE_propertyList]; id object = [[self alloc] init]; for (NSString *key in propertyList) { if (dict[key]) { [object setValue:dict[key] forKey:key]; } } return object; } // 獲取所有的屬性列表 + (NSArray *)WCE_propertyList{ NSMutableArray *mArr = [NSMutableArray array]; // 獲取所有的屬性列表 unsigned int count = 0; // 獲取各種所有成員屬性,無論是屬性還是成員變數。私有的也可以,這裡假設Person只有三個屬性
// 如果只想獲取屬性可以用 class_copyPropertyList Ivar *ivarList = class_copyIvarList(self, &count); for (int i = 0; i < count; i++) { Ivar ivar = ivarList[i]; // 獲取成員屬性名 NSString *name = [NSString stringWithUTF8String:ivar_getName(ivar)]; // 因為獲取的是屬性,成員變數就是 _屬性名,這裡是去除下劃線 name = [name substringFromIndex:1]; // 添加到數組 [mArr addObject:name]; } return mArr; }
5.6 動態添加一個類
KVO(鍵值觀察)的實現就是利用的runtime能夠動態添加類
當你對一個對象進行觀察時,系統會自動新建一個類繼承自源類,然後重寫被觀察屬性的setter方法,然後重寫的setter方法會負責在調用原setter方法前後通知觀察者,然後把原對象的isa指針指向這個新類,我們知道,對象是通過isa指針去查找自己是屬於哪個類,並去所在類的方法列表中查找方法的,所以這個時候這個對象就自然地變成了新類的實例對象。
就像KVO一樣,系統是在程式運行的時候,根據你要監聽的類,動態添加一個新類繼承自該類,然後重寫原類的setter方法併在裡面通知observer的。
那麼,如何動態添加一個類呢?
// 創建一個類(size_t extraBytes該參數通常指定為0, 該參數是分配給類和元類對象尾部的索引ivars的位元組數。) Class clazz = objc_allocateClassPair([NSObject class], "GoodPerson", 0); // 添加ivar // @encode(aType) : 返回該類型的C字元串 class_addIvar(clazz, "_name", sizeof(NSString *), log2(sizeof(NSString *)), @encode(NSString *)); class_addIvar(clazz, "_age", sizeof(NSUInteger), log2(sizeof(NSUInteger)), @encode(NSUInteger)); // 註冊該類 objc_registerClassPair(clazz); // 創建實例對象 id object = [[clazz alloc] init]; // 設置ivar [object setValue:@"Tracy" forKey:@"name"]; Ivar ageIvar = class_getInstanceVariable(clazz, "_age"); object_setIvar(object, ageIvar, @18); // 列印對象的類和記憶體地址 NSLog(@"%@", object); // 列印對象的屬性值 NSLog(@"name = %@, age = %@", [object valueForKey:@"name"], object_getIvar(object, ageIvar)); // 當類或者它的子類的實例還存在,則不能調用objc_disposeClassPair方法 object = nil; // 銷毀類 objc_disposeClassPair(clazz);
六、項目中的具體使用
6.1 替換系統的方法
也就是將系統的方法和自己定義的方法替換,比如可變數組添加新對象、按標取值時的增加的防止崩潰的判斷,當我們接手一個新項目時,想更快瞭解界面跳轉,可以替換系統的viewWillAppear:方法。
6.2 實現分類也可以添加屬性
MJRefresh 就是動態的給UIScrollView增加的mj_header和mj_footer
6.3 字典和模型的自動轉換
其實主要的也是使用了,遍歷屬性這個方法。
6.4 動態的增加方法
動態的為某個類增加一個方法,可以對某一個功能,做擴展。
6.5 自動歸檔和自動解檔
我們假設有個Moviel類,有三個屬性 id name url ,通常在歸解檔的時候,我們都會
實現這兩個協議方法:
- (void)encodeWithCoder:(NSCoder *)aCoder { [aCoder encodeObject:_movieId forKey:@"id"]; [aCoder encodeObject:_movieName forKey:@"name"]; [aCoder encodeObject:_pic_url forKey:@"url"]; } - (id)initWithCoder:(NSCoder *)aDecoder { if (self = [super init]) { self.movieId = [aDecoder decodeObjectForKey:@"id"]; self.movieName = [aDecoder decodeObjectForKey:@"name"]; self.pic_url = [aDecoder decodeObjectForKey:@"url"]; } return self; } @end
如果這裡有100個屬性,那麼我們也只能把100個屬性都給寫一遍,
不過用runtime之後,就簡單了:
#import "Movie.h" #import <objc/runtime.h> @implementation Movie - (void)encodeWithCoder:(NSCoder *)encoder { unsigned int count = 0; Ivar *ivars = class_copyIvarList([Movie class], &count); for (int i = 0; i<count; i++) { // 取出i位置對應的成員變數 Ivar ivar = ivars[i]; // 查看成員變數 const char *name = ivar_getName(ivar); // 歸檔 NSString *key = [NSString stringWithUTF8String:name]; id value = [self valueForKey:key]; [encoder encodeObject:value forKey:key]; } free(ivars); } - (id)initWithCoder:(NSCoder *)decoder { if (self = [super init]) { unsigned int count = 0; Ivar *ivars = class_copyIvarList([Movie class], &count); for (int i = 0; i<count; i++) { // 取出i位置對應的成員變數 Ivar ivar = ivars[i]; // 查看成員變數 const char *name = ivar_getName(ivar); // 歸檔 NSString *key = [NSString stringWithUTF8String:name]; id value = [decoder decodeObjectForKey:key]; // 設置到成員變數身上 [self setValue:value forKey:key]; } free(ivars); } return self; } @end
如果還嫌麻煩,我們可以把encodeWithCoder 和 initWithCoder 這兩個方法抽成巨集
#import "Movie.h" #import <objc/runtime.h> #define encodeRuntime(A) \ \ unsigned int count = 0;\ Ivar *ivars = class_copyIvarList([A class], &count);\ for (int 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];\ [encoder encodeObject:value forKey:key];\ }\ free(ivars);\ \ #define initCoderRuntime(A) \ \ if (self = [super init]) {\ unsigned int count = 0;\ Ivar *ivars = class_copyIvarList([A class], &count);\ for (int i = 0; i<count; i++) {\ Ivar ivar = ivars[i];\ const char *name = ivar_getName(ivar);\ NSString *key = [NSString stringWithUTF8String:name];\ id value = [decoder decodeObjectForKey:key];\ [self setValue:value forKey:key];\ }\ free(ivars);\ }\ return self;\ \ @implementation Movie - (void)encodeWithCoder:(NSCoder *)encoder { encodeRuntime(Movie) } - (id)initWithCoder:(NSCoder *)decoder { initCoderRuntime(Movie) } @end
6.6 界面指定跳轉
在接收推送通知的時候,我們希望可以點擊這條通知跳轉到對應的頁面,簡單的方法就是,判斷,在判斷。但是我們可以利用runtime動態生成對象、屬性、方法這特性,我們可以先跟服務端商量好,定義跳轉規則,比如要跳轉到A控制器,需要傳屬性 id、type,那麼服務端返回字典給我,裡面有控制器名,兩個屬性名跟屬性值,客戶端就可以根據控制器名生成對象,再用kvc給對象賦值,這樣就搞定了。
比如,根據推送規則跳轉對應頁面 HSFeedsViewController
// 進入該頁面需要穿的屬性 @interface HSFeedsViewController : UIViewController // 註:根據下麵的兩個屬性,可以從伺服器獲取對應的頻道列表數據 /** 頻道ID */ @property (nonatomic, copy) NSString *ID; /** 頻道type */ @property (nonatomic, copy) NSString *type; @end
AppDelegate.m 中添加以下代碼片段:
// 這個規則肯定事先跟服務端溝通好,跳轉對應的界面需要對應的參數 NSDictionary *userInfo = @{ @"class": @"HSFeedsViewController", @"property": @{ @"ID": @"123", @"type": @"12" } };
接收到推送的消息後:
- (void)push:(NSDictionary *)params { // 類名 NSString *class =[NSString stringWithFormat:@"%@", params[@"class"]]; const char *className = [class cStringUsingEncoding:NSASCIIStringEncoding]; // 從一個字串返回一個類 Class newClass = objc_getClass(className); if (!newClass) { // 創建一個類 Class superClass = [NSObject class]; newClass = objc_allocateClassPair(superClass, className, 0); // 註冊你創建的這個類 objc_registerClassPair(newClass); } // 創建對象 id instance = [[newClass alloc] init]; // 對該對象賦值屬性 NSDictionary * propertys = params[@"property"]; [propertys enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) { // 檢測這個對象是否存在該屬性 if ([self checkIsExistPropertyWithInstance:instance verifyPropertyName:key]) { // 利用kvc賦值 [instance setValue:obj forKey:key]; } }]; // 獲取導航控制器 UITabBarController *tabVC = (UITabBarController *)self.window.rootViewController; UINavigationController *pushClassStance = (UINavigationController *)tabVC.viewControllers[tabVC.selectedIndex]; // 跳轉到對應的控制器 [pushClassStance pushViewController:instance animated:YES]; }
// 檢查對象是否存在該屬性:
- (BOOL)checkIsExistPropertyWithInstance:(id)instance verifyPropertyName:(NSString *)verifyPropertyName { unsigned int outCount, i; // 獲取對象里的屬性列表 objc_property_t * properties = class_copyPropertyList([instance class], &outCount); for (i = 0; i < outCount; i++) { objc_property_t property =properties[i]; // 屬性名轉成字元串 NSString *propertyName = [[NSString alloc] initWithCString:property_getName(property) encoding:NSUTF8StringEncoding]; // 判斷該屬性是否存在 if ([propertyName isEqualToString:verifyPropertyName]) { free(properties); return YES; } } free(properties); // 用copy的時候記得釋放 return NO; }
// 官方文檔翻譯
http://blog.csdn.net/coyote1994/article/details/52441513