漫談 KVC 與 KVO

KVC 與 KVO 無(wú)疑是 Cocoa 提供給我們的一個(gè)非常強(qiáng)大的特性,使用熟練可以讓我們的代碼變得非常簡(jiǎn)潔并且易讀折砸。但 KVC 與 KVO 提供的 API 又是比較復(fù)雜的,絕對(duì)超出我們不經(jīng)深究之前所理解到的復(fù)雜度,這次大家就來(lái)跟我一起深入認(rèn)識(shí)這兩個(gè)特性吧。

基礎(chǔ)使用

首先喊暖,咱們要說(shuō)的是 KVC (Key-Value Coding), 它是一種用間接方式訪問(wèn)類的屬性的機(jī)制撕瞧。在 Swift 中為一個(gè)類實(shí)現(xiàn) KVC 的話陵叽,需要讓它繼承自 NSObject:

class Person: NSObject {
    
    var firstName: String
    var lastName: String
    
    init(firstName: String, lastName: String) {
        
        self.firstName = firstName
        self.lastName = lastName
        
    }
    
}

這樣,我們就可以使用 KVC 的方式訪問(wèn) Person 類的屬性了:

let peter = Person(firstName: "Cook", lastName: "Peter")

print(peter.lastName)
print(peter.valueForKey("lastName")!)

注意我們的兩個(gè) print 語(yǔ)句丛版,第一個(gè)是使用直接引用屬性的方式巩掺,第二個(gè)就是使用 KVC 機(jī)制訪問(wèn)的方式。 valueForKey 是 KVC 協(xié)議中定義的方法页畦,它接受一個(gè)參數(shù)胖替,我們把它叫做 key,這個(gè) key 表示要訪問(wèn)的屬性名稱豫缨,KVC 就會(huì)根據(jù)我們傳入的 key 幫助我們找到對(duì)應(yīng)的屬性独令。

不同之處

在 Swift 中處理 KVC和 Objective-C 中還是有些細(xì)微的差別。比如州胳,Objective-C 中所有的類都繼承自 NSObject记焊,而 Swift 中卻不是,所以我們?cè)?Swift 中需要顯式的聲明繼承自 NSObject栓撞。

可為什么要繼承自 NSObject 呢遍膜?我們?cè)谔O(píng)果官方的 KVC 文檔中找到了答案。其實(shí) KVC 機(jī)制是由一個(gè)協(xié)議 NSKeyValueCoding 定義的瓤湘。NSObject 幫我們實(shí)現(xiàn)了這個(gè)協(xié)議瓢颅,所以 KVC 核心的邏輯都在 NSObject 中,我們繼承 NSObject 才能讓我們的類獲得 KVC 的能力弛说。(理論上說(shuō)挽懦,如果你遵循 NSKeyValueCoding 協(xié)議的接口,其實(shí)也可以自己實(shí)現(xiàn) KVC 的細(xì)節(jié)木人,完全行得通信柿。但在實(shí)踐上冀偶,這么做就不太值得了,太費(fèi)時(shí)間了~)渔嚷。

另外进鸠,因?yàn)?Swift 中的 Optional 機(jī)制,所以 valueForKey 方法返回的是一個(gè) Optional 值形病,我們還需要對(duì)返回值做一次解包處理客年,才能得到實(shí)際的屬性值。

關(guān)于 Optional 特性的內(nèi)容漠吻,可以參考這兩篇文章
淺談 Swift 中的 Optionals
關(guān)于 Optional 的一點(diǎn)嘮叨

那么書(shū)歸正傳量瓜,KVC 最主要的好處是什么呢,簡(jiǎn)單來(lái)說(shuō)就是我們可以不用過(guò)多的依賴編譯時(shí)的限制途乃,而是為我們提供了更多的運(yùn)行時(shí)的能力绍傲。

valueForUndefinedKey

還是繼續(xù)咱們上面的例子,假如我們又寫(xiě)了這樣一個(gè)語(yǔ)句會(huì)怎么樣呢:

peter.valueForKey("noExist")

因?yàn)槲覀兌x的 Person 類中是沒(méi)有 noExist 這個(gè)屬性的欺劳,所以 KVC 也無(wú)法找到這個(gè)屬性值唧取,這時(shí)候 KVC 協(xié)議其實(shí)會(huì)調(diào)用 valueForUndefinedKey 方法铅鲤,NSObject 對(duì)這個(gè)方法的默認(rèn)實(shí)現(xiàn)是拋出一個(gè) NSUndefinedKeyException 異常划提。所以如果我們沒(méi)有自己重寫(xiě) valueForUndefinedKey 方法的話,這時(shí)應(yīng)用就會(huì)因?yàn)楫惓1罎ⅰ?/p>

我們也可以在 Person 類中實(shí)現(xiàn)我們自己的 valueForUndefinedKey 方法:

class PersonHandleUndefinedKey: NSObject {
    
    var firstName: String
    var lastName: String
    
    init(firstName: String, lastName: String) {
        
        self.firstName = firstName
        self.lastName = lastName
        
    }
    
    override func valueForUndefinedKey(key: String) -> AnyObject? {
        return ""
    }
    
}


let peter2 = PersonHandleUndefinedKey(firstName: "Cook", lastName: "Peter")
print(peter2.valueForKey("noExist"))

這次定義了 valueForUndefinedKey 對(duì)于未定義的 key 返回一個(gè)空字符串邢享,這樣我們的 KVC 調(diào)用就能以更加優(yōu)雅的方式處理這個(gè)異常行為了鹏往。

valueForKeyPath

KVC 除了可以用單個(gè)的 key 來(lái)訪問(wèn)單個(gè)屬性,還提供了一個(gè)叫做 keyPath 的東西骇塘。所謂 keyPath伊履,就比如你的屬性本身也有自己的屬性,那么想引用這個(gè)屬性款违,就需要用到 keyPath唐瀑。咱們用一個(gè)示例來(lái)說(shuō)明:


class Address: NSObject {
    
    var firstLine: String
    var secondLine: String
    
    init(firstLine: String, secondLine: String) {
        
        self.firstLine = firstLine
        self.secondLine = secondLine
        
    }
    
    
}

class PersonHandleKeyPath: NSObject {
    
    var firstName: String
    var lastName: String
    var address: Address
    
    init(firstName: String, lastName: String, address: Address) {
        
        self.firstName = firstName
        self.lastName = lastName
        self.address = address
        
    }
    
}


var peter3 = PersonHandleKeyPath(firstName: "Cook", lastName: "Peter", address: Address(firstLine: "Beijing", secondLine: "Haidian"))

print(peter3.valueForKeyPath("address.firstLine")!)

PersonHandleKeyPath 類定義了一個(gè)屬性 address, 這個(gè) address 本身又是一個(gè)類插爹,它也有兩個(gè)屬性 firstLinelastLine哄辣, 那么我們?nèi)绻胍?address 的 firstLine 屬性,就可以使用 KVC 的 keyPath 機(jī)制:

print(peter3.valueForKeyPath("address.firstLine")!)

通過(guò) keyPath赠尾,我們可以使用 KVC 將屬性引用范圍擴(kuò)大很多力穗。這個(gè)規(guī)則對(duì) Cocoa 系統(tǒng)類也適用,比如:

let view = UIView()
print(view.valueForKeyPath("superview.superview"))

我們可以通過(guò) KVC 的這個(gè)機(jī)制遍歷 UIView 層級(jí)气嫁。

同樣的当窗,如果 keyPath 中引用的任何一級(jí)屬性不存在或者不符合 KVC 規(guī)范, valueForUndefinedKey 方法就會(huì)被調(diào)用寸宵。

SetValueForKey

KVC 定義了使用 valueForKey 方法獲取屬性的值崖面,同樣也提供了設(shè)置屬性值的方法元咙,就是 setValue:forKey ", 還是接著上面的例子:

peter3.setValue("swift", forKey: "firstName")
print(peter3.valueForKey("firstName")!)

setValue:forKey 方法接受兩個(gè)參數(shù),第一個(gè)參數(shù)是我們要設(shè)置的屬性的值巫员,第二個(gè)參數(shù)是屬性的 key蛾坯。這個(gè)接口很簡(jiǎn)單明了,就不多贅述了疏遏。

和 valueForKey 一樣脉课,如果我們給 setValue 傳遞一個(gè)不存在的 key 值,KVC 就會(huì)去調(diào)用 setValue: forUndefinedKey 方法财异,NSObject 對(duì)這個(gè)方法的默認(rèn)實(shí)現(xiàn)依然是拋出一個(gè) NSUndefinedKeyException 異常倘零。

關(guān)于標(biāo)量值

所謂標(biāo)量值(Scalar Type),指的是簡(jiǎn)單類型的屬性戳寸,比如 int呈驶,float 這些非對(duì)象的屬性。關(guān)于標(biāo)量值的在 KVC 中的處理有有些地方需要我們注意疫鹊,我們把 Person 類再重寫(xiě)一下:

class PersonForScalar : NSObject {
    
    var firstName: String
    var lastName: String
    var age: Int
    
    init(firstName: String, lastName: String, age: Int) {
        
        self.firstName = firstName
        self.lastName = lastName
        self.age = age
        
    }
    
}

那么現(xiàn)在可以使用 KVC 來(lái)操作它的各個(gè)屬性:

var person4 = PersonForScalar(firstName: "peter", lastName: "cook", age: 32)
person4.setValue(55, forKey: "age")
print(person4.valueForKey("age")!)

通過(guò) setValue 方法袖瞻,我們將 age 設(shè)置為 55,并在下一行代碼中使用 valueForKey 將這個(gè)值打印出來(lái)拆吆。一切看似沒(méi)什么不同聋迎。

那么假如我們又寫(xiě)了這一行語(yǔ)句呢:

person4.setValue(nil, forKey: "age")

額,你可以自己嘗試一下枣耀,這時(shí)候程序會(huì)崩潰霉晕。原因嘛,很簡(jiǎn)單捞奕。 我們先來(lái)看 age 的定義:

var age: Int

age 是一個(gè)簡(jiǎn)單標(biāo)量值(Int 整型變量)牺堰,而標(biāo)量值是不能夠設(shè)置成 nil 的。雖然 KVC 提供給我們的 setValue 方法可以接受任何類型的參數(shù)作為值的設(shè)置颅围,但 age 的底層存儲(chǔ)確實(shí)標(biāo)量值伟葫,因此我們執(zhí)行上面那條 setValue 語(yǔ)句的時(shí)候必然會(huì)造成程序的崩潰。(這點(diǎn)在開(kāi)發(fā)程序的時(shí)候確實(shí)需要格外留意院促,稍不留神可能就會(huì)浪費(fèi)很多時(shí)間去調(diào)試錯(cuò)誤)筏养。

那么我們除了注意避免將 nil 傳遞給底層存儲(chǔ)是標(biāo)量類型的屬性之外,還有沒(méi)有其他方法呢一疯? 答案是有的撼玄。

KVC 為我們提供了一個(gè) setNilValueForKey 方法,每當(dāng)我們要將 nil 設(shè)置給一個(gè) key 的時(shí)候墩邀,這個(gè)方法就會(huì)被調(diào)用掌猛,所以我們可以修改一下 Person 類的定義:

class PersonForScalar : NSObject {
    
    //...
    
    override func setNilValueForKey(key: String) {
        
        if key == "age" {
            
            self.setValue(18, forKey: "age")
            
        }
        
    }
    
    //...
    
}

我們?cè)?setNilValueForKey 方法中,判斷如果當(dāng)前的 key 是 age 的話,就給它設(shè)置一個(gè)默認(rèn)值 18荔茬。這次我們?cè)俅蝹魅?nil 的時(shí)候废膘,程序就不會(huì)因?yàn)閽伋霎惓6罎ⅲ菫檫@個(gè) age 屬性設(shè)置一個(gè)默認(rèn)值慕蔚。

集合屬性

KVC 還提供了對(duì)集合屬性的處理丐黄,簡(jiǎn)單來(lái)說(shuō)就是這樣,我們?yōu)?Person 類再添加一個(gè) friends 屬性孔飒,用于表示這個(gè)人的朋友:

class PersonForCollection : NSObject {
    
    var firstName: String
    var lastName: String
    var friends: NSMutableArray
    
}

如果我們要為某一個(gè) Person 的實(shí)例添加一個(gè)新朋友灌闺,或者獲取它現(xiàn)有的朋友該怎么做呢? 大家可能會(huì)直接想到這樣:

person5.friends.addObject(person6)

通過(guò)直接的屬性引用坏瞄,我們可以完成這樣的需求桂对。不過(guò)嘛,KVC 還給我們提供了專屬的集合操作協(xié)議鸠匀,這樣我們就可以通過(guò) KVC 的方式操作集合中的內(nèi)容了蕉斜,我們將 Person 類改寫(xiě)一下:

class PersonForCollection : NSObject {
    
    var firstName: String
    var lastName: String
    var friends: NSMutableArray
    
    init(firstName: String, lastName: String) {
        
        self.firstName = firstName
        self.lastName = lastName
        self.friends = NSMutableArray()
        
    }

    func countOfFriends() -> Int {
        
        return self.friends.count
        
    }
    
    func objectInFriendsAtIndex(index: Int) -> AnyObject? {
        
        return self.friends[index]
        
    }
    
}

這次我們新添加了兩個(gè)方法,countOfFriendsobjectInFriendsAtIndex 缀棍,這兩個(gè)方法是 KVC 預(yù)定義的協(xié)議方法宅此,用于集合類型的操作。注意這兩個(gè)協(xié)議更明確的定義是這樣 countOf<Key>objectIn<Key>AtIndex爬范。 其中的 Key 代表集合操作的應(yīng)的屬性 key 的名字父腕。比如 countOfFriends, countOfAddress, countOfBooks 這些都是合法的集合操作協(xié)議方法,前提是只要相應(yīng) key 值對(duì)應(yīng)的屬性存在坦敌。

那么集合操作方法定義好了侣诵,我們來(lái)看看如何使用 KVC 來(lái)操作集合屬性吧:

person5.mutableArrayValueForKey("friends").count

這個(gè)調(diào)用取得當(dāng)前的 friends 集合的 count 屬性痢法,這時(shí)候?qū)嶋H上調(diào)用了 countOfFriends 方法狱窘。自然,我們剛才還實(shí)現(xiàn)了 objectInFriendsAtIndex 方法财搁,大家也能推理出這個(gè)方法如何使用了吧:

let friend = person5.mutableArrayValueForKey("friends")[0]

就是這樣了蘸炸,實(shí)際上 KVC 對(duì)于我們這個(gè)集合屬性 friends 的操作都會(huì)通過(guò) mutableArrayValueForKey 方法來(lái)進(jìn)行华望,它會(huì)用我們傳入的 key 值在當(dāng)前實(shí)例中進(jìn)行解析竣况,如果接續(xù)成功會(huì)返回一個(gè) NSMutableArray 類型的對(duì)象,我們就可以直接使用 NSMutableArray 的接口對(duì)集合類的屬性進(jìn)行操作了获洲,不論他的底層存儲(chǔ)是不是 NSMutableArray提茁,它也是 NSKeyValueCoding 協(xié)議中定義的方法(這個(gè)協(xié)議定義我們?cè)谇懊嫣岬竭^(guò)淹禾,大家還記得吧~)。

我們剛才實(shí)現(xiàn)了集合相關(guān)的兩個(gè)方法還缺了些什么呢 — 我們只實(shí)現(xiàn)了集合操作的 getter 方法茴扁,并沒(méi)有實(shí)現(xiàn) setter 方法铃岔。到目前,我們還不能通過(guò) KVC 機(jī)制來(lái)給 firends 數(shù)組添加元素峭火。

我們還需要添加兩個(gè)方法:

class PersonForCollection : NSObject {

    func insertObjectInFriendsAtIndex(friend: PersonForCollection, index: Int) {
        
        self.friends.insertObject(friend, atIndex: index)
        
    }
    
    func removeObjectFromFriendsAtIndex(index: Int) {
        
        self.friends.removeObjectAtIndex(index)
        
    }

}

insertObjectInFriendsAtIndexremoveObjectFromFriendsAtIndex 分別用于向 friends 屬性中插入元素和刪除元素』傧埃現(xiàn)在我們也可以用 KVC 來(lái)操作集合內(nèi)容了:

person5.mutableArrayValueForKey("friends").addObject(person6)
person5.mutableArrayValueForKey("friends").count
person5.mutableArrayValueForKey("friends").removeObjectAtIndex(0)

通過(guò) KVC 的集合操作協(xié)議智嚷,我們實(shí)現(xiàn)了直接用 KVC 接口來(lái)操作集合屬性的內(nèi)容。 KVC 集合操作會(huì)更加靈活纺且,friends 屬性不一定是 NSMutableArray 類型盏道, 它的底層存儲(chǔ)可以是任何形式,只要我們實(shí)現(xiàn)了 KVC 集合操作接口载碌,我們就能通過(guò) KVC 像使用 NSMutableArray 一樣來(lái)操作底層的集合了猜嘱。

總結(jié)

好了,關(guān)于 KVC 咱們就說(shuō)這么多嫁艇,它還提供了很多其他非常好的特性泉坐,比如屬性驗(yàn)證,可以通過(guò)這個(gè)方式來(lái)對(duì)屬性的設(shè)置過(guò)程進(jìn)行類似 filter 的操作裳仆。還提供了keyPath 的集合操作腕让,比如我們通過(guò)這樣一個(gè) KeyPath 就可以獲得 friends 集合的元素總數(shù):

person5.valueForKeyPath("friends.@count")

善用 KVC 肯定會(huì)對(duì)我們的開(kāi)發(fā)有很大的幫助。關(guān)于 KVC 如果大家想了解更多歧斟,推薦大家看一看蘋(píng)果官方的文檔 Key-Value Coding Programming Guide纯丸。

希望本篇文章的內(nèi)容讓大家再看了之后多多少少有些收貨吧,我們下篇文章將會(huì)和大家一起探討 KVO 的相關(guān)內(nèi)容静袖,也希望大家喜歡觉鼻。

本篇內(nèi)容相關(guān)代碼的 playground 大家可以在 Github 上面找到: https://github.com/swiftcafex/kvc-kvo-samples

更多精彩內(nèi)容可關(guān)注微信公眾號(hào):
swift-cafe

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市队橙,隨后出現(xiàn)的幾起案子坠陈,更是在濱河造成了極大的恐慌,老刑警劉巖捐康,帶你破解...
    沈念sama閱讀 211,042評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件仇矾,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡解总,警方通過(guò)查閱死者的電腦和手機(jī)贮匕,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,996評(píng)論 2 384
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)花枫,“玉大人刻盐,你說(shuō)我怎么就攤上這事±秃玻” “怎么了敦锌?”我有些...
    開(kāi)封第一講書(shū)人閱讀 156,674評(píng)論 0 345
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)佳簸。 經(jīng)常有香客問(wèn)我乙墙,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,340評(píng)論 1 283
  • 正文 為了忘掉前任伶丐,我火速辦了婚禮悼做,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘哗魂。我一直安慰自己肛走,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,404評(píng)論 5 384
  • 文/花漫 我一把揭開(kāi)白布录别。 她就那樣靜靜地躺著朽色,像睡著了一般。 火紅的嫁衣襯著肌膚如雪组题。 梳的紋絲不亂的頭發(fā)上葫男,一...
    開(kāi)封第一講書(shū)人閱讀 49,749評(píng)論 1 289
  • 那天,我揣著相機(jī)與錄音崔列,去河邊找鬼梢褐。 笑死,一個(gè)胖子當(dāng)著我的面吹牛赵讯,可吹牛的內(nèi)容都是我干的盈咳。 我是一名探鬼主播,決...
    沈念sama閱讀 38,902評(píng)論 3 405
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼边翼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼鱼响!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起组底,我...
    開(kāi)封第一講書(shū)人閱讀 37,662評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤丈积,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后债鸡,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體江滨,經(jīng)...
    沈念sama閱讀 44,110評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,451評(píng)論 2 325
  • 正文 我和宋清朗相戀三年娘锁,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了牙寞。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,577評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡莫秆,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出悔详,到底是詐尸還是另有隱情镊屎,我是刑警寧澤,帶...
    沈念sama閱讀 34,258評(píng)論 4 328
  • 正文 年R本政府宣布茄螃,位于F島的核電站缝驳,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜用狱,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,848評(píng)論 3 312
  • 文/蒙蒙 一运怖、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧夏伊,春花似錦摇展、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,726評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至鲁森,卻和暖如春祟滴,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背歌溉。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,952評(píng)論 1 264
  • 我被黑心中介騙來(lái)泰國(guó)打工垄懂, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人痛垛。 一個(gè)月前我還...
    沈念sama閱讀 46,271評(píng)論 2 360
  • 正文 我出身青樓埠偿,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親榜晦。 傳聞我的和親對(duì)象是個(gè)殘疾皇子冠蒋,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,452評(píng)論 2 348

推薦閱讀更多精彩內(nèi)容

  • KVC 與 KVO 無(wú)疑是 Cocoa 提供給我們的一個(gè)非常強(qiáng)大的特性,使用熟練可以讓我們的代碼變得非常簡(jiǎn)潔并且易...
    面包與世界閱讀 747評(píng)論 0 3
  • KVC(Key-value coding)鍵值編碼乾胶,單看這個(gè)名字可能不太好理解抖剿。其實(shí)翻譯一下就很簡(jiǎn)單了,就是指iO...
    黑暗中的孤影閱讀 49,675評(píng)論 74 441
  • KVC(Key-value coding)鍵值編碼识窿,單看這個(gè)名字可能不太好理解斩郎。其實(shí)翻譯一下就很簡(jiǎn)單了,就是指iO...
    朽木自雕也閱讀 1,552評(píng)論 6 1
  • KVC(Key-value coding)鍵值編碼喻频,單看這個(gè)名字可能不太好理解缩宜。其實(shí)翻譯一下就很簡(jiǎn)單了,就是指iO...
    Fendouzhe閱讀 669評(píng)論 0 6
  • log4j的配置可以通過(guò)以下四種方式完成: 通過(guò)XML, JSON, YAML, 或者 properties格式的...
    high_m閱讀 827評(píng)論 0 0