@dynamic 模擬NSManagedObject類的內部實現,AFN的很是規用法

@property和@synthesize複習

@property生成setter和getter的聲明,同時生成屬性對應的成員變量,而且前面加一個下劃線_。若是將getter和setter的實現同時重寫以後,它不會幫助生成屬性對應的變量名。 (TestOne.m)這種寫法,沒法編譯經過,由於_name已經不存在。數據庫

@interface TestOne : NSObject

@property (nonatomic, copy) NSString *name;

@end


@implementation TestOne

- (void)setName:(NSString *)name {
    _name = name; // Use of undeclared identifier '_name'
}

- (NSString *)name {
    return _name; // Use of undeclared identifier '_name'
}

@end

比較特殊的一種狀況是readonly的屬性只會幫助生成getter的聲明和變量。,若是當getter被重寫以後,成員變量也不存在。編程

@interface TestTwo : NSObject

@property (readonly, nonatomic, copy) NSString *name;

@end


@implementation TestTwo

- (NSString *)name {
    return _name; // Use of undeclared identifier '_name'
}

@end

若是在.h使用@property聲明瞭方法屬性,又想在.m重寫方法怎麼辦呢。一般咱們會使用@synthesize.數組

@synthesize是按照系統默認規則幫助生成getter和setter的實現,同時生成一個緊跟在關鍵字@synthesize後面的屬性對應的成員變量,還能夠更改屬性對應的成員變量的名字。
同時若是使用了@synthesize以後,還能夠繼續重寫getter和setter的實現,同時它幫助生成的成員變量依然存在。下面的代碼是沒有任何問題的。app

@interface TestThree : NSObject

@property (nonatomic, copy) NSString *name;

@end

@implementation TestThree
@synthesize name = _name; // 使用 @synthesize name 生成的成員變量是'name'

- (void)setName:(NSString *)name {
    _name = name;
}

- (NSString *)name {
    return _name;
}

@end

這時候@property的做用僅僅是至關於對外聲明瞭方法原型:框架

- (void)setName:(NSString *)name;

- (NSString *)name;

可是不能夠將.h中的@property (nonatomic, copy) NSString *name;替換爲上面兩句方法聲明,由於@synthesize使用的前提是使用@property聲明過這個屬性。編程語言

對於readonly的屬性,一樣可使用@synthesize關鍵字找到屬性對應的成員變量名,而後重寫getter就沒有問題了。ide

@dynamic

咱們都知道對應@dynamic修飾的屬性,須要手動實現這個屬性的getter和setter,不然雖然編譯階段可以經過,可是運行時會形成崩潰,錯誤信息爲沒有指定的方法。編譯時沒問題,運行時才執行相應的方法,這就是所謂的動態綁定。
例以下面的代碼函數

@interface TestFour : NSObject

@property (nonatomic, copy) NSString *name;

@end

@implementation TestFour

@dynamic name;

@end

// 在main.m中
TestFour *testFour = [[TestFour alloc] init];
testFour.name = @"Mike"; //已經奔潰 [TestFour setName:]: unrecognized selector sent to instance
NSLog(@"%@", testFour.name);

這裏就體現出了@synthesize和@dynamic的區別:
@synthesize的語義是若是你沒有手動實現setter方法和getter方法,那麼編譯器會自動爲你加上這兩個方法的實現。
@dynamic告訴編譯器,屬性的setter與getter方法須要實現,可是不會自動幫助生成。(固然對於readonly的屬性只需提供getter實現便可)測試

@dynamic最經常使用的使用是在NSManagedObject中,此時不須要顯示編程setter和getter方法。緣由是:其getter和setter方法會在運行時動態建立,由Core Data框架爲此類屬性生成存取方法。那麼CoreData究竟如何幫助NSManagedObject的子類生成子類屬性的getter和setter實現的呢。咱們大致能夠模擬一下這個過程:編碼

模擬CoreData NSManagedObject類的底層實現

根據OC運行時的特性,當子類的方法沒有實現的實現,會去尋找父類的方法實現,爲了語義上更好理解我使用Person和它的父類Super用來測試:(這其中Person模擬的是NSManagedObject的子類)

@interface Person : Super
@property (nonatomic, copy) NSString *name;
@end

@implementation Person
@dynamic name;
@end

@interface Super : NSObject
@end

@implementation Super
- (void)setName:(NSString *)name {
    NSLog(@"執行了Super的setName:");
    // setName ....
}
- (NSString *)name {
    NSLog(@"執行了Super的name");
    return @"Mike在Super中設置";
}
@end

Person *person = [[Person alloc] init];
person.name = @"Mike";
NSLog(@"%@", person.name);

所以上面的程序執行結果爲:

執行了Super的setName:
執行了Super的name
Mike在Super中設置

那麼問題就來了NSManagedObject是如何截獲它子類的全部屬性的getter和setter方法的調用,並完成代碼實現的,畢竟它不會傻乎乎地把全部的屬性都寫一個getter和setter方法吧。雖然不知道它的具體實現方法,可是能夠模擬一下這個過程。

這裏提供一種利用消息轉發機制來實現,主要用到兩個方法- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector- (void)forwardInvocation:(NSInvocation *)anInvocation:

methodSignatureForSelector:方法用來返回參數指定的SEL的指定的方法簽名(方法簽名是各類編程語言中通用的概念,主要是對方法的返回值類型、參數類型和參數的個數的描述,函數重載的概念就是同名方法具備不一樣的方法簽名)。對於- (void)setName:(NSString *)name方法的方法簽名就是:返回值爲void類型參數個數爲1參數類型爲NSString。用天然語言描述是這樣,若是用OC的方法編碼描述就是:@"v@:@"(爲何這樣寫:可參考我以前關於Runtime的日誌).methodSignatureForSelector:調用的時機爲:對象進行任何方法調用都會通過這個方法的過濾。

要注意的是,若是經過經過調用這個方法以後沒有獲得參數Selector對應的方法簽名,那麼就會直接致使奔潰,錯誤爲:unrecognized selector sent to xxx。而若是找到了方法的簽名,則會繼續調用forwardInvocation:以求經過消息轉發的方式找到方法實現。

forwardInvocation:方法用來實現消息轉發,也就是在它的內部處理一些接收到的方法的實現細節,或者將實現的細節交給其餘對象。有關這個方法apple有一段長長的文檔,我簡單地翻譯了一下:

當一個對象發送了一個消息,它卻沒有相應的實現方法,runtime系統會給這個接受者一個機會來委派這個消息給另外一個接受者。它經過建立一個NSInvocation對象委派這個消息,這個對象表明着這個消息同時會發送給接受者一個包含着這個NSInvocation對象做爲參數的forwardInvocation:消息。而後,接收者的forwardInvocation:方法就能夠選擇轉發這個消息給另外一個對象。(若是那個對象也不對這個消息響應,它也會給一個機會轉發它)。
所以,forwardInvocation:方法容許一個對象爲某些消息創建與對這個對象有影響的其它對象的關係。好比,轉發對象能"繼承"一些它要轉發的對象的特性。
IMPORTANT
想要響應你的對象本身不能識別的方法,除了forwardInvocation:外,你還必須重寫methodSignatureForSelector:方法.消息轉發機制使用從methodSignatureForSelector:得到的信息來建立被轉發的NSInvocation對象。 重寫方法時,必須爲指定的selector提供一個合適的方法簽名,。要麼是經過預先格式化的簽名,要麼就從另外一個對象中獲取。

一個forwardInvocation:方法的實現包括兩項任務:
設置一個可以在anInvocation中響應消息編碼的對象,對全部的消息而言,這個對象沒必要相同。
用anInvocation對那個對象發送消息。anInvocation會持有結果值,runtime系統會把這個結果值取出並傳遞給最初的發送者。

這裏有一個-methodSignatureForSelector:-forwardInvocation:方法的使用示例
例如在Super類中的實現修改成:

@implementation Super
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
    NSString *selName = NSStringFromSelector(aSelector);
    if ([selName isEqualToString:@"tenAdd:"]) { // 過濾`tenAdd:`方法,指定它的方法簽名
        return [NSMethodSignature signatureWithObjCTypes:"i@:@"]; // 'i':int, '@:':OC方法,'@':對象類型
    } else {
        return [super methodSignatureForSelector:aSelector];
    }
    
}

- (void)forwardInvocation:(NSInvocation *)anInvocation {
    NSString *selName = NSStringFromSelector([anInvocation selector]);
    if ([selName isEqualToString:@"tenAdd:"]) { // 在這裏實現`tenAdd:`方法
        NSNumber *arg;
        [anInvocation getArgument:&arg atIndex:2]; // 0:self  1:_cmd  2:@(3)
        NSLog(@"%@", NSStringFromClass([[anInvocation target] class])); // Person
        int result = 10 + [arg intValue];
        [anInvocation setReturnValue:&result];
    } else {
        [super forwardInvocation:anInvocation];
    }
}
@end

而後進行以下的測試

Person *person = [[Person alloc] init];
// 在子類(Person類)中調用`tenAdd:`方法    
NSLog(@"%zd", [person performSelector:@selector(tenAdd:) withObject:@(3)]); // 13

在上面的Super實現中會接收到來自子類的對於各類方法的調用消息,使用methodSignatureForSelector:(SEL)aSelector獲取一個方法的簽名,若是發現方法名是@"tenAdd:"那麼它的簽名就是"i@:@",在方法forwardInvocation:中實現了@"tenAdd:"方法的內容用10和傳遞過來的參數相加,並將計算的結果做爲這個方法調用(invocation類型)的返回值。

理清這兩個方法的用法,能夠來改造Super類來模擬NSManagedObject類的實現了,使用靜態的可變數組tableData表明了數據庫表中已經存在的數據,columnNames表明全部的列名的集合,對應着每一個Person類的屬性名。經過person的id來獲取一條記錄所在的行,爲了方便,在本例中全部的行號都傳遞0。代碼以下:

@implementation Super

static NSMutableArray *tableData = nil;
static NSArray *columnNames = nil;

+ (void)initialize {
    [super initialize];
    
    tableData = [NSMutableArray array];
    [tableData addObject:[NSMutableDictionary dictionaryWithDictionary:@{@"name":@"Mike"}]];
    [tableData addObject:[NSMutableDictionary dictionaryWithDictionary:@{@"name":@"John"}]];
    
    columnNames = @[@"name"];
}

- (NSDictionary *)rowDataInTableWithRowId:(NSInteger)rowId {
    return tableData[rowId];
}

- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
    NSString *selName = NSStringFromSelector(aSelector);
    if ([selName rangeOfString:@"set"].location == 0) { // 處理setter
        return [NSMethodSignature signatureWithObjCTypes:"v@:@"];
    } else if ([columnNames containsObject:selName.description]){ // 處理getter
        return [NSMethodSignature signatureWithObjCTypes:"@@:"];
    } else {
        return [super methodSignatureForSelector:aSelector];
    }
}

- (void)forwardInvocation:(NSInvocation *)anInvocation {
    NSString *selName = NSStringFromSelector([anInvocation selector]);
    if ([selName rangeOfString:@"set"].location == 0) { // setter
        NSString *propertyName = [selName substringWithRange:NSMakeRange(3, [selName length]- 3)];
        propertyName = [propertyName stringByReplacingCharactersInRange:NSMakeRange(0,1) withString:[[propertyName substringToIndex:1] lowercaseString]];
        propertyName = [propertyName characterAtIndex:propertyName.length - 1] == ':' ? [propertyName substringToIndex:propertyName.length - 1] : propertyName;
        NSString *arg;
        [anInvocation getArgument:&arg atIndex:2];
        id obj = [self rowDataInTableWithRowId:0]; // 這裏始終傳入0,表明着表中的第一條數據。實際上rowId應該從[anInvocation target]中獲取, 也就是從Person對象中獲取。
        [obj setObject:arg forKey:propertyName];
    } else if ([columnNames containsObject:selName.description]){ // getter
        id obj = [self rowDataInTableWithRowId:0];
        id value = [obj objectForKey:selName.description];
        [anInvocation setReturnValue:&value]; // 設置返回值
    } else {
        [super forwardInvocation:anInvocation];
    }
}

@end

在main.m中進行測試,

Person *person = [[Person alloc] init];
person.name = @"修改後的Mike";

NSLog(@"%@", person.name);  // 修改後的Mike

正式如咱們指望的結果。這樣咱們沒有改變Person類的任何代碼,只是修改了它的父類,便將Person類全部屬性的getter和setter全都實現了。並且這是一個通用的代碼,即便再給Person類增長別的屬性,也沒有任何問題。

AFN一些@dynamic很是規代碼

最近看到了AFN中使用@dynamic的代碼,可是這些並非咱們經常使用的代碼。如在(2.x版本)AFHTTPRequestOperation.m中:

@interface AFURLConnectionOperation ()
@property (readwrite, nonatomic, strong) NSURLRequest *request;
@property (readwrite, nonatomic, strong) NSURLResponse *response;
@end

@interface AFHTTPRequestOperation ()
@property (readwrite, nonatomic, strong) NSHTTPURLResponse *response;
@property (readwrite, nonatomic, strong) id responseObject;
@property (readwrite, nonatomic, strong) NSError *responseSerializationError;
@property (readwrite, nonatomic, strong) NSRecursiveLock *lock;
@end

@implementation AFHTTPRequestOperation
@dynamic response;
@dynamic lock;
// ...
// ...
@end

AFHTTPRequestOperation原本就是AFURLConnectionOperation的子類,但是在這裏居然又爲它加了一個拓展,對於response和lock屬性原本也是從AFURLConnectionOperation繼承而來,但在子類中又使用了擴展從新定義了一下,而在AFURLConnectionOperation.m中他們也有一樣的定義:
在.h中
@interface AFURLConnectionOperation
@property (readonly, nonatomic, strong) NSURLResponse *response;
// ...
@end

在AFURLConnectionOperation.m中AFURLConnectionOperation的擴展中
@interface AFURLConnectionOperation ()
@property (readwrite, nonatomic, strong) NSRecursiveLock lock;
@property (readwrite, nonatomic, strong) NSURLResponse
response;
// ...
@end

在AFHTTPRequestOperation.h中
@interface AFURLConnectionOperation : AFURLConnectionOperation
@property (readonly, nonatomic, strong) NSHTTPURLResponse *response;
// ...
@end

對此我作了以下分析,(AFURLConnectionOperation簡稱爲URLCO, AFHTTPRequestOperation簡稱爲HTTPRO)

首先爲何URLCO中已經定義了response屬性,爲何子類HTTPRO中還要定義,這個是很好理解的:

URLCO中的reponse是NSURLResponse類型,而HTTPRO中的response是NSHTTPURLResponse類型,對於使用HTTPRO的response屬性的狀況,省去了諸如類型強轉等重複性的操做,面向接口編程的原則中有抽象類的屬性使用抽象類的思想,而這裏是具體的類的屬性使用具體的類

爲何要在擴展中從新定義屬性response?

實際上這裏的response定義是和@dynamic配合使用的:

首先,response已經在URLCO.h中定義爲readonly若是要在URLCO.m中爲成員變量賦值,或者修改其值,要使用@synthesize生成成員變量的getter和setter同時保證成員變量依然存在。AFN並無使用這種方法,而是經過在擴展中增長readwrite的關鍵字再次定義這樣一樣使getter和setter可用,屬性也依然存在,而在URLCO.m中不須要@dynamic修飾屬性,由於已經能夠確保getter和setter的實現。

再者,在子類HTTPRO中,一樣使用上面的方法依然使得在HTTPRO.m中response的getter和setter可用了,可是在getter和setter的實現內部與父類URLCO中的reponse屬性失去了聯繫,這是後配合使用@dynamic response使得原本的setter和gette實現變得不可用,可是對於調用依然沒有編譯期間的錯誤,而在運行階段,實際是調用了父類URLCO中的gette和setter,繼續將方法調用向上傳遞。

而對於爲何要在HTTPRO中再次定義父類的擴展?

對於request屬性來講是有用的,由於子類中沒有定義過request,可是對response來講是沒有什麼意義的,我的以爲這句能夠直接刪掉。

相關文章
相關標籤/搜索