[TOC]
多用类型常量,少用 #define
- 对于局部常量(.m文件中),
- 使用 static 声明表明变量只在本文件中可见,所以无需前缀
- 同时使用 static const 关键字与#define 效果相同,好处在于带有类型信息。
static const CGFloat kTopViewHeight = 40;
- 对于全局常量
- 由于全局使用,使用类名做前缀
- 在 .h 文件中使用 extern 声明
extern NSString * const KKSlideTabBarBgColor;
- 在 .m 文件中定义
NSString * const KKSlideTabBarBgColor = @"name";
NS_ENUM 与 NS_OPTIONS
- 使用
NS_ENUM
和NS_OPTIONS
可以指定底层数据类型,而且可以保证系统兼容 - 当多种状态可以互相组合时,使用
NS_OPTIONS
,否则使用NS_ENUM
- 命名规则:前缀+状态
typedef NS_ENUM(NSUInteger, GDFConnectionState) {
GDFConnectionStateDisconnected,
GDFConnectionStateConnecting,
GDFConnectionStateConnected,
};
GDFConnectionStateDisconnected,
GDFConnectionStateConnecting,
GDFConnectionStateConnected,
};
typedef NS_OPTIONS(NSUInteger, GDFDirection) {
GDFDirectionUp = 1 << 0,
GDFDirectionDown = 1 << 1,
GDFDirectionLeft = 1 << 2,
GDFDirectionRight = 1 << 3,
};
消息转发 message forwarding
e28856e7825cc2b7113288d11b2f1be6.png动态方法解析 resolve method
- 动态方法解析是消息转发的第一步,在这里处理,效率最高
@dynamic
属性 使编译器不自动生成实例变量及存储方法
调用的方法:
+ (BOOL)resolveInstanceMethod:(SEL)selector
代码参见:Runtime.md 动态方法解析 resolve method
方法调配 method swizzling
- 作用1:在运行死交换两个方法的实现
// 根据方法名找到方法的实现
class_getInstanceMethod(__unsafe_unretained Class cls, SEL name)
// 交换两个方法的实现
method_exchangeImplementations(Method m1, Method m2)
-
作用2:为既有的方法实现添加新功能——调试黑盒方法,为完全不知道具体实现的方法添加日志功能
fed427a97a343ee16f2c5edc37065689.png
@implementation NSString (EOC)
+ (void)load {
Method originalMethod = class_getInstanceMethod([NSString class],
@selector(lowercaseString));
Method swappedMethod = class_getInstanceMethod([NSString class],
@selector(eoc_myLowercaseString));
method_exchangeImplementations(originalMethod, swappedMethod);
}
- (NSString *)eoc_myLowercaseString {
NSString *lowercase = [self eoc_myLowercaseString];
NSLog(@"%@ => %@", self, lowercase);
return lowercase;
}
类对象
// 对象结构体
// isa 指针指向类对象
struct objc_object {
Class isa;
};
// 类结构体
// 1. 这个结构存放类的元数据,实例中的方法,变量等信息就存储在类对象中
// 2. isa 指针指向元类(metaclass),元类描述类对象本身所具有的元数据,类方法就存储在元类中
// 3. 每个类只有一个类对象,每个类对象只有一个元类
typedef struct objc_class *Class;
struct objc_class {
Class isa;
Class super_class;
const char *name;
long version;
long info;
long instance_size;
struct objc_ivar_list *ivars;
struct objc_method_list **methodLists;
struct objc_cache *cache;
struct objc_protocol_list *protocols;
};
f81c1ab1af0d8cee47e96db2568859d0.png
前缀
- 苹果保留了两字符前缀,我们应该使用三字符前缀
- C函数名应该加上前缀
- 头文件中的全局变量需要加上前缀
- 为私有方法名添加前缀(例如p_),用于区分哪些方法是私有的,哪些是公有的,私有方法可以随意改动,公有方法则要三思而后行。
- 为第三方类添加分类时,分类中的方法要增加前缀,可以避免覆盖原有方法。
使用段位缓存代理能否响应某个方法
- 段位:结构体可以设置其成员所占的二进制位数
struct {
unsigned int fieldA : 8; // fieldA 占 8 个二进制位,即 0 ~ 255
unsigned int fieldB : 4;
unsigned int fieldC : 2;
unsigned int fieldD : 1;}_delegateFlag;
- 实例:
@protocol KKSlideTabBarViewDelegate <NSObject>
@optional;
- (void)slideTabBarView:(KKSlideTabBarView *)tabBar pageChangedFromIndex:(NSUInteger)from toIndex:(NSUInteger)to;
- (void)slideTabBarView:(KKSlideTabBarView *)tabBar itemMoreClicked:(UIButton *)itemMore;
@end
@interface KKSlideTabBarView : UIView
@property (nonatomic,weak) id <KKSlideTabBarViewDelegate> delegate;
@end
@implementation KKSlideTabBarView {
struct {
unsigned int didPageChangedHandle : 1;
unsigned int didItemMoreClickdHandle : 1;
}_delegateFlags;
}
- (void)setDelegate:(id<KKSlideTabBarViewDelegate>)delegate {
_delegate = delegate;
_delegateFlags.didPageChangedHandle = [self.delegate respondsToSelector:@selector(slideTabBarView:pageChangedFromIndex:toIndex:)];
_delegateFlags.didItemMoreClickdHandle = [self.delegate respondsToSelector:@selector(itemMoreClicked:)];
}
- (void)itemMoreClicked:(UIButton *)sender
{
if (_delegateFlags.didItemMoreClickdHandle) {
[self.delegate slideTabBarView:self itemMoreClicked:sender];
}}
}
NSOperation 与 GCD 优缺点
- GCD 优点:纯 C api,更加轻量级。而operation 是对象,更加重量级
- NSOperation 优点:
- 操作加入队列后可以取消(已经启动的任务无法取消)
- 可以自动操作之间的依赖关系
- 可以使用 KVO 监控 NSOperation 对象的属性,比如通过 isCancelled 判断任务是否取消, isFinished 属性判断任务是否完成
- 可以指定每个操作的优先级,而 GCD 只能指定队列的优先级
- 可以自定义 operation 对象
使用 NSCache 和 NSPurgeableData 缓存数据
- 只有费时操作才值得放入缓存,比如需要从网络获取的数据、从磁盘读取的数据
@interface KKSlideTabBarViewController ()
{
NSCache *_cache;
}
@end
_cache = [NSCache new];
_cache.countLimit = 100;
_cache.totalCostLimit = 5 * 1024 * 1024;
NSPurgeableData *cacheData = [_cacheobjectForKey:@"url..."];
if (cacheData) {
// stop the data being purged
[cacheData beginContentAccess];
// 使用数据
// ......
// Mark that the data may be purged again
[cacheData endContentAccess];
} else {
NSData *data = [NSData dataWithContentsOfURL:@"xxx"];
NSPurgeableData *purgeableData = [NSPurgeableData dataWithData:data];
[_cache setObject:purgeableData
forKey:@"url..."
cost:data.length];
// With access already maked
// user data
// .....
// Mark that the data may be purged now
[purgeableData endContentAccess];
}
精简 + load 与 + initialize 方法
它们都是在类载入系统时由运行时系统调用,不能手动调用。
+ load
方法:
在类加载时调用。
系统会先调用父类的 load 再调用子类的 load,先调用类本身 load,再调用 categery load。
在 load 方法中,运行时系统处于脆弱状态,不能确定其他类是否加载完毕。不能在 load 方法中使用其他类对象,因为无法知道这个类是否加载了。
+ initialize
方法
+ initialize
方法是惰性加载,使用到类时才会调用。
+ initialize
方法调用时,系统处于正常状态,可以在 + initialize 方法中使用其他类对象。
+ initialize
方法一定会在线程安全的环境中执行,那么执行 + initialize 方法时会阻塞其他线程
如果子类没有实现 + initialize 方法,而父类实现了,那么会调用夫类的。所以应该这么实现:
+ (void)initialize
{
if (self == [KKSlideTabBarView class]) {
// 只有当 KKSlideTabBarView 类载入系统时才执行这里的代码
// 不然的话 KKSlideTabBarView 的父类载入系统也会调用 initialize
}
}
网友评论