iOS知识集锦(持续更新)

ruby.taobao.org 停止更新了,使用 cocoapods 的童鞋,更新到 ruby china 的源

$ gem sources --add https://gems.ruby-china.org/ --remove https://rubygems.org/
$ gem sources -lhttps://gems.ruby-china.org # 确保只有 gems.ruby-china.org
$ gem sources -l 
*** CURRENT SOURCES ***
https://gems.ruby-china.org
# 请确保只有 gems.ruby-china.org
$ gem install rails

$ gem install rails时出现这样的错误:

yitudevdeiMac:~ yitudev$ gem install rails
Building native extensions.  This could take a while...
ERROR:  While executing gem ... (Errno::EACCES)
    Permission denied @ dir_s_mkdir - /usr/local/lib/ruby/gems/2.3.0/extensions/x86_64-darwin-15/2.3.0/nokogiri-1.6.8.1
yitudevdeiMac:~ yitudev$ gem install rails
Building native extensions.  This could take a while...

进行操作:

yitudevdeiMac:~ yitudev$ sudo gem install cocoapods-core 

再$ gem install rails。当pod setup 进入Setting up CocoaPods master repo 等待的时候表示正在下载了,此时你可通过新开一个终端窗口,输入"cd ~/.cocoapods/"命令行跳到cocoapods文件夹内,执行"du -sh *"查看正在下载的文件夹的大小。
CocoaPods 的简单快速安装方法(Setting up CocoaPods master repo 卡着不动,是因为淘宝镜像已经不能用了)

  • 使用CocoaPods来添加第三方类库,无论是执行pod install还是pod update都卡在了Analyzing dependencies不动原因在于当执行以上两个命令的时候会升级CocoaPods的spec仓库,加一个参数可以省略这一步,然后速度就会提升不少。(最近进行install 和 update,有时即使加了--no-repo-update还是死慢)
pod install --verbose --no-repo-update
pod update --verbose --no-repo-update

NULL、nil、Nil、NSNull区别

标志 含义
NULL (void *)0 C 指针的字面空值
nil (id)0 Objective-C 对象的字面空值
Nil (Class)0 Objective-C 类的字面空值
NSNull [NSNull null] 用来表示空值的 Objective-C 对象
  • NULL一般用于表示 C 指针空值,如:
int *pointerToInt = NULL;
char *pointerToChar = NULL;
struct TreeNode *rootNode = NULL;
  • nil:
NSString *someString = nil;
NSURL *someURL = nil;
id someObject = nil;
if (anotherObject == nil) 
  • Nil:
Class someClass = Nil;
Class anotherClass = [NSString class];
  • NSNull
    是一个 Objective-C 对象,是一个用于表示空值的类,而且它只有一个单例方法:+[NSNull null],一般用于在集合对象中保存一个空的占位对象。
// 当 NSArray 里遇到 nil 时,就说明这个数组对象的元素截止了,即 NSArray 只关注 nil 之前的对象,nil 之后的对象会被抛弃。
NSArray *array = [NSArray arrayWithObjects:@"one", @"two", nil];

// 错误的使用
NSMutableDictionary *dict = [NSMutableDictionary dictionary];
[dict setObject:nil forKey:@"someKey"];

// 正确的使用
NSMutableDictionary *dict = [NSMutableDictionary dictionary];
[dict setObject:[NSNull null] forKey:@"someKey"];

取消延迟执行函数

  • 法一:performSelector
- (void)performSelector:(SEL)aSelector withObject:(id)anArgument afterDelay:(NSTimeInterval)delay inModes:(NSArray *)modes;  
- (void)performSelector:(SEL)aSelector withObject:(id)anArgument afterDelay:(NSTimeInterval)delay;  
// 取消某一个延时调用请求
+ (void)cancelPreviousPerformRequestsWithTarget:(id)aTarget selector:(SEL)aSelector object:(id)anArgument; 
// 取消performSelector的所有被延迟执行的方法 
+ (void)cancelPreviousPerformRequestsWithTarget:(id)aTarget; 
  • 法二:创建定时器
// 创建定时器的time-类方法,需要手动fire开启定时器,将执行方法封装到NSInvocation中
+ (NSTimer *)timerWithTimeInterval:(NSTimeInterval)ti invocation:(NSInvocation *)invocation repeats:(BOOL)yesOrNo;
// 创建定时器的time-类方法,需要手动fire开启定时器
+ (NSTimer *)timerWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(id)userInfo repeats:(BOOL)yesOrNo;
// 创建定时器的scheduled-类方法,不需要手动fire开启定时器,将执行方法封装到NSInvocation中
+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)ti invocation:(NSInvocation *)invocation repeats:(BOOL)yesOrNo;
// 创建定时器的scheduled-类方法,不需要手动fire开启定时器
+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(id)userInfo repeats:(BOOL)yesOrNo;
// 创建定时器的实例方法,会在指定时间开启定时器
- (instancetype)initWithFireDate:(NSDate *)date interval:(NSTimeInterval)ti target:(id)t selector:(SEL)s userInfo:(id)ui repeats:(BOOL)rep;
// 开启定时器
- (void)fire;
// 使定时器失效,将定时器从循环池移除掉
- (void)invalidate;

self.timer = [NSTimer scheduledTimerWithTimeInterval:3.0f target:self selector:@selector(prtintMyWords:) userInfo:@{@"words":@"hello world"} repeats:NO];
[self.timer invalidate];
  • 法三:使用GCD
    取消GCD的延迟执行还不知道如何操作,可参考Dispatch-Cancel
// 创建延迟时间,从当前时间开始,3秒后执行。 3秒需要转化为纳秒,因为该函数是以纳秒为基础进行的
dispatch_time_t delay = dispatch_time(DISPATCH_TIME_NOW, 3.0*NSEC_PER_SEC);
// 执行延迟函数
dispatch_after(delay, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
   [self printString:@"hello world"];
});
  • 注意:
    1、performSelector和scheduledTimerWithTimeInterval方法都是基于runloop的。我们知道,当一个应用启动时,系统会开启一个主线程,并且把主线程的runloop激活,也就是run起来,并且主线程的runloop是不会停止的。所以,当这两个方法在主线程可以被正常调用。但情况往往不是这样的。实际编码中,我们更多的逻辑是放在子线程中执行的。而子线程的runloop是默认关闭的。这时如果不手动激活runloop,performSelector和scheduledTimerWithTimeInterval的调用将是无效的。
    2、NSTimer、performSelector的创建与撤销必须在同一个线程操作。
    3、当一个timer被schedule的时候,timer会持有target对象,NSRunLoop对象会持有timer。当invalidate被调用时,NSRunLoop对象会释放对timer的持有,timer会释放对target的持有。除此之外,我们没有途径可以释放timer对target的持有。所以解决内存泄露就必须撤销timer,若不撤销,target对象将永远无法释放。
    4、若使用dispatch_after,系统会帮我们处理线程级的逻辑,这样也我们更易于享受系统对线程所做的优化。除此之外,我们不用关心runloop的问题。并且调用的对象也不会被强行持有,这样上述的内存问题也不复存在。当然,需要注意block会持有其传入的对象,但这可以通过weakself解决。所以在这种延迟操作方案中,使用dispatch_after更佳。

版本号如何控制

GNU 风格的版本号管理策略:一般的版本号划分为如下3个部分——“主版本号 . 子版本号 . 修正版本号”,简单直观清晰。
1、项目初版本时,版本号可以为 0.1 或 0.1.0, 也可以为 1.0 或 1.0.0 (例如: 1.0.0 )
2、当项目在进行了局部修改或 bug 修正时,主版本号和子版本号都不变,修正版本号加 1 (例如: 1.0.1 )
3、当项目在原有的基础上增加了部分功能时,主版本号不变,子版本号加 1,修正版本号复位为 0 (例如: 1.1.0 )
4、当项目在进行了重大修改或局部修正累积较多,而导致项目整体发生全局变化时,主版本号加 1 (例如: 2.0.0 )
5、另外,编译版本号一般是编译器在编译过程中自动生成的,我们只定义其格式,并不进行人为控制。

直接调用方法和使用performSelector调用的区别

1、两种方式执行的效果其实和发送消息是等价的。
2、performSelector允许发送未在运行时确定的消息;也就是说,只要这个消息能够被转发到正确的接收者,能够被最后的接收者识别,都是可以正确运行的。否则,就会在运行时报错“unrecognized selector sent to instance”。如果没有实现该方法,只能在运行时才会报错,编译阶段发现不了。
3、Obejct-C的动态特性是允许在运行时向某个类添加方法的,这个时候就一定需要使用performSelector。那么为了避免运行时出现错误,在使用performSelector之前一定要使用如下的检查方法来进行判断。我们一般都忽略的用该方法进行判断。

- (BOOL)respondsToSelector:(SEL)aSelector;

#pragma 处理防止编译器警告

  • 首先#pragma在本质上是声明,常用的功能就是注释,尤其是给Code分段注释;而且它还有另一个强大的功能是处理编译器警告,但却没有上一个功能用的那么多。
    clang diagnostic 是#pragma 第一个常用命令:
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-相关命令" 
// 你自己的代码
#pragma clang diagnostic pop
#pragma clang diagnostic push 
#pragma clang diagnostic ignored "-Wdeprecated-declarations" 
[TestFlight setDeviceIdentifier:[[UIDevice currentDevice] uniqueIdentifier]]; 
#pragma clang diagnostic pop

2、不兼容指针类型

#pragma clang diagnostic push 
#pragma clang diagnostic ignored "-Wincompatible-pointer-types"  
// 
#pragma clang diagnostic pop

3、循环引用

#pragma clang diagnostic push 
#pragma clang diagnostic ignored "-Warc-retain-cycles"  
  self.completionBlock = ^ {
     ... 
  }; 
#pragma clang diagnostic pop

4、未使用变量

#pragma clang diagnostic push 
#pragma clang diagnostic ignored "-Wunused-variable"  
int a; 
#pragma clang diagnostic pop

5、参数不固定的情况:以DKNightVersion为例

DKImagePicker DKImagePickerWithNames(NSString *normalName, ...) {
    NSArray<DKThemeVersion *> *themes = [DKColorTable sharedColorTable].themes;
    NSMutableArray<NSString *> *names = [[NSMutableArray alloc] initWithCapacity:themes.count];
    [names addObject:normalName];
    NSUInteger num_args = themes.count - 1;
    va_list names_list;
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wvarargs"
    va_start(names_list, num_args);
#pragma clang diagnostic pop
    for (NSUInteger i = 0; i < num_args; i++) {
        NSString *name = va_arg(names_list, NSString *);
        [names addObject:name];
    }
    va_end(names_list);

    return [DKImage pickerWithNames:names];
}

va_arg(va_list ap, type),参数ap应该首先被宏va_start 或 va_copy初始化,但又必须在被宏va_end调用之前使用。每次调用va_arg都会改变ap值使得后续的参数值能被依次添加。参数type应该是一个类型名,并且用type*能够得到该类型的指针类型。如果type为空,或者type和实际参数不匹配, 那么除了以下两种情况,这个宏的行为是未定义的。
用法:
(1)首先在函数里定义一具VA_LIST型的变量,这个变量是指向参数的指针;
(2)然后用VA_START宏初始化刚定义的VA_LIST变量;
(3)然后用VA_ARG返回可变的参数,VA_ARG的第二个参数是你要返回的参数的类型(如果函数有多个可变参数的,依次调用VA_ARG获取各个参数);
(4)最后用VA_END宏结束可变参数的获取。

在对象内部尽量直接访问实例变量

  • 在对象内部读取数据时,应该直接通过实例变量来读,而写入数据时,应该通过属性来写。
  • 在初始化及dealloc方法中,总是应该直接通过实例变量来读写数据。

消息传递、转发机制

  • objc_msgSend依据接收者和选择子的类型来调用适当的方法。该方法需要在接收者所属的类中搜寻其“方法列表”,如能找到与选择子名称相符的方法,就跳转至其实现代码。若找不到,沿着继承体系继续向上查找,等找到合适的方法后再跳转。如最终还是找不到相符的方法,就执行“消息转发”操作。
  • 消息转发分两步:
    1、先征询接收者所属的类,看其是否能动态添加方法,以处理当前这个“未知的选择子”,这叫做“动态方法解析”。
    2、涉及“完整的消息转发机制”,分两步:接收者看有没有其他对象能处理这条消息,若有,则运行期系统会把消息转给那个对象,消息转发结束。若没有“备援的接收者”,则启动完整的消息转发机制,运行期系统会把消息有关的全部细节封装到NSInvocation中,再给接收者最后一次机会,令其设法解决当前还未处理的这条消息。

isMemberOfClass:与isKindOfClass:区别

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

推荐阅读更多精彩内容