TNLExample/TAPI/TAPIModel.m (172 lines of code) (raw):
//
// TAPIModel.m
// TwitterNetworkLayer
//
// Created on 10/17/14.
// Copyright © 2020 Twitter. All rights reserved.
//
#import <TwitterNetworkLayer/TwitterNetworkLayer.h>
#import "TAPIModel.h"
@interface TAPIUserObject : NSObject <TAPIUserModel>
@property (nonatomic, readonly) long long userID;
@property (nonatomic, readonly, copy) NSString *name;
@property (nonatomic, readonly, copy) NSString *screenName;
- (instancetype)initWithJSONObject:(id)object;
@end
@interface TAPIImageEntityVariantObject : NSObject <TAPIImageEntityVariantModel>
@property (nonatomic, readonly, copy) NSString *name;
@property (nonatomic, readonly) CGSize dimensions;
- (instancetype)initWithName:(NSString *)name dimensions:(CGSize)dims;
@end
@interface TAPIImageEntityObject : NSObject <TAPIImageEntityModel>
@property (nonatomic, readonly, copy) NSString *baseURLString;
@property (nonatomic, readonly, copy) NSString *format;
@property (nonatomic, readonly, copy) NSArray<id<TAPIImageEntityVariantModel>> *variants;
- (instancetype)initWithJSONObject:(id)object;
@end
@interface TAPIStatusObject : NSObject<TAPIStatusModel>
@property (nonatomic, readonly) long long statusID;
@property (nonatomic, readonly) NSDate *creationDate;
@property (nonatomic, readonly, copy) NSString *text;
@property (nonatomic, readonly) long long retweetCount;
@property (nonatomic, readonly) long long favoriteCount;
@property (nonatomic, readonly) BOOL possiblySensitive;
@property (nonatomic, readonly) id<TAPIUserModel> user;
@property (nonatomic, readonly, copy) NSArray<id<TAPIImageEntityModel>> *images;
- (instancetype)initWithJSONObject:(id)object;
@end
@implementation TAPIUserObject
- (instancetype)initWithJSONObject:(id)object
{
if (![object isKindOfClass:[NSDictionary class]]) {
return nil;
}
_userID = [object[@"id"] longLongValue];
_name = [object[@"name"] copy];
_screenName = [object[@"screen_name"] copy];
if (_screenName.length == 0 || !_name || !_userID) {
return nil;
}
return self;
}
@end
@implementation TAPIImageEntityVariantObject
- (instancetype)initWithName:(NSString *)name dimensions:(CGSize)dims
{
if (!name.length) {
return nil;
}
if (dims.height <= 0 || dims.width <= 0) {
return nil;
}
_name = [name copy];
_dimensions = dims;
return self;
}
@end
@implementation TAPIImageEntityObject
- (instancetype)initWithJSONObject:(id)object
{
if (![object isKindOfClass:[NSDictionary class]]) {
return nil;
}
NSDictionary *media = object;
if (![media[@"type"] isEqual:@"photo"]) {
return nil;
}
NSString *URLString = media[@"media_url_https"];
if (URLString.length == 0) {
return nil;
}
_format = [URLString pathExtension];
if (_format.length == 0) {
return nil;
}
_baseURLString = [URLString substringToIndex:URLString.length - (_format.length + 1)];
if (_baseURLString.length == 0) {
return nil;
}
NSDictionary *sizes = media[@"sizes"];
if (![sizes isKindOfClass:[NSDictionary class]] || sizes.count == 0) {
return nil;
}
NSMutableArray<id<TAPIImageEntityVariantModel>> *variants = [[NSMutableArray alloc] initWithCapacity:sizes.count];
for (NSString *variantName in sizes.allKeys) {
NSDictionary *info = sizes[variantName];
if (![info isKindOfClass:[NSDictionary class]] || ![info[@"resize"] isEqual:@"fit"]) {
continue;
}
const CGSize dims = CGSizeMake((CGFloat)[info[@"w"] integerValue], (CGFloat)[info[@"h"] integerValue]);
id<TAPIImageEntityVariantModel> variant = [[TAPIImageEntityVariantObject alloc] initWithName:variantName dimensions:dims];
if (variant) {
[variants addObject:variant];
}
}
if (!variants.count) {
return nil;
}
[variants sortUsingComparator:^NSComparisonResult(id<TAPIImageEntityVariantModel> obj1, id<TAPIImageEntityVariantModel> obj2) {
const NSUInteger pixels1 = (NSUInteger)obj1.dimensions.width * (NSUInteger)obj1.dimensions.width;
const NSUInteger pixels2 = (NSUInteger)obj2.dimensions.width * (NSUInteger)obj2.dimensions.width;
if (pixels1 == pixels2) {
return NSOrderedSame;
}
if (pixels1 < pixels2) {
return NSOrderedAscending;
}
return NSOrderedDescending;
}];
id<TAPIImageEntityVariantModel> lastVariant = variants.firstObject;
for (NSUInteger i = 1; i < variants.count; i++) {
id<TAPIImageEntityVariantModel> variant = variants[i];
if (CGSizeEqualToSize(variant.dimensions, lastVariant.dimensions)) {
// trim up the variants
while (i < variants.count) {
[variants removeLastObject];
}
break;
}
lastVariant = variant;
}
_variants = [variants copy];
return self;
}
@end
@implementation TAPIStatusObject
- (instancetype)initWithJSONObject:(id)object
{
if (![object isKindOfClass:[NSDictionary class]]) {
return nil;
}
_statusID = [object[@"id"] longLongValue];
_creationDate = TNLHTTPDateFromString(object[@"created_at"], NULL);
_text = [object[@"text"] copy];
_retweetCount = [object[@"retweet_count"] longLongValue];
_favoriteCount = [object[@"favorite_count"] longLongValue];
_possiblySensitive = [object[@"possibly_sensitive"] boolValue];
_user = [[TAPIUserObject alloc] initWithJSONObject:object[@"user"]];
NSDictionary *entities = object[@"entities"];
if ([entities isKindOfClass:[NSDictionary class]]) {
NSArray *media = entities[@"media"];
if ([media isKindOfClass:[NSArray class]]) {
NSMutableArray<id<TAPIImageEntityModel>> *images = [[NSMutableArray alloc] initWithCapacity:4];
for (id mediaValue in media) {
id<TAPIImageEntityModel> image = [[TAPIImageEntityObject alloc] initWithJSONObject:mediaValue];
if (image) {
[images addObject:image];
}
}
_images = (images.count > 0) ? [images copy] : nil;
}
}
if (!_statusID || !_creationDate || !_user) {
return nil;
}
return self;
}
@end
NSArray<id<TAPIStatusModel>> *TAPIStatusModelsFromJSONObjects(NSArray<id> *objects)
{
NSMutableArray *models = [[NSMutableArray alloc] initWithCapacity:objects.count];
for (id object in objects) {
@autoreleasepool {
id<TAPIStatusModel> status = [[TAPIStatusObject alloc] initWithJSONObject:object];
if (status) {
[models addObject:status];
}
}
}
return (models.count > 0) ? models : nil;
}