ObjectiveC additions for humans. Ruby style.

Overview

logo

Write Objective C like a boss.

A set of functional additions for Foundation you wish you'd had in the first place.

Build Status

Usage

  1. Install via CocoaPods

    pod 'ObjectiveSugar'
    
  2. Import the public header

    #import <ObjectiveSugar/ObjectiveSugar.h>
    

Documentation

NSNumber additions

[@3 times:^{
    NSLog(@"Hello!");
}];
// Hello!
// Hello!
// Hello!

[@3 timesWithIndex:^(NSUInteger index) {
    NSLog(@"Another version with number: %d", index);
}];
// Another version with number: 0
// Another version with number: 1
// Another version with number: 2


[@1 upto:4 do:^(NSInteger numbah) {
    NSLog(@"Current number.. %d", numbah);
}];
// Current number.. 1
// Current number.. 2
// Current number.. 3
// Current number.. 4

[@7 downto:4 do:^(NSInteger numbah) {
    NSLog(@"Current number.. %d", numbah);
}];
// Current number.. 7
// Current number.. 6
// Current number.. 5
// Current number.. 4

NSDate *firstOfDecember = [NSDate date]; // let's pretend it's 1st of December

NSDate *firstOfNovember = [@30.days since:firstOfDecember];
// 2012-11-01 00:00:00 +0000

NSDate *christmas = [@7.days until:newYearsDay];
// 2012-12-25 00:00:00 +0000

NSDate *future = @24.days.fromNow;
// 2012-12-25 20:49:05 +0000

NSDate *past = @1.month.ago;
// 2012-11-01 20:50:28 +00:00

-- NSArray / NSSet additions

// All of these methods return a modified copy of the array.
// They're not modifying the source array.

NSArray *cars = @[@"Testarossa", @"F50", @"F458 Italia"]; // or NSSet

[cars each:^(id object) {
    NSLog(@"Car: %@", object);
}];
// Car: Testarossa
// Car: F50
// Car: F458 Italia

[cars eachWithIndex:^(id object, NSUInteger index) {
    NSLog(@"Car: %@ index: %i", object, index);
}];
// Car: Testarossa index: 0
// Car: F50 index: 1
// Car: F458 Italia index: 2

[cars each:^(id object) {
    NSLog(@"Car: %@", object);
} options:NSEnumerationReverse];
// Car: F458 Italia
// Car: F50
// Car: Testarossa

[cars eachWithIndex:^(id object, NSUInteger index) {
    NSLog(@"Car: %@ index: %i", object, index);
} options:NSEnumerationReverse];
// Car: F458 Italia index: 2
// Car: F50 index: 1
// Car: Testarossa index: 0

[cars map:^(NSString* car) {
    return car.lowercaseString;
}];
// testarossa, f50, f458 italia

// Or, a more common example:
[cars map:^(NSString* carName) {
    return [[Car alloc] initWithName:carName];
}];
// array of Car objects

NSArray *mixedData = @[ @1, @"Objective Sugar!", @"Github", @4, @"5"];

[mixedData select:^BOOL(id object) {
  return ([object class] == [NSString class]);
}];
// Objective Sugar, Github, 5

[mixedData reject:^BOOL(id object) {
    return ([object class] == [NSString class]);
}];
// 1, 4

NSArray *numbers = @[ @5, @2, @7, @1 ];
[numbers sort];
// 1, 2, 5, 7

cars.sample
// 458 Italia
cars.sample
// F50

-- NSArray only

NSArray *numbers = @[@1, @2, @3, @4, @5, @6];

// index from 2 to 4
numbers[@"2..4"];
// [@3, @4, @5]

// index from 2 to 4 (excluded)
numbers[@"2...4"];
// [@3, @4]

// With NSRange location: 2, length: 4
numbers[@"2,4"];
// [@3, @4, @5, @6]

NSValue *range = [NSValue valueWithRange:NSMakeRange(2, 4)];
numbers[range];
// [@3, @4, @5, @6]

[numbers reverse];
// [@6, @5, @4, @3, @2, @1]


NSArray *fruits = @[ @"banana", @"mango", @"apple", @"pear" ];

[fruits includes:@"apple"];
// YES

[fruits take:3];
// banana, mango, apple

[fruits takeWhile:^BOOL(id fruit) {
    return ![fruit isEqualToString:@"apple"];
}];
// banana, mango

NSArray *nestedArray = @[ @[ @1, @2, @3 ], @[ @4, @5, @6, @[ @7, @8 ] ], @9, @10 ];
[nestedArray flatten];
// 1, 2, 3, 4, 5, 6, 7, 8, 9, 10

NSArray *abc = @[ @"a", @"b", @"c" ];
[abc join];
// abc

[abc join:@"-"];
// a-b-c

NSArray *mixedData = @[ @1, @"Objective Sugar!", @"Github", @4, @"5"];

[mixedData detect:^BOOL(id object) {
    return ([object class] == [NSString class]);
}];
// Objective Sugar



// TODO: Make a better / simpler example of this
NSArray *landlockedCountries = @[ @"Bolivia", @"Paraguay", @"Austria", @"Switzerland", @"Hungary" ];
NSArray *europeanCountries = @[ @"France", @"Germany", @"Austria", @"Spain", @"Hungary", @"Poland", @"Switzerland" ];


[landlockedCountries intersectionWithArray:europeanCountries];
// landlockedEuropeanCountries = Austria, Switzerland, Hungary

[landlockedCountries unionWithArray:europeanCountries];
// landlockedOrEuropean = Bolivia, Paraguay, Austria, Switzerland, Hungary, France, Germany, Spain, Poland

[landlockedCountries relativeComplement:europeanCountries];
// nonEuropeanLandlockedCountries = Bolivia, Paraguay

[europeanCountries relativeComplement:landlockedCountries];
// notLandlockedEuropeanCountries = France, Germany, Spain, Poland

[landlockedCountries symmetricDifference:europeanCountries];
// uniqueCountries = Bolivia, Paraguay, France, Germany, Spain, Poland

-- NSMutableArray additions

NSMutableArray *people = @[ @"Alice", @"Benjamin", @"Christopher" ];

[people push:@"Daniel"]; // Alice, Benjamin, Christopher, Daniel

[people pop]; // Daniel
// people = Alice, Benjamin, Christopher

[people pop:2]; // Benjamin, Christopher
// people = Alice

[people concat:@[ @"Evan", @"Frank", @"Gavin" ]];
// people = Alice, Evan, Frank, Gavin

[people keepIf:^BOOL(id object) {
    return [object characterAtIndex:0] == 'E';
}];
// people = Evan

-- NSDictionary additions

NSDictionary *dict = @{ @"one" : @1, @"two" : @2, @"three" : @3 };

[dict each:^(id key, id value){
    NSLog(@"Key: %@, Value: %@", key, value);
}];
// Key: one, Value: 1
// Key: two, Value: 2
// Key: three, Value: 3

[dict eachKey:^(id key) {
    NSLog(@"Key: %@", key);
}];
// Key: one
// Key: two
// Key: three

[dict eachValue:^(id value) {
    NSLog(@"Value: %@", value);
}];
// Value: 1
// Value: 2
// Value: 3

[dict invert];
// { 1 = one, 2 = two, 3 = three}

NSDictionary *errors = @{
    @"username" : @[ @"already taken" ],
    @"password" : @[ @"is too short (minimum is 8 characters)", @"not complex enough" ],
    @"email" : @[ @"can't be blank" ];
};

[errors map:^(id attribute, id reasons) {
    return NSStringWithFormat(@"%@ %@", attribute, [reasons join:@", "]);
}];
// username already taken
// password is too short (minimum is 8 characters), not complex enough
// email can't be blank

[errors hasKey:@"email"]
// true
[errors hasKey:@"Alcatraz"]
// false

-- NSString additions

NSString *sentence = NSStringWithFormat(@"This is a text-with-argument %@", @1234);
// This is a text-with-argument 1234

[sentence split];
// array = this, is, a, text-with-argument, 1234

[sentence split:@"-"]
// array = this is a text, with, argument 1234

[sentence containsString:@"this is a"];
// YES

[sentence match:@"-[a-z]+-"]
// -with-

-- C additions

unless(_messages) {
    // The body is only executed if the condition is false
    _messages = [self initializeMessages];
}

int iterations = 10;
until(iterations == 0) {
    // The body is executed until the condition is false
    // 10 9 8 7 6 5 4 3 2 1
    printf("%d ", iterations);
    iterations--;
}
printf("\n");

iterations = 10;
do {
    // The body is executed at least once until the condition is false
    // Will print: Executed!
    printf("Executed!\n");
} until(true);

Contributing

ObjectiveSugar is tested with Kiwi, and tests are located in Example.
If you plan on contributing to the project, please:

  • Write tests
  • Write documentation

Team

Comments
  • documentation up to date?

    documentation up to date?

    So, how up-to-date is the documentation in the README? Some of the examples don't seem to work (though I may just be an idiot):

    //this gives the error "no visibile @interface for 'nsarray' declares the selector 'detect'"

    NSArray *mixedData = @[ @1, @"Objective Sugar!", @"Github", @4, @"5"];
    [mixedData detect:^BOOL(id object) {
        return ([object class] == [NSString class]);
    }];
    

    //this gives the error 'Expected method to read dictionary element not found on object of type 'NSArray *'

    NSArray *indices = @[@1, @2, @3, @4, @5];
    indices[@"1..3"];
    

    Other examples seem to work, however. Am I missing something?

    opened by seanicus 15
  • added reduce methods to NSArray and NSSet

    added reduce methods to NSArray and NSSet

    Return a single value from an array or set by iterating through the elements and transforming a running total. Similar to Ruby's reduce method, also known as fold or inject.

    opened by robinsenior 13
  • Zip with

    Zip with

    I needed zipWith:: function and wrote some code. Basically it simply zips two arrays into new array of shortest possible size using provided zip block. Also added zip: function which is alias for zipWith:: and block which returns @[left, right] as new value (tuples, where are you?:-) Added tests.

    opened by nmtitov 11
  • Shorter NSStringWithFormat

    Shorter NSStringWithFormat

    As you all know, [NSString stringWithFormat:@"...", ...] is a lot of syntax for something so common. This is why ObjectiveSugar implemented the NSStringWithFormat function. It is indeed shorter, but well... not a lot.

    How about a format: method on NSString? [@"..." format:...] would be much more appealing in my opinion.

    I will implement this method and update the tests and doc if anyone finds this useful.

    opened by iluuu1994 10
  • Reduce

    Reduce

    @robinsenior here's your PR #75 merged and counterparts added.

    On the side note - By this point I'm thinking if we should stop supporting NSSet additions for 2.0, because the behavior with nils and ordering makes it a bit of mess.

    opened by supermarin 6
  • Fix potential leak based on analyzer advice

    Fix potential leak based on analyzer advice

    Analyzer Warnings:
    
      0) Pods/ObjectiveSugar/Classes/NSArray+ObjectiveSugar.m:172:12: Potential leak of an object stored into 'descriptor':
    ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
    169 
    170 - (NSArray *)sortBy:(NSString*)key; {
    171     NSSortDescriptor *descriptor = [[NSSortDescriptor alloc] initWithKey:key ascending:YES];
    172     return [self sortedArrayUsingDescriptors:@[descriptor]];
            ~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    173 }
    174 <rest>
    
    Pods/ObjectiveSugar/Classes/NSArray+ObjectiveSugar.m:171:36: Method returns an Objective-C object with a +1 retain count
    Pods/ObjectiveSugar/Classes/NSArray+ObjectiveSugar.m:172:12: Object leaked: object allocated and stored into 'descriptor' is not referenced later in this execution path and has a retain count of +1
    ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
    
    ** ANALYZE FAILED ** (13137 ms)
    
    opened by mickeyreiss 6
  • Clean up

    Clean up

    Naming consistency: all categories are now named +ObjectiveSugar. Tests all pass.

    Removed pods from the git repo. I don't think this was ever intended to be committed? I've also moved the .gitignore file to the top most level, where it should be for it to work properly.

    opened by kevinrenskers 6
  • Macros proposal

    Macros proposal

    Hi,

    thanks for starting this library! I was planning to do it myself, but never found the time to. I have experimented with some macros to bring the part of ruby that I love the most to Objective-C. Following you can find the unfinished result. What do you think? Is this something that you would be interested in adding to your ObjectiveSugar?

    //#define RACAble(...) metamacro_if_eq(1, metamacro_argcount(__VA_ARGS__))(_RACAbleObject(self, __VA_ARGS__))(_RACAbleObject(__VA_ARGS__))
    
    ///-----------------------------------------------------------------------------
    // @name map
    ///-----------------------------------------------------------------------------
    
    #define $to(selector) ^id (id x) { return [x selector]; }
    [authors map:$to(name)] 
    [authors map:$to(nameWithAge:date)]
    
    ///-----------------------------------------------------------------------------
    // @name filter & select
    ///-----------------------------------------------------------------------------
    
    #define $by(selector) ^BOOL (id x) { return [x selector]; }
    [views filter:$by(hidden)]
    
    #define $if(expression) ^BOOL (id x) { return (expression); }
    [views filter:$if([x frame].origin.x > 10)]
    
    
    ///-----------------------------------------------------------------------------
    // @name each
    ///-----------------------------------------------------------------------------
    
    #define $do(expression) ^(id x) { {expression;}; }
    [lions each:$do(attack)]
    
    #define $doWithKey(expression) ^(id* key, id x) { (expression); }
    
    opened by fabiopelosin 6
  • Add slather for test coverage

    Add slather for test coverage

    Computes test coverage using slather. Allows you to see what code is tested, and also gives you a cute little badge. Just need to enable the repo at https://coveralls.io

    Coverage Status

    opened by marklarr 5
  • NSSet should return NSSet

    NSSet should return NSSet

    I've been looking for a replacement for BlocksKit, and I was wondering why NSSet selectors return an NSArray? And would you be open for a PR to change it?

    opened by seivan 5
  • Use fast enumeration

    Use fast enumeration

    NSArray+ObjectiveSugar.m

    • (void)each:(void (^)(id object))block { for (id object in self) block(object); }

    NSDictionary+ObjectiveSugar.m

    • (void)eachKey:(void (^)(id k))block { for (id key in self) block(key); }

    NSSet+ObjectiveSugar.m

    • (void)each:(void (^)(id object))block { for (id object in self) block(object); }
    opened by viking2009 4
  • Add one function : map NSArray and skip someone

    Add one function : map NSArray and skip someone

    Sometimes not only need to map out an array, but also need to conditional mapping. Reference to NSArray's block traversal method - (void) enumerateObjectsUsingBlock: (void (NS_NOESCAPE ^) (ObjectType obj, NSUInteger idx, BOOL * stop)) block.

    Add a skip option for the mapping method:

    - (NSArray *) mm_mapWithskip:(id (^)(id obj, BOOL *skip))callback{
        
        NSMutableArray * _self = [NSMutableArray arrayWithCapacity:self.count];
        
        for( id obj in self ){
            
            BOOL skip = NO;
            
            id mapObj = callback(obj, &skip);
            
            if( !skip ){
                [_self addObject:mapObj];
            }
        }
        return [_self copy];
    }
    

    Refer to this answer

    opened by Yrocky 0
  • -[NSArray reduce:withBlock:] implementation is incorrect

    -[NSArray reduce:withBlock:] implementation is incorrect

    When initial value is set to nil and the array consists of single item, this item is returned and reduce block is never evaluated.

    There seems to be an issue at https://github.com/supermarin/ObjectiveSugar/blob/master/Classes/NSArray%2BObjectiveSugar.m#L178

    I would rather write it as

    accumulator = block(accumulator, object);
    

    Does this make sense?

    opened by soxjke 0
Owner
Marin
Marin
Elegant Apply Style by Swift Method Chain.🌙

ApplyStyleKit ApplyStyleKit is a library that applies styles to UIKit using Swifty Method Chain. Normally, when applying styles to UIView etc.,it is n

shindyu 203 Nov 22, 2022
Make trains of constraints with a clean style!

Constren ?? . ?? . ?? Make trains of constraints with style! button.constren.centerY() .lead(spacing: 16) .trail(image.l

Doruk Çoban 4 Dec 19, 2021
Basic Style Dictionary With Swift

Basic Style Dictionary This example code is bare-bones to show you what this framework can do. If you have the style-dictionary module installed globa

Tiago Oliveira 1 Nov 24, 2021
A danger-swift plug-in to manage/post danger checking results with markdown style

DangerSwiftShoki A danger-swift plug-in to manage/post danger checking results with markdown style Install DangerSwiftShoki SwiftPM (Recommended) Add

YUMEMI Inc. 4 Dec 17, 2021
Generates Heroku-style random project names in Swift

RandomProjectName.swift Generates Heroku-style random project names in Swift. Usage Just call String.randomProjectName(), and specify the optional suf

NLUDB 0 Dec 6, 2021
Extensions which helps to convert objc-style target/action to swifty closures

ActionClosurable Usage ActionClosurable extends UIControl, UIButton, UIRefreshControl, UIGestureRecognizer and UIBarButtonItem. It helps writing swift

takasek 121 Aug 11, 2022
Adding ruby style each iterator to Cocoa/Cocoa touch Swift Array and Range classes, And Int.times{} to Int class

Collection-Each Adding ruby style each iterator to Cocoa/Cocoa touch Swift Array, Dictionary and Range classes, and Int.times ###Why? Array/Dictionary

Omar Abdelhafith 65 Jun 9, 2018
Extensions and additions to AsyncSequence, AsyncStream and AsyncThrowingStream.

Asynchone Extensions and additions to AsyncSequence, AsyncStream and AsyncThrowingStream. Requirements iOS 15.0+ macOS 12.0+ Installation Swift Packag

Red Davis 101 Jan 6, 2023
AsyncTaskKit - contains some additions to async/await Task

AsyncTaskKit This repo contains some additions to async/await Task. In general i

Michał Zaborowski 0 Jan 2, 2022
Swift HTTP for Humans

Just is a client-side HTTP library inspired by python-requests - HTTP for Humans. Features Just lets you to the following effortlessly: URL queries cu

Daniel Duan 1.4k Dec 30, 2022
Swift HTTP for Humans

Just is a client-side HTTP library inspired by python-requests - HTTP for Humans. Features Just lets you to the following effortlessly: URL queries cu

Daniel Duan 1.4k Dec 30, 2022
Weather and forecasts for humans. Information you can act on.

Tropos Weather and forecasts for humans. Information you can act on. Most weather apps throw a lot of information at you but that doesn't answer the q

thoughtbot, inc. 1.5k Dec 28, 2022
SwiftCop is a validation library fully written in Swift and inspired by the clarity of Ruby On Rails Active Record validations.

SwiftCop is a validation library fully written in Swift and inspired by the clarity of Ruby On Rails Active Record validations. Objective Build a stan

Andres Canal 542 Sep 17, 2022
This is a Swift port of Ruby's Faker library that generates fake data.

This is a Swift port of Ruby's Faker library that generates fake data. Are you still bothered with meaningless randomly character strings? Just relax

Vadym Markov 1.6k Jan 3, 2023
A Ruby on Rails inspired Web Framework for Swift that runs on Linux and OS X

IMPORTANT! We don't see any way how to make web development as great as Ruby on Rails or Django with a very static nature of current Swift. We hope th

Saulius Grigaitis 2k Dec 5, 2022
Ruby Gem for Rails - Easy iTunes In-App Purchase Receipt validation, including auto-renewable subscriptions

Monza is a ruby gem that makes In-App Purchase receipt and Auto-Renewable subscription validation easy. You should always validate receipts on the ser

Gabriel 159 Jan 7, 2023
A Ruby on Rails inspired Web Framework for Swift that runs on Linux and OS X

IMPORTANT! We don't see any way how to make web development as great as Ruby on Rails or Django with a very static nature of current Swift. We hope th

Saulius Grigaitis 2k Dec 5, 2022
SwiftLint - A tool to enforce Swift style and conventions, loosely based on Swift Style Guide.

SwiftLint - A tool to enforce Swift style and conventions, loosely based on Swift Style Guide.

Realm 16.9k Dec 30, 2022
Style Art library process images using COREML with a set of pre trained machine learning models and convert them to Art style.

StyleArt Style Art is a library that process images using COREML with a set of pre trained machine learning models and convert them to Art style. Prev

iLeaf Solutions Pvt. Ltd. 222 Dec 17, 2022
Airbnb's Swift Style Guide.

Airbnb Swift Style Guide Goals Following this style guide should: Make it easier to read and begin understanding unfamiliar code. Make code easier to

Airbnb 1.8k Jan 3, 2023