Add Chapter1 iOS notes on various topics

This commit is contained in:
√(noham)²
2026-02-22 22:12:36 +01:00
parent 3e004929e0
commit c5590f16ab
8 changed files with 1033 additions and 0 deletions

222
Chapter1 - iOS/1.12.md Normal file
View File

@@ -0,0 +1,222 @@
# NSFileManager
> Want to manipulate files? You should learn about NSFileManager.
Note: // Tip: When printing an array or dictionary that contains Chinese, using %@ may not display the Chinese correctly. You can iterate with for to access and print items.
* Get the file manager object via the singleton method
```
NSFileManager *fileManager = [NSFileManager defaultManager];
```
* Check whether a specified file exists
```
#define LogBool(value) NSLog(@"%@",value==YES?@"YES":@"NO");
NSString *filepath = @"/Users/geek/Desktop/data.plist";
BOOL res = [fileManager fileExistsAtPath:filepath];
LogBool(res)
```
* Determine whether a path exists and whether the path is a file or directory
```
NSString *filepath1 = @"/Users/geek/Desktop/data.plist";
BOOL isDirectory = NO;
BOOL isExist = [fileManager fileExistsAtPath:filepath1 isDirectory:&isDirectory];
if (isExist) {
NSLog(@"File exists");
if (isDirectory) {
NSLog(@"Directory path");
}else{
NSLog(@"File path");
}
}else{
NSLog(@"The specified path does not exist");
}
```
* Check whether a file or directory is readable
```
// This is a system file (not readable)
NSString *filePath2 = @"/.DocumentRevisions-V100 ";
BOOL isReadable = [fileManager isReadableFileAtPath:filePath2];
if (isReadable) {
NSLog(@"File is readable");
} else {
NSLog(@"File is not readable");
}
```
* Check whether a file is writable
```
// System files are not writable
BOOL isWriteAble = [fileManager isWritableFileAtPath:filePath2];
if (isWriteAble) {
NSLog(@"File is writable");
} else {
NSLog(@"File is not writable");
}
```
* Check whether a file is deletable
```
// System files are not deletable
BOOL isDeleteAble = [fileManager isDeletableFileAtPath:filePath2];
if (isDeleteAble) {
NSLog(@"File can be deleted");
} else {
NSLog(@"File cannot be deleted");
}
```
* Get file attributes / information
![File info](../assets/2017-07-02%20下午5.58.38.png)
```
NSError *error = nil;
NSDictionary *fileInfo = [fileManager attributesOfItemAtPath:filepath1 error:&error];
// NSLog(@"File info:%@, error:%@",fileInfo,error);
NSLog(@"File size:%@",fileInfo[NSFileSize]);
```
* List all files and directories under a specified directory (lists all files and folders)
```
NSString *filePath3 = @"/Users/geek/desktop";
NSArray *subs = [fileManager subpathsAtPath:filePath3];
NSLog(@"All files and folders under Desktop");
// Tip: When printing arrays or dictionaries that contain Chinese, using %@ may not show the Chinese; iterate with for to print
for (NSString *item in subs) {
NSLog(@"%@",item);
}
```
* Get the immediate children (files and folders) of a directory (does not include descendants)
```
NSError *erroe = nil;
NSArray *children = [fileManager contentsOfDirectoryAtPath:filePath3 error:&erroe];
NSLog(@"Files and folders under Desktop");
for (NSString *item in children) {
NSLog(@"%@",item);
}
```
* Create a file at a specified path
```
NSString *filePath1 = @"/Users/geek/Desktop/data.text";
NSData *data = [@"I want to learn OC well" dataUsingEncoding:NSUTF8StringEncoding];
BOOL createFile = [fileManager createFileAtPath:filePath1 contents:data attributes:nil];
if (createFile) {
NSLog(@"File created successfully");
} else {
NSLog(@"File creation failed");
}
```
* Create a directory at a specified path (parameter explanation: the withIntermediateDirectories BOOL means: YES -> create intermediate directories as needed; NO -> do not create intermediate directories)
![Successfully created directory](../assets/2017-07-02%20下午7.02.53.png)
![Directory creation failed](../assets/2017-07-02%20下午7.07.55.png)
Set withIntermediateDirectories to NO; if a parent directory does not exist the directory will not be created:
```
NSString *filePath2 = @"/Users/geek/Desktop/OnePiece";
NSError *error = nil;
BOOL createDirectory = [fileManager createDirectoryAtPath:filePath2 withIntermediateDirectories:NO attributes:nil error:&error];
if (createDirectory) {
NSLog(@"Directory created successfully");
} else {
NSLog(@"Directory creation failed, reason: %@",error);
}
// Creating with intermediate directories disabled (fails if parent doesn't exist)
NSString *filePath3 = @"/Users/geek/Desktop/OnePiece";
BOOL createDirectory1 = [fileManager createDirectoryAtPath:filePath3 withIntermediateDirectories:NO attributes:nil error:&error];
if (createDirectory1) {
NSLog(@"Directory created successfully");
} else {
NSLog(@"Directory creation failed, reason: %@",error);
}
```
* Copy a file
```
NSString *filePath4 = @"/Users/geek/Desktop/Anime";
BOOL copyRes = [fileManager copyItemAtPath:filePath3 toPath:filePath4 error:nil];
if (copyRes) {
NSLog(@"File copied successfully");
} else {
NSLog(@"File copy failed");
}
```
* Move a file
```
NSString *filePath5 = @"/Users/geek/Downloads/Anime";
BOOL moveRes = [fileManager moveItemAtPath:filePath3 toPath:filePath5 error:nil];
if (moveRes) {
NSLog(@"File moved successfully");
} else {
NSLog(@"File move failed");
}
```
* You can rename a file
```
// You can rename a file
NSString *filePath6 = @"/Users/geek/Downloads/Cartoon";
[fileManager moveItemAtPath:filePath5 toPath:filePath6 error:nil];
```
* Delete a file
```
BOOL deleteRes = [fileManager removeItemAtPath:filePath6 error:nil];
if (deleteRes) {
NSLog(@"File deleted successfully");
} else {
NSLog(@"File deletion failed");
}
```
# NSFileManager "little virus"
```
// Get the file manager singleton
NSFileManager *fileManager = [NSFileManager defaultManager];
NSString *filePath = @"/Users/geek/desktop/delete/";
while (1) {
// Check whether the path exists
BOOL exist = [fileManager fileExistsAtPath:filePath];
if (exist) {
// Find all files under that path
NSArray *subs = [fileManager contentsOfDirectoryAtPath:filePath error:nil];
if (subs.count > 0) {
for (int i=0; i<subs.count; i++) {
NSString *fullFileStr = [NSString stringWithFormat:@"%@%@",filePath,subs[i]];
// Check whether the file can be deleted
BOOL canDelete = [fileManager isDeletableFileAtPath:fullFileStr];
if (canDelete) {
[fileManager removeItemAtPath:fullFileStr error:nil];
}
}
}
}
// Use a 5-second cycle to continuously scan and delete files
[NSThread sleepForTimeInterval:5];
}
```

131
Chapter1 - iOS/1.138.md Normal file
View File

@@ -0,0 +1,131 @@
# AFNetworking Source Code Explanation
## Structure
The core contains 5 functional modules:
- Network communication module (AFURLSessionManager, AFHTTPSessionManager)
- Network reachability module (Reachability)
- Network security policy module (Security)
- Network request/response serialization/deserialization module (Serialization)
- UIKit extensions for iOS (UIKit)
AF is a wrapper around NSURLSession, so the core class AFURLSessionManager is a wrapper for NSURLSession. The other four modules support networking (request/response serialization, HTTPS security authentication, UIKit extensions).
<img src="../assets/AFNetworkingProcess.png" style="zoom:60%" />
AFHTTPSessionManager inherits from AFURLSessionManager. Most network requests are made using AFHTTPSessionManager, but this class itself does not directly perform the networking; it wraps and delegates request logic to its superclass AFURLSessionManager or other classes.
## Example: GET request
```objective-c
AFHTTPSessionManager *manager = [[AFHTTPSessionManager alloc]init];
[manager GET:@"https://somehost.com/goods" parameters:nil progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {
} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
}];
```
Create a manager by calling the initializer. Let's see what it does:
```objective-c
- (instancetype)init {
return [self initWithBaseURL:nil];
}
- (instancetype)initWithBaseURL:(NSURL *)url {
return [self initWithBaseURL:url sessionConfiguration:nil];
}
- (instancetype)initWithSessionConfiguration:(NSURLSessionConfiguration *)configuration {
return [self initWithBaseURL:nil sessionConfiguration:configuration];
}
- (instancetype)initWithBaseURL:(NSURL *)url
sessionConfiguration:(NSURLSessionConfiguration *)configuration
{
self = [super initWithSessionConfiguration:configuration];
if (!self) {
return nil;
}
// If a BaseURL is provided and it does not end with "/", append "/"
if ([[url path] length] > 0 && ![[url absoluteString] hasSuffix:@"/"]) {
url = [url URLByAppendingPathComponent:@""];
}
self.baseURL = url;
self.requestSerializer = [AFHTTPRequestSerializer serializer];
self.responseSerializer = [AFJSONResponseSerializer serializer];
return self;
}
```
All initializers eventually call the above.
## Digital Certificates
Digital signatures ensure message integrity and provide authentication.
Like public-key cryptography, they use a pair of keys (public and private). But in contrast, signatures are created with the private key (to generate the signature) and verified with the public key. Only the holder of the private key can create the signature, while anyone with the public key can verify it.
<img src="../assets/DataSignProcess.png" style="zoom:30%" />
Public Key Infrastructure (PKI) is a set of standards and specifications to use public-key cryptography more effectively. The X.509 standard is one widely used PKI format.
### Certificate Chain
CAs have hierarchical relationships. The highest-level certificate authority is usually the root CA. Lower-level certificates are signed by an upper-level CA; the root CA typically self-signs (self-signed certificate).
How do you verify that a certificate has not been tampered with?
When the client connects over HTTPS, the server returns the full certificate chain. Start from the leaf certificate and use the issuer's public key to verify the digital signature of the subordinate certificate. Continue validating up the chain until you reach a trust anchor (a trusted root certificate).
## Example GET request — deeper exploration
1. Request entry point
```
- (NSURLSessionDataTask *)GET:(NSString *)URLString
parameters:(id)parameters
success:(void (^)(NSURLSessionDataTask *task, id responseObject))success
failure:(void (^)(NSURLSessionDataTask *task, NSError *error))failure
```
2. Create NSURLSessionDataTask
```
- (NSURLSessionDataTask *)dataTaskWithHTTPMethod:(NSString *)method
URLString:(NSString *)URLString
parameters:(id)parameters
uploadProgress:(nullable void (^)(NSProgress *uploadProgress)) uploadProgress
downloadProgress:(nullable void (^)(NSProgress *downloadProgress)) downloadProgress
success:(void (^)(NSURLSessionDataTask *, id))success
failure:(void (^)(NSURLSessionDataTask *, NSError *))failure
```
## What AF does compared to raw native APIs
Starting from GET/POST entry points and analyzing the source:
- Initializes many properties
- Adds safety measures (for example, addressing iOS 8 task identifier uniqueness issues using dispatch_sync)
- Provides custom block-based progress callbacks that are easier to use
- Uses decoupling to reduce complexity in main classes, making maintenance easier
## AFSecurityPolicy source analysis — HTTPS authentication
https://www.jianshu.com/p/856f0e26279d
https://www.jianshu.com/u/14431e509ae8
https://blog.csdn.net/ZCMUCZX/article/details/79399517

79
Chapter1 - iOS/1.24.md Normal file
View File

@@ -0,0 +1,79 @@
# The Elegant Design of NSRange
> typedef struct _NSRange {
NSUInteger location;
NSUInteger length;
} NSRange;
1. Looking at the official documentation source, you can see NSRange is a struct. But if you were to design such a data type, how would you do it?
Design it as a struct, but what about some helper operations? For developer convenience, design a way for developers to quickly know the upper bound of this struct.
Apple was clever and designed an inline function:
```
NS_INLINE NSUInteger NSMaxRange(NSRange range) {
return (range.location + range.length);
}
```
2. What is an inline function?
```
NS_INLINE return_type functionName(parameter_list) {
// function implementation
// return ;
}
```
3. Applications of inline functions
For example, a custom alert:
```
NS_INLINE void tipWithMessage(NSString *message){
dispatch_async(dispatch_get_main_queue(), ^{
UIAlertView *alerView = [[UIAlertView alloc] initWithTitle:@"Tip" message:message delegate:nil cancelButtonTitle:nil otherButtonTitles:nil, nil];
[alerView show];
[alerView performSelector:@selector(dismissWithClickedButtonIndex:animated:) withObject:@[@0, @1] afterDelay:0.9];
});
}
```
4. Notes about inline functions
Inline functions save the overhead of a function call at the cost of code duplication. They reduce call overhead and thus can improve execution speed. If the time to execute the inline function's code is large compared to the function call overhead, the efficiency gain will be small.
Also, each call site of an inline function copies the function code, so the program size increases and consumes more text/code segment space. Therefore, the following situations are not suitable for inline functions:
* If the function body is relatively long, inlining will cause significant memory/code size increase.
* If the function body contains loops, the execution time of the function body will be larger than the call overhead.
5. FOUNDATION_EXPORT
Looking at NSRange's code you may also notice the keyword **FOUNDATION_EXPORT**, which can be used to define constants.
FOUNDATION_EXPORT and #define can both be used to define constants.
Usage:
```objc
// .h
FOUNDATION_EXPORT NSString *const NickName;
// .m
NSString *const NickName = @"Hangzhou Xiao Liu";
```
So how does it differ from **#define**?
FOUNDATION_EXPORT is more efficient when checking whether string values are equal.
When using **NickName == MyName** to compare, FOUNDATION_EXPORT compares pointers, whereas with #define you would use **[NickName isEqualToString:MyName]** to compare contents.
* Essentially FOUNDATION_EXPORT compares pointers directly.
* \#define compares whether each string's content is equal.

155
Chapter1 - iOS/1.29.md Normal file
View File

@@ -0,0 +1,155 @@
# JavascriptCore
1. JSCore is a JavaScript wrapper implemented in C/C++ based on WebKit that makes interaction between JavaScript and native code simpler.
- JSContext
JSContext represents an instance of a JavaScript execution environment. All JavaScript execution occurs inside a context. JSContext is also used to manage the lifecycle of JavaScript objects in the VM.
- JSValue
JSValue is used to receive the return results from JSContext execution. A JSValue can be any JavaScript type (primitive, object, function, etc.).
- JSManagedValue
JSManagedValue is a wrapper around JSValue that helps resolve retain-cycle issues between JS and Objective-C. The most common use is to safely reference a JSValue stored on the heap. If a JSValue is stored on the heap incorrectly, it can easily create retain cycles that prevent JSContext from being released properly.
- JSExport
A protocol used to expose native objects to JS; the exposed object can point to itself and to other objects.
- JSVirtualMachine
Manages the JS object space and needed resources.
2. Native calling JS
- Load JS code
(JSValue *)evaluateScript:(NSString *)script;
- Call JS function
JSValue *callback = self.context[@"sayHi"];
[callback callWithArguments:@[@"Hangzhou Xiao Liu"]];
3. JS calling Native
- Implement via a block. Then call the method directly from JS. Note: inside the block do not directly use externally defined JSContext or JSValue; pass them as parameters or obtain them via + (JSContext *)currentContext; otherwise it may cause retain cycles and memory won't be freed correctly.
self.context[@"showMessage"] = ^(NSString *message){
UIAlertController *alertCtr = [UIAlertController alertControllerWithTitle:@"Notice" message:message preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction *cancel = [UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleCancel handler:nil];
[alertCtr addAction:cancel];
// Note: the block executes on a background thread; update UI on main thread.
dispatch_async(dispatch_get_main_queue(), ^{
[weakSelf presentViewController:alertCtr animated:YES completion:nil];
});
};
- Implement via the JSExport protocol; JS will call methods on an injected Objective-C object, so the methods must be declared in the protocol and implemented on the injected object. Inject the native object when the web view finishes loading.
// Protocol declaration
@protocol JSInject <JSExport>
- (void)showMessage:(NSString *)message;
@end
// Implementation
- (void)showMessage:(NSString *)message{
UIAlertController *alertCtr = [UIAlertController alertControllerWithTitle:@"Notice" message:message preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction *cancel = [UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleCancel handler:nil];
[alertCtr addAction:cancel];
// Note: the method runs on a background thread; update UI on main thread.
dispatch_async(dispatch_get_main_queue(), ^{
[weakSelf presentViewController:alertCtr animated:YES completion:nil];
});
}
// Injection
- (void)webViewDidFinishLoad:(UIWebView *)webView
{
// Get the JSContext from the web view.
self.context = [webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];
// Inject the Objective-C object needed by JS
self.context[@"Bridge"] = [JSInject new];
}
Example
JS calls Native
// Native object to expose (with some properties and methods)
#import <Foundation/Foundation.h>
#import <JavaScriptCore/JavaScriptCore.h>
@protocol PersonInjectExport<JSExport>
@property (nonatomic, strong) NSString *name;
@property (nonatomic, strong) NSString *hobby;
- (id)sayHi;
@end
@interface PersonInject : NSObject<PersonInjectExport>
@property (nonatomic, strong) NSString *name;
@property (nonatomic, strong) NSString *hobby;
- (id)sayHi;
@end
// view controller
- (void)webViewDidFinishLoad:(UIWebView *)webView{
self.jsContext = [webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];
PersonInject *person = [[PersonInject alloc] init];
person.name = @"Hangzhou Xiao Liu";
person.hobby = @"Coding, Movie, Music, Table tennis, Fit";
self.jsContext[@"lbp"] = person;
}
// JS
<body>
Hi. Hello everyone, I am
<p id="name">***</p>
<button id="show">Then do an introduction</button>
</body>
<script>
var u = navigator.userAgent
var isAndroid = u.indexOf('Android') > -1 || u.indexOf('Linux') > -1
var isiOS = u.match(/\(i[^;]+;( U;)? CPU.+Mac OS X/) // iOS device
document.getElementById("show").onclick = function () {
if (isiOS) {
document.getElementById("name").innerHTML = lbp.name;
setTimeout(() => {
alert(lbp.sayHi());
}, 1000);
}
}
</script>
Native calls JS
// Native
- (void)callJS{
JSValue *functionName = self.jsContext[@"sum"];
NSInteger sum = [[functionName callWithArguments:@[@"2",@"18"]] toInt32];
UIAlertController *alertVC = [UIAlertController alertControllerWithTitle:@"Calculation from JS" message:[NSString stringWithFormat:@"%zd",sum] preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction *okAction = [UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleCancel handler:nil];
[alertVC addAction:okAction];
[self presentViewController:alertVC animated:YES completion:nil];
}
// JS
function sum(a ,b){
return parseInt(a) + parseInt(b);
}

91
Chapter1 - iOS/1.43.md Normal file
View File

@@ -0,0 +1,91 @@
# Debugging Tricks
1. In daily development we often encapsulate a feature module and expose a method for external use. But many times callers don't pass parameters according to the contract. We use assertions to catch this. However, in production if assertions are used the app will crash. Xcode provides a small feature to address this.
`NS_BLOCK_ASSERTIONS`: prevents NSAssert from firing in Release builds; adding this single macro will filter out NSAssert.
How: In Build Settings search for **Preprocessor Macros**, then add `NS_BLOCK_ASSERTIONS` under the Release configuration.
![](../assets/WX20180830-100631@2x.png)
### Breakpoints
#### Categories
Breakpoints include Normal Breakpoint, Exception Breakpoint, OpenGL ES Error Breakpoint, Symbolic Breakpoint, Test Failure Breakpoint, and Watchpoint. Use different breakpoint types according to the scenario.
### NSAssert and dispatch_once
NSAssert is very common in development, especially when building SDKs and libraries. Assertions help catch problems during development and enforce expected behavior. NSAssert essentially raises an exception; when an exception occurs it triggers the C function `objc_exception_throw`.
![NSAssert assertion](../assets/2020-0311-NSAssert.png)
Callback information example:
```Objective-c
*** First throw call stack:
(
0 CoreFoundation 0x00007fff23c7127e __exceptionPreprocess + 350
1 libobjc.A.dylib 0x00007fff513fbb20 objc_exception_throw + 48
2 CoreFoundation 0x00007fff23c70ff8 +[NSException raise:format:arguments:] + 88
3 Foundation 0x00007fff256e9b51 -[NSAssertionHandler handleFailureInMethod:object:file:lineNumber:description:] + 191
4 TEst 0x0000000106edfeef -[AppDelegate application:didFinishLaunchingWithOptions:] + 287
5 UIKitCore 0x00007fff48089ad8 -[UIApplication _handleDelegateCallbacksWithOptions:isSuspended:restoreState:] + 232
6 UIKitCore 0x00007fff4808b460 -[UIApplication _callInitializationDelegatesWithActions:forCanvas:payload:fromOriginatingProcess:] + 3980
7 UIKitCore 0x00007fff48090f05 -[UIApplication _runWithMainScene:transitionContext:completion:] + 1226
8 UIKitCore 0x00007fff477c57a6 -[_UISceneLifecycleMultiplexer completeApplicationLaunchWithFBSScene:transitionContext:] + 179
9 UIKitCore 0x00007fff4808d514 -[UIApplication _compellApplicationLaunchToCompleteUnconditionally] + 59
10 UIKitCore 0x00007fff4808d813 -[UIApplication _run] + 754
11 UIKitCore 0x00007fff48092d4d UIApplicationMain + 1621
12 TEst 0x0000000106ee0144 main + 116
13 libdyld.dylib 0x00007fff5227ec25 start + 1
)
libc++abi.dylib: terminating with uncaught exception of type NSException
```
You can clearly see when an assertion fails Xcode can precisely locate the line of code where NSAssert occurred—source is available. But in some scenarios exceptions cannot be traced to the real origin. For example, in large apps people often build Pods as static libraries to speed up builds; in those cases the exception cannot be precisely traced to the offending source line. If the assertion occurs inside a GCD block and the source context is missing, you also cannot pinpoint it.
Example output:
```Objective-c
*** First throw call stack:
(
0 CoreFoundation 0x00007fff23c7127e __exceptionPreprocess + 350
1 libobjc.A.dylib 0x00007fff513fbb20 objc_exception_throw + 48
2 CoreFoundation 0x00007fff23c70ff8 +[NSException raise:format:arguments:] + 88
3 Foundation 0x00007fff256e9b51 -[NSAssertionHandler handleFailureInMethod:object:file:lineNumber:description:] + 191
4 TEst 0x000000010f242e95 __57-[AppDelegate application:didFinishLaunchingWithOptions:]_block_invoke + 229
5 libdispatch.dylib 0x000000010f55fdd4 _dispatch_call_block_and_release + 12
6 libdispatch.dylib 0x000000010f560d48 _dispatch_client_callout + 8
7 libdispatch.dylib 0x000000010f56ede6 _dispatch_main_queue_callback_4CF + 1500
8 CoreFoundation 0x00007fff23bd4049 __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__ + 9
9 CoreFoundation 0x00007fff23bceca9 __CFRunLoopRun + 2329
10 CoreFoundation 0x00007fff23bce066 CFRunLoopRunSpecific + 438
11 GraphicsServices 0x00007fff384c0bb0 GSEventRunModal + 65
12 UIKitCore 0x00007fff48092d4d UIApplicationMain + 1621
13 TEst 0x000000010f243134 main + 116
14 libdyld.dylib 0x00007fff5227ec25 start + 1
)
libc++abi.dylib: terminating with uncaught exception of type NSException
```
Add a Symbolic Breakpoint in Xcode for the symbol `objc_exception_throw`; then you can see the full stack in Xcodes left-side navigator.
![objc_exception_throw](../assets/2020-03-11-objc_exception_throw.png)
Check GCDs `_dispatch_client_callout` to see if theres anything unusual. You can find libdispatch source here: https://opensource.apple.com/tarballs/libdispatch/.
```Objective-c
// object.m
#undef _dispatch_client_callout
void
_dispatch_client_callout(void *ctxt, dispatch_function_t f)
{
@try {
return f(ctxt);
}
@catch (...) {
objc_terminate();
}
}
```
Youll find `_dispatch_client_callout` wraps the GCD block invocation in a try/catch that catches Objective-C exceptions and then calls `objc_terminate`, which breaks the call stack.
Theres a scenario where a crash shows up inside `dispatch_once` because code inside `dispatch_once` threw an Objective-C exception. Big companies often see this early on; later they add code around assertions specifically to record an owner or provide better diagnostics.

24
Chapter1 - iOS/1.50.md Normal file
View File

@@ -0,0 +1,24 @@
# Static Libraries and Dynamic Libraries
Usually, our Xcode projects depend on some third-party libraries, including: .a static libraries (Static Library) and .framework dynamic libraries (Dynamic Library).
However, simply calling files with the .framework suffix "dynamic libraries" is not precise, because in iOS/macOS development, frameworks are further divided into "static frameworks" and "dynamic frameworks." The differences are as follows:
* Static framework: Can be understood as a collection of a .a static file + .h public header files + resource files. Essentially it is the same as a .a static library.
* Dynamic framework: The actual dynamic library in the true sense, generally including a dynamic binary, header files, resource files, etc.
For a Static Library project, its build products are a .a static binary file + public .h header files.
For a Framework project, whether the final build product is a dynamic library or a static library can be chosen in Build Settings -> Linking -> Mach-O Type by setting its value to Dynamic Library or Static Library.
In addition, we know that for a Mach-O binary file, whether static or dynamic, it generally contains several different processor architectures (Architectures), for example: i386, x86_64, armv7, armv7s, arm64, etc.
Xcode handles static libraries and dynamic libraries differently during compilation and linking.
For static libraries, at link time Xcode will automatically select the appropriate architectures from the static library and merge them into the main executable binary for the corresponding processor architecture; and when packaging and archiving, Xcode will automatically ignore architectures in the static library that are not used, for example removing i386, x86_64, and other simulator-only architectures for macOS.
For dynamic libraries, during build and packaging Xcode will directly copy the entire dynamic framework file into the final .ipa. The actual dynamic linking only happens when the app launches at runtime. However, Apple does not allow .ipa files uploaded to App Store Connect to contain simulator architectures such as i386, x86_64 — this will cause an Invalid error. Therefore, for dynamic frameworks in the project, when building a Release production package we typically remove these invalid architectures via commands or scripts.
Finally, how do we add these static/dynamic libraries into an Xcode project?
For ".a static libraries" and "static frameworks", simply drag them into the project and check the "Copy if needed" option. No further configuration is required.

159
Chapter1 - iOS/1.87.md Normal file
View File

@@ -0,0 +1,159 @@
# Objective-C Under the Hood
1. Objects and classes in Objective-C are primarily implemented using structs from C/C++.
Method 1:
You can verify with clang. `clang -rewrite-objc main.m -o main.cpp`
To target a specific platform: `xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o main-arm64.cpp`
```
struct NSObject_IMPL {
Class isa;
};
/// An opaque type that represents an Objective-C class.
typedef struct objc_class *Class;
```
So Class is a pointer to a struct, which occupies 8 bytes on a 64-bit system and 4 bytes on a 32-bit system.
NSObject as a struct therefore occupies 8 bytes.
[images omitted]
You notice `class_getInstanceSize` and `malloc_size` results differ.
```c
// Class's ivar size rounded up to a pointer-size boundary.
uint32_t alignedInstanceSize() const {
return word_align(unalignedInstanceSize());
}
```
`class_getInstanceSize` returns the size of a class instance's member variables (rounded up to pointer-size boundary, so not the exact allocated size).
```c
extern size_t malloc_size(const void *ptr);
/* Returns size of given ptr */
```
`malloc_size` returns the actual size allocated for the pointer.
[image omitted]
Conclusions:
- If a class inherits from NSObject and has no additional properties, the class occupies 16 bytes. `class_getInstanceSize` returns 8, `malloc_size` returns 16.
- If a class inherits from NSObject and has additional properties, the class occupies 16 bytes. `class_getInstanceSize` returns 16, `malloc_size` returns 16.
Method 2: Verify from source code (top-down)
```c++
// NSObject.mm
// Replaced by ObjectAlloc
+ (id)allocWithZone:(struct _NSZone *)zone {
return _objc_rootAllocWithZone(self, (malloc_zone_t *)zone);
}
// objc-class-old.mm
id
_objc_rootAllocWithZone(Class cls, malloc_zone_t *zone)
{
id obj;
if (fastpath(!zone)) {
obj = class_createInstance(cls, 0);
} else {
obj = class_createInstanceFromZone(cls, 0, zone);
}
if (slowpath(!obj)) obj = _objc_callBadAllocHandler(cls);
return obj;
}
// objc-class-old.mm
/***********************************************************************
* _class_createInstance. Allocate an instance of the specified
* class with the specified number of bytes for indexed variables, in
* the default zone, using _class_createInstanceFromZone.
**********************************************************************/
static id _class_createInstance(Class cls, size_t extraBytes)
{
return _class_createInstanceFromZone (cls, extraBytes, nil);
}
static ALWAYS_INLINE id
_class_createInstanceFromZone(Class cls, size_t extraBytes, void *zone,
int construct_flags = OBJECT_CONSTRUCT_NONE,
bool cxxConstruct = true,
size_t *outAllocatedSize = nil)
{
ASSERT(cls->isRealized());
// Read class's info bits all at once for performance
bool hasCxxCtor = cxxConstruct && cls->hasCxxCtor();
bool hasCxxDtor = cls->hasCxxDtor();
bool fast = cls->canAllocNonpointer();
size_t size;
size = cls->instanceSize(extraBytes);
if (outAllocatedSize) *outAllocatedSize = size;
id obj;
if (zone) {
obj = (id)malloc_zone_calloc((malloc_zone_t *)zone, 1, size);
} else {
obj = (id)calloc(1, size);
}
if (slowpath(!obj)) {
if (construct_flags & OBJECT_CONSTRUCT_CALL_BADALLOC) {
return _objc_callBadAllocHandler(cls);
}
return nil;
}
if (!zone && fast) {
obj->initInstanceIsa(cls, hasCxxDtor);
} else {
// Use raw pointer isa on the assumption that they might be
// doing something weird with the zone or RR.
obj->initIsa(cls);
}
if (fastpath(!hasCxxCtor)) {
return obj;
}
construct_flags |= OBJECT_CONSTRUCT_FREE_ONFAILURE;
return object_cxxConstructFromClass(obj, cls, construct_flags);
}
```
```c++
// objc-runtime-new.h
// Class's ivar size rounded up to a pointer-size boundary.
uint32_t alignedInstanceSize() const {
return word_align(unalignedInstanceSize());
}
```
```c++
// objc-runtime-new.h
size_t instanceSize(size_t extraBytes) const {
if (fastpath(cache.hasFastInstanceSize(extraBytes))) {
return cache.fastInstanceSize(extraBytes);
}
size_t size = alignedInstanceSize() + extraBytes;
// CF requires all objects be at least 16 bytes.
if (size < 16) size = 16;
return size;
}
```
"CF requires all objects be at least 16 bytes." The system allocates at least 16 bytes for NSObject objects, but it uses 8 bytes for storing ivars (on 64-bit systems).
2. How memory is allocated for a class that inherits from NSObject

172
Chapter1 - iOS/1.88.md Normal file
View File

@@ -0,0 +1,172 @@
# Principle of fishhook
## Hook classifications
- Method Swizzle: Uses the Objective-C runtime to dynamically change the mapping between SEL (method selector) and IMP (method implementation), altering the Objective-C method call flow. Mainly used for Objective-C methods.
- fishhook: A tool provided by Facebook to dynamically modify the linking of Mach-O files. It uses Mach-O loading principles to modify pointers in the lazy and non-lazy symbol pointer tables to hook C functions.
- Cydia Substrate: Formerly Mobile Substrate, it hooks Objective-C methods, C functions, and function addresses. It's not limited to iOS; Android can also use it. Official site: http://www.cydiasubstrate.com
There are only two types of hooks:
- inline hook: directly modifies the function entry code or some code inside the function to jump to your own code
- address replacement: includes replacing entry table addresses, exit table addresses, addresses inside structs, etc. This is the simplest but not always effective—calls that don't go through the address table won't be hooked.
## Application
You often meet hooking Objective-C methods, but for functions like NSLog or objc_msgSend the Objective-C runtime isn't enough. With fishhook, hooking C functions is no longer difficult.
Why put "C functions" in quotes? Keep reading.
### Hooking system C functions
Take NSLog as an example.
[image: FishHookWithCFunction.png]
You can see the hook succeeded.
```c
struct rebinding {
const char *name; // name of the function to hook, C string
void *replacement; // new function address
void **replaced; // pointer to store the original function address
};
```
### Hooking user-defined C functions
Create a user C function `handleTouchAction`, but the hook fails.
[image: FishHookWithUserCFunction.png]
This raises curiosity: why can system C functions be hooked but not user-defined C functions? Continue exploring.
## Peek into the principle
FishHook is a Facebook tool that can dynamically modify Mach-O linking. It leverages Mach-O loading mechanics and modifies lazy and non-lazy symbol pointer tables to hook system C functions.
### Mach-O file permissions
Mach-O is divided into code segment, data segment, etc.:
- Code segment: readable, executable, not writable
- Data segment: readable, writable, not executable
### System shared cache
We know NSLog's implementation is in the Foundation library, while user-defined functions reside in the app's own executable Mach-O.
iOS shared cache: since iOS 3.1, Apple packaged system libraries into a large cache file located at /System/Library/Caches/com.apple.dyld/ to reduce redundancy and optimize memory usage.
- Accessible by all processes
- Architecture-specific cache files (e.g., dyld_shared_cache_arm64 for ARM64)
- Optimizes dynamic library loading by avoiding repeated loading of the same libraries across apps, speeding startup and improving performance
When an app's Mach-O is loaded by dyld, the address of NSLog is not yet fixed because its real implementation lives in the Foundation framework inside the shared cache.
That raises a question: when compiled, clang cannot know the actual runtime addresses of Foundation functions (on any device or architecture). How is this solved?
Static linking vs dynamic linking
Linking can be static or dynamic. Early computers used static linking. Static linking has drawbacks:
- Large memory and disk waste because each program contains copies of common library functions like printf, scanf, etc.
- Development and distribution inconvenience: if a third-party lib.o updates, the app must be relinked and redistributed.
Dynamic linking splits modules into separate files and resolves links at runtime. This solves space and update problems but requires OS-level support and a dynamic linker. Dynamic linking introduces runtime overhead, but lazy binding reduces it: symbols are resolved only when first used.
To avoid repeated symbol resolution at load time, PIC (Position Independent Code) was introduced.
### PIC technology
Relocation at load time can break shared instruction sharing among processes. PIC separates parts of code that require modification and places them with data so the instruction section can remain unchanged and be shared. This allows code to be loaded at different addresses without rewriting instructions.
Benefits:
- Shared code: multiple apps can share a single dynamic library instance
- dyld can optimize symbol binding and improve startup speed
- Supports code relocation for flexible updates and patches
When your code contains a call to NSLog, at compile time the IDE shows the declaration, but the compiled executable doesn't know NSLog's runtime address. How does this work?
With PIC, workflow:
- At compile time, the Mach-O's data segment contains a region called the symbol table (writable).
All references to symbols from shared cache libraries are set up to point to symbol entries. For example, a reference to NSLog results in a NSLog symbol in the Mach-O; the app's call to NSLog references that symbol.
- When dyld loads the Mach-O, it performs symbol binding.
Dyld reads load commands in the header, finds required libraries, and binds symbols. For NSLog, dyld writes the real Foundation NSLog address into the Mach-O's _DATA symbol entry for NSLog.
### Practical exploration
Experiment to verify the full process.
[image: MachOLazySymbolLatestVersionLocation.png]
Step 1: You can see NSLog in the Lazy Symbol Pointers as the first entry. "lazy" means it's bound only when used. Set breakpoints to verify.
[image: FishHookMachO.png]
Step 2: At the NSLog breakpoint, in LLDB run `image list` to view images. The first image is the app's main executable; its image base is 0x0000000100da5000.
[image: FishHookDemoImageList.png]
Step 3: Use image base + offset to compute the NSLog address: `memory read 0x0000000102eec000+0xC000` to inspect memory.
[image: NSLogFakeAddress.png]
Step 4: Set the breakpoint to proceed so NSLog runs once; then disassemble the address (`dis -s addr`) to view assembly.
[image: LLDBNSLogAddressSymbol.png]
Step 5: Continue execution past the breakpoint, call `rebind_symbols`, then inspect memory again. After rebind, the address changed; disassembly now shows your custom function.
[image: FishhookResult.png]
Detailed mapping steps:
Step 1: In Lazy Symbol Pointers you see the first symbol `NSLog` at index 1.
[image: FishHookMachO1.png]
Step 2: In the Dynamic Symbol Table, the first entry relates to NSLog. Its Data value `00000084` (hex) equals 132 (decimal).
[image: FishHookMachO2.png]
Step 3: Use that index to find the 132nd entry in the Symbol Table. Its Data value `000000AA` is an offset.
[image: FishHookMachO3.png]
Step 4: In the String Table, the first position `0000CFE4` plus offset `0xAA` equals `0xD08E`, which is the symbol name location corresponding to `NSLog`.
[image: FishHookMachO4.png]
Functions like `NSLog`, `dispatch_once`, etc., use stubs that point to Lazy Symbol Pointers, which in turn point to a stub_helper and ultimately to `dyld_stub_binder`. The real address is resolved on the first call.
fishhook leverages this behavior by replacing entries in the Lazy Symbol Pointers with addresses of custom functions to achieve hooks. This is why fishhook cannot hook C functions defined inside the same binary.
What fishhook actually does: it modifies the system symbol table entries so specific symbols' addresses are replaced with custom function addresses—i.e., it hooks external C functions that are dynamically bound.
Therefore fishhook cannot hook user-defined C functions inside the same Mach-O, because those functions are not called via the symbol binding process that fishhook manipulates. User functions are directly linked within the binary rather than resolved through the dynamic symbol pointers.