一、OC
APP构造函数/析构函数
__attribute__((constructor)) 构造函数声明(在APP进入main之前调用)
#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被杀掉的频率
}
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查看/导出
1、通过Xcode - Window - Organizer - Crashes,查看用户上报的 Crash 日志。
2、通过 Xcode - Window - Devices and Simulators - View Device Logs,导出设备的 Crash 日志。
Crash分析
1. 堆栈信息
内存地址:Mach-O 原始的内存地址,16进制,Hex(偏移地址的十进制 - 偏移量)
2. Mach-O文件
应用的 Mach-O 路径:解压ipa包,/Payload/ProductName/ProductName
系统 framework 的 Mach-O 路劲:~/Library/Developer/Xcode/iOS DeviceSupport/16.5 (20F66) arm64e/Symbols/System/Library/Frameworks/UIKit.framework
3. Hopper应用
打开 Hopper Disassembler,导入 Mach-O 文件,通过偏移量锁定调用栈信息,结合调用栈的顺序来分析 Crash 的具体原因。
try-catch应用
iOS 的 try-catch 只对 NSException 异常有效,用于防护可能出现 NSException 异常的代码块,如第三方库集成、系统 API 调用异常。在 ARC 机制下使用 try-catch 捕获异常,try-catch 中的代码存在内存泄漏的问题,应当减少 try-catch 的使用。
- (void)pushViewController:(UIViewController *)viewController animated:(BOOL)animated {
@try {
[super pushViewController:viewController animated:animated];
} @catch (NSException *exception) {
NSLog(@"页面堆栈异常");
}
}
方法参数的传递方式
OC 常见的参数传递方式:值传递、指针传递。
值传递:将参数的值复制一份传递给方法 ,方法内部对参数进行修改不会影响到原始值。
int num = 10;
[self valuePass:num];
- (void)valuePass:(int)num {
NSLog(@"%d", num);
}
指针传递:将参数的指针传递给方法,方法内部对参数进行修改会影响到原始值。
int num = 10;
[self pointerPass:&num];
- (void)pointerPass:(int *)num {
*num = 20;
}
GCD队列的应用场景
在使用 GCD 时,可以根据任务的性质和需求来选择是使用全局并发队列还是自定义的队列。
使用全局并发队列的情况:
-
同时执行多个独立的任务,全局并发队列能够充分利用系统资源。
-
执行大量的计算或者涉及大量的 I/O 操作,全局并发队列能够更好地利用系统资源,并根据系统情况动态调整并发数。
-
使用全局并发队列中的高优先级队列,确保这些任务得到优先处理,提高响应速度。
使用自定义队列的情况:
-
需要按照特定的顺序执行任务,使用自定义的串行队列。
-
需要对并发执行的任务进行更精细的控制(组合、栏栅化等),使用自定义的并发队列。
总的来说,全局并发队列适用于大多数常见的并发任务,而自定义队列更适合满足特定需求、对队列行为进行更精细控制或实现任务之间的依赖关系。
虚拟内存与物理内存
内存可以分为虚拟内存和物理内存,物理内存是实际占用的内存,虚拟内存是在物理内存之上建立的一层逻辑地址,保证内存访问安全的同时为应用提供了连续的地址空间。
物理内存和虚拟内存以页为单位映射,一页物理内存可能对应多页虚拟内存;一页虚拟内存也可能不占用物理内存。
Linux常规文件操作
Linux 系统为了提高 I/O 效率,以及硬件设备的使用寿命,在用户空间和内核空间都加入缓冲区 buffer,可以降低 I/O 读写和系统调用的频次。
从系统调用角度,用户进程读数据时,先从设备读取数据到内核缓冲区,再拷贝到用户缓冲区;用户进程写数据,先把用户缓冲数据拷贝到内核缓冲区,再写入设备。
从内存映射角度,内核空间通过 MMU 分配物理内存,读取磁盘数据,拷贝到内核缓冲区对应的物理内存,再拷贝到用户缓冲区对应的物理内存,通过 MMU 实现物理内存地址与虚拟内存地址的映射。
内存管理单元MMU
进程访问的页表存储在内核空间,内存管理单元-MMU 将虚拟内存地址转换成物理内存地址。
当进程访问一个虚拟内存Page对应的物理内存 Page 不存在时,系统会产生一个缺页中断(Page Fault),由内核空间分配物理内存、更新进程页表,恢复进程的运行。
文件内存映射mmap
mmap - 文件内存映射,最常用到的场景是 MMKV(mmap key value)- MMKV在iOS中的应用,其次用到的是日志打印。通过系统提供的内存映射储存数据,可以避免 APP 被杀掉 | crash 导致数据丢失 - iOS的文件内存映射mmap
mmap 将磁盘上文件的地址信息与进程用的虚拟逻辑地址进行映射,建立映射的过程与普通的内存读取不同:正常的是将文件拷贝到内存,mmap 只是建立映射而不会将文件加载到内存中。
动态库与静态库
1. 两者差异
动态库在 APP 启动时通过动态链接器加载到内存,动态库可以多程序共用,节省内存,系统提供的大多数 Framework 和 dylib 均为动态库。
静态库在编译/打包时拷贝至可执行文件中,模块分工,少量改动不会造成大量的重复编译,有 .framework 和 .a 两种格式。
动态库 - Dynamic Library | 静态库 - Static Library | |
---|---|---|
命名空间 | 有独立的命名空间,不同库相同的命名不会冲突,使用 #import<xxx/xxx.h> 的方式引入 | 没有独立的命名空间,不同库相同的命名会冲突,引入方式 #import"xxx.h" |
加载时机 | 在APP启动时,通过Dyld加载到内存中 | 在编译构建时,完整拷贝至可执行文件中 |
依赖关系 | 可以依赖动态库,不能依赖静态库 | 可以依赖动态库和静态库 |
2. 编译构建
iOS常见线程锁
互斥锁:调用者判断保持者是否可用,不可用就终止,互斥锁会引起调用者休眠。
- pthread_mutex_t
- dispatch_sync(serial_queue)
- NSLock
- @synchronized
自旋锁:调用者会循环判断保持者是否已释放,若已释放执行下一个锁,自旋锁不会引起调用者休眠,但较为消耗 CPU 资源。
- os_unfair_lock
OSSpinLock(存在优先级反转问题)- atomic
信号量:有时被称为信号灯,是在多线程环境下使用的一种设施,是可以用来保证两个或多个关键代码段不被并发调用。
- dispatch_semaphore_t
递归锁:在多次被同一个线程进行加锁时,不会造成死锁,主要用在循环或递归操作中。
- pthread_mutex_t(recursive)
- NSRecursiveLock
读写锁:一种用于解决读写冲突的线程同步机制,实现文件或数据的多读单写 / 先写后读。
- dispatch_barrier
- pthread_rwlock_t
条件锁:条件是信号量的另一种类型,当某个条件为 true 时,它允许线程相互发信号。
- pthread_cond_t
- NSCondition
- NSConditionLock
死锁是指两个及两个以上的进程 / 线程在运行过程中因争夺资源而造成的一种僵局。
// 死锁场景1
_lock = [[NSLock alloc] init];
[_lock lock];
[_lock lock];
// 死锁场景2
dispatch_sync(dispatch_get_main_queue(), ^{
});
// 死锁场景3
_lock = [[NSLock alloc] init];
for (int i=0; i<n; i++) {
dispatch_async(dispatch_get_global_queue(0, 0), ^{
[_lock lock];
// 长时间占用锁资源
[_lock unlock];
});
}
UIImage裁剪/缩放
苹果官方推荐使用 CoreGraphics 或 ImageIO 实现图片的裁剪/缩放,两者在执行时耗和内存开销的性能更优,ImageIO 在缩略过程中,不会生成对应的 bitmap,大大降低了瞬时峰值。
1. UIKit
- (UIImage *)resizeImage:(UIImage *)originImage size:(CGSize)size {
UIGraphicsBeginImageContext(size);
[originImage drawInRect:CGRectMake(0, 0, size.width, size.height)];
UIImage *resizeImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return resizeImage;
}
2. CoreGraphics
- (UIImage *)resizeImage:(UIImage *)originImage size:(CGSize)size {
CGImageRef imageRef = originImage.CGImage;
size_t width = size.width;
size_t height = size.height;
size_t bitsPerComponent = CGImageGetBitsPerComponent(imageRef);
size_t bytesPerRow = CGImageGetBytesPerRow(imageRef);
CGColorSpaceRef space = CGImageGetColorSpace(imageRef);
uint32_t bitmapInfo = CGImageGetBitmapInfo(imageRef);
CGContextRef contextRef = CGBitmapContextCreate(NULL, width, height, bitsPerComponent, bytesPerRow, space, bitmapInfo);
CGContextDrawImage(contextRef, CGRectMake(0, 0, width, height), imageRef);
CGImageRef resizeImageRef = CGBitmapContextCreateImage(contextRef);
UIImage * resizeImage = [UIImage imageWithCGImage:resizeImageRef];
CGContextRelease(contextRef);
CGImageRelease(resizeImageRef);
return resizeImage;
}
3. ImageIO
- (UIImage *)resizeImage:(UIImage *)originImage size:(CGSize)size {
NSData *data = UIImagePNGRepresentation(originImage);
CGFloat maxPixelSize = MAX(size.width, size.height);
CGImageSourceRef imageSource = CGImageSourceCreateWithData((__bridge CFDataRef) data, NULL);
NSDictionary *options = @{ (__bridge id)kCGImageSourceCreateThumbnailWithTransform : @YES,
(__bridge id)kCGImageSourceCreateThumbnailFromImageAlways : @YES,
(__bridge id)kCGImageSourceThumbnailMaxPixelSize : @(maxPixelSize) };
CGImageRef resizeImageRef = CGImageSourceCreateThumbnailAtIndex(imageSource, 0, (__bridge CFDictionaryRef)options);
UIImage *resizeImage = [UIImage imageWithCGImage:resizeImageRef];
CFRelease(imageSource);
CGImageRelease(resizeImageRef);
return resizeImage;
}
Masonry应用
mas_equalTo:入参可以是 id 对象,也可以是数字类型,入参为 id 对象就类似于 equalTo,为数字类型表示固定的像素值,或这是相对偏移量。
equalTo:view-参照视图,viewAttribute-参照视图约束值,number-固定像素/相对偏移量。
offset:偏移量(CGFloat 类型)
使用 mas_makeConstraints 添加约束,设置约束的 view 需要先添加到 superview。
/* 等价调用示例 */
[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];
通过 setNeedsUpdateConstraints 标记需要更新布局,由主线程的 mainRunLoop 自动触发更新,或者调用 updateConstraintsIfNeeded 手动触发更新。
- (void)setNeedsUpdateConstraints 标记需要进行重新布局
- (void)updateConstraintsIfNeeded 调用此方法,如果有标记为需要重新布局的约束,则立即进行重新布局,内部会调用updateConstraints方法
- (void)updateConstraints 重写此方法,内部实现自定义布局过程
- (BOOL)needsUpdateConstraints 当前是否需要重新布局,内部会判断当前有没有被标记的约束
ReactiveObjC应用
RACChannelTo:实现 view-model 的双向绑定
// 双向绑定
RACChannelTo(_textField, text) = RACChannelTo(_model, title);
RACObserve:订阅属性的变化信号,实现 model-view 的单向绑定。
// 订阅textField.text的变化信号
[RACObserve(_textField, text) subscribeNext:^(id x) {
}];
// 订阅textField.text的变化信号
[_textField.rac_textSignal subscribeNext:^(id x) {
}];
// 订阅textField.text的变化信号,绑定label.text
RAC(_label, text) = _textField.rac_textSignal;
// 实现model-view的单向绑定
RAC(_label, text) = RACObserve(_viewModel.model, title);
监听 NSNotification 通知事件
[[[NSNotificationCenter defaultCenter] rac_addObserverForName:@"kNotificationLogin" object:nil] subscribeNext:^(NSNotification* notification) {
NSLog(@"notification_name: %@", notification.name);
}];
RACSubject:订阅信号、发送信号,用来代替或者增强原生的 delegate 与 block 能力。
RACSubject* subject = [RACSubject subject];
// 订阅信号
[subject subscribeNext:^(id x) {
}];
// 发送信号
[subject sendNext:nil];
通过 merge 把多个信号合并为一个信号,任何一个信号有新值的时候就会调用。
RACSubject* subject1 = [RACSubject subject];
RACSubject* subject2 = [RACSubject subject];
RACSignal *mergeSignal = [RACSignal merge:@[subject1, subject2]];
[mergeSignal subscribeNext:^(NSString * _Nullable x) {
NSLog(@"接收到subject%@的消息", x);
}];
[subject1 sendNext:@"1"];
[subject2 sendNext:@"2"];
通过 combineLatest 将多个信号合并起来,并且拿到各个信号的最新值;所有信号都必须有过一次 sendNext,才会触发 combineSignal 的订阅。
RACSubject* subject1 = [RACSubject subject];
RACSubject* subject2 = [RACSubject subject];
RACSignal *combineSignal = [RACSignal combineLatest:@[subject1, subject2]];
[combineSignal subscribeNext:^(RACTuple * _Nullable x) {
RACTupleUnpack(NSString *value1, NSString *value2) = x;
NSLog(@"接收到组合信号:(%@, %@)", value1, value2);
}];
[subject1 sendNext:@"1"];
[subject2 sendNext:@"2"];
RACSignal *combineSignal = [RACSignal combineLatest:@[RACObserve(_phoneInput, text), RACObserve(_codeInput, text)]];
RAC(_loginButton, enabled) = [combineSignal map:^id _Nullable(RACTuple * _Nullable value) {
// 元组分解
RACTupleUnpack(NSString *phoneNumber, NSString *verifyCode) = value;
return @(phoneNumber.length==11 && verifyCode.length==6);
}];
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]; // 发送异常,结束信号
}];
return [RACDisposable disposableWithBlock:^{
// 信号已销毁
}];
}];
}];
// 允许并发执行命令
command.allowsConcurrentExecution = YES;
// 订阅命令的执行状态(跳过第一次,executing信号一绑定就会'sendNext:@(NO)')
[command.executing skip:1] subscribeNext:^(NSNumber *boolNum) {
if ([boolNum boolValue]) {
// 还在执行
} else {
// 执行结束
}
}];
// 订阅命令的执行信号
[command.executionSignals.switchToLatest subscribeNext:^(NSDictionary *response) {
}];
// 订阅命令的执行异常
[command.errors subscribeNext:^(NSError *error) {
}];
/// 执行命令
[command execute:nil];
NSInvocation应用
+ (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(array)];
}
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
获取Bundle对象
// 工程 | pod库的bundle文件(pod 1.x)
NSString *bundlePath = [[NSBundle mainBundle] pathForResource:@"app-image"ofType:@"bundle"];
NSBundle *resourceBundle = [NSBundle bundleWithPath:bundlePath];
// pod库的bundle文件(pod 1.x和0.x)
NSString *bundlePath = [[NSBundle bundleForClass:self.class] pathForResource:@"lib-image"ofType:@"bundle"];
NSBundle *resourceBundle = [NSBundle bundleWithPath:bundlePath];
HTTP请求绕过全局代理
设置 connectionProxyDictionary 为空字典,取消全局代理,使抓包工具无效。
NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
configuration.connectionProxyDictionary = @{};
WebKit加载本地H5资源包
设置 allowFileAccessFromFileURLs,允许资源跨域访问。
[webView.configuration.preferences setValue:@(YES) forKey:@"allowFileAccessFromFileURLs"];
图片MD5的平台差异
iOS 图片元数据添加了头信息,导致元数据 MD5 签名和其他平台不一致。
二、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"
三、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: {}}));