天天看點

iOS各種裝置識别碼IDFA、UDID、UUID、MAC、OpenUDID、IDFV

今天在app服務端接口文檔中看到2個接口名稱,是和idfa相關的,就搜尋了解了一下,順便梳理了ios各種裝置識别碼。

*IDFA英文全稱Identifier+for+Advertising,即廣告标示符,目前是蘋果生态内廣告交易的主要标示符,一般和廣告商交易一個使用者後,廣告商會提供使用者的idfa作為憑證。主流廣告平台騰訊廣點通,新浪粉絲通都是基于idfa。

*ios中路徑:設定-->>隐私-->>廣告-->>還原廣告辨別符;

         每次切換開關按鈕和點選[還原廣告辨別符],在ios10中及之後版本中,商戶隻能擷取到一串毫無意義的0,這樣商戶就沒法對用進行定向投             放了,但是不會減少投放量。使用者就會成為沒有身份的“黑戶”。

iOS各種裝置識别碼IDFA、UDID、UUID、MAC、OpenUDID、IDFV
iOS各種裝置識别碼IDFA、UDID、UUID、MAC、OpenUDID、IDFV

【其他】認識一下iOS系統的各種裝置識别碼

1、UDID----Unique Device Identifier,蘋果ios唯一裝置識别碼,由40個數字字母組合,為了保護使用者隐私,蘋果已禁讀;

2、UUID----Universally Unique IDentifier,是基于蘋果裝置上某個單個應用程式;隻要使用者不删除程式,在使用者使用期間時候不變的,如果使用者删除後再重新安裝程式,uuid就會發生變化,缺點是使用者删除後程式後再安裝,基本不可能拿到之前的資料。

3、MAC位址,用來定義網絡裝置位置,一個主機有一個MAC位址,MAC位址是網卡決定的,是固定的,為了隐私,蘋果已禁讀;

4、OpenUDID,不是蘋果官方,是第三方用來替代UDID的解決方案,如果全部删除帶有OpenUDID SDK包的APP,那麼OpenUDID會重新生成,和之前的還不相同,相當于新裝置。

5、IDFA 廣告标示符,适用于對外:例如廣告推廣,換量等跨應用的使用者追蹤等。

6、IDFV,Vindor 标示符 (IDFV-identifierForVendor),來自于同一個開發商(jianshu.App1和jianshu.App2)應用在同一個裝置上,此屬性值是相同的,不同的營運商運用在同一個裝置上值也不同。

那麼,這麼多裝置,我們在網際網路的世界,如何标明唯一的一個裝置呢?

這個時候就引入了“裝置唯一辨別符”的概念。

在iOS的生态中,有我們所熟知的IDFA,IDFV,UDID,UUID。

簡直能把人整暈。但是他們含義不同,應用場景不同。

為了能在我們的業務中更好的辨別一個唯一裝置,我們引入OpenUDID類庫。

而在介紹這個庫之前,我們先來看看,上面說的這幾個編碼到底有哪裡不同,以及如何應用。

一、IDFA

IDFA是一串16進制的32位串。全稱是Identifier For Advertising

設計目的是,辨別使用者裝置,用于提供給各個APP之間跟蹤廣告所用。

例如,你在淘寶裡搜尋了某個商品之後,你在用浏覽器去浏覽網頁的時候,那個網頁的廣告就會給你展示相應的那個商品的廣告。

IDFA具有以下特性:

  1. IDFA和裝置綁定。所有應用在統一裝置上擷取的都是一樣的
  2. iOS10之後,使用者可以在設定中關閉限制APP擷取
  3. 可以被更改,更改的時機為:
    1. 設定程式 -> 通用 -> 還原 -> 還原位置與隐私
    2. 設定程式-> 通用 -> 關于本機 -> 廣告 -> 還原廣告标示符

是以,IDFA可以作為一台裝置的唯一辨別符。但是會有風險被重試,或者被限制擷取。

二、IDFV

和IDFA非常類似,是一串16進制的32位串。全稱是Identifier For Vendor。

但是不同的是,不僅和裝置相關,也和APP提供商相關。

是以,具有以下特性:

  1. IDFV同時和裝置,以及APP的提供商相關。一台裝置上,同一個APP提供商的IDFV是一樣的。

    是以,如果将此台裝置上,所有這個APP提供商的APP都删除了,則下一次重新安裝此提供商APP時,擷取的IDFV會變化。

    例如,Dailyyoga和每日瑜伽擷取的IDFV相同,但是把他們兩個都删除了,下次重新安裝後,IDFV會變化。

是以,IDFV,一般可以用于同一個廠家不同APP之間的在本地的資料互通。

三、UDID

是 iOS 裝置的一個唯一識别碼,每台 iOS 裝置都有一個獨一無二的編碼,全稱是Unique Device Identifier。由40位16進制數組成的字元串。

但是在iOS5以後,已經被蘋果禁止通過代碼擷取了。

目前還有三種方式可以看到UDID:

  1. 使用iTunes,将手機連接配接電腦
iOS各種裝置識别碼IDFA、UDID、UUID、MAC、OpenUDID、IDFV

image

  1. 使用XCode->Device,将手機連接配接電腦
    iOS各種裝置識别碼IDFA、UDID、UUID、MAC、OpenUDID、IDFV
    image
  2. 使用企業級的MDM,制作描述檔案,然後搭配網頁展示擷取UDID。但是隻能在Safari中實作。

    目前蒲公英就是這麼做的。可以參考蒲公英的擷取UDID的位址:https://www.pgyer.com/udid

    具體實作方案,可以參考這裡:http://www.skyfox.org/safari-ios-device-udid.html

是以,蘋果為了防止廠家濫用,對于用戶端來說,目前已經失去了擷取UDID的能力。

四、UUID

UUID 是 通用唯一識别碼(Universally Unique Identifier)的縮寫,是一種軟體建構的标準,是國際标準化組織(ISO)提出了一個概念。

它是一個32位的十六進制序列。

其設計目的是,讓分布式系統中的所有元素,都能有唯一的辨識資訊,而不需要通過中央控制端來做辨識資訊的指定。

目前幾乎所有系統,windows,Linux,macOS,都已經接入此标準。

UUID是基于目前時間、計數器(counter)和硬體辨別(通常為無線網卡的MAC位址)等資料計算生成的。是以,每次重新擷取的時候,都不一樣,可以在此時此地辨別唯一的一台裝置。

五、OpenUDID —— 我們使用的識别辨別庫

UUID目前是比較通用的,在OC中可以通過NSUUID來擷取。根據其擷取方式,可以知道每次擷取都會不同,實際驗證也如此。OpenUDID實際也是基于UUID來實作的。

但是我們想要是,一旦我的APP在這台機器上安裝上了,無論以後怎麼折騰,都擷取的是統一個。那麼,我們應該如何做呢?

此時,OpenUUID給了我們一個解決政策。(雖然這個庫已經很多年沒更新過了,API基于系統底層,是以相對穩定。并且我們的APP使用了很長時間,也沒有辦法替換)

由于DeviceID對于我們的重要性,我們還是有必要仔細閱讀一下此庫的源碼,來弄清楚我們DeviceID的前世今生。接下來,我們一起來看看。

我們首先用流程圖來表示一下,OpenUDID的核心功能,以及其資料流向:

iOS各種裝置識别碼IDFA、UDID、UUID、MAC、OpenUDID、IDFV

YbFEz4.png

  1. OpenUDID中所擷取的UDID是使用32位UUID進行了40位的補全而來,它的本質就是一個UUID,是可變的
  2. 為了使其不可變,OpenUDID将此值分别存儲在了三個地方
    1. 記憶體變量 — — 在不退出APP時,可以從記憶體中直接擷取之前的ID
    2. NSUserDefault — — 在不删除APP時,每次打開APP,可以在NSUserDefault中擷取到之前生成的ID
    3. UIPasteboard — — 删除了APP後,在同一裝置上重新安裝APP,依然可以擷取到之前已經生成的ID
  3. 不可變是我們要的常态,那麼我們需要着重關心的是,這個ID什麼時候會變化

    由于最終是存在UIPasteboard的不同域中,而iOS不允許使用者主動清理剪貼闆,是以變化的時機有以下幾個:

    1. 使用者抹掉手機中所有資料,出廠還原為一個新手機
    2. 有其他APP,使用API強制清除所有剪貼闆(待确認可實施性,但是不排除有這個可能)
    3. 由于OpenUDID是開源,不排除極端情況,有其他惡意APP清除OpenUDID使用的剪貼闆域

綜上,OpenUDID由于聰明地使用了剪貼闆做資料持久化以及共享。是以可以保證隻要裝置上有APP使用了OpenUDID,就可以生成一個唯一識别碼,除非以上情況,此識别碼将可以一直持續使用,是以,可靠性很高。

對于我們的業務來說,部分功能使用了DeviceID(即我們通過OpenUDID擷取給伺服器的ID)進行裝置識别的子產品,例如,即使使用者删除APP,再次安裝時,也要在登入頁面彈出上次他在這台裝置上最後一次登入的賬号的功能,可以大膽放心的使用此ID作為識别碼。

最後,放上OpenUDID源碼。有表述不清或者不對的地方,還請提出質疑。

Let’s think!

//
//  OpenUDID.m
//  openudid
//
//  initiated by Yann Lechelle (cofounder @Appsfire) on 8/28/11.
//  Copyright 2011, 2012 OpenUDID.org
//
//  Initiators/root branches
//      iOS code: https://github.com/ylechelle/OpenUDID
//      Android code: https://github.com/vieux/OpenUDID
//
//  Contributors:
//      https://github.com/ylechelle/OpenUDID/contributors
//

/*
 http://en.wikipedia.org/wiki/Zlib_License
 
 This software is provided 'as-is', without any express or implied
 warranty. In no event will the authors be held liable for any damages
 arising from the use of this software.
 
 Permission is granted to anyone to use this software for any purpose,
 including commercial applications, and to alter it and redistribute it
 freely, subject to the following restrictions:
 
 1. The origin of this software must not be misrepresented; you must not
 claim that you wrote the original software. If you use this software
 in a product, an acknowledgment in the product documentation would be
 appreciated but is not required.
 
 2. Altered source versions must be plainly marked as such, and must not be
 misrepresented as being the original software.
 
 3. This notice may not be removed or altered from any source
 distribution.
*/

#if __has_feature(objc_arc)
#error This file uses the classic non-ARC retain/release model; hints below... 
    // to selectively compile this file as non-ARC, do as follows:
    // https://img.skitch.com/20120717-g3ag5h9a6ehkgpmpjiuen3qpwp.png
#endif

#define SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(v)  ([[[UIDevice currentDevice] systemVersion] compare:v options:NSNumericSearch] != NSOrderedAscending)

#import "OpenUDID.h"
#import <CommonCrypto/CommonDigest.h> // Need to import for CC_MD5 access
#if TARGET_OS_IPHONE || TARGET_IPHONE_SIMULATOR
#import <UIKit/UIPasteboard.h>
#import <UIKit/UIKit.h>
#else
#import <AppKit/NSPasteboard.h>
#endif

#define OpenUDIDLog(fmt, ...)
//#define OpenUDIDLog(fmt, ...) //NSLog((@"%s [Line %d] " fmt), __PRETTY_FUNCTION__, __LINE__, ##__VA_ARGS__);
//#define OpenUDIDLog(fmt, ...) //NSLog((@"[Line %d] " fmt), __LINE__, ##__VA_ARGS__);

static NSString * kOpenUDIDSessionCache = nil;
//static NSString * const kOpenUDIDDescription = @"OpenUDID_with_iOS6_Support";
static NSString * const kOpenUDIDKey = @"OpenUDID";
static NSString * const kOpenUDIDSlotKey = @"OpenUDID_slot";
static NSString * const kOpenUDIDAppUIDKey = @"OpenUDID_appUID";
static NSString * const kOpenUDIDTSKey = @"OpenUDID_createdTS";
static NSString * const kOpenUDIDOOTSKey = @"OpenUDID_optOutTS";
static NSString * const kOpenUDIDDomain = @"org.OpenUDID";
static NSString * const kOpenUDIDSlotPBPrefix = @"org.OpenUDID.slot.";
static int const kOpenUDIDRedundancySlots = 100;

@interface OpenUDID (Private)
+ (void) _setDict:(id)dict forPasteboard:(id)pboard;
+ (NSMutableDictionary*) _getDictFromPasteboard:(id)pboard;
+ (NSString*) _generateFreshOpenUDID;
@end

@implementation OpenUDID

// Archive a NSDictionary inside a pasteboard of a given type
// Convenience method to support iOS & Mac OS X
//
+ (void) _setDict:(id)dict forPasteboard:(id)pboard {
#if TARGET_OS_IPHONE || TARGET_IPHONE_SIMULATOR     
    [pboard setData:[NSKeyedArchiver archivedDataWithRootObject:dict] forPasteboardType:kOpenUDIDDomain];
#else
    [pboard setData:[NSKeyedArchiver archivedDataWithRootObject:dict] forType:kOpenUDIDDomain];
#endif
}

// Retrieve an NSDictionary from a pasteboard of a given type
// Convenience method to support iOS & Mac OS X
//
+ (NSMutableDictionary*) _getDictFromPasteboard:(id)pboard {
#if TARGET_OS_IPHONE || TARGET_IPHONE_SIMULATOR 
    id item = nil;
    if([pboard respondsToSelector:@selector(dataForPasteboardType:)]){
        item = [pboard dataForPasteboardType:kOpenUDIDDomain];
    }
#else
    id item = [pboard dataForType:kOpenUDIDDomain];
#endif  
    if (item) {
        @try{
            item = [NSKeyedUnarchiver unarchiveObjectWithData:item];
        } @catch(NSException* e) {
            OpenUDIDLog(@"Unable to unarchive item %@ on pasteboard!", [pboard name]);
            item = nil;
        }
    }
    
    // return an instance of a MutableDictionary 
    return [NSMutableDictionary dictionaryWithDictionary:(item == nil || [item isKindOfClass:[NSDictionary class]]) ? item : nil];
}

// Private method to create and return a new OpenUDID
// Theoretically, this function is called once ever per application when calling [OpenUDID value] for the first time.
// After that, the caching/pasteboard/redundancy mechanism inside [OpenUDID value] returns a persistent and cross application OpenUDID
//
+ (NSString*) _generateFreshOpenUDID {
    
    NSString* _openUDID = nil;
    
    // August 2011: One day, this may no longer be allowed in iOS. When that is, just comment this line out.
    // March 25th 2012: this day has come, let's remove this "outlawed" call...
    // August 2012: well, perhaps much ado about nothing; in any case WWDC2012 gave us something to work with; read below
#if TARGET_OS_IPHONE    
//    if([UIDevice instancesRespondToSelector:@selector(uniqueIdentifier)]){
//        _openUDID = [[UIDevice currentDevice] uniqueIdentifier];
//    }
#endif
    
    //
    // !!!!! IMPORTANT README !!!!!
    //
    // August 2012: iOS 6 introduces new APIs that help us deal with the now deprecated [UIDevice uniqueIdentifier]
    // Since iOS 6 is still pre-release and under NDA, the following piece of code is meant to produce an error at
    // compile time. Accredited developers integrating OpenUDID are expected to review the iOS 6 release notes and
    // documentation, and replace the underscore ______ in the last part of the selector below with the right
    // selector syntax as described here (make sure to use the right one! last word starts with the letter "A"):
    // https://developer.apple.com/library/prerelease/ios/#documentation/UIKit/Reference/UIDevice_Class/Reference/UIDevice.html
    //
    // The semantic compiler warnings are still normal if you are compiling for iOS 5 only since Xcode will not
    // know about the two instance methods used on that line; the code running on iOS will work well at run-time.
    // Either way, it's time that you junped on the iOS 6 bandwagon and start testing your code on iOS 6 ;-)
    //
    // WHAT IS THE SIGNIFICANCE OF THIS CHANGE?
    //
    // Essentially, this means that OpenUDID will keep on behaving the same way as before for existing users or
    // new users in iOS 5 and before. For new users on iOS 6 and after, the new official public APIs will take over.
    // OpenUDID will therefore be obsoleted when iOS reaches significant adoption, anticipated around mid-2013.

    /*

        September 14; ok, new development. The alleged API has moved!
        This part of the code will therefore be updated when iOS 6 is actually released.
        Nevertheless, if you want to go ahead, the code should be pretty easy to
        guess... it involves including a .h file from a nine-letter framework that ends
        with the word "Support", and then assigning _openUDID with the only identifier method called on the sharedManager of that new class... don't forget to add
        the framework to your project!
     
#if TARGET_OS_IPHONE
    if (SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(@"6.0")) {
        _openUDID = [[[UIDevice currentDevice] identifierForA_______] UUIDString];
# error                                                         ^ read comments above, fix accordingly, and remove this #error line
    }
#endif
    
     */
    
    // Next we generate a UUID.
    // UUIDs (Universally Unique Identifiers), also known as GUIDs (Globally Unique Identifiers) or IIDs
    // (Interface Identifiers), are 128-bit values guaranteed to be unique. A UUID is made unique over 
    // both space and time by combining a value unique to the computer on which it was generated—usually the
    // Ethernet hardware address—and a value representing the number of 100-nanosecond intervals since 
    // October 15, 1582 at 00:00:00.
    // We then hash this UUID with md5 to get 32 bytes, and then add 4 extra random bytes
    // Collision is possible of course, but unlikely and suitable for most industry needs (e.g. aggregate tracking)
    //
    if (_openUDID==nil) {
        CFUUIDRef uuid = CFUUIDCreate(kCFAllocatorDefault);
        CFStringRef cfstring = CFUUIDCreateString(kCFAllocatorDefault, uuid);
        const char *cStr = CFStringGetCStringPtr(cfstring,CFStringGetFastestEncoding(cfstring));
        unsigned char result[16];
        CC_MD5( cStr, (int)strlen(cStr), result );
        CFRelease(uuid);
        CFRelease(cfstring);

        _openUDID = [NSString stringWithFormat:
                @"%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%08lx",
                result[0], result[1], result[2], result[3], 
                result[4], result[5], result[6], result[7],
                result[8], result[9], result[10], result[11],
                result[12], result[13], result[14], result[15],
                     (unsigned long)(arc4random() % NSUIntegerMax)];
    }
    
    // Call to other developers in the Open Source community:
    //
    // feel free to suggest better or alternative "UDID" generation code above.
    // NOTE that the goal is NOT to find a better hash method, but rather, find a decentralized (i.e. not web-based)
    // 160 bits / 20 bytes random string generator with the fewest possible collisions.
    // 

    return _openUDID;
}


// Main public method that returns the OpenUDID
// This method will generate and store the OpenUDID if it doesn't exist, typically the first time it is called
// It will return the null udid (forty zeros) if the user has somehow opted this app out (this is subject to 3rd party implementation)
// Otherwise, it will register the current app and return the OpenUDID
//

+ (NSString*) value {
    static NSString*ygOpenUDID = nil;
    if(!ygOpenUDID){
        @try {
            ygOpenUDID = [OpenUDID valueWithError:nil]?:@"";
        }@catch (NSException *exception) {
            return @"";
        }
    }
    return ygOpenUDID?:@"";
}

+ (NSString*) valueWithError:(NSError **)error {

    if (kOpenUDIDSessionCache!=nil) {
        if (error!=nil)
            *error = [NSError errorWithDomain:kOpenUDIDDomain
                                         code:kOpenUDIDErrorNone
                                     userInfo:[NSDictionary dictionaryWithObjectsAndKeys:@"OpenUDID in cache from first call",@"description", nil]];
        return kOpenUDIDSessionCache;
    }
    
    NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
    
    // The AppUID will uniquely identify this app within the pastebins
    //
    NSString * appUID = [defaults objectForKey:kOpenUDIDAppUIDKey];
    if(appUID == nil)
    {
      // generate a new uuid and store it in user defaults
        CFUUIDRef uuid = CFUUIDCreate(NULL);
        appUID = (NSString *) CFUUIDCreateString(NULL, uuid);
        CFRelease(uuid);
        [appUID autorelease];
    }
  
    NSString* openUDID = nil;
    NSString* myRedundancySlotPBid = nil;
    NSDate* optedOutDate = nil;
    BOOL optedOut = NO;
    BOOL saveLocalDictToDefaults = NO;
    BOOL isCompromised = NO;
    
    // Do we have a local copy of the OpenUDID dictionary?
    // This local copy contains a copy of the openUDID, myRedundancySlotPBid (and unused in this block, the local bundleid, and the timestamp)
    //
    id localDict = [defaults objectForKey:kOpenUDIDKey];
    if ([localDict isKindOfClass:[NSDictionary class]]) {
        localDict = [NSMutableDictionary dictionaryWithDictionary:localDict]; // we might need to set/overwrite the redundancy slot
        openUDID = [localDict objectForKey:kOpenUDIDKey];
        myRedundancySlotPBid = [localDict objectForKey:kOpenUDIDSlotKey];
        optedOutDate = [localDict objectForKey:kOpenUDIDOOTSKey];
        optedOut = optedOutDate!=nil;
        OpenUDIDLog(@"localDict = %@",localDict);
    }
    
    // Here we go through a sequence of slots, each of which being a UIPasteboard created by each participating app
    // The idea behind this is to both multiple and redundant representations of OpenUDIDs, as well as serve as placeholder for potential opt-out
    //
    NSString* availableSlotPBid = nil;
    NSMutableDictionary* frequencyDict = [NSMutableDictionary dictionaryWithCapacity:kOpenUDIDRedundancySlots];
    for (int n=0; n<kOpenUDIDRedundancySlots; n++) {
        NSString* slotPBid = [NSString stringWithFormat:@"%@%d",kOpenUDIDSlotPBPrefix,n];
#if TARGET_OS_IPHONE || TARGET_IPHONE_SIMULATOR
        UIPasteboard* slotPB = [UIPasteboard pasteboardWithName:slotPBid create:NO];
#else
        NSPasteboard* slotPB = [NSPasteboard pasteboardWithName:slotPBid];
#endif
        OpenUDIDLog(@"SlotPB name = %@",slotPBid);
        if (slotPB==nil) {
            // assign availableSlotPBid to be the first one available
            if (availableSlotPBid==nil) availableSlotPBid = slotPBid;
        } else {
            NSDictionary* dict = [OpenUDID _getDictFromPasteboard:slotPB];
            NSString* oudid = [dict objectForKey:kOpenUDIDKey];
            OpenUDIDLog(@"SlotPB dict = %@",dict);
            if (oudid==nil) {
                // availableSlotPBid could inside a non null slot where no oudid can be found
                if (availableSlotPBid==nil) availableSlotPBid = slotPBid;
            } else {
                // increment the frequency of this oudid key
                int count = [[frequencyDict valueForKey:oudid] intValue];
                [frequencyDict setObject:[NSNumber numberWithInt:++count] forKey:oudid];
            }
            // if we have a match with the app unique id,
            // then let's look if the external UIPasteboard representation marks this app as OptedOut
            NSString* gid = [dict objectForKey:kOpenUDIDAppUIDKey];
            if (gid!=nil && [gid isEqualToString:appUID]) {
                myRedundancySlotPBid = slotPBid;
                // the local dictionary is prime on the opt-out subject, so ignore if already opted-out locally
                if (optedOut) {
                    optedOutDate = [dict objectForKey:kOpenUDIDOOTSKey];
                    optedOut = optedOutDate!=nil;   
                }
            }
        }
    }
    
    // sort the Frequency dict with highest occurence count of the same OpenUDID (redundancy, failsafe)
    // highest is last in the list
    //
    NSArray* arrayOfUDIDs = [frequencyDict keysSortedByValueUsingSelector:@selector(compare:)];
    NSString* mostReliableOpenUDID = (arrayOfUDIDs!=nil && [arrayOfUDIDs count]>0)? [arrayOfUDIDs lastObject] : nil;
    OpenUDIDLog(@"Freq Dict = %@\nMost reliable %@",frequencyDict,mostReliableOpenUDID);
        
    // if openUDID was not retrieved from the local preferences, then let's try to get it from the frequency dictionary above
    //
    if (openUDID==nil) {        
        if (mostReliableOpenUDID==nil) {
            // this is the case where this app instance is likely to be the first one to use OpenUDID on this device
            // we create the OpenUDID, legacy or semi-random (i.e. most certainly unique)
            //
            openUDID = [OpenUDID _generateFreshOpenUDID];
        } else {
            // or we leverage the OpenUDID shared by other apps that have already gone through the process
            // 
            openUDID = mostReliableOpenUDID;
        }
        // then we create a local representation
        //
        if (localDict==nil) { 
            localDict = [NSMutableDictionary dictionaryWithCapacity:4];
            [localDict setObject:openUDID forKey:kOpenUDIDKey];
            [localDict setObject:appUID forKey:kOpenUDIDAppUIDKey];
            [localDict setObject:[NSDate date] forKey:kOpenUDIDTSKey];
            if (optedOut) [localDict setObject:optedOutDate forKey:kOpenUDIDTSKey];
            saveLocalDictToDefaults = YES;
        }
    }
    else {
        // Sanity/tampering check
        //
        if (mostReliableOpenUDID!=nil && ![mostReliableOpenUDID isEqualToString:openUDID])
            isCompromised = YES;
    }
    
    // Here we store in the available PB slot, if applicable
    //
    OpenUDIDLog(@"Available Slot %@ Existing Slot %@",availableSlotPBid,myRedundancySlotPBid);
    if (availableSlotPBid!=nil && (myRedundancySlotPBid==nil || [availableSlotPBid isEqualToString:myRedundancySlotPBid])) {
#if TARGET_OS_IPHONE || TARGET_IPHONE_SIMULATOR
        UIPasteboard* slotPB = [UIPasteboard pasteboardWithName:availableSlotPBid create:YES];
        [slotPB setPersistent:YES];
#else
        NSPasteboard* slotPB = [NSPasteboard pasteboardWithName:availableSlotPBid];
#endif
        
        // save slotPBid to the defaults, and remember to save later
        //
        if (localDict) {
            [localDict setObject:availableSlotPBid forKey:kOpenUDIDSlotKey];
            saveLocalDictToDefaults = YES;
        }
        
        // Save the local dictionary to the corresponding UIPasteboard slot
        //
        if (openUDID && localDict)
            [OpenUDID _setDict:localDict forPasteboard:slotPB];
    }

    // Save the dictionary locally if applicable
    //
    if (localDict && saveLocalDictToDefaults)
        [defaults setObject:localDict forKey:kOpenUDIDKey];

    // If the UIPasteboard external representation marks this app as opted-out, then to respect privacy, we return the ZERO OpenUDID, a sequence of 40 zeros...
    // This is a *new* case that developers have to deal with. Unlikely, statistically low, but still.
    // To circumvent this and maintain good tracking (conversion ratios, etc.), developers are invited to calculate how many of their users have opted-out from the full set of users.
    // This ratio will let them extrapolate convertion ratios more accurately.
    //
    if (optedOut) {
        if (error!=nil) *error = [NSError errorWithDomain:kOpenUDIDDomain
                                                     code:kOpenUDIDErrorOptedOut
                                                 userInfo:[NSDictionary dictionaryWithObjectsAndKeys:[NSString stringWithFormat:@"Application with unique id %@ is opted-out from OpenUDID as of %@",appUID,optedOutDate],@"description", nil]];
            
        kOpenUDIDSessionCache = [[NSString stringWithFormat:@"%040x",0] retain];
        return kOpenUDIDSessionCache;
    }

    // return the well earned openUDID!
    //
    if (error!=nil) {
        if (isCompromised)
            *error = [NSError errorWithDomain:kOpenUDIDDomain
                                         code:kOpenUDIDErrorCompromised
                                     userInfo:[NSDictionary dictionaryWithObjectsAndKeys:@"Found a discrepancy between stored OpenUDID (reliable) and redundant copies; one of the apps on the device is most likely corrupting the OpenUDID protocol",@"description", nil]];
        else
            *error = [NSError errorWithDomain:kOpenUDIDDomain
                                         code:kOpenUDIDErrorNone
                                     userInfo:[NSDictionary dictionaryWithObjectsAndKeys:@"OpenUDID succesfully retrieved",@"description", nil]];
    }
    kOpenUDIDSessionCache = [openUDID retain];
    return kOpenUDIDSessionCache;
}

+ (void) setOptOut:(BOOL)optOutValue {

    // init call
    [OpenUDID value];
    
    NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];

    // load the dictionary from local cache or create one
    id dict = [defaults objectForKey:kOpenUDIDKey];
    if ([dict isKindOfClass:[NSDictionary class]]) {
        dict = [NSMutableDictionary dictionaryWithDictionary:dict];
    } else {
        dict = [NSMutableDictionary dictionaryWithCapacity:2];
    }

    // set the opt-out date or remove key, according to parameter
    if (optOutValue)
        [dict setObject:[NSDate date] forKey:kOpenUDIDOOTSKey];
    else
        [dict removeObjectForKey:kOpenUDIDOOTSKey];

    // store the dictionary locally
    [defaults setObject:dict forKey:kOpenUDIDKey];
    
    OpenUDIDLog(@"Local dict after opt-out = %@",dict);
    
    // reset memory cache 
    kOpenUDIDSessionCache = nil;
    
}

@end
           

作者:宋奕Ekis

連結:https://www.jianshu.com/p/69a526f72639

來源:簡書

著作權歸作者所有。商業轉載請聯系作者獲得授權,非商業轉載請注明出處。