Skip to content

Commit 6c07d21

Browse files
author
Eugene Kazaev
committed
Minor performance improvements
1 parent b7fe23d commit 6c07d21

File tree

77 files changed

+2056
-1832
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

77 files changed

+2056
-1832
lines changed

ChatLayout.podspec

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
Pod::Spec.new do |s|
22
s.name = 'ChatLayout'
3-
s.version = '1.2.7'
3+
s.version = '1.2.8'
44
s.summary = 'Chat UI Library. It uses custom UICollectionViewLayout to provide you full control over the presentation.'
55
s.swift_version = '5.7'
66

ChatLayout/Classes/Core/CollectionViewChatLayout.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -285,9 +285,11 @@ public final class CollectionViewChatLayout: UICollectionViewLayout {
285285
return
286286
}
287287

288+
#if DEBUG
288289
if collectionView.isPrefetchingEnabled {
289290
preconditionFailure("UICollectionView with prefetching enabled is not supported due to https://openradar.appspot.com/40926834 bug.")
290291
}
292+
#endif
291293

292294
if prepareActions.contains(.switchStates) {
293295
controller.commitUpdates()

ChatLayout/Classes/Core/Model/LayoutModel.swift

Lines changed: 21 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -40,22 +40,25 @@ struct LayoutModel {
4040
var offset: CGFloat = collectionLayout.settings.additionalInsets.top
4141

4242
var sectionIndexByIdentifierCache = [UUID: Int](minimumCapacity: sections.count)
43-
var itemPathByIdentifierCache = [ItemUUIDKey: ItemPath]()
44-
45-
for sectionIndex in 0..<sections.count {
46-
sectionIndexByIdentifierCache[sections[sectionIndex].id] = sectionIndex
47-
sections[sectionIndex].offsetY = offset
48-
offset += sections[sectionIndex].height + collectionLayout.settings.interSectionSpacing
49-
if let header = sections[sectionIndex].header {
50-
itemPathByIdentifierCache[ItemUUIDKey(kind: .header, id: header.id)] = ItemPath(item: 0, section: sectionIndex)
51-
}
52-
for itemIndex in 0..<sections[sectionIndex].items.count {
53-
itemPathByIdentifierCache[ItemUUIDKey(kind: .cell, id: sections[sectionIndex].items[itemIndex].id)] = ItemPath(item: itemIndex, section: sectionIndex)
54-
}
55-
if let footer = sections[sectionIndex].footer {
56-
itemPathByIdentifierCache[ItemUUIDKey(kind: .footer, id: footer.id)] = ItemPath(item: 0, section: sectionIndex)
43+
var itemPathByIdentifierCache = [ItemUUIDKey: ItemPath](minimumCapacity: sections.reduce(into: 0) { $0 += $1.items.count })
44+
45+
sections.withUnsafeMutableBufferPointer { directlyMutableSections in
46+
for sectionIndex in 0..<directlyMutableSections.count {
47+
sectionIndexByIdentifierCache[directlyMutableSections[sectionIndex].id] = sectionIndex
48+
directlyMutableSections[sectionIndex].offsetY = offset
49+
offset += directlyMutableSections[sectionIndex].height + collectionLayout.settings.interSectionSpacing
50+
if let header = directlyMutableSections[sectionIndex].header {
51+
itemPathByIdentifierCache[ItemUUIDKey(kind: .header, id: header.id)] = ItemPath(item: 0, section: sectionIndex)
52+
}
53+
for itemIndex in 0..<directlyMutableSections[sectionIndex].items.count {
54+
itemPathByIdentifierCache[ItemUUIDKey(kind: .cell, id: directlyMutableSections[sectionIndex].items[itemIndex].id)] = ItemPath(item: itemIndex, section: sectionIndex)
55+
}
56+
if let footer = directlyMutableSections[sectionIndex].footer {
57+
itemPathByIdentifierCache[ItemUUIDKey(kind: .footer, id: footer.id)] = ItemPath(item: 0, section: sectionIndex)
58+
}
5759
}
5860
}
61+
5962
self.itemPathByIdentifierCache = itemPathByIdentifierCache
6063
self.sectionIndexByIdentifierCache = sectionIndexByIdentifierCache
6164
}
@@ -133,8 +136,10 @@ struct LayoutModel {
133136
return
134137
}
135138
if index < sections.count &- 1 {
136-
for index in (index &+ 1)..<sections.count {
137-
sections[index].offsetY += heightDiff
139+
sections.withUnsafeMutableBufferPointer { directlyMutableSections in
140+
for index in (index &+ 1)..<directlyMutableSections.count {
141+
directlyMutableSections[index].offsetY += heightDiff
142+
}
138143
}
139144
}
140145
}

ChatLayout/Classes/Core/Model/SectionModel.swift

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,6 @@ struct SectionModel {
2727

2828
private unowned var collectionLayout: ChatLayoutRepresentation
2929

30-
var count: Int {
31-
items.count
32-
}
33-
3430
var frame: CGRect {
3531
let additionalInsets = collectionLayout.settings.additionalInsets
3632
return CGRect(x: 0,
@@ -74,9 +70,11 @@ struct SectionModel {
7470
offsetY += header?.frame.height ?? 0
7571
}
7672

77-
for rowIndex in 0..<items.count {
78-
items[rowIndex].offsetY = offsetY
79-
offsetY += items[rowIndex].height + collectionLayout.settings.interItemSpacing
73+
items.withUnsafeMutableBufferPointer { directlyMutableItems in
74+
for rowIndex in 0..<directlyMutableItems.count {
75+
directlyMutableItems[rowIndex].offsetY = offsetY
76+
offsetY += directlyMutableItems[rowIndex].height + collectionLayout.settings.interItemSpacing
77+
}
8078
}
8179

8280
if footer != nil {
@@ -103,7 +101,7 @@ struct SectionModel {
103101
}
104102

105103
mutating func setAndAssemble(item: ItemModel, at index: Int) {
106-
guard index < count else {
104+
guard index < items.count else {
107105
assertionFailure("Incorrect item index.")
108106
return
109107
}
@@ -152,8 +150,10 @@ struct SectionModel {
152150
return
153151
}
154152
if index < items.count &- 1 {
155-
for index in (index &+ 1)..<items.count {
156-
items[index].offsetY += heightDiff
153+
items.withUnsafeMutableBufferPointer { directlyMutableItems in
154+
for index in (index &+ 1)..<directlyMutableItems.count {
155+
directlyMutableItems[index].offsetY += heightDiff
156+
}
157157
}
158158
}
159159
footer?.offsetY += heightDiff
@@ -162,23 +162,23 @@ struct SectionModel {
162162
// MARK: To use only withing process(updateItems:)
163163

164164
mutating func insert(_ item: ItemModel, at index: Int) {
165-
guard index <= count else {
165+
guard index <= items.count else {
166166
assertionFailure("Incorrect item index.")
167167
return
168168
}
169169
items.insert(item, at: index)
170170
}
171171

172172
mutating func replace(_ item: ItemModel, at index: Int) {
173-
guard index <= count else {
173+
guard index <= items.count else {
174174
assertionFailure("Incorrect item index.")
175175
return
176176
}
177177
items[index] = item
178178
}
179179

180180
mutating func remove(at index: Int) {
181-
guard index < count else {
181+
guard index < items.count else {
182182
assertionFailure("Incorrect item index.")
183183
return
184184
}

ChatLayout/Classes/Core/Model/StateController.swift

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,7 @@ final class StateController {
118118
if !ignoreCache,
119119
let cachedAttributesState = cachedAttributesState,
120120
cachedAttributesState.rect.contains(rect) {
121-
return cachedAttributesState.attributes.binarySearchRange(predicate: predicate)
121+
return cachedAttributesState.attributes.withUnsafeBufferPointer { $0.binarySearchRange(predicate: predicate) }
122122
} else {
123123
let totalRect: CGRect
124124
switch state {
@@ -131,7 +131,7 @@ final class StateController {
131131
if !ignoreCache {
132132
cachedAttributesState = (rect: totalRect, attributes: attributes)
133133
}
134-
let visibleAttributes = rect != totalRect ? attributes.binarySearchRange(predicate: predicate) : attributes
134+
let visibleAttributes = rect != totalRect ? attributes.withUnsafeBufferPointer { $0.binarySearchRange(predicate: predicate) } : attributes
135135
return visibleAttributes
136136
}
137137
}
@@ -294,9 +294,11 @@ final class StateController {
294294
}
295295

296296
func section(at index: Int, at state: ModelState) -> SectionModel {
297+
#if DEBUG
297298
guard index < layout(at: state).sections.count else {
298299
preconditionFailure("Section index \(index) is bigger than the amount of sections \(layout(at: state).sections.count).")
299300
}
301+
#endif
300302
return layout(at: state).sections[index]
301303
}
302304

@@ -355,7 +357,7 @@ final class StateController {
355357
return footer
356358
case .cell:
357359
guard itemPath.section < layout(at: state).sections.count,
358-
itemPath.item < layout(at: state).sections[itemPath.section].count else {
360+
itemPath.item < layout(at: state).sections[itemPath.section].items.count else {
359361
// This occurs when getting layout attributes for initial / final animations
360362
return nil
361363
}
@@ -501,12 +503,15 @@ final class StateController {
501503
}
502504
}
503505

504-
afterUpdateModel = LayoutModel(sections: afterUpdateModel.sections.map { section -> SectionModel in
505-
var section = section
506-
section.assembleLayout()
507-
return section
508-
}, collectionLayout: layoutRepresentation)
506+
var afterUpdateModelSections = afterUpdateModel.sections
507+
afterUpdateModelSections.withUnsafeMutableBufferPointer { directlyMutableSections in
508+
for index in 0..<directlyMutableSections.count {
509+
directlyMutableSections[index].assembleLayout()
510+
}
511+
}
512+
afterUpdateModel = LayoutModel(sections: afterUpdateModelSections, collectionLayout: layoutRepresentation)
509513
afterUpdateModel.assembleLayout()
514+
510515
storage[.afterUpdate] = afterUpdateModel
511516

512517
// Calculating potential content offset changes after the updates
@@ -680,7 +685,7 @@ final class StateController {
680685

681686
// Find if any of the items of the section is visible
682687
if [ComparisonResult.orderedSame, .orderedDescending].contains(predicate(itemIndex: section.items.count - 1)),
683-
let firstMatchingIndex = Array(0...section.items.count - 1).binarySearch(predicate: predicate) {
688+
let firstMatchingIndex = Array(0...section.items.count - 1).withUnsafeBufferPointer({ $0.binarySearch(predicate: predicate) }) {
684689
// Find first item that is visible
685690
startingIndex = firstMatchingIndex
686691
for itemIndex in (0..<firstMatchingIndex).reversed() {

Example/ChatLayout.xcodeproj/project.pbxproj

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@
8484
607FACE01AFB9204008FA782 /* LaunchScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = 607FACDE1AFB9204008FA782 /* LaunchScreen.xib */; };
8585
607FACEC1AFB9204008FA782 /* StateControllerProcessUpdatesTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 607FACEB1AFB9204008FA782 /* StateControllerProcessUpdatesTests.swift */; };
8686
65D4D44932474631D9677C50 /* Pods_ChatLayout_Tests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 12FB56A5FF321AA24C456635 /* Pods_ChatLayout_Tests.framework */; };
87+
D5A31E60E9BF6F8C13EDFDC5 /* PerformanceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5A31C93E56F2602C9BA605B /* PerformanceTests.swift */; };
8788
/* End PBXBuildFile section */
8889

8990
/* Begin PBXContainerItemProxy section */
@@ -186,6 +187,7 @@
186187
B520747F8C032C454221D1EC /* README.md */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = net.daringfireball.markdown; name = README.md; path = ../README.md; sourceTree = "<group>"; };
187188
BDD575DC427C1961B83F685C /* Pods-ChatLayout_Example.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ChatLayout_Example.release.xcconfig"; path = "Target Support Files/Pods-ChatLayout_Example/Pods-ChatLayout_Example.release.xcconfig"; sourceTree = "<group>"; };
188189
CFE0D61EDC4670E905EC3EF0 /* Pods-ChatLayout_Example.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ChatLayout_Example.debug.xcconfig"; path = "Target Support Files/Pods-ChatLayout_Example/Pods-ChatLayout_Example.debug.xcconfig"; sourceTree = "<group>"; };
190+
D5A31C93E56F2602C9BA605B /* PerformanceTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PerformanceTests.swift; sourceTree = "<group>"; };
189191
/* End PBXFileReference section */
190192

191193
/* Begin PBXFrameworksBuildPhase section */
@@ -511,6 +513,7 @@
511513
1BE5443B3AE1C502DEC3F805 /* MockUICollectionViewUpdateItem.swift */,
512514
1BE54A1413D0DCF3036C9508 /* StateControllerInternalTests.swift */,
513515
1BE542A452EBF25EC59440C0 /* HelpersTests.swift */,
516+
D5A31C93E56F2602C9BA605B /* PerformanceTests.swift */,
514517
);
515518
path = Tests;
516519
sourceTree = "<group>";
@@ -803,6 +806,7 @@
803806
1BE54DBD5F84AF25128B6789 /* MockUICollectionViewUpdateItem.swift in Sources */,
804807
1BE544269CC3DC62CEB1D56E /* StateControllerInternalTests.swift in Sources */,
805808
1BE54FDB9836397E2694B9D7 /* HelpersTests.swift in Sources */,
809+
D5A31E60E9BF6F8C13EDFDC5 /* PerformanceTests.swift in Sources */,
806810
);
807811
runOnlyForDeploymentPostprocessing = 0;
808812
};

Example/ChatLayout.xcodeproj/xcshareddata/xcbaselines/607FACE41AFB9204008FA782.xcbaseline/71E5FE30-BEA6-4194-B013-E550EA60539B.plist

Lines changed: 109 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,120 @@
44
<dict>
55
<key>classNames</key>
66
<dict>
7+
<key>HelpersTests</key>
8+
<dict>
9+
<key>testBinarySearchPerformance()</key>
10+
<dict>
11+
<key>com.apple.XCTPerformanceMetric_WallClockTime</key>
12+
<dict>
13+
<key>baselineAverage</key>
14+
<real>0.620000</real>
15+
<key>baselineIntegrationDisplayName</key>
16+
<string>Local Baseline</string>
17+
</dict>
18+
</dict>
19+
<key>testBinarySearchRangePerformance()</key>
20+
<dict>
21+
<key>com.apple.XCTPerformanceMetric_WallClockTime</key>
22+
<dict>
23+
<key>baselineAverage</key>
24+
<real>0.952000</real>
25+
<key>baselineIntegrationDisplayName</key>
26+
<string>Local Baseline</string>
27+
</dict>
28+
</dict>
29+
<key>testLayoutAttributesForElementsPerformance()</key>
30+
<dict>
31+
<key>com.apple.XCTPerformanceMetric_WallClockTime</key>
32+
<dict>
33+
<key>baselineAverage</key>
34+
<real>0.510668</real>
35+
<key>baselineIntegrationDisplayName</key>
36+
<string>Local Baseline</string>
37+
</dict>
38+
</dict>
39+
</dict>
40+
<key>PerformanceTests</key>
41+
<dict>
42+
<key>testBinarySearchPerformance()</key>
43+
<dict>
44+
<key>com.apple.XCTPerformanceMetric_WallClockTime</key>
45+
<dict>
46+
<key>baselineAverage</key>
47+
<real>0.610726</real>
48+
<key>baselineIntegrationDisplayName</key>
49+
<string>Local Baseline</string>
50+
</dict>
51+
</dict>
52+
<key>testBinarySearchRangePerformance()</key>
53+
<dict>
54+
<key>com.apple.XCTPerformanceMetric_WallClockTime</key>
55+
<dict>
56+
<key>baselineAverage</key>
57+
<real>0.931973</real>
58+
<key>baselineIntegrationDisplayName</key>
59+
<string>Local Baseline</string>
60+
</dict>
61+
</dict>
62+
<key>testDeletePerformance()</key>
63+
<dict>
64+
<key>com.apple.XCTPerformanceMetric_WallClockTime</key>
65+
<dict>
66+
<key>baselineAverage</key>
67+
<real>0.288655</real>
68+
<key>baselineIntegrationDisplayName</key>
69+
<string>Local Baseline</string>
70+
</dict>
71+
</dict>
72+
<key>testInsertionPerformance()</key>
73+
<dict>
74+
<key>com.apple.XCTPerformanceMetric_WallClockTime</key>
75+
<dict>
76+
<key>baselineAverage</key>
77+
<real>0.138624</real>
78+
<key>baselineIntegrationDisplayName</key>
79+
<string>Local Baseline</string>
80+
</dict>
81+
</dict>
82+
<key>testItemUpdatePerformance()</key>
83+
<dict>
84+
<key>com.apple.XCTPerformanceMetric_WallClockTime</key>
85+
<dict>
86+
<key>baselineAverage</key>
87+
<real>0.053014</real>
88+
<key>baselineIntegrationDisplayName</key>
89+
<string>Local Baseline</string>
90+
</dict>
91+
</dict>
92+
<key>testLayoutAttributesForElementsPerformance()</key>
93+
<dict>
94+
<key>com.apple.XCTPerformanceMetric_WallClockTime</key>
95+
<dict>
96+
<key>baselineAverage</key>
97+
<real>0.509836</real>
98+
<key>baselineIntegrationDisplayName</key>
99+
<string>Local Baseline</string>
100+
</dict>
101+
</dict>
102+
<key>testReloadPerformance()</key>
103+
<dict>
104+
<key>com.apple.XCTPerformanceMetric_WallClockTime</key>
105+
<dict>
106+
<key>baselineAverage</key>
107+
<real>0.016561</real>
108+
<key>baselineIntegrationDisplayName</key>
109+
<string>Local Baseline</string>
110+
</dict>
111+
</dict>
112+
</dict>
7113
<key>StateControllerProcessUpdatesTests</key>
8114
<dict>
9115
<key>testDeletePerformance()</key>
10116
<dict>
11117
<key>com.apple.XCTPerformanceMetric_WallClockTime</key>
12118
<dict>
13119
<key>baselineAverage</key>
14-
<real>0.25939</real>
120+
<real>0.259390</real>
15121
<key>baselineIntegrationDisplayName</key>
16122
<string>Local Baseline</string>
17123
</dict>
@@ -21,7 +127,7 @@
21127
<key>com.apple.XCTPerformanceMetric_WallClockTime</key>
22128
<dict>
23129
<key>baselineAverage</key>
24-
<real>0.12628</real>
130+
<real>0.126280</real>
25131
<key>baselineIntegrationDisplayName</key>
26132
<string>Local Baseline</string>
27133
</dict>
@@ -31,7 +137,7 @@
31137
<key>com.apple.XCTPerformanceMetric_WallClockTime</key>
32138
<dict>
33139
<key>baselineAverage</key>
34-
<real>0.30716</real>
140+
<real>0.307160</real>
35141
<key>baselineIntegrationDisplayName</key>
36142
<string>Local Baseline</string>
37143
</dict>

0 commit comments

Comments
 (0)