@maccesar/titools 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS-TEMPLATE.md +173 -0
- package/README.md +867 -0
- package/agents/ti-researcher.md +108 -0
- package/bin/titools.js +53 -0
- package/lib/commands/agents.js +126 -0
- package/lib/commands/install.js +188 -0
- package/lib/commands/uninstall.js +215 -0
- package/lib/commands/update.js +159 -0
- package/lib/config.js +119 -0
- package/lib/downloader.js +153 -0
- package/lib/installer.js +253 -0
- package/lib/platform.js +108 -0
- package/lib/symlink.js +142 -0
- package/lib/utils.js +270 -0
- package/package.json +67 -0
- package/skills/alloy-expert/SKILL.md +247 -0
- package/skills/alloy-expert/assets/ControllerAutoCleanup.js +182 -0
- package/skills/alloy-expert/references/alloy-structure.md +381 -0
- package/skills/alloy-expert/references/anti-patterns.md +133 -0
- package/skills/alloy-expert/references/code-conventions.md +469 -0
- package/skills/alloy-expert/references/contracts.md +280 -0
- package/skills/alloy-expert/references/controller-patterns.md +520 -0
- package/skills/alloy-expert/references/error-handling.md +484 -0
- package/skills/alloy-expert/references/examples.md +735 -0
- package/skills/alloy-expert/references/migration-patterns.md +298 -0
- package/skills/alloy-expert/references/patterns.md +448 -0
- package/skills/alloy-expert/references/performance-patterns.md +855 -0
- package/skills/alloy-expert/references/security-patterns.md +847 -0
- package/skills/alloy-expert/references/state-management.md +779 -0
- package/skills/alloy-expert/references/testing.md +872 -0
- package/skills/alloy-guides/SKILL.md +214 -0
- package/skills/alloy-guides/references/CLI_TASKS.md +243 -0
- package/skills/alloy-guides/references/CONCEPTS.md +191 -0
- package/skills/alloy-guides/references/CONTROLLERS.md +298 -0
- package/skills/alloy-guides/references/MODELS.md +1028 -0
- package/skills/alloy-guides/references/PURGETSS.md +56 -0
- package/skills/alloy-guides/references/VIEWS_DYNAMIC.md +242 -0
- package/skills/alloy-guides/references/VIEWS_STYLES.md +388 -0
- package/skills/alloy-guides/references/VIEWS_WITHOUT_CONTROLLERS.md +109 -0
- package/skills/alloy-guides/references/VIEWS_XML.md +558 -0
- package/skills/alloy-guides/references/WIDGETS.md +176 -0
- package/skills/alloy-howtos/SKILL.md +203 -0
- package/skills/alloy-howtos/references/best_practices.md +138 -0
- package/skills/alloy-howtos/references/cli_reference.md +253 -0
- package/skills/alloy-howtos/references/config_files.md +87 -0
- package/skills/alloy-howtos/references/custom_tags.md +147 -0
- package/skills/alloy-howtos/references/debugging_troubleshooting.md +101 -0
- package/skills/alloy-howtos/references/samples.md +167 -0
- package/skills/purgetss/SKILL.md +442 -0
- package/skills/purgetss/assets/purgetss.config.cjs +17 -0
- package/skills/purgetss/references/EXAMPLES.md +247 -0
- package/skills/purgetss/references/animation-system.md +1294 -0
- package/skills/purgetss/references/apply-directive.md +375 -0
- package/skills/purgetss/references/arbitrary-values.md +612 -0
- package/skills/purgetss/references/class-index.md +1350 -0
- package/skills/purgetss/references/cli-commands.md +948 -0
- package/skills/purgetss/references/configurable-properties.md +654 -0
- package/skills/purgetss/references/custom-rules.md +161 -0
- package/skills/purgetss/references/customization-deep-dive.md +722 -0
- package/skills/purgetss/references/dynamic-component-creation.md +489 -0
- package/skills/purgetss/references/grid-layout.md +455 -0
- package/skills/purgetss/references/icon-fonts.md +609 -0
- package/skills/purgetss/references/installation-setup.md +366 -0
- package/skills/purgetss/references/opacity-modifier.md +291 -0
- package/skills/purgetss/references/platform-modifiers.md +479 -0
- package/skills/purgetss/references/smart-mappings.md +42 -0
- package/skills/purgetss/references/titanium-resets.md +359 -0
- package/skills/purgetss/references/ui-ux-design.md +1526 -0
- package/skills/ti-guides/SKILL.md +94 -0
- package/skills/ti-guides/references/advanced-data-and-images.md +19 -0
- package/skills/ti-guides/references/alloy-cli-advanced.md +84 -0
- package/skills/ti-guides/references/alloy-data-mastery.md +29 -0
- package/skills/ti-guides/references/alloy-widgets-and-themes.md +19 -0
- package/skills/ti-guides/references/android-manifest.md +97 -0
- package/skills/ti-guides/references/app-distribution.md +258 -0
- package/skills/ti-guides/references/application-frameworks.md +377 -0
- package/skills/ti-guides/references/cli-reference.md +402 -0
- package/skills/ti-guides/references/coding-best-practices.md +102 -0
- package/skills/ti-guides/references/commonjs-advanced.md +134 -0
- package/skills/ti-guides/references/hello-world.md +100 -0
- package/skills/ti-guides/references/hyperloop-native-access.md +62 -0
- package/skills/ti-guides/references/javascript-primer.md +411 -0
- package/skills/ti-guides/references/reserved-words.md +36 -0
- package/skills/ti-guides/references/resources.md +183 -0
- package/skills/ti-guides/references/style-and-conventions.md +48 -0
- package/skills/ti-guides/references/tiapp-config.md +609 -0
- package/skills/ti-howtos/SKILL.md +174 -0
- package/skills/ti-howtos/references/android-platform-deep-dives.md +658 -0
- package/skills/ti-howtos/references/automation-fastlane-appium.md +95 -0
- package/skills/ti-howtos/references/buffer-codec-streams.md +140 -0
- package/skills/ti-howtos/references/cross-platform-development.md +348 -0
- package/skills/ti-howtos/references/debugging-profiling.md +543 -0
- package/skills/ti-howtos/references/extending-titanium.md +723 -0
- package/skills/ti-howtos/references/google-maps-v2.md +169 -0
- package/skills/ti-howtos/references/ios-map-kit.md +143 -0
- package/skills/ti-howtos/references/ios-platform-deep-dives.md +783 -0
- package/skills/ti-howtos/references/local-data-sources.md +301 -0
- package/skills/ti-howtos/references/location-and-maps.md +252 -0
- package/skills/ti-howtos/references/media-apis.md +210 -0
- package/skills/ti-howtos/references/notification-services.md +599 -0
- package/skills/ti-howtos/references/remote-data-sources.md +349 -0
- package/skills/ti-howtos/references/tutorials.md +502 -0
- package/skills/ti-howtos/references/using-modules.md +237 -0
- package/skills/ti-howtos/references/web-content-integration.md +307 -0
- package/skills/ti-howtos/references/webpack-build-pipeline.md +78 -0
- package/skills/ti-ui/SKILL.md +179 -0
- package/skills/ti-ui/references/accessibility-deep-dive.md +242 -0
- package/skills/ti-ui/references/animation-and-matrices.md +599 -0
- package/skills/ti-ui/references/application-structures.md +655 -0
- package/skills/ti-ui/references/custom-fonts-styling.md +579 -0
- package/skills/ti-ui/references/event-handling.md +393 -0
- package/skills/ti-ui/references/gestures.md +473 -0
- package/skills/ti-ui/references/icons-and-splash-screens.md +409 -0
- package/skills/ti-ui/references/layouts-and-positioning.md +462 -0
- package/skills/ti-ui/references/listviews-and-performance.md +619 -0
- package/skills/ti-ui/references/orientation.md +362 -0
- package/skills/ti-ui/references/platform-ui-android.md +635 -0
- package/skills/ti-ui/references/platform-ui-ios.md +469 -0
- package/skills/ti-ui/references/scrolling-views.md +252 -0
- package/skills/ti-ui/references/tableviews.md +568 -0
|
@@ -0,0 +1,855 @@
|
|
|
1
|
+
# Performance Optimization Guide
|
|
2
|
+
|
|
3
|
+
## Critical Rules
|
|
4
|
+
|
|
5
|
+
1. **NEVER use `Ti.UI.SIZE` in ListView items** - Causes layout recalculation on every scroll
|
|
6
|
+
2. **ALWAYS use fixed heights** - Pre-calculated heights enable fast scrolling
|
|
7
|
+
3. **USE templates** - Reuse view instances instead of creating new ones
|
|
8
|
+
4. **CACHE Ti.Platform properties** - Avoid repeated bridge crossings
|
|
9
|
+
5. **USE applyProperties()** - Batch UI updates in a single call
|
|
10
|
+
|
|
11
|
+
## ListView Performance
|
|
12
|
+
|
|
13
|
+
### Optimized ListView Template
|
|
14
|
+
|
|
15
|
+
```xml
|
|
16
|
+
<!-- views/user/list.xml -->
|
|
17
|
+
<Alloy>
|
|
18
|
+
<ListView class="wh-screen">
|
|
19
|
+
<Templates>
|
|
20
|
+
<!-- FIXED HEIGHT is critical for performance -->
|
|
21
|
+
<ItemTemplate name="userTemplate" height="64">
|
|
22
|
+
<View class="horizontal h-16 w-screen">
|
|
23
|
+
<ImageView bindId="avatar" class="rounded-full-12 ml-4" />
|
|
24
|
+
<View class="vertical ml-3">
|
|
25
|
+
<Label bindId="name" class="text-base font-bold" />
|
|
26
|
+
<Label bindId="email" class="text-sm text-gray-500" />
|
|
27
|
+
</View>
|
|
28
|
+
</View>
|
|
29
|
+
</ItemTemplate>
|
|
30
|
+
</Templates>
|
|
31
|
+
|
|
32
|
+
<ListSection id="section" dataCollection="users">
|
|
33
|
+
<ListItem template="userTemplate" avatar:image="{avatar}" name:text="{name}" email:text="{email}" />
|
|
34
|
+
</ListSection>
|
|
35
|
+
</ListView>
|
|
36
|
+
</Alloy>
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
**PurgeTSS Layout Rules:**
|
|
40
|
+
- Use `horizontal`/`vertical` for layout (NOT flexbox)
|
|
41
|
+
- Use `m-*` on children for spacing (NOT `p-*` on parent)
|
|
42
|
+
- Use `wh-screen` for full width + height
|
|
43
|
+
|
|
44
|
+
### Efficient Data Binding
|
|
45
|
+
|
|
46
|
+
```javascript
|
|
47
|
+
// controllers/feed/list.js
|
|
48
|
+
function renderItems(items) {
|
|
49
|
+
// Pre-format data to avoid calculation in render
|
|
50
|
+
const listItems = items.map(item => ({
|
|
51
|
+
template: 'feedTemplate',
|
|
52
|
+
properties: {
|
|
53
|
+
itemId: item.id,
|
|
54
|
+
searchableText: `${item.title} ${item.description}`
|
|
55
|
+
},
|
|
56
|
+
title: { text: item.title },
|
|
57
|
+
description: { text: item.description },
|
|
58
|
+
timestamp: { text: formatTimestamp(item.created_at) }
|
|
59
|
+
}))
|
|
60
|
+
|
|
61
|
+
// Single update
|
|
62
|
+
$.section.items = listItems
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Timestamp formatter (cache results)
|
|
66
|
+
const timestampCache = new Map()
|
|
67
|
+
|
|
68
|
+
function formatTimestamp(timestamp) {
|
|
69
|
+
if (timestampCache.has(timestamp)) {
|
|
70
|
+
return timestampCache.get(timestamp)
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const formatted = new Date(timestamp).toLocaleString()
|
|
74
|
+
timestampCache.set(timestamp, formatted)
|
|
75
|
+
|
|
76
|
+
return formatted
|
|
77
|
+
}
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### Image Loading & Caching
|
|
81
|
+
|
|
82
|
+
```javascript
|
|
83
|
+
// lib/services/imageCache.js
|
|
84
|
+
exports.ImageCache = {
|
|
85
|
+
_cache: new Map(),
|
|
86
|
+
_loading: new Map(),
|
|
87
|
+
|
|
88
|
+
// Get image at appropriate size for list item
|
|
89
|
+
getListThumbnail(url) {
|
|
90
|
+
const cacheKey = `thumb_${url}`
|
|
91
|
+
|
|
92
|
+
// Return cached if available
|
|
93
|
+
if (this._cache.has(cacheKey)) {
|
|
94
|
+
return this._cache.get(cacheKey)
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Check if already loading
|
|
98
|
+
if (this._loading.has(cacheKey)) {
|
|
99
|
+
return this._loading.get(cacheKey)
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Load and resize
|
|
103
|
+
const promise = this._loadAndResize(url, { width: 80, height: 80 })
|
|
104
|
+
.then(resized => {
|
|
105
|
+
this._cache.set(cacheKey, resized)
|
|
106
|
+
this._loading.delete(cacheKey)
|
|
107
|
+
return resized
|
|
108
|
+
})
|
|
109
|
+
|
|
110
|
+
this._loading.set(cacheKey, promise)
|
|
111
|
+
|
|
112
|
+
return promise
|
|
113
|
+
},
|
|
114
|
+
|
|
115
|
+
async _loadAndResize(url, size) {
|
|
116
|
+
return new Promise((resolve, reject) => {
|
|
117
|
+
const imageView = Ti.UI.createImageView({
|
|
118
|
+
image: url,
|
|
119
|
+
width: size.width,
|
|
120
|
+
height: size.height,
|
|
121
|
+
preventsDefaultAnimation: true
|
|
122
|
+
})
|
|
123
|
+
|
|
124
|
+
imageView.addEventListener('load', () => {
|
|
125
|
+
const blob = imageView.toImage()
|
|
126
|
+
const resized = blob.imageAsResized(size.width, size.height)
|
|
127
|
+
imageView.image = null
|
|
128
|
+
resolve(resized)
|
|
129
|
+
})
|
|
130
|
+
|
|
131
|
+
imageView.addEventListener('error', (e) => {
|
|
132
|
+
reject(e)
|
|
133
|
+
})
|
|
134
|
+
})
|
|
135
|
+
},
|
|
136
|
+
|
|
137
|
+
clear() {
|
|
138
|
+
this._cache.clear()
|
|
139
|
+
this._loading.clear()
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
## Bridge Optimization
|
|
145
|
+
|
|
146
|
+
### Minimize Bridge Crossings
|
|
147
|
+
|
|
148
|
+
Every JavaScript → Native call crosses a bridge. Minimize these:
|
|
149
|
+
|
|
150
|
+
```javascript
|
|
151
|
+
// BAD: Multiple bridge crossings
|
|
152
|
+
const width = Ti.Platform.displayCaps.platformWidth
|
|
153
|
+
const height = Ti.Platform.displayCaps.platformHeight
|
|
154
|
+
const dpi = Ti.Platform.displayCaps.dpi
|
|
155
|
+
|
|
156
|
+
// GOOD: Cache properties locally
|
|
157
|
+
const displayCaps = Ti.Platform.displayCaps
|
|
158
|
+
const { platformWidth, platformHeight, dpi } = displayCaps
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
### Batch UI Updates
|
|
162
|
+
|
|
163
|
+
```javascript
|
|
164
|
+
// BAD: Multiple bridge crossings
|
|
165
|
+
$.nameLabel.text = user.name
|
|
166
|
+
$.nameLabel.color = '#000'
|
|
167
|
+
$.nameLabel.font = { fontSize: 16 }
|
|
168
|
+
$.nameLabel.left = 10
|
|
169
|
+
|
|
170
|
+
// GOOD: Single applyProperties call
|
|
171
|
+
$.nameLabel.applyProperties({
|
|
172
|
+
text: user.name,
|
|
173
|
+
color: '#000',
|
|
174
|
+
font: { fontSize: 16 },
|
|
175
|
+
left: 10
|
|
176
|
+
})
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
### Use PurgeTSS Classes
|
|
180
|
+
|
|
181
|
+
```xml
|
|
182
|
+
<!-- BAD: Inline styling = more bridge crossings -->
|
|
183
|
+
<Label text="Hello" width="200" height="40" color="#000" font="{fontSize:16}" />
|
|
184
|
+
|
|
185
|
+
<!-- GOOD: Single class application -->
|
|
186
|
+
<Label text="Hello" class="h-10 w-1/2 text-base text-black" />
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
## Memory Management
|
|
190
|
+
|
|
191
|
+
### Controller Cleanup Pattern
|
|
192
|
+
|
|
193
|
+
```javascript
|
|
194
|
+
// controllers/detail.js
|
|
195
|
+
// Store references for cleanup
|
|
196
|
+
const _listeners = []
|
|
197
|
+
const _intervals = []
|
|
198
|
+
const _timers = []
|
|
199
|
+
|
|
200
|
+
function init() {
|
|
201
|
+
// Store listener reference
|
|
202
|
+
const updateHandler = onUpdate.bind(this)
|
|
203
|
+
Ti.App.addEventListener('app:update', updateHandler)
|
|
204
|
+
_listeners.push({ type: 'Ti.App', event: 'app:update', handler: updateHandler })
|
|
205
|
+
|
|
206
|
+
// Store interval reference
|
|
207
|
+
const intervalId = setInterval(pollData, 30000)
|
|
208
|
+
_intervals.push(intervalId)
|
|
209
|
+
|
|
210
|
+
// Store timer reference
|
|
211
|
+
const timerId = setTimeout(showTimeout, 5000)
|
|
212
|
+
_timers.push(timerId)
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
function cleanup() {
|
|
216
|
+
// Remove all listeners
|
|
217
|
+
_listeners.forEach(({ type, event, handler }) => {
|
|
218
|
+
if (type === 'Ti.App') {
|
|
219
|
+
Ti.App.removeEventListener(event, handler)
|
|
220
|
+
} else {
|
|
221
|
+
$.getView().removeEventListener(event, handler)
|
|
222
|
+
}
|
|
223
|
+
})
|
|
224
|
+
|
|
225
|
+
// Clear all intervals
|
|
226
|
+
_intervals.forEach(clearInterval)
|
|
227
|
+
|
|
228
|
+
// Clear all timers
|
|
229
|
+
_timers.forEach(clearTimeout)
|
|
230
|
+
|
|
231
|
+
// Null heavy objects
|
|
232
|
+
this._largeData = null
|
|
233
|
+
this._cachedImages = null
|
|
234
|
+
|
|
235
|
+
// Destroy Alloy bindings
|
|
236
|
+
$.destroy()
|
|
237
|
+
|
|
238
|
+
// Log cleanup
|
|
239
|
+
console.log('Detail controller cleaned up')
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
$.cleanup = cleanup
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
### Image Memory Management
|
|
246
|
+
|
|
247
|
+
```javascript
|
|
248
|
+
// lib/services/imageManager.js
|
|
249
|
+
exports.ImageManager = {
|
|
250
|
+
// Resize images to avoid loading full-resolution into memory
|
|
251
|
+
resizeForDisplay(imageUrl, maxWidth, maxHeight) {
|
|
252
|
+
const imageView = Ti.UI.createImageView({
|
|
253
|
+
image: imageUrl,
|
|
254
|
+
width: Ti.UI.SIZE,
|
|
255
|
+
height: Ti.UI.SIZE
|
|
256
|
+
})
|
|
257
|
+
|
|
258
|
+
// Get actual size
|
|
259
|
+
const blob = imageView.toImage()
|
|
260
|
+
|
|
261
|
+
// Calculate aspect ratio
|
|
262
|
+
const aspect = blob.width / blob.height
|
|
263
|
+
let newWidth = maxWidth
|
|
264
|
+
let newHeight = maxWidth / aspect
|
|
265
|
+
|
|
266
|
+
if (newHeight > maxHeight) {
|
|
267
|
+
newHeight = maxHeight
|
|
268
|
+
newWidth = maxHeight * aspect
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// Resize to reduce memory footprint
|
|
272
|
+
return blob.imageAsResized(newWidth, newHeight)
|
|
273
|
+
},
|
|
274
|
+
|
|
275
|
+
// Release image memory when done
|
|
276
|
+
release(imageView) {
|
|
277
|
+
if (!imageView) return
|
|
278
|
+
|
|
279
|
+
// Release image memory
|
|
280
|
+
if (imageView.image && imageView.image.release) {
|
|
281
|
+
imageView.image.release()
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
imageView.image = null
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
## Lazy Loading Pattern
|
|
290
|
+
|
|
291
|
+
```javascript
|
|
292
|
+
// controllers/feed/list.js
|
|
293
|
+
const PAGE_SIZE = 20
|
|
294
|
+
let currentPage = 1
|
|
295
|
+
let isLoading = false
|
|
296
|
+
let hasMore = true
|
|
297
|
+
|
|
298
|
+
function init() {
|
|
299
|
+
loadPage(1)
|
|
300
|
+
|
|
301
|
+
// Detect near end of list
|
|
302
|
+
$.listView.addEventListener('marker', onMarker)
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
function onMarker(e) {
|
|
306
|
+
if (!hasMore || isLoading) return
|
|
307
|
+
|
|
308
|
+
const totalItems = $.section.items?.length || 0
|
|
309
|
+
|
|
310
|
+
// Load more when 5 items from end
|
|
311
|
+
if (e.itemIndex >= totalItems - 5) {
|
|
312
|
+
loadNextPage()
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
async function loadNextPage() {
|
|
317
|
+
if (isLoading || !hasMore) return
|
|
318
|
+
|
|
319
|
+
isLoading = true
|
|
320
|
+
|
|
321
|
+
try {
|
|
322
|
+
const items = await api.getFeedPage(++currentPage, PAGE_SIZE)
|
|
323
|
+
|
|
324
|
+
if (items.length < PAGE_SIZE) {
|
|
325
|
+
hasMore = false
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
$.section.appendItems(items)
|
|
329
|
+
|
|
330
|
+
} catch (error) {
|
|
331
|
+
console.error('Failed to load more items', error)
|
|
332
|
+
currentPage-- // Retry same page
|
|
333
|
+
} finally {
|
|
334
|
+
isLoading = false
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
function cleanup() {
|
|
339
|
+
$.listView.removeEventListener('marker', onMarker)
|
|
340
|
+
$.destroy()
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
$.cleanup = cleanup
|
|
344
|
+
```
|
|
345
|
+
|
|
346
|
+
## Database Performance
|
|
347
|
+
|
|
348
|
+
```javascript
|
|
349
|
+
// lib/services/database.js
|
|
350
|
+
exports.DB = {
|
|
351
|
+
// Use transactions for multiple writes
|
|
352
|
+
batchInsert(items) {
|
|
353
|
+
const db = Ti.Database.open('mydb')
|
|
354
|
+
|
|
355
|
+
try {
|
|
356
|
+
db.begin_transaction()
|
|
357
|
+
|
|
358
|
+
items.forEach(item => {
|
|
359
|
+
db.execute(
|
|
360
|
+
'INSERT INTO items (name, value, created_at) VALUES (?, ?, ?)',
|
|
361
|
+
item.name,
|
|
362
|
+
item.value,
|
|
363
|
+
Date.now()
|
|
364
|
+
)
|
|
365
|
+
})
|
|
366
|
+
|
|
367
|
+
db.commit()
|
|
368
|
+
|
|
369
|
+
console.log(`Inserted ${items.length} items`)
|
|
370
|
+
|
|
371
|
+
} catch (e) {
|
|
372
|
+
db.rollback()
|
|
373
|
+
console.error('Batch insert failed', e)
|
|
374
|
+
throw e
|
|
375
|
+
|
|
376
|
+
} finally {
|
|
377
|
+
db.close()
|
|
378
|
+
}
|
|
379
|
+
},
|
|
380
|
+
|
|
381
|
+
// Use prepared statements for repeated queries
|
|
382
|
+
findUsersByName(namePattern) {
|
|
383
|
+
const db = Ti.Database.open('mydb')
|
|
384
|
+
|
|
385
|
+
try {
|
|
386
|
+
const rows = db.execute(
|
|
387
|
+
'SELECT id, name, email FROM users WHERE name LIKE ?',
|
|
388
|
+
`%${namePattern}%`
|
|
389
|
+
)
|
|
390
|
+
|
|
391
|
+
const users = []
|
|
392
|
+
|
|
393
|
+
while (rows.isValidRow()) {
|
|
394
|
+
users.push({
|
|
395
|
+
id: rows.fieldByName('id'),
|
|
396
|
+
name: rows.fieldByName('name'),
|
|
397
|
+
email: rows.fieldByName('email')
|
|
398
|
+
})
|
|
399
|
+
rows.next()
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
rows.close()
|
|
403
|
+
|
|
404
|
+
return users
|
|
405
|
+
|
|
406
|
+
} finally {
|
|
407
|
+
db.close()
|
|
408
|
+
}
|
|
409
|
+
},
|
|
410
|
+
|
|
411
|
+
// Create indexes for frequently queried columns
|
|
412
|
+
createIndexes() {
|
|
413
|
+
const db = Ti.Database.open('mydb')
|
|
414
|
+
|
|
415
|
+
db.execute('CREATE INDEX IF NOT EXISTS idx_items_name ON items(name)')
|
|
416
|
+
db.execute('CREATE INDEX IF NOT EXISTS idx_items_date ON items(created_at)')
|
|
417
|
+
|
|
418
|
+
db.close()
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
```
|
|
422
|
+
|
|
423
|
+
## Performance Monitoring
|
|
424
|
+
|
|
425
|
+
```javascript
|
|
426
|
+
// lib/services/perfMonitor.js
|
|
427
|
+
exports.Perf = {
|
|
428
|
+
_metrics: {
|
|
429
|
+
renders: [],
|
|
430
|
+
bridgeCrossings: 0
|
|
431
|
+
},
|
|
432
|
+
|
|
433
|
+
start(label) {
|
|
434
|
+
const start = Date.now()
|
|
435
|
+
|
|
436
|
+
return {
|
|
437
|
+
label,
|
|
438
|
+
start,
|
|
439
|
+
|
|
440
|
+
end() {
|
|
441
|
+
const duration = Date.now() - this.start
|
|
442
|
+
Perf._metrics.renders.push({ label: this.label, duration })
|
|
443
|
+
|
|
444
|
+
console.log(`[PERF] ${this.label}: ${duration}ms`)
|
|
445
|
+
|
|
446
|
+
if (duration > 100) {
|
|
447
|
+
console.warn(`[PERF] SLOW: ${this.label} took ${duration}ms`)
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
},
|
|
452
|
+
|
|
453
|
+
countBridgeCall() {
|
|
454
|
+
this._metrics.bridgeCrossings++
|
|
455
|
+
},
|
|
456
|
+
|
|
457
|
+
logMemoryUsage() {
|
|
458
|
+
const available = Ti.Platform.availableMemory
|
|
459
|
+
|
|
460
|
+
console.log(`[MEMORY] Available: ${available}MB`)
|
|
461
|
+
},
|
|
462
|
+
|
|
463
|
+
getReport() {
|
|
464
|
+
const avgRender = this._metrics.renders.reduce((sum, m) =>
|
|
465
|
+
sum + m.duration, 0
|
|
466
|
+
) / (this._metrics.renders.length || 1)
|
|
467
|
+
|
|
468
|
+
return {
|
|
469
|
+
averageRenderTime: Math.round(avgRender),
|
|
470
|
+
totalBridgeCalls: this._metrics.bridgeCrossings,
|
|
471
|
+
slowOperations: this._metrics.renders.filter(m => m.duration > 100)
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
// Usage example
|
|
477
|
+
const measure = Perf.start('user_list_render')
|
|
478
|
+
renderUsers(users)
|
|
479
|
+
measure.end()
|
|
480
|
+
```
|
|
481
|
+
|
|
482
|
+
## Performance Checklist
|
|
483
|
+
|
|
484
|
+
| Area | Check |
|
|
485
|
+
| ------------ | ----------------------------------------- |
|
|
486
|
+
| **ListView** | Fixed heights on all templates |
|
|
487
|
+
| **ListView** | Using templates, not dynamic views |
|
|
488
|
+
| **ListView** | Image pre-sizing and caching |
|
|
489
|
+
| **Bridge** | Cached Ti.Platform properties |
|
|
490
|
+
| **Bridge** | Using applyProperties for updates |
|
|
491
|
+
| **Bridge** | PurgeTSS classes instead of inline styles |
|
|
492
|
+
| **Memory** | All global listeners cleaned up |
|
|
493
|
+
| **Memory** | Heavy objects nulled in cleanup |
|
|
494
|
+
| **Memory** | Images resized appropriately |
|
|
495
|
+
| **Database** | Using transactions for batch ops |
|
|
496
|
+
| **Database** | Indexes on frequently queried columns |
|
|
497
|
+
| **Database** | ResultSets and DB handles closed |
|
|
498
|
+
|
|
499
|
+
## ScrollView Performance
|
|
500
|
+
|
|
501
|
+
### Optimizing Large ScrollViews
|
|
502
|
+
|
|
503
|
+
```xml
|
|
504
|
+
<!-- Avoid: Creating many views at once -->
|
|
505
|
+
<ScrollView class="wh-screen vertical">
|
|
506
|
+
<!-- DON'T: 100+ views created immediately -->
|
|
507
|
+
</ScrollView>
|
|
508
|
+
|
|
509
|
+
<!-- Better: Use ListView for list-like content -->
|
|
510
|
+
<ListView class="wh-screen">
|
|
511
|
+
<!-- Views created on-demand as user scrolls -->
|
|
512
|
+
</ListView>
|
|
513
|
+
```
|
|
514
|
+
|
|
515
|
+
### When You Must Use ScrollView
|
|
516
|
+
|
|
517
|
+
```javascript
|
|
518
|
+
// Lazy load content sections
|
|
519
|
+
const sections = [
|
|
520
|
+
{ id: 'header', height: 200 },
|
|
521
|
+
{ id: 'featured', height: 300 },
|
|
522
|
+
{ id: 'products', height: 400 },
|
|
523
|
+
{ id: 'reviews', height: 500 }
|
|
524
|
+
]
|
|
525
|
+
|
|
526
|
+
let loadedSections = new Set()
|
|
527
|
+
|
|
528
|
+
function init() {
|
|
529
|
+
// Load only visible sections initially
|
|
530
|
+
loadSection('header')
|
|
531
|
+
|
|
532
|
+
$.scrollView.addEventListener('scroll', onScroll)
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
function onScroll(e) {
|
|
536
|
+
const scrollY = e.y
|
|
537
|
+
const viewportHeight = $.scrollView.rect.height
|
|
538
|
+
|
|
539
|
+
sections.forEach(section => {
|
|
540
|
+
if (loadedSections.has(section.id)) return
|
|
541
|
+
|
|
542
|
+
// Check if section is about to be visible
|
|
543
|
+
const sectionTop = getSectionTop(section.id)
|
|
544
|
+
|
|
545
|
+
if (sectionTop < scrollY + viewportHeight + 100) {
|
|
546
|
+
loadSection(section.id)
|
|
547
|
+
}
|
|
548
|
+
})
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
function loadSection(sectionId) {
|
|
552
|
+
if (loadedSections.has(sectionId)) return
|
|
553
|
+
|
|
554
|
+
loadedSections.add(sectionId)
|
|
555
|
+
|
|
556
|
+
// Load content for this section
|
|
557
|
+
const container = $[sectionId + 'Container']
|
|
558
|
+
const content = createSectionContent(sectionId)
|
|
559
|
+
container.add(content)
|
|
560
|
+
}
|
|
561
|
+
```
|
|
562
|
+
|
|
563
|
+
### ScrollView Memory Management
|
|
564
|
+
|
|
565
|
+
```javascript
|
|
566
|
+
// Release images when scrolled far away
|
|
567
|
+
function onScroll(e) {
|
|
568
|
+
const scrollY = e.y
|
|
569
|
+
const viewportHeight = $.scrollView.rect.height
|
|
570
|
+
|
|
571
|
+
// Release images more than 2 screens away
|
|
572
|
+
const releaseThreshold = viewportHeight * 2
|
|
573
|
+
|
|
574
|
+
imageViews.forEach((img, index) => {
|
|
575
|
+
const imgTop = img.rect.y
|
|
576
|
+
const distance = Math.abs(imgTop - scrollY)
|
|
577
|
+
|
|
578
|
+
if (distance > releaseThreshold && img.image) {
|
|
579
|
+
// Store URL for later reload
|
|
580
|
+
img._originalUrl = img.image
|
|
581
|
+
img.image = null
|
|
582
|
+
} else if (distance < viewportHeight && img._originalUrl) {
|
|
583
|
+
// Reload when close to viewport
|
|
584
|
+
img.image = img._originalUrl
|
|
585
|
+
delete img._originalUrl
|
|
586
|
+
}
|
|
587
|
+
})
|
|
588
|
+
}
|
|
589
|
+
```
|
|
590
|
+
|
|
591
|
+
## Animation Performance
|
|
592
|
+
|
|
593
|
+
### 60fps Animation Rules
|
|
594
|
+
|
|
595
|
+
```javascript
|
|
596
|
+
// Rule 1: Use native animations (not JavaScript intervals)
|
|
597
|
+
// BAD: JavaScript-driven animation
|
|
598
|
+
let x = 0
|
|
599
|
+
setInterval(() => {
|
|
600
|
+
x += 1
|
|
601
|
+
$.view.left = x // 60 bridge crossings per second!
|
|
602
|
+
}, 16)
|
|
603
|
+
|
|
604
|
+
// GOOD: Native animation
|
|
605
|
+
const animation = Ti.UI.createAnimation({
|
|
606
|
+
left: 100,
|
|
607
|
+
duration: 500,
|
|
608
|
+
curve: Ti.UI.ANIMATION_CURVE_EASE_OUT
|
|
609
|
+
})
|
|
610
|
+
$.view.animate(animation)
|
|
611
|
+
```
|
|
612
|
+
|
|
613
|
+
### PurgeTSS Animation Component (Recommended)
|
|
614
|
+
|
|
615
|
+
```xml
|
|
616
|
+
<!-- Define animations with state modifiers -->
|
|
617
|
+
<Animation id="fadeIn" module="purgetss.ui" class="close:opacity-0 duration-300 open:opacity-100" />
|
|
618
|
+
<Animation id="fadeOut" module="purgetss.ui" class="close:opacity-100 duration-300 open:opacity-0" />
|
|
619
|
+
<Animation id="slideInRight" module="purgetss.ui" class="close:translate-x-full duration-300 open:translate-x-0" />
|
|
620
|
+
<Animation id="scalePress" module="purgetss.ui" class="close:scale-100 duration-150 open:scale-95" />
|
|
621
|
+
```
|
|
622
|
+
|
|
623
|
+
```javascript
|
|
624
|
+
// Fade in/out
|
|
625
|
+
$.fadeIn.open($.card)
|
|
626
|
+
$.fadeOut.close($.card)
|
|
627
|
+
|
|
628
|
+
// Slide animations
|
|
629
|
+
$.slideInRight.open($.panel)
|
|
630
|
+
$.slideInRight.close($.panel)
|
|
631
|
+
|
|
632
|
+
// Scale animations (press effect)
|
|
633
|
+
$.scalePress.open($.button) // Scale down
|
|
634
|
+
$.scalePress.close($.button) // Scale back up
|
|
635
|
+
|
|
636
|
+
// Chained animations with callback
|
|
637
|
+
$.fadeIn.open($.modal, () => {
|
|
638
|
+
// Animation complete, trigger next animation
|
|
639
|
+
$.slideInRight.open($.content)
|
|
640
|
+
})
|
|
641
|
+
```
|
|
642
|
+
|
|
643
|
+
### Hardware-Accelerated Properties
|
|
644
|
+
|
|
645
|
+
```javascript
|
|
646
|
+
// These properties are GPU-accelerated (fast):
|
|
647
|
+
// - opacity
|
|
648
|
+
// - transform (translate, scale, rotate)
|
|
649
|
+
|
|
650
|
+
// These trigger layout recalculation (slow):
|
|
651
|
+
// - width, height
|
|
652
|
+
// - top, left, right, bottom
|
|
653
|
+
// - visible
|
|
654
|
+
|
|
655
|
+
// GOOD: Animate opacity for fade effects
|
|
656
|
+
const fadeOut = Ti.UI.createAnimation({
|
|
657
|
+
opacity: 0,
|
|
658
|
+
duration: 200
|
|
659
|
+
})
|
|
660
|
+
|
|
661
|
+
// GOOD: Use transform for movement
|
|
662
|
+
const slideRight = Ti.UI.createAnimation({
|
|
663
|
+
duration: 300,
|
|
664
|
+
transform: Ti.UI.createMatrix2D().translate(100, 0),
|
|
665
|
+
})
|
|
666
|
+
|
|
667
|
+
// AVOID: Animating layout properties
|
|
668
|
+
const badAnimation = Ti.UI.createAnimation({
|
|
669
|
+
left: 100, // Triggers layout recalc
|
|
670
|
+
width: 200, // Triggers layout recalc
|
|
671
|
+
duration: 300
|
|
672
|
+
})
|
|
673
|
+
```
|
|
674
|
+
|
|
675
|
+
### Animation Cleanup
|
|
676
|
+
|
|
677
|
+
```javascript
|
|
678
|
+
// Always remove animation listeners
|
|
679
|
+
let currentAnimation = null
|
|
680
|
+
|
|
681
|
+
function animateIn() {
|
|
682
|
+
currentAnimation = Ti.UI.createAnimation({
|
|
683
|
+
opacity: 1,
|
|
684
|
+
duration: 300
|
|
685
|
+
})
|
|
686
|
+
|
|
687
|
+
const onComplete = () => {
|
|
688
|
+
currentAnimation.removeEventListener('complete', onComplete)
|
|
689
|
+
currentAnimation = null
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
currentAnimation.addEventListener('complete', onComplete)
|
|
693
|
+
$.view.animate(currentAnimation)
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
function cleanup() {
|
|
697
|
+
// Cancel any running animation
|
|
698
|
+
if (currentAnimation) {
|
|
699
|
+
$.view.animate({ duration: 0 }) // Cancel
|
|
700
|
+
currentAnimation = null
|
|
701
|
+
}
|
|
702
|
+
$.destroy()
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
$.cleanup = cleanup
|
|
706
|
+
```
|
|
707
|
+
|
|
708
|
+
## Debouncing and Throttling
|
|
709
|
+
|
|
710
|
+
### Debounce Pattern
|
|
711
|
+
|
|
712
|
+
Use when you want to wait for the user to stop an action before processing.
|
|
713
|
+
|
|
714
|
+
```javascript
|
|
715
|
+
// lib/helpers/timing.js
|
|
716
|
+
exports.debounce = function debounce(fn, delay = 300) {
|
|
717
|
+
let timeoutId = null
|
|
718
|
+
|
|
719
|
+
const debounced = function(...args) {
|
|
720
|
+
clearTimeout(timeoutId)
|
|
721
|
+
timeoutId = setTimeout(() => {
|
|
722
|
+
fn.apply(this, args)
|
|
723
|
+
}, delay)
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
debounced.cancel = () => {
|
|
727
|
+
clearTimeout(timeoutId)
|
|
728
|
+
timeoutId = null
|
|
729
|
+
}
|
|
730
|
+
|
|
731
|
+
return debounced
|
|
732
|
+
}
|
|
733
|
+
```
|
|
734
|
+
|
|
735
|
+
```javascript
|
|
736
|
+
// Usage: Search input
|
|
737
|
+
const { debounce } = require('lib/helpers/timing')
|
|
738
|
+
|
|
739
|
+
const debouncedSearch = debounce(async (query) => {
|
|
740
|
+
const results = await searchService.search(query)
|
|
741
|
+
renderResults(results)
|
|
742
|
+
}, 300)
|
|
743
|
+
|
|
744
|
+
function onSearchChange(e) {
|
|
745
|
+
debouncedSearch(e.value)
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
function cleanup() {
|
|
749
|
+
debouncedSearch.cancel()
|
|
750
|
+
$.destroy()
|
|
751
|
+
}
|
|
752
|
+
```
|
|
753
|
+
|
|
754
|
+
### Throttle Pattern
|
|
755
|
+
|
|
756
|
+
Use when you want to limit how often a function can run.
|
|
757
|
+
|
|
758
|
+
```javascript
|
|
759
|
+
// lib/helpers/timing.js
|
|
760
|
+
exports.throttle = function throttle(fn, limit = 100) {
|
|
761
|
+
let lastRun = 0
|
|
762
|
+
let timeoutId = null
|
|
763
|
+
|
|
764
|
+
const throttled = function(...args) {
|
|
765
|
+
const now = Date.now()
|
|
766
|
+
const remaining = limit - (now - lastRun)
|
|
767
|
+
|
|
768
|
+
if (remaining <= 0) {
|
|
769
|
+
lastRun = now
|
|
770
|
+
fn.apply(this, args)
|
|
771
|
+
} else if (!timeoutId) {
|
|
772
|
+
timeoutId = setTimeout(() => {
|
|
773
|
+
lastRun = Date.now()
|
|
774
|
+
timeoutId = null
|
|
775
|
+
fn.apply(this, args)
|
|
776
|
+
}, remaining)
|
|
777
|
+
}
|
|
778
|
+
}
|
|
779
|
+
|
|
780
|
+
throttled.cancel = () => {
|
|
781
|
+
clearTimeout(timeoutId)
|
|
782
|
+
timeoutId = null
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
return throttled
|
|
786
|
+
}
|
|
787
|
+
```
|
|
788
|
+
|
|
789
|
+
```javascript
|
|
790
|
+
// Usage: Scroll handler
|
|
791
|
+
const { throttle } = require('lib/helpers/timing')
|
|
792
|
+
|
|
793
|
+
const throttledScroll = throttle((scrollY) => {
|
|
794
|
+
updateHeaderOpacity(scrollY)
|
|
795
|
+
checkLazyLoadImages(scrollY)
|
|
796
|
+
}, 50) // Max 20 calls per second
|
|
797
|
+
|
|
798
|
+
function onScroll(e) {
|
|
799
|
+
throttledScroll(e.y)
|
|
800
|
+
}
|
|
801
|
+
|
|
802
|
+
function cleanup() {
|
|
803
|
+
throttledScroll.cancel()
|
|
804
|
+
$.scrollView.removeEventListener('scroll', onScroll)
|
|
805
|
+
$.destroy()
|
|
806
|
+
}
|
|
807
|
+
```
|
|
808
|
+
|
|
809
|
+
### Common Use Cases
|
|
810
|
+
|
|
811
|
+
| Pattern | Use Case | Delay |
|
|
812
|
+
| -------- | ---------------- | ------------ |
|
|
813
|
+
| Debounce | Search input | 300ms |
|
|
814
|
+
| Debounce | Auto-save | 1000ms |
|
|
815
|
+
| Debounce | Window resize | 150ms |
|
|
816
|
+
| Throttle | Scroll events | 50-100ms |
|
|
817
|
+
| Throttle | Mouse/touch move | 16ms (60fps) |
|
|
818
|
+
| Throttle | API polling | 5000ms+ |
|
|
819
|
+
|
|
820
|
+
### Combined Pattern for Real-time + Final
|
|
821
|
+
|
|
822
|
+
```javascript
|
|
823
|
+
// Show immediate feedback while typing, but only search when done
|
|
824
|
+
const { debounce, throttle } = require('lib/helpers/timing')
|
|
825
|
+
|
|
826
|
+
// Update UI immediately (throttled)
|
|
827
|
+
const updateSuggestions = throttle((query) => {
|
|
828
|
+
// Filter local cache for quick suggestions
|
|
829
|
+
const suggestions = localCache.filter(q => q.includes(query))
|
|
830
|
+
renderSuggestions(suggestions)
|
|
831
|
+
}, 100)
|
|
832
|
+
|
|
833
|
+
// Search API only when typing stops (debounced)
|
|
834
|
+
const searchApi = debounce(async (query) => {
|
|
835
|
+
const results = await api.search(query)
|
|
836
|
+
renderResults(results)
|
|
837
|
+
}, 500)
|
|
838
|
+
|
|
839
|
+
function onSearchChange(e) {
|
|
840
|
+
const query = e.value.trim()
|
|
841
|
+
|
|
842
|
+
if (query.length > 0) {
|
|
843
|
+
updateSuggestions(query) // Immediate feedback
|
|
844
|
+
searchApi(query) // API call when done typing
|
|
845
|
+
} else {
|
|
846
|
+
clearResults()
|
|
847
|
+
}
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
function cleanup() {
|
|
851
|
+
updateSuggestions.cancel()
|
|
852
|
+
searchApi.cancel()
|
|
853
|
+
$.destroy()
|
|
854
|
+
}
|
|
855
|
+
```
|