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; }