一、Objective-C 简介
- 可以用 OC 开发 Mac OSX 平台和 IOS 平台的应用程序
- 完全兼容 C 语言,文件以.m结尾
- 可以在 OC 代码中混入 C 语言代码,甚至是 C++代码「混入C++代码的OC文件以.mm结尾」
- 关键字:为了区分 C 语言和 C++,OC 基本所有的关键字都以
@
开头「包括字符串,且中间不能有空格」 - 开发过程:.m「源文件」→编译→ .o「目标文件」→链接→ a.out「可执行文件」
编译:cc -c 文件名.m 文件名2.c 文件名3.m
连接:cc 文件名1.o 文件名2.o 文件名3.o -framework Fundation「如果包含 Fundation框架的话」 - @package「介于@private 和 @public 之间」
其他包中访问就是 private
当前包中访问就是 public - BOOL 类型
//BOOL类型本质
typedef signed char BOOL;
//BOOL变量有两种取值 YES/N
#define YES (BOOL)1
#define NO (BOOL)0
//BOOL类型输出「当做整数来用」
NSLog(@"%d %d",YES,NO);
二、Objective-C 特点
#pragma mark 「纯粹是Xcode的工具,非objc语法,可以写在任意一行,帮助阅读和查找函数」
不允许修改OC对象的 结构体成员
1. 对象中有一个 isa 指针,指向对象的类
基于 C 底层面向过程,弱语法,在运行过程中才会检测对象有没有实行相应的方法
2. __kindof
类型说明关键字「iOS9新增」
格式:- (__kindof ClassName *)function;
作用:告诉编译器返回值可能是 ClassName 或 ClassName的子类
3. 编译器特性: 点语法
- 前提:给属性提供了get、set方法
- 本质:set、get方法 的方法调用
是编译器的特性, 会在程序翻译成二进制的时候 将 .语法自动转换为set、get方法 - 注意:写在同一 set/get 方法里会造成死循环
- 在 = 的左边, 编译器自动转换为set方法
在 = 的右边, 或无 =, 编译器自动转换为get方法
p.name = @"haha" //编译器转为 [p setName:@"haha"]; 但不能写成 p.setName
NSString *s = p.name; //编译器转为 [p name:@"haha"]; 但不能写成 p.getName
- 一般给成员变量赋值, 若不是给成员变量赋值不建议使用, 但也可以用
p.test; //编译器转为 [p test];
<h2 id="propertyFirst">4. 编译器特性: @property @synthesize 关键字</h2>
@property - 上 基础
- 只用在 @interface 中
- 自动生成 某个变量a的 getter 和 setter 声明
如果成员变量a 未声明,则自动声明一个 私有的 _a - Xcode版本是4.4及其以后
自动生成 某个变量的 getter 和 setter 声明 和 实现
@property int age
// 编译器会自动替换为以下两行
- (void)setAge(int):age;
- (int)age;
// 参数不止一个 但 数据类型要相同 「一般很少这样连在一起」
@property int min, max;
- nullable 标识符「iOS9新增」能为空的参数,默认情况,只用于对象方法
- nonnull 标识符「iOS9新增」不能为空的参数
如果为空,编译器会警告
@property (nonatomic, nonnull) NSString *name; // getter 和 setter方法都不能为空
@property (nonatomic) NSString *__nonnull name; // 和上一句等价,表示的 name值不能为空
// setter方法能为 nil,getter方法不能为 nil
// 因此,此属性定义后,要重写getter方法,确保getter方法不为nil,否则 编译器会警告
@property (nonatomic, null_resettable) NSString *name;
@property (nonatomic, null_resettable) NSString *name;
-
NS_ASSUME_NONNULL_BEGIN
宏定义,期间的变量不能为 nil
NS_ASSUME_NONNULL_BEGIN
@interface class
@property NSString *__nullable name;
@property NSString *mother;
@property NSString *father;
@end
NS_ASSUME_NONNULL_END
// 等价于
@interface class
@property NSString *__nullable name;
@property NSString *__nonnull mother;
@property NSString *__nonnull father;
@end
- 泛型「iOS9新增」
// 规定数组只能装 NSString 类型数据
@property (nonatomic) NSArray<NSString *> *names;
// 字典的key只能为NSString类型,value只能为NSNumber类型
@property (nonatomic) NSMutableDictionary<NSString *, NSNumber *> *names;
// 自定义泛型,classType只是个占位符,可以随便起名
@interface className<classType> : NSObject
- (void)set:(classType)obj;
- (classType)get;
@end
// 使用自定义类型
// 不指名,默认为 id 类型
className *test0 = [[className alloc] init];
// 指明类型,必须为指明的类型
className<NSString *> *test1 = [[className alloc] init];
className<NSMutableString *> *test2 = [[className alloc] init];
// 类型转换
// 以下两行代码不会报警告
test0 = test1;
test1 = test0;
// 以下代码会报警告
// 为了防止警告,自定义泛型应添加 __covariant 为 @interface className<__covariant classType> : NSObject
test1 = test2; // __covariant:子类行 转为 父类型「仅限泛型的类型」
test2 = test1; // __cotravariant:父类行 转为 子类型「仅限泛型的类型」
@synthesize
- 只用在 @implementation 中
- 自动生成 成员变量age 的 getter 和 setter 实现
- 如果成员变量 不存在,就会自动生成 @private 作用域的 成员变量
在 implementation 里创建的成员变量,作用域为 @private - 注意
如果手动实现了setter/getter 方法,编译器自动生成 一个缺少的 getter/setter 方法
如果手动实现了setter 和 getter 两个方法,编译器不会自动生成,且不会自动生成不存在的变量
@synthesize age = _age;
// 编译器会自动替换为以下代码
- (void)setAge(int):age {
age = _age;
}
- (int)age {
return _age;
}
@synthesize age;
// 编译器会自动替换为以下代码
- (void)setAge(int):age {
self->age = age;
}
- (int)age {
return age;
}
// 参数不止一个「一般很少这样连在一起」
synthesize min = _min, max = _max;
5. 常用函数
+ (void)load
- 作用:程序启动时加载所有 类和分类,加载类和分类的 +load方法
- 先调用父类的 +load 后调用子类的 +load方法
- 不管程序有没有用到 这个类,都会调用类的 +load方法
+ (void)initialize
- 作用:在对像创建前进行一些初始化操作
- 第一次使用类的时候调用 +initialize方法「比如创建对象」
- 一个类只会调用一次 +initialize方法,先调用父类,在调用子类
*- (NSString )description
NSLog 和 %@ 输出某个对象时
- 会调用对象的description方法,并拿到返回值进行输出
- 默认格式:<类名: 对象内存地址>
*+ (NSString )description
NSLog 和 %@ 输出某个类时
- 会调用类的description方法,并拿到返回值进行输出
- 默认格式:类名
(void)NSLog 补充
- 以输出 C语言字符格式,不能输出中文
char *s = "C语言字符串";
NSLog(@"%s", s); // 字符串含英文不能输出
NSString *o = @"Objective-c语言字符串";
NSLog(@"%@", o);
- <code>__FILE__</code>: 源代码文件名
- <code>__LINE__</code>: NSLog 代码在第几行
- <code>_cmd</code>: 当前方法 SEL
// 会造成死循环
- (void)test{ [self performSelector:_cmd];
}
6. SEL 数据类型
本质:消息机制中的消息就是 SEL
定义:<code>typedef struct objc_selector *SEL;</code>
作用:
- 每个类的方法列表都存储在类中
- 每个方法都有对应的 SEL类型的对象
- 根据 SEL类型对象可以找到方法地址,进而调用方法
使用:
// 方法一
SEL s1 = @seletor(方法名);
// 方法二
SEL s2 = NSSelectorFromString("方法名");
// 其他用法
// 将 SEL对象转换成 NSString 对象
NSString *s = NSStringFromSelector(@selector(方法名));
// 间接调用
[对象名 performSelector: @selector(方法名)];
7. @protocol 协议
作用:
- 用来声明方法「不能声明 成员变量」
- 类遵守了协议,就会拥有协议的所有方法声明
- 父类遵守了协议,子类也会遵守
基协议:
- 基类:NSObject ,最基本的类,任何其他类最终都要继承它
- 基协议:NSObject,最根本的协议,与基类同名,声明了很多基本的方法「description, retain, release」
建议每个新的协议都要遵守基协议
格式:
- 协议的编写
@protocol 协议名称
@required // 必须实现的方法「默认,不实现 编译器会警告」
// 方法声明
@optional // 可以不用实现的方法
// 方法声明
@end // 与 @protocol 对应
- 类遵守多个协议
#import "协议名1.h" // 在 .h 文件这里也可以用 @protocol 协议名1, 协议名2
#import "协议名2.h" // 使用了提前声明的方法,在 .m文件要 #import "协议名1.h" ...
@interface 类名 : 父类 <协议名1, 协议名2>
@end
- 协议遵守多个协议
#import "协议名1.h"
#import "协议名2.h"
@protocol 协议名 <协议名1, 协议名2>
@end
- 定义一个变量,限制该变量保存的对象 必须遵守协议 1, 2
如果没有对应的协议,编译器会警告
类名 <协议名1, 协议名2> *变量名;
id<协议名1, 协议名2> 变量名; // 与上面一样效果
- @property 中声明的属性也可用做一个遵守协议的限制
@property (nonmatic, strong) 类名<协议名1, 协议名2> *属性名;
@property (nonmatic, strong) id<协议名1, 协议名2> 属性名; // 与上面一样效果
用法:
协议可以定义在单独 .h 文件里,也可以定义在某个类中
- 协议只用在一个类中,把协议定义在该类中
- 协议用在多个类中,把协议定义在单独的 .h 文件里
8. 代理设计模式
原理:一个对象麻烦的事,代理给另一个对象做
原则:明确属性、方法,低耦合
代理的作用:
- A对象 监听 B对象的行为,A成为 B对象的代理
- A对象想 告诉 B一些事情,B成为 A对象的代理
代理设计模式开发步骤:
- 定义一个 protocol「协议名字格式:类名+Delegate」,在协议里 声明一些代理方法「一般代理方法是
@optional
」
代理的方法「如果代理有实现这个方法,才会调用这个方法」
if ([self.delegate respondsToSelector:@selector(loadMoreFooterDidClickLoadMoreBtn:)]) {
[self.delegate loadMoreFooterDidClickLoadMoreBtn:self];
}
- 声明一个低耦合的代理属性:
@property (nonatomic,weak) id<protocol> delegate;
如果代理指针是 强指针 则 可能 产生循环引用
DelegateClass *d = [[DelegateClass alloc] init];
self.tableView = d; // self → tableView → d
d.delegate = self; // d → delegate → self
// 相当于:self → tableView → d → delegate → self
self.tableView.delegate = self; // 循环引用
- 在内部发生某些行为时,调用代理对应的代理方法,通知代理内部发生了什么事
- 设置代理:B对象.delegate = 监听B对象行为的 A对象
- A对象遵守协议,实现 B对象要求的代理方法
代理和通知的区别:
- 代理:一个对像只能告诉 一个对象 一些事情
- 通知:一个对象可以告诉 多个对象 一些事情
9. 通知
通知中心「NSNotificationCenter」:
- 每一个程序都有 一个通知中心实例,专门负责不同对象的消息传递
- 任何对象都可以向通知中心发布通知,描述自己在干什么
- 任何对象都可以申请在某个特定的通知发布时 收到这个通知「Observer」
初始化通知对象
/**
* @param aName 通知的名称
* @param anObject 通知的发布者
* @param aUserInfo 额外的信息(发布者传给接受着的信息内容)
* @return 初始化的通知对象
*/
+ (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;
发布通知
/**
* @param aName 通知名称
* @param anObject 发布者
* @param aUserInfo 额外信息
*/
- (void)postNotification:(NSNotification *)notification;
- (void)postNotificationName:(NSString *)aName object:(id)anObject;
- (void)postNotificationName:(NSString *)aName object:(id)anObject userInfo:(NSDictionary *)aUserInfo;
注册通知监听器
/**
* @param observer 监听器「谁要监听这个通知」
* @param aSelector 收到通知后,回调监听器的方法,并把通知对像当参数传入
* @param aName 通知名称,若为nil,则全部监听器都可以收到这个通知
* @param anObject 通知发布者,若 aName 和 anObject 都为 nil则,监听器都收到所有的通知
*/
- (void)addObserver:(id)observer selector:(SEL)aSelector name:(NSString *)aName object:(id)anObject;
/**
* @param name 通知名称
* @param obj 通知发布者
* @param queue 决定block在那个操作队列中执行,若是 nil则在当前队列中执行
* @param block 收到对应通知时回调这个 block
*
* @return 返回注册好的监听器
*/
- (id)addObserverForName:(NSString *)name object:(id)obj queue:(NSOperationQueue *)queue usingBlock:(void (^)(NSNotification *note))block;
取消通知监听器
- 通知中心不会保留(retain)监听器对象,注册过的对象,必须在释放前取消注册。
- 否则,当相应的通知再次出现时,通知中心仍然会向该监听器发送消息。因为相应的监听器对象已经被释放了,所以可能会导致应用崩溃
// 通知中心提供了相应的方法来取消注册监听器
- (void)removeObserver:(id)observer;
- (void)removeObserver:(id)observer name:(NSString *)aName object:(id)anObject;
// 一般在监听器销毁之前取消注册(如在监听器中加入下列代码):
- (void)dealloc {
// [super dealloc]; 非ARC中需要调用此句
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
UIDevice 通知
- UIDevice类提供了一个单例对象,它代表着设备
- 通过它可以获得一些设备相关的信息
比如电池电量值(batteryLevel)、电池状态(batteryState)、设备的类型(model,比如iPod、iPhone等)、设备的系统(systemVersion) - 通过
[UIDevice currentDevice]
可以获取这个单例对象
10. KVC、KVO
KVC:Key Value Coding,常见作用:给模型赋值「模型必须与字典类的 属性名 相同」
KVC 原理:
- 遍历字典的所有的 Key
- 去所在类查找 有无 与Key值名称对应的
- set方法:,若找到则,赋值
- 没找到,则查找 _name属性,若找到 赋值
- 没找到,则查找 name属性,若找到 赋值
- 还没找到,报错
+ (instancetype)dealWithDic:(NSDictionary *)dict{
Deal *deal = [[self alloc]init];
[deal setValuesForKeysWithDictionary:dict];
return deal;
}
KVO:Key Value Observing,常见作用:监听模型属性值的改变
原理:
- 判断有没有调用一个对象的 set 方法,如果没有调用 set 方法,则不能监听这属性值的改变
- 底层实现
- 动态创建「运行时创建」原来 类A 的子类
NSKVONotifying_A的类名
,用来做 KVO - 修改当前对象的
isa 指针-> NSKVONotifying_A的类名
- 只要调用对象的 set 方法,就会调用
NSKVONotifying_类名
的 set 方法 - 重写
NSKVONotifying_类名
的 set 方法- 调用父类
- 通知观察者属性改变
- 动态创建「运行时创建」原来 类A 的子类
缺点:由于时刻监听,耗费系统性能,能少用就少用
// 对象A 监听 对象B 的 属性名C是否改变「只要对象B 的属性C改变,则会通知对象A这件事」
// NSObjectA: 观察者「谁想监听」
[NSObjectB addObserver:NSObjectA forKeyPath:@"属性名C" options:(NSKeyValueObservingOptions) context:(nullable void *)];
// KVO监听方法:对象B 的属性C改变后,会调用此方法「是NSObject 的方法」
// 监听 object的 keyPath属性发生了改变「change 是根据上面 options的值产生的值改变的记录」
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context{
NSLog(@"监听到 %@对像的 %@属性发生了 %@的改变",object,keyPath,change);
}
// 监听对象A销毁后要移除,否则再次传值给监听对象A的时候,会因找不到已经销毁的对象A而崩溃「是NSObject 的方法」
- (void)dealloc{
[NSObjectB removeObserver:NSObjectA forKeyPath:@"属性名C"];
}