「Objective-C」基础

一、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 原理:

  1. 遍历字典的所有的 Key
  2. 去所在类查找 有无 与Key值名称对应的
    • set方法:,若找到则,赋值
    • 没找到,则查找 _name属性,若找到 赋值
    • 没找到,则查找 name属性,若找到 赋值
    • 还没找到,报错
+ (instancetype)dealWithDic:(NSDictionary *)dict{
    Deal *deal = [[self alloc]init];
    [deal setValuesForKeysWithDictionary:dict];
    return deal;
}

KVO:Key Value Observing,常见作用:监听模型属性值的改变
原理:

  • 判断有没有调用一个对象的 set 方法,如果没有调用 set 方法,则不能监听这属性值的改变
  • 底层实现
    1. 动态创建「运行时创建」原来 类A 的子类 NSKVONotifying_A的类名,用来做 KVO
    2. 修改当前对象的 isa 指针-> NSKVONotifying_A的类名
    3. 只要调用对象的 set 方法,就会调用 NSKVONotifying_类名 的 set 方法
    4. 重写 NSKVONotifying_类名 的 set 方法
      • 调用父类
      • 通知观察者属性改变

缺点:由于时刻监听,耗费系统性能,能少用就少用

// 对象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"];
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,530评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 86,403评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,120评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,770评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,758评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,649评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,021评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,675评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,931评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,659评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,751评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,410评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,004评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,969评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,042评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,493评论 2 343

推荐阅读更多精彩内容