From d35e6c4d9133ec810f8e86de768564e90c552b36 Mon Sep 17 00:00:00 2001 From: iska Date: Sun, 9 Apr 2017 20:47:55 +0200 Subject: [PATCH 1/9] Fix memory leaks in CSS Input Stream - Release allocated instances when returning nil - Pass autoreleased instances on valid return value --- Sources/CSSInputStream.m | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/Sources/CSSInputStream.m b/Sources/CSSInputStream.m index 4b3cb81..f1fc03b 100644 --- a/Sources/CSSInputStream.m +++ b/Sources/CSSInputStream.m @@ -24,14 +24,14 @@ - (void)consumeWhitespace - (NSString *)consumeIdentifier { - CFMutableStringRef value = CFStringCreateMutable(kCFAllocatorDefault, 0); - if (!isValidIdentifierStart([self inputCharacterPointAtOffset:0], [self inputCharacterPointAtOffset:1], [self inputCharacterPointAtOffset:2])) { return nil; } + CFMutableStringRef value = CFStringCreateMutable(kCFAllocatorDefault, 0); + while (YES) { UTF32Char codePoint = [self consumeNextInputCharacter]; if (codePoint == EOF) { @@ -47,7 +47,12 @@ - (NSString *)consumeIdentifier } } - return (__bridge NSString *)(CFStringGetLength(value) > 0 ? value : nil); + if (CFStringGetLength(value) > 0) { + return (__bridge_transfer NSString *)value; + } + + CFRelease(value); + return nil; } - (NSString *)consumeStringWithEndingCodePoint:(UTF32Char)endingCodePoint @@ -85,7 +90,12 @@ - (NSString *)consumeStringWithEndingCodePoint:(UTF32Char)endingCodePoint } } - return (__bridge NSString *)(CFStringGetLength(value) > 0 ? value : nil); + if (CFStringGetLength(value) > 0) { + return (__bridge_transfer NSString *)value; + } + + CFRelease(value); + return nil; } - (UTF32Char)consumeEscapedCodePoint @@ -105,7 +115,7 @@ - (UTF32Char)consumeEscapedCodePoint [self consumeNextInputCharacter]; } - NSScanner *scanner = [NSScanner scannerWithString:(__bridge NSString *)(hexString)]; + NSScanner *scanner = [NSScanner scannerWithString:(__bridge_transfer NSString *)(hexString)]; unsigned int number; [scanner scanHexInt:&number]; From b693a60358dc352ad23ca9d1999b093c2c215ad4 Mon Sep 17 00:00:00 2001 From: iska Date: Sun, 9 Apr 2017 20:52:06 +0200 Subject: [PATCH 2/9] Use lazy allocation for underlying collections in HTML Nodes Do not allocate empty collections for child nodes or attributes when initializing new HTML Nodes or Elements. These are initialized the first time they are accessed. Analogously, the mutable data string of CharacterData is also allocated with the empty string on first access. --- Sources/HTMLCharacterData.m | 19 +++++++++++++++---- Sources/HTMLElement.m | 30 ++++++++++++++++++++---------- Sources/HTMLNode.m | 11 ++++++++++- Sources/include/HTMLElement.h | 4 ++-- 4 files changed, 47 insertions(+), 17 deletions(-) diff --git a/Sources/HTMLCharacterData.m b/Sources/HTMLCharacterData.m index 68a7e19..46615ca 100644 --- a/Sources/HTMLCharacterData.m +++ b/Sources/HTMLCharacterData.m @@ -24,14 +24,25 @@ - (instancetype)initWithName:(NSString *)name type:(HTMLNodeType)type data:(NSSt { self = [super initWithName:name type:type]; if (self) { - _data = [[NSMutableString alloc] initWithString:data ?: @""]; + if (data) { + _data = [[NSMutableString alloc] initWithString:data]; + } } return self; } +- (NSString *)data +{ + if (_data == nil) { + _data = [[NSMutableString alloc] initWithString:@""]; + } + + return _data; +} + - (NSString *)textContent { - return [_data copy]; + return [self.data copy]; } - (void)setTextContent:(NSString *)textContent @@ -41,7 +52,7 @@ - (void)setTextContent:(NSString *)textContent - (NSUInteger)length { - return _data.length; + return self.data.length; } #pragma mark - Data @@ -81,7 +92,7 @@ - (void)replaceDataInRange:(NSRange)range withData:(NSString *)data range.length = MIN(range.length, self.length - range.location); - [_data replaceCharactersInRange:range withString:data]; + [(NSMutableString *)self.data replaceCharactersInRange:range withString:data]; [self.ownerDocument didRemoveCharacterDataInNode:self atOffset:range.location withLength:range.length]; [self.ownerDocument didAddCharacterDataToNode:self atOffset:range.location withLength:data.length]; } diff --git a/Sources/HTMLElement.m b/Sources/HTMLElement.m index ec3ef89..f36bc02 100644 --- a/Sources/HTMLElement.m +++ b/Sources/HTMLElement.m @@ -32,7 +32,7 @@ - (instancetype)init - (instancetype)initWithTagName:(NSString *)tagName { - return [self initWithTagName:tagName attributes:@{}]; + return [self initWithTagName:tagName attributes:nil]; } - (instancetype)initWithTagName:(NSString *)tagName attributes:(NSDictionary *)attributes @@ -45,8 +45,9 @@ - (instancetype)initWithTagName:(NSString *)tagName namespace:(HTMLNamespace)htm self = [super initWithName:tagName type:HTMLNodeElement]; if (self) { _tagName = [tagName copy]; - _attributes = [HTMLOrderedDictionary new]; + _attributes = nil; if (attributes != nil) { + _attributes = [HTMLOrderedDictionary new]; [_attributes addEntriesFromDictionary:attributes]; } _htmlNamespace = htmlNamespace; @@ -56,24 +57,33 @@ - (instancetype)initWithTagName:(NSString *)tagName namespace:(HTMLNamespace)htm #pragma mark - Special Attributes +- (NSMutableDictionary *)attributes +{ + if (_attributes == nil) { + _attributes = [HTMLOrderedDictionary new]; + } + + return _attributes; +} + - (NSString *)elementId { - return _attributes[@"id"] ?: @""; + return self.attributes[@"id"] ?: @""; } - (void)setElementId:(NSString *)elementId { - _attributes[@"id"] = elementId; + self.attributes[@"id"] = elementId; } - (NSString *)className { - return _attributes[@"class"] ?: @""; + return self.attributes[@"class"] ?: @""; } - (void)setClassName:(NSString *)className { - _attributes[@"class"] = className; + self.attributes[@"class"] = className; } - (HTMLDOMTokenList *)classList @@ -85,22 +95,22 @@ - (HTMLDOMTokenList *)classList - (BOOL)hasAttribute:(NSString *)name { - return _attributes[name] != nil; + return self.attributes[name] != nil; } - (NSString *)objectForKeyedSubscript:(NSString *)name; { - return _attributes[name]; + return self.attributes[name]; } - (void)setObject:(NSString *)value forKeyedSubscript:(NSString *)attribute { - _attributes[attribute] = value; + self.attributes[attribute] = value; } - (void)removeAttribute:(NSString *)name { - [_attributes removeObjectForKey:name]; + [self.attributes removeObjectForKey:name]; } - (NSString *)textContent diff --git a/Sources/HTMLNode.m b/Sources/HTMLNode.m index e1c74c3..f0f95d5 100644 --- a/Sources/HTMLNode.m +++ b/Sources/HTMLNode.m @@ -36,13 +36,22 @@ - (instancetype)initWithName:(NSString *)name type:(HTMLNodeType)type if (self) { _name = name; _nodeType = type; - _childNodes = [NSMutableOrderedSet new]; + _childNodes = nil; } return self; } #pragma mark - Properties +- (NSOrderedSet *)childNodes +{ + if (_childNodes == nil) { + _childNodes = [NSMutableOrderedSet new]; + } + + return _childNodes; +} + - (HTMLDocument *)ownerDocument { if (_nodeType == HTMLNodeDocument) { diff --git a/Sources/include/HTMLElement.h b/Sources/include/HTMLElement.h index dcf5372..524921a 100644 --- a/Sources/include/HTMLElement.h +++ b/Sources/include/HTMLElement.h @@ -75,7 +75,7 @@ NS_ASSUME_NONNULL_BEGIN @param attributes The attributes. @return A new HTML element. */ -- (instancetype)initWithTagName:(NSString *)tagName attributes:(NSDictionary *)attributes; +- (instancetype)initWithTagName:(NSString *)tagName attributes:(nullable NSDictionary *)attributes; /** Initializes a new HTML element with the given tag name, namespace, and attributes. @@ -85,7 +85,7 @@ NS_ASSUME_NONNULL_BEGIN @param attributes The attributes. @return A new HTML element. */ -- (instancetype)initWithTagName:(NSString *)tagName namespace:(HTMLNamespace)htmlNamespace attributes:(NSDictionary *)attributes; +- (instancetype)initWithTagName:(NSString *)tagName namespace:(HTMLNamespace)htmlNamespace attributes:(nullable NSDictionary *)attributes; /** Checks whether this element has an attribute with the given name. From 14dfc0b854ed178c0ab554abd4d54042660d604d Mon Sep 17 00:00:00 2001 From: iska Date: Sun, 9 Apr 2017 20:53:11 +0200 Subject: [PATCH 3/9] Replace performSelector with for-loop in HTML Node methods --- Sources/HTMLNode.m | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/Sources/HTMLNode.m b/Sources/HTMLNode.m index f0f95d5..da9d2b6 100644 --- a/Sources/HTMLNode.m +++ b/Sources/HTMLNode.m @@ -64,7 +64,9 @@ - (HTMLDocument *)ownerDocument - (void)setOwnerDocument:(HTMLDocument *)ownerDocument { _ownerDocument = ownerDocument; - [self.childNodes.array makeObjectsPerformSelector:@selector(setOwnerDocument:) withObject:ownerDocument]; + for (HTMLNode *child in self.childNodes) { + [child setOwnerDocument:ownerDocument]; + } } - (HTMLNode *)rootNode @@ -271,7 +273,9 @@ - (HTMLNode *)insertNode:(HTMLNode *)node beforeChildNode:(HTMLNode *)child [node removeAllChildNodes]; } - [nodes makeObjectsPerformSelector:@selector(setParentNode:) withObject:self]; + for (HTMLNode *node in nodes) { + [node setParentNode:self]; + } return node; } @@ -331,7 +335,7 @@ - (HTMLNode *)removeChildNodeAtIndex:(NSUInteger)index - (void)reparentChildNodesIntoNode:(HTMLNode *)node { - for (HTMLNode *child in self.childNodes.array) { + for (HTMLNode *child in self.childNodes) { [node appendNode:child]; } [(NSMutableOrderedSet *)self.childNodes removeAllObjects]; @@ -339,7 +343,9 @@ - (void)reparentChildNodesIntoNode:(HTMLNode *)node - (void)removeAllChildNodes { - [self.childNodes.array makeObjectsPerformSelector:@selector(setParentNode:) withObject:nil]; + for (HTMLNode *child in self.childNodes) { + [child setParentNode:nil]; + } [(NSMutableOrderedSet *)self.childNodes removeAllObjects]; } From 30389c50106caf34c9b9bbe7d8f7bffb76a14b8a Mon Sep 17 00:00:00 2001 From: iska Date: Sun, 9 Apr 2017 20:54:01 +0200 Subject: [PATCH 4/9] =?UTF-8?q?Add=20an=20autorelease=20pool=20in=20the=20?= =?UTF-8?q?Tokenizer=E2=80=99s=20iteration=20method?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Sources/HTMLTokenizer.m | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/Sources/HTMLTokenizer.m b/Sources/HTMLTokenizer.m index 705eb45..9f69ee6 100644 --- a/Sources/HTMLTokenizer.m +++ b/Sources/HTMLTokenizer.m @@ -77,14 +77,16 @@ - (NSString *)string - (id)nextObject { - while (_eof == NO && _tokens.count == 0) { - [self read]; - } - HTMLToken *nextToken = [_tokens firstObject]; - if (_tokens.count > 0) { - [_tokens removeObjectAtIndex:0]; + @autoreleasepool { + while (_eof == NO && _tokens.count == 0) { + [self read]; + } + HTMLToken *nextToken = [_tokens firstObject]; + if (_tokens.count > 0) { + [_tokens removeObjectAtIndex:0]; + } + return nextToken; } - return nextToken; } - (void)read From d9670cddf4d19e7893abbf9a79aaf620533745c3 Mon Sep 17 00:00:00 2001 From: iska Date: Sun, 9 Apr 2017 20:58:25 +0200 Subject: [PATCH 5/9] Add workaround for Xcode8.3 issue with modulemaps This should be the fix for #12. However the issue will stay open until tested with Xcode 8.3.1 --- Sources/include/{module.modulemap => HTMLKit.modulemap} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename Sources/include/{module.modulemap => HTMLKit.modulemap} (100%) diff --git a/Sources/include/module.modulemap b/Sources/include/HTMLKit.modulemap similarity index 100% rename from Sources/include/module.modulemap rename to Sources/include/HTMLKit.modulemap From 653f6cdf7e73d6fc46d0b151e9a2f47a33963614 Mon Sep 17 00:00:00 2001 From: iska Date: Tue, 18 Apr 2017 23:58:46 +0200 Subject: [PATCH 6/9] Add Changelog entry for HTMLKit 2.0.5 --- CHANGELOG.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 87aa529..4f67a6c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,21 @@ # Change Log +## [2.0.5](https://github.com/iabudiab/HTMLKit/releases/tag/2.0.5) + +Released on 2017.04.19 + +### Fixed +- Xcode 8.3 issue with modulemaps + - Temporary workaround (renamed modulemap file) +- Memory Leaks in `CSSInputStream` + +### Added +- Minor memory consumption improvements + - Collections for child nodes or attributes of HTML Nodes or Elements are allocated lazily + - Underyling data string of `CharacterData` is allocated on first access + - Autorelease pool for the main `HTMLTokenizer` loop + + ## [2.0.4](https://github.com/iabudiab/HTMLKit/releases/tag/2.0.4) Released on 2017.04.2 From 746ef2ea3b153182dc2c0848be422506a0db22d8 Mon Sep 17 00:00:00 2001 From: iska Date: Tue, 18 Apr 2017 23:59:27 +0200 Subject: [PATCH 7/9] Update jazzy.yaml for 2.0.5 --- .jazzy.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.jazzy.yaml b/.jazzy.yaml index 4b28c93..4bcae51 100644 --- a/.jazzy.yaml +++ b/.jazzy.yaml @@ -1,5 +1,5 @@ module: HTMLKit -module_version: 2.0.4 +module_version: 2.0.5 author: Iskandar Abudiab author_url: https://twitter.com/iabudiab github_url: https://github.com/iabudiab/HTMLKit From 49bdfa018e1e7ab2d41171ace205e7b884113a7b Mon Sep 17 00:00:00 2001 From: iska Date: Tue, 18 Apr 2017 23:59:37 +0200 Subject: [PATCH 8/9] Update podspec for 2.0.5 --- HTMLKit.podspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/HTMLKit.podspec b/HTMLKit.podspec index 48d2a6c..4991a29 100644 --- a/HTMLKit.podspec +++ b/HTMLKit.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = "HTMLKit" - s.version = "2.0.4" + s.version = "2.0.5" s.summary = "HTMLKit, an Objective-C framework for your everyday HTML needs." s.license = "MIT" s.homepage = "https://github.com/iabudiab/HTMLKit" From 76b379448b361f52cc0e264fa90a5fe3fdb2edb7 Mon Sep 17 00:00:00 2001 From: iska Date: Tue, 18 Apr 2017 23:59:57 +0200 Subject: [PATCH 9/9] Bump HTMLKit version to 2.0.5 --- Sources/HTMLKit-Info.plist | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/HTMLKit-Info.plist b/Sources/HTMLKit-Info.plist index a058d41..11b6ce9 100644 --- a/Sources/HTMLKit-Info.plist +++ b/Sources/HTMLKit-Info.plist @@ -17,7 +17,7 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 2.0.4 + 2.0.5 CFBundleSignature ???? CFBundleVersion