Mhurd has uploaded a new change for review. https://gerrit.wikimedia.org/r/201092
Change subject: Reuse of NSRegularExpression objects in string formatting method. ...................................................................... Reuse of NSRegularExpression objects in string formatting method. Had been re-instancing regex objects in a loop. Added tests. ~65% performance increase. Change-Id: I30ddc7bd59b163b1300dbe6b0c2587337d8581b7 --- M Wikipedia.xcodeproj/project.pbxproj A WikipediaUnitTests/NSString+FormattedAttributedStringTests.m M wikipedia/Categories/NSString+FormattedAttributedString.m 3 files changed, 126 insertions(+), 11 deletions(-) git pull ssh://gerrit.wikimedia.org:29418/apps/ios/wikipedia refs/changes/92/201092/1 diff --git a/Wikipedia.xcodeproj/project.pbxproj b/Wikipedia.xcodeproj/project.pbxproj index b3dbec8..c9f89a3 100644 --- a/Wikipedia.xcodeproj/project.pbxproj +++ b/Wikipedia.xcodeproj/project.pbxproj @@ -207,6 +207,8 @@ 04EDEE311A21CB4100798076 /* UIScreen+Extras.m in Sources */ = {isa = PBXBuildFile; fileRef = 04EDEE301A21CB4100798076 /* UIScreen+Extras.m */; }; 04F0E2EA186EDC1A00468738 /* UIWebView+ElementLocation.m in Sources */ = {isa = PBXBuildFile; fileRef = 04F0E2E9186EDC1A00468738 /* UIWebView+ElementLocation.m */; }; 04F0E2EE186FB2D100468738 /* TOCSectionCellView.m in Sources */ = {isa = PBXBuildFile; fileRef = 04F0E2ED186FB2D100468738 /* TOCSectionCellView.m */; }; + 04F122671ACB818F002FC3B5 /* NSString+FormattedAttributedString.m in Sources */ = {isa = PBXBuildFile; fileRef = 04B91AAA18E3D9E200FFAA1C /* NSString+FormattedAttributedString.m */; }; + 04F1226A1ACB822D002FC3B5 /* NSString+FormattedAttributedStringTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 04F122691ACB822D002FC3B5 /* NSString+FormattedAttributedStringTests.m */; }; 04F27B7518FE0F2E00EDD838 /* PageHistoryResultCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 04F27B6F18FE0F2E00EDD838 /* PageHistoryResultCell.m */; }; 04F27B7618FE0F2E00EDD838 /* PageHistoryResultPrototypeView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 04F27B7018FE0F2E00EDD838 /* PageHistoryResultPrototypeView.xib */; }; 04F27B7818FE0F2E00EDD838 /* PageHistoryViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 04F27B7418FE0F2E00EDD838 /* PageHistoryViewController.m */; }; @@ -731,6 +733,7 @@ 04F0E2E9186EDC1A00468738 /* UIWebView+ElementLocation.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIWebView+ElementLocation.m"; sourceTree = "<group>"; }; 04F0E2EC186FB2D000468738 /* TOCSectionCellView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TOCSectionCellView.h; sourceTree = "<group>"; }; 04F0E2ED186FB2D100468738 /* TOCSectionCellView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TOCSectionCellView.m; sourceTree = "<group>"; }; + 04F122691ACB822D002FC3B5 /* NSString+FormattedAttributedStringTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSString+FormattedAttributedStringTests.m"; sourceTree = "<group>"; }; 04F27B6E18FE0F2E00EDD838 /* PageHistoryResultCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PageHistoryResultCell.h; sourceTree = "<group>"; }; 04F27B6F18FE0F2E00EDD838 /* PageHistoryResultCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; lineEnding = 0; path = PageHistoryResultCell.m; sourceTree = "<group>"; xcLanguageSpecificationIdentifier = xcode.lang.objc; }; 04F27B7018FE0F2E00EDD838 /* PageHistoryResultPrototypeView.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = PageHistoryResultPrototypeView.xib; sourceTree = "<group>"; }; @@ -2045,6 +2048,7 @@ BCB669FC1A84158200C7B1FE /* CircularBitwiseRotationTests.m */, BCB58F7B1A8D0C8E00465627 /* NSArray+BKIndexTests.m */, C983151B1AA5205700E25EE1 /* NSString+WMFHTMLParsingTests.m */, + 04F122691ACB822D002FC3B5 /* NSString+FormattedAttributedStringTests.m */, BCBDE0AB1AA76EAC006BD29A /* WMFImageURLParsingTests.m */, BCB8487A1AAAADF90077EC24 /* WMFRoundingUtilitiesTests.m */, BCDB75C31AB0E8300005593F /* WMFSubstringUtilsTests.m */, @@ -2912,6 +2916,7 @@ 04D686F51AB2949C0009B44A /* MenuButton.m in Sources */, 04D686F91AB2949C0009B44A /* PaddedLabel.m in Sources */, BC0FED771AAA026C002488D7 /* WMFImageURLParsingTests.m in Sources */, + 04F1226A1ACB822D002FC3B5 /* NSString+FormattedAttributedStringTests.m in Sources */, BC0FED6B1AAA0268002488D7 /* MWKDataStoreStorageTests.m in Sources */, 0484B9071ABB50FA00874073 /* WMFArticleParsing.m in Sources */, BC0FED751AAA026C002488D7 /* NSArray+BKIndexTests.m in Sources */, @@ -2935,6 +2940,7 @@ BCA6765A1AC0600500A16160 /* MWKDataStore+TemporaryDataStore.m in Sources */, 04D686CA1AB28FE40009B44A /* UIImage+WMFFocalImageDrawing.m in Sources */, 04D686FF1AB2949C0009B44A /* WikiGlyphLabel.m in Sources */, + 04F122671ACB818F002FC3B5 /* NSString+FormattedAttributedString.m in Sources */, BC0FED711AAA026C002488D7 /* WMFJoinedPropertyParametersTests.m in Sources */, BC0FED6A1AAA0268002488D7 /* MWKDataStorePathTests.m in Sources */, BC0FED761AAA026C002488D7 /* NSString+WMFHTMLParsingTests.m in Sources */, diff --git a/WikipediaUnitTests/NSString+FormattedAttributedStringTests.m b/WikipediaUnitTests/NSString+FormattedAttributedStringTests.m new file mode 100644 index 0000000..a9752b3 --- /dev/null +++ b/WikipediaUnitTests/NSString+FormattedAttributedStringTests.m @@ -0,0 +1,91 @@ +// +// NSString+FormattedAttributedStringTests.m +// Wikipedia +// +// Created by Monte Hurd on 3/31/15. +// Copyright (c) 2015 Wikimedia Foundation. All rights reserved. +// + +#import <UIKit/UIKit.h> +#import <XCTest/XCTest.h> +#import "NSString+FormattedAttributedString.h" + +@interface NSString_FormattedAttributedStringTests : XCTestCase + +@property (nonatomic, strong) NSDictionary* largeOrangeText; +@property (nonatomic, strong) NSDictionary* smallGreenText; +@property (nonatomic, strong) NSDictionary* mediumBlueText; + +@end + +@implementation NSString_FormattedAttributedStringTests + +- (void)setUp { + [super setUp]; + // Put setup code here. This method is called before the invocation of each test method in the class. + + self.largeOrangeText = @{ + NSFontAttributeName: [UIFont fontWithName:@"Georgia" size:20], + NSForegroundColorAttributeName: [UIColor orangeColor] + }; + self.smallGreenText = @{ + NSFontAttributeName: [UIFont boldSystemFontOfSize:8], + NSForegroundColorAttributeName: [UIColor greenColor] + }; + self.mediumBlueText = @{ + NSFontAttributeName: [UIFont systemFontOfSize:14], + NSForegroundColorAttributeName: [UIColor blueColor], + NSStrikethroughStyleAttributeName: @YES + }; +} + +- (void)tearDown { + // Put teardown code here. This method is called after the invocation of each test method in the class. + [super tearDown]; +} + +- (void)testComplexAttributedStringCreation { + + // First create complex attributed string (complexAttributedString1) using our substitution method: + // (Note the multiple occurences of "$1".) + NSAttributedString* complexAttributedString1 = + [@"Large orange text and some $1 and $2 text. More $1 text." + attributedStringWithAttributes:self.largeOrangeText + substitutionStrings:@[@"small green", @"medium blue"] + substitutionAttributes:@[self.smallGreenText, self.mediumBlueText] + ]; + + // Now create identical complex attributed string (complexAttributedString2) using standard methods: + NSMutableAttributedString* complexAttributedString2 = [[NSMutableAttributedString alloc] init]; + [complexAttributedString2 appendAttributedString:[[NSMutableAttributedString alloc] initWithString:@"Large orange text and some " attributes:self.largeOrangeText]]; + [complexAttributedString2 appendAttributedString:[[NSMutableAttributedString alloc] initWithString:@"small green" attributes:self.smallGreenText]]; + [complexAttributedString2 appendAttributedString:[[NSMutableAttributedString alloc] initWithString:@" and " attributes:self.largeOrangeText]]; + [complexAttributedString2 appendAttributedString:[[NSMutableAttributedString alloc] initWithString:@"medium blue" attributes:self.mediumBlueText]]; + [complexAttributedString2 appendAttributedString:[[NSMutableAttributedString alloc] initWithString:@" text. More " attributes:self.largeOrangeText]]; + [complexAttributedString2 appendAttributedString:[[NSMutableAttributedString alloc] initWithString:@"small green" attributes:self.smallGreenText]]; + [complexAttributedString2 appendAttributedString:[[NSMutableAttributedString alloc] initWithString:@" text." attributes:self.largeOrangeText]]; + + // Test equality. + XCTAssert([complexAttributedString1 isEqualToAttributedString:complexAttributedString2]); + +} + +- (void)testPerformanceExample { + // This is an example of a performance test case. + [self measureBlock:^{ + // Put the code you want to measure the time of here. + + for (NSInteger i = 0; i < 10000; i++) { + + NSAttributedString* complexAttributedString1 = + [@"Large orange text and some $1 and $2 text. More $1 text." + attributedStringWithAttributes:self.largeOrangeText + substitutionStrings:@[@"small green", @"medium blue"] + substitutionAttributes:@[self.smallGreenText, self.mediumBlueText] + ]; + } + + }]; +} + +@end diff --git a/wikipedia/Categories/NSString+FormattedAttributedString.m b/wikipedia/Categories/NSString+FormattedAttributedString.m index 449dbf6..f31e1f1 100644 --- a/wikipedia/Categories/NSString+FormattedAttributedString.m +++ b/wikipedia/Categories/NSString+FormattedAttributedString.m @@ -8,23 +8,41 @@ - (NSAttributedString*)attributedStringWithAttributes:(NSDictionary*)attributes substitutionStrings:(NSArray*)substitutionStrings substitutionAttributes:(NSArray*)substitutionAttributes { + static NSLock* regexArrayProtectionLock = nil; + if (!regexArrayProtectionLock) { + regexArrayProtectionLock = [[NSLock alloc] init]; + } + + [regexArrayProtectionLock lock]; + + static NSMutableArray* regexArray = nil; + if (!regexArray) { + regexArray = @[].mutableCopy; + } + if (regexArray.count < substitutionStrings.count) { + NSInteger regexCountNeeded = substitutionStrings.count - regexArray.count; + NSInteger regexCountPrevious = regexArray.count; + for (NSUInteger i = 0; i < regexCountNeeded; i++) { + NSRegularExpression* newRegex = [NSRegularExpression regularExpressionWithPattern:[NSString stringWithFormat:@"\\$%lu+", (unsigned long)i + regexCountPrevious + 1] options:0 error:nil]; + [regexArray addObject:newRegex]; + } + } + NSAssert(regexArray.count >= substitutionStrings.count, @"Not enough NSRegularExpression objects."); + + [regexArrayProtectionLock unlock]; + NSMutableAttributedString* returnString = [[NSMutableAttributedString alloc] initWithString:self attributes:attributes]; for (NSUInteger i = 0; i < substitutionStrings.count; i++) { - NSRegularExpression* regex = - [NSRegularExpression regularExpressionWithPattern:[NSString stringWithFormat:@"\\$%lu+", (unsigned long)i + 1] - options:0 - error:nil]; - NSArray* matches = - [regex matchesInString:returnString.string - options:0 - range:NSMakeRange(0, returnString.string.length)]; + NSArray* matches = [regexArray[i] matchesInString:returnString.string + options:0 + range:NSMakeRange(0, returnString.string.length)]; - for (NSTextCheckingResult* checkingResult in [matches reverseObjectEnumerator]) { - [returnString setAttributes:substitutionAttributes[i] range:checkingResult.range]; - [returnString replaceCharactersInRange:checkingResult.range withString:substitutionStrings[i]]; + for (NSTextCheckingResult* match in [matches reverseObjectEnumerator]) { + [returnString setAttributes:substitutionAttributes[i] range:match.range]; + [returnString replaceCharactersInRange:match.range withString:substitutionStrings[i]]; } } return returnString; -- To view, visit https://gerrit.wikimedia.org/r/201092 To unsubscribe, visit https://gerrit.wikimedia.org/r/settings Gerrit-MessageType: newchange Gerrit-Change-Id: I30ddc7bd59b163b1300dbe6b0c2587337d8581b7 Gerrit-PatchSet: 1 Gerrit-Project: apps/ios/wikipedia Gerrit-Branch: master Gerrit-Owner: Mhurd <mh...@wikimedia.org> _______________________________________________ MediaWiki-commits mailing list MediaWiki-commits@lists.wikimedia.org https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits