天天看點

iOS擷取裝置的唯一辨別的方法代碼說完了,下面說說原理:實作代碼

網上有很多的擷取方法,最常用的也是蘋果推薦的方法是:

NSString *uuid0 = [[[UIDevice currentDevice] identifierForVendor] UUIDString];
NSLog(@"--1唯一識别碼uuid-->%@", uuid0);           

也有文章說:

CFUUIDRef puuid = CFUUIDCreate( nil );
    CFStringRef uuidString = CFUUIDCreateString( nil, puuid );
    NSString * uuid = (NSString *)CFStringCreateCopy( NULL, uuidString);
    CFRelease(puuid);
    CFRelease(uuidString);
    NSLog(@"----2唯一識别碼uuid-->%@", uuid);
    return [uuid autorelease];           

但是上面的方法,在app重新安裝之後,會重新生成新的uuid,這個uuid就不是唯一的了。

要想解決上面的問題,就隻能是把uuid儲存到手機檔案裡面,隻要不刷機,下一次擷取的時候,先判斷檔案裡面是否存在,沒有則寫入,有則直接讀取;隻有這樣才可以保證uuid的唯一,解除安裝重裝app也不會受到影響,下面是實作代碼:

//AppController.h
#import <Foundation/Foundation.h>
#import <Security/Security.h>

- (NSString *)getDeviceIDInKeychain;



//AppController.mm
NSString * const KEY_UDID_INSTEAD = @"com.myapp.udid.test";

-(NSString *)getDeviceIDInKeychain
{
    NSString *getUDIDInKeychain = (NSString *)[AppController 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 _____重新存儲 UUID _____\n \n \n  %@",result);
        [AppController save:KEY_UDID_INSTEAD data:result];
        getUDIDInKeychain = (NSString *)[AppController load:KEY_UDID_INSTEAD];
    }
    NSLog(@"最終 ———— UDID_INSTEAD %@",getUDIDInKeychain);
    return getUDIDInKeychain;
}

//調用測試
[self getDeviceIDInKeychain];


//輸出結果資訊(前面兩次是重新開機app,第三次是覆寫安裝,最後一次是解除安裝安裝)
2019-09-10 17:25:16.234553+0800 llh_cocos04-mobile[621:123195] [DYMTLInitPlatform] platform initialization successful
2019-09-10 17:25:16.302116+0800 llh_cocos04-mobile[621:123075] [NetworkInfo] Signal strength query returned error: Error Domain=NSPOSIXErrorDomain Code=13 "Permission denied", descriptor: <CTServiceDescriptor 0x282ca7800, domain=1, instance=1>
2019-09-10 17:25:16.475819+0800 llh_cocos04-mobile[621:123075] 裝置型号-->iPhone 6s
2019-09-10 17:25:16.476890+0800 llh_cocos04-mobile[621:123075] 目前時間-->17:25
2019-09-10 17:25:16.534251+0800 llh_cocos04-mobile[621:123075] iPhone名稱-->“tony”的 iPhone
2019-09-10 17:25:16.534323+0800 llh_cocos04-mobile[621:123075] 電池電量-->-1.000000
2019-09-10 17:25:16.536294+0800 llh_cocos04-mobile[621:123075] --1唯一識别碼uuid-->4E0A38DB-65D5-4C26-8FE3-0A35373D8B9E
2019-09-10 17:25:16.536363+0800 llh_cocos04-mobile[621:123075] ----2唯一識别碼uuid-->AFD1D2DA-4476-4F9E-881A-D86AFE1C7D55
2019-09-10 17:25:16.536397+0800 llh_cocos04-mobile[621:123075] screenX-->1136.000000
2019-09-10 17:25:16.536446+0800 llh_cocos04-mobile[621:123075] screenY-->640.000000
2019-09-10 17:25:16.536472+0800 llh_cocos04-mobile[621:123075] 裝置wifi----
2019-09-10 17:25:16.537042+0800 llh_cocos04-mobile[621:123075] 裝置wifi-->3
2019-09-10 17:25:16.540024+0800 llh_cocos04-mobile[621:123075] 從keychain中擷取到的 UDID_INSTEAD (null)
2019-09-10 17:25:16.540094+0800 llh_cocos04-mobile[621:123075]  
 _____重新存儲 UUID _____
  F6E5B3AC-776C-429D-AF0E-EA397420FBE0
2019-09-10 17:25:16.549913+0800 llh_cocos04-mobile[621:123075] 最終 ———— UDID_INSTEAD F6E5B3AC-776C-429D-AF0E-EA397420FBE0
2019-09-10 17:25:16.589835+0800 llh_cocos04-mobile[621:123075] Metal GPU Frame Capture Enabled





2019-09-10 17:26:53.210949+0800 llh_cocos04-mobile[628:123606] [DYMTLInitPlatform] platform initialization successful
2019-09-10 17:26:53.264851+0800 llh_cocos04-mobile[628:123562] [NetworkInfo] Signal strength query returned error: Error Domain=NSPOSIXErrorDomain Code=13 "Permission denied", descriptor: <CTServiceDescriptor 0x2807d64e0, domain=1, instance=1>
2019-09-10 17:26:53.309921+0800 llh_cocos04-mobile[628:123562] 裝置型号-->iPhone 6s
2019-09-10 17:26:53.311062+0800 llh_cocos04-mobile[628:123562] 目前時間-->17:26
2019-09-10 17:26:53.312147+0800 llh_cocos04-mobile[628:123562] iPhone名稱-->“tony”的 iPhone
2019-09-10 17:26:53.312185+0800 llh_cocos04-mobile[628:123562] 電池電量-->-1.000000
2019-09-10 17:26:53.313867+0800 llh_cocos04-mobile[628:123562] --1唯一識别碼uuid-->4E0A38DB-65D5-4C26-8FE3-0A35373D8B9E
2019-09-10 17:26:53.313936+0800 llh_cocos04-mobile[628:123562] ----2唯一識别碼uuid-->77162EB6-FA18-45E7-9E37-0BCEC993CB12
2019-09-10 17:26:53.313956+0800 llh_cocos04-mobile[628:123562] screenX-->1136.000000
2019-09-10 17:26:53.313974+0800 llh_cocos04-mobile[628:123562] screenY-->640.000000
2019-09-10 17:26:53.313991+0800 llh_cocos04-mobile[628:123562] 裝置wifi----
2019-09-10 17:26:53.314451+0800 llh_cocos04-mobile[628:123562] 裝置wifi-->3
2019-09-10 17:26:53.319100+0800 llh_cocos04-mobile[628:123562] 從keychain中擷取到的 UDID_INSTEAD F6E5B3AC-776C-429D-AF0E-EA397420FBE0
2019-09-10 17:26:53.319140+0800 llh_cocos04-mobile[628:123562] 最終 ———— UDID_INSTEAD F6E5B3AC-776C-429D-AF0E-EA397420FBE0
2019-09-10 17:26:53.325320+0800 llh_cocos04-mobile[628:123562] Metal GPU Frame Capture Enabled



2019-09-10 17:29:08.585556+0800 llh_cocos04-mobile[634:124020] [DYMTLInitPlatform] platform initialization successful
2019-09-10 17:29:08.641030+0800 llh_cocos04-mobile[634:123982] [NetworkInfo] Signal strength query returned error: Error Domain=NSPOSIXErrorDomain Code=13 "Permission denied", descriptor: <CTServiceDescriptor 0x280438b80, domain=1, instance=1>
2019-09-10 17:29:08.792096+0800 llh_cocos04-mobile[634:123982] 裝置型号-->iPhone 6s
2019-09-10 17:29:08.793194+0800 llh_cocos04-mobile[634:123982] 目前時間-->17:29
2019-09-10 17:29:08.827365+0800 llh_cocos04-mobile[634:123982] iPhone名稱-->“tony”的 iPhone
2019-09-10 17:29:08.827432+0800 llh_cocos04-mobile[634:123982] 電池電量-->-1.000000
2019-09-10 17:29:08.829339+0800 llh_cocos04-mobile[634:123982] --1唯一識别碼uuid-->4E0A38DB-65D5-4C26-8FE3-0A35373D8B9E
2019-09-10 17:29:08.829406+0800 llh_cocos04-mobile[634:123982] ----2唯一識别碼uuid-->2A124975-66DE-45E3-86AE-CB08C002D764
2019-09-10 17:29:08.829432+0800 llh_cocos04-mobile[634:123982] screenX-->1136.000000
2019-09-10 17:29:08.829453+0800 llh_cocos04-mobile[634:123982] screenY-->640.000000
2019-09-10 17:29:08.829478+0800 llh_cocos04-mobile[634:123982] 裝置wifi----
2019-09-10 17:29:08.829819+0800 llh_cocos04-mobile[634:123982] 裝置wifi-->3
2019-09-10 17:29:08.834254+0800 llh_cocos04-mobile[634:123982] 從keychain中擷取到的 UDID_INSTEAD F6E5B3AC-776C-429D-AF0E-EA397420FBE0
2019-09-10 17:29:08.834305+0800 llh_cocos04-mobile[634:123982] 最終 ———— UDID_INSTEAD F6E5B3AC-776C-429D-AF0E-EA397420FBE0
2019-09-10 17:29:08.876675+0800 llh_cocos04-mobile[634:123982] Metal GPU Frame Capture Enabled



2019-09-10 17:38:59.482604+0800 llh_cocos04-mobile[655:127285] [DYMTLInitPlatform] platform initialization successful
2019-09-10 17:38:59.555159+0800 llh_cocos04-mobile[655:127166] [NetworkInfo] Signal strength query returned error: Error Domain=NSPOSIXErrorDomain Code=13 "Permission denied", descriptor: <CTServiceDescriptor 0x280b215c0, domain=1, instance=1>
2019-09-10 17:38:59.725164+0800 llh_cocos04-mobile[655:127166] 裝置型号-->iPhone 6s
2019-09-10 17:38:59.726342+0800 llh_cocos04-mobile[655:127166] 目前時間-->17:38
2019-09-10 17:38:59.760048+0800 llh_cocos04-mobile[655:127166] iPhone名稱-->“tony”的 iPhone
2019-09-10 17:38:59.760111+0800 llh_cocos04-mobile[655:127166] 電池電量-->-1.000000
2019-09-10 17:38:59.761820+0800 llh_cocos04-mobile[655:127166] --1唯一識别碼uuid-->8C13DB92-4239-4CAA-BE8D-57D34F057226
2019-09-10 17:38:59.761881+0800 llh_cocos04-mobile[655:127166] ----2唯一識别碼uuid-->9048C252-3286-4356-8D28-3D9E06ECABC0
2019-09-10 17:38:59.761906+0800 llh_cocos04-mobile[655:127166] screenX-->1136.000000
2019-09-10 17:38:59.761931+0800 llh_cocos04-mobile[655:127166] screenY-->640.000000
2019-09-10 17:38:59.761968+0800 llh_cocos04-mobile[655:127166] 裝置wifi----
2019-09-10 17:38:59.762282+0800 llh_cocos04-mobile[655:127166] 裝置wifi-->3
2019-09-10 17:38:59.767251+0800 llh_cocos04-mobile[655:127166] 從keychain中擷取到的 UDID_INSTEAD F6E5B3AC-776C-429D-AF0E-EA397420FBE0
2019-09-10 17:38:59.767297+0800 llh_cocos04-mobile[655:127166] 最終 ———— UDID_INSTEAD F6E5B3AC-776C-429D-AF0E-EA397420FBE0
2019-09-10 17:38:59.806945+0800 llh_cocos04-mobile[655:127166] Metal GPU Frame Capture Enabled           

代碼說完了,下面說說原理:

一.UDID(Unique Device Identifier)

UDID的全稱是Unique Device Identifier,它就是蘋果iOS裝置的唯一識别碼,它由40位16進制數的字母和數字組成(越獄的裝置通過某些工具可以改變裝置的UDID)。移動網絡可利用UDID來識别移動裝置,但是,從IOS5.0(2011年8月份)開始,蘋果宣布将不再支援用uniqueIdentifier方法擷取裝置的UDID,iOS5以下是可以用的。蘋果從iOS5開始就移除了通過代碼通路UDID的權限。從2013年5月1日起,試圖通路UIDIDs的程式将不再被稽核通過,替代的方案是開發者應該使用“在iOS 6中介紹的Vendor或Advertising标示符”。是以UDID是絕對是不能再使用了.

//UUID , 已廢除
NSString *udid = [[UIDevice currentDevice] uniqueIdentifier];           

為什麼蘋果反對開發人員使用UDID?

iOS 2.0版本以後UIDevice提供一個擷取裝置唯一辨別符的方法uniqueIdentifier,通過該方法我們可以擷取裝置的序列号,這個也是目前為止唯一可以确認唯一的标示符。 許多開發者把UDID跟使用者的真實姓名、密碼、住址、其它資料關聯起來;網絡窺探者會從多個應用收集這些資料,然後順藤摸瓜得到這個人的許多隐私資料。同時大部分應用确實在頻繁傳輸UDID和私人資訊。 為了避免集體訴訟,蘋果最終決定在iOS 5 的時候,将這一慣例廢除,開發者被引導生成一個唯一的辨別符,隻能檢測應用程式,其他的資訊不提供。現在應用試圖擷取UDID已被禁止且不允許上架。

二.UUID(Universally Unique Identifier)

UUID是Universally Unique Identifier的縮寫,中文意思是通用唯一識别碼。它是讓分布式系統中的所有元素,都能有唯一的辨識資訊,而不需要透過中央控制端來做辨識資訊的指定。這樣,每個人都可以建立不與其它人沖突的 UUID。在此情況下,就不需考慮資料庫建立時的名稱重複問題。蘋果公司建議使用UUID為應用生成唯一辨別字元串。

獲得的UUID值系統沒有存儲, 而且每次調用得到UUID,系統都會傳回一個新的唯一标示符。如果你希望存儲這個标示符,那麼需要自己将其存儲到NSUserDefaults, Keychain, Pasteboard或其它地方。

  • CFUUID

    從iOS2.0開始,CFUUID就已經出現了。它是CoreFoundatio包的一部分,是以API屬于C語言風格。CFUUIDCreate 方法用來建立CFUUIDRef,并且可以獲得一個相應的NSString,如下代碼:

    CFUUIDRef cfuuid = CFUUIDCreate(kCFAllocatorDefault);
    NSString *cfuuidString = (NSString*)CFBridgingRelease(CFUUIDCreateString(kCFAllocatorDefault, cfuuid));           
    獲得的這個CFUUID值系統并沒有存儲。每次調用CFUUIDCreate,系統都會傳回一個新的唯一标示符。如果你希望存儲這個标示符,那麼需要自己将其存儲到NSUserDefaults, Keychain, Pasteboard或其它地方。
  • NSUUID

    NSUUID在iOS 6中才出現,這跟CFUUID幾乎完全一樣,隻不過它是Objective-C接口。+ (id)UUID 是一個類方法,調用該方法可以獲得一個UUID。通過下面的代碼可以獲得一個UUID字元串:

    NSString *uuid = [[NSUUID UUID] UUIDString];           
    跟CFUUID一樣,這個值系統也不會存儲,每次調用的時候都會獲得一個新的唯一标示符。如果要存儲的話,你需要自己存儲。在我讀取NSUUID時,注意到擷取到的這個值跟CFUUID完全一樣(不過也可能不一樣

    三.Open UDID

    在iOS 5釋出時,uniqueIdentifier被棄用了,這引起了廣大開發者需要尋找一個可以替代UDID,并且不受蘋果控制的方案。由此OpenUDID成為了當時使用最廣泛的開源UDID替代方案。OpenUDID在工程中實作起來非常簡單,并且還支援一系列的廣告提供商。

    OpenUDID利用了一個非常巧妙的方法在不同程式間存儲标示符 — 在粘貼闆中用了一個特殊的名稱來存儲标示符。通過這種方法,别的程式(同樣使用了OpenUDID)知道去什麼地方擷取已經生成的标示符(而不用再生成一個新的)。而且根據貢獻者的代碼和方法,和一些開發者的經驗,如果把使用了OpenUDID方案的應用全部都删除,再重新擷取OpenUDID,此時的OpenUDID就跟以前的不一樣。可見,這種方法還是不保險。

    但是OpenUDID庫早已經棄用了, 在其官方的部落格中也指明了, 停止維護OpenUDID的原因是為了更好的向蘋果的舉措靠攏, 還指明了MAC Address不是一個好的選擇。

    四.MAC Address

  • 這個MAC位址是指什麼?有什麼用?

    MAC(Medium/Media Access Control)位址,用來表示網際網路上每一個站點的辨別符,采用十六進制數表示,共六個位元組(48位)。其中,前三個位元組是由IEEE的注冊管理機構 RA負責給不同廠家配置設定的代碼(高位24位),也稱為“編制上唯一的辨別符” (Organizationally Unique Identifier),後三個位元組(低位24位)由各廠家自行指派給生産的擴充卡接口,稱為擴充辨別符(唯一性)。

  • MAC位址在網絡上用來區分裝置的唯一性,接入網絡的裝置都有一個MAC位址,他們肯定都是不同的,是唯一的。一部iPhone上可能有多個MAC位址,包括WIFI的、SIM的等,但是iTouch和iPad上就有一個WIFI的,是以隻需擷取WIFI的MAC位址就好了,也就是en0的位址。

    形象的說,MAC位址就如同我們身份證上的身份證号碼,具有全球唯一性。這樣就可以非常好的辨別裝置唯一性,類似與蘋果裝置的UDID号,通常的用途有:

    (1) 用于一些統計與分析目的,利用使用者的操作習慣和資料更好的規劃産品;

    (2) 作為使用者ID來唯一識别使用者,可以用遊客身份使用app又能在伺服器端儲存相應的資訊,省去使用者名、密碼等注冊過程。

  • 如何使用Mac位址生成裝置的唯一辨別呢?

    主要分三種:

    1、直接使用“MAC Address”

    2、使用“MD5(MAC Address)”

    3、使用“MD5(Mac Address+bundle_id)”獲得“機器+應用”的唯一辨別(bundle_id 是應用的唯一辨別)

  • iOS7之前,因為Mac位址是唯一的, 一般app開發者會采取第3種方式來識别安裝對應app的裝置。為什麼會使用它?在iOS5之前,都是使用UDID的,後來被禁用。蘋果推薦使用UUID 但是也有諸多問題,進而使用MAC位址。而MAC位址跟UDID一樣,存在隐私問題,現在蘋果新釋出的iOS7上,如果請求Mac位址都會傳回一個固定值,那麼Mac Address+bundle_id這個值大家的裝置都變成一緻的啦,跟UDID一樣相當于被禁用, 是以Mac Address 是不能夠被使用為擷取裝置唯一辨別的。

    五.廣告标示符(IDFA-identifierForIdentifier)

    廣告标示符,在同一個裝置上的所有App都會取到相同的值,是蘋果專門給各廣告提供商用來追蹤使用者而設的。但好在Apple預設是允許追蹤的,而且一般使用者都不知道有這麼個設定,是以基本上用來監測推廣效果,是戳戳有餘了。

    它是iOS 6中另外一個新的方法,提供了一個方法advertisingIdentifier,通過調用該方法會傳回一個NSUUID執行個體,最後可以獲得一個UUID,由系統存儲着的。

    #import <AdSupport/AdSupport.h>
    NSString *adId = [[[ASIdentifierManager sharedManager] advertisingIdentifier] UUIDString];           

    不過即使這是由系統存儲的,但是有幾種情況下,會重新生成廣告标示符。

    (1)如果使用者完全重置系統((設定程式 -> 通用 -> 還原 -> 還原位置與隐私) ,這個廣告标示符會重新生成。

    (2)另外如果使用者明确的還原廣告(設定程式-> 通用 -> 關于本機 -> 廣告 -> 還原廣告标示符) ,那麼廣告标示符也會重新生成。

    關于廣告标示符的還原,有一點需要注意:如果程式在背景運作,此時使用者“還原廣告标示符”,然後再回到程式中,此時擷取廣 告标示符并不會立即獲得還原後的标示符。必須要終止程式,然後再重新啟動程式,才能獲得還原後的廣告标示符。

    是以IDFA也不可以作為擷取唯一辨別的方法,來識别使用者。

  • 六.Vendor标示符 (IDFV-identifierForVendor)

  • Vendor标示符,是給Vendor辨別使用者用的,每個裝置在所屬同一個Vender的應用裡,都有相同的值。其中的Vender是指應用提供商,但準确點說,是通過BundleID的反轉的前兩部分進行比對,如果相同就是同一個Vender,例如對于com.taobao.app1, com.taobao.app2 這兩個BundleID來說,就屬于同一個Vender,共享同一個IDFV的值。和IDFA不同的是,IDFV的值是一定能取到的,是以非常适合于作為内部使用者行為分析的主id,來辨別使用者,替代OpenUDID。

    它是iOS 6中新增的,跟advertisingIdentifier一樣,該方法傳回的是一個 NSUUID對象,可以獲得一個UUID。如果滿足條件“相同的一個程式裡面-相同的vendor-相同的裝置”,那麼擷取到的這個屬性值就不會變。如果是“相同的程式-相同的裝置-不同的vendor,或者是相同的程式-不同的裝置-無論是否相同的vendor”這樣的情況,那麼這個值是不會相同的。

  • NSString *strIDFV = [[[UIDevice currentDevice] identifierForVendor] UUIDString];           
    但是如果使用者将屬于此Vender的所有App解除安裝,則IDFV的值會被重置,即再重裝此Vender的App,IDFV的值和之前不同。
  • 七.推送token+bundle_id

    推送token+bundle_id的方法:

    1、應用中增加推送用來擷取token

    2、擷取應用bundle_id

    3、根據token+bundle_id進行散列運算

    apple push token保證裝置唯一,但必須有網絡情況下才能工作,該方法并不是依賴于裝置本身,而是依賴于apple push機制,是以當蘋果push做出改變時, 你擷取所謂的唯一辨別也就随之失效了。是以此方法還是不可取的。

  • 總結

    說了這麼多, 才發現原來沒有一種方法是可行的。沒錯, 其實自從蘋果廢除UDID後, 就不能達到擷取裝置真正的唯一辨別了。因為這些方法中導緻擷取的唯一标示産生改變的原因, 或是重新調用方法, 或是重新開機裝置, 或是解除安裝應用, 或是還原某些辨別, 或者重新整理系統…

    是以, 不能達到從根本上擷取唯一辨別, 我們隻能做到盡可能接近。下面是我用過的方法。

    如何正确的擷取裝置的唯一辨別

    我用的方法是将擷取的UUID永久存儲在裝置的KeyChain中, 這個方法在應用第一次啟動時, 将擷取的UUID存儲進KeyChain中, 每次取的時候, 檢查本地鑰匙串中有沒有, 如果沒有則需要将擷取的UUID存儲進去。當你重新開機裝置, 解除安裝應用再次安裝,都不影響, 隻是當裝置刷機時, KeyChain會清空, 才會消失, 才會失效。

    不隻是這一種方法, 你也可以儲存除UUID之外,其他合适的辨別, 但利用KeyChain去存儲辨別的方式應該是最接近的。

    利用keyChain和UUID永久獲得裝置的唯一辨別

    開發者可以在應用第一次啟動時調用一次,然後将該串存儲起來,以便以後替代UDID來使用。但是,如果使用者删除該應用再次安裝時,又會生成新的字元串,是以不能保證唯一識别該裝置。這就需要各路高手想出各種解決方案。是以,之前很多應用就采用MAC Address。但是現在如果使用者更新到iOS7(及其以後的蘋果系統)後,他們機子的MAC Address就是一樣的,沒辦法做區分,隻能棄用此方法,重新使用UUID來辨別。如果使用UUID,就要考慮應用被删除後再重新安裝時的處理。

    什麼是鑰匙串?

  • 在應用間利用KeyChain共享資料
  • 我們可以把KeyChain了解為一個Dictionary,所有資料都以key-value的形式存儲,可以對這個Dictionary進行add、update、get、delete這四個操作。對于每一個應用來說,KeyChain都有兩個通路區,私有區和公共區。私有區是一個sandbox,本程式存儲的任何資料都對其他程式不可見。而要想在将存儲的内容放在公共區,需要先聲明公共區的名稱,官方文檔管這個名稱叫“keychain access group”,聲明的方法是建立一個plist檔案,名字随便起,内容如下
    iOS擷取裝置的唯一辨別的方法代碼說完了,下面說說原理:實作代碼

    plist.png

    “yourAppID.com.yourCompany.whatever”就是你要起的公共區名稱,除了whatever字段可以随便定之外,其他的都必須如實填寫。這個檔案的路徑要配置在 Project->build setting->Code Signing Entitlements裡,否則公共區無效,配置好後,須用你正式的證書簽名編譯才可通過,否則xcode會彈框告訴你code signing有問題。是以,蘋果限制了你隻能同公司的産品共享KeyChain資料,别的公司通路不了你公司産品的KeyChain。

  • 儲存私密資訊

  • iOS的keychain服務提供了一種安全的儲存私密資訊(密碼,序列号,證書等)的方式,每個ios程式都有一個獨立的keychain存儲。相對于NSUserDefaults、檔案儲存等一般方式,keychain儲存更為安全,而且keychain裡儲存的資訊不會因App被删除而丢失,是以在重裝App後,keychain裡的資料還能使用。

    實作代碼

    介紹下我使用的方法以及封裝的工具類, 在應用裡使用使用keyChain,我們需要導入Security.framework。下面介紹下, 我在其他庫基礎上封裝的一個擷取唯一辨別的工具類:
  • #import <Foundation/Foundation.h>
    #import <Security/Security.h>
    
    @interface BGKeychainTool : NSObject
    
    /**
     本方法是得到 UUID 後存入系統中的 keychain 的方法
     不用添加 plist 檔案
     程式删除後重裝,仍可以得到相同的唯一标示
     但是當系統更新或者刷機後,系統中的鑰匙串會被清空,此時本方法失效
     */
    +(NSString *)getDeviceIDInKeychain;
    
    @end           
    #import "BGKeychainTool.h"
    
    NSString * const KEY_UDID_INSTEAD = @"com.myapp.udid.test";
    
    @implementation BGKeychainTool
    
    +(NSString *)getDeviceIDInKeychain {
        NSString *getUDIDInKeychain = (NSString *)[BGKeychainTool 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 _____重新存儲 UUID _____\n \n \n  %@",result);
            [BGKeychainTool save:KEY_UDID_INSTEAD data:result];
            getUDIDInKeychain = (NSString *)[BGKeychainTool load:KEY_UDID_INSTEAD];
        }
        NSLog(@"最終 ———— UDID_INSTEAD %@",getUDIDInKeychain);
        return getUDIDInKeychain;
    }           

    下面有幾個私有方法

  • 通過 key 擷取資料

  • + (NSMutableDictionary *)getKeychainQuery:(NSString *)service {
        return [NSMutableDictionary dictionaryWithObjectsAndKeys:
                (id)kSecClassGenericPassword,(id)kSecClass,
                service, (id)kSecAttrService,
                service, (id)kSecAttrAccount,
                (id)kSecAttrAccessibleAfterFirstUnlock,(id)kSecAttrAccessible,
                nil];
    }           
  • 儲存資料至鑰匙串中

  • + (void)save:(NSString *)service data:(id)data {
        //Get search dictionary
        NSMutableDictionary *keychainQuery = [self getKeychainQuery:service];
        //Delete old item before add new item
        SecItemDelete((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 dictionary
        SecItemAdd((CFDictionaryRef)keychainQuery, NULL);
    }           
  • 從鑰匙串中加載資料

  • + (id)load:(NSString *)service {
        id ret = nil;
        NSMutableDictionary *keychainQuery = [self getKeychainQuery: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:(__bridge NSData *)keyData];
            } @catch (NSException *e) {
                NSLog(@"Unarchive of %@ failed: %@", service, e);
            } @finally {
            }
        }
        if (keyData)
            CFRelease(keyData);
        return ret;
    }           
  • 删除資料

  • + (void)delete:(NSString *)service {
        NSMutableDictionary *keychainQuery = [self getKeychainQuery:service];
        SecItemDelete((CFDictionaryRef)keychainQuery);
    }           

    感謝(本文參考以下文摘):

  • 如何使用KeyChain儲存和擷取UDID
  • 擷取iOS裝置唯一辨別
  • The Developer’s Guide to Unique Identifiers
  • iOS擷取裝置的唯一辨別的方法總結
  • iOS擷取裝置的唯一辨別的方法總結以及最好的方法