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

Reply via email to