Reason
Usage
To run the example project, clone the repo, and run pod install
from the Example directory first.
Installation
KSReason is available through CocoaPods. To install it add the following line to your Podfile:
pod "KSReason"
Usage
Backbone
Backbone is a component designed to implement models and collections (inspired by the web framework with the same name).
Models
Models are designed to be extended and include serialization, validations, getters, setters, archiving, and copying:
KSModel *model = [KSModel new];
[model parse:@{ @"id": @"...", @"title": @"...", @"description": @"..." }];
[model get:@"title"];
[model get:@"description"];
Collections
Collections are ordered sets of models and include serialization, enumeration, archiving, and copying:
KSCollection *collection = [KSCollection new];
[collection parse:@[@{ @"id": @"...", @"title": @"...", @"description": @"..." }]];
collection.models;
Events
Events can be applied to both models and collections to easily track modifications:
KSModel *model = [KSModel new];
void (^callback)(KSModel *) = ^(KSModel *model) {
// ...
};
[model on:@"change" callback:callback];
[model parse:@{ @"id": @"...", @"title": @"...", @"description": @"..." }];
[model off:@"change" callback:callback];
KSCollection *collection = [KSCollection new];
void (^callback)(KSCollection *) = ^(KSCollection *collection) {
// ...
};
[collection on:@"reset" callback:callback];
[model parse:@[@{ @"id": @"...", @"title": @"...", @"description": @"..." }]];
[collection off:@"reset" callback:callback];
Stateful
Stateful is a component designed to implement finite state machines. It support states, transitions, guards, and callbacks.
Configuration
Configuration of a finite state machine is done by configuring states, transitions, guards and callbacks:
__block typeof(self) bself = self;
KSFSM *fsm = [KSFSM new];
// States:
KSState *sleeping = [KSState named:@"sleeping"];
KSState *working = [KSState named:@"working"];
KSState *resting = [KSState named:@"resting"];
KSState *dead = [KSState named:@"dead"];
[fsm.states add:sleeping];
[fsm.states add:working];
[fsm.states add:resting];
[fsm.states add:dead];
fsm.states.initial = resting;
// Transitions:
[fsm.transitions add:[KSTransition named:@"work" from:sleeping to:working]];
[fsm.transitions add:[KSTransition named:@"rest" from:sleeping to:resting]];
[fsm.transitions add:[KSTransition named:@"sleep" from:@[working,resting] to:sleeping]];
[fsm.transitions add:[KSTransition named:@"sleep" from:@[sleeping,working,resting] to:dead]];
// Guards:
[work enterable:^BOOL {
// i.e. return bself.weekday;
}];
[rest enterable:^BOOL {
// i.e. return bself.weekend;
}];
[sleep exitable:^BOOL {
// i.e. return bself.daytime;
}];
// Callbacks:
[sleeping entered:^(KSState *state, KSTransition *transition) {
// i.e. ZZZ ZZZ ZZZ
}];
[sleeping exited:^(KSState *state, KSTransition *transition) {
// i.e. BEEP BEEP BEEP
}];
Usage
Usage of a stateful object is done by executing transitions and checking for errors:
NSError *error;
[fsm reset]; // Optional.
[fsm transition:@"work" error:&error].execute;
fsm.state; // working;
[fsm transition:@"sleep" error:&error].execute;
fsm.state; // sleeping;
[fsm transition:@"rest" error:&error].execute;
fsm.state; // resting;
[fsm transition:@"sleep" error:&error].execute;
fsm.state; // sleeping;
[fsm transitionable:@"sleep"]; // i.e. YES or NO
if (![fsm transition:@"sleep" error:&error].execute);
error; // <NSError @"invalid transition 'sleep' from 'sleeping' to 'sleeping">;
Enumerable
Enumerable offers a number of extentions to support better enumeration for arrays, dictionaries, and sets:
Each
Each performs a block once on each entry of a collection:
Sets:
NSSet *collection = [NSSet setWithObjects:@"Canada", @"Greece", @"Russia", NULL];
[collection ks_each:^(NSString *object) {
// ...
}];
Arrays:
NSArray *collection = [NSArray arrayWithObjects;@"Canada", @"Greece", @"Russia", NULL];
[collection ks_each:^(NSString *object) {
// ...
}];
Dictionaries:
NSDictionary *collection = @{ @"Canada": @"Victoria", @"Russia": @"Moscow", @"Greece": @"Athens" };
[collection ks_each:^(NSString *key, NSString *value) {
// ...
}];
Map
Map performs a block once on each element of a collection and returns a collection of the same type with the block results:
Sets:
NSSet *collection = [NSSet setWithObjects:@"Canada", @"Greece", @"Russia", NULL];
NSSet *mapping = [collection ks_map:^NSString *(NSString *object) {
// ex: NSString *mapping = [object reverse];
return mapping;
}];
Arrays:
NSArray *collection = [NSArray arrayWithObjects;@"Canada", @"Greece", @"Russia", NULL];
NSArray *mapping = [collection ks_map:^NSString *(NSString *object) {
// ex: NSString *mapping = [object reverse];
return mapping;
}];
Dictionaries:
NSDictionary *collection = @{ @"Canada": @"Victoria", @"Russia": @"Moscow", @"Greece": @"Athens" };
NSDictionary *mapping = [collection ks_map:^NSString *(NSString *key, NSString *value) {
// ex: NSString *mapping = [value reverse];
return mapping;
}];
Reduce
Reduce performs a block once on each element of a collection tracking a memo that will be returned as every entry is iterated:
Sets:
NSSet *collection = [NSSet setWithObjects:@"Canada", @"Greece", @"Russia", NULL];
NSString *reduction = [collection ks_reduce:^NSString *(NSString *memo, NSString *object){
return [NSString stringWithFormat:@"%@ %@", memo, object];
} memo:@"reduction:"];
Arrays:
NSArray *collection = [NSArray arrayWithObjects;@"Canada", @"Greece", @"Russia", NULL];
NSString *reduction = [collection ks_reduce:^NSString *(NSString *memo, NSString *object){
return [NSString stringWithFormat:@"%@ %@", memo, object];
} memo:@"reduction:"];
Dictionaries:
NSArray *collection = [NSArray arrayWithObjects;@"Canada", @"Greece", @"Russia", NULL];
NSString *reduction = [collection ks_reduce:^NSString *(NSString *memo, NSString *key, NSString *value){
return [NSString stringWithFormat:@"%@ %@", memo, value];
} memo:@"reduction:"];
Find
Find passes each element of a collection and returns the element matching the criteria:
Sets:
NSSet *collection = [NSSet setWithObjects:@"Canada", @"Greece", @"Russia", NULL];
NSString *element = [collection ks_find:^BOOL (NSString *object) {
return [object isEqualToString:@"..."];
}];
Arrays:
NSArray *collection = [NSArray arrayWithObjects:@"Canada", @"Greece", @"Russia", NULL];
NSString *element = [collection ks_find:^BOOL (NSString *object) {
return [object isEqualToString:@"..."];
}];
Dictionaries:
NSDictionary *collection = @{ @"Canada" : @"Victoria", @"Greece": @"Athens", @"Russia": @"Moscow" };
NSString *element = [collection ks_find:^BOOL (NSString *key, NSString *value) {
return [key isEqualToString:@"..."];
}];
Any
Any passes each element of a collection to a given block and returns a boolean indicating if the block matches any element:
Sets:
NSSet *collection = [NSSet setWithObjects:@"Canada", @"Greece", @"Russia", NULL];
BOOL any = [collection ks_any:^BOOL (NSString *object) {
return [object isEqualToString:@"..."];
}];
Arrays:
NSArray *collection = [NSArray arrayWithObjects:@"Canada", @"Greece", @"Russia", NULL];
BOOL any = [collection ks_any:^BOOL (NSString *object) {
return [object isEqualToString:@"..."];
}];
Dictionaries:
NSDictionary *collection = @{ @"Canada" : @"Victoria", @"Greece": @"Athens", @"Russia": @"Moscow" };
BOOL any = [collection ks_any:^BOOL (NSString *key, NSString *value) {
return [key isEqualToString:@"..."];
}];
All
All passes each element of a collection to a given block and returns a boolean indicating if the block ever matches every element:
Sets:
NSSet *collection = [NSSet setWithObjects:@"Canada", @"Greece", @"Russia", NULL];
BOOL all = [collection ks_all:^BOOL (NSString *object) {
return [object isEqualToString:@"..."];
}];
Arrays:
NSArray *collection = [NSArray arrayWithObjects:@"Canada", @"Greece", @"Russia", NULL];
BOOL all = [collection ks_all:^BOOL (NSString *object) {
return [object isEqualToString:@"..."];
}];
Dictionaries:
NSDictionary *collection = @{ @"Canada" : @"Victoria", @"Greece": @"Athens", @"Russia": @"Moscow" };
BOOL all = [collection ks_all:^BOOL (NSString *key, NSString *value) {
return [key isEqualToString:@"..."];
}];
Filter
Filter passes each element of a collection to a given block and returns the filtered elements that do match the block:
Sets:
NSSet *collection = [NSSet setWithObjects:@"Canada", @"Greece", @"Russia", NULL];
NSSet *filter = [collection ks_filter:^BOOL (NSString *object) {
// ex.: BOOL match = [object isEqualToString:@"..."];
return match;
}];
Arrays:
NSArray *collection = [NSArray arrayWithObjects:@"Canada", @"Greece", @"Russia", NULL];
NSArray *filtered = [collection ks_filter:^BOOL (NSString *object) {
// ex.: BOOL match = [object isEqualToString:@"..."];
return match;
}];
Dictionaries:
NSDictionary *collection = @{ @"Canada" : @"Victoria", @"Greece": @"Athens", @"Russia": @"Moscow" };
NSDictionary *filtered = [collection ks_filter:^BOOL (NSString *key, NSString *value) {
// ex.: BOOL match = [key isEqualToString:@"..."] || [value isEqualToString:@"..."];
return match;
}];
Reject
Reject passes each element of a collection to a given block and returns the filtered elements that do not match the block:
Sets:
NSSet *collection = [NSSet setWithObjects:@"Canada", @"Greece", @"Russia", NULL];
NSSet *filter = [collection ks_reject:^BOOL (NSString *object) {
// ex.: BOOL match = [object isEqualToString:@"..."];
return match;
}];
Arrays:
NSArray *collection = [NSArray arrayWithObjects:@"Canada", @"Greece", @"Russia", NULL];
NSArray *filtered = [collection ks_reject:^BOOL (NSString *object) {
// ex.: BOOL match = [object isEqualToString:@"..."];
return match;
}];
Dictionaries:
NSDictionary *collection = @{ @"Canada" : @"Victoria", @"Greece": @"Athens", @"Russia": @"Moscow" };
NSDictionary *filtered = [collection ks_reject:^BOOL (NSString *key, NSString *value) {
// ex.: BOOL match = [key isEqualToString:@"..."] || [value isEqualToString:@"..."];
return match;
}];
Union
Union produces the union of the target collection and the parameter collection:
Sets:
NSSet *alpha = [NSSet setWithObjects:@"A", @"B", NULL];
NSSet *omega = [NSSet setWithObjects:@"B", @"C", NULL];
[alpha ks_union:omega]; // [NSSet setWithObjects:@"A", @"B", @"C", NULL];
Arrays:
NSArray *alpha = [NSArray arrayWithObjects:@"A", @"B", NULL];
NSArray *omega = [NSArray arrayWithObjects:@"B", @"C", NULL];
[alpha ks_union:omega]; // [NSArray arrayWithObjects:@"A", @"B", @"C", NULL];
Dictionaries:
NSDictionary *alpha = @{ @"A": @"A", @"B": @"B" };
NSDictionary *omega = @{ @"B": @"B", @"C": @"C" };
[alpha ks_union:omega]; // @{ @"A": @"A", @"B": @"B", @"C": @"C" };
Intersection
Intersection produces the intersection of the target collection and the parameter collection:
Sets:
NSSet *alpha = [NSSet setWithObjects:@"A", @"B", NULL];
NSSet *omega = [NSSet setWithObjects:@"B", @"C", NULL];
[alpha ks_intersection:omega]; // [NSSet setWithObjects:@"B", NULL];
Arrays:
NSArray *alpha = [NSArray arrayWithObjects:@"A", @"B", NULL];
NSArray *omega = [NSArray arrayWithObjects:@"B", @"C", NULL];
[alpha ks_intersection:omega]; // [NSArray arrayWithObjects:@"B", NULL];
Dictionaries:
NSDictionary *alpha = @{ @"A": @"A", @"B": @"B" };
NSDictionary *omega = @{ @"B": @"B", @"C": @"C" };
[alpha ks_intersection:omega]; // @{ @"B": @"B" };
Difference
Difference produces the difference of the target collection and the parameter collection:
Sets:
NSSet *alpha = [NSSet setWithObjects:@"A", @"B", NULL];
NSSet *omega = [NSSet setWithObjects:@"B", @"C", NULL];
[alpha ks_difference:omega]; // [NSSet setWithObjects:@"A", @"C", NULL];
Arrays:
NSArray *alpha = [NSArray arrayWithObjects:@"A", @"B", NULL];
NSArray *omega = [NSArray arrayWithObjects:@"B", @"C", NULL];
[alpha ks_difference:omega]; // [NSArray arrayWithObjects:@"A", @"C", NULL];
Dictionaries:
NSDictionary *alpha = @{ @"A": @"A", @"B": @"B" };
NSDictionary *omega = @{ @"B": @"B", @"C": @"C" };
[alpha ks_difference:omega]; // @{ @"A": @"A", @"C": @"C" };
Minimum
Minimum searches a collection using compare:
to get the minimum element (elements must implement compare:
):
Sets:
NSSet *collection = [NSSet setWithObjects:@1.0, @1.25, @0.75, NULL];
collection.ks_minimum; //0.75
Arrays:
NSArray *collection = [NSArray arrayWithObjects:@1.00, @1.25, @0.75, NULL];
collection.ks_minimum; // 0.75;
Dictionaries:
NSDictionary *collection = @{ @"USD": @1.00, @"CDN": @1.25, @"EUR": @0.75 };
collection.ks_minimum; // 0.75
Maximum
Maximum searches a collection using compare:
to get the maximum element (elements must implement compare:
):
Sets:
it (@"exposes the maximum in an set", ^{
NSSet *collection = [NSSet setWithObjects:@1.00, @1.25, @0.75, NULL];
collection.ks_maximum; // 1.25
Arrays:
it (@"exposes the maximum in an array", ^{
NSArray *collection = [NSArray arrayWithObjects:@1.0, @1.25, @0.75, NULL];
collection.ks_maximum; //1.25);
Dictionaries:
NSDictionary *collection = @{ @"USD": @1.0, @"CDN": @1.25, @"EUR": @0.75 };
collection.ks_maximum; //1.25
Sample
Sample grabs a random element from a collection.
Sets:
NSSet *collection = [NSSet setWithObjects:@1.0, @1.25, @0.75, NULL];
collection.ks_sample; // (1.0 | 1.25 | 0.75)
Arrays:
NSArray *collection = [NSArray arrayWithObjects:@1.0, @1.25, @0.75, NULL];
collection.ks_sample; // (1.0 | 1.25 | 0.75)
Dictionaries:
NSDictionary *collection = @{ @"USD": @1.0, @"CDN": @1.25, @"EUR": @0.75 };
collection.ks_sample; // (1.0 | 1.25 | 0.75)
Functions
Functions offers a number of helpers for working with blocks:
Debounce
Debounce defers a block from executing until after an interval elapses:
KSDebounce *debounce = [KSDebounce new];
__block typeof(self) bself;
[debounce debounce:0.2 block: ^{
[bself search:self.mainSearchBar.text];
}];
Validations
Validate
NSDictionary *attributes = @{ @"name": @"John Smith", @"email": @"john.smith@mail.org", @"phone": @"+1 555-555-5555" };
NSDictionary *validations = @{
@"name": @{ KSValidate.presence: @{ KSValidate.message: @"must be entered" } },
@"tagline": @{ KSValidate.length: @{ KSValidate.minimum: @20, KSValidate.maximum: @80 } },
@"email": @{ KSValidate.format: @{ KSValidate.with: KSValidateFormatEmail } },
@"phone": @{ KSValidate.format: @{ KSValidate.with: KSValidateFormatPhone } },
@"country": @{ KSValidate.inclusion: @{ KSValidate.of: @[@"Canada"] } },
@"region": @{ KSValidate.exclusion: @{ KSValidate.of: @[@"PEI"] } },
};
KSValidator *validator = [KSValidator validator:validations];
[validator validate:attributes];
validator.errors;
// ex.:
// @{
// @"tagline": @[@"must be between 20 and 80 characters", @"cannot contain inappriate language"],
// @"email": @[@"is formatted wrong"], @"phone": @[@"is formatted wrong"]
// };
validator.humanize;
// @"tagline must be between 20 and 80 characters, tagline cannot contain inapproriate language, email is formatted wrong, phone is formatted wrong and name can't be blank"
Length
[KSValidator length:@"test" is:8]; // NO
[KSValidator length:@"test" is:4]; // YES
[KSValidator length:@"test" minimum:5]; // NO
[KSValidator length:@"test" minimum:4]; // YES
[KSValidator length:@"test" minimum:3]; // YES
[KSValidator length:@"test" maximum:3]; // NO
[KSValidator length:@"test" maximum:4]; // YES
[KSValidator length:@"test" maximum:5]; // YES
Format
[KSValidator format:@"tester" with:KSValidationEmail]; // NO
[KSValidator format:@"tester@mail.org" with:KSValidationEmail]; // YES
[KSValidator format:@"tester" with:KSValidationPhone]; // NO
[KSValidator format:@"+1 555-555-5555" with:KSValidationPhone]; // YES
[KSValidator format:@"..." with:@"\\...\\"]; // NO
[KSValidator format:@"abcdefghijklmnopqrstuvwxyz" with:@"\\..\\"]; // YES
Inclusion
[KSValidator inclusion:@"blue" collection:@[@"blue"]]; // YES
[KSValidator inclusion:@"pink" collection:@[@"blue"]]; // NO
Exclusion
[KSValidator exclusion:@"blue" collection:@[@"blue"]]; // YES
[KSValidator exclusion:@"pink" collection:@[@"blue"]]; // NO
Presence
[KSValidator presence:@"Greetings!"]; // YES
Absence
[KSValidator absence:@"Greetings!"]; // NO
The attribute names may also be addint the following to Localizable.strings
:
Localizations
Messages:
"is invalid": "...";
"can't be blank": "...";
"can't be present": "...";
"must be exactly %@ characters": "...";
"must be between %@ and %@ characters": "...";
"must be a minimum of %@ characters": "...";
"must be a maximum of %@ characters": "...";
Attributes:
"ssn" = "social security number";
Inflections
Pluralize
[@"cookie" ks_pluralize]; // "cookies"
[@"cherry" ks_pluralize]; // "cherries"
[@"potato" ks_pluralize]; // "potatoes"
Singularize
[@"cookies" ks_singularize]; // "cookie"
[@"cherries" ks_singularize]; // "cherry"
[@"potatoes" ks_singularize]; // "potato"
Inflecting
[KSInflector.shared inflect:1 string:@"star"]; // "1 star" [KSInflector.shared inflect:2 string:@"star"]; // "2 stars" [KSInflector.shared inflect:3 string:@"star"]; // "3 stars"
Advanced
[KSInflector config:^(KSInflector *inflector) {
[inflector singular:@"oxen$" replacement:@"ox"];
[inflector plural:@"ox$" replacement:@"oxen"];
}];
Existentialism
Objects
[NSNull null].ks_exists; // NO
[NSObject new].ks_exists; // YES
Sets
[NSSet set].ks_exists; // NO
[NSSet setWithObject:object].ks_exists; // YES
Arrays
[NSArray set].ks_exists; // NO
[NSArray setWithObject:object].ks_exists; // YES
Dictionaries
@{}.ks_exists; // NO
@{ key: value }.ks_exists; // YES
Strings
@"".ks_exists; // NO
@"Greetings!".ks_exists; // YES
Author
Kevin Sylvestre, kevin@ksylvest.com
License
KSReason is available under the MIT license. See the LICENSE file for more info.