一.UDID(Unique Device Identifier)
UDID的全稱是Unique Device Identifier,它就是蘋(píng)果iOS設(shè)備的唯一識(shí)別碼,它由40位16進(jìn)制數(shù)的字母和數(shù)字組成(越獄的設(shè)備通過(guò)某些工具可以改變?cè)O(shè)備的UDID)。移動(dòng)網(wǎng)絡(luò)可利用UDID來(lái)識(shí)別移動(dòng)設(shè)備,但是,從IOS5.0(2011年8月份)開(kāi)始,蘋(píng)果宣布將不再支持用uniqueIdentifier方法獲取設(shè)備的UDID,iOS5以下是可以用的。蘋(píng)果從iOS5開(kāi)始就移除了通過(guò)代碼訪問(wèn)UDID的權(quán)限。從2013年5月1日起,試圖訪問(wèn)UIDIDs的程序?qū)⒉辉俦粚徍送ㄟ^(guò),替代的方案是開(kāi)發(fā)者應(yīng)該使用“在iOS 6中介紹的Vendor或Advertising標(biāo)示符”。所以UDID是絕對(duì)是不能再使用了。
//UUID , 已廢除NSString*udid = [[UIDevice currentDevice] uniqueIdentifier];
1
2
1
2
為什么蘋(píng)果反對(duì)開(kāi)發(fā)人員使用UDID?
iOS 2.0版本以后UIDevice提供一個(gè)獲取設(shè)備唯一標(biāo)識(shí)符的方法uniqueIdentifier,通過(guò)該方法我們可以獲取設(shè)備的序列號(hào),這個(gè)也是目前為止唯一可以確認(rèn)唯一的標(biāo)示符。 許多開(kāi)發(fā)者把UDID跟用戶的真實(shí)姓名、密碼、住址、其它數(shù)據(jù)關(guān)聯(lián)起來(lái);網(wǎng)絡(luò)窺探者會(huì)從多個(gè)應(yīng)用收集這些數(shù)據(jù),然后順藤摸瓜得到這個(gè)人的許多隱私數(shù)據(jù)。同時(shí)大部分應(yīng)用確實(shí)在頻繁傳輸U(kuò)DID和私人信息。 為了避免集體訴訟,蘋(píng)果最終決定在iOS 5 的時(shí)候,將這一慣例廢除,開(kāi)發(fā)者被引導(dǎo)生成一個(gè)唯一的標(biāo)識(shí)符,只能檢測(cè)應(yīng)用程序,其他的信息不提供?,F(xiàn)在應(yīng)用試圖獲取UDID已被禁止且不允許上架。
二.UUID(Universally Unique Identifier)
UUID是Universally Unique Identifier的縮寫(xiě),中文意思是通用唯一識(shí)別碼。它是讓分布式系統(tǒng)中的所有元素,都能有唯一的辨識(shí)資訊,而不需要透過(guò)中央控制端來(lái)做辨識(shí)資訊的指定。這樣,每個(gè)人都可以建立不與其它人沖突的 UUID。在此情況下,就不需考慮數(shù)據(jù)庫(kù)建立時(shí)的名稱重復(fù)問(wèn)題。蘋(píng)果公司建議使用UUID為應(yīng)用生成唯一標(biāo)識(shí)字符串。
獲得的UUID值系統(tǒng)沒(méi)有存儲(chǔ), 而且每次調(diào)用得到UUID,系統(tǒng)都會(huì)返回一個(gè)新的唯一標(biāo)示符。如果你希望存儲(chǔ)這個(gè)標(biāo)示符,那么需要自己將其存儲(chǔ)到NSUserDefaults, Keychain, Pasteboard或其它地方。
從iOS2.0開(kāi)始,CFUUID就已經(jīng)出現(xiàn)了。它是CoreFoundatio包的一部分,因此API屬于C語(yǔ)言風(fēng)格。CFUUIDCreate 方法用來(lái)創(chuàng)建CFUUIDRef,并且可以獲得一個(gè)相應(yīng)的NSString,如下代碼:
CFUUIDRef cfuuid = CFUUIDCreate(kCFAllocatorDefault);NSString*cfuuidString = (NSString*)CFBridgingRelease(CFUUIDCreateString(kCFAllocatorDefault, cfuuid));
1
1
獲得的這個(gè)CFUUID值系統(tǒng)并沒(méi)有存儲(chǔ)。每次調(diào)用CFUUIDCreate,系統(tǒng)都會(huì)返回一個(gè)新的唯一標(biāo)示符。如果你希望存儲(chǔ)這個(gè)標(biāo)示符,那么需要自己將其存儲(chǔ)到NSUserDefaults, Keychain, Pasteboard或其它地方。
NSUUID在iOS 6中才出現(xiàn),這跟CFUUID幾乎完全一樣,只不過(guò)它是Objective-C接口。+ (id)UUID 是一個(gè)類方法,調(diào)用該方法可以獲得一個(gè)UUID。通過(guò)下面的代碼可以獲得一個(gè)UUID字符串:
NSString *uuid=[[NSUUID UUID] UUIDString];
1
1
跟CFUUID一樣,這個(gè)值系統(tǒng)也不會(huì)存儲(chǔ),每次調(diào)用的時(shí)候都會(huì)獲得一個(gè)新的唯一標(biāo)示符。如果要存儲(chǔ)的話,你需要自己存儲(chǔ)。在我讀取NSUUID時(shí),注意到獲取到的這個(gè)值跟CFUUID完全一樣(不過(guò)也可能不一樣)
在iOS 5發(fā)布時(shí),uniqueIdentifier被棄用了,這引起了廣大開(kāi)發(fā)者需要尋找一個(gè)可以替代UDID,并且不受蘋(píng)果控制的方案。由此OpenUDID成為了當(dāng)時(shí)使用最廣泛的開(kāi)源UDID替代方案。OpenUDID在工程中實(shí)現(xiàn)起來(lái)非常簡(jiǎn)單,并且還支持一系列的廣告提供商。
OpenUDID利用了一個(gè)非常巧妙的方法在不同程序間存儲(chǔ)標(biāo)示符 — 在粘貼板中用了一個(gè)特殊的名稱來(lái)存儲(chǔ)標(biāo)示符。通過(guò)這種方法,別的程序(同樣使用了OpenUDID)知道去什么地方獲取已經(jīng)生成的標(biāo)示符(而不用再生成一個(gè)新的)。而且根據(jù)貢獻(xiàn)者的代碼和方法,和一些開(kāi)發(fā)者的經(jīng)驗(yàn),如果把使用了OpenUDID方案的應(yīng)用全部都刪除,再重新獲取OpenUDID,此時(shí)的OpenUDID就跟以前的不一樣??梢?jiàn),這種方法還是不保險(xiǎn)。
但是OpenUDID庫(kù)早已經(jīng)棄用了, 在其官方的博客中也指明了, 停止維護(hù)OpenUDID的原因是為了更好的向蘋(píng)果的舉措靠攏, 還指明了MAC Address不是一個(gè)好的選擇。
MAC(Medium/Media Access Control)地址,用來(lái)表示互聯(lián)網(wǎng)上每一個(gè)站點(diǎn)的標(biāo)識(shí)符,采用十六進(jìn)制數(shù)表示,共六個(gè)字節(jié)(48位)。其中,前三個(gè)字節(jié)是由IEEE的注冊(cè)管理機(jī)構(gòu) RA負(fù)責(zé)給不同廠家分配的代碼(高位24位),也稱為“編制上唯一的標(biāo)識(shí)符” (Organizationally Unique Identifier),后三個(gè)字節(jié)(低位24位)由各廠家自行指派給生產(chǎn)的適配器接口,稱為擴(kuò)展標(biāo)識(shí)符(唯一性)。
MAC地址在網(wǎng)絡(luò)上用來(lái)區(qū)分設(shè)備的唯一性,接入網(wǎng)絡(luò)的設(shè)備都有一個(gè)MAC地址,他們肯定都是不同的,是唯一的。一部iPhone上可能有多個(gè)MAC地址,包括WIFI的、SIM的等,但是iTouch和iPad上就有一個(gè)WIFI的,因此只需獲取WIFI的MAC地址就好了,也就是en0的地址。
形象的說(shuō),MAC地址就如同我們身份證上的身份證號(hào)碼,具有全球唯一性。這樣就可以非常好的標(biāo)識(shí)設(shè)備唯一性,類似與蘋(píng)果設(shè)備的UDID號(hào),通常的用途有:
1)用于一些統(tǒng)計(jì)與分析目的,利用用戶的操作習(xí)慣和數(shù)據(jù)更好的規(guī)劃產(chǎn)品;
2)作為用戶ID來(lái)唯一識(shí)別用戶,可以用游客身份使用app又能在服務(wù)器端保存相應(yīng)的信息,省去用戶名、密碼等注冊(cè)過(guò)程。
2.如何使用Mac地址生成設(shè)備的唯一標(biāo)識(shí)呢?
主要分三種:
1、直接使用“MAC Address”
2、使用“MD5(MAC Address)”
3、使用“MD5(Mac Address+bundle_id)”獲得“機(jī)器+應(yīng)用”的唯一標(biāo)識(shí)(bundle_id 是應(yīng)用的唯一標(biāo)識(shí))
iOS7之前,因?yàn)镸ac地址是唯一的, 一般app開(kāi)發(fā)者會(huì)采取第3種方式來(lái)識(shí)別安裝對(duì)應(yīng)app的設(shè)備。為什么會(huì)使用它?在iOS5之前,都是使用UDID的,后來(lái)被禁用。蘋(píng)果推薦使用UUID 但是也有諸多問(wèn)題,從而使用MAC地址。而MAC地址跟UDID一樣,存在隱私問(wèn)題,現(xiàn)在蘋(píng)果新發(fā)布的iOS7上,如果請(qǐng)求Mac地址都會(huì)返回一個(gè)固定值,那么Mac Address+bundle_id這個(gè)值大家的設(shè)備都變成一致的啦,跟UDID一樣相當(dāng)于被禁用, 所以Mac Address 是不能夠被使用為獲取設(shè)備唯一標(biāo)識(shí)的。
五.廣告標(biāo)示符(IDFA-identifierForIdentifier)
廣告標(biāo)示符,在同一個(gè)設(shè)備上的所有App都會(huì)取到相同的值,是蘋(píng)果專門(mén)給各廣告提供商用來(lái)追蹤用戶而設(shè)的。但好在Apple默認(rèn)是允許追蹤的,而且一般用戶都不知道有這么個(gè)設(shè)置,所以基本上用來(lái)監(jiān)測(cè)推廣效果,是戳戳有余了。
它是iOS 6中另外一個(gè)新的方法,提供了一個(gè)方法advertisingIdentifier,通過(guò)調(diào)用該方法會(huì)返回一個(gè)NSUUID實(shí)例,最后可以獲得一個(gè)UUID,由系統(tǒng)存儲(chǔ)著的。
#importNSString*adId = [[[ASIdentifierManager sharedManager] advertisingIdentifier] UUIDString];
1
2
1
2
不過(guò)即使這是由系統(tǒng)存儲(chǔ)的,但是有幾種情況下,會(huì)重新生成廣告標(biāo)示符。如果用戶完全重置系統(tǒng)((設(shè)置程序 -> 通用 -> 還原 -> 還原位置與隱私) ,這個(gè)廣告標(biāo)示符會(huì)重新生成。另外如果用戶明確的還原廣告(設(shè)置程序-> 通用 -> 關(guān)于本機(jī) -> 廣告 -> 還原廣告標(biāo)示符) ,那么廣告標(biāo)示符也會(huì)重新生成。
關(guān)于廣告標(biāo)示符的還原,有一點(diǎn)需要注意:如果程序在后臺(tái)運(yùn)行,此時(shí)用戶“還原廣告標(biāo)示符”,然后再回到程序中,此時(shí)獲取廣 告標(biāo)示符并不會(huì)立即獲得還原后的標(biāo)示符。必須要終止程序,然后再重新啟動(dòng)程序,才能獲得還原后的廣告標(biāo)示符。
所以IDFA也不可以作為獲取唯一標(biāo)識(shí)的方法,來(lái)識(shí)別用戶。
六.Vendor標(biāo)示符 (IDFV-identifierForVendor)
Vendor標(biāo)示符,是給Vendor標(biāo)識(shí)用戶用的,每個(gè)設(shè)備在所屬同一個(gè)Vender的應(yīng)用里,都有相同的值。其中的Vender是指應(yīng)用提供商,但準(zhǔn)確點(diǎn)說(shuō),是通過(guò)BundleID的反轉(zhuǎn)的前兩部分進(jìn)行匹配,如果相同就是同一個(gè)Vender,例如對(duì)于com.taobao.app1, com.taobao.app2 這兩個(gè)BundleID來(lái)說(shuō),就屬于同一個(gè)Vender,共享同一個(gè)IDFV的值。和IDFA不同的是,IDFV的值是一定能取到的,所以非常適合于作為內(nèi)部用戶行為分析的主id,來(lái)標(biāo)識(shí)用戶,替代OpenUDID。
它是iOS 6中新增的,跟advertisingIdentifier一樣,該方法返回的是一個(gè) NSUUID對(duì)象,可以獲得一個(gè)UUID。如果滿足條件“相同的一個(gè)程序里面-相同的vendor-相同的設(shè)備”,那么獲取到的這個(gè)屬性值就不會(huì)變。如果是“相同的程序-相同的設(shè)備-不同的vendor,或者是相同的程序-不同的設(shè)備-無(wú)論是否相同的vendor”這樣的情況,那么這個(gè)值是不會(huì)相同的。
NSString *strIDFV=[[[UIDevice currentDevice] identifierForVendor] UUIDString];
1
1
但是如果用戶將屬于此Vender的所有App卸載,則IDFV的值會(huì)被重置,即再重裝此Vender的App,IDFV的值和之前不同。
推送token+bundle_id的方法:
1、應(yīng)用中增加推送用來(lái)獲取token
2、獲取應(yīng)用bundle_id
3、根據(jù)token+bundle_id進(jìn)行散列運(yùn)算
apple push token保證設(shè)備唯一,但必須有網(wǎng)絡(luò)情況下才能工作,該方法并不是依賴于設(shè)備本身,而是依賴于apple push機(jī)制,所以當(dāng)蘋(píng)果push做出改變時(shí), 你獲取所謂的唯一標(biāo)識(shí)也就隨之失效了。所以此方法還是不可取的。
八. NSUUID, CFUUID, IDFA, IDFV獲取的標(biāo)識(shí)對(duì)比
首次運(yùn)行
NSUUID:9D820D3A-4429-4918-97F7-A69588B388A4
CFUUID:80F961D0-1E6A-4ECD-A0A9-F58ED858FE20
IDFA:687E6A90-50A3-4424-871C-BE255D050AFD
IDFV:8E740A99-283B-4F6A-87EF-443FB7778488
二次運(yùn)行
NSUUID:23AB8D3D-4F1D-45E2-8BD7-83B451125326
CFUUID:14DDBFCF-67A6-46B7-BB48-4EF2ADC5429F
IDFA:687E6A90-50A3-4424-871C-BE255D050AFD
IDFV:8E740A99-283B-4F6A-87EF-443FB7778488
卸載后, 重新安裝運(yùn)行
NSUUID:BD934F9C-B7EC-4BD1-B65E-964C66537CAB
CFUUID:29654DE0-AC93-40F9-98AB-1E10A271AF8D
IDFA:687E6A90-50A3-4424-871C-BE255D050AFD
IDFV:8E740A99-283B-4F6A-87EF-443FB7778488
重啟后運(yùn)行
NSUUID:82711557-3A17-4B82-8F18-09AADF9DD37B
CFUUID:FFBC73EC-CFBE-414C-870E-77C0714E0347
IDFA:687E6A90-50A3-4424-871C-BE255D050AFD
IDFV:8E740A99-283B-4F6A-87EF-443FB7778488
說(shuō)了這么多, 才發(fā)現(xiàn)原來(lái)沒(méi)有一種方法是可行的。沒(méi)錯(cuò), 其實(shí)自從蘋(píng)果廢除UDID后, 就不能達(dá)到獲取設(shè)備真正的唯一標(biāo)識(shí)了。因?yàn)檫@些方法中導(dǎo)致獲取的唯一標(biāo)示產(chǎn)生改變的原因, 或是重新調(diào)用方法, 或是重啟設(shè)備, 或是卸載應(yīng)用, 或是還原某些標(biāo)識(shí), 或者刷新系統(tǒng)…
所以, 不能達(dá)到從根本上獲取唯一標(biāo)識(shí), 我們只能做到盡可能接近。下面是我用過(guò)的方法。
如何正確的獲取設(shè)備的唯一標(biāo)識(shí)
我用的方法是將獲取的UUID永久存儲(chǔ)在設(shè)備的KeyChain中, 這個(gè)方法在應(yīng)用第一次啟動(dòng)時(shí), 將獲取的UUID存儲(chǔ)進(jìn)KeyChain中, 每次取的時(shí)候, 檢查本地鑰匙串中有沒(méi)有, 如果沒(méi)有則需要將獲取的UUID存儲(chǔ)進(jìn)去。當(dāng)你重啟設(shè)備, 卸載應(yīng)用再次安裝,都不影響, 只是當(dāng)設(shè)備刷機(jī)時(shí), KeyChain會(huì)清空, 才會(huì)消失, 才會(huì)失效。
不只是這一種方法, 你也可以保存除UUID之外,其他合適的標(biāo)識(shí), 但利用KeyChain去存儲(chǔ)標(biāo)識(shí)的方式應(yīng)該是最接近的。
利用keyChain和UUID永久獲得設(shè)備的唯一標(biāo)識(shí)
開(kāi)發(fā)者可以在應(yīng)用第一次啟動(dòng)時(shí)調(diào)用一次,然后將該串存儲(chǔ)起來(lái),以便以后替代UDID來(lái)使用。但是,如果用戶刪除該應(yīng)用再次安裝時(shí),又會(huì)生成新的字符串,所以不能保證唯一識(shí)別該設(shè)備。這就需要各路高手想出各種解決方案。所以,之前很多應(yīng)用就采用MAC Address。但是現(xiàn)在如果用戶升級(jí)到iOS7(及其以后的蘋(píng)果系統(tǒng))后,他們機(jī)子的MAC Address就是一樣的,沒(méi)辦法做區(qū)分,只能棄用此方法,重新使用UUID來(lái)標(biāo)識(shí)。如果使用UUID,就要考慮應(yīng)用被刪除后再重新安裝時(shí)的處理。
一、在應(yīng)用間利用KeyChain共享數(shù)據(jù)
我們可以把KeyChain理解為一個(gè)Dictionary,所有數(shù)據(jù)都以key-value的形式存儲(chǔ),可以對(duì)這個(gè)Dictionary進(jìn)行add、update、get、delete這四個(gè)操作。對(duì)于每一個(gè)應(yīng)用來(lái)說(shuō),KeyChain都有兩個(gè)訪問(wèn)區(qū),私有區(qū)和公共區(qū)。私有區(qū)是一個(gè)sandbox,本程序存儲(chǔ)的任何數(shù)據(jù)都對(duì)其他程序不可見(jiàn)。而要想在將存儲(chǔ)的內(nèi)容放在公共區(qū),需要先聲明公共區(qū)的名稱,官方文檔管這個(gè)名稱叫“keychain access group”,聲明的方法是新建一個(gè)plist文件,名字隨便起,內(nèi)容如下:
“yourAppID.com.yourCompany.whatever”就是你要起的公共區(qū)名稱,除了whatever字段可以隨便定之外,其他的都必須如實(shí)填寫(xiě)。這個(gè)文件的路徑要配置在 Project->build setting->Code Signing Entitlements里,否則公共區(qū)無(wú)效,配置好后,須用你正式的證書(shū)簽名編譯才可通過(guò),否則xcode會(huì)彈框告訴你code signing有問(wèn)題。所以,蘋(píng)果限制了你只能同公司的產(chǎn)品共享KeyChain數(shù)據(jù),別的公司訪問(wèn)不了你公司產(chǎn)品的KeyChain。
二、保存私密信息
iOS的keychain服務(wù)提供了一種安全的保存私密信息(密碼,序列號(hào),證書(shū)等)的方式,每個(gè)ios程序都有一個(gè)獨(dú)立的keychain存儲(chǔ)。相對(duì)于NSUserDefaults、文件保存等一般方式,keychain保存更為安全,而且keychain里保存的信息不會(huì)因App被刪除而丟失,所以在重裝App后,keychain里的數(shù)據(jù)還能使用。
首先, 我先推薦兩篇文章,里面介紹了如利用keyChain和UUID永久獲得設(shè)備的唯一標(biāo)識(shí),Classes/KeychainItemWrapper.m和如何使用KeyChain保存和獲取UDID。
然后, 再介紹下我使用的方法以及封裝的工具類, 在應(yīng)用里使用使用keyChain,我們需要導(dǎo)入Security.framework。下面介紹下, 我在其他庫(kù)基礎(chǔ)上封裝的一個(gè)獲取唯一標(biāo)識(shí)的工具類:
#import#importNSString *constKEY_UDID_INSTEAD = @"com.myapp.udid.test";@interfaceLZKeychain : NSObject/**
本方法是得到 UUID 后存入系統(tǒng)中的 keychain 的方法
不用添加 plist 文件
程序刪除后重裝,仍可以得到相同的唯一標(biāo)示
但是當(dāng)系統(tǒng)升級(jí)或者刷機(jī)后,系統(tǒng)中的鑰匙串會(huì)被清空,此時(shí)本方法失效
*/+(NSString *)getDeviceIDInKeychain;@end
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#import"LZKeychain.h"@implementationLZKeychain+(NSString*)getDeviceIDInKeychain{NSString*getUDIDInKeychain = (NSString*)[LZKeychain load:KEY_UDID_INSTEAD];NSLog(@"從keychain中獲取到的 UDID_INSTEAD %@",getUDIDInKeychain);if(!getUDIDInKeychain ||[getUDIDInKeychain isEqualToString:@""]||[getUDIDInKeychain isKindOfClass:[NSNull class]]) {? ? ? ? CFUUIDRef puuid = CFUUIDCreate(nil);? ? ? ? CFStringRef uuidString = CFUUIDCreateString(nil, puuid );NSString* result = (NSString*)CFBridgingRelease(CFStringCreateCopy(NULL, uuidString));? ? ? ? CFRelease(puuid);? ? ? ? CFRelease(uuidString);NSLog(@"\n \n \n _____重新存儲(chǔ) UUID _____\n \n \n? %@",result);? ? ? ? [LZKeychain save:KEY_UDID_INSTEAD data:result];? ? ? ? getUDIDInKeychain = (NSString*)[LZKeychain load:KEY_UDID_INSTEAD];? ? }NSLog(@"最終 ———— UDID_INSTEAD %@",getUDIDInKeychain);returngetUDIDInKeychain;}#pragma mark - private+ (NSMutableDictionary*)getKeychainQuery:(NSString*)service {return[NSMutableDictionarydictionaryWithObjectsAndKeys:? ? ? ? ? ? (id)kSecClassGenericPassword,(id)kSecClass,? ? ? ? ? ? service, (id)kSecAttrService,? ? ? ? ? ? service, (id)kSecAttrAccount,? ? ? ? ? ? (id)kSecAttrAccessibleAfterFirstUnlock,(id)kSecAttrAccessible,nil];}+ (void)save:(NSString*)service data:(id)data {//Get search dictionaryNSMutableDictionary*keychainQuery = [selfgetKeychainQuery:service];//Delete old item before add new itemSecItemDelete((CFDictionaryRef)keychainQuery);//Add new object to search dictionary(Attention:the data format)[keychainQuery setObject:[NSKeyedArchiver archivedDataWithRootObject:data] forKey:(id)kSecValueData];//Add item to keychain with the search dictionarySecItemAdd((CFDictionaryRef)keychainQuery,NULL);}+ (id)load:(NSString*)service {idret =nil;NSMutableDictionary*keychainQuery = [selfgetKeychainQuery:service];//Configure the search setting//Since in our simple case we are expecting only a single attribute to be returned (the password) we can set the attribute kSecReturnData to kCFBooleanTrue[keychainQuery setObject:(id)kCFBooleanTrue forKey:(id)kSecReturnData];? ? [keychainQuery setObject:(id)kSecMatchLimitOne forKey:(id)kSecMatchLimit];? ? CFDataRef keyData =NULL;if(SecItemCopyMatching((CFDictionaryRef)keychainQuery, (CFTypeRef *)&keyData) == noErr) {@try{? ? ? ? ? ? ret = [NSKeyedUnarchiver unarchiveObjectWithData:(NSData *)keyData];? ? ? ? }@catch(NSException*e) {NSLog(@"Unarchive of %@ failed: %@", service, e);? ? ? ? }@finally{? ? ? ? }? ? }if(keyData)? ? ? ? CFRelease(keyData);returnret;}+ (void)delete:(NSString*)service {NSMutableDictionary*keychainQuery = [selfgetKeychainQuery:service];? ? SecItemDelete((CFDictionaryRef)keychainQuery);}@end
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
參考資料: