These improvements are only available when iOS 14 is the deployment target.
Class Data Structures
Classes used to be represented (in-memory) as:
The ro struct is read-only, and is a lot less expensive because of it.
The other two structures are r/w, and so can be a lot more expensive.
The new representation is:
The rw_ext struct is only required by about 10% of apps, and so is allocated only when requested.
System-wide memory usage improvement: 30MB.
The class_* methods/APIs continue to work, but code accessing these structures directly will now break.
Relative Method Lists
Methods contain these pieces of metadata:
(pointer to) name
(pointer to) type information
pointer to the implementation
Pointers are 64-bit; large address space to accommodate the heap, stack, and dynamically linked binaries
These pointers are clean memory, but not free.
Pointers need to be fixed/resolved at load time because of dynamic linking.
Importantly, a method call from one binary can’t contain pointers to another binary, so there’s really no need for these pointers to cover the entire 64-bit address space.
This is now improved using relative method lists: pointers that cover a 32-bit relative address space.
No resolution required after dynamic linking.
50% space reduction; 40MB saving on a typical iPhone.
“more space you can use to delight your users” 🙄
Swizzled methods can be implemented anywhere, not just the current binary
A global table is maintained, mapping these 32-bit offsets to their full 64-bit (potentially swizzled) address
A single swizzle creates a new table entry, which is much cheaper than dirtying an entire page.
A potential landmine here is when the deployment target is specified incorrectly; an older runtime will attempt to interpret these relative offsets as 64-bit pointers, almost certainly causing a crash.
Object pointer layout:
Low bits are always zero because of alignment requirements: objects must be located at a multiple of the pointer size.
High bits (1-2 bytes) are always zero.
A pointer with the lowest bit set to 1 is not a regular pointer but a tagged pointer (this is Intel; ARM is flipped because of endianness).
As an example, this can be used to store a number directly in the pointer. Vaguely similar to an
Obfuscation is provided by combining these tagged pointers with a random value that’s set up at (app?) startup.
The “tag” field specifies the type of tag this is, such as an NSNumber. Tags can be extended up to 256 unique types (at the cost of a smaller payload).
Only Apple can add tagged types, but Swift enums use tagged pointers behind the scenes.
iOS 14 flips things around on ARM a bit more to allow a full object pointer to be stored inside a tagged pointer:
This allows a tagged pointer to point to static data in the binary.
Direct bit-twiddling against a tagged pointer structure is going to break on iOS 14 because of this.