现代移动应用的用户体验依赖于其稳定性和可靠性。然而,在开发过程中,我们时常会遇到各种崩溃问题。崩溃不仅会影响用户的使用体验,还可能损害应用的声誉。因此,本文将详细介绍一个名为CrashPrevention的工具类,它能够为iOS开发者提供多方面的崩溃预防措施,借助该工具类,开发者能够有效减少崩溃的发生,并提升应用的稳定性。

CrashPrevention工具类概述

CrashPrevention是一个易于集成的工具类,专为iOS应用中的多种常见崩溃情况提供预防措施。通过调用相关方法,开发者可以开启针对数组操作、字典操作、未识别的选择器、通知中心、键值观察(KVO)、字符串操作、多线程操作以及UI线程操作的保护机制。特别值得一提的是,CrashPrevention让开发者可以通过一个全局的 isDebug 标志,灵活控制是否启用这些崩溃预防措施。

CrashPrevention.h 头文件

首先,我们来看一下CrashPrevention的头文件,其中定义了所有的预防方法:

#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h> @interface CrashPrevention : NSObject // 设置是否在 Debug 模式中启用崩溃预防
+ (void)setDebugMode:(BOOL)isDebug; // 启用所有崩溃保护
+ (void)enableAllCrashPrevention; // 启用各类崩溃保护
+ (void)enableArrayProtection;
+ (void)enableDictionaryProtection;
+ (void)enableSelectorProtection;
+ (void)enableNotificationProtection;
+ (void)enableKVOCrashProtection;
+ (void)enableStringProtection;
+ (void)enableThreadSafetyProtection;
+ (void)enableUIThreadProtection; @end

CrashPrevention.m 实现文件

接下来,我们深入了解实现文件的设计思路和具体代码。

全局Debug标志

@implementation CrashPrevention

// 用于记录是否在 Debug 模式下启用崩溃预防
static BOOL debugModeEnabled = NO; // 设置是否在 Debug 模式中启用崩溃预防
+ (void)setDebugMode:(BOOL)isDebug {
debugModeEnabled = isDebug;
}

通过 static BOOL debugModeEnabled,我们可以记录是否启用调试模式,基于此标志决定是否启用崩溃预防功能。

启用所有崩溃保护

+ (void)enableAllCrashPrevention {
if (!debugModeEnabled) {
return;
}
[self enableArrayProtection];
[self enableDictionaryProtection];
[self enableSelectorProtection];
[self enableNotificationProtection];
[self enableKVOCrashProtection];
[self enableStringProtection];
[self enableThreadSafetyProtection];
[self enableUIThreadProtection];
}

该方法通过检查 debugModeEnabled 标志,决定是否依次启用各类崩溃保护。

数组越界保护

+ (void)enableArrayProtection {
if (!debugModeEnabled) {
return;
}
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
[self swizzleInstanceMethod:NSClassFromString(@"__NSArrayI")
original:@selector(objectAtIndex:)
swizzled:@selector(safe_objectAtIndex:)];
[self swizzleInstanceMethod:NSClassFromString(@"__NSArrayM")
original:@selector(objectAtIndex:)
swizzled:@selector(safe_objectAtIndex:)];
});
} - (id)safe_objectAtIndex:(NSUInteger)index {
if (index < self.count) {
return [self safe_objectAtIndex:index];
} else {
@try {
NSLog(@"Array index out of bound: %lu", (unsigned long)index);
} @catch (NSException *exception) {
// 处理异常
}
return nil;
}
}

通过 Method Swizzling,我们可以将数组的 objectAtIndex: 方法替换为安全版本。在越界的情况下,返回 nil 并记录日志,而不会崩溃。

字典键值检查保护

+ (void)enableDictionaryProtection {
if (!debugModeEnabled) {
return;
}
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
[self swizzleInstanceMethod:NSClassFromString(@"__NSDictionaryI")
original:@selector(objectForKey:)
swizzled:@selector(safe_objectForKey:)];
});
} - (id)safe_objectForKey:(id)key {
if (key) {
return [self safe_objectForKey:key];
} else {
@try {
NSLog(@"Attempted to access dictionary with nil key");
} @catch (NSException *exception) {
// 处理异常
}
return nil;
}
}

类似地,通过 Method Swizzling,我们可以将 objectForKey: 方法替换为安全版本,防止使用 nil 作为键值时的崩溃。

消息转发保护

+ (void)enableSelectorProtection {
if (!debugModeEnabled) {
return;
}
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Method forwardMethod = class_getInstanceMethod([self class], @selector(forwardingMethod:));
class_addMethod([self class], NSSelectorFromString(@"unrecognizedSelectorHandler"), method_getImplementation(forwardMethod), method_getTypeEncoding(forwardMethod));
});
} - (void)forwardingMethod:(SEL)aSelector {} + (BOOL)resolveInstanceMethod:(SEL)sel {
if (!debugModeEnabled) {
return [super resolveInstanceMethod:sel];
}
class_addMethod([self class], sel, class_getMethodImplementation([self class], @selector(forwardingMethod:)), "v@:");
return YES;
}

通过添加一个默认的 forwardingMethod: ,我们防止调用未实现的方法时崩溃。

通知中心保护

+ (void)enableNotificationProtection {
if (!debugModeEnabled) {
return;
}
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
[self swizzleInstanceMethod:[NSNotificationCenter class]
original:@selector(addObserver:selector:name:object:)
swizzled:@selector(safe_addObserver:selector:name:object:)];
[self swizzleInstanceMethod:[NSNotificationCenter class]
original:@selector(removeObserver:name:object:)
swizzled:@selector(safe_removeObserver:name:object:)];
});
} - (void)safe_addObserver:(NSObject *)observer selector:(SEL)aSelector name:(NSString *)aName object:(id)anObject {
if (observer) {
[[NSNotificationCenter defaultCenter] safe_addObserver:observer selector:aSelector name:aName object:anObject];
} else {
@try {
NSLog(@"Attempted to add a nil observer for name: %@", aName);
} @catch (NSException *exception) {
// 处理异常
}
}
} - (void)safe_removeObserver:(NSObject *)observer name:(NSString *)aName object:(id)anObject {
if (observer) {
[[NSNotificationCenter defaultCenter] safe_removeObserver:observer name:aName object:anObject];
} else {
@try {
NSLog(@"Attempted to remove a nil observer for name: %@", aName);
} @catch (NSException *exception) {
// 处理异常
}
}
}

在添加和移除观察者时进行空检查,防止空观察者导致的崩溃。

KVO 保护

+ (void)enableKVOCrashProtection {
if (!debugModeEnabled) {
return;
}
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
[self swizzleInstanceMethod:[NSObject class]
original:@selector(addObserver:forKeyPath:options:context:)
swizzled:@selector(safe_addObserver:forKeyPath:options:context:)];
[self swizzleInstanceMethod:[NSObject class]
original:@selector(removeObserver:forKeyPath:)
swizzled:@selector(safe_removeObserver:forKeyPath:)];
});
} - (void)safe_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(void *)context {
if (observer && keyPath) {
[self safe_addObserver:observer forKeyPath:keyPath options:options context:context];
} else {
@try {
NSLog(@"Attempted to add observer with nil observer or key path: %@", keyPath);
} @catch (NSException *exception) {
// 处理异常
}
}
} - (void)safe_removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath {
if (observer && keyPath) {
[self safe_removeObserver:observer forKeyPath:keyPath];
} else {
@try {
NSLog(@"Attempted to remove observer with nil observer or key path: %@", keyPath);
} @catch (NSException *exception) {
// 处理异常
}
}
}

在添加和移除KVO时进行必要检查,确保参数合法,防止崩溃。

字符串越界检查

+ (void)enableStringProtection {
if (!debugModeEnabled) {
return;
}
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
[self swizzleInstanceMethod:NSClassFromString(@"__NSCFConstantString")
original:@selector(substringFromIndex:)
swizzled:@selector(safe_substringFromIndex:)];
});
} - (NSString *)safe_substringFromIndex:(NSUInteger)from {
if (from <= self.length) {
return [self safe_substringFromIndex:from];
} else {
@try {
NSLog(@"String index out of bound: %lu", (unsigned long)from);
} @catch (NSException *exception) {
// 处理异常
}
return nil;
}
}

通过交换NSString的相关方法,确保在越界访问时返回 nil 并记录日志,从而避免崩溃。

线程安全保护

+ (void)enableThreadSafetyProtection {
if (!debugModeEnabled) {
return;
}
// 实现是与具体使用场景相关的,需要结合项目实际情况实现
}

这部分的实现高度依赖于具体的使用场景,比如可以使用 dispatch_barrier_asyncNSLock 等技术实现。

UI线程保护

+ (void)enableUIThreadProtection {
if (!debugModeEnabled) {
return;
}
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
[self swizzleInstanceMethod:[UIView class]
original:@selector(setNeedsLayout)
swizzled:@selector(safe_setNeedsLayout)];
});
} - (void)safe_setNeedsLayout {
if ([NSThread isMainThread]) {
[self safe_setNeedsLayout];
} else {
dispatch_async(dispatch_get_main_queue(), ^{
[self safe_setNeedsLayout];
});
@try {
NSLog(@"setNeedsLayout was called off the main thread. Fixed by dispatching to main queue.");
} @catch (NSException *exception) {
// 处理异常
}
}
}

确保UI操作总在主线程进行,如果不是,则调度到主线程执行,并记录警告日志。

方法交换

#pragma mark - Method Swizzling
+ (void)swizzleInstanceMethod:(Class)cls original:(SEL)originalSelector swizzled:(SEL)swizzledSelector {
Method originalMethod = class_getInstanceMethod(cls, originalSelector);
Method swizzledMethod = class_getInstanceMethod(cls, swizzledSelector); BOOL didAddMethod = class_addMethod(cls, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod));
if (didAddMethod) {
class_replaceMethod(cls, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod));
} else {
method_exchangeImplementations(originalMethod, swizzledMethod);
}
}
@end

核心方法交换逻辑,通过 Method Swizzling 替换原有的方法实现。

使用CrashPrevention工具类

在应用启动时初始化CrashPrevention,并设置是否启用调试模式:

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// 设置 Debug 模式
#ifdef DEBUG
[CrashPrevention setDebugMode:YES];
#else
[CrashPrevention setDebugMode:NO];
#endif [CrashPrevention enableAllCrashPrevention];
return YES;
}

总结

CrashPrevention工具类为iOS开发者提供了多个方面的崩溃预防措施,通过简单调用,即可为数组、字典、未识别选择器、通知中心、KVO、字符串、多线程和UI线程的操作提供全面的保护。特别是通过 isDebug 标志,让开发者可以灵活控制这些预防措施在调试阶段和正式发布中的启用状态。借助这一工具类,开发者能够有效减少崩溃问题的发生,提升应用的稳定性和用户体验。

最后附上完整代码:

CrashPrevention.h文件

#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h> @interface CrashPrevention : NSObject // 设置是否在 Debug 模式中启用崩溃预防
+ (void)setDebugMode:(BOOL)isDebug; // 启用所有崩溃保护
+ (void)enableAllCrashPrevention; // 启用各类崩溃保护
+ (void)enableArrayProtection;
+ (void)enableDictionaryProtection;
+ (void)enableSelectorProtection;
+ (void)enableNotificationProtection;
+ (void)enableKVOCrashProtection;
+ (void)enableStringProtection;
+ (void)enableThreadSafetyProtection;
+ (void)enableUIThreadProtection; @end

CrashPrevention.m文件

#import "CrashPrevention.h"
#import <objc/runtime.h> @implementation CrashPrevention // 用于记录是否在 Debug 模式下启用崩溃预防
static BOOL debugModeEnabled = NO; // 设置是否在 Debug 模式中启用崩溃预防
+ (void)setDebugMode:(BOOL)isDebug {
debugModeEnabled = isDebug;
} // 启用所有崩溃保护
+ (void)enableAllCrashPrevention {
if (!debugModeEnabled) {
return;
}
[self enableArrayProtection];
[self enableDictionaryProtection];
[self enableSelectorProtection];
[self enableNotificationProtection];
[self enableKVOCrashProtection];
[self enableStringProtection];
[self enableThreadSafetyProtection];
[self enableUIThreadProtection];
} #pragma mark - Array Protection
+ (void)enableArrayProtection {
if (!debugModeEnabled) {
return;
}
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
[self swizzleInstanceMethod:NSClassFromString(@"__NSArrayI")
original:@selector(objectAtIndex:)
swizzled:@selector(safe_objectAtIndex:)];
[self swizzleInstanceMethod:NSClassFromString(@"__NSArrayM")
original:@selector(objectAtIndex:)
swizzled:@selector(safe_objectAtIndex:)];
});
} - (id)safe_objectAtIndex:(NSUInteger)index {
if (index < self.count) {
return [self safe_objectAtIndex:index];
} else {
@try {
NSLog(@"Array index out of bound: %lu", (unsigned long)index);
} @catch (NSException *exception) {
// 处理异常
} @finally {
return nil;
}
}
} #pragma mark - Dictionary Protection
+ (void)enableDictionaryProtection {
if (!debugModeEnabled) {
return;
}
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
[self swizzleInstanceMethod:NSClassFromString(@"__NSDictionaryI")
original:@selector(objectForKey:)
swizzled:@selector(safe_objectForKey:)];
[self swizzleInstanceMethod:NSClassFromString(@"__NSDictionaryM")
original:@selector(objectForKey:)
swizzled:@selector(safe_objectForKey:)];
});
} - (id)safe_objectForKey:(id)key {
if (key) {
return [self safe_objectForKey:key];
} else {
@try {
NSLog(@"Attempted to access dictionary with nil key");
} @catch (NSException *exception) {
// 处理异常
} @finally {
return nil;
}
}
} #pragma mark - Selector Protection
+ (void)enableSelectorProtection {
if (!debugModeEnabled) {
return;
}
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Method forwardMethod = class_getInstanceMethod([self class], @selector(forwardingMethod:));
class_addMethod([self class], NSSelectorFromString(@"unrecognizedSelectorHandler"), method_getImplementation(forwardMethod), method_getTypeEncoding(forwardMethod));
});
} - (void)forwardingMethod:(SEL)aSelector {} + (BOOL)resolveInstanceMethod:(SEL)sel {
if (!debugModeEnabled) {
return [super resolveInstanceMethod:sel];
}
class_addMethod([self class], sel, class_getMethodImplementation([self class], @selector(forwardingMethod:)), "v@:");
return YES;
} #pragma mark - Notification Protection
+ (void)enableNotificationProtection {
if (!debugModeEnabled) {
return;
}
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
[self swizzleInstanceMethod:[NSNotificationCenter class]
original:@selector(addObserver:selector:name:object:)
swizzled:@selector(safe_addObserver:selector:name:object:)];
[self swizzleInstanceMethod:[NSNotificationCenter class]
original:@selector(removeObserver:name:object:)
swizzled:@selector(safe_removeObserver:name:object:)];
[self swizzleInstanceMethod:[NSNotificationCenter class]
original:@selector(removeObserver:)
swizzled:@selector(safe_removeObserver:)];
});
} - (void)safe_addObserver:(NSObject *)observer selector:(SEL)aSelector name:(NSString *)aName object:(id)anObject {
if (observer) {
[[NSNotificationCenter defaultCenter] safe_addObserver:observer selector:aSelector name:aName object:anObject];
} else {
@try {
NSLog(@"Attempted to add a nil observer for name: %@", aName);
} @catch (NSException *exception) {
// 处理异常
} @finally {
// 什么也不做
}
}
} - (void)safe_removeObserver:(NSObject *)observer {
if (observer) {
[[NSNotificationCenter defaultCenter] safe_removeObserver:observer];
} else {
@try {
NSLog(@"Attempted to remove a nil observer");
} @catch (NSException *exception) {
// 处理异常
} @finally {
// 什么也不做
}
}
} - (void)safe_removeObserver:(NSObject *)observer name:(NSString *)aName object:(id)anObject {
if (observer) {
[[NSNotificationCenter defaultCenter] safe_removeObserver:observer name:aName object:anObject];
} else {
@try {
NSLog(@"Attempted to remove a nil observer for name: %@", aName);
} @catch (NSException *exception) {
// 处理异常
} @finally {
// 什么也不做
}
}
} #pragma mark - KVO Protection
+ (void)enableKVOCrashProtection {
if (!debugModeEnabled) {
return;
}
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
[self swizzleInstanceMethod:[NSObject class]
original:@selector(addObserver:forKeyPath:options:context:)
swizzled:@selector(safe_addObserver:forKeyPath:options:context:)];
[self swizzleInstanceMethod:[NSObject class]
original:@selector(removeObserver:forKeyPath:)
swizzled:@selector(safe_removeObserver:forKeyPath:)];
});
} - (void)safe_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(void *)context {
if (observer && keyPath) {
[self safe_addObserver:observer forKeyPath:keyPath options:options context:context];
} else {
@try {
NSLog(@"Attempted to add observer with nil observer or key path: %@", keyPath);
} @catch (NSException *exception) {
// 处理异常
} @finally {
// 什么也不做
}
}
} - (void)safe_removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath {
if (observer && keyPath) {
[self safe_removeObserver:observer forKeyPath:keyPath];
} else {
@try {
NSLog(@"Attempted to remove observer with nil observer or key path: %@", keyPath);
} @catch (NSException *exception) {
// 处理异常
} @finally {
// 什么也不做
}
}
} #pragma mark - String Protection
+ (void)enableStringProtection {
if (!debugModeEnabled) {
return;
}
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
[self swizzleInstanceMethod:NSClassFromString(@"__NSCFConstantString")
original:@selector(substringFromIndex:)
swizzled:@selector(safe_substringFromIndex:)];
[self swizzleInstanceMethod:NSClassFromString(@"__NSCFConstantString")
original:@selector(substringToIndex:)
swizzled:@selector(safe_substringToIndex:)];
[self swizzleInstanceMethod:NSClassFromString(@"__NSCFConstantString")
original:@selector(substringWithRange:)
swizzled:@selector(safe_substringWithRange:)];
});
} - (NSString *)safe_substringFromIndex:(NSUInteger)from {
if (from <= self.length) {
return [self safe_substringFromIndex:from];
} else {
@try {
NSLog(@"String index out of bound: %lu", (unsigned long)from);
} @catch (NSException *exception) {
// 处理异常
} @finally {
return nil;
}
}
} - (NSString *)safe_substringToIndex:(NSUInteger)to {
if (to <= self.length) {
return [self safe_substringToIndex:to];
} else {
@try {
NSLog(@"String index out of bound: %lu", (unsigned long)to);
} @catch (NSException *exception) {
// 处理异常
} @finally {
return nil;
}
}
} - (NSString *)safe_substringWithRange:(NSRange)range {
if (range.location + range.length <= self.length) {
return [self safe_substringWithRange:range];
} else {
@try {
NSLog(@"String range out of bound: %@", NSStringFromRange(range));
} @catch (NSException *exception) {
// 处理异常
} @finally {
return nil;
}
}
} #pragma mark - Thread Safety Protection
+ (void)enableThreadSafetyProtection {
if (!debugModeEnabled) {
return;
}
// 实现是与具体使用场景相关的,需要结合项目实际情况实现
} #pragma mark - UI Thread Protection
+ (void)enableUIThreadProtection {
if (!debugModeEnabled) {
return;
}
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
[self swizzleInstanceMethod:[UIView class]
original:@selector(setNeedsLayout)
swizzled:@selector(safe_setNeedsLayout)];
[self swizzleInstanceMethod:[UIView class]
original:@selector(setNeedsDisplay)
swizzled:@selector(safe_setNeedsDisplay)];
[self swizzleInstanceMethod:[UIView class]
original:@selector(setNeedsDisplayInRect:)
swizzled:@selector(safe_setNeedsDisplayInRect:)];
});
} - (void)safe_setNeedsLayout {
if ([NSThread isMainThread]) {
[self safe_setNeedsLayout];
} else {
dispatch_async(dispatch_get_main_queue(), ^{
[self safe_setNeedsLayout];
});
@try {
NSLog(@"setNeedsLayout was called off the main thread. Fixed by dispatching to main queue.");
} @catch (NSException *exception) {
// 处理异常
} @finally {
// 什么也不做
}
}
} - (void)safe_setNeedsDisplay {
if ([NSThread isMainThread]) {
[self safe_setNeedsDisplay];
} else {
dispatch_async(dispatch_get_main_queue(), ^{
[self safe_setNeedsDisplay];
});
@try {
NSLog(@"setNeedsDisplay was called off the main thread. Fixed by dispatching to main queue.");
} @catch (NSException *exception) {
// 处理异常
} @finally {
// 什么也不做
}
}
} - (void)safe_setNeedsDisplayInRect:(CGRect)rect {
if ([NSThread isMainThread]) {
[self safe_setNeedsDisplayInRect:rect];
} else {
dispatch_async(dispatch_get_main_queue(), ^{
[self safe_setNeedsDisplayInRect:rect];
});
@try {
NSLog(@"setNeedsDisplayInRect: was called off the main thread. Fixed by dispatching to main queue.");
} @catch (NSException *exception) {
// 处理异常
} @finally {
// 什么也不做
}
}
} #pragma mark - Method Swizzling
+ (void)swizzleInstanceMethod:(Class)cls original:(SEL)originalSelector swizzled:(SEL)swizzledSelector {
Method originalMethod = class_getInstanceMethod(cls, originalSelector);
Method swizzledMethod = class_getInstanceMethod(cls, swizzledSelector); BOOL didAddMethod = class_addMethod(cls, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod));
if (didAddMethod) {
class_replaceMethod(cls, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod));
} else {
method_exchangeImplementations(originalMethod, swizzledMethod);
}
} @end

iOS开发基础133-崩溃预防的更多相关文章

  1. iOS开发基础-九宫格坐标(2)之模型

    在iOS开发基础-九宫格(1)中,属性变量 apps 是从plist文件中加载数据的,在 viewDidLoad 方法中的第20行.26行中,直接通过字典的键名来获取相应的信息,使得 ViewCont ...

  2. IOS开发基础知识碎片-导航

    1:IOS开发基础知识--碎片1 a:NSString与NSInteger的互换 b:Objective-c中集合里面不能存放基础类型,比如int string float等,只能把它们转化成对象才可 ...

  3. iOS开发——总结篇&IOS开发基础知识

    IOS开发基础知识 1:Objective-C语法之动态类型(isKindOfClass, isMemberOfClass,id) 对象在运行时获取其类型的能力称为内省.内省可以有多种方法实现. 判断 ...

  4. IOS开发基础环境搭建

    一.目的 本文的目的是windows下IOS开发基础环境搭建做了对应的介绍,大家可根据文档步骤进行mac环境部署: 二.安装虚拟机 下载虚拟机安装文件绿色版,点击如下文件安装 获取安装包:       ...

  5. iOS开发基础-九宫格坐标(6)

    继续对iOS开发基础-九宫格坐标(5)中的代码进行优化. 优化思路:把字典转模型部分的数据处理操作也拿到模型类中去实现,即将 ViewController 类实现中 apps 方法搬到 WJQAppI ...

  6. iOS开发基础-九宫格坐标(5)

    继续在iOS开发基础-九宫格坐标(4)的基础上进行优化. 一.改进思路 1)iOS开发基础-九宫格坐标(4)中 viewDidLoad 方法中的第21.22行对控件属性的设置能否拿到视图类 WJQAp ...

  7. iOS开发基础-九宫格坐标(4)

    对iOS开发基础-九宫格坐标(3)的代码进行进一步优化. 新建一个 UIView 的子类,并命名为 WJQAppView ,将 appxib.xib 中的 UIView 对象与新建的视图类进行关联. ...

  8. iOS开发基础-九宫格坐标(3)之Xib

    延续iOS开发基础-九宫格坐标(2)的内容,对其进行部分修改. 本部分采用 Xib 文件来创建用于显示图片的 UIView 对象. 一.简单介绍  Xib 和 storyboard 的比较: 1) X ...

  9. iOS开发基础-图片切换(4)之懒加载

    延续:iOS开发基础-图片切换(3),对(3)里面的代码用懒加载进行改善. 一.懒加载基本内容 懒加载(延迟加载):即在需要的时候才加载,修改属性的 getter 方法. 注意:懒加载时一定要先判断该 ...

  10. iOS开发基础-图片切换(3)之属性列表

    延续:iOS开发基础-图片切换(2),对(2)里面的代码用属性列表plist进行改善. 新建 Property List 命名为 Data 获得一个后缀为 .plist 的文件. 按如图修改刚创建的文 ...

随机推荐

  1. Swoole 源码分析之 Http Server 模块

    首发原文链接:Swoole 源码分析之 Http Server 模块 Swoole 源码分析之 Http Server 模块 Http 模块的注册初始化 这次我们分析的就是 Swoole 官网的这段代 ...

  2. 轻松绕过 Graphql 接口爬取有米有数的商品数据

    轻松绕过 Graphql 接口爬取有米有数的商品数据 有米有数数据的 API 接口,使用的是一种 API 查询语言 graphql.所有的 API 只有一个入口,具体的操作隐藏在请求数据体里面传输. ...

  3. 华为云大咖说:开发者应用AI大模型的“道、法、术”

    本文分享自华为云社区<华为大咖说 | 企业应用AI大模型的"道.法.术" --道:认知篇>,作者:华为云PaaS服务小智. 本期核心观点 上车:AGI是未来5-10年内 ...

  4. Kubernetes1.16安装[kubadm方式]

    Kubernetes 安装手册(非高可用版) 集群信息 1. 节点规划 部署k8s集群的节点按照用途可以划分为如下2类角色: master:集群的master节点,集群的初始化节点,基础配置不低于2C ...

  5. Flutter TextField开始输入中文的时候,被打断导致错误输入字母问题

    一.Bug样例 建立一个web demo flutter run -d chrome --web-renderer html 出现问题: 输入中文的时候,比如打字 hao, 第一个字母h会先输入,变成 ...

  6. 未能加载文件或程序集“SissPayWebApi”或它的某一个依赖项。试图加载格式不正确

    "未能加载文件或程序集"xxx"或它的某一个依赖项.试图加载格式不正确的程序."这个错误可能在IIS或VS中出现,一般是平台和dll版本不一致导致的. 一.VS ...

  7. python根据文件目录批量过滤空行

    import shutil import os path = "E:\\in\\" #文件夹操作目录 path2 = "E:\\out\\" #文件夹输出目录 ...

  8. java8 Lambda 测试示例

    import com.google.gson.Gson; import org.junit.Test; import java.util.Arrays; import java.util.IntSum ...

  9. Big Exponential Addition

    Big Exponential Addition 给定一非负整数n计算2^n的值,一般而言把 2 乘上 n 次,就能得到答案.然而,当n特别大时,2^n要一次次地乘2可能稍嫌太慢,面对此一巨大问题利用 ...

  10. 《Vue3.x +TpyeScript实践指南》勘误

    图书出版已有一段时间,书中已发现错误如下: 书的第14页,倒数第3行,npm init -y命令中,init和-y之间应该有个空格: 书的第32页,代码的第1行,应该为模板字符串符号 `,我看印刷的是 ...