Runtime 筆記

runtime 介紹

Objective-C 語言將決定盡可能的從編譯和鏈接時推遲到運行時。只要有可能,Objective-C 總是使用動態 的方式來解決問題。這意味著 Objective-C 語言不僅需要一個編譯器,同時也需要一個運行時系統來執行 編譯好的代碼。這兒的運行時系統扮演的角色類似于 Objective-C 語言的操作系統,Objective-C 基于該系統來工作。

runtime 了解

方法調用的本質,就是讓對象發送消息。
objc_msgSend,只有對象才能發送消息,因此以objc開頭.
使用消息機制前提,必須導入#import <objc/runtime.h>

消息機制簡單使用
// 創建person對象
Person*p = [[Person alloc] init];
// 調用對象方法
[p eat];
// 本質:讓對象發送消息
objc_msgSend(p, @selector(eat));
// 調用類方法的方式:兩種
// 第一種通過類名調用
[Person eat];
// 第二種通過類對象調用
[[Person class]eat];
// 用類名調用類方法,底層會自動把類名轉換成類對象調用
// 本質:讓類對象發送消息
objc_msgSend([Personclass],@selector(eat));
三次拯救程序的機會
Method resolution
Fast forwarding
Normal forwarding

Method Resolution
首先,Objective-C 運行時會調用+resolveInstanceMethod:或者+resolveClassMethod:,讓你有機會提供一個函數實現。如果你添加了函數并返回 YES, 那運行時系統就會重新啟動一次消息發送的過程。還是以foo為例,你可以這么實現:

+ (BOOL)resolveInstanceMethod:(SEL)aSEL
{

if(aSEL ==@selector(foo:)){
class_addMethod([selfclass], aSEL, (IMP)fooMethod,"v@:");
returnYES;
}
return[superresolveInstanceMethod];
}

Core Data 有用到這個方法。NSManagedObjects 中 properties 的 getter 和 setter 就是在運行時動態添加的。
如果 resolve 方法返回 NO ,運行時就會移到下一步:消息轉發(Message Forwarding)。
PS:iOS 4.3 加入很多新的 runtime 方法,主要都是以 imp 為前綴的方法,比如imp_implementationWithBlock()用 block 快速創建一個 imp 。
上面的例子可以重寫成:

IMPfooIMP = imp_implementationWithBlock(^(id_self) {
NSLog(@"Doing foo");
});
class_addMethod([selfclass], aSEL, fooIMP,"v@:");

Fast forwarding
如果目標對象實現了-forwardingTargetForSelector:,Runtime 這時就會調用這個方法,給你把這個消息轉發給其他對象的機會。

- (id)forwardingTargetForSelector:(SEL)aSelector
{
if(aSelector ==@selector(foo:)){
returnalternateObject;
}
return[superforwardingTargetForSelector:aSelector];
}

只要這個方法返回的不是 nil 和 self,整個消息發送的過程就會被重啟,當然發送的對象會變成你返回的那個對象。否則,就會繼續 Normal Fowarding 。
這里叫 Fast ,只是為了區別下一步的轉發機制。因為這一步不會創建任何新的對象,但下一步轉發會創建一個 NSInvocation 對象,所以相對更快點。
****Normal forwarding****
這一步是 Runtime 最后一次給你挽救的機會。首先它會發送-methodSignatureForSelector:消息獲得函數的參數和返回值類型。如果-methodSignatureForSelector:返回 nil ,Runtime 則會發出-doesNotRecognizeSelector:消息,程序這時也就掛掉了。如果返回了一個函數簽名,Runtime 就會創建一個 NSInvocation 對象并發送-forwardInvocation:消息給目標對象。
NSInvocation 實際上就是對一個消息的描述,包括selector 以及參數等信息。所以你可以在-forwardInvocation:里修改傳進來的 NSInvocation 對象,然后發送-invokeWithTarget:消息給它,傳進去一個新的目標:

- (void)forwardInvocation:(NSInvocation*)invocation
{
SELsel = invocation.selector;
if([alternateObject respondsToSelector:sel]) {
[invocation invokeWithTarget:alternateObject];
}
else{
[selfdoesNotRecognizeSelector:sel];
}
}

Cocoa 里很多地方都利用到了消息傳遞機制來對語言進行擴展,如 Proxies、NSUndoManager 跟 Responder Chain。NSProxy 就是專門用來作為代理轉發消息的;NSUndoManager 截取一個消息之后再發送;而 Responder Chain 保證一個消息轉發給合適的響應者。
Objective-C 中給一個對象發送消息會經過以下幾個步驟:
在對象類的 dispatch table 中嘗試找到該消息。如果找到了,跳到相應的函數IMP去執行實現代碼;
如果沒有找到,Runtime 會發送+resolveInstanceMethod:或者+resolveClassMethod:嘗試去 resolve 這個消息;
如果 resolve 方法返回 NO,Runtime 就發送-forwardingTargetForSelector:允許你把這個消息轉發給另一個對象;
如果沒有新的目標對象返回, Runtime 就會發送-methodSignatureForSelector:和-forwardInvocation:消息。你可以發送-invokeWithTarget:消息來手動轉發消息或者發送-doesNotRecognizeSelector:拋出異常。
runtime 方法運用
person 類為例

@interfacePerson:NSObject
{
NSString*address;
}
@property(nonatomic,strong)NSString*name;
@property(nonatomic,assign)NSIntegerage;
//遍歷一個person類的所有的成員變量IvarList
- (void) getAllIvarList {
unsignedintmethodCount =0;
Ivar * ivars = class_copyIvarList([Personclass], &methodCount);
for(unsignedinti =0; i < methodCount; i ++) {
Ivar ivar = ivars[i];
constchar* name = ivar_getName(ivar);
constchar* type = ivar_getTypeEncoding(ivar);
NSLog(@"Person擁有的成員變量的類型為%s,名字為 %s ",type, name);
}
free(ivars);
}
2016-06-1520:26:39.412demo-Cocoa之method swizzle[17798:2565569] Person擁有的成員變量的類型為@"NSString",名字為 address
2016-06-1520:26:39.413demo-Cocoa之method swizzle[17798:2565569] Person擁有的成員變量的類型為@"NSString",名字為 _name
2016-06-1520:26:39.413demo-Cocoa之method swizzle[17798:2565569] Person擁有的成員變量的類型為q,名字為 _age
//遍歷獲取所有屬性Property
- (void) getAllProperty {
unsignedintpropertyCount =0;
objc_property_t*propertyList = class_copyPropertyList([Personclass], &propertyCount);
for(unsignedinti =0; i < propertyCount; i++ ) {
objc_property_t*thisProperty = propertyList[i];
constchar* propertyName = property_getName(*thisProperty);
NSLog(@"Person擁有的屬性為: '%s'", propertyName);
}
}
2016-06-15 20:25:19.653 demo-Cocoa之method swizzle[17778:2564081] Person擁有的屬性為: 'name'
2016-06-15 20:25:19.653 demo-Cocoa之method swizzle[17778:2564081] Person擁有的屬性為: 'age'

訪問私有變量
我們知道,OC中沒有真正意義上的私有變量和方法,要讓成員變量私有,要放在m文件中聲明,不對外暴露。如果我們知道這個成員變量的名稱,可以通過runtime獲取成員變量,再通過getIvar來獲取它的值。

Ivar ivar = class_getInstanceVariable([Model class],"_str1");
NSString * str1 = object_getIvar(model, ivar);

runtime學習階段 (可以通過這個例子實現 one-to-N的delegates)
1.調用一個函數,但是函數沒有實現

- (void)viewDidLoad {
[super viewDidLoad];
[self performSelector:@selector(doSomething)];
}

不出意外,程序崩潰

reason: '-[ViewController doSomething]: unrecognized selector sent to instance 0x7fe9f3736680'**
***** First throw call stack:**

2.實現+ (BOOL)resolveInstanceMethod:(SEL)sel,依舊崩潰
因為沒有找到doSomething這個方法,下面我們在里面實現+ (BOOL)resolveInstanceMethod:(SEL)sel這個方法,并且判斷如果SEL是doSomething那就輸出add method here

- (void)viewDidLoad {
[super viewDidLoad];
[self performSelector:@selector(doSomething)];
}
+ (BOOL)resolveInstanceMethod:(SEL)sel {
if(sel ==@selector(doSomething)) {
NSLog(@"add method here");
returnYES;
}
return[super resolveInstanceMethod:sel];
}

繼續運行, NSLog

**2015-12-24 10:47:24.687 RuntimeTest1[2007:382077] add method here**
**2015-12-24 10:47:24.687 RuntimeTest1[2007:382077] -[ViewController doSomething]: unrecognized selector sent to instance 0x7f9568c331f0**
**2015-12-24 10:47:24.690 RuntimeTest1[2007:382077] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[ViewController doSomething]: unrecognized selector sent to instance 0x7f9568c331f0'**
***** First throw call stack:**

3.通過class_addMethod添加方法
可以看到程序依然是崩潰了,但是我們可以看到輸出了add method here,這說明我們+ (BOOL)resolveInstanceMethod:(SEL)sel這個方法執行了,并進入了判斷,所以,在這兒,我們可以做一下操作,使這個方法得到響應,不至于走到最后
- (void)doesNotRecognizeSelector:(SEL)aSelector這個方法中而崩掉了,接下來,我們繼續操作,如下

- (void)viewDidLoad {
    [super viewDidLoad];
    [self performSelector:@selector(doSomething)];
}
+ (BOOL)resolveInstanceMethod:(SEL)sel {
    if(sel ==@selector(doSomething)) {
        NSLog(@"add method here");        
        class_addMethod([selfclass], sel, (IMP)dynamicMethodIMP,"v@:");
        returnYES;
    }
    return [superresolveInstanceMethod:sel];
}
void dynamicMethodIMP (idself, SEL _cmd) {
    NSLog(@"doSomething SEL");
}


導入了并且在+ (BOOL)resolveInstanceMethod:(SEL)sel中執行了class_addMethod這個方法,然后定義了一個void dynamicMethodIMP (id self, SEL _cmd)這個函數,運行工程,看log

**2015-12-2411:45:11.934RuntimeTest1[2284:478571] add method here**
**2015-12-2411:45:11.934RuntimeTest1[2284:478571] doSomething SEL**

這時候我們發現,程序并沒有崩潰,而且還輸出了doSomething SEL
這時候就說明我們已經通過runtime成功的向我們這個類中添加了一個方法。
關于class_addMethod這個方法的定義
OBJC_EXPORTBOOLclass_addMethod(Class cls, SEL name, IMP imp,constchar*types)
cls在這個類中添加方法,也就是需要添加方法的類
name方法名
imp本質上就是一個函數指針,指向方法的實現
types定義該數返回值類型和參數類型的字符串,這里比如"v@:",其中v就是void,帶表返回類型就是空,@代表參數,這里指的是id(self),這里:指的是方法SEL(_cmd),比如我再定義一個函數

int newMethod (idself, SEL _cmd,NSString*str) {
return100;
}

那么添加這個函數的方法就應該是
ass_addMethod([self class], @selector(newMethod), (IMP)newMethod, "i@:@");
4.如果在+ (BOOL)resolveInstanceMethod:(SEL)sel中沒有找到或者添加方法
那么,消息繼續往下傳遞到- (id)forwardingTargetForSelector:(SEL)aSelector
重新建個工程,然后增加一個叫SecondViewController的類,里面添加一個- (void)secondVCMethod方法,如下
在SecondViewController中

@implementationSecondViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
}
- (void)secondVCMethod {
NSLog(@"This is secondVC method !");
}

目錄結構:
在ViewController中

@implementationViewController
- (void)viewDidLoad {
[super viewDidLoad];
[self performSelector:@selector(secondVCMethod)];
}

運行結果,崩潰

reason: '-[ViewController secondVCMethod]: unrecognized selector sent to instance 0x7fc3a8535c10'**

5.在ViewController中實現 forwardingTargetForSelector:(SEL)aSelector 方法后,解決崩潰

@implementationViewController
- (void)viewDidLoad {
[super viewDidLoad];
[self performSelector:@selector(secondMethod)];
}
- (id)forwardingTargetForSelector:(SEL)aSelector {
//先得到第二個控制器的類
Class class =NSClassFromString(@"SecondViewController");
//創建新的控制器
UIViewController*VC = [classnew];
//如果方法名相同,就實現方法
if(aSelector ==NSSelectorFromString(@"secondMethod")) {
NSLog(@"secondMethod will do it");
returnVC;
}
returnnil;
}
+ (BOOL)resolveInstanceMethod:(SEL)sel{
return[superresolveInstanceMethod:sel];
}

運行結果

**2015-12-2414:00:34.168RuntimeTest2[3284:870957] secondVCdothis!**
**2015-12-2414:00:34.169RuntimeTest2[3284:870957] This is secondVC method !**

解釋:

- (id)forwardingTargetForSelector:(SEL)aSelector {
Class class =NSClassFromString(@"SecondViewController");
UIViewController*vc = class.new;
if(aSelector ==NSSelectorFromString(@"secondVCMethod")) {
NSLog(@"secondVC do this !");
returnvc;
}
returnnil;
}

在沒有找到- (void)secondVCMethod這個方法的時候,消息繼續傳遞,直到- (id)forwardingTargetForSelector:(SEL)aSelector,然后我在里面創建了一個SecondViewController的對象,并且判斷如果有這個方法,就返回SecondViewController的對象。這個函數就是消息的轉發,在這兒我們成功的把消息傳給了SecondViewController,然后讓它來執行,所以就執行了那個方法。同時,也相當于完成了一個多繼承!
可以通過message forwarding 去實現 one-to-N的delegates
runtime的使用場景
1.字典轉模型
篇幅一:

NSDictionary*dict =@{@"name":@"itcast",@"age":@18,@"height":@1.7};
unsignedintcount =0;
Ivar*Ivars =class_copyIvarList([CZPersonclass], &count);
CZPerson*person = [CZPersonnew];
for(inti =0; i < count; ++i) {
Ivarvar = Ivars[i];
constchar*str =ivar_getName(var);
NSMutableString*strName = [NSMutableStringstringWithString:[NSStringstringWithUTF8String:str]];
NSString*name =  [strNamesubstringFromIndex:1];
[personsetValue:dict[name]forKey:name];
}
NSLog(@"%@ %d %f",person.name,person.age,person.height);

篇幅二: 小馬哥
思路:利用運行時,遍歷模型中所有屬性,根據模型的屬性名,去字典中查找key,取出對應的值,給模型的屬性賦值。
步驟:提供一個NSObject分類,專門字典轉模型,以后所有模型都可以通過這個分類轉。

@implementationViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
// 解析Plist文件
NSString*filePath = [[NSBundlemainBundle] pathForResource:@"status.plist"ofType:nil];
NSDictionary*statusDict = [NSDictionarydictionaryWithContentsOfFile:filePath];
// 獲取字典數組
NSArray*dictArr = statusDict[@"statuses"];
// 自動生成模型的屬性字符串
//    [NSObject resolveDict:dictArr[0][@"user"]];
_statuses = [NSMutableArrayarray];
// 遍歷字典數組
for(NSDictionary*dictindictArr) {
Status *status = [Status modelWithDict:dict];
[_statuses addObject:status];
}
// 測試數據
NSLog(@"%@ %@",_statuses,[_statuses[0] user]);
}
@end
@implementationNSObject(Model)
+ (instancetype)modelWithDict:(NSDictionary*)dict
{
// 思路:遍歷模型中所有屬性-》使用運行時
// 0.創建對應的對象
idobjc = [[selfalloc] init];
// 1.利用runtime給對象中的成員屬性賦值
// class_copyIvarList:獲取類中的所有成員屬性
// Ivar:成員屬性的意思
// 第一個參數:表示獲取哪個類中的成員屬性
// 第二個參數:表示這個類有多少成員屬性,傳入一個Int變量地址,會自動給這個變量賦值
// 返回值Ivar *:指的是一個ivar數組,會把所有成員屬性放在一個數組中,通過返回的數組就能全部獲取到。
/* 類似下面這種寫法
Ivar ivar;
Ivar ivar1;
Ivar ivar2;
// 定義一個ivar的數組a
Ivar a[] = {ivar,ivar1,ivar2};
// 用一個Ivar *指針指向數組第一個元素
Ivar *ivarList = a;
// 根據指針訪問數組第一個元素
ivarList[0];
*/
unsignedintcount;
// 獲取類中的所有成員屬性
Ivar *ivarList = class_copyIvarList(self, &count);
for(inti =0; i < count; i++) {
// 根據角標,從數組取出對應的成員屬性
Ivar ivar = ivarList[i];
// 獲取成員屬性名
NSString*name = [NSStringstringWithUTF8String:ivar_getName(ivar)];
// 處理成員屬性名->字典中的key
// 從第一個角標開始截取
NSString*key = [name substringFromIndex:1];
// 根據成員屬性名去字典中查找對應的value
idvalue = dict[key];
// 二級轉換:如果字典中還有字典,也需要把對應的字典轉換成模型
// 判斷下value是否是字典
if([value isKindOfClass:[NSDictionaryclass]]) {
// 字典轉模型
// 獲取模型的類對象,調用modelWithDict
// 模型的類名已知,就是成員屬性的類型
// 獲取成員屬性類型
NSString*type = [NSStringstringWithUTF8String:ivar_getTypeEncoding(ivar)];
// 生成的是這種@"@\"User\"" 類型 -》 @"User"  在OC字符串中 \" -> ",\是轉義的意思,不占用字符
// 裁剪類型字符串
NSRangerange = [type rangeOfString:@"\""];
type = [type substringFromIndex:range.location+ range.length];
range = [type rangeOfString:@"\""];
// 裁剪到哪個角標,不包括當前角標
type = [type substringToIndex:range.location];
// 根據字符串類名生成類對象
Class modelClass =NSClassFromString(type);
if(modelClass) {// 有對應的模型才需要轉
// 把字典轉模型
value  =  [modelClass modelWithDict:value];
}
}

三級轉換:NSArray中也是字典,把數組中的字典轉換成模型.

// 判斷值是否是數組
if([value isKindOfClass:[NSArrayclass]]) {
// 判斷對應類有沒有實現字典數組轉模型數組的協議
if([selfrespondsToSelector:@selector(arrayContainModelClass)]) {
// 轉換成id類型,就能調用任何對象的方法
ididSelf =self;
// 獲取數組中字典對應的模型
NSString*type =  [idSelf arrayContainModelClass][key];
// 生成模型
Class classModel =NSClassFromString(type);
NSMutableArray*arrM = [NSMutableArrayarray];
// 遍歷字典數組,生成模型數組
for(NSDictionary*dictinvalue) {
// 字典轉模型
idmodel =  [classModel modelWithDict:dict];
[arrM addObject:model];
}
// 把模型數組賦值給value
value = arrM;
}
}
if(value) {// 有值,才需要給模型的屬性賦值
// 利用KVC給模型中的屬性賦值
[objc setValue:value forKey:key];
}
}
returnobjc;
}
@end

2.歸檔解檔
篇幅一:

//歸檔
- (void)encodeWithCoder:(NSCoder*)enCoder{
//取得所有成員變量名
NSArray*properNames = [[selfclass] propertyOfSelf];
for(NSString*propertyNameinproperNames) {
//創建指向get方法
SELgetSel =NSSelectorFromString(propertyName);
//對每一個屬性實現歸檔
[enCoderencodeObject:[selfperformSelector:getSel]forKey:propertyName];
}
}
//解檔
- (id)initWithCoder:(NSCoder*)aDecoder{
//取得所有成員變量名
NSArray*properNames = [[selfclass] propertyOfSelf];
for(NSString*propertyNameinproperNames) {
//創建指向屬性的set方法
// 1.獲取屬性名的第一個字符,變為大寫字母
NSString*firstCharater = [propertyNamesubstringToIndex:1].uppercaseString;
// 2.替換掉屬性名的第一個字符為大寫字符,并拼接出set方法的方法名
NSString*setPropertyName = [NSStringstringWithFormat:@"set%@%@:",firstCharater,[propertyNamesubstringFromIndex:1]];
SELsetSel =NSSelectorFromString(setPropertyName);
[selfperformSelector:setSelwithObject:[aDecoderdecodeObjectForKey:propertyName]];
}
returnself;
}

篇幅二:
原理描述:用runtime提供的函數遍歷Model自身所有屬性,并對屬性進行encode和decode操作。
核心方法:在Model的基類中重寫方法:

- (void)encodeWithCoder:(NSCoder*)aCoder {
unsignedintoutCount;
Ivar * ivars = class_copyIvarList([selfclass], &outCount);
for(inti =0; i < outCount; i ++) {
Ivar ivar = ivars[i];
NSString* key = [NSStringstringWithUTF8String:ivar_getName(ivar)];
[aCoder encodeObject:[selfvalueForKey:key] forKey:key];
}
}
- (id)initWithCoder:(NSCoder*)aDecoder {
if(self= [superinit]) {
unsignedintoutCount;
Ivar * ivars = class_copyIvarList([selfclass], &outCount);
for(inti =0; i < outCount; i ++) {
Ivar ivar = ivars[i];
NSString* key = [NSStringstringWithUTF8String:ivar_getName(ivar)];
[selfsetValue:[aDecoder decodeObjectForKey:key] forKey:key];
}
}
returnself;
}

3.runtime交換方法
開發使用場景:系統自帶的方法功能不夠,給系統自帶的方法擴展一些功能,并且保持原有的功能。
方式一:繼承系統的類,重寫方法.
方式二:使用runtime,交換方法.

@implementationViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
// 需求:給imageNamed方法提供功能,每次加載圖片就判斷下圖片是否加載成功。
// 步驟一:先搞個分類,定義一個能加載圖片并且能打印的方法+ (instancetype)imageWithName:(NSString *)name;
// 步驟二:交換imageNamed和imageWithName的實現,就能調用imageWithName,間接調用imageWithName的實現。
UIImage*image = [UIImageimageNamed:@"123"];
}
@end
@implementationUIImage(Image)
// 加載分類到內存的時候調用
+ (void)load
{
// 交換方法
// 獲取imageWithName方法地址
Method imageWithName = class_getClassMethod(self,@selector(imageWithName:));
// 獲取imageWithName方法地址
Method imageName = class_getClassMethod(self,@selector(imageNamed:));
// 交換方法地址,相當于交換實現方式
method_exchangeImplementations(imageWithName, imageName);
}
// 不能在分類中重寫系統方法imageNamed,因為會把系統的功能給覆蓋掉,而且分類中不能調用super.
// 既能加載圖片又能打印
+ (instancetype)imageWithName:(NSString*)name
{
// 這里調用imageWithName,相當于調用imageName
UIImage*image = [selfimageWithName:name];
if(image == nil) {
NSLog(@"加載空的圖片");
}
returnimage;
}
@end

4.動態添加方法 (可以用于 “代理”)
開發使用場景:如果一個類方法非常多,加載類到內存的時候也比較耗費資源,需要給每個方法生成映射表,可以使用動態給某個類,添加方法解決。
經典面試題:有沒有使用performSelector,其實主要想問你有沒有動態添加過方法。
簡單使用

@implementationViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
Person *p = [[Person alloc] init];
// 默認person,沒有實現eat方法,可以通過performSelector調用,但是會報錯。
// 動態添加方法就不會報錯
[p performSelector:@selector(eat)];
}
@end
@implementationPerson
// void(*)()
// 默認方法都有兩個隱式參數,
voideat(idself,SEL sel)
{
NSLog(@"%@ %@",self,NSStringFromSelector(sel));
}
// 當一個對象調用未實現的方法,會調用這個方法處理,并且會把對應的方法列表傳過來.
// 剛好可以用來判斷,未實現的方法是不是我們想要動態添加的方法
+ (BOOL)resolveInstanceMethod:(SEL)sel
{
if(sel ==@selector(eat)) {
// 動態添加eat方法
// 第一個參數:給哪個類添加方法
// 第二個參數:添加方法的方法編號
// 第三個參數:添加方法的函數實現(函數地址)
// 第四個參數:函數的類型,(返回值+參數類型) v:void @:對象->self :表示SEL->_cmd
class_addMethod(self,@selector(eat), eat,"v@:");
}
return[superresolveInstanceMethod:sel];
}
@end

5.動態添加屬性
原理:給一個類聲明屬性,其實本質就是給這個類添加關聯,并不是直接把這個值的內存空間添加到類存空間。

@implementationViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
// 給系統NSObject類動態添加屬性name
NSObject*objc = [[NSObjectalloc] init];
objc.name=@"小碼哥";
NSLog(@"%@",objc.name);
}
@end
// 定義關聯的key
staticconstchar*key ="name";
@implementationNSObject(Property)
- (NSString*)name
{
// 根據關聯的key,獲取關聯的值。
returnobjc_getAssociatedObject(self, key);
}
- (void)setName:(NSString*)name
{
// 第一個參數:給哪個對象添加關聯
// 第二個參數:關聯的key,通過這個key獲取
// 第三個參數:關聯的value
// 第四個參數:關聯的策略
objc_setAssociatedObject(self, key, name, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
@end

6.Json到Model的轉化
原理描述:用runtime提供的函數遍歷Model自身所有屬性,如果屬性在json中有對應的值,則將其賦值。
核心方法:在NSObject的分類中添加方法:

- (instancetype)initWithDict:(NSDictionary*)dict {
if(self= [selfinit]) {
//(1)獲取類的屬性及屬性對應的類型
NSMutableArray* keys = [NSMutableArrayarray];
NSMutableArray* attributes = [NSMutableArrayarray];
/*
* 例子
* name = value3 attribute = T@"NSString",C,N,V_value3
* name = value4 attribute = T^i,N,V_value4
*/
unsignedintoutCount;
objc_property_t * properties = class_copyPropertyList([selfclass], &outCount);
for(inti =0; i < outCount; i ++) {
objc_property_t property = properties[i];
//通過property_getName函數獲得屬性的名字
NSString* propertyName = [NSStringstringWithCString:property_getName(property) encoding:NSUTF8StringEncoding];
[keys addObject:propertyName];
//通過property_getAttributes函數可以獲得屬性的名字和@encode編碼
NSString* propertyAttribute = [NSStringstringWithCString:property_getAttributes(property) encoding:NSUTF8StringEncoding];
[attributes addObject:propertyAttribute];
}
//立即釋放properties指向的內存
free(properties);
//(2)根據類型給屬性賦值
for(NSString* keyinkeys) {
if([dict valueForKey:key] == nil)
continue;
[self set Value:[dict valueForKey:key] forKey:key];
}
}
return self;
}
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,182評論 6 543
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,489評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,290評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,776評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,510評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,866評論 1 328
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,860評論 3 447
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,036評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,585評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,331評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,536評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,058評論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,754評論 3 349
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,154評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,469評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,273評論 3 399
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,505評論 2 379

推薦閱讀更多精彩內容

  • 轉至元數據結尾創建: 董瀟偉,最新修改于: 十二月 23, 2016 轉至元數據起始第一章:isa和Class一....
    40c0490e5268閱讀 1,755評論 0 9
  • OC是一門動態語言,它將很多靜態語言在編譯和鏈接時期做的事情推遲到了運行時來處理,這就意味著它不僅需要一個編譯器,...
    zziazm閱讀 215評論 0 0
  • 這篇文章完全是基于南峰子老師博客的轉載 這篇文章完全是基于南峰子老師博客的轉載 這篇文章完全是基于南峰子老師博客的...
    西木閱讀 30,580評論 33 466
  • 我們常常會聽說 Objective-C 是一門動態語言,那么這個「動態」表現在哪呢?我想最主要的表現就是 Obje...
    Ethan_Struggle閱讀 2,229評論 0 7
  • 我們部門要招聘一個新人,掛和我一樣的title,分擔我的工作。 剛進公司的時候這個部門只有我一個人,靠著大小bos...
    綿綿piu閱讀 410評論 3 1