阅读视图

发现新文章,点击刷新页面。

Swift Global Actor 完全指南

什么是 Global Actor? 概念 一句话解释 Actor 一种引用类型,串行化地执行对其状态的访问,天然线程安全。 Global Actor 全局唯一的 Actor,可被标注到 任意函数 /

在 Lua 中定义类型的简单方法

我通常用 Lua 定义一个类型只需要这样做:

-- 定义一个 object 的新类型
local object = {}; object.__index = object

-- 定义构建 object 的函数
local function new_object(self)
  return setmetatable(self or {}, object)
end

-- 给 object 添加一个 get 方法
function object:get(what)
  return self[what]
end

-- 测试一下
local obj = new_object { x = "x" }
assert(obj:get "x" == "x")

这样写足够简单,如果写熟了就不用额外再做封装。如果一定要做一点封装,可以这样:

local class = {}; setmetatable(class, class)

function class:__index(name)
    local class_methods = {}; class_methods.__index = class_methods
    local class_object = {}
    local class_meta = {
        __newindex = class_methods,
        __index = class_methods,
        __call = function(self, init)
            return setmetatable(init or {}, class_methods)
        end
    }
    class[name] = setmetatable(class_object, class_meta)
    return class_object
end

封装的意义在于:你可以通过上面这个 class 模块定义新的类型,且能通过它用类型名找到所有定义的新类型。而上面的第一版通常用于放在独立模块文件中,依赖 lua 的模块机制找到 new_object 这个构建方法。

而封装后可以这样用:

-- 定义一个名为 object 的新类型,并添加 get 方法:

local object = class.object

function object:get(what)
    return self[what]
end

-- 创建新的 object 实例,测试方法 object:get
local obj = class.object { x = "x" }
assert(obj:get "x" == "x")

如果觉得 local object = class.object 的写法容易产生歧义,也可以加一点小技巧(同时提供特殊的代码文本模式,方便日后搜索代码):

function class:__call(name)
    return self[name]
end

-- 等价于 local object = class.object
local object = class "object" 

如果我们要定义的类型是一个容器该怎么做好?

容器的数据结构有两个部分:容纳数据的集合和容器的元数据。之前,我通常把元数据直接放在对象实例中,把集合对象看作元数据中的一个。

比如定义一个集合类型 set 以及两个方法 get 和 set :

local set = class "set"

function set:new()
    return self {
        container = {},
        n = 0,
    }
end

function set:set(key, value)
    local container = self.container
    if value == nil then
        if container[key] ~= nil then
            container[key] = nil
            self.n = self.n - 1
        end
    else
        if container[key] == nil then
            self.n = self.n + 1
        end
        container[key] = value
    end
end

function set:get(key)
    return self.container[key]
end

真正集合容器在 self.container 里,这里 self.n 是集合的元信息,即集合元素的个数。注意这里集合类型需要有一个构造函数 new ,因为它在构造实例时必须初始化 .n 和 .container 。这里的 set:new 构造函数调用了前面生成的 class.set 这个默认构造行为。

测试一下:注意这里用 class.set:new() 调用了构造函数。它等价于 class.set { container = {}, n = 0 } ,因为 .container 和 .n 属于实现细节,所以不推荐使用。

local obj = class.set:new()
obj:set("x", 1)
obj:set("y", 2)
assert(obj.n == 2)
assert(obj:get "x" == 1)

如果使用者要直接访问容器的内部数据结构,它可以用 obj.container 找到引用。但我们可能希望 set 表现得更像 lua table 一样,所以也可能想这样实现:

local set2 = class "set2"

function set2:new()
    return self {
        _n = 0,
    }
end

function set2:set(key, value)
    if value == nil then
        if self[key] ~= nil then
            self[key] = nil
            self._n = self._n - 1
        end
    else
        if self[key] == nil then
            self._n = self._n + 1
        end
        self[key] = value
    end
end

-- 测试一下

local obj = class.set2:new()
obj:set("x", 1)
obj:set("y", 2)
assert(obj._n == 2)
assert(obj.x == 1)

这个版本去掉了 .container 而直接把数据放在 self 里。所以不再需要 get 方法。为了让元数据 n 区分开,所以改为了 ._n 。


如果规范了命名规则,用下划线区分元数据未尝不是一个好的方法,但在迭代容器的时候会需要剔除它们比较麻烦。所以有时候我们会把元数据外置,这里就需要用到 lua 5.2 引入的 ephemeron table 来帮助 gc 。

local set3 = class "set3"

local SET = setmetatable({}, { __mode = "k" })

function set3:new()
    local object = self()
    SET[object] = { n = 0 }
    return object
end

function set3:set(key, value)
    if value == nil then
        if self[key] ~= nil then
            self[key] = nil
            SET[self].n = SET[self].n - 1
        end
    else
        if self[key] == nil then
            SET[self].n = SET[self].n + 1
        end
        self[key] = value
    end
end

function set3:__len()
    return SET[self].n
end

-- 测试一下:

local obj = class.set3:new()
obj:set("x", 1)
obj:set("y", 2)
assert(#obj == 2)
assert(obj.x == 1)

-- 迭代 obj 已经看不到元数据了。
for k,v in pairs(obj) do
    print(k,v)
end

由于 ._n 外部不可见,所以我们用 #obj 来获取它。


如果不想用 ephemeron table 管理元数据,是否有什么简单的方法剔除元数据呢?

最近发现另一个小技巧,那就是使用 false 作为元数据的 key :

local set4 = class "set4"

function set4:new()
    return self {
        [false] = 0,
    }
end

function set4:set(key, value)
    if value == nil then
        if self[key] ~= nil then
            self[key] = nil
            self[false] = self[false] - 1
        end
    else
        if self[key] == nil then
            self[false] = self[false] + 1
        end
        self[key] = value
    end
end

function set4:__len()
    return self[false]
end

-- 测试一下

local obj = class.set4:new()
obj:set("x", 1)
obj:set("y", 2)

for k,v in pairs(obj) do
    if k then
        print(k,v)
    end
end

这个版本几乎和第二版相同,不同的地方只是在于把 ["_n"] 换成了 [false] 。这里只有一个元数据,如果有多个,可以把 [false] = {} 设为一张表。

这样就不需要额外使用弱表,在迭代时也只需要判断 key 是否为真来剔除它。虽然有这么一点点局限,但贵在足够简单。


当然你也可以给它再定义一个 __pairs 方法滤掉 false :

function set4:next(k)
    local nk, v = next(self, k)
    if nk == false then
        return next(self, false)
    else
        return nk, v
    end
end

function set4:__pairs()
    return self.next, self
end

或者给加一种叫 class.container 的类型创建方法

local function container_next(self, k)
    local nk, v = next(self, k)
    if nk == false then
        return next(self, false)
    else
        return nk, v
    end
end

function class.container(name)
    local container_class = class[name]
    function container_class:__pairs()
        return container_next, self
    end
    return container_class  
end

如果你不需要 class 提供的默认构造函数,同时不喜欢定义一个新的 new 方法,也可以直接覆盖默认构造函数(同时避免别处再给它增加新的方法):

local set5 = class.container "set5"

function set5:set(key, value)
    if value == nil then
        if self[key] ~= nil then
            self[key] = nil
            self[false] = self[false] - 1
        end
    else
        if self[key] == nil then
            self[false] = self[false] + 1
        end
        self[key] = value
    end
end

function set5:__len()
    return self[false]
end

function class.set5()
    return set5 {
        [false] = 0,
    }
end

local obj = class.set5()
obj:set("x", 1)
obj:set("y", 2)

for k,v in pairs(obj) do
    print(k,v)
end

未来将至:人形机器人运动会 - 肘子的 Swift 周报 #99

不久前在北京举办的世界人形机器人运动会上,出现了许多令人忍俊不禁的场景:机器人对着空气挥拳、跑步时左摇右摆、踢球时相互碰撞后集体倒地。尽管这些画面看起来颇为滑稽,但回顾过去几年人形机器人的发展历程就会发现,即便当前的产品仍存在诸多不足,其进步却是惊人的。按照这样的发展速度,也许在十年甚至更短的时间内,人形机器人就将走进我们的日常生活,满足各种实际需求。

Flutter 其他组件:让交互更丰富

🎯 为什么其他组件如此重要? 在我开发的一个电商应用中,用户反馈最多的问题是"操作反馈不够及时"和"选择功能不够方便"。后来我重新设计了交互组件,添加了美观的对话框、及时的提示信息和便捷的选择器,用户

Understanding alignment - from source to object file

Alignment refers to the practice of placing data or code at memoryaddresses that are multiples of a specific value, typically a power of2. This is typically done to meet the requirements of the programminglanguage, ABI, or the underlying hardware. Misaligned memory accessesmight be expensive or will cause traps on certain architectures.

This blog post explores how alignment is represented and managed asC++ code is transformed through the compilation pipeline: from sourcecode to LLVM IR, assembly, and finally the object file. We'll focus onalignment for both variables and functions.

Alignment in C++ source code

C++ [basic.align]specifies

Object types have alignment requirements ([basic.fundamental],[basic.compound]) which place restrictions on the addresses at which anobject of that type may be allocated. An alignment is animplementation-defined integer value representing the number of bytesbetween successive addresses at which a given object can be allocated.An object type imposes an alignment requirement on every object of thattype; stricter alignment can be requested using the alignment specifier([dcl.align]). Attempting to create an object ([intro.object]) instorage that does not meet the alignment requirements of the object'stype is undefined behavior.

alignas can be used to request a stricter alignment. [decl.align]

An alignment-specifier may be applied to a variable or to a classdata member, but it shall not be applied to a bit-field, a functionparameter, or an exception-declaration ([except.handle]). Analignment-specifier may also be applied to the declaration of a class(in an elaborated-type-specifier ([dcl.type.elab]) or class-head([class]), respectively). An alignment-specifier with an ellipsis is apack expansion ([temp.variadic]).

Example:

1
2
alignas(16) int i0;
struct alignas(8) S {};

If the strictest alignas on a declaration is weaker thanthe alignment it would have without any alignas specifiers, the programis ill-formed.

1
2
3
4
5
% echo 'alignas(2) int v;' | clang -fsyntax-only -xc++ -
<stdin>:1:1: error: requested alignment is less than minimum alignment of 4 for type 'int'
1 | alignas(2) int v;
| ^
1 error generated.

However, the GNU extension __attribute__((aligned(1)))can request a weaker alignment.

1
typedef int32_t __attribute__((aligned(1))) unaligned_int32_t;

Further reading: Whatis the Strict Aliasing Rule and Why do we care?

LLVM IR representation

In the LLVM Intermediate Representation (IR), both global variablesand functions can have an align attribute to specify theirrequired alignment.

Globalvariable alignment:

An explicit alignment may be specified for a global, which must be apower of 2. If not present, or if the alignment is set to zero, thealignment of the global is set by the target to whatever it feelsconvenient. If an explicit alignment is specified, the global is forcedto have exactly that alignment. Targets and optimizers are not allowedto over-align the global if the global has an assigned section. In thiscase, the extra alignment could be observable: for example, code couldassume that the globals are densely packed in their section and try toiterate over them as an array, alignment padding would break thisiteration. For TLS variables, the module flag MaxTLSAlign, if present,limits the alignment to the given value. Optimizers are not allowed toimpose a stronger alignment on these variables. The maximum alignment is1 << 32.

Function alignment

An explicit alignment may be specified for a function. If notpresent, or if the alignment is set to zero, the alignment of thefunction is set by the target to whatever it feels convenient. If anexplicit alignment is specified, the function is forced to have at leastthat much alignment. All alignments must be a power of 2.

A backend can override this with a preferred function alignment(STI->getTargetLowering()->getPrefFunctionAlignment()),if that is larger than the specified align value. (https://discourse.llvm.org/t/rfc-enhancing-function-alignment-attributes/88019/3)


In addition, align can be used in parameter attributesto decorate a pointer or vector of pointers.

LLVM back end representation

Global variablesAsmPrinter::emitGlobalVariable determines the alignment forglobal variables based on a set of nuanced rules:

  • With an explicit alignment (explicit),
    • If the variable has a section attribute, returnexplicit.
    • Otherwise, compute a preferred alignment for the data layout(getPrefTypeAlign, referred to as pref).Returnpref < explicit ? explicit : max(E, getABITypeAlign).
  • Without an explicit alignment: returngetPrefTypeAlign.

getPrefTypeAlign employs a heuristic for global variabledefinitions: if the variable's size exceeds 16 bytes and the preferredalignment is less than 16 bytes, it sets the alignment to 16 bytes. Thisheuristic balances performance and memory efficiency for common cases,though it may not be optimal for all scenarios. (See Preferredalignment of globals > 16bytes in 2012)

For assembly output, AsmPrinter emits .p2align (power of2 alignment) directives with a zero fill value (i.e. the padding bytesare zeros).

1
2
3
4
5
6
7
8
9
10
% echo 'int v0;' | clang --target=x86_64 -S -xc - -o -
.file "-"
.type v0,@object # @v0
.bss
.globl v0
.p2align 2, 0x0
v0:
.long 0 # 0x0
.size v0, 4
...

Functions For functions,AsmPrinter::emitFunctionHeader emits alignment directivesbased on the machine function's alignment settings.

1
2
3
4
5
6
7
8
void MachineFunction::init() {
...
Alignment = STI.getTargetLowering()->getMinFunctionAlignment();

// FIXME: Shouldn't use pref alignment if explicit alignment is set on F.
if (!F.hasOptSize())
Alignment = std::max(Alignment,
STI.getTargetLowering()->getPrefFunctionAlignment());
  • The subtarget's minimum function alignment
  • If the function is not optimized for size (i.e. not compiled with-Os or -Oz), take the maximum of the minimumalignment and the preferred alignment. For example,X86TargetLowering sets the preferred function alignment to16.
1
2
3
4
5
6
7
8
9
10
11
12
% echo 'void f(){} [[gnu::aligned(32)]] void g(){}' | clang --target=x86_64 -S -xc - -o -
.file "-"
.text
.globl f # -- Begin function f
.p2align 4
.type f,@function
f: # @f
...
.globl g # -- Begin function g
.p2align 5
.type g,@function
g: # @g

The emitted .p2align directives omits the fill valueargument: for code sections, this space is filled with no-opinstructions.

Assembly representation

GNU Assembler supports multiple alignment directives:

  • .p2align 3: align to 2**3
  • .balign 8: align to 8
  • .align 8: this is identical to .balign onsome targets and .p2align on the others.

Clang supports "direct object emission" (clang -ctypically bypasses a separate assembler), the LLVMAsmPrinter directlyuses the MCObjectStreamer API. This allows Clang to emitthe machine code directly into the object file, bypassing the need toparse and interpret alignment directives and instructions from atext-based assembly file.

These alignment directives has an optional third argument: themaximum number of bytes to skip. If doing the alignment would requireskipping more bytes than the specified maximum, the alignment is notdone at all. GCC's -falign-functions=m:n utilizes thisfeature.

Object file format

In an object file, the section alignment is determined by thestrictest alignment directive present in that section. The assemblersets the section's overall alignment to the maximum of all thesedirectives, as if an implicit directive were at the start.

1
2
3
4
5
6
7
.section .text.a,"ax"
# implicit alignment max(4, 8)

.long 0
.balign 4
.long 0
.balign 8

This alignment is stored in the sh_addralign fieldwithin the ELF section header table. You can inspect this value usingtools such as readelf -WS (llvm-readelf -S) orobjdump -h (llvm-objdump -h).

Linker considerations

The linker combines multiple object files into a single executable.When it maps input sections from each object file into output sectionsin the final executable, it ensures that section alignments specified inthe object files are preserved.

How the linker handlessection alignment

Output section alignment: This is the maximumsh_addralign value among all its contributing inputsections. This ensures the strictest alignment requirements are met.

Section placement: The linker also uses inputsh_addralign information to position each input sectionwithin the output section. As illustrated in the following example, eachinput section (like a.o:.text.f or b.o:.text)is aligned according to its sh_addralign value before beingplaced sequentially.

1
2
3
4
5
6
7
8
9
output .text
# align to sh_addralign(a.o:.text). No-op if this is the first section without any preceding DOT assignment or data command.
a.o:.text
# align to sh_addralign(a.o:.text.f)
a.o:.text.f
# align to sh_addralign(b.o:.text)
b.o:.text
# align to sh_addralign(b.o:.text.g)
b.o:.text.g

Link script control A linker script can override thedefault alignment behavior. The ALIGN keyword enforces astricter alignment. For example .text : ALIGN(32) { ... }aligns the section to at least a 32-byte boundary. This is often done tooptimize for specific hardware or for memory mapping requirements.

The SUBALIGN keyword on an output section overrides theinput section alignments.

Padding: To achieve the required alignment, thelinker may insert padding between sections or before the first inputsection (if there is a gap after the output section start). The fillvalue is determined by the following rules:

  • If specified, use the =fillexpoutput section attribute (within an output sectiondescription).
  • If a non-code section, use zero.
  • Otherwise, use a trap or no-op instructin.

Padding and sectionreordering

Linkers typically preserve the order of input sections from objectfiles. To minimize the padding required between sections, linker scriptscan use a SORT_BY_ALIGNMENT keyword to arrange inputsections in descending order of their alignment requirements. Similarly,GNU ld supports --sort-commonto sort COMMON symbols by decreasing alignment.

While this sorting can reduce wasted space, modern linking strategiesoften prioritize other factors, such as cache locality (for performance)and data similarity (for Lempel–Ziv compression ratio), which canconflict with sorting by alignment. (Search--bp-compression-sort= on Explain GNU stylelinker options).

System page size

The alignment of a variable or function can be as large as the systempage size. Some implementations allow a larger alignment. (Over-alignedsegment)

ABI compliance

Some platforms have special rules. For example,

  • On SystemZ, the larl (load address relative long)instruction cannot generate odd addresses. To prevent GOT indirection,compilers ensure that symbols are at least aligned by 2. (Toolchainnotes on z/Architecture)
  • On AIX, the default alignment mode is power: for doubleand long double, the first member of this data type is aligned accordingto its natural alignment value; subsequent members of the aggregate arealigned on 4-byte boundaries. (https://reviews.llvm.org/D79719)
  • z/OS caps the maximum alignment of static storage variables to 16.(https://reviews.llvm.org/D98864)

The standard representation of the the Itanium C++ ABI requiresmember function pointers to be even, to distinguish between virtual andnon-virtual functions.

In the standard representation, a member function pointer for avirtual function is represented with ptr set to 1 plus the function'sv-table entry offset (in bytes), converted to a function pointer as ifbyreinterpret_cast<fnptr_t>(uintfnptr_t(1 + offset)),where uintfnptr_t is an unsigned integer of the same sizeas fnptr_t.

Conceptually, a pointer to member function is a tuple:

  • A function pointer or virtual table index, discriminated by theleast significant bit
  • A displacement to apply to the this pointer

Due to the least significant bit discriminator, members function needa stricter alignment even if __attribute__((aligned(1))) isspecified:

1
virtual void bar1() __attribute__((aligned(1)));

Side note: check out MSVC C++ ABI MemberFunction Pointers for a comparison with the MSVC C++ ABI.

Architecture considerations

Contemporary architectures generally support unaligned memory access,likely with very small performance penalties. However, someimplementations might restrict or penalize unaligned accesses heavily,or require specific handling. Even on architectures supporting unalignedaccess, atomic operations might still require alignment.

  • On AArch64, a bit in the system control registersctlr_el1 enables alignment check.
  • On x86, if the AM bit is set in the CR0 register and the AC bit isset in the EFLAGS register, alignment checking of user-mode dataaccessing is enabled.

Linux's RISC-V port supportsprctl(PR_SET_UNALIGN, PR_UNALIGN_SIGBUS); to enable strictalignment.

clang -fsanitize=alignment can detect misaligned memoryaccess. Check out my write-up.

In 1989, US Patent 4814976, which covers "RISC computer withunaligned reference handling and method for the same" (4 instructions:lwl, lwr, swl, and swr), was granted to MIPS Computer Systems Inc. Itcaused a barrier for other RISC processors, see The Lexra Story.

Almost every microprocessor in the world can emulate thefunctionality of unaligned loads and stores in software. MIPSTechnologies did not invent that. By any reasonable interpretation ofthe MIPS Technologies' patent, Lexra did not infringe. In mid-2001 Lexrareceived a ruling from the USPTO that all claims in the the lawsuit wereinvalid because of prior art in an IBM CISC patent. However, MIPSTechnologies appealed the USPTO ruling in Federal court, adding toLexra's legal costs and hurting its sales. That forced Lexra into anunfavorable settlement. The patent expired on December 23, 2006 at whichpoint it became legal for anybody to implement the complete MIPS-Iinstruction set, including unaligned loads and stores.

Aligning code forperformance

GCC offers a family of performance-tuning options named-falign-*, that instruct the compiler to align certain codesegments to specific memory boundaries. These options might improveperformance by preventing certain instructions from crossing cache lineboundaries (or instruction fetch boundaries), which can otherwise causean extra cache miss.

  • -falign-function=n: Align functions.
  • -falign-labels=n: Align branch targets.
  • -falign-jumps=n: Align branch targets, for branchtargets where the targets can only be reached by jumping.
  • -falign-loops=n: Align the beginning of loops.

Important considerations

Inefficiency with Small Functions: Aligning smallfunctions can be inefficient and may not be worth the overhead. Toaddress this, GCC introduced -flimit-function-alignment in2016. The option sets .p2align directive's max-skip operandto the estimated function size minus one.

1
2
3
4
% echo 'int add1(int a){return a+1;}' | gcc -O2 -S -fcf-protection=none -xc - -o - -falign-functions=16 | grep p2align
.p2align 4
% echo 'int add1(int a){return a+1;}' | gcc -O2 -S -fcf-protection=none -xc - -o - -falign-functions=16 -flimit-function-alignment | p2align
.p2align 4,,3

The max-skip operand, if present, is evaluated at parse time, so youcannot do:

1
2
3
4
.p2align 4, , b-a
a:
nop
b:

In LLVM, the x86 backend does not implementTargetInstrInfo::getInstSizeInBytes, making it challengingto implement -flimit-function-alignment.

Cold code: These options don't apply to coldfunctions. To ensure that cold functions are also aligned, use-fmin-function-alignment=n instead.

Benchmarking: Aligning functions can make benchmarksmore reliable. For example, on x86-64, a hot function less than 32 bytesmight be placed in a way that uses one or two cache lines (determined byfunction_addr % cache_line_size), making benchmark resultsnoisy. Using -falign-functions=32 can ensure the functionalways occupies a single cache line, leading to more consistentperformance measurements.


LLVM notes: In clang/lib/CodeGen/CodeGenModule.cpp,-falign-function=N sets the alignment if a function doesnot have the gnu::aligned attribute.

A hardware loop typically consistants of 3 parts:

A low-overhead loop (also called a zero-overhead loop) is ahardware-assisted looping mechanism found in many processorarchitectures, particularly digital signal processors (DSPs). Theprocessor includes dedicated registers that store the loop startaddress, loop end address, and loop count. A hardware loop typicallyconsists of three components:

  • Loop setup instruction: Sets the loop end address and iterationcount
  • Loop body: Contains the actual instructions to be repeated
  • Loop end instruction: Jumps back to the loop body if furtheriterations are required

Here is an example from Arm v8.1-M low-overhead branch extension.

1
2
3
4
5
1:
dls lr, Rn // Setup loop with count in Rn
... // Loop body instructions
2:
le lr, 1b // Loop end - branch back to label 1 if needed

To minimize the number of cache lines used by the loop body, ideallythe loop body (the instruction immediately following DLS) should bealigned to a 64-byte boundary. However, GNU Assembler lacks a directiveto specify alignment like "align DLS to a multiple of 64 plus 60 bytes."Inserting an alignment after the DLS is counterproductive, as it wouldintroduce unwanted NOP instructions at the beginning of the loop body,negating the performance benefits of the low-overhead loopmechanism.

It would be desirable to simulate the functionality with.org ((.+4+63) & -64) - 4 // ensure that .+4 is aligned to 64-byte boundary,but this complex expression involves bitwise AND and is not arelocatable expression. LLVM integrated assembler would reportexpected absolute expression while GNU Assembler has asimilar error.

A potential solution would be to extend the alignment directives withan optional offset parameter:

1
2
3
4
5
# Align to 64-byte boundary with 60-byte offset, using NOP padding in code sections
.balign 64, , , 60

# Same alignment with offset, but skip at most 16 bytes of padding
.balign 64, , 16, 60

Xtensa's LOOP instructions has similar alignmentrequirement, but I am not familiar with the detail. The GNU Assembleruses the special alignment as a special machine-dependent fragment. (https://sourceware.org/binutils/docs/as/Xtensa-Automatic-Alignment.html)

老司机 iOS 周报 #348 | 2025-08-25

ios-weekly
老司机 iOS 周报,只为你呈现有价值的信息。

你也可以为这个项目出一份力,如果发现有价值的信息、文章、工具等可以到 Issues 里提给我们,我们会尽快处理。记得写上推荐的理由哦。有建议和意见也欢迎到 Issues 提出。

新手推荐

🐎 High Level Anatomy of a Camera Capturing Session

@AidenRao:这边文章用比较简单易懂的话,介绍苹果的相机从拍摄到 Swift 中展示的完整流程。文章不长,比较适合做个相机原理了解。

文章

🌟 🐕 从 DisplayList 到 Transaction: SwiftUI 调试实战

@Kyle-Ye: 文章介绍了如何通过 SwiftUI 中的相关环境变量,使用 DisplayList 输出分析视图渲染问题,通过符号断点和汇编调试深入分析 SwiftUI 内部机制,并使用 AttributeGraph 等调试工具进行问题定位。

🐕 Faster Equatable and Hashable conformances with Identifiable

@Smallfly:这篇文章聚焦 Swift 中 EquatableHashable 协议的性能优化,揭示了编译器自动合成实现的潜在瓶颈,并提出结合 Identifiable 协议的改进方案。核心内容包括:

  • 问题分析:默认合成的 Equatable/Hashable 会逐成员比较或哈希,对含大集合(如 [User])或嵌套结构的类型,复杂度达 O(N),在 SwiftUI 视图更新、Set 操作中易成性能瓶颈。
  • 优化方案:利用 Identifiableid 属性(如 UUID),仅基于唯一标识实现 EquatableHashable,将操作复杂度降至 O(1)。
  • 数据验证:基准测试显示,含 1000+ 员工的 Company 类型,Identifiable 方案的 Equatable 快 3 倍,Hashable 快 3 万倍。

文章结合编译器源码与 SwiftUI 实践,为性能敏感场景提供了可落地的优化思路。

🐢 What's New in UIKit

@Barney:这篇文章详细总结了 iOS 26 中 UIKit 的全面更新。尽管 UIKit 不再是 WWDC 的主角,但今年仍获得了大量新特性。
主要更新概况:
Liquid Glass 设计语言:新增 UIGlassEffectUIButton.Configuration 的玻璃按钮样式,以及 UIBarButtonItem 的共享背景支持
• 导航栏增强:UINavigationItem 新增 subtitlelargeTitleattributedTitle 等属性,支持更丰富的标题展示
• 分割视图改进:UISplitViewController 支持新的 inspector 列,提供类似 macOS 的检查器面板
• 标签栏配件:UITabAccessory 允许在标签栏上方添加浮动工具栏,支持折叠展开动画
• HDR 色彩支持:UIColor 新增 HDR 初始化方法,UIColorPickerViewController 支持曝光调节
• 角落配置 API:UICornerConfiguration 提供统一的圆角设置方案,支持容器同心圆角
• 自然文本选择:UITextView 支持混合左右文字的自然选择,selectedRanges 替代 selectedRange
• 主菜单系统:UIMainMenuSystemiPadOS 提供 macOS 风格的菜单栏
• 观察者模式集成:UIViewUIViewController 原生支持 Swift Observation 框架
• 滑块增强:UISlider 新增刻度配置和无拖柄样式
整体而言,iOS 26 的 UIKit 更新聚焦于视觉现代化、跨平台一致性和开发便利性的提升。

🐕 SwiftUI for Mac 2025

@Cooper Chen:这篇文章总结了 SwiftUI 在 macOS 26 上的多项改进,主要亮点包括:

  • 统一图标格式:Xcode 26 新增 Icon Composer,可用 SVG 分层生成跨平台图标,并向下兼容旧系统。
  • Liquid Glass 风格:按钮、滑块、切换等控件拥有玻璃质感与动态反馈,UI 更现代。
  • 原生 WebView:SwiftUI 首次内置 WebView,无需桥接即可加载网页并追踪导航事件。
  • 列表性能优化:List 在处理上万条数据时依然流畅,适合大数据量展示。

整体来看,SwiftUI 在 Mac 上的易用性与表现力进一步提升,对想要打造现代化界面的开发者非常有参考价值。

🐎 Git 2.51 support push/pull stash

@david-clang:过去 git stash 难以在不同机器之间迁移,Git 在 8 月 18 日发布的 2.51.0 版本支持 push/pull stash,实现跨机器共享 stash。但要在 GUI 工具上应用该特性,还要再等等,目前 Fork 支持的 Git 版本是 2.45.2。

内推

重新开始更新「iOS 靠谱内推专题」,整理了最近明确在招人的岗位,供大家参考

具体信息请移步:https://www.yuque.com/iosalliance/article/bhutav 进行查看(如有招聘需求请联系 iTDriverr)

关注我们

我们是「老司机技术周报」,一个持续追求精品 iOS 内容的技术公众号,欢迎关注。

关注有礼,关注【老司机技术周报】,回复「2024」,领取 2024 及往年内参

同时也支持了 RSS 订阅:https://github.com/SwiftOldDriver/iOS-Weekly/releases.atom

说明

🚧 表示需某工具,🌟 表示编辑推荐

预计阅读时间:🐎 很快就能读完(1 - 10 mins);🐕 中等 (10 - 20 mins);🐢 慢(20+ mins)

What's Changed

Full Changelog: #347...#348

❌