首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> 關於C語言

Objective-C Runtime 動態修正 nil 屬性:自動將空字串屬性設為空字串

     編輯:關於C語言

背景與痛點

iOS 專案中常將 JSON 直接映射為 Model 物件。若後端回傳欄位為 null 或缺失,對應的 NSString 屬性會保持 nil。後續直接使用這些屬性(如 [label setText:model.name])雖不會崩潰,但可能導致 UI 顯示異常或邏輯判斷錯誤。需求:遍歷物件所有 NSString 型別屬性,若為 nil 則自動設為 @"",且不需預知屬性名稱與數量。

核心實作:GMCommonHelper

// GMCommonHelper.h
#import 
#import 

@interface GMCommonHelper : NSObject
- (void)fixNilStringProperties:(id)object;
@end

// GMCommonHelper.m
#import "GMCommonHelper.h"

@implementation GMCommonHelper

- (void)fixNilStringProperties:(id)object {
    if (!object) return;
    
    unsigned int count = 0;
    objc_property_t* properties = class_copyPropertyList([object class], &count);
    
    for (unsigned int i = 0; i < count; ++i) {
        objc_property_t prop = properties[i];
        const char* name = property_getName(prop);
        const char* attrs = property_getAttributes(prop);
        
        // 屬性型別編碼:@"NSString" 開頭為物件類型
        if (attrs && attrs[0] == 'T' && attrs[1] == '@' && strstr(attrs, "NSString")) {
            NSString* key = [NSString stringWithUTF8String:name];
            id value = [object valueForKey:key];
            if (value == nil) {
                [object setValue:@"" forKey:key];
            }
        }
    }
    free(properties);
}

@end

關鍵技術細節解析

1. 屬性型別編碼解讀

property_getAttributes 回傳字串格式如 T@"NSString",C,N,V_name:

  • T:型別編碼前綴
  • @"NSString":Objective-C 物件類型(Class 名稱在雙引號內)
  • C:複製語意(copy)
  • N:非原子性
  • V_name:實例變數名稱

判斷是否為 NSString:strstr(attrs, "NSString") 即可,簡單可靠。

2. 僅處理可寫屬性

若屬性為 readonly,setValue:forKey: 會拋出異常。屬性編碼中包含 ,R, 表示唯讀,可加入檢查:

if (strstr(attrs, ",R,")) continue; // 跳過唯讀屬性

3. 遞歸處理巢狀物件

若屬性為自訂 Model 類別,需遞歸修正:

if (value && ![value isKindOfClass:[NSString class]] && ![value isKindOfClass:[NSNumber class]] && ![value isKindOfClass:[NSArray class]] && ![value isKindOfClass:[NSDictionary class]]) {
    [self fixNilStringProperties:value]; // 遞歸處理子物件
}

進階版:分類擴充 NSObject

// NSObject+FixNil.h
#import 

@interface NSObject (FixNil)
- (void)fixNilStringsRecursively;
@end

// NSObject+FixNil.m
#import "NSObject+FixNil.h"
#import 

@implementation NSObject (FixNil)

- (void)fixNilStringsRecursively {
    static NSMutableSet* visited = nil;
    if (!visited) visited = [NSMutableSet new];
    if ([visited containsObject:self]) return; // 防循環引用
    [visited addObject:self];
    
    unsigned int count = 0;
    objc_property_t* props = class_copyPropertyList([self class], &count);
    for (unsigned int i = 0; i < count; ++i) {
        objc_property_t prop = props[i];
        const char* attrs = property_getAttributes(prop);
        if (!attrs) continue;
        if (strstr(attrs, ",R,")) continue; // 唯讀跳過
        
        const char* name = property_getName(prop);
        NSString* key = [NSString stringWithUTF8String:name];
        id value = [self valueForKey:key];
        
        // NSString 且為 nil -> 設為空字串
        if (value == nil && strstr(attrs, "NSString")) {
            [self setValue:@"" forKey:key];
        }
        // 自訂物件遞歸
        else if (value && ![value isKindOfClass:[NSString class]] && ![value isKindOfClass:[NSNumber class]] && ![value isKindOfClass:[NSArray class]] && ![value isKindOfClass:[NSDictionary class]] && ![value isKindOfClass:[NSNull class]]) {
            [value fixNilStringsRecursively];
        }
    }
    free(props);
    [visited removeObject:self];
}

@end

使用範例

UserModel* user = [UserModel modelWithJSON:jsonDict];
[user fixNilStringsRecursively];
// 現在 user.name、user.nickname 等 NSString 屬性保證不為 nil
self.nameLabel.text = user.name; // 安全

效能與注意事項

  • 效能:Runtime 遍歷屬性屬 O(n),n 為屬性數。單次呼叫微秒級,可接受。大量資料建議在解析階段一次性處理。
  • 循環引用:使用 NSMutableSet 記錄已訪問物件防止無限遞歸。
  • Swift 相容性:Swift 類別需繼承 NSObject 且屬性標記 @objc 才能被 Runtime 發現。
  • 私有屬性:class_copyPropertyList 僅返回公開屬性,私有 ivar 需用 class_copyIvarList。

總結

利用 Objective-C Runtime 的反射能力,我們實現了通用的 nil 字串自動修正工具。核心在於 property_getAttributes 型別判斷與 setValue:forKey: 動態賦值。配合分類擴充,可零侵入式接入現有專案,大幅減少防衛性程式碼,提升程式碼健壯性。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved