进阶内容
引入方式
OC中有四种引入方式,#include,#import,@import和@class
#include来自C语言,用于包含一个头文件。
#import 大部分功能和 #include 一样,但是它解决了#include重复定义的问题
@import 是 iOS 7 之后增加的语法。这种方式叫模块导入,当前仅适用于内置系统框架的引入。该特性在Xcode5 以后默认开启,无需显式去写。当编译器看到模块导入时,它将加载框架的独立预编译版本。它还会自动处理与模块的链接,这意味着不再需要在Xcode中手动添加框架。
@class主要是用于声明一个类,告诉编译器它后面的名字是一个类的名字。仅仅只是声明一个类,所以在后面的实现文件里面是需要去 #import这个类的。一般来说,在 interface 中用@class引用一个类,它会把这个类作为一个类型来使用,而在实现文件中,还是需要 #import 。使用它的好处是提升编译效率,同时还能解决循环导入的问题。
枚举
OC基于C语言,所以它也支持C的枚举类型。
enum Color{
Blue,
Red,
Green
}
如上,编译器会用一个整数来保存枚举的值,Blue是0,Red是1,以此类推。至于这个整数的具体类型,是由编译器决定的。以上的枚举值较少,编译器会用一个char来表示。
这样直接定义枚举类型,声明变量的时候会比较麻烦:
enum Color color = Blue;
因此通常借助typedef关键字定义枚举
// 可以在定义完枚举之后加一行
typedef enum Color Color;
//也可以直接在定义枚举时加上
typedef enum Color{
Blue,
Red,
Green
} Color;
C语言的枚举存在一个问题,就是不能显式的定义枚举值的底层类型。得益于C++11的特性,OC有一种新的语法来在定义枚举时显式指定枚举值的底层类型。
enum Color : NSInteger{
Blue,
Red,
Green
};
此外,OC还提供了两个宏帮助我们更简便的定义枚举
typedef NS_ENUM(NSInteger, MyColor){
Blue,
Red,
Green
};
typedef NS_OPTIONS(NSInteger, Color){
Blue = 1 << 0,
Red = 1 << 1,
Green = 1 << 2
};
Block
Block用来保存某一段代码, 可以在需要的时候取出来调用。它其实就相当于匿名函数或者闭包。
其基本格式如下:
返回值类型 (^block变量名)(形参列表) = ^(形参列表) {
代码
};

简单示例:
// 无参无返回值
void (^myBlock)() = ^{
NSLog(@"Hello");
};
// 调用
myBlock();
// 有参有返回值
int (^myAdd)(int, int) = ^(int num1, int num2){
return num1 + num2;
};
NSLog(@"%i", myAdd(10,8));
结合typedef一起使用
// 利用 typedef 给 block 起别名
typedef int (^calculteBlock)(int , int);
int main(int argc, const char * argv[]) {
calculateBlock sumBlock = ^(int v1, int v2){
return v1 + v2;
};
int res = sumBlock(10, 20);
NSLog(@"res = %i", res);
return 0;
}
Block通常会使用外部的其他变量,当执行Block时,为了确保其使用的外部变量始终存在,Block会捕获这些变量。对于基本类型,捕获意味着拷贝变量值到Block内部的局部变量中保存。但是对于指针类型外部变量,Block则会使用强引用。这意味着Block对象用到的那些对象会被一直保留,直到Block被释放,其引用的外部变量引用计数才会减1。
小结:
在Block中可以访问外部的变量,但是默认情况下,不能在Block中修改外部的局部变量值(非引用类型)
想在Block中修改外部局部变量的值,必须在外部局部变量前面加上
__block修饰。例如__block int a = 10;在类中使用时,为了防止循环引用,应使用弱指针
__weak typeof(self) weakSelf = self; self.myBlock = ^(void){ [weakSelf doSomething]; };
异常处理
OC 对异常处理提供四个编译器指令:@try、@catch、@throw 以及@finally
OC 虽然像其他语言一样提供了try...catch...捕获异常机制,但通常并不使用。Apple认为,异常属于代码错误,应该在编码阶段就解决,而不是在运行阶段处理异常。当需要抛出异常时,往往是直接让程序crash。
@try {
// 正常代码...
}@catch (NSException *exception) {
// 捕获异常...
}@finally {
// 最终执行...
}
属性修饰符
OC有3类属性修饰符:多线程相关、读写相关、内存管理相关
@property (nonatomic, readwrite, strong) NSString *name;
多线程相关修饰符:
nonatomic非原子的atomic原子的。默认类型
读写相关修饰符:
readwrite可读写。默认类型readonly只读
readwrite意味着系统实现了setter和getter方法。readonly意味着只实现getter方法
内存管理相关修饰符:
strong强引用weak弱引用(避免循环强引用出现内存泄露问题)copy复制
对象间通信
- 基于协议的委托
- Block
- 通知中心
- Target-action
通知中心

每一个应用程序都有一个通知中心(NSNotificationCenter)实例,专门负责协助不同对象之间的消息通信。任何一个对象都可以向通知中心发布通知(NSNotification),其他感兴趣的对象(Observer)可以申请在某个特定通知发布时(或在某个特定的对象发布时)收到这个通知。
- (void)myMethod:(NSNotification *)notification {
NSLog(@"userInfo =%@",notification.userInfo);
}
- (void)dealloc {
//移除指定的通知
// [[NSNotificationCenter defaultCenter] removeObserver:self name:@"test" object:self];
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
//注册通知:
[[NSNotificationCenter defaultCenter]addObserver:self selector:@selector(myMethod:) name:@"test" object:nil];
//发送通知
[[NSNotificationCenter defaultCenter] postNotificationName:@"test" object:nil userInfo:@{@"key":@"value"}];
一个完整的通知通常包含3个属性:
(NSString *)name通知的名称(id)object通知发布者(NSDictionary *)userInfo一些需要传递的参数(发布者传递给接收者)
注册通知监听器
//observer:监听器,即接收者
//aSelector:回调方法,方法参数必须包含通知对象
//aName:通知的名称。如果为nil,忽略名称,监听器能收到任意名称的通知
//anObject:发布者。如果为anObject和aName都为nil,监听器收到所有的通知
- (void)addObserver:(id)observer selector:(SEL)aSelector name:(NSString *)aName object:(id)anObject;
//name:通知名称
//obj:发布者
//queue:决定了block在哪个操作队列中执行,如果传nil,默认在当前操作队列中同步执行
//block:收到通知时,回调此block
- (id)addObserverForName:(NSString *)name object:(id)obj queue:(NSOperationQueue *)queue usingBlock:(void (^)(NSNotification *note))block;
注销通知监听器
- (void)removeObserver:(id)observer;
- (void)removeObserver:(id)observer name:(NSString *)aName object:(id)anObject;
发通知
//发布一个notification通知
- (void)postNotification:(NSNotification *)notification;
//发布一个名称为aName的通知,anObject为此通知的发布者
- (void)postNotificationName:(NSString *)aName object:(id)anObject;
//发布一个名称为aName的通知,aUserInfo为需要传递的参数信息
- (void)postNotificationName:(NSString *)aName object:(id)anObject userInfo:(NSDictionary *)aUserInfo;
初始化NSNotification对象的方法:
+ (instancetype)notificationWithName:(NSString *)aName object:(id)anObject;
+ (instancetype)notificationWithName:(NSString *)aName object:(id)anObject userInfo:(NSDictionary *)aUserInfo;
- (instancetype)initWithName:(NSString *)name object:(id)object userInfo:(NSDictionary *)userInfo;
Target-action
每隔两秒回调对象方法
#import "TSLogger.h"
@implementation TSLogger
-(void)update:(NSTimer *)t{
NSLog(@"update...");
}
@end
#import <Foundation/Foundation.h>
#import "TSLogger.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
TSLogger *logger=[[TSLogger alloc]init];
__unused NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:2.0 target:logger selector:@selector(update:) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop]run];
}
return 0;
}
Runtime
即运行时。OC就是一种动态编程语言,在运行时有一些机制,其中主要就是消息机制。OC的函数属于动态调用,在编译的时候并不能决定真正调用哪个函数,只有在运行的时候才会根据函数的名称找到对应的函数来调用。在编译阶段,OC可以调用任何函数,即使这个函数并未实现,只要声明过就不会报错。
Runtime实际上是一个库,这个库使我们可以在程序运行时动态的创建对象、检查对象,修改类和对象的方法。
反射
方法调用的本质,就是让对象发送消息,对象根据方法编号SEL去映射表查找对应的方法实现。因此我们可以通过运行时机制去动态调用方法,这就类似于Java语言中的反射。可用于私有方法的调用。
@interface Person : NSObject
- (void)test:(NSInteger)param;
- (void)run;
@end
每个类都有一个对应的Class,OC有三种方式获取Class对象:
- 通过
Class clazz = NSClassFromString(@"Person") Class clazz = [Person class]- 通过调用对象的
class方法获取,Class clazz = [p class]
有了Class对象,就通过调用它的alloc和init方法来实例化该类。
Class clazz = [Person class]
id p = [[clazz alloc]init];
// 注意完整的方法签名,有冒号的要包含冒号
SEL s1 = @selector(test:);
// 判断实例是否实现此方法
if([p respondsToSelector:@selector(s1)]){
// 调用方法
[p performSelector:s1 withObject:@1];
}
这里是一些相关的API:
// SEL和字符串的转换
NSString *NSStringFromSelector(SEL aSelector);
SEL NSSelectorFromString(NSString *aSelectorName);
// Class和字符串的转换
NSString *NSStringFromClass(Class aClass);
Class NSClassFromString(NSString *aClassName);
// Protocol和字符串的转换
NSString *NSStringFromProtocol(Protocol *proto);
Protocol *NSProtocolFromString(NSString *namestr);
/// 常用判断方法
// 当前对象是否这个类或其子类的实例
- (BOOL)isKindOfClass:(Class)aClass;
// 当前对象是否是这个类的实例
- (BOOL)isMemberOfClass:(Class)aClass;
// 当前对象是否遵守指定的协议
- (BOOL)conformsToProtocol:(Protocol *)aProtocol;
// 当前对象是否实现此方法
- (BOOL)respondsToSelector:(SEL)aSelector;
使用Runtime机制
// 注意导入头文件
#import <objc/message.h>
// 创建person对象
// Person *p = [[Person alloc] init];
Person *p = objc_msgSend(objc_getClass("Person"), sel_registerName("alloc"));
p = objc_msgSend(p, sel_registerName("init"));
// 调用对象方法,本质就是让对象发送消息
objc_msgSend(p, @selector(test:),20);
objc_msgSend(p, @selector(run));
// 用类名调用类方法,底层会自动把类名转换成类对象调用
// 本质让类对象发送消息
objc_msgSend([Person class], @selector(run));
方法调用流程:
- 通过
isa去对应的类中查找 - 注册方法编号
- 根据方法编号去查找对应方法
- 找到是最终函数地址,根据地址去方法区调用对应函数
注意,通过Runtime机制,还可以动态的给类添加方法和属性。
公众号“编程之路从0到1”