Coder Social home page Coder Social logo

mixpanel / mixpanel-iphone Goto Github PK

View Code? Open in Web Editor NEW
1.0K 1.0K 565.0 42.22 MB

Official iOS (Objective-C) Tracking Library for Mixpanel Analytics

Home Page: http://mixpanel.com

License: Apache License 2.0

Objective-C 98.30% Ruby 0.78% Shell 0.16% Python 0.51% Swift 0.17% C 0.08%
analytics carthage cocoapods mixpanel mixpanel-iphone mixpanel-sdk objective-c sdk

mixpanel-iphone's People

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

mixpanel-iphone's Issues

mixpanel can block the main thread on applicationDidEnterBackground

Repro steps

  1. Simulate a bad network connection (e.g., using Network Link Conditioner on your Mac or the Developer tools on a device)
  2. Do something that triggers Mixpanel events
  3. Background the app
  4. Foreground the app

Expected Results

  1. App remains responsive

Actual Results

  1. App is unresponsive, main thread is blocked.

It looks like Mixpanel is blocked on self.serialQueue in the archive method.

flush does a synchronous network call which blocks the serial queue, and archive uses dispatch_sync instead of dispatch_async.

- (void)archive
{
   // Must archive from the serial queue to avoid conflicts from data mutation
   dispatch_sync(self.serialQueue, ^{
      [self archiveFromSerialQueue];
   });
}

I think the fix could be as easy as changing it to use dispatch_async instead.

Put image files in Asset Catalog

Since iOS 7 image files can be stored nicely in asset catalogs. As a project can contain unlimited amount of asset catalogs and i suggest to create an asset catalog for all the image files that come with Mixpanel.

Note on iOS 6 and prior: As an asset catalog is just a directory containing the image files and some meta data files, the images still can be used in older project too.

Warning when compiling Mixpanel.m in Release build

Mixpanel.m:337:12: Unused variable 'v'

'v' seems to be used only in an NSAssert, but in Xcode 4 assertions are blocked by default in Release builds. It would be nice if Mixpanel compiled cleanly in this case.

killed by: active assertions beyond permitted time: [Mixpanel applicationDidEnterBackground:]

I'm getting this in my console after backgrounding my app:

Dec 20 04:51:52 Lees-iPhone-5s backboardd[31] <Warning>: abiosapp[1101] has active assertions beyond permitted time: 
    {(
        <BKProcessAssertion: 0x170277f00> identifier: Called by abiosapp, from -[Mixpanel applicationDidEnterBackground:] process: abiosapp[1101] permittedBackgroundDuration: 180.000000 reason: finishTask owner pid:1101 preventSuspend  preventIdleSleep  preventSuspendOnSleep 
    )}
Dec 20 04:51:52 Lees-iPhone-5s backboardd[31] <Warning>: Forcing crash report of abiosapp[1101]...
Dec 20 04:51:53 Lees-iPhone-5s backboardd[31] <Warning>: Finished crash reporting.

The last item for my app exactly 3 minutes before this was the call to mixpanel /engage api.

Background tasks may be ended more than once

If self.flushOnBackground is YES, then when an app integrating Mixpanel is backgrounded, a background task will be created and events will be flushed.

But, if the app itself runs background tasks, and those background tasks track events and run long enough, the SDK may try to flush again. Currently, subsequent flushes will attempt to end the background task again, causing an error.

A simple fix is to set taskId = UIBackgroundTaskInvalid on completion, but a broader improvement to avoid flushing in the background, or to set background tasks when this takes place would be helpful. Without that, subsequent background flushes could be interrupted if the app's background tasks finish while a flush is in progress.

Automatic Reference Counting

My project uses automatic reference counting (ARC). I still need to look into how to combine ARC with non-ARC code.

Are you working on an ARC branch?

NSMutableArray modified while enumerating

Hi,
I'm using Mixpale sdk for a few days and I got the following error:
* Collection <__NSArrayM: xxxxxxxx> was mutated while being enumerated.*

I've inspected memory xxxxxxxx and it was the array used to collect events to be tracked (property called eventsQueue).

I got a read at the Mixpanel.m class and found that you've used @synchronized code when accessing eventsQueue object.

Our app is sending a lot of tracking events, even when no connection is available, so they can be sent as soon as the connection restart working again.

I noticed that if events were not sent because of connection lack, they will be archived using method archiveEvents, where's the exception seems to occour, I think because, at the same time, I'll continue adding events .

I added a @synchronized block around the code and problem seems disappear (done this also for archivePeople method).

- (void)archiveEvents
{
    @synchronized( self)  <-- added code
    {
        NSString *filePath = [self eventsFilePath];
        DevLog(@"%@ archiving events data to %@: %@", self, filePath, self.eventsQueue);
        if (![NSKeyedArchiver archiveRootObject:self.eventsQueue toFile:filePath]) {
            NSLog(@"%@ unable to archive events data", self);
        }
    }
}

I'd like to know if someone got into the same problem and an opinion if the solution can be viable.

Thanks!

William Pompei

Frequently receiving 0 responses

Hey guys, the majority of my requests are failing with a 0 response: "failed 0", but every once and a while, one manages to slip through. Your website says 0s are returned whenever a request is improperly formatted, but then why would some data points manage to make it?

This is how I'm initializing the API in my appDelegate:

    // Get specific device name: u.machine
    struct utsname u;
    uname(&u);

    MixpanelAPI *mixpanel = [MixpanelAPI sharedAPIWithToken:MIXPANEL_TOKEN];

    [mixpanel registerSuperProperties:[NSDictionary dictionaryWithObjectsAndKeys:
                                       [[UIDevice currentDevice] systemVersion], @"OS Version",
                                       [NSString stringWithFormat:@"%s", u.machine], @"Hardware",
                                       [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleVersion"], @"Listenr Version",
                                       nil]
     ];

And this is how I'm calling it from my various classes:

        MixpanelAPI *mixpanel = [MixpanelAPI sharedAPI];
        [mixpanel track:@"shuffle on" properties:[NSDictionary 
                                                  dictionaryWithObject:music.origin forKey:@"origin"]];

What gives?

Impossible to identify events and people separately

While I agree that it was confusing to have to identify events and people separately (#43), the fix (6194f18) seems to eliminate the ability to give a separate identifier for events and people. A simple use case for a separate identifier is:

  1. User installs the app
    • event identifier is tied to the device
    • people identifier is nil because they are not logged in
  2. User logs in
    • event identifier is still tied to the device
    • people identifier corresponds to a different user id

In Mixpanel, we have to be able to make a funnel to track conversion from installation to log in to activity within an app. Requiring the event identifier to change when the user logs in is unacceptable because it will make this type of funnel impossible. Furthermore, the Mixpanel People interface shows events based on an identifier that can be distinct from the people identifier. While that unfortunately does not support multiple devices per user it at least allows us to see the activity of the user from their most recent device, with combined people statistics from all their devices.

It does not seem to be possible to set $distinct_id as a property on People because it will be overwritten by the value of the distinctId property. I apologize if I did not notice a way that the current approach accounts for this.

Please add semantic version tags in order to support CocoaPods

Issue title

Please add semantic version tags.

Issue description

I’ve recently added Mixpanel to the CocoaPods package manager repo.

CocoaPods is a tool for managing dependencies for OS X and iOS Xcode projects and provides a central repository for iOS/OS X libraries. This makes adding libraries to a project and updating them extremely easy and it will help users to resolve dependencies of the libraries they use.

However, Mixpanel doesn't have any version tags. I’ve added the current HEAD as version 0.0.1, but a version tag will make dependency resolution much easier.

Semantic version tags (instead of plain commit hashes/revisions) allow for resolution of cross-dependencies.

In case you didn’t know this yet; you can tag the current HEAD as, for instance, version 1.0.0, like so:

$ git tag -a 1.0.0 -m "Tag release 1.0.0"
$ git push --tags

Setup mixpanel to be usable as submodule

When crucial fixes like issue #36 get committed, it would be nice to be able to realize that a submodule like mixpanel has an update, go in, evaluate and then pull the changes.

Currently if I add mixpanel as a submodule to my git project, I can't add/import/soft-link its files to my main xcode .pbxproj file because git complains that they already are being monitored by the submodule.

Would it be possible for the mixpanel team to prioritize shaping this project such that it could be used with another project as a submodule?

Compiler errors from setProperty:forKey: which conflicts with NSStream

The new setProperty:forKey: and setProperties: conflict with matching selector names in NSStream. Since the methods have different parameter types, this causes an error in ARC due to the inability to resolve which is being used when called on [MixpanelAPI sharedAPI].

A workaround is to cast the result of sharedAPI, but it would be better if this were not necessary:
[(MixpanelAPI *)[MixpanelAPI sharedAPI] setProperty:property forKey:key];

In general, the new method names for People tracking are unnecessarily obscure. The ability to set "super properties," "properties," and more properties when tracking an event must be confusing for someone who is just getting started with Mixpanel. In our analytics abstraction layer we use names like setUserProperty:forKey: where "user" makes it very clear which properties are being set. Adding "User" to these method names would also resolve the naming conflict.

NSRunLoop runMode blocks on app start

Code snipped below from flushEvents/People in Mixpanel.m seems to be blocking the main thread.

Symptoms: whenever app comes to foreground there's a lag of ~5secs while you can't do anything because UI is blocked. Instruments are showing that below is running then, commenting flushEvents/People out removes this lag.

This is related to a fix to issue 35 (#35) - on app going to background new thread is started and code below tries to keep it alive. The only reason to have a new thread there seems to be this 5sec limit which could run over if you were to base64 encode lots of data (you can actually base64 encode quite a bit of data in 5secs) because NSURLConnection calls are non-blocking.

So maybe the solution is to change it so NSURLConnection is blocking if you think 5secs is not enough to encode data? Or maybe change below in a way so it doesn't block the main thread?

A side bug of the below is that if you're under a very slow connection and close the app and immediately reopen it it crashes - seems to be that this background thread is still kept alive while a new one kicks in for foreground processing but the self.eventsConnection is still alive and they both try to clear it which results in crash.

 if(![NSThread isMainThread]){
       DevLog(@"%@ keeping background events connection thread alive", self);
       while(self.eventsConnection) {
           [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
       }
       DevLog(@"%@ letting go of background events connection thread", self);
   }

Mixpanel logs when DEBUG=1

We try to keep our logging to a minimum. We just updated to the latest version of mixpanel and are now getting logs because we have DEBUG=1 for our debug builds. This logging was recently added in #31. We'd love it if even the non-verbose logging of Mixpanel looked at something Mixpanel specific to disable/enable that logging. For now, we've just #define DebugLog to nothing to silence Mixpanel but maybe it could look for MIXPANEL_DEBUG or again, something Mixpanel specific.

Ability to track app crashes

Folks, I would like to track stack trace & exception reason in my main.m when the app crashes as a property of "App Crashed" event + increment "Crashed" property in people.

However at that point none of the hooks to archive data get called and flush doesn't push data out because app quits right away.

What about making archive method public so that it could be called manually in this situation?

Thanks,
Viktoras

[Mixpanel sharedAPIWithToken:] raises exception. Causes app to crash on launch every time.

Version SHA: 8656f67

Stack trace:

0 CoreFoundation 0x3577a88f __exceptionPreprocess + 163
1 libobjc.A.dylib 0x37b21259 objc_exception_throw + 33
2 CoreFoundation 0x3577a789 +[NSException raise:format:] + 1
3 CoreFoundation 0x3577a7ab +[NSException raise:format:] + 35
4 Foundation 0x3520c181 -[NSKeyedUnarchiver initForReadingWithData:] + 2553
5 Foundation 0x3520b6ab +[NSKeyedUnarchiver unarchiveObjectWithFile:] + 115
6 Luma Beta 0x000b76b5 -[MixpanelAPI unarchiveEvents] (MixpanelAPI.m:402)
7 Luma Beta 0x000b7665 -[MixpanelAPI unarchiveData] (MixpanelAPI.m:398)
8 Luma Beta 0x000b6931 -[MixpanelAPI start] (MixpanelAPI.m:199)
9 Luma Beta 0x000b6abf +[MixpanelAPI sharedAPIWithToken:] (MixpanelAPI.m:225)
10 Luma Beta 0x0006cfa7 -[AppDelegate initializeMixpanel] (AppDelegate.m:80)
11 Luma Beta 0x0006d237 -[AppDelegate application:didFinishLaunchingWithOptions:] (AppDelegate.m:106)

Can't associate an event tracked with a person before identifying it

Like:
// Event A can't be seen in Rodrigo's activity feed
[mixpanel track:@"A"]
[mixpanel identify:@"Rodrigo"] // Perhaps, this should call people.identify, right? Yeah but it's making people to flush.
[mixpanel.people identify:@"Rodrigo"] // So I have to put this line to work. I refered to this in #70
// Only event B can be seen, probably because it's being called after
[mixpanel track:@"B"]

Usage of @synchronized leads to app deadlock when firing multiple events in quick succession

Using synchronized only, without threading, also puts most or all of your code on the main thread. This contributed to about 15% of our main queue CPU usage. Addressing this would probably make a bunch of your clients happy.

Let me know if I can provide more info here.

I'd recommend using an NSOperationQueue with a maxConcurrentOperationCount of 1. You'll have to make sure that all your enqueueing, file IO (unarchiving/archiving), and networking is synchronized and off the main thread like this.

You might want to consider using a second synchronous queue for strictly networking operations. You'll probably need to keep your enqueuing and IO on the same operation queue.

Crash

We're getting a mixpanel error on one of our ipads. Only on one of them, but this still seems bad:

*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[__NSCFDictionary setObject:forKey:]: attempt to insert nil value (key: mp_name_tag)'

Does not show on Mixpanel Streams

Events, even with the proper distinct_id, mp_name_tag, and nameTag do not show on streams page. On Js it automatically does this.

Is there a way to make it work with the iphone as well?

Cheers,

  • Daniel

Flushing on applicationDidEnterBackground is broken

Function from Mixpanel.m below calls up [self flush] in a new thread which seems to be killed before NSURLConnection had a chance to send any data.

Maybe there's no need to move it to a separate thread - these seem to be killed straight away once app enters background instead of hanging around till everything finishes and expiration handler gets chance to be executed.

So self.eventsConnection is never cleaned and no subsequent flushes are even attempted.

End result - we're not getting any more data from our iPhone app built with most recent lib into MixPanel.

- (void)applicationDidEnterBackground:(NSNotificationCenter *)notification
{
    DevLog(@"%@ did enter background", self);

    @synchronized(self) {

#if __IPHONE_OS_VERSION_MIN_REQUIRED >= 40000
        if (self.flushOnBackground &&
            [[UIApplication sharedApplication] respondsToSelector:@selector(beginBackgroundTaskWithExpirationHandler:)] &&
            [[UIApplication sharedApplication] respondsToSelector:@selector(endBackgroundTask:)]) {

            self.taskId = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{
                DevLog(@"%@ background flush cut short", self);
                [self.eventsConnection cancel];
                [self.peopleConnection cancel];
                self.eventsConnection = nil;
                self.peopleConnection = nil;
                [[UIApplication sharedApplication] endBackgroundTask:self.taskId];
                self.taskId = UIBackgroundTaskInvalid;
            }];

           dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
                DevLog(@"%@ background flush starting", self);
                [self flush];
                [[UIApplication sharedApplication] endBackgroundTask:self.taskId];
                self.taskId = UIBackgroundTaskInvalid;
            });

            DevLog(@"%@ background flush dispatched", self);
        }
#endif
    }
}

No way to set name_tag

The name_tag property is a pretty crucial element of the Mixpanel dashboard, it would be useful if this plugin supported it. Is it in development?

Unexpected behavior when removing name tag

I would expect that calling -[[MixpanelAPI sharedAPI] setNameTag:nil] should remove any existing name tag in the Mixpanel Stream for the current user. We use this when the user logs out just to ensure that they are shown as a Guest. However, setNameTag:nil simply removes the value from the super properties which causes that property to not be included in the outgoing data. The backend will continue to represent the user with the previous name, rather than as a Guest, until it receives an explicit value for that property.

In order to clear a previously-defined name tag, the name tag should probably be set to [NSNull null] which will be sent to the backend, allowing it to update the name tag displayed in the stream. The nameTag accessor will also need to be modified to return nil if the stored value is [NSNull null] to prevent mayhem.

namespace classes in library

A customer already had a class called NSData+Base64 with different methods, so we should namespace the classes we add

iOS 4.2 Universal App crashes on iPad 3.2

This is for iOS SDK 4.2 builds targeting iOS 3.0+.

It crashes on line 144 in MixPanelAPI.m with the code:
taskId = UIBackgroundTaskInvalid;

UIBackgroundTaskInvalid is only defined for iOS 4.0+.

Support currency in trackCharge

[[Mixpanel sharedInstance].people trackCharge:] is totally useless under iOS, because it doesn't allow to send currency of charge and developer have to convert all charges to single currency (which he will not do).

Potential leak

Mixpanel.m:123:9: Assuming 'apiToken' is not equal to nil
Mixpanel.m:129:9: Assuming 'self' is not nil
Mixpanel.m:143:28: Call to function 'dispatch_queue_create' returns an Objective-C object with a +1 retain count
Mixpanel.m:144:9: Object leaked: allocated object is not referenced later in this execution path and has a retain count of +1
Mixpanel.m:144:9: Potential leak of an object

Text not visible in Mixpanel Surveys question

When a Survey is presented on certain view controllers, the text and highlight is not visible. Perhaps being able to specify a tintColor to the MPSurveyQuestionViewController view would be a better solution than algorithmically trying to determine the color? Or even better, using the tintColor specified by the key UIWindow.

ios simulator screen shot nov 20 2013 3 28 41 pm

Please add semantic version tags.

I’ve recently added Mixpanel to the CocoaPods package manager repo.

CocoaPods is a tool for managing dependencies for OSX and iOS Xcode projects and provides a central repository for iOS/OSX libraries. This makes adding libraries to a project and updating them extremely easy and it will help users to resolve dependencies of the libraries they use.

However, Mixpanel doesn't have any version tags. I’ve added the current HEAD as version 0.0.1, but a version tag will make dependency resolution much easier.

Semantic version tags (instead of plain commit hashes/revisions) allow for resolution of cross-dependencies.

In case you didn’t know this yet; you can tag the current HEAD as, for instance, version 1.0.0, like so:

$ git tag -a 1.0.0 -m "Tag release 1.0.0"
$ git push --tags

NSDictionaryM was mutated while being enumerated

This issue seems similar to an older one ( issue #39 ) so I'm tempted to think that it has creeped again in a different manner due to new code or re-arrangement.

It doesn't happen on commit dac6251 marked as v2.0.0 but it does occur for the latest commit 193f048 marked as v2.0.2 consistently for me.

What are my options?
screen shot 2013-09-16 at 11 55 27 am

Avoid discarding all events in an upload simply due to a parsing error

Currently, a parsing error during an upload results in an error in the console and no data in the upload, even if some of the events were valid. Unfortunately, although none of the events in eventsBatch were actually uploaded they are still removed from the eventsQueue. Ultimately the events are discarded and never sent to the Mixpanel endpoint, which makes for very unexpected behavior in the data that actually gets uploaded.

Please consider the following revision to +encodeAPIData: which should ensure that an error in any number of events does not cause the valid events in the upload to be discarded:

+ (NSString *)encodeAPIData:(NSArray *)array
{
    MPCJSONDataSerializer *serializer = [MPCJSONDataSerializer serializer];
    NSError *error = nil;
    NSData *data = [serializer serializeArray:array error:&error];
    if (error) {
        // There was only one event, no need to look for valid events
        if (array.count == 1) {
            return @"";
        }

        NSMutableArray * validEvents = [NSMutableArray arrayWithCapacity:array.count];
        MPCJSONSerializer * stringSerializer = [MPCJSONSerializer serializer];
        NSString * eventSerialization;

        // Serialize each event individually
        for (NSDictionary * eventInfo in array)
        {
            error = nil;
            eventSerialization = [stringSerializer serializeDictionary:eventInfo error:&error];
            if (error) {
                NSLog(@"%@ error encoding api data: %@", self, error);
            }
            else {
                [validEvents addObject:eventSerialization];
            }
        }

        // Nothing was valid
        if (validEvents.count == 0) {
            return @"";
        }

        // Generate JSON array from valid event components
        NSString * jsonString = [NSString stringWithFormat:@"[%@]", [validEvents componentsJoinedByString:@","]];
        data = [jsonString dataUsingEncoding:NSUTF8StringEncoding];
    }
    NSString *b64String = [data mp_base64EncodedString];
    b64String = (id)CFURLCreateStringByAddingPercentEscapes(kCFAllocatorDefault,
                                                            (CFStringRef)b64String,
                                                            NULL,
                                                            CFSTR("!*'();:@&=+$,/?%#[]"),
                                                            kCFStringEncodingUTF8);
    return [b64String autorelease];
}

Ultimately this could be a business decision since the inability to record a single event in a transaction might affect the consistency of data in Mixpanel. My 2¢ is that it is far worse to not record everything that can be recorded simply due to an error in one of many events. There is often no association between events in an upload unless the app happens to upload transactionally.

array bounds issue in calculateHMAC_SHA1

In MixPanelAPI.h in calculateHMAC_SHA1. The "digest" array is only 20 items in length, but the format string call below access 24 items.

return [NSString stringWithFormat: @"%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X",
digest[0], digest[1], digest[2], digest[3],
digest[4], digest[5], digest[6], digest[7],
digest[8], digest[9], digest[10], digest[11],
digest[12], digest[13], digest[14], digest[15],
digest[16], digest[17], digest[18], digest[19],
digest[20], digest[21], digest[22], digest[23]
];

Its possible this is not causing issues because the format string only has 20 parameters. The compiler warns, however, and it looks like something that could cause issues.

"NSString* calculateHMAC_SHA1(NSString *str, NSString *key)" in MixPanelAPI.h creates a 20 item array, but accessed 24 items.

NSString* calculateHMAC_SHA1(NSString *str, NSString *key) {
const char *cStr = [str UTF8String];
const char *cSecretStr = [key UTF8String];
unsigned char digest[CC_SHA1_DIGEST_LENGTH];
memset((void *)digest, 0x0, CC_SHA1_DIGEST_LENGTH);
CCHmac(kCCHmacAlgSHA1, cSecretStr, strlen(cSecretStr), cStr, strlen(cStr), digest);
return [NSString stringWithFormat:
@"%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X",
digest[0], digest[1], digest[2], digest[3],
digest[4], digest[5], digest[6], digest[7],
digest[8], digest[9], digest[10], digest[11],
digest[12], digest[13], digest[14], digest[15],
digest[16], digest[17], digest[18], digest[19],
digest[20], digest[21], digest[22], digest[23]
];
}

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.