diff --git a/Examples/Cocoa/Sources/Objective_C/Everything.m b/Examples/Cocoa/Sources/Objective_C/Everything.m index bc82ff74..d1e2a631 100644 --- a/Examples/Cocoa/Sources/Objective_C/Everything.m +++ b/Examples/Cocoa/Sources/Objective_C/Everything.m @@ -617,6 +617,10 @@ - (void)encodeWithCoder:(NSCoder *)aCoder unsigned int EverythingDirtyPropertyUriProp:1; }; +struct EverythingBooleanProperties { + unsigned int EverythingBooleanBooleanProp:1; +}; + @interface Everything () { EverythingCharEnum _charEnum; @@ -630,6 +634,7 @@ @interface Everything () EverythingUnsignedShortEnum _unsignedShortEnum; } @property (nonatomic, assign, readwrite) struct EverythingDirtyProperties everythingDirtyProperties; +@property (nonatomic, assign, readwrite) struct EverythingBooleanProperties everythingBooleanProperties; @end @interface EverythingBuilder () @@ -704,7 +709,7 @@ - (instancetype)initWithModelDictionary:(NS_VALID_UNTIL_END_OF_SCOPE NSDictionar __unsafe_unretained id value = modelDictionary[@"boolean_prop"]; // Collection will retain. if (value != nil) { if (value != (id)kCFNull) { - self->_booleanProp = [value boolValue]; + self->_everythingBooleanProperties.EverythingBooleanBooleanProp = [value boolValue] & 0x1; } self->_everythingDirtyProperties.EverythingDirtyPropertyBooleanProp = 1; } @@ -1049,7 +1054,7 @@ - (instancetype)initWithModelDictionary:(NS_VALID_UNTIL_END_OF_SCOPE NSDictionar self->_polymorphicProp = [EverythingPolymorphicProp objectWithString:[value copy]]; } if ([value isKindOfClass:[NSNumber class]] && strcmp([value objCType], @encode(BOOL)) == 0) { - self->_polymorphicProp = [EverythingPolymorphicProp objectWithBoolean:[value boolValue]]; + self->_polymorphicProp = [EverythingPolymorphicProp objectWithBoolean:[value boolValue] & 0x1]; } if ([value isKindOfClass:[NSNumber class]] && (strcmp([value objCType], @encode(int)) == 0 || strcmp([value objCType], @encode(unsigned int)) == 0 || @@ -1223,7 +1228,6 @@ - (instancetype)initWithBuilder:(EverythingBuilder *)builder initType:(PlankMode return self; } _arrayProp = builder.arrayProp; - _booleanProp = builder.booleanProp; _charEnum = builder.charEnum; _dateProp = builder.dateProp; _intEnum = builder.intEnum; @@ -1258,6 +1262,7 @@ - (instancetype)initWithBuilder:(EverythingBuilder *)builder initType:(PlankMode _unsignedIntEnum = builder.unsignedIntEnum; _unsignedShortEnum = builder.unsignedShortEnum; _uriProp = builder.uriProp; + _everythingBooleanProperties.EverythingBooleanBooleanProp = builder.booleanProp == 1; _everythingDirtyProperties = builder.everythingDirtyProperties; if ([self class] == [Everything class]) { [[NSNotificationCenter defaultCenter] postNotificationName:kPlankDidInitializeNotification object:self userInfo:@{ kPlankInitTypeKey : @(initType) }]; @@ -1273,9 +1278,6 @@ - (NSString *)debugDescription if (props.EverythingDirtyPropertyArrayProp) { [descriptionFields addObject:[@"_arrayProp = " stringByAppendingFormat:@"%@", _arrayProp]]; } - if (props.EverythingDirtyPropertyBooleanProp) { - [descriptionFields addObject:[@"_booleanProp = " stringByAppendingFormat:@"%@", @(_booleanProp)]]; - } if (props.EverythingDirtyPropertyCharEnum) { [descriptionFields addObject:[@"_charEnum = " stringByAppendingFormat:@"%@", @(_charEnum)]]; } @@ -1378,6 +1380,9 @@ - (NSString *)debugDescription if (props.EverythingDirtyPropertyUriProp) { [descriptionFields addObject:[@"_uriProp = " stringByAppendingFormat:@"%@", _uriProp]]; } + if (props.EverythingDirtyPropertyBooleanProp) { + [descriptionFields addObject:[@"_everythingBooleanProperties.EverythingBooleanBooleanProp = " stringByAppendingFormat:@"%@", @(_everythingBooleanProperties.EverythingBooleanBooleanProp)]]; + } return [NSString stringWithFormat:@"Everything = {\n%@\n}", debugDescriptionForFields(descriptionFields)]; } - (instancetype)copyWithBlock:(PLANK_NOESCAPE void (^)(EverythingBuilder *builder))block @@ -1412,7 +1417,7 @@ - (BOOL)isEqualToEverything:(Everything *)anObject (_intProp == anObject.intProp) && (_intEnum == anObject.intEnum) && (_charEnum == anObject.charEnum) && - (_booleanProp == anObject.booleanProp) && + (_everythingBooleanProperties.EverythingBooleanBooleanProp == anObject.booleanProp) && (_arrayProp == anObject.arrayProp || [_arrayProp isEqualToArray:anObject.arrayProp]) && (_dateProp == anObject.dateProp || [_dateProp isEqualToDate:anObject.dateProp]) && (_listPolymorphicValues == anObject.listPolymorphicValues || [_listPolymorphicValues isEqualToArray:anObject.listPolymorphicValues]) && @@ -1444,7 +1449,7 @@ - (NSUInteger)hash NSUInteger subhashes[] = { 17, [_arrayProp hash], - (_booleanProp ? 1231 : 1237), + (_everythingBooleanProperties.EverythingBooleanBooleanProp ? 1231 : 1237), (NSUInteger)_charEnum, [_dateProp hash], (NSUInteger)_intEnum, @@ -1503,9 +1508,6 @@ - (NSDictionary *)dictionaryObjectRepresentation [dict setObject:[NSNull null] forKey:@"array_prop"]; } } - if (_everythingDirtyProperties.EverythingDirtyPropertyBooleanProp) { - [dict setObject:@(_booleanProp) forKey: @"boolean_prop"]; - } if (_everythingDirtyProperties.EverythingDirtyPropertyCharEnum) { [dict setObject:@(_charEnum) forKey:@"char_enum"]; } @@ -1791,6 +1793,9 @@ - (NSDictionary *)dictionaryObjectRepresentation [dict setObject:[NSNull null] forKey:@"uri_prop"]; } } + if (_everythingDirtyProperties.EverythingDirtyPropertyBooleanProp) { + [dict setObject:@(_everythingBooleanProperties.EverythingBooleanBooleanProp) forKey: @"boolean_prop"]; + } return dict; } - (BOOL)isArrayPropSet @@ -1937,6 +1942,10 @@ - (BOOL)isUriPropSet { return _everythingDirtyProperties.EverythingDirtyPropertyUriProp == 1; } +- (BOOL)booleanProp +{ + return _everythingBooleanProperties.EverythingBooleanBooleanProp; +} #pragma mark - NSCopying - (id)copyWithZone:(NSZone *)zone { @@ -1953,7 +1962,6 @@ - (instancetype)initWithCoder:(NSCoder *)aDecoder return self; } _arrayProp = [aDecoder decodeObjectOfClass:[NSArray class] forKey:@"array_prop"]; - _booleanProp = [aDecoder decodeBoolForKey:@"boolean_prop"]; _charEnum = (EverythingCharEnum)[aDecoder decodeIntegerForKey:@"char_enum"]; _dateProp = [aDecoder decodeObjectOfClass:[NSDate class] forKey:@"date_prop"]; _intEnum = (EverythingIntEnum)[aDecoder decodeIntegerForKey:@"int_enum"]; @@ -1988,6 +1996,7 @@ - (instancetype)initWithCoder:(NSCoder *)aDecoder _unsignedIntEnum = (EverythingUnsignedIntEnum)[aDecoder decodeIntegerForKey:@"unsigned_int_enum"]; _unsignedShortEnum = (EverythingUnsignedShortEnum)[aDecoder decodeIntegerForKey:@"unsigned_short_enum"]; _uriProp = [aDecoder decodeObjectOfClass:[NSURL class] forKey:@"uri_prop"]; + _everythingBooleanProperties.EverythingBooleanBooleanProp = [aDecoder decodeBoolForKey:@"boolean_prop"] & 0x1; _everythingDirtyProperties.EverythingDirtyPropertyArrayProp = [aDecoder decodeIntForKey:@"array_prop_dirty_property"] & 0x1; _everythingDirtyProperties.EverythingDirtyPropertyBooleanProp = [aDecoder decodeIntForKey:@"boolean_prop_dirty_property"] & 0x1; _everythingDirtyProperties.EverythingDirtyPropertyCharEnum = [aDecoder decodeIntForKey:@"char_enum_dirty_property"] & 0x1; diff --git a/Examples/Cocoa/Sources/Objective_C/include/Everything.h b/Examples/Cocoa/Sources/Objective_C/include/Everything.h index 7042c165..fc81f55b 100644 --- a/Examples/Cocoa/Sources/Objective_C/include/Everything.h +++ b/Examples/Cocoa/Sources/Objective_C/include/Everything.h @@ -213,6 +213,7 @@ NS_ASSUME_NONNULL_BEGIN - (BOOL)isUnsignedIntEnumSet; - (BOOL)isUnsignedShortEnumSet; - (BOOL)isUriPropSet; +- (BOOL)booleanProp; @end @interface EverythingBuilder : NSObject diff --git a/Sources/Core/ObjCModelRenderer.swift b/Sources/Core/ObjCModelRenderer.swift index eb0a94cc..687a3f4c 100644 --- a/Sources/Core/ObjCModelRenderer.swift +++ b/Sources/Core/ObjCModelRenderer.swift @@ -23,10 +23,18 @@ public struct ObjCModelRenderer: ObjCFileRenderer { return "\(className)DirtyProperties" } + var booleanPropertiesStructName: String { + return "\(className)BooleanProperties" + } + var dirtyPropertiesIVarName: String { return "\(Languages.objectiveC.snakeCaseToPropertyName(rootSchema.name))DirtyProperties" } + var booleanPropertiesIVarName: String { + return "\(Languages.objectiveC.snakeCaseToPropertyName(rootSchema.name))BooleanProperties" + } + // MARK: Model methods func renderClassName() -> ObjCIR.Method { @@ -119,7 +127,30 @@ public struct ObjCModelRenderer: ObjCFileRenderer { } } + func renderBooleanPropertyAccessorMethods() -> [ObjCIR.Method] { + return properties.flatMap { (param, prop) -> [ObjCIR.Method] in + switch prop.schema { + case .boolean: + return [ObjCIR.method("- (BOOL)\(Languages.objectiveC.snakeCaseToPropertyName(param))") { ["return _\(self.booleanPropertiesIVarName).\(booleanPropertyOption(propertyName: param, className: self.className));"] }] + default: + return [] + } + } + } + func renderRoots() -> [ObjCIR.Root] { + let booleanProperties = rootSchema.properties.filter { (arg) -> Bool in + let (_, schema) = arg + return schema.schema.isBoolean() + } + + let booleanStructDeclaration = !booleanProperties.isEmpty ? [ObjCIR.Root.structDecl(name: self.booleanPropertiesStructName, + fields: booleanProperties.keys.map { "unsigned int \(booleanPropertyOption(propertyName: $0, className: self.className)):1;" })] : [] + + let booleanIvarDeclaration: [SimpleProperty] = !booleanProperties.isEmpty ? [(self.booleanPropertiesIVarName, "struct \(self.booleanPropertiesStructName)", + SchemaObjectProperty(schema: .integer, nullability: nil), + .readwrite)] : [] + let enumProperties = properties.filter { (arg) -> Bool in let (_, schema) = arg switch schema.schema { @@ -129,6 +160,7 @@ public struct ObjCModelRenderer: ObjCFileRenderer { return false } } + let enumIVarDeclarations: [(Parameter, TypeName)] = enumProperties.compactMap { (arg) -> (Parameter, TypeName) in let (param, prop) = arg return (param, enumTypeName(propertyName: param, className: self.className)) @@ -199,59 +231,60 @@ public struct ObjCModelRenderer: ObjCFileRenderer { ObjCIR.Root.structDecl(name: self.dirtyPropertyOptionName, fields: rootSchema.properties.keys .map { "unsigned int \(dirtyPropertyOption(propertyName: $0, className: self.className)):1;" }), - ObjCIR.Root.category(className: self.className, - categoryName: nil, - methods: [], - properties: [(self.dirtyPropertiesIVarName, "struct \(self.dirtyPropertyOptionName)", - SchemaObjectProperty(schema: .integer, nullability: nil), - .readwrite)], - variables: enumIVarDeclarations), - ObjCIR.Root.category(className: self.builderClassName, - categoryName: nil, - methods: [], - properties: [(self.dirtyPropertiesIVarName, "struct \(self.dirtyPropertyOptionName)", - SchemaObjectProperty(schema: .integer, nullability: nil), - .readwrite)], - variables: []), - ] + renderStringEnumerationMethods().map { ObjCIR.Root.function($0) } + [ - ObjCIR.Root.macro("NS_ASSUME_NONNULL_BEGIN"), - ObjCIR.Root.classDecl( - name: self.className, - extends: parentName, - methods: [ - (self.isBaseClass ? .publicM : .privateM, self.renderClassName()), - (self.isBaseClass ? .publicM : .privateM, self.renderPolymorphicTypeIdentifier()), - (self.isBaseClass ? .publicM : .privateM, self.renderModelObjectWithDictionary()), - (.privateM, self.renderDesignatedInit()), - (self.isBaseClass ? .publicM : .privateM, self.renderInitWithModelDictionary()), - (.publicM, self.renderInitWithBuilder()), - (self.isBaseClass ? .publicM : .privateM, self.renderInitWithBuilderWithInitType()), - (.privateM, self.renderDebugDescription()), - (.publicM, self.renderCopyWithBlock()), - (.privateM, self.renderIsEqual()), - (.publicM, self.renderIsEqualToClass()), - (.privateM, self.renderHash()), - (.publicM, self.renderMergeWithModel()), - (.publicM, self.renderMergeWithModelWithInitType()), - (self.isBaseClass ? .publicM : .privateM, self.renderGenerateDictionary()), - ] + self.renderIsSetMethods().map { (.publicM, $0) }, - properties: properties.map { param, prop in (param, typeFromSchema(param, prop), prop, .readonly) }.sorted { $0.0 < $1.0 }, - protocols: protocols - ), - ObjCIR.Root.classDecl( - name: self.builderClassName, - extends: resolveClassName(self.parentDescriptor).map { "\($0)Builder" }, - methods: [ - (.publicM, self.renderBuilderInitWithModel()), - (.publicM, ObjCIR.method("- (\(self.className) *)build") { - ["return [[\(self.className) alloc] initWithBuilder:self];"] - }), - (.publicM, self.renderBuilderMergeWithModel()), - ] + self.renderBuilderPropertySetters().map { (.privateM, $0) }, - properties: properties.map { param, prop in (param, typeFromSchema(param, prop), prop, .readwrite) }, - protocols: [:] - ), - ObjCIR.Root.macro("NS_ASSUME_NONNULL_END"), ] + + booleanStructDeclaration + + [ObjCIR.Root.category(className: self.className, + categoryName: nil, + methods: [], + properties: [(self.dirtyPropertiesIVarName, "struct \(self.dirtyPropertyOptionName)", + SchemaObjectProperty(schema: .integer, nullability: nil), + .readwrite)] + booleanIvarDeclaration, + variables: enumIVarDeclarations), + ObjCIR.Root.category(className: self.builderClassName, + categoryName: nil, + methods: [], + properties: [(self.dirtyPropertiesIVarName, "struct \(self.dirtyPropertyOptionName)", + SchemaObjectProperty(schema: .integer, nullability: nil), + .readwrite)], + variables: [])] + renderStringEnumerationMethods().map { ObjCIR.Root.function($0) } + [ + ObjCIR.Root.macro("NS_ASSUME_NONNULL_BEGIN"), + ObjCIR.Root.classDecl( + name: self.className, + extends: parentName, + methods: [ + (self.isBaseClass ? .publicM : .privateM, self.renderClassName()), + (self.isBaseClass ? .publicM : .privateM, self.renderPolymorphicTypeIdentifier()), + (self.isBaseClass ? .publicM : .privateM, self.renderModelObjectWithDictionary()), + (.privateM, self.renderDesignatedInit()), + (self.isBaseClass ? .publicM : .privateM, self.renderInitWithModelDictionary()), + (.publicM, self.renderInitWithBuilder()), + (self.isBaseClass ? .publicM : .privateM, self.renderInitWithBuilderWithInitType()), + (.privateM, self.renderDebugDescription()), + (.publicM, self.renderCopyWithBlock()), + (.privateM, self.renderIsEqual()), + (.publicM, self.renderIsEqualToClass(self.booleanPropertiesIVarName)), + (.privateM, self.renderHash(self.booleanPropertiesIVarName)), + (.publicM, self.renderMergeWithModel()), + (.publicM, self.renderMergeWithModelWithInitType()), + (self.isBaseClass ? .publicM : .privateM, self.renderGenerateDictionary()), + ] + self.renderIsSetMethods().map { (.publicM, $0) } + self.renderBooleanPropertyAccessorMethods().map { (.publicM, $0) }, + properties: properties.map { param, prop in (param, typeFromSchema(param, prop), prop, .readonly) }.sorted { $0.0 < $1.0 }, + protocols: protocols + ), + ObjCIR.Root.classDecl( + name: self.builderClassName, + extends: resolveClassName(self.parentDescriptor).map { "\($0)Builder" }, + methods: [ + (.publicM, self.renderBuilderInitWithModel()), + (.publicM, ObjCIR.method("- (\(self.className) *)build") { + ["return [[\(self.className) alloc] initWithBuilder:self];"] + }), + (.publicM, self.renderBuilderMergeWithModel()), + ] + self.renderBuilderPropertySetters().map { (.privateM, $0) }, + properties: properties.map { param, prop in (param, typeFromSchema(param, prop), prop, .readwrite) }, + protocols: [:] + ), + ObjCIR.Root.macro("NS_ASSUME_NONNULL_END"), + ] } } diff --git a/Sources/Core/ObjectiveCDebugExtension.swift b/Sources/Core/ObjectiveCDebugExtension.swift index 0f1a9ee8..e2f622ae 100644 --- a/Sources/Core/ObjectiveCDebugExtension.swift +++ b/Sources/Core/ObjectiveCDebugExtension.swift @@ -10,13 +10,24 @@ import Foundation extension ObjCModelRenderer { func renderDebugDescription() -> ObjCIR.Method { - let props = properties.map { (param, prop) -> String in + let props = properties.filter { (_, schema) -> Bool in + !schema.schema.isBoolean() + }.map { (param, prop) -> String in ObjCIR.ifStmt("props.\(dirtyPropertyOption(propertyName: param, className: self.className))") { let ivarName = "_\(Languages.objectiveC.snakeCaseToPropertyName(param))" return ["[descriptionFields addObject:[\((ivarName + " = ").objcLiteral()) stringByAppendingFormat:\("%@".objcLiteral()), \(renderDebugStatement(param, prop.schema))]];"] } }.joined(separator: "\n") + let boolProps = properties.filter { (_, schema) -> Bool in + schema.schema.isBoolean() + }.map { (param, _) -> String in + ObjCIR.ifStmt("props.\(dirtyPropertyOption(propertyName: param, className: self.className))") { + let ivarName = "_\(booleanPropertiesIVarName).\(booleanPropertyOption(propertyName: param, className: self.className))" + return ["[descriptionFields addObject:[@\"\(ivarName) = \" stringByAppendingFormat:\("%@".objcLiteral()), @(\(ivarName))]];"] + } + } + let printFormat = "\(className) = {\\n%@\\n}".objcLiteral() return ObjCIR.method("- (NSString *)debugDescription") { [ "NSArray *parentDebugDescription = [[super debugDescription] componentsSeparatedByString:\("\\n".objcLiteral())];", @@ -24,8 +35,7 @@ extension ObjCModelRenderer { "[descriptionFields addObject:parentDebugDescription];", !self.properties.isEmpty ? "struct \(self.dirtyPropertyOptionName) props = _\(self.dirtyPropertiesIVarName);" : "", props, - "return [NSString stringWithFormat:\(printFormat), debugDescriptionForFields(descriptionFields)];", - ] } + ] + boolProps + ["return [NSString stringWithFormat:\(printFormat), debugDescriptionForFields(descriptionFields)];"] } } } @@ -55,7 +65,9 @@ extension ObjCFileRenderer { switch schema { case .enumT(.string): return enumToStringMethodName(propertyName: param, className: className) + "(\(propIVarName))" - case .boolean, .float, .integer: + case .boolean: + return "@(_.\(booleanPropertyOption(propertyName: param, className: className)))" + case .float, .integer: return "@(\(propIVarName))" case .enumT(.integer): return "@(\(propIVarName))" diff --git a/Sources/Core/ObjectiveCDictionaryExtension.swift b/Sources/Core/ObjectiveCDictionaryExtension.swift index eedee0a2..392f3b3e 100644 --- a/Sources/Core/ObjectiveCDictionaryExtension.swift +++ b/Sources/Core/ObjectiveCDictionaryExtension.swift @@ -12,8 +12,9 @@ extension ObjCModelRenderer { func renderGenerateDictionary() -> ObjCIR.Method { let dictionary = "dict" - let props = properties.map { (param, schemaObj) -> String in - + let props = properties.filter { (_, schema) -> Bool in + !schema.schema.isBoolean() + }.map { (param, schemaObj) -> String in ObjCIR.ifStmt("_" + "\(self.dirtyPropertiesIVarName).\(dirtyPropertyOption(propertyName: param, className: self.className))") { [ schemaObj.schema.isObjCPrimitiveType ? self.renderAddToDictionaryStatement(.ivar(param), schemaObj.schema, dictionary) : @@ -24,13 +25,22 @@ extension ObjCModelRenderer { ] }, ] } }.joined(separator: "\n") + + let boolProps = properties.filter { (_, schema) -> Bool in + schema.schema.isBoolean() + }.map { (param, _) -> String in + let ivarName = "_\(booleanPropertiesIVarName).\(booleanPropertyOption(propertyName: param, className: self.className))" + return ObjCIR.ifStmt("_" + "\(self.dirtyPropertiesIVarName).\(dirtyPropertyOption(propertyName: param, className: self.className))") { [ + "[\(dictionary) setObject:@(\(ivarName)) forKey: @\"\(param)\"];", + ] } + } + return ObjCIR.method("- (NSDictionary *)dictionaryObjectRepresentation") { [ "NSMutableDictionary *\(dictionary) = " + (self.isBaseClass ? "[[NSMutableDictionary alloc] initWithCapacity:\(self.properties.count)];" : "[[super dictionaryObjectRepresentation] mutableCopy];"), props, - "return \(dictionary);", - ] } + ] + boolProps + ["return \(dictionary);"] } } } diff --git a/Sources/Core/ObjectiveCEqualityExtension.swift b/Sources/Core/ObjectiveCEqualityExtension.swift index e3d5d5f3..9ecf2562 100644 --- a/Sources/Core/ObjectiveCEqualityExtension.swift +++ b/Sources/Core/ObjectiveCEqualityExtension.swift @@ -12,7 +12,7 @@ extension ObjCFileRenderer { // MARK: Hash Method - Inspired from Mike Ash article on Equality / Hashing // https://www.mikeash.com/pyblog/friday-qa-2010-06-18-implementing-equality-and-hashing.html - func renderHash() -> ObjCIR.Method { + func renderHash(_ booleanIVarName: String = "") -> ObjCIR.Method { func schemaHashStatement(with param: Parameter, for schema: Schema) -> String { switch schema { case .enumT, .integer: @@ -24,7 +24,9 @@ extension ObjCFileRenderer { case .boolean: // Values 1231 for true and 1237 for false are adopted from the Java hashCode specification // http://docs.oracle.com/javase/7/docs/api/java/lang/Boolean.html#hashCode - return "(_\(param) ? 1231 : 1237)" + return !booleanIVarName.isEmpty ? + "(_\(booleanIVarName).\(booleanPropertyOption(propertyName: param, className: className)) ? 1231 : 1237)" : + "(_\(param) ? 1231 : 1237)" case let .reference(with: ref): switch ref.force() { case let .some(.object(schemaRoot)): @@ -53,7 +55,7 @@ extension ObjCFileRenderer { // MARK: Equality Methods inspired from NSHipster article on Equality: http://nshipster.com/equality/ - func renderIsEqualToClass() -> ObjCIR.Method { + func renderIsEqualToClass(_ booleanIVarName: String = "") -> ObjCIR.Method { func schemaIsEqualStatement(with param: Parameter, for schema: Schema) -> String { switch schema { case .integer, .float, .enumT, .boolean: @@ -93,9 +95,18 @@ extension ObjCFileRenderer { let propReturnStmts = sortedProps.map { param, prop -> String in let formattedParam = Languages.objectiveC.snakeCaseToPropertyName(param) - let pointerEqStmt = "_\(formattedParam) == anObject.\(formattedParam)" - let deepEqStmt = schemaIsEqualStatement(with: formattedParam, for: prop.schema) - return [pointerEqStmt, deepEqStmt].filter { $0 != "" }.joined(separator: " || ") + switch prop.schema { + case .boolean: + if booleanIVarName.isEmpty { + fallthrough + } else { + return "_\(booleanIVarName).\(booleanPropertyOption(propertyName: param, className: self.className)) == anObject.\(formattedParam)" + } + default: + let pointerEqStmt = "_\(formattedParam) == anObject.\(formattedParam)" + let deepEqStmt = schemaIsEqualStatement(with: formattedParam, for: prop.schema) + return [pointerEqStmt, deepEqStmt].filter { $0 != "" }.joined(separator: " || ") + } } func parentName(_ schema: Schema?) -> String? { diff --git a/Sources/Core/ObjectiveCIR.swift b/Sources/Core/ObjectiveCIR.swift index b0ef33f6..504c5e7d 100644 --- a/Sources/Core/ObjectiveCIR.swift +++ b/Sources/Core/ObjectiveCIR.swift @@ -52,14 +52,22 @@ typealias Parameter = String typealias TypeName = String typealias SimpleProperty = (Parameter, TypeName, SchemaObjectProperty, ObjCMutabilityType) -func dirtyPropertyOption(propertyName aPropertyName: String, className: String) -> String { +func propertyOption(propertyName aPropertyName: String, className: String, variant: String) -> String { guard !className.isEmpty, !aPropertyName.isEmpty else { - fatalError("Invalid class name or property name passed to dirtyPropertyOption(propertyName:className)") + fatalError("Invalid class name or property name passed propertyOption(propertyName:className:variant)") } let propertyName = Languages.objectiveC.snakeCaseToPropertyName(aPropertyName) let capitalizedFirstLetter = String(propertyName[propertyName.startIndex]).uppercased() let capitalizedPropertyName = capitalizedFirstLetter + String(propertyName.dropFirst()) - return className + "DirtyProperty" + capitalizedPropertyName + return className + variant + capitalizedPropertyName +} + +func dirtyPropertyOption(propertyName aPropertyName: String, className: String) -> String { + return propertyOption(propertyName: aPropertyName, className: className, variant: "DirtyProperty") +} + +func booleanPropertyOption(propertyName aPropertyName: String, className: String) -> String { + return propertyOption(propertyName: aPropertyName, className: className, variant: "Boolean") } func enumFromStringMethodName(propertyName: String, className: String) -> String { diff --git a/Sources/Core/ObjectiveCInitExtension.swift b/Sources/Core/ObjectiveCInitExtension.swift index f0aec954..fead49db 100644 --- a/Sources/Core/ObjectiveCInitExtension.swift +++ b/Sources/Core/ObjectiveCInitExtension.swift @@ -46,15 +46,23 @@ extension ObjCModelRenderer { "NSParameterAssert(builder);", self.isBaseClass ? ObjCIR.ifStmt("!(self = [super init])") { ["return self;"] } : ObjCIR.ifStmt("!(self = [super initWithBuilder:builder initType:initType])") { ["return self;"] }, - self.properties.map { name, _ in + self.properties.filter { (_, schema) -> Bool in + !schema.schema.isBoolean() + }.map { name, _ in "_\(Languages.objectiveC.snakeCaseToPropertyName(name)) = builder.\(Languages.objectiveC.snakeCaseToPropertyName(name));" }.joined(separator: "\n"), - "_\(self.dirtyPropertiesIVarName) = builder.\(self.dirtyPropertiesIVarName);", - ObjCIR.ifStmt("[self class] == [\(self.className) class]") { - [renderPostInitNotification(type: "initType")] - }, - "return self;", - ] + ] + + self.properties.filter { (_, schema) -> Bool in + schema.schema.isBoolean() + }.map { name, _ in + "_\(self.booleanPropertiesIVarName).\(booleanPropertyOption(propertyName: name, className: self.className)) = builder.\(Languages.objectiveC.snakeCaseToPropertyName(name)) == 1;" + } + [ + "_\(self.dirtyPropertiesIVarName) = builder.\(self.dirtyPropertiesIVarName);", + ObjCIR.ifStmt("[self class] == [\(self.className) class]") { + [renderPostInitNotification(type: "initType")] + }, + "return self;", + ] } } @@ -144,7 +152,7 @@ extension ObjCModelRenderer { case .integer: return ["\(propertyToAssign) = [\(rawObjectName) integerValue];"] case .boolean: - return ["\(propertyToAssign) = [\(rawObjectName) boolValue];"] + return ["\(propertyToAssign) = [\(rawObjectName) boolValue] & 0x1;"] case .string(format: .some(.uri)): return ["\(propertyToAssign) = [NSURL URLWithString:\(rawObjectName)];"] case .string(format: .some(.dateTime)): @@ -269,7 +277,12 @@ extension ObjCModelRenderer { "__unsafe_unretained id value = modelDictionary[\(name.objcLiteral())]; // Collection will retain.", ObjCIR.ifStmt("value != nil") { [ ObjCIR.ifStmt("value != (id)kCFNull") { - renderPropertyInit("self->_\(Languages.objectiveC.snakeCaseToPropertyName(name))", "value", schema: prop.schema, firstName: name) + switch prop.schema { + case .boolean: + return renderPropertyInit("self->_\(booleanPropertiesIVarName).\(booleanPropertyOption(propertyName: name, className: className))", "value", schema: prop.schema, firstName: name) + default: + return renderPropertyInit("self->_\(Languages.objectiveC.snakeCaseToPropertyName(name))", "value", schema: prop.schema, firstName: name) + } }, "self->_\(dirtyPropertiesIVarName).\(dirtyPropertyOption(propertyName: name, className: className)) = 1;", ] }, diff --git a/Sources/Core/ObjectiveCNSCodingExtension.swift b/Sources/Core/ObjectiveCNSCodingExtension.swift index 9a26bce9..60fed70c 100644 --- a/Sources/Core/ObjectiveCNSCodingExtension.swift +++ b/Sources/Core/ObjectiveCNSCodingExtension.swift @@ -14,18 +14,27 @@ extension ObjCModelRenderer { [ self.isBaseClass ? ObjCIR.ifStmt("!(self = [super init])") { ["return self;"] } : "if (!(self = [super initWithCoder:aDecoder])) { return self; }", - self.properties.map { ($0.0, $0.1.schema) } + self.properties.filter { (_, schema) -> Bool in + !schema.schema.isBoolean() + }.map { ($0.0, $0.1.schema) } .map(decodeStatement) .joined(separator: "\n"), - self.properties.map { (param, _) -> String in - "_\(dirtyPropertiesIVarName).\(dirtyPropertyOption(propertyName: param, className: self.className)) = [aDecoder decodeIntForKey:\((param + "_dirty_property").objcLiteral())] & 0x1;" - }.joined(separator: "\n"), + ] + + self.properties.filter { (_, schema) -> Bool in + schema.schema.isBoolean() + }.map { (arg: (Parameter, SchemaObjectProperty)) -> String in + let (param, _) = arg + return "_\(booleanPropertiesIVarName).\(booleanPropertyOption(propertyName: param, className: self.className)) = [aDecoder decodeBoolForKey:\(param.objcLiteral())] & 0x1;" + } + [ + self.properties.map { (param, _) -> String in + "_\(dirtyPropertiesIVarName).\(dirtyPropertyOption(propertyName: param, className: self.className)) = [aDecoder decodeIntForKey:\((param + "_dirty_property").objcLiteral())] & 0x1;" + }.joined(separator: "\n"), - ObjCIR.ifStmt("[self class] == [\(self.className) class]") { - [renderPostInitNotification(type: "PlankModelInitTypeDefault")] - }, - "return self;", - ] + ObjCIR.ifStmt("[self class] == [\(self.className) class]") { + [renderPostInitNotification(type: "PlankModelInitTypeDefault")] + }, + "return self;", + ] } } diff --git a/Sources/Core/Schema.swift b/Sources/Core/Schema.swift index f61f43b6..be19d269 100644 --- a/Sources/Core/Schema.swift +++ b/Sources/Core/Schema.swift @@ -343,3 +343,14 @@ extension Schema { return propertyForType } } + +extension Schema { + func isBoolean() -> Bool { + switch self { + case .boolean: + return true + default: + return false + } + } +}