The C and C++ standards leave nearly every detail to theimplementation. C23 §6.7.3.2:
An implementation may allocate any addressable storage unit largeenough to hold a bit-field. If enough space remains, a bit-field thatimmediately follows another bit-field in a structure shall be packedinto adjacent bits of the same unit. If insufficient space remains,whether a bit-field that does not fit is put into the next unit oroverlaps adjacent units is implementation-defined. The order ofallocation of bit-fields within a unit (high-order to low-order orlow-order to high-order) is implementation-defined. The alignment of theaddressable storage unit is unspecified
C++ is also terse — [class.bit]p1:
Allocation of bit-fields within a class object isimplementation-defined. Alignment of bit-fields isimplementation-defined. Bit-fields are packed into some addressableallocation unit.
The actual rules come from the platform ABI:
Itanium ABI — used on Linux, macOS, BSD, and mostnon-Windows platforms. The Itanium C++ ABI (section2.4) defers bit-field placement to "the base C ABI" but adds its ownconstraints (notably: bit-fields are never placed in the tail padding ofa base class).
System V ABI Processor Supplement. The x86-64 psABI says littleabout bit-fields, while the AArch64AAPCS has a more detailed description.
Microsoft ABI — used on Windows (MSVC). In GCC andClang, structs with the ms_struct attribute also mimicsthis ABI.
Clang implements both ABIs inclang/lib/AST/RecordLayoutBuilder.cpp. It processesbit-fields in two distinct phases:
Layout (storage units) — assign a bit offset toevery bit-field. This is ABI-specified and determinessizeof and alignof.
Codegen (access units) — choose what LLVM IR loadsand stores to emit. This is a compiler optimization that affectsgenerated code but not the ABI.
Understanding these separately is the key to understandingbit-fields. This article focuses on Itanium (the default on mostplatforms), with a section on how the Microsoft ABI differs.
Phase 1: Storage Units
In clang/lib/AST/RecordLayoutBuilder.cpp,ItaniumRecordLayoutBuilder::LayoutFields lays out fields ofa RecordDecl. For each bit field, it callsLayoutBitField to determine the storage unit and bitoffset.
A storage unit is a region of sizeof(T)bytes, by default aligned to alignof(T). For anint bit-field, that's a 4-byte region at a 4-byte-alignedoffset. The alignment can be reduced by the packedattribute and #pragma pack.
StorageUnitSize = sizeof(T) * 8 — the unit's size inbits
FieldAlign = alignof(T) in bits — the unit's alignment(before modifiers)
FieldOffset — the first bit after the lastbit-field
Compute where FieldOffset falls within its alignedstorage unit. If the remaining space is less thanFieldSize, round up to the next aligned boundary.Otherwise, pack the bit-field at the current position.
Declared Type Matters
Consider two structs that store the same total number of bits (7 + 7+ 2 = 16) but use different declared types:
a at bit 0. Position = 0, 14 fits in 32. Offset= 0.
b at bit 14. Position = 14, 14 + 10 = 24 <= 32.Fits. Offset = 14. Bits 24–31 are padding (unfilledtail of the first storage unit).
c at bit 24. Position = 24, 24 + 30 = 54 > 32.Doesn't fit. New unit at bit 32. Offset = 32. Bits62–63 are padding (unfilled tail of the second storage unit).
sizeof(S1) = 8, alignof(S1) = 4.
Note: Phase 1 uses two int storage units, but Phase 2 isfree to merge a, b, and c into asingle i64 access unit (since there are no non-bitfieldbarriers and 8 bytes fits in a register). On x86_64, the LLVM type endsup as { i64 }.
Mixed Types
When bit-fields have different declared types, the storage unit sizechanges:
1
structS2 {int a:24; short b:8; }; // sizeof = 4
a is int (StorageUnitSize = 32). Placed atbit 0.
b is short (StorageUnitSize = 16,FieldAlign = 16). Current offset = 24. Position within a 16-bit alignedunit: 24 % 16 = 8. 8 + 8 = 16 <= 16. Fits. Offset =24.
sizeof(S2) = 4. The short bit-fieldoverlaps into the int's storage unit. Under Itanium,storage units of different types can share bytes.
The short can also reuse space left by a smallerbit-field:
1
structS2b {int a:16; short b:8; }; // sizeof = 4
a is int (StorageUnitSize = 32). Placed atbit 0.
b is short (StorageUnitSize = 16,FieldAlign = 16). Current offset = 16. Position within a 16-bit alignedunit: 16 % 16 = 0. 0 + 8 = 8 <= 16. Fits. Offset =16.
Here b's 16-bit storage unit (bits 16–31) falls entirelywithin a's 32-bit storage unit.
Under Microsoft ABI, sizeof is 8: the type size changefrom int to short forces a new storageunit.
This overlapping extends to non-bit-field members too. Anon-bit-field can be allocated within the unfilled bytes of a precedingbit-field's storage unit:
first is uint16_t:8. Placed at bit 0. Uses8 bits of a 16-bit storage unit (bytes 0–1).
second is a non-bit-field uint8_t. Thebitfield state resets, but DataSize is only 1 byte. second(alignment 1) goes at byte 1 (bit 8) — insidefirst's storage unit.
Note that this overlapping means a write to first viaits access unit could touch byte 1 where second lives.Phase 2 must ensure the access units don't clobber each other (see Hard constraints).
Under Microsoft ABI, sizeof is 4: firstgets a full uint16_t unit (2 bytes), andsecond starts at byte 2 instead of byte 1.
Non-bitfield After Bitfield
When a non-bitfield field cannot fit within the remaining bytes, itresets the bitfield state and unfilled bits become padding:
1
structS3 {int a:10; int b:6; char c; int d:6; }; // sizeof = 4
a at bit 0, b at bit 10 — both fit in thefirst int storage unit. a + b occupy 16 bits =2 bytes, leaving 16 bits unused in the 32-bit storage unit.
c is not a bit-field. It resetsUnfilledBitsInLastUnit to 0. c (achar, alignment 1) goes at byte 2 (bit16). A subsequent bit-field could have used bits 16–31, but thenon-bit-field c claims byte 2.
d is a new int bit-field. Current bitoffset = 24 (byte 3). Position = 24 % 32 = 24. 24 + 6 = 30 <= 32.Fits. Offset = 24.
sizeof(S3) = 4.
Under Microsoft ABI, sizeof is 12:a+b get a full int unit (4bytes), c starts at byte 4, and d gets a newint unit at byte 8.
Bitfield After Non-bitfield
The overlap works in the other direction too. When a bit-fieldfollows a non-bit-field, its storage unit can encompass the precedingbytes:
b's 4-byte int storage unit (bytes 0–3)encompasses a at byte 0. No padding is inserted — the corerule only cares whether the field fits within an aligned unit, notwhether that unit overlaps earlier non-bit-field storage.
Under Microsoft ABI, sizeof is 8: b'sint unit starts at byte 4, after a is paddedto int alignment.
Attributes and Pragmas
Several attributes and pragmas alter the placement rules. They allwork by changing FieldAlign.
packed — setsFieldAlign = 1 (bit-granular packing). Bitfields pack atthe next available bit with no alignment constraint.
Under Microsoft ABI, sizeof is 12: each bit-field mustfit within a single int unit, so x,y, and z each get their own 4-byte unit.
packed can also be applied to individual fields:
1 2
structP2 {short a:8; [[gnu::packed]] int b:30; }; // sizeof = 6, b at bit 8 // Without packed on b: b at bit 32, sizeof = 8
Without packed, b's FieldAlign is 32, so it doesn't fitin a's short storage unit and starts a newint unit at bit 32. With packed, b'sFieldAlign drops to 1, so it packs immediately after a atbit 8.
#pragma pack(N) — capsFieldAlign at N * 8 bits and suppresses thepadding-insertion test (AllowPadding = false, so theoverflow check is skipped — the field is placed at the current offsetwithout rounding up).
1 2 3
#pragma pack(1) structPP {char a; int b:4; int c:28; char s; }; // sizeof = 6 #pragma pack()
b packs at bit 8 by the normal core rule —(8 & 31) + 4 = 12 ≤ 32, so it fits. Without#pragma pack, c:28 at bit 12 would fail thesame check — 12 + 28 = 40 > 32 — and round up to bit 32.With #pragma pack(1), AllowPadding is false,so the overflow check is skipped and c stays at bit 12.Total: a(8) + b+c(32) +s(8) = 48 bits = 6 bytes.
aligned(N) — forces minimum alignment.Overrides packed, but is itself overridden by#pragma pack.
1 2
structA {char a; [[gnu::aligned(16)]] int b:1; char c; }; // b aligned to 16 bytes = bit 128. c at byte 17. sizeof = 32, alignof = 16.
T : 0 rounds up to alignof(T), acting as aseparator. Subsequent fields start in a new storage unit.
1 2 3
structZ {char x; int : 0; char y; }; // x86: y at offset 4, sizeof = 5, alignof = 1 // ARM/AArch64: y at offset 4, sizeof = 8, alignof = 4
On most targets, anonymous bit-fields don't contribute to structalignment. But on AArch32/AArch64 (withuseZeroLengthBitfieldAlignment()), zero-width bit-fieldsdo raise the struct's alignment.
Zero-width bitfields are exempt from both packed and#pragma pack — they always round up toalignof(T).
Microsoft ABI Differences
Clang uses the Microsoft layout rules in two situations: targeting aWindows triple (e.g. x86_64-windows-msvc), which usesMicrosoftRecordLayoutBuilder; or applying__attribute__((ms_struct)) to individual structs on anytarget, which activates the IsMsStruct path insideItaniumRecordLayoutBuilder. GCC documents the rules underTARGET_MS_BITFIELD_LAYOUT_P.
The Microsoft ABI uses a fundamentally different layout strategy.While Itanium packs bit-fields into overlapping storage units ofpotentially different types, Microsoft allocates acomplete storage unit of the declared type, thenparcels bits among successive bit-fields of the same typesize.
The key differences:
Type size changes force a new storage unit. In theGCC documentation's wording: "a bit-field won't share the same storageunit with the previous bit-field if their underlying types havedifferent sizes, and the bit-field will be aligned to the highestalignment of the underlying types of itself and of the previousbit-field." Itanium would let them overlap.
1 2
structItn {int a:24; short b:8; }; // sizeof = 4 struct __attribute__((ms_struct)) MS {int a:24; short b:8; }; // sizeof = 8
Under Itanium, b's short storage unitoverlaps into a's int unit — everything fitsin 4 bytes. Under Microsoft, the type size changes from 4 to 2, sob gets its own storage unit. The int unit (4bytes) plus the short unit (2 bytes, padded to 4 foralignment) gives 8 bytes. Note that the rule is about typesize, not type identity — int a:24; unsigned b:8share a unit because both types are 4 bytes.
Each unit is discrete — this is a direct consequence of the type sizerule.
Zero-width bit-fields are ignored unless they follow anon-zero-width bitfield.(MicrosoftRecordLayoutBuilder::layoutZeroWidthBitField.)GCC's documentation: "zero-sized bit-fields are disregarded unless theyfollow another nonzero-size bit-field." When honored, they terminate thecurrent run and affect the struct's alignment.
Alignment = type size. The alignment of afundamental type always equals its size —alignof(long long) == 8 even on targets where the naturalalignment is 4 (like Darwin PPC32).
Unions. ms_struct ignores all alignment attributesin unions. All bit-fields use alignment 1 and start at offset 0.
Phase 2: Access Units
LLVM IR has no bit-field concept. To access a bit-field, theClang-generated IR must:
Load an integer from memory (the access unit)
Mask and shift to extract or insert the bit-field's bits
Store the integer back
The access unit is the LLVM type that gets loaded and stored.Choosing it well matters:
Too narrow means multiple memory operations for adjacent bit-fieldwrites;
Too wide means touching memory unnecessarily or clobbering adjacentdata.
Overlap non-bitfield storage. The C memory modelallows non-bitfield members to be accessed from other threads. Aload/store of the access unit must not touch bytes belonging to othermembers.
Cross a zero-width bit-field at a byte boundary.Zero-width bit-fields define memory location boundaries — they arebarriers.
Extend into reusable tail padding. In C++, aderived class may place fields in a non-POD base class's tail padding.The access unit must not overwrite those bytes.
Soft goals — subject to the hard constraints, accessunits should be:
Power-of-2 sized (1, 2, 4, 8 bytes). Non-power-of-2sizes (e.g., 3 bytes) get lowered as multiple smaller loads plus bitmanipulation.
No wider than a register. Avoids multi-registerloads.
Naturally aligned (on strict-alignment targets).Avoids the compiler synthesizing unaligned access sequences.
As wide as possible within the above. Fewer, wideraccesses let LLVM combine adjacent bit-field writes into oneread-modify-write.
The algorithm: spans then merging.
Step 1 — Spans. Bitfields that share a byte are inseparable.They form a minimal "span" that must be in the same access unit. A spanis a maximal run of bit-fields where each successive one startsmid-byte.
Spans break at byte-aligned boundaries and at zero-width bit-fieldbarriers. A field mid-byte is unconditionally part of the current span —step 2 never sees it as a merge point.
Step 2 — Merge. Starting from each span, try to widen theaccess unit by incorporating the next span. Accept the merge if thecombined unit:
Fits in one register (<= RegSize)
Is power-of-2 and naturally aligned (on strict-alignmenttargets)
Doesn't cross a barrier (zero-width bit-field or non-bitfieldstorage)
The natural iN type fits before the limit offset
Track the best candidate and install it when merging can't improvefurther.
Access unit representation.
Clang represents each access unit as either an integer typeiN or an array type [N x i8] (seeCGRecordLowering::accumulateBitFields). iN ispreferred — it generates a single load/store instruction. But LLVM'siN types have allocation sizes rounded up to powers of 2(DataLayout.getTypeAllocSize). For example,i24 has allocation size 4 bytes.
If that rounded-up size would extend past the next field or pastreusable tail padding, the access unit is clipped to[N x i8], which has an exact byte count. Clang assumesclipped for each new span (BestClipped = true) and sets itto false only when the natural iN fits within the availablespace (BeginOffset + TypeSize <= LimitOffset).
1 2 3 4 5 6
// Tail padding reuse (C++) structA {int x:24; ~A(); }; // non-POD: DataSize=3, Size=4 structB : A { char c; }; // c at offset 3, in A's tail padding
// i24 allocates 4 bytes, but byte 3 belongs to B::c. // Access unit for x is clipped to [3 x i8].
Strict vs cheap unaligned. On targets with cheapunaligned access (x86, AArch64 without +strict-align),alignment checks are skipped — spans merge freely up to register width.On strict-alignment targets (MIPS, Hexagon, bare-metal ARM), a merge isrejected if the combined access unit would not be naturally aligned atits offset within the struct.
-ffine-grained-bitfield-accesses. ThisClang flag disables merging entirely. Each span becomes its own accessunit — no adjacent spans are combined. For example:
1 2 3
structS4 {unsignedlong f1:28, f2:4, f3:12; }; // Default: %struct.S4 = type { i64 } — spans merged into one access unit // Fine-grained: %struct.S4 = type { i32, i16 } — each span kept separate
Phase 2 sees two bit-field runs (separated by non-bitfieldc):
Run 1: a and b (bits 0–15, bytes0–1). They share byte 1 (bits 8–15), so they form one span. The spancovers 2 bytes. The natural type i16 fits exactly — noclipping needed. Access unit: i16.
Run 2: d (bits 24–29, byte 3). Single span, 6bits in 1 byte. Access unit: i8.
The resulting LLVM struct type:
1 2
%struct.S3 = type { i16, i8, i8 } a,b c d
To read a, codegen loads the i16, extractsbits 0–9. To read b, it loads the same i16,extracts bits 10–15. Neither load touches c.
When clipping is needed. Widen the bit-fields soa + b no longer fits in 2 bytes:
1
structS3w {int a:14; int b:10; char c; int d:6; };
Run 1: a and b (bits 0–23, bytes0–2). The span covers 3 bytes. The natural type i24 hasallocation size 4 bytes — but byte 3 belongs to c. Theaccess unit is clipped to [3 x i8].
Run 2: d (bits 32–37, byte 4). Access unit:i8.
1 2
%struct.S3w = type { [3 x i8], i8, i8, [3 x i8] } a,b c d padding
Microsoft: Discrete AccessUnits
Microsoft ABI's codegen is simple: each bit-field gets an access unitof its declared type. Adjacent bit-fields of the same type size shareone access unit. Zero-width bit-fields and type-size changes break runs.There is no complex merging — the Phase 1 storage units are theaccess units.
Contrast S3 under both ABIs:
1
structS3 {int a:10; int b:6; char c; int d:6; };
1 2
Itanium: %struct.S3 = type { i16, i8, i8 } // a,b merged into i16, d is i8 Microsoft: %struct.MS3 = type { i32, i8, i32 } // a,b share i32 unit, d gets own i32
Itanium's Phase 2 merges a and b into thetightest access unit that covers both (i16), and clips orshrinks to avoid touching c. Microsoft uses the fulldeclared type (int = i32) for each storageunit — no merging, no clipping.
Similarly for mixed types:
1
structS2 {int a:24; short b:8; };
1 2
Itanium: %struct.S2 = type { i32 } // a and b merged into one i32 Microsoft: %struct.MS2 = type { i32, i16 } // separate units: i32 for a, i16 for b
Itanium merges a and b into a singlei32 since they share the same 4 bytes. Microsoft gives eachits own access unit matching the declared type.
Conclusion
Phase 1 decides where bits go — it's specified by the ABIand determines sizeof and alignof. Phase 2decides how to access them — it's a compiler optimization thataffects codegen but not the binary layout. They answer differentquestions and often produce different-sized units. The storage unit fora bit-field is determined by its declared type; the access unit isdetermined by what's safe and efficient to load.
最近看到一个很有意思的项目,它是一个由国外 Rork 团队推出的 AI 移动应用开发平台,宣称是“全球首个在浏览器中构建原生 Swift 应用的 AI 工具”,也就是,你可以不需要 Mac 和 Xcode ,同时一次性完成 iPhone、手表、iPad、电视和 Vision Pro 的应用,甚至还有 AR 和 3D 支持。
所以实际上就是:Cloud 版本的 Xcode/Mac ,然后搭配 Claude Code 和 Opus 4.6 ,然后生成对应的 iOS App 并提交 Apple Store 审核。
而 Rork 在这里也是采用了类似于云游戏的实时视频流协议(低延迟传输),所以你在浏览器里的每一次点击都会传回云端 Mac 的模拟器,画面变化再实时推送到前端
实际上就是一个远程主机,本质和 AI Studio 类似。
当然,Rork Max 的核心肯定还是他们的 Agent 管理和产品流程,这里的 AI Agent 除了利用 Opus 4.6 写代码之外,还要管理它的所有报错,测试运行和工程管理,同时 Rork 内置了 App Store Connect 的自动化流程,用户登录 Apple ID 后,AI 可以代理证书配置、App 打包和提审等流程。
从这里看,Rork Max 的客户更多的可能是非开发者,所以它的目标是将复杂的工程基座(Mac 硬件 + Xcode SDK + 苹果证书体系)完全抽象化,让开发者只需要关注逻辑和创意。
另外,这里 Rork 自己强调了“非模版化”。它不是通过预设模版拼凑应用,而是通过大模型实时推理,通过自己实现的“持续上下文注入”的技术,让 AI 记住你之前所有对 UI 的微调,确保跨平台迁移时风格的一致性。
实际上它更多是一个从零构建、测试、安装并上架的 Apple 体系生产平台。它直接把“idea → 上架 App Store 的原生 Swift 应用”压缩成一个网页操作,从而大幅度降低了门槛。
目前已经有一些 Rork max 用户开始体验,反馈褒贬不一,但是我是没真实体验的,因为 Rork Max 的价格还是挺感人:
为什么不体验其他的?因为我看到所有说不好用的回复里,官方都是问:你是否打开了 Rork Max ?
当然,觉得它有意思的原因,也是它这个产品形态或者是未来的代表之一,开发者不再需要装什么 IDE 或者 SDK ,甚至都不需要纠结是 win 还是 mac 甚至 linux ,只需要一个入口,就可以完成需要开发,当然,那时候如果真的到来的话,也许开发者也不是开发者了,可能更多只是 token 账单的消费者。
我曾有一个坏习惯:常常会忍不住打开推特,看看有什么新闻(尤其是 AI 技术日新月异的当下,容易 FOMO)、关注的人又发布了什么动态、自己的推文有没有被点赞或评论。一开始倒没觉得有什么问题,但当这个行为的发生频率变高之后,我意识到它带来了明显的负面影响:注意力难以集中,思维变得碎片化。于是我想改掉这个坏习惯。
以 Claude Code 为代表的 Coding Agent 对软件行业的重塑已成定局。它们的可用性已然突破临界点,使得代码生成的边际成本显著下降,比如 Claude Code 本身已经已经全部由 Claude Code 编写了。过去需要一周的硬编码工作,现在可能缩短为半天;过去因技术门槛高而不敢涉猎的领域,现在变得触手可及。
在 AI 时代,有什么不懂直接问 AI 就好了,为什么还要记笔记?因为缺少内化的知识网络,就问不出好问题,没有好问题,就很难得到好答案,就无法最大程度地挖掘 AI 的潜力。大语言模型遵循的是 GIGO(Garbage In Garbage Out)原则,没有好的输入,就很难得到好的输出。笔记系统可以帮助我们构建/强化知识网络,从而问出好问题。
比如前一阵很火的 Dan Koe 的 How to fix your entire life in 1 day 这篇文章,看完之后,可能觉得很有道理,但不一定能问出合适的 follow up,比如文章提到的思想跟斯多葛的消极想象有什么联系?或文章提到的身份认同理论是否与 Atomic Habits 中提到的身份认同概念一致?以这些问题为切入点,可能又能获得到一些不错的新的知识点和看世界的角度,进而丰富自己的知识体系。
「电子书」我没有使用 Apple Books 或 Calibre,而是直接使用 macOS Finder + Tags。把待看的书扔进文件夹,看完的书打上特定的标签,这样只要 filter by tag,就能看到看过的书和没看的书。这么做的一个原因是不争气的 Apple Books,它不支持 Smart Filter,只能手动创建 Collection,这样就很不方便筛选出没有看的书,我希望它像一个 Queue 或 Stack,随着书一本一本被看完,这个列表里的内容也会逐渐减少。还有一个原因是,书放进去后,再导出来也不太方便。
我们常说 Déjà vu(既视感),即对陌生环境感到熟悉;而 ET 模式追求的是完全相反的状态——Vuja De(未视感)。即:面对最熟悉的事物,强迫自己把它当成第一次见到,甚至完全不理解其用途。
「火星人观察报告」:尝试描述一件日常小事,但不使用约定俗成的名词。以「开会」为例,如果剥离掉「会议」这个概念,在 ET 眼中看到的是:一群碳基生物围坐在一张木板旁,地位最高的雄性发出声波,其余低阶生物低头在发光的玻璃板上快速移动手指。洞察:这种视角的价值在于剥离了社会强加给事物的「功能固着」。当不再把「低效的会议」理所当然地看作「工作流程」,才更有可能发现其本质——比如「信息传递效率极低」,进而思考:为什么不直接进行脑电波传输(发文档)?
「为什么追问链」:因为 ET 从没见过地球的物品,所以一切都值得质疑。顺着这个逻辑链条深挖:为什么手机屏幕是长方形的?(为了适应手掌抓握);为什么一定要手持?(因为要随时观看);为什么一定要用眼睛看?(目前的信息交互受限于视觉)。这种像孩子一样的连续追问(比如我小时就很好奇,为什么大人们打招呼通常都是「饭吃了吗」),往往能带我们穿透表象,触达事物的底层逻辑或生理极限。
2. 福尔摩斯模式(侦探视角):对抗「视而不见」
核心理念:观察而非仅仅「看见」
福尔摩斯有一句名言:「你只是在看,你没有在观察。」 (You see, but you do not observe.) 这个模式要求我们将模糊的现状清晰化,寻找因果链条和逻辑漏洞。
Paul Graham 在 What to do 中探讨了一个看似简单却极具深意的问题:人的一生应该做什么?除了「帮助他人」和「爱护世界」这两个显而易见的道德责任外,他提出了第三个关键点:创造美好的新事物(Make good new things)。
读到这段话时,我马上想到的是 Make Something Wonderful 这本书。某种程度上,两者共享了同一个核心理念:「创造美好」不应只是一次性的行为,而是一种值得毕生追求的生活方式。
Steve Jobs 曾这样描述 Make Something Wonderful 这句话背后的动机:
There’s lots of ways to be as a person, and some people express their deep appreciation in different ways, but one of the ways that I believe people express their appreciation to the rest of humanity is to make something wonderful and put it out there.
And you never meet the people, you never shake their hands, you never hear their story or tell yours, but somehow, in the act of making something with a great deal of care and love, something is transmitted there.
And it’s a way of expressing to the rest of our species our deep appreciation. So, we need to be true to who we are and remember what’s really important to us. That’s what’s going to keep Apple Apple: is if we keep us us.
创造的产物不限形式,它可以是宏大的牛顿力学定律,也可以是一把精致的维京椅。文章也是一种常见的创作。在 AI 时代,「是否还有写博客的必要」成为了备受热议的话题。博客的独特价值在于其内容的多样性——它可以是一篇游记、一篇散文、一次技术折腾的记录、一本好书的读后感,甚至是稍纵即逝的灵感碎片。个体独特的经历与细腻的感受,是 AI 无法替代的。或者,也可以像 Paul Graham 或 Gwern 那样,通过写作对某一话题进行深度挖掘,以确保自己真正掌握了真理。
除了写作,还可以开发 App。AI Coding Assistants 的崛起极大地降低了编程门槛,普通人只需花时间熟悉这些助手,便能在短时间内构建出像模像样的产品。而随着各类 AI 图像生成工具(如 Nano Banana Pro 等)的出现,绘画创作也不再遥不可及。这正是 AI 时代对个体的最大赋能:曾经专属于专业人士的领域,如今已向所有人敞开大门。
用的 Claude Code w/Sonnet-4.5,以为是个简单的需求,就给了一个最直接的 prompt:
Make the Boolean Type Calendar Scrollable. Scroll left/right to view previous/next month.
经过几次迭代后,这个 Calendar 可以滚动了,但很卡,于是我把这个信息告诉它,让它进行优化。
there's a scroll glith on boolean type calender view, when I scroll the calender past half, and release, it will lag twice, then slide to the end. think carefully to fix this bug.
the boolean calendar view is a bit lag while scrolling, it seems to be the view don't have a consistent id, so they re-render while page change. reduce the re-render by giving them appropreate ids.
这就很奇怪了,难道是这个用 SwiftUI 实现的 Calendar 有性能问题?为了验证这个想法,我让 CC 简化代码,用最简单的色块代替 Calendar,看看滚动起来是否顺畅。
now it works, but the lag persistent. can we first identify what's causing the lag, by simplify the scenario like use a random color, etc?
CC 听取了建议,把 Calendar 变成了纯色块,滑动是顺畅了,但有个问题,滑动过一半后,色块的颜色就变成了下一个 Calendar 的色块,我分析了下,应该是滑动过半后,page 自动变成了 next page,而这个色块会监听这个值的变化,于是也就变了。把这个信息给 CC 后,它很快就 fix 了。
- the prev/next button works perfect.- but the scroll still has a problem, it seems to be caused by the page variable, once it pass half, the page change, and current scrolling item will be replace to that page.
it seems the SwiftUI's Calendar is the root cause of glitch, maybe we can use UIKit to represent this calendar?
这个改动其实挺大的,所以 CC 也是尝试很多轮后,结合我的反馈,才终于基本可用了,中间还因为用满了 5 小时的 Token 限制,休息了一会。
yeah, its smooth, but after scroll end, the calendar doesn't refresh, all blank day, the title updated though.---the behavior is after scroll ended, it first display blank date, then immediately updated with some data, but it's not the final data, it will refresh quickly again to reveal the final data, so visually, like it blinks.---yes, it's better now, but the colored background only appear when scroll end, visually its not too good, can we pre fill the calendar?---the previous month's colored circles appear immediately, but the month before still blank and fulfilled after scroll end.---better, but ${currentMonth}-3 is still blank first when scrolled.
CC 的这个策略看起来有点 rigid,能不能先预加载 3 Calendar,当滑动到倒数第二个预加载的 Calendar 时,再往前加载 3 Calendar?
can we make it simpler? because the scroll always from large month to small month (if scroll back to large month, it's already loaded), so why not just prefetch previous 3 months, if scroll to the prefetched month - 1, then start prefetch next 3 months?
很不幸,CC 在 Operate 的过程中触发了 Weekly Limit,好在还有 Codex,于是切换到 Codex,继续这个 CC 未完成的任务。
I'm in the middle of optimizing boolean type calendar scroll performance, I want the strategy be: first preload previous 3 months data, when user scroll to the second to last preloaded data's month, preload next 3 month. help me implement this strategy.
这个看似简单的需求,如果过程中缺少人的协作,很难达到满意的效果。尽管 AI 在代码生成和辅助开发方面能力强大,但在面对复杂、深层或性能敏感的需求时,它仍然是一个强大的工具,而非完全独立的解决方案。它需要有人能够帮忙诊断问题、制定策略,并在必要时进行干预和引导。纯粹的 Vibe Coding 适用于简单、明确的需求,但对于有挑战的任务,人与 AI 的高效协作,即 “人机协作编程”,才是提升效率和解决问题的关键。