閱讀459 返回首頁    go 阿裏雲 go 技術社區[雲棲]


Core Data淺談係列之六 : 驗證用戶輸入

在做Web開發時,需要謹記的一條原則是“絕不要相信用戶的任何輸入”(參見《Essential PHP Security》)。
與網頁上的表單提交類似,做客戶端開發時也應該考慮用戶輸入,比如可以為UITextField設置代理處理用戶實時輸入的內容,也可以讀取完用戶輸入再做檢查,或者是NSManagedObject的驗證功能

比如,我們可以在Player的實現裏提供驗證函數: 
#define PLAYER_ERROR_DOMAIN @"PLAYER_ERROR_DOMAIN"

enum _playerErrorCode {
    PLAYER_INVALID_AGE_CODE = 0,
    PLAYER_INVALID_NAME_CODE,
    PLAYER_INVALID_CODE
};
typedef enum _playerErrorCode PlayerErrorCode;

@implementation Player

@dynamic age;
@dynamic name;
@dynamic team;

- (BOOL)validateName:(id *)ioValue error:(NSError **)outError
{
    NSString *playerName = *ioValue;
    playerName = [playerName stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
    if (!playerName || [playerName length] == 0) {
        if (outError) {
            NSString *errorStr = @"Player's name should not be empty.";
            NSDictionary *userInfoDict = @{ NSLocalizedDescriptionKey : errorStr };
            NSError *error = [[NSError alloc] initWithDomain:PLAYER_ERROR_DOMAIN
                                                        code:PLAYER_INVALID_NAME_CODE
                                                    userInfo:userInfoDict];
            *outError = error;
        }
        return NO;
    }

    return YES;
}

@end
當context在執行save動作時,這些屬性驗證函數會得到調用,如果驗證函數返回NO,則save動作會失敗:

2013-01-17 22:36:42.393 cdNBA[673:c07] Error Error Domain=PLAYER_ERROR_DOMAIN Code=1 "Player's name should not be empty." UserInfo=0x827e380 {NSLocalizedDescription=Player's name should not be empty.}, Player's name should not be empty.
當然,我們絕對不會希望異常發生在這個位置,讓程序直接掛掉 —— 這裏隻是一個Demo。
因為隻有在保存context時才會調用驗證函數,為了不讓程序掛在這裏,我們可以提前進行驗證: 
    NSString *name = self.nameTextField.text;
    NSError *error = NULL;
    [playerObject validateValue:&name forKey:@"name" error:&error];
    if (error) {
        NSLog(@"%@\n", [error localizedDescription]);
    }
除了name,我們還可能驗證age或者其它屬性,同樣地可以寫相應的屬性驗證函數。但是,有些屬性的驗證是需要結合在一起作為判斷條件的。這種情況下,我們可以利用屬性間的結合驗證:
- (BOOL)validateForInsert:(NSError **)outError
{
    BOOL valid = [super validateForInsert:outError];
    
    NSString *playerName = self.name;
    if (!playerName || [playerName length] == 0) {
        if (outError) {
            NSString *errorStr = @"Player's name should not be empty.";
            NSDictionary *userInfoDict = @{ NSLocalizedDescriptionKey : errorStr };
            NSError *error = [[NSError alloc] initWithDomain:PLAYER_ERROR_DOMAIN
                                                        code:PLAYER_INVALID_NAME_CODE
                                                    userInfo:userInfoDict];
            *outError = [self errorFromOriginalError:error error:nil];
        }
        valid = NO;
    }
    
    NSInteger playerAge = [self.age integerValue];
    if (!self.age || (playerAge < 16 || playerAge > 50)) {
        if (outError) {
            NSString *errorStr = @"Player's age should be in [16, 50].";
            NSDictionary *userInfoDict = @{ NSLocalizedDescriptionKey : errorStr };
            NSError *error = [[NSError alloc] initWithDomain:PLAYER_ERROR_DOMAIN
                                                        code:PLAYER_INVALID_AGE_CODE
                                                    userInfo:userInfoDict];
            *outError = [self errorFromOriginalError:*outError error:error];
        }
        valid = NO;
    }
    
    return valid;
}

// Modified from https://developer.apple.com/library/mac/
//
- (NSError *)errorFromOriginalError:(NSError *)originalError error:(NSError *)secondError
{
    NSMutableDictionary *userInfo = [NSMutableDictionarydictionary];
    
    NSMutableArray *errors = [NSMutableArrayarray];
    if (secondError) {
        [errors addObject:secondError];
    }
    
    if ([originalError code] == NSValidationMultipleErrorsError) {
        [userInfo addEntriesFromDictionary:[originalError userInfo]];
        [errors addObjectsFromArray:[userInfo objectForKey:NSDetailedErrorsKey]];
    } else {
        [errors addObject:originalError];
    }
    
    [userInfo setObject:errors forKey:NSDetailedErrorsKey];
    
    return [NSErrorerrorWithDomain:NSCocoaErrorDomain
                               code:NSValidationMultipleErrorsError
                           userInfo:userInfo];
}
由於存在多種屬性的驗證,所以提供了錯誤信息的連接函數,這有點類似php中我們經常會寫的字符串連接點符號:
error = "Invalid username or password.";
error .= "Invalid token.";
NSManagedObject提供了三個函數用戶在插入、修改、刪除之前進行驗證,分別是上麵的validateForInsert,以及validateForUpdate和validateForDelete。
這次如果name和age都為空,則會輸出如下錯誤信息: 
2013-01-17 23:42:03.979 cdNBA[1064:c07] Error Error Domain=NSCocoaErrorDomain Code=1560 "The operation couldn’t be completed. (Cocoa error 1560.)" UserInfo=0x111394b0 {NSDetailedErrors=(
    "Error Domain=PLAYER_ERROR_DOMAIN Code=0 \"Player's age should be in [16, 50].\" UserInfo=0x1112fbf0 {NSLocalizedDescription=Player's age should be in [16, 50].}",
    "Error Domain=PLAYER_ERROR_DOMAIN Code=1 \"Player's name should not be empty.\" UserInfo=0x11139430 {NSLocalizedDescription=Player's name should not be empty.}"
)}, The operation couldn’t be completed. (Cocoa error 1560.)
上麵隻是簡單地對name和age進行是否為空的判定,實際操作還需要判斷其它條件。比如還可以判斷該球員是否已經存在,或者是之前提到的球隊同名問題。

假設我們輸入了合法的數據,創建了一名球員的信息,結果返回到上一級視圖發現沒有得到展現。對於這種情況,我們可以先很黃很暴力地在viewWillAppear裏麵重新reload下table,或者通過觀察者模式監聽相應的消息進行刷新。這裏即將討論的方法是使用NSFetchedResultsController這個類。

Brief Talk About Core Data Series, Part 6 : Input Validation 

Jason Lee @ Hangzhou
Blog : https://blog.csdn.net/jasonblog
Weibo : https://weibo.com/jasonmblog


最後更新:2017-04-04 07:03:39

  上一篇:go windows和linux雙係統刪除linux
  下一篇:go getSharedPreferences()與getSharedPreferences()與getDefaultSharedPreferences()的區別