1. C
Memory application and release
- The memory after malloc is not initialized, you need to call memset to implement, calloc does not need (alloc + init)
- realloc will apply for a new piece of memory, and after copying the content, release the original memory space
void allocInit() {
char *str = malloc(sizeof(char)*5);
memset(str, 0, sizeof(char)*5);
/* 等价于:char *str = calloc(5, sizeof(chart)); */
strcpy(str, "abcde"); // 拷贝:abcde
str = (char *)realloc(str, sizeof(char)*10);
strcat(str, "fghij"); // 拼接:fghij
free(str);
str = NULL; // 释放内存并赋为NULL,防止野指针
}
placeholder for int | char array
int nums[10];
char str[10];
数组未填充会有占位符
int数组占位符:0 | 随机数字
char数组占位符:'\0'
Structure struct use (struct type + struct pointer)
typedef struct sPersonInfo {
char name[20];
char telNumber[20];
int age;
} PersonInfo;
typedef struct sList {
PersonInfo infos[20];
int count;
} List;
void test() {
PersonInfo personInfo;
strcpy(personInfo.telNunber, "13676399598");
strcpy(personInfo.name, "刘帅");
personInfo.age = 27;
list* list = (List *)malloc(sizeof(List));
memset(list, 0, sizeof(List));
list->infos[0] = personInfo;
list->count = 1;
free(list);
list = NULL;
}
References to Xcode projects
Actual referenced code example (demo.h/demo.c)
#ifdef __cplusplus
extern "C" {
#endif
extern int demo_main(int argc, char * argv[]);
#ifdef __cplusplus
}
#endif
#include "demo.h"
Two, OC
APP Constructor & Destructor
__attribute__((constructor)) Constructor declaration (called before APP enters main) - self-registration implementation of module startup items for iOS cold start optimization
#include <mach-o/dyld.h>
static void dyld_register_func(const struct mach_header *mh, intptr_t vmaddr_slide) {
// 实现模块自注册
}
__attribute__((constructor))
static void load_file(void) {
_dyld_register_func_for_add_image(dyld_register_func);
}
__attribute__((destructor)) Destructor declaration (called before the APP process is killed)
__attribute__((destructor))
static void exit_app(void) {
// 监听APP被杀掉的频率
}
Using NSInvocation (argument & return)
+ (BOOL)invokeTarget:(id)target
action:(_Nonnull SEL)selector
arguments:(NSArray* _Nullable )arguments
returnValue:(void* _Nullable)result; {
if (target && [target respondsToSelector:selector]) {
NSMethodSignature *signature = [target methodSignatureForSelector:selector];
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];
[invocation setTarget:target];
[invocation setSelector:selector];
for (NSUInteger i = 0; i<[arguments count]; i++) {
NSUInteger argIndex = i+2;
id argument = arguments[i];
if ([argument isKindOfClass:NSNumber.class]) {
//convert number object to basic num type if needs
NSNumber *num = (NSNumber*)argument;
const char *type = [signature getArgumentTypeAtIndex:argIndex];
// 目前协议方法只需要支持bool、integer与float
if (strcmp(type, @encode(BOOL)) == 0) {
BOOL rawNum = [num boolValue];
[invocation setArgument:&rawNum atIndex:argIndex];
continue;
} else if (strcmp(type, @encode(int)) == 0
|| strcmp(type, @encode(short)) == 0
|| strcmp(type, @encode(long)) == 0) {
NSInteger rawNum = [num integerValue];
[invocation setArgument:&rawNum atIndex:argIndex];
continue;
} else if (strcmp(type, @encode(float)) == 0) {
float rawNum = [num floatValue];
[invocation setArgument:&rawNum atIndex:argIndex];
continue;
}
}
if ([argument isKindOfClass:[NSNull class]]) {
argument = nil;
}
[invocation setArgument:&argument atIndex:argIndex];
}
[invocation invoke];
NSString *methodReturnType = [NSString stringWithUTF8String:signature.methodReturnType];
if (result && ![methodReturnType isEqualToString:@"v"]) { //if return type is not void
if([methodReturnType isEqualToString:@"@"]) { //if it's kind of NSObject
CFTypeRef cfResult = nil;
[invocation getReturnValue:&cfResult]; //this operation won't retain the result
if (cfResult) {
CFRetain(cfResult); //we need to retain it manually
*(void**)result = (__bridge_retained void *)((__bridge_transfer id)cfResult);
}
} else {
[invocation getReturnValue:result];
}
}
return YES;
}
return NO;
}
Class / Protocol get method and property
Determine whether to declare a protocol, and how to implement it
- (void)verifyFunction {
// 是否符合协议 & 是否响应方法
[NSArray conformsToProtocol:@protocol(NSObject)];
[NSArray respondsToSelector:@selector(isEqual:)];
}
Class acquisition methods, properties and protocols
- (void)classMethods_Propertys_Protocols {
unsigned int count;
Class class = NSClassFromString(@"NSArray");
/* 等价于:[NSArray class] */
// 获取Class的方法
Method *classMethodList = class_copyMethodList(class, &count);
for (int i=0; i<count; i++) {
Method classMethod = classMethodList[i];
NSLog(@"%@", NSStringFromSelector(method_getName(classMethod)));
}
free(classMethodList);
// 获取Class的属性
objc_property_t *classPropertyList = class_copyPropertyList(class, &count);
for (int i=0; i<count; i++) {
objc_property_t classProperty = classPropertyList[i];
NSLog(@"%s", property_getName(classProperty));
}
free(classPropertyList);
// 获取Class的协议
Protocol * __unsafe_unretained *classProtocolList = class_copyProtocolList(class, &count);
for (int i=0; i<count; i++) {
Protocol *protocol = classProtocolList[i];
NSLog(@"%@", NSStringFromProtocol(protocol));
}
free(classProtocolList);
}
Protocol acquisition method and attribute
- (void)protocolMethods_Propertys {
unsigned int count;
Protocol* protocol = NSProtocolFromString(@"NSObject");
/* 等价于:@protocol(NSObject) */
// 获取Protocol的方法
struct objc_method_description *protocolMethodList = protocol_copyMethodDescriptionList(protocol, NO, YES, &count);
for (int i=0; i<count; i++) {
struct objc_method_description protocolMethod = protocolMethodList[i];
NSLog(@"%@", NSStringFromSelector(protocolMethod.name));
}
free(protocolMethodList);
// 获取Protocol的属性
objc_property_t *protocolPropertyList = protocol_copyPropertyList(protocol, &count);
for (int i=0; i<count; i++) {
objc_property_t protocolProperty = protocolPropertyList[i];
NSLog(@"%s", property_getName(protocolProperty));
}
free(protocolPropertyList);
}
Category attribute lazy loading
@interface UIViewController (Category)
@property (nonatomic, strong) NSMutableArray *datas;
@end
static const void *datasKey = &datasKey;
@implementation UIViewController (Category)
- (NSMutableArray *)datas {
NSMutableArray *datas = objc_getAssociatedObject(self, datasKey);
if (!datas) {
datas = [[NSMutableArray alloc] init];
[self setDatas:datas];
}
return datas;
}
- (void)setDatas:(NSMutableArray *)datas {
objc_setAssociatedObject(self, datasKey, datas, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
@end
objc_class structure
struct objc_class {
Class isa // 所属类的指针
Class super_class // 指向父类的指针
const char *name // 类名
struct objc_ivar_list *ivars // 成员变量列表
struct objc_method_list **methodLists // 方法列表
struct objc_protocol_list *protocols // 协议列表
}
Crash analysis - memory address
Memory address: hexadecimal, Mach-O original memory address.
Memory address = Hex (decimal of offset address - offset)
Open Hopper Disassembler , import the Mach-O file, and lock the specific position through the offset .
Using Hopper for assembly-based iOS-Crash stack analysis
iOS system version Frameworks path : ~/Library/Developer/Xcode/iOS DeviceSupport/16.1.2 (20B110) arm64e/Symbols/System/Library/Frameworks/UIKit.framework
file memory mapping mmap
mmap - memory mapping, the most commonly used scenario is MMKV (mmap key value) - the application of MMKV in iOS , followed by log printing. Store data through the memory map provided by the system, which can avoid data loss caused by APP being killed | crash.
mmap maps the address information of the file on the disk with the virtual logical address used by the process. The process of establishing the mapping is different from ordinary memory reading: normally, the file is copied to the memory, and mmap only establishes the mapping without loading the file into in memory.
Spinlocks and mutexes
Mutex: The caller judges whether the holder is available, and terminates if it is not available. The mutex will cause the caller to sleep.
- pthread_mutex
- NSLock
- NSRecursiveLock
- NSConditionLock
- @synchronized
Spin lock: The caller will keep looping to determine whether the holder has been released. If it has been released to execute the next lock, the spin lock will not cause the caller to sleep, but it consumes more CPU resources (atomic operation).
- atomic
- dispatch_semaphore_t
OSSpinLock(with priority inversion problem)
// 读写锁-多读单写 (pthread_rwlock | dispatch_barrier)
pthread_rwlock_t rwLock;
pthread_rwlock_init(&rwLock, NULL);
pthread_rwlock_rdlock(&rwLock);
pthread_rwlock_unlock(&rwLock);
pthread_rwlock_wrlock(&rwLock);
pthread_rwlock_unlock(&rwLock);
pthread_rwlock_destroy(&rwLock);
HTTP requests bypass the global proxy
connectionProxyDictionary is set to an empty dictionary, canceling the global proxy and making the packet capture tool invalid
NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
configuration.connectionProxyDictionary = @{};
HTTPS certificate configuration and verification
Handle the callback method corresponding to URLSession
- (void)URLSession:(NSURLSession *)session
task:(NSURLSessionTask *)task
didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge
completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler {
NSURLSessionAuthChallengeDisposition disposition = NSURLSessionAuthChallengePerformDefaultHandling;
NSURLCredential *credential = nil;
if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) {
// 校验HTTPS服务器证书与域名的一致性,IP直连:域名在转换IP前存于header中,取出'域名'与'证书'进行关联
if ([self evaluateServerTrust:challenge.protectionSpace.serverTrust forDomain:challenge.protectionSpace.host]) {
credential = [[NSURLCredential alloc] initWithTrust:challenge.protectionSpace.serverTrust];
// 使用证书
disposition = NSURLSessionAuthChallengeUseCredential;
} else {
// 取消身份验证质询,请求失败
disposition = NSURLSessionAuthChallengeCancelAuthenticationChallenge;
}
} else if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodClientCertificate]) {
/* 配置P12证书,开启双向认证
credential =[NSURLCredential credentialWithIdentity:identity certificates:(__bridge NSArray*)certArray persistence:NSURLCredentialPersistencePermanent];
completionHandler(NSURLSessionAuthChallengeUseCredential, credential);
*/
} else {
// 执行默认处理
disposition = NSURLSessionAuthChallengePerformDefaultHandling;
}
if (completionHandler) {
completionHandler(disposition, credential);
}
}
Verify whether the server is trustworthy, add the switch to allow invalid certificates and local certificate verification
static BOOL serverTrustIsVaid(SecTrustRef serverTrust) {
SecTrustResultType result;
SecTrustEvaluate(serverTrust, &result);
BOOL isValid = (result == kSecTrustResultUnspecified || result == kSecTrustResultProceed);
return isValid;
}
static BOOL allowInvalidCertificates = YES;
static BOOL SSLPinningCertificate = NO;
static BOOL validatesDomainName = YES;
Associate the domain name with the certificate issued by the server to implement the verification logic of the cer certificate or public key
- (BOOL)evaluateServerTrust:(SecTrustRef)serverTrust forDomain:(NSString *)domain {
NSMutableArray *policies = [NSMutableArray array];
if (domain) {
if (validatesDomainName) {
// 域名校验
[policies addObject:(__bridge_transfer id)SecPolicyCreateSSL(true, (__bridge CFStringRef)domain)];
} else {
// 不进行域名校验
[policies addObject:(__bridge_transfer id)SecPolicyCreateBasicX509()];
}
} else {
// 没有绑定domain不会进行域名校验
[policies addObject:(__bridge_transfer id)SecPolicyCreateBasicX509()];
}
SecTrustSetPolicies(serverTrust, (__bridge CFArrayRef)policies);
if (SSLPinningCertificate == NO) {
// 允许无效证书,或服务器可信任,返回YES
return allowInvalidCertificates || serverTrustIsVaid(serverTrust);
} else if (!allowInvalidCertificates && !serverTrustIsVaid(serverTrust)) {
// 不允许无效证书,且服务器不可信,返回NO
return NO;
}
// 单向认证防止中间人攻击:获取本地cer证书集合,判断是否包含服务器下发的证书(serverTrust),不匹配则取消身份验证质询
NSArray *pinnedCertificates = @[];
NSArray *serverCertificates = @[];
BOOL isContains = NO;
for (NSData *trustChainCertificate in [serverCertificates reverseObjectEnumerator]) {
if ([pinnedCertificates containsObject:trustChainCertificate]) {
isContains = YES;
break;
}
}
if (isContains) return YES;
/*
NSArray *pinnedPublicKeys = @[];
NSArray *serverPublicKeys = @[];
BOOL isContains = NO;
for (NSData * publicKey in [serverPublicKeys reverseObjectEnumerator]) {
if ([pinnedPublicKeys containsObject:publicKey]) {
isContains = YES;
break;;
}
}
if (isContains) return YES;
*/
return NO;
}
Masonry application
The use of mas_makeConstraints must be after addSubView , otherwise an assertion will be triggered.
mas_equalTo: The input parameter can be an id object or a number type. The input parameter is an id object, which is similar to equalTo, which represents a fixed pixel value for a number type, or this is a relative offset
[subView mas_makeConstraints:^(MASConstraintMaker *make) {
make.bottom.mas_equalTo(-10);
/* 等价于:make.bottom.equalTo(view).offset(-10); */
make.height.mas_equalTo(100);
}];
equalTo(id): view-reference view, viewAttribute-reference view constraint value, number-fixed pixel/relative offset
offset: offset (CGFloat type)
/* 等价调用示例 */
[subView mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.right.top.bottom.mas_equalTo(0);
/* 等价调用 */
make.edges.mas_equalTo(view);
make.left.right.top.bottom.equalTo(view);
make.left.right.top.bottom.equalTo(@0);
make.bottom.mas_equalTo(-10);
/* 等价调用 */
make.bottom.equalTo(view).offset(-10);
make.bottom.equalTo(@(-10));
make.bottom.equalTo(view.mas_bottom).offset(-10);
}];
/* subView常规布局约束示例 */
[subView mas_makeConstraints:^(MASConstraintMaker *make) {
/* view中有多个subview的情况,边距设置最好是有参照视图,除非subView是最靠近view边沿的位置 */
/* subView上方有topView,不推荐这样使用:make.top.equalTo(@12); */
make.top.equalTo(topView.mas_bottom).offset(12);
make.width.mas_equalTo(140);
make.height.mas_equalTo(48);
make.centerX.mas_equalTo(view.mas_centerX).offset(10);
}];
/* 通过subView约束决定view的高度 */
[subView mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.left.equalTo(view).offset(12);
make.right.equalTo(view).offset(-12);
make.height.mas_equalTo(351);
// 由subView适配view的高度,view不能设置高度,否则后有约束冲突。
make.bottom.equalTo(view).offset(-10);
}];
Add/update constraints using mas_updateConstraints
// 更新约束
[subView mas_updateConstraints:^(MASConstraintMaker *make) {
make.bottom.equalTo(view).offset(-15);
}];
[view setNeedsUpdateConstraints];
Add/update/remove constraints using mas_remakeConstraints
// 先删除再添加约束
[subView mas_remakeConstraints:^(MASConstraintMaker *make) {
make.top.left.equalTo(view).offset(20);
make.right.equalTo(view).offset(-20);
make.height.mas_equalTo(335);
make.bottom.equalTo(view).offset(-20);
}];
[view setNeedsUpdateConstraints];
AutoLayout layout update: After the setNeedsUpdateConstraints flag needs to update the layout, you can call updateConstraintsIfNeeded to execute immediately, or you can wait for the mainRunLoop of the main thread to trigger the execution.
- (void)setNeedsUpdateConstraints 标记需要进行重新布局
- (void)updateConstraintsIfNeeded 调用此方法,如果有标记为需要重新布局的约束,则立即进行重新布局,内部会调用updateConstraints方法
- (void)updateConstraints 重写此方法,内部实现自定义布局过程
- (BOOL)needsUpdateConstraints 当前是否需要重新布局,内部会判断当前有没有被标记的约束
ReactiveObjC application
1. RACChannelTo : realize the two-way binding of view-model;
/// 双向绑定
RACChannelTo(_textField, text) = RACChannelTo(_model, title);
2. RACObserve : Subscribe to the change signal of the attribute and realize the one-way binding of model-view;
/// 订阅textField.text的变化信号
[RACObserve(_textField, text) subscribeNext:^(id x) {
}];
/// 订阅textField.text的变化信号
[_textField.rac_textSignal subscribeNext:^(id x) {
}];
/// lable订阅了textField的文本变化信号
RAC(_label, text) = _textField.rac_textSignal;
/// 实现model-view的单向绑定
RAC(_label, text) = RACObserve(self, title);
Listen to Notification notification events
[[[NSNotificationCenter defaultCenter] rac_addObserverForName:@"kNotificationLogin" object:nil] subscribeNext:^(NSNotification* notification) {
NSLog(@"notification_name: %@", notification.name);
}];
3. RACSubject : subscribe to signals, send signals, used to replace or enhance the native delegate and block capabilities;
RACSubject* subject = [RACSubject subject];
/// 订阅信号
[subject subscribeNext:^(id x) {
}];
/// 发送信号
[subject sendNext:nil];
4. RACCommand : Realize data acquisition (create signal, subscribe signal, subscribe execution status, subscribe exception, execute command, send signal, send exception, end signal);
@weakify(self);
RACCommand* command = [[RACCommand alloc] initWithSignalBlock:^RACSignal *(id input) {
/// 创建信号
@strongify(self);
return [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
/// 执行异步请求
@strongify(self);
[self GET:URLString success:^(NSDictionary* response) {
[subscriber sendNext:response]; /// 发送信号
[subscriber sendCompleted]; /// 结束信号
} failure:^(NSError *error) {
[subscriber sendError:error]; /// 发送异常
[subscriber sendCompleted]; /// 结束信号
}];
return [RACDisposable disposableWithBlock:^{
/// 信号已销毁
}];
}];
}];
/// 订阅每个信号
[command.executionSignals subscribeNext:^(NSDictionary *response) {
}];
/// 订阅completed前的最后一个信号
[command.executionSignals.switchToLatest subscribeNext:^(NSDictionary *response) {
}];
/// 订阅每个异常
[command.errors subscribeNext:^(NSError *error) {
}];
/// 订阅completed前的最后一个异常
[command.errors.switchToLatest subscribeNext:^(NSError *error) {
}];
/// 订阅信号的执行状态
[command.executing subscribeNext:^(NSNumber *boolNum) {
if ([boolNum boolValue]) {
/// 还在执行
} else {
/// 执行结束
}
}];
/// 执行命令
[command executing];
Get the bundle object
NSString *bundlePath = [[NSBundle mainBundle] pathForResource:@"app-image"ofType:@"bundle"];
NSBundle *resourceBundle = [NSBundle bundleWithPath:bundlePath];
Platform differences of image MD5
Header information is added to iOS image metadata, which makes the metadata MD5 signature inconsistent with other platforms.
3. JS
Constructor
Declare the properties and methods of the constructor
function MyClass() {
this.myProperty = "it is a property!";
this.call = function(parameter) {
// call方法实现
console.log(parameter);
};
}
Use prototype to implement constructor inheritance
function SuperClass() {
this.value = "it is a value!";
let _this = this;
this.setValue = function(value) {
_this.value = value;
console.log(_this.value);
};
}
MyClass.prototype = new SuperClass();
Use prototype to implement the extension method of the constructor
MyClass.prototype.callBack = function(result) {
// callBack方法实现
console.log(this.myProperty);
console.log(result);
};
Create an instance of the constructor and execute the method
let instance = new MyClass();
instance.call(JSON.stringify({title: '标题', content: '内容'})); // 内部方法
instance.setValue('lt is a new value!'); // 继承
instance.callBack(JSON.stringify({code: 0, message: 'success', data: {}})); // 扩展
Mount the method on the instance, call the method
instance.handleMessage = function(message) {
// 处理消息
console.log(message);
};
instance.handleMessage(JSON.stringify({type: 'notify', payload: {}}));