利用Runtime清空單例屬性

weixin_33716557發表於2018-01-03
- (void)qy_clearPropertyValue {
        // 1.儲存屬性的個數
        unsigned int propertyCount = 0;
        // 2.通過執行時獲取當前類的所有屬性
        objc_property_t *properties = class_copyPropertyList([self class], &propertyCount);
        // 3.遍歷屬性
        for (int i = 0; i < propertyCount; i ++) {
            objc_property_t property = properties[i];
            NSString *propertyName = [NSString stringWithFormat:@"%s", property_getName(property)];
            // 4.利用KVC 設定屬性nil
            [self setValue:nil forKey:propertyName];
        }
        // 釋放指標
        free(properties);
}

但是上面程式碼有一個重大bug:

當物件裡面的屬性不是物件屬性時(例如NSInteger CGFloat等),程式會崩潰,控制檯報錯:could not set nil as the value for the key

解決辦法:

  • 第一種:重寫setNilValueForKey:方法,逐個屬性判斷

  • 第二種:異常捕捉,統一處理

- (void)setNilValueForKey:(NSString *)key {
    if ([key isEqualToString:@"屬性名1"]) {
        _屬性1 = 0;
    }  else if ([key isEqualToString:@"屬性名2"]) {
        _屬性1 = 0;
    }
    ...
    ...
    ...
  else {
        [super setNilValueForKey:key];
    }
}

最優雅的寫法應該是:

- (void)qy_clearPropertyValue {
        // 1.儲存屬性的個數
        unsigned int propertyCount = 0;
        // 2.通過執行時獲取當前類的所有屬性
        objc_property_t *properties = class_copyPropertyList([self class], &propertyCount);
        // 3.遍歷屬性
        for (int i = 0; i < propertyCount; i ++) {
            objc_property_t property = properties[i];
            NSString *propertyName = [NSString stringWithFormat:@"%s", property_getName(property)];
            // 4.利用KVC 設定屬性nil
            @try {
                [self setValue:nil forKey:propertyName];
            } @catch (NSException *exception) {
                [self setValue:@0 forKey:propertyName];
            } @finally {
                //
            }
            
        }
        // 釋放指標
        free(properties);
}

PS:詭異的死鎖Bug:

#pragma mark - 退出登入
- (void)loginOut {
    UIAlertController *alertVc = [UIAlertController alertControllerWithTitle:nil message:@"您真的要退出嗎?" preferredStyle:UIAlertControllerStyleAlert];
    [alertVc addAction:[UIAlertAction actionWithTitle:@"取消" style:UIAlertActionStyleDefault handler:nil]];
    [alertVc addAction:[UIAlertAction actionWithTitle:@"確認" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
            
            //  下面? 這一行執行會有死鎖的bug, app會卡主,處於假死狀態
            [[QYMyInfoModel sharedMyInfoModel] qy_clearPropertyValue];

    }]];
  
    [self presentViewController:alertVc animated:YES completion:nil];    
}

但是,把那句程式碼放到非同步執行緒執行,就不會死鎖,不知道什麼原因,如果有人知道,麻煩告知一下

#pragma mark - 退出登入
- (void)loginOut {
    UIAlertController *alertVc = [UIAlertController alertControllerWithTitle:nil message:@"您真的要退出嗎?" preferredStyle:UIAlertControllerStyleAlert];
    [alertVc addAction:[UIAlertAction actionWithTitle:@"取消" style:UIAlertActionStyleDefault handler:nil]];
    [alertVc addAction:[UIAlertAction actionWithTitle:@"確認" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
            
            //  下面? 這一行執行會有死鎖的bug, app會卡主,處於假死狀態,所以在非同步執行緒執行
                  dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
            [[QYMyInfoModel sharedMyInfoModel] qy_clearPropertyValue];
        });

    }]];
  
    [self presentViewController:alertVc animated:YES completion:nil];    
}

相關文章