一、C
内存申请与释放
- malloc后的内存没有初始化,需要调用memset实现,calloc则不需要(alloc + init)
- realloc会申请一块新的内存,并在拷贝内容后,释放掉原有的内存空间
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,防止野指针
}
int | char数组的占位符
int nums[10];
char str[10];
数组未填充会有占位符
int数组占位符:0 | 随机数字
char数组占位符:'\0'
结构体struct使用(struct类型 + struct指针)
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;
}
Xcode工程的引用
实际引用的代码示例(demo.h / demo.c)
#ifdef __cplusplus
extern "C" {
#endif
extern int demo_main(int argc, char * argv[]);
#ifdef __cplusplus
}
#endif
#include "demo.h"
二、OC
APP构造函数 & 析构函数
__attribute__((constructor)) 构造函数声明(在APP进入main之前调用)- iOS冷启动优化之模块启动项自注册实现
#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)) 析构函数声明(在APP进程被杀掉之前调用 )
__attribute__((destructor))
static void exit_app(void) {
// 监听APP被杀掉的频率
}
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 获取方法与属性
判断是否声明协议,以及实现方法
- (void)verifyFunction {
// 是否符合协议 & 是否响应方法
[NSArray conformsToProtocol:@protocol(NSObject)];
[NSArray respondsToSelector:@selector(isEqual:)];
}
Class获取方法、属性与协议
- (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获取方法、属性
- (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属性懒加载
@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结构
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分析-内存地址
内存地址:16进制,Mach-O原始的内存地址。
内存地址 = Hex(偏移地址的十进制 - 偏移量)
打开Hopper Disassembler,导入Mach-O文件,通过偏移量锁定具体的位置。
iOS系统版本Frameworks路劲:~/Library/Developer/Xcode/iOS DeviceSupport/16.1.2 (20B110) arm64e/Symbols/System/Library/Frameworks/UIKit.framework
文件内存映射mmap
mmap-内存映射,最常用到的场景是MMKV(mmap key value)-MMKV在iOS中的应用,其次用到的是日志打印。通过系统提供的内存映射储存数据,可以避免APP被杀掉 | crash导致数据丢失。
mmap将磁盘上文件的地址信息与进程用的虚拟逻辑地址进行映射,建立映射的过程与普通的内存读取不同:正常的是将文件拷贝到内存,mmap只是建立映射而不会将文件加载到内存中。
自旋锁与互斥锁
互斥锁:调用者判断保持者是否可用,不可用就终止,互斥锁会引起调用者休眠。
- pthread_mutex
- NSLock
- NSRecursiveLock
- NSConditionLock
- @synchronized
自旋锁:调用者会一直循环判断保持者是否已释放,若已释放执行下一个锁,自旋锁不会引起调用者休眠,但较为消耗CPU资源(原子操作)。
- atomic
- dispatch_semaphore_t
OSSpinLock(存在优先级反转问题)
// 读写锁-多读单写 (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请求绕过全局代理
connectionProxyDictionary设置为空字典,取消全局代理,使抓包工具无效
NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
configuration.connectionProxyDictionary = @{};
HTTPS证书配置与校验
处理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);
}
}
校验服务器是否可信任,添加允许无效证书与本地证书校验开关
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;
为服务器下发的证书关联域名,实现cer证书或公钥的校验逻辑
- (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应用
mas_makeConstraints的使用必须在addSubView之后,否则会触发断言。
mas_equalTo:入参可以是id对象,也可以是数字类型,入参为id对象就类似于equalTo,为数字类型表示固定的像素值,或这是相对偏移量
[subView mas_makeConstraints:^(MASConstraintMaker *make) {
make.bottom.mas_equalTo(-10);
/* 等价于:make.bottom.equalTo(view).offset(-10); */
make.height.mas_equalTo(100);
}];
equalTo(id):view-参照视图,viewAttribute-参照视图约束值,number-固定像素/相对偏移量
offset:偏移量(CGFloat类型)
/* 等价调用示例 */
[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);
}];
使用mas_updateConstraints添加/更新约束
// 更新约束
[subView mas_updateConstraints:^(MASConstraintMaker *make) {
make.bottom.equalTo(view).offset(-15);
}];
[view setNeedsUpdateConstraints];
使用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布局更新:setNeedsUpdateConstraints标记需要更新布局后,可以调用updateConstraintsIfNeeded立马执行,也可以等主线程的mainRunLoop来触发执行。
- (void)setNeedsUpdateConstraints 标记需要进行重新布局
- (void)updateConstraintsIfNeeded 调用此方法,如果有标记为需要重新布局的约束,则立即进行重新布局,内部会调用updateConstraints方法
- (void)updateConstraints 重写此方法,内部实现自定义布局过程
- (BOOL)needsUpdateConstraints 当前是否需要重新布局,内部会判断当前有没有被标记的约束
ReactiveObjC应用
1. RACChannelTo:实现view-model的双向绑定;
/// 双向绑定
RACChannelTo(_textField, text) = RACChannelTo(_model, title);
2. RACObserve:订阅属性的变化信号、实现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);
监听Notification通知事件
[[[NSNotificationCenter defaultCenter] rac_addObserverForName:@"kNotificationLogin" object:nil] subscribeNext:^(NSNotification* notification) {
NSLog(@"notification_name: %@", notification.name);
}];
3. RACSubject:订阅信号、发送信号,用来代替或者增强原生的delegate与block能力;
RACSubject* subject = [RACSubject subject];
/// 订阅信号
[subject subscribeNext:^(id x) {
}];
/// 发送信号
[subject sendNext:nil];
4. RACCommand:实现数据获取(创建信号、订阅信号、订阅执行状态、订阅异常、执行命令、发送信号、发送异常、结束信号);
@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];
获取bundle对象
NSString *bundlePath = [[NSBundle mainBundle] pathForResource:@"app-image"ofType:@"bundle"];
NSBundle *resourceBundle = [NSBundle bundleWithPath:bundlePath];
图片MD5的平台差异
iOS图片元数据添加了头信息,导致元数据MD5签名和其他平台不一致。
三、JS
构造函数
声明构造函数的属性与方法
function MyClass() {
this.myProperty = "it is a property!";
this.call = function(parameter) {
// call方法实现
console.log(parameter);
};
}
使用prototype实现构造函数的继承
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();
使用prototype实现构造函数的扩展方法
MyClass.prototype.callBack = function(result) {
// callBack方法实现
console.log(this.myProperty);
console.log(result);
};
创建构造函数的实例并执行方法
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: {}})); // 扩展
在实例上挂载方法,调用方法
instance.handleMessage = function(message) {
// 处理消息
console.log(message);
};
instance.handleMessage(JSON.stringify({type: 'notify', payload: {}}));