diff --git a/_config.yml b/_config.yml index 409f2d263dc..0ca4f046604 100644 --- a/_config.yml +++ b/_config.yml @@ -1,29 +1,29 @@ # Site settings -title: BY Blog -SEOTitle: 柏荧的博客 | BY Blog +title: No-Error +SEOTitle: No-Error的博客 | No-Error's Blog header-img: img/post-bg-desk.jpg -email: qiubaiying@gmail.com -description: "Every failure is leading towards success." -keyword: "BY, BY Blog, 柏荧的博客, qiubaiying, 邱柏荧, iOS, Apple, iPhone" -url: "http://qiubaiying.github.io" # your host, for absolute URL +email: 949664458@gmail.com +description: " May I get some error?" +keyword: "No-Error, No-Error's Blog, ;No-Error的博客, No-Error, No-Error, iOS, Apple, iPhone" +url: "http://linpupu.github.io" # your host, for absolute URL baseurl: "" # for example, '/blog' if your blog hosted on 'host/blog' -github_repo: "https://github.com/qiubaiying/qiubaiying.github.io.git" # you code repository +github_repo: "https://github.com/linpupu/linpupu.github.io.git" # you code repository # Sidebar settings sidebar: true # whether or not using Sidebar. sidebar-about-description: "Goals determine what you going to be!" -sidebar-avatar: /img/about-BY-gentle.jpg # use absolute URL, seeing it's used in both `/` and `/about/` +sidebar-avatar: /img/about-Lyn-gentle.jpg # use absolute URL, seeing it's used in both `/` and `/about/` # SNS settings RSS: false -# weibo_username: qiubaiying -zhihu_username: qiubaiying -github_username: qiubaiying -facebook_username: baiying.qiu.7 -jianshu_username: e71990ada2fd -#twitter_username: qiubaiying +# weibo_username: linpupu +#zhihu_username: linpupu +github_username: linpupu +#facebook_username: baiying.qiu.7 +#jianshu_username: e71990ada2fd +#twitter_username: linpupu @@ -57,16 +57,16 @@ kramdown: # 评论系统 # Disqus(https://disqus.com/) -# disqus_username: qiubaiying +# disqus_username: linpupu # Gitalk gitalk: enable: true #是否开启Gitalk评论 clientID: f2c84e7629bb1446c1a4 #生成的clientID clientSecret: ca6d6139d1e1b8c43f8b2e19492ddcac8b322d0d #生成的clientSecret - repo: qiubaiying.github.io #仓库名称 - owner: qiubaiying #github用户名 - admin: qiubaiying + repo: linpupu.github.io #仓库名称 + owner: linpupu #github用户名 + admin: linpupu distractionFreeMode: true #是否启用类似FB的阴影遮罩 @@ -78,7 +78,7 @@ ba_track_id: b50bf2b12b5338a1845e33832976fd68 # Google Analytics ga_track_id: 'UA-90855596-1' # Format: UA-xxxxxx-xx -ga_domain: qiubaiying.top # 默认的是 auto, 这里我是自定义了的域名,你如果没有自己的域名,需要改成auto +ga_domain: linpupu.top # 默认的是 auto, 这里我是自定义了的域名,你如果没有自己的域名,需要改成auto diff --git "a/_posts/2016-10-10-iOS-\346\211\213\345\212\277\344\270\216\345\217\230\345\275\242.md" "b/_posts/2016-10-10-iOS-\346\211\213\345\212\277\344\270\216\345\217\230\345\275\242.md" deleted file mode 100644 index 46df1778ae1..00000000000 --- "a/_posts/2016-10-10-iOS-\346\211\213\345\212\277\344\270\216\345\217\230\345\275\242.md" +++ /dev/null @@ -1,467 +0,0 @@ ---- -layout: post -title: iOS手势与变形 -subtitle: 手势与变形基础知识笔记 -date: 2016-10-10 -author: BY -header-img: img/post-bg-ios9-web.jpg -catalog: true -tags: - - iOS - - iOS开发基础 ---- - ->手势在用户交互中有着举足轻重的作用,这篇文字简单的介绍了iOS中的手势,并通过手势对控件进行变形处理。 - -# 手势 - -iOS手势分为下面这几种: - -- UITapGestureRecognizer(点按) -- UIPanGestureRecognizer(拖动) -- UIScreenEdgePanGestureRecognizer (边缘拖动) -- UIPinchGestureRecognizer(捏合) -- UIRotationGestureRecognizer(旋转) -- UILongPressGestureRecognizer(长按) -- ​UISwipeGestureRecognizer(轻扫) - - - -这些手势大都继承于**UIGestureRecognizer**类,(`UIScreenEdgePanGestureRecognizer`继承于`UIPanGestureRecognizer`类), - -需要说明的是这些手势只有一个是**离散型手势**,那就是`UITapGestureRecognizer`,一旦识别就无法取消,而且只会调用一次手势操作事件。 - -换句话说其他手势是**连续型手势**,而连续型手势的特点就是:会多次调用手势操作事件,而且在连续手势识别后可以取消手势。 - -从下图可以看出两者调用操作事件的次数是不同的: - -![](http://ww3.sinaimg.cn/large/006tNc79gw1fb0neee6mlj30dw0aldgf.jpg) - -这些手势类有着以下共同的方法: - - -创建方法: - -``` -- (instancetype)initWithTarget:(nullable id)target action:(nullable SEL)action; -``` - -移除方法: - -``` -- (void)removeTarget:(nullable id)target action:(nullable SEL)action; -``` - -添加事件: - -``` -- (void)addTarget:(id)target action:(SEL)action; -``` - -还有下面这些属性等: - -``` -@property(nonatomic,readonly) UIGestureRecognizerState state;// 手势状态 - -typedef NS_ENUM(NSInteger, UIGestureRecognizerState) { - UIGestureRecognizerStatePossible, // 尚未识别是何种手势操作(但可能已经触发了触摸事件),默认状态 - UIGestureRecognizerStateBegan, // 手势已经开始,此时已经被识别,但是这个过程中可能发生变化,手势操作尚未完成 - UIGestureRecognizerStateChanged, // 手势状态发生转变 - UIGestureRecognizerStateEnded, // 手势识别操作完成(此时已经松开手指) - UIGestureRecognizerStateCancelled, // 手势被取消,恢复到默认状态 - UIGestureRecognizerStateFailed, // 手势识别失败,恢复到默认状态 - UIGestureRecognizerStateRecognized = UIGestureRecognizerStateEnded // 手势识别完成,同UIGestureRecognizerStateEnded - }; - -@property(nullable,nonatomic,weak) id delegate; // 代理 - -@property(nonatomic, getter=isEnabled) BOOL enabled; -``` - -当然我们也可以自定义手势来实现特殊的需求,关于自定义手势可以看[这篇博客](http://blog.csdn.net/mmoaay/article/details/47355709). - -接下来我们来看看这些常用手势的用法. - -#### UITapGestureRecognizer(点按) - -Tap手势有两个属性, - -- numberOfTapsRequired -- numberOfTouchesRequired: - -`numberOfTapsRequired`为触发事件需要点击的次数,默认是1; - -`numberOfTouchesRequired`为触发事件需要的几个手指点按,默认是1; - -若都设置为2,就需要`两个`手指同时点按`2次`才会触发事件。 - -Tap手势也是我们最常用的手势之一, 比如点击ImageView跳转到其他界面,或者双击图片放大缩小等。 - -创建: - -``` - UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc]initWithTarget:self action:@selector(tap:)]; - tap.numberOfTapsRequired = 2; - tap.numberOfTouchesRequired = 1; - [self.imageView addGestureRecognizer:tap]; -``` - -#### UIPanGestureRecognizer(拖动) - -Pan手势的属性和方法: - -- @property (nonatomic) NSUInteger minimumNumberOfTouches __TVOS_PROHIBITED; -- @property (nonatomic) NSUInteger minimumNumberOfTouches __TVOS_PROHIBITED; -- \-(CGPoint)translationInView:(nullable UIView *)view; -- \-(void)setTranslation:(CGPoint)translation inView:(nullable UIView *)view; -- \-(CGPoint)velocityInView:(nullable UIView *)view; - -`translationInView:`方法获取`View`的偏移量; - -`setTranslation:`方法设置手势的偏移量; - -`velocityInView:`方法获取速度; - -所以手势的创建方法都类似,这里就不在一一列举了。 - - -#### UIScreenEdgePanGestureRecognizer (边缘拖动) - -ScreenEdgePan继承于`UIPanGestureRecognizer`,在屏幕边缘滑动才会触发 - -- @property (readwrite, nonatomic, assign) UIRectEdge edges; - -`edges`为指定边缘拖动触发的边,是一个枚举: - -``` -typedef NS_OPTIONS(NSUInteger, UIRectEdge) { - UIRectEdgeNone = 0, - UIRectEdgeTop = 1 << 0, - UIRectEdgeLeft = 1 << 1, - UIRectEdgeBottom = 1 << 2, - UIRectEdgeRight = 1 << 3, - UIRectEdgeAll = UIRectEdgeTop | UIRectEdgeLeft | UIRectEdgeBottom | UIRectEdgeRight -} NS_ENUM_AVAILABLE_IOS(7_0); -``` -其他方法和Tap手势一致,主要用于像左右抽屉视图的变换等处理。 - - -#### UIPinchGestureRecognizer(捏合) -Pinch手势有两个属性: - -- @property (nonatomic) CGFloat scale; -- @property (nonatomic,readonly) CGFloat velocity; - -`scale`:捏合比例 - -`velocity`:捏合速度 = `scale/second` - -#### UIRotationGestureRecognizer(旋转) -Rotation手势和Pinch手势类似,同样有两个手势: - -- @property (nonatomic) CGFloat rotation; -- @property (nonatomic,readonly) CGFloat velocity; - -`rotation`:旋转弧度,注意,这里的单位是**弧度** - -`velocity`:旋转速度 - -#### UILongPressGestureRecognizer(长按) - -LongPress的属性: - -- @property (nonatomic) NSUInteger numberOfTapsRequired; // Default is 0. -- @property (nonatomic) NSUInteger numberOfTouchesRequired __TVOS_PROHIBITED; // Default is 1. -- @property (nonatomic) CFTimeInterval minimumPressDuration; -- @property (nonatomic) CGFloat allowableMovement; - -`numberOfTapsRequired`和`numberOfTouchesRequired`和Tap手势类似,都是指定触发需要的点击次数和手指数量,但是LongPress手势的`numberOfTapsRequired`是指定长按前需要点击的次数。 - -`minimumPressDuration`:触发时间 - -`allowableMovement`:允许长按时间触发前允许手指滑动的范围。若是你在长按时手指移动,该长按手势将会失败,`allowableMovement`设置你能容忍的滑动范围,默认是10. - -# 变形 ---- -iOS的变形指的是图片的旋转、平移和缩放。这些变形可以和上面介绍的手势结合,完成许多变形操作。 - -说变形前我们来看看**CGAffineTransform**,**CGAffineTransform**为一个结构体: - -``` -struct CGAffineTransform { - CGFloat a, b, c, d; - CGFloat tx, ty; -}; -``` -我们输出一个控件的`transform`看看 - -``` -NSLog(@"%@", NSStringFromCGAffineTransform(self.label.transform)); -``` - -输出 - -``` -2016-12-22 17:01:19.211 手势[6489:1481987] [1, 0, 0, 1, 0, 0] -``` - -我们可以看到输出了一个长度为6的数组:`[1, 0, 0, 1, 0, 0]`,并且我们可以猜测对应结构体中的`[a, b, c, d, tx, ty]`,并且默认的`transform`值就是`[1, 0, 0, 1, 0, 0]`。 - -想进一步了解可以看这篇[《iOS CGAffineTransform详解》](http://blog.csdn.net/ashiyanshi/article/details/48160429) - -对iOS控件进行变形实际就是对控件`transform`属性进行操作。 - -但是我们使用中,使用已经封装好的的API对控件进行变形处理。分别是: - - -- CGAffineTransformScale() -- CGAffineTransformTranslate() -- CGAffineTransformRotate() - -和: - -- CGAffineTransformMakeScale() -- CGAffineTransformMakeTranslate() -- CGAffineTransformMakeRotate() - -这些API都是对设置`CGAffineTransform`的一个封装,针对`[a, b, c, d, tx, ty]`中不同的位置进行操作。 - - -下面我们在ViewController创建一个`UILabel`控件。然后对它进行变形操作。 - -![](http://ww4.sinaimg.cn/large/006tNc79jw1fazplz4mvmj306x0cbt8m.jpg) - -#### 缩放 - -首先来看一个缩放操作 - -``` -// 缩放到90%(相对) -self.label.transform = CGAffineTransformScale(self.label.transform, 0.9, 0.9); - -NSLog(@"%@", NSStringFromCGAffineTransform( self.label.transform)); - -``` - -输出: - -``` -2016-12-22 17:26:25.074 手势[6526:1564064] [0.90000000000000002, 0, 0, 0.90000000000000002, 0, 0] -2016-12-22 17:26:26.096 手势[6526:1564064] [0.81000000000000005, 0, 0, 0.81000000000000005, 0, 0] -2016-12-22 17:26:26.963 手势[6526:1564064] [0.72900000000000009, 0, 0, 0.72900000000000009, 0, 0] -2016-12-22 17:26:28.830 手势[6526:1564064] [0.65610000000000013, 0, 0, 0.65610000000000013, 0, 0] -``` - -我们再看看另一个缩放: - -``` -self.label.transform = CGAffineTransformMakeScale(0.9, 0.9); - -NSLog(@"%@", NSStringFromCGAffineTransform( self.label.transform)); -``` - -输出 - -``` -2016-12-22 17:32:32.972 手势[6581:1600236] [0.90000000000000002, 0, 0, 0.90000000000000002, 0, 0] -2016-12-22 17:32:34.164 手势[6581:1600236] [0.90000000000000002, 0, 0, 0.90000000000000002, 0, 0] -2016-12-22 17:32:35.246 手势[6581:1600236] [0.90000000000000002, 0, 0, 0.90000000000000002, 0, 0] -``` - -对比可以发现`CGAffineTransformScale()`与`CGAffineTransformMakeScale()`的区别在于,`CGAffineTransformScale()`实在原理的基础上在进行缩放操作,而`CGAffineTransformMakeScale()`直接将缩放值设定为0.9不变了。 - -缩放操作变动的是构体中`[a, b, c, d, tx, ty]`的`a`和`d`,值和变形系数`Scale`是相对应的,大于1是放大,小于1是缩小。。 - -`a`是横向缩放, `d`是纵向缩放。 - -#### 平移 - -先来看一个平移操作: - -``` -self.label.transform = CGAffineTransformTranslate(self.label.transform, 10, 10); - NSLog(@"%@", NSStringFromCGAffineTransform( self.label.transform)); -``` -输出 - -``` -2016-12-22 17:40:38.568 手势[6608:1631232] [1, 0, 0, 1, 10, 10] -2016-12-22 17:40:40.833 手势[6608:1631232] [1, 0, 0, 1, 20, 20] -2016-12-22 17:40:41.834 手势[6608:1631232] [1, 0, 0, 1, 30, 30] -2016-12-22 17:40:42.532 手势[6608:1631232] [1, 0, 0, 1, 40, 40] -2016-12-22 17:40:43.162 手势[6608:1631232] [1, 0, 0, 1, 50, 50] -``` - -我们可以看到label往右下角移动 - -![](http://ww1.sinaimg.cn/large/006tNc79jw1fazply7kkpj306v0ca0sp.jpg) - -对应xy的正向坐标为右下角。 - -#### 旋转 - -``` -self.label.transform = CGAffineTransformRotate(self.label.transform, M_PI_2); -NSLog(@"%@", NSStringFromCGAffineTransform( self.label.transform)); -``` -输出: - -``` -2016-12-22 17:59:43.680 手势[6667:1717130] [6.123233995736766e-17, 1, -1, 6.123233995736766e-17, 0, 0] -``` - -![](http://ww2.sinaimg.cn/large/006tNc79gw1fazq3j2ud5j306z0cfdft.jpg) - -可以看到`label`顺时针旋转了`π/2`弧度(90°)。 - -# 手势结合变形 ---- -手势结合变形就是通过手势对控件变形处理。 - -上代码: - -``` -#import "ViewController.h" - -@interface ViewController () -@property (weak, nonatomic) IBOutlet UIImageView *imageView; - -@end - -@implementation ViewController - -- (void)viewDidLoad { - [super viewDidLoad]; - -// CGAffineTransform *mytransform = self.imageView.transform; - self.imageView.userInteractionEnabled = YES; - //1双击 恢复 - UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc]initWithTarget:self action:@selector(tap:)]; - tap.numberOfTapsRequired = 2; - tap.numberOfTouchesRequired = 1; - [self.imageView addGestureRecognizer:tap]; - - //2拖拽 - UIPanGestureRecognizer *pan = [[UIPanGestureRecognizer alloc]initWithTarget:self action:@selector(pan:)]; -// pan.delegate = self; - [self.imageView addGestureRecognizer:pan]; - - //3捏合 - UIPinchGestureRecognizer *pinch = [[UIPinchGestureRecognizer alloc]initWithTarget:self action:@selector(pinch:)]; - pinch.delegate = self; - [self.imageView addGestureRecognizer:pinch]; - - //4旋转 - UIRotationGestureRecognizer *rotaion = [[UIRotationGestureRecognizer alloc]initWithTarget:self action:@selector(rotaion:)]; -// pinch.delegate = self; - [self.imageView addGestureRecognizer:rotaion]; - - // 长按 - UILongPressGestureRecognizer *longPress = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(longPress:)]; - longPress.numberOfTapsRequired = 0; - longPress.minimumPressDuration = 1; -// longPress.allowableMovement = 3; - [self.imageView addGestureRecognizer:longPress]; - -} -- (void)tap:(UITapGestureRecognizer *)sender{ - - NSLog(@"tap!"); - - //恢复 - self.imageView.transform = CGAffineTransformIdentity; - -} -- (void)pan:(UIPanGestureRecognizer *)sender{ -// CGPoint center = self.imageView.center; -// if (center.x < 0){ -// center.x = 0; -// }else{ -// center.x += [sender translationInView:self.view].x; -// } -// -// center.y += [sender translationInView:self.view].y; -// self.imageView.center = center; - //将相对偏移量清零 -// [sender setTranslation:CGPointMake(0, 0) inView:self.view]; - - self.imageView.transform = CGAffineTransformTranslate(self.imageView.transform, [sender translationInView:self.imageView].x, [sender translationInView:self.imageView].y); - [sender setTranslation:CGPointZero inView:self.view]; - -} - -- (void)pinch:(UIPinchGestureRecognizer *)sender{ - - CGFloat scale = sender.scale; - self.imageView.transform = CGAffineTransformScale(self.imageView.transform, scale, scale); - [sender setScale:1]; - -} - -- (void)rotaion:(UIRotationGestureRecognizer *)sender{ - //获取旋转弧度 - CGFloat rotation = sender.rotation; - self.imageView.transform = CGAffineTransformRotate(self.imageView.transform, rotation); - sender.rotation = 0; - -// self.imageView.transform = CGAffineTransformMakeRotation(sender.rotation); - -} - -- (void)longPress:(UILongPressGestureRecognizer *)sender { - - NSLog(@"longPress:%@", sender); - - // 判断长按事件触发 - if (sender.state == UIGestureRecognizerStateBegan) { - self.imageView.transform = CGAffineTransformMake(1, 0, 0, 1, 0, 0); - } - -} - - -//希望两个手势共存 -//遵守 UIGestureRecognizerDelegate 协议 -//实现方法 -(BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer -//将要同时实现的手势设置代理 pinch.delegate = self; pinch.delegate = self; - --(BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer{ - return YES; -} - -``` - -有几点需要注意: - -- 给本身没有交互功能的控件()imagView, UIlabel, View等)添加手势,要设置`userInteractionEnabled`为**YES**,否则识别不了手势 -- 想要手势共存需要: - - 遵守 `UIGestureRecognizerDelegate` 协议 - - 实现`-(BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer`方法,返回`YES` - - 将要同时实现的手势设置代理 `pinch.delegate = self`; `pinch.delegate = self` - -# 在storyboard中添加手势 - -在`storyboard`的控件栏中我们可以看到这些手势控件: - -![storyboard中的手势控件](http://ww1.sinaimg.cn/large/006tNc79gw1fb0j188yh1j30780evwfq.jpg) - -#### 使用方法: - -1. 直接将手势控件拖到要添加的视图上 - - ![](http://ww3.sinaimg.cn/large/006tNc79gw1fb0ja1f8fnj30f206nwev.jpg) - -2. 关联手势事件 - - ![](http://ww2.sinaimg.cn/large/006tNc79gw1fb0jaxllv6j30ol0be77b.jpg) - -3. 设置手势属性 - - ![](http://ww2.sinaimg.cn/large/006tNc79gw1fb0jc5mon3j307c06ydgd.jpg) - -注意:若想同时识别多个手势,方法和上面相同,遵循协议,实现方法,设置代理,不过代理可以手动关联。 - -![](http://ww4.sinaimg.cn/large/006tNc79gw1fb0jokip2vj30ej0aq3zz.jpg) - - - diff --git "a/_posts/2016-10-18-Xcode-Debug-\345\244\247\345\205\250.md" "b/_posts/2016-10-18-Xcode-Debug-\345\244\247\345\205\250.md" deleted file mode 100644 index ce2e762f275..00000000000 --- "a/_posts/2016-10-18-Xcode-Debug-\345\244\247\345\205\250.md" +++ /dev/null @@ -1,249 +0,0 @@ ---- -layout: post -title: Xcode Debug 大全 -subtitle: iOS开发中利用 Xcode 各种调试Bug方法 -date: 2016-10-18 -author: BY -header-img: img/post-bg-ios9-web.jpg -catalog: true -tags: - - iOS - - 开发技巧 - - Debug ---- - - -# 前言 - ->BUG,简单来说就是程序运行结果与预期的不同,下面来说说Xcode中的DEBUG方法 -> ->[参考博文](http://www.cnblogs.com/daiweilai/p/4421340.html#quanjuduandian) - - - -# 断点调试 - -- 普通断点 -- 全局断点 -- 条件断点 - -#### 1.普通断点 -看图 - -![](http://ww4.sinaimg.cn/large/65e4f1e6gw1f8rti38wlxj20ke0d3n0h.jpg) - -当程序运行到断点处时会停下,然后进行单步调试 -![](http://images.cnitblog.com/blog2015/680363/201504/131002381048966.png) - - -#### 2.全局断点 - -当程序运行出现崩溃时,就会自动断点到出现crash的代码行 - -![](http://images.cnitblog.com/blog2015/680363/201504/130933043392329.png) - -#### 3.条件断点 - - -我们如果在一个循环里面使用了断点,如果这个循环执行了100万次,那你的断点要执行那么多次,你不觉得蛋蛋都凉了的忧伤么?所以我们这么做: - -编辑断点 - -![](http://ww1.sinaimg.cn/large/65e4f1e6gw1f8rw64yys0j207i03laah.jpg) - -添加条件Condition - -![](http://ww2.sinaimg.cn/large/65e4f1e6gw1f8rw52q1tjj20ct04lmxo.jpg) - - -![](http://ww3.sinaimg.cn/large/65e4f1e6gw1f8rw44p4ykj20ln0g10vg.jpg) - -还可以Action中在条件断点触发时执行事件 - -![](http://ww3.sinaimg.cn/large/65e4f1e6gw1f8rwq16872j20cv07amyg.jpg) - -如:输出信息 - -![](http://ww2.sinaimg.cn/large/65e4f1e6gw1f8rwms50t3j20dj07bjso.jpg) - -#### 4.方法断点 - -# 打印调试(NSLog) - -尽管ARC已经让内存管理变得简单、省时和高效,但是在object的life-cycles中跟踪一些重要事件依然十分重要。毕竟ARC并没有完全排除内存泄露的可能性,或者试图访问一个被release的对象。 - -- NSLog - -强化NSLog - -``` -//A better version of NSLog -#define NSLog(format, ...) do { \ -fprintf(stderr, "<%s : %d> %s\n", \ -[[[NSString stringWithUTF8String:__FILE__] lastPathComponent] UTF8String], \ -__LINE__, __func__); \ -(NSLog)((format), ##__VA_ARGS__); \ -fprintf(stderr, "-------\n"); \ -} while (0) -``` -控制台输出 - -``` - -[ViewController viewDidLoad] -2016-10-14 17:33:31.022 DEUBG[12852:1238167] Hello World! -------- -``` - -利用NSString输出多种类型 - -![](http://ww4.sinaimg.cn/large/65e4f1e6gw1f8rxvn6fqlj20nc05cgoh.jpg) - -- 开启僵尸对象 - -Xcode可以把那些已经release掉得对象,变成“僵尸”,当我们访问一个Zombie对象时,Xcode可以告诉我们正在访问的对象是一个不应该存在的对象了。因为Xcode知道这个对象是什么,所以可以让我们知道这个对象在哪里,以及这是什么时候发生的。 -所以Zombies是你的好基友!他可以让你输出的信息更具体! - -具体这样做:(僵尸只能用在模拟器和OC语言) - -![](http://images.cnitblog.com/blog2015/680363/201504/130941016986159.png) - -# 控制台(lldb 命令) - - - -LLDB 是一个有着 REPL 的特性和 C++ ,Python 插件的开源调试器。LLDB 绑定在 Xcode 内部,存在于主窗口底部的控制台中。调试器允许你在程序运行的特定时暂停它,你可以查看变量的值,执行自定的指令,并且按照你所认为合适的步骤来操作程序的进展。(这里有一个关于调试器如何工作的总体的解释。) - -你以前有可能已经使用过调试器,即使只是在 Xcode 的界面上加一些断点。但是通过一些小的技巧,你就可以做一些非常酷的事情。GDB to LLDB 参考是一个非常好的调试器可用命令的总览。你也可以安装 Chisel,它是一个开源的 LLDB 插件合辑,这会使调试变得更加有趣。 - -参考: - -[与调试器共舞 - LLDB 的华尔兹](http://objccn.io/issue-19-2/) - -[LLDB调试命令初探](http://www.starfelix.com/blog/2014/03/17/lldbdiao-shi-ming-ling-chu-tan/) - -[About LLDB and Xcode](https://developer.apple.com/library/content/documentation/IDEs/Conceptual/gdb_to_lldb_transition_guide/document/Introduction.html) - -[The LLDB Debugger](http://lldb.llvm.org/tutorial.html) - -#### 基础 -###### *help* -在控制台输入`help`,显示控制台支持的lldb命令 - -###### *print* -打印值 - -缩写`p` - -print是 `expression --` 的缩写 - - -![](http://ww4.sinaimg.cn/large/006y8lVagw1f8vakv88vuj30b204s74x.jpg) - -printk可以指定格式打印 -如 -`默认 p` - -`十六进制 p/x`、 - -`二进制 p/t` - -``` -(lldb) p 16 -16 - -(lldb) p/x 16 -0x10 - -(lldb) p/t 16 -0b00000000000000000000000000010000 - -(lldb) p/t (char)16 -0b00010000 - -``` -你也可以使用 p/c 打印字符,或者 p/s 打印以空终止的字符串 p/d打印ACRSII(译者注:以 '\0' 结尾的字符串)。 - -完整清单[点击查看](https://sourceware.org/gdb/onlinedocs/gdb/Output-Formats.html) - - -###### *po* - -打印对象,是 `e -o --`的缩写 - -###### *expression* - -#### 流程控制 - -当你通过 Xcode 的源码编辑器的侧边槽 (或者通过下面的方法) 插入一个断点,程序到达断点时会就会停止运行。 - -调试条上会出现四个你可以用来控制程序的执行流程的按钮。 - -![](https://objccn.io/images/issues/issue-19/Image_2014-11-22_at_10.37.45_AM.png) - -从左到右,四个按钮分别是:continue,step over,step into,step out。 - -第一个,continue 按钮,会取消程序的暂停,允许程序正常执行 (要么一直执行下去,要么到达下一个断点)。在 LLDB 中,你可以使用 process continue 命令来达到同样的效果,它的别名为 continue,或者也可以缩写为 c。 - -第二个,step over 按钮,会以黑盒的方式执行一行代码。如果所在这行代码是一个函数调用,那么就不会跳进这个函数,而是会执行这个函数,然后继续。LLDB 则可以使用 thread step-over,next,或者 n 命令。 - -如果你确实想跳进一个函数调用来调试或者检查程序的执行情况,那就用第三个按钮,step in,或者在LLDB中使用 thread step in,step,或者 s 命令。注意,当前行不是函数调用时,next 和 step 效果是一样的。 - -大多数人知道 c,n 和 s,但是其实还有第四个按钮,step out。如果你曾经不小心跳进一个函数,但实际上你想跳过它,常见的反应是重复的运行 n 直到函数返回。其实这种情况,step out 按钮是你的救世主。它会继续执行到下一个返回语句 (直到一个堆栈帧结束) 然后再次停止。 - -###### frame info - -会告诉你当前的行数和源码文件 - -``` -(lldb) frame info -frame #0: 0x000000010a53bcd4 DebuggerDance`main + 68 at main.m:17 - -``` - -###### Thread Return -调试时,还有一个很棒的函数可以用来控制程序流程:thread return 。它有一个可选参数,在执行时它会把可选参数加载进返回寄存器里,然后立刻执行返回命令,跳出当前栈帧。这意味这函数剩余的部分不会被执行。这会给 ARC 的引用计数造成一些问题,或者会使函数内的清理部分失效。但是在函数的开头执行这个命令,是个非常好的隔离这个函数,伪造返回值的方式 。 - -`(lldb) thread return NO` - -#### 不用断点调试 - -在程序运行时,点击暂停按钮,即可进入调试状态,能对全局变量做操作 - -![](http://ww4.sinaimg.cn/large/006y8lVagw1f8vd4vy66ej307300xjr8.jpg) - - -# 工具调试(instruments) -instruments Xcode自带许多工具供大家使用,打开方式如下图: - -![](http://ww1.sinaimg.cn/large/006y8lVagw1f8ve05g45cj30qd0f276o.jpg) - -**leaks**内存泄漏检查工具 - -![](http://ww4.sinaimg.cn/large/006y8lVagw1f8ve5wnnr6j30li0c1wgd.jpg) - -运行后查看 - -![](http://ww4.sinaimg.cn/large/006y8lVagw1f8vebiu6r5j30se0kdqcr.jpg) - -# 视图调试 - -启用视图调试:运行app过程中,按下底部的Debug View Hierarchy 按钮,或者从菜单中选择Debug > View Debugging > Capture View Hierarchy 来启动视图调试。 - -![](http://ww1.sinaimg.cn/large/006y8lVagw1f8vejy3rmgj30by01kmx8.jpg) - -启动视图调试后,Xcode会对应用程序的视图层次拍一个快照并展示三维原型视图来探究用户界面的层级。该三维视图除了展示app的视图层次外,还展示每个视图的位置、顺序和视图尺寸,以及视图间的交互方式。 - -# 模拟器调试 - -编译并运行应用程序,选中模拟器,从 Debug菜单中选择Color Blended Layers选项。 - -![](http://ww2.sinaimg.cn/large/006y8lVagw1f8vezdqlh1j3092075dgz.jpg) - -然后会看到app的用户界面被红色和绿色覆盖,显示了哪些图层可以被叠加覆盖,以及哪些图层是透明的。混合层属于计算密集型视图,所以推荐尽可能地使用不透明的图层。 - -![](http://ww3.sinaimg.cn/large/006y8lVagw1f8vf07u522j30ag0j1q36.jpg) - -# 结语 -目前所知道的调试方法大概就是上面这几种了,若有什么有趣的方法,请和我分享哈! - - diff --git a/_posts/2016-10-26-BYPhoneNumTF.md b/_posts/2016-10-26-BYPhoneNumTF.md deleted file mode 100644 index 74900bf67cb..00000000000 --- a/_posts/2016-10-26-BYPhoneNumTF.md +++ /dev/null @@ -1,397 +0,0 @@ ---- -layout: post -title: BYPhoneNumTF -subtitle: 一个电话号码格式的文本框 -date: 2017-02-04 -author: BY -header-img: img/post-bg-ios9-web.jpg -catalog: true -tags: - - iOS - - 轮子 ---- - ->**BYPhoneNumTF** 一个电话号码格式的文本框 - -# 功能 - -当在`TextField`输入数字时,会自动分隔为:137 9922 2299 或 137-9922-2299 - -限制文本输入个数 - -限制只能输入数字 -# 效果: - -![](http://ww2.sinaimg.cn/large/7853084cgw1fa3cqnu8s2g207i0dc4qp.gif) - - -# 实现方法 - -要实现电话号码格式的输入看似简单,但是实现起来坑非常多,至于坑是什么只有各位动手写了才能体会~ - -下面我们来实现该功能: - -首先要遵守协议`` - -然后在`- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string` 方法中实现我们的逻辑 - -代码: - - -``` -#import "LoginVC.h" - -#define placeholder @" " - -@interface LoginVC () - -@property (weak, nonatomic) IBOutlet UITextField *phoneNumberTF; - -@end - - -- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string { - - NSString *phStr = placeholder; - unichar phChar = ' '; - if (phStr.length) { - phChar = [phStr characterAtIndex:0]; - } - - - if (textField) { - NSString* text = textField.text; - //删除 - if([string isEqualToString:@""]){ - - //删除一位 - if(range.length == 1){ - //最后一位,遇到空格则多删除一次 - if (range.location == text.length - 1 ) { - if ([text characterAtIndex:text.length - 1] == phChar) { - [textField deleteBackward]; - } - return YES; - } - //从中间删除 - else{ - NSInteger offset = range.location; - - if (range.location < text.length && [text characterAtIndex:range.location] == phChar && [textField.selectedTextRange isEmpty]) { - [textField deleteBackward]; - offset --; - } - [textField deleteBackward]; - textField.text = [self _parseString:textField.text]; - UITextPosition *newPos = [textField positionFromPosition:textField.beginningOfDocument offset:offset]; - textField.selectedTextRange = [textField textRangeFromPosition:newPos toPosition:newPos]; - return NO; - } - } - else if (range.length > 1) { - BOOL isLast = NO; - //如果是从最后一位开始 - if(range.location + range.length == textField.text.length ){ - isLast = YES; - } - [textField deleteBackward]; - textField.text = [self _parseString:textField.text]; - - NSInteger offset = range.location; - if (range.location == 3 || range.location == 8) { - offset ++; - } - if (isLast) { - //光标直接在最后一位了 - }else{ - UITextPosition *newPos = [textField positionFromPosition:textField.beginningOfDocument offset:offset]; - textField.selectedTextRange = [textField textRangeFromPosition:newPos toPosition:newPos]; - } - - return NO; - } - - else{ - return YES; - } - } - - else if(string.length >0){ - - //限制输入字符个数 - if (([self _noneSpaseString:textField.text].length + string.length - range.length > 11) ) { - return NO; - } - - //判断是否是纯数字(搜狗,百度输入法,数字键盘居然可以输入其他字符) - if(![self _isNum:string]){ - return NO; - } - [textField insertText:string]; - textField.text = [self _parseString:textField.text]; - - NSInteger offset = range.location + string.length; - if (range.location == 3 || range.location == 8) { - offset ++; - } - UITextPosition *newPos = [textField positionFromPosition:textField.beginningOfDocument offset:offset]; - textField.selectedTextRange = [textField textRangeFromPosition:newPos toPosition:newPos]; - return NO; - }else{ - return YES; - } - - } - - return YES; - - -} - -- (NSString*)_parseString:(NSString*)string{ - - if (!string) { - return nil; - } - NSMutableString* mStr = [NSMutableString stringWithString:[string stringByReplacingOccurrencesOfString:placeholder withString:@""]]; - if (mStr.length >3) { - [mStr insertString:placeholder atIndex:3]; - }if (mStr.length > 8) { - [mStr insertString:placeholder atIndex:8]; - - } - - return mStr; - -} - -/** 获取正常电话号码(去掉空格) */ -- (NSString*)_noneSpaseString:(NSString*)string{ - - return [string stringByReplacingOccurrencesOfString:placeholder withString:@""]; - -} - -- (BOOL)_isNum:(NSString *)checkedNumString { - - if (!checkedNumString) { - return NO; - } - - checkedNumString = [checkedNumString stringByTrimmingCharactersInSet:[NSCharacterSet decimalDigitCharacterSet]]; - - if(checkedNumString.length > 0) { - return NO; - } - - return YES; - -} - -``` - - -###封装方法 - -需要实现的代码就是要这么多,但这些代码写在ViewController显得太臃肿了,所以我对代码进行了封装: - -``` -// -// BYPhoneNumTF.h -// -// Created by BY on 16/12/2. -// Copyright © 2016年 BY. All rights reserved. -// 电话号码类型的文本输入框,且只能输入数字 -// 输入显示:137 9922 1234 或 137-9922-1234 -// 使用方法:在XIB中的TextField继承该类即可 -// 修改占位符placeholder即可改变样式 - -#import - -// @" " or @"-" -#define placeholder @" " - -@interface BYPhoneNumTF : UITextField - -/** 去掉格式的电话号码 */ -@property (nonatomic, strong) NSString *plainPhoneNum; - -@end -``` - -``` -// -// BYPhoneNumTF.m -// dev-Jack -// -// Created by BY on 16/12/2. -// Copyright © 2016年 Jack. All rights reserved. -// - -#import "BYPhoneNumTF.h" - - -@interface BYPhoneNumTF () - -@end - -@implementation BYPhoneNumTF - -- (NSString *)plainPhoneNum { - return [self _noneSpaseString:self.text]; -} - -- (void)awakeFromNib { - [super awakeFromNib]; - self.delegate = self; -} - - -- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string { - - NSString *phStr = placeholder; - unichar phChar = ' '; - if (phStr.length) { - phChar = [phStr characterAtIndex:0]; - } - - - if (textField) { - NSString* text = textField.text; - //删除 - if([string isEqualToString:@""]){ - - //删除一位 - if(range.length == 1){ - //最后一位,遇到空格则多删除一次 - if (range.location == text.length - 1 ) { - if ([text characterAtIndex:text.length - 1] == phChar) { - [textField deleteBackward]; - } - return YES; - } - //从中间删除 - else{ - NSInteger offset = range.location; - - if (range.location < text.length && [text characterAtIndex:range.location] == phChar && [textField.selectedTextRange isEmpty]) { - [textField deleteBackward]; - offset --; - } - [textField deleteBackward]; - textField.text = [self _parseString:textField.text]; - UITextPosition *newPos = [textField positionFromPosition:textField.beginningOfDocument offset:offset]; - textField.selectedTextRange = [textField textRangeFromPosition:newPos toPosition:newPos]; - return NO; - } - } - else if (range.length > 1) { - BOOL isLast = NO; - //如果是从最后一位开始 - if(range.location + range.length == textField.text.length ){ - isLast = YES; - } - [textField deleteBackward]; - textField.text = [self _parseString:textField.text]; - - NSInteger offset = range.location; - if (range.location == 3 || range.location == 8) { - offset ++; - } - if (isLast) { - //光标直接在最后一位了 - }else{ - UITextPosition *newPos = [textField positionFromPosition:textField.beginningOfDocument offset:offset]; - textField.selectedTextRange = [textField textRangeFromPosition:newPos toPosition:newPos]; - } - - return NO; - } - - else{ - return YES; - } - } - - else if(string.length >0){ - - //限制输入字符个数 - if (([self _noneSpaseString:textField.text].length + string.length - range.length > 11) ) { - return NO; - } - - //判断是否是纯数字(搜狗,百度输入法,数字键盘居然可以输入其他字符) - if(![self _isNum:string]){ - return NO; - } - [textField insertText:string]; - textField.text = [self _parseString:textField.text]; - - NSInteger offset = range.location + string.length; - if (range.location == 3 || range.location == 8) { - offset ++; - } - UITextPosition *newPos = [textField positionFromPosition:textField.beginningOfDocument offset:offset]; - textField.selectedTextRange = [textField textRangeFromPosition:newPos toPosition:newPos]; - return NO; - }else{ - return YES; - } - - } - - return YES; - - -} - -- (NSString*)_parseString:(NSString*)string{ - - if (!string) { - return nil; - } - NSMutableString* mStr = [NSMutableString stringWithString:[string stringByReplacingOccurrencesOfString:placeholder withString:@""]]; - if (mStr.length >3) { - [mStr insertString:placeholder atIndex:3]; - }if (mStr.length > 8) { - [mStr insertString:placeholder atIndex:8]; - - } - - return mStr; - -} - -/** 获取正常电话号码(去掉空格) */ -- (NSString*)_noneSpaseString:(NSString*)string{ - - return [string stringByReplacingOccurrencesOfString:placeholder withString:@""]; - -} - -- (BOOL)_isNum:(NSString *)checkedNumString { - - if (!checkedNumString) { - return NO; - } - - checkedNumString = [checkedNumString stringByTrimmingCharactersInSet:[NSCharacterSet decimalDigitCharacterSet]]; - - if(checkedNumString.length > 0) { - return NO; - } - - return YES; - -} - - -@end - -``` - -# 使用方法 - -在storyboard中的`TextField`控件的Calss类型选择该类`BYPhoneNumTF`即可。 - -代码及Demo下载地址:[BYPhoneNumTF](https://github.com/qiubaiying/BYPhoneNumTF) \ No newline at end of file diff --git "a/_posts/2016-10-26-JSON-\350\275\254\346\250\241\345\236\213-For-YYModel.md" "b/_posts/2016-10-26-JSON-\350\275\254\346\250\241\345\236\213-For-YYModel.md" deleted file mode 100644 index f14e03dd9ac..00000000000 --- "a/_posts/2016-10-26-JSON-\350\275\254\346\250\241\345\236\213-For-YYModel.md" +++ /dev/null @@ -1,78 +0,0 @@ ---- -layout: post -title: JSON转模型 For YYModel -subtitle: 使用 YYModel库 快速完成 JSON 转模型 -date: 2016-10-26 -author: BY -header-img: img/post-bg-ios9-web.jpg -catalog: true -tags: - - iOS - - 开发技巧 ---- - ->JSON转模型是我们做iOS开发的基础技能,本文将通过[YYModel](https://github.com/ibireme/YYModel)这个框架安全快速的完成JSON到模型的转换,其中还会介绍到一款好用的插件[ESJsonFormat](https://github.com/EnjoySR/ESJsonFormat-Xcode)。 - -# 1、首先创建模型类 -创建模型类我们可以通过[ESJsonFormat](https://github.com/EnjoySR/ESJsonFormat-Xcode)这款插件快速完成。 - -使用方法: - -将光标移动到代码行中 如下图的13行 - -然后点击`Window`->`ESJsonFormat`->`Input JSON Window`调出窗口 - - -![](http://ww1.sinaimg.cn/large/006y8lVagw1f95tr49ed7j30no0csdir.jpg) - -在窗口中输入你要解析的JSON文本,如下图: - -![](http://ww4.sinaimg.cn/large/006y8lVagw1f97s13l4b9j30jv0e8dhp.jpg) - -按`Enter`继续,然后神奇的一幕发生了 - -![](http://ww3.sinaimg.cn/large/006y8lVagw1f97s46k95tj30k30dydj9.jpg) - -![](http://ww1.sinaimg.cn/large/006y8lVagw1f97s6yp9hmj30iw0b840m.jpg) - -看到在.h中 所有的属性自动为你填上,而且帮你选好了类型 - -.m 也为你声明了`list`中成员的类型,不过这里需要稍作修改,因为我们需要用到YYModel进行解析,所以方法名改成`modelContainerPropertyGenericClass` - -``` -+ (NSDictionary *)modelContainerPropertyGenericClass { - return @{@"list" : [List class]}; -} - -``` - -还有问题就是属性中出现关键字`id`,我们需要将id改为`teacherId` - -然后在.m的`implementation`中声明,将字典的的`id` - -``` -+ (NSDictionary *)modelCustomPropertyMapper { - return @{@"teacherId" : @"id"}; -} -``` - -这样,模型的创建就完成了,剩下的就是用YYModel进行解析了 - -# 2、使用YYModel进行解析 - -解析很简单,就只需要一句话 - -``` -// 将 JSON (NSData,NSString,NSDictionary) 转换为 Model: -Model *model = [Model yy_modelWithJSON:json]; - -// 或者 -Model *model = [[Model alloc] init]; -[model yy_modelSetWithDictionary:json]; - -``` - -到此,简便快速的完成了JSON到模型的转换。 - - -最后,[这里附上一篇YYModel的使用](http://www.jianshu.com/p/25e678fa43d3) \ No newline at end of file diff --git "a/_posts/2016-11-10-Objective-C Runtime\350\257\246\350\247\243.md" "b/_posts/2016-11-10-Objective-C Runtime\350\257\246\350\247\243.md" deleted file mode 100644 index d68ad366cb9..00000000000 --- "a/_posts/2016-11-10-Objective-C Runtime\350\257\246\350\247\243.md" +++ /dev/null @@ -1,863 +0,0 @@ ---- -layout: post -title: Objective-C Runtime 详解 -subtitle: Runtime 详解 -date: 2017-02-04 -author: BY -header-img: img/post-bg-ios9-web.jpg -catalog: true -tags: - - Obj-C - - Runtime - - iOS ---- - -# 前言 ->最近在学习Runtime的知识,恰巧发现了这篇博客[《Objective-C Runtime》](http://yulingtianxia.com/blog/2014/11/05/objective-c-runtime/),在此基础上,进行了些许补充说明,如有错误或其他想法,欢迎提出交流。 - -## 基础知识 -- 引言 -- 简介 -- 与Runtime交互 -- RunTime术语 -- 消息 -- 动态方法解析 -- 消息转发 -- 健壮的实例变量 -- 动态添加属性(Object-C Associated Objects) -- 方法调剂(Method Swizzling) -- 总结 - -### 引言 - -Objective-C的方法调用实则为“发送消息”,我们来看`[dog eat]`实际会被编译器转化为 - -``` -objc_msgSend(dog, SEL)//SEL为eat方法的标识符@selector(@"eat") -``` - -若方法中函数参数,则为: - -``` -objc_msgSend(dog, SEL, arg1, arg2, ...) -``` - -如果消息的接收者能够找到对应的方法,那么就相当于直接执行了接收者这个对象的特定方法;否则,消息要么被转发,或是临时向接收者动态添加这个方法对应的实现内容,要么就干脆就crash掉。 - -现在可以看出`[dog eat]`真的不是一个简简单单的方法调用。因为这只是在编译阶段确定了要向接收者发送`eat`这条消息,而`dog`将要如何响应这条消息,那就要看运行时发生的情况来决定了。 - -Objective-C 的 Runtime 铸就了它动态语言的特性,这些深层次的知识虽然平时写代码用的少一些,但是却是每个 Objc 程序员需要了解的。 - -### 简介 - -因为Objc是一门动态语言,所以它总是想办法把一些决定工作从编译连接推迟到运行时。也就是说只有编译器是不够的,还需要一个运行时系统 (runtime system) 来执行编译后的代码。这就是 Objective-C Runtime 系统存在的意义,它是整个Objc运行框架的一块基石。 - -Runtime其实有两个版本:“modern”和 “legacy”。我们现在用的 Objective-C 2.0 采用的是现行(Modern)版的Runtime系统,只能运行在 iOS 和 OS X 10.5 之后的64位程序中。而OS X较老的32位程序仍采用 Objective-C 1中的(早期)Legacy 版本的 Runtime 系统。这两个版本最大的区别在于当你更改一个类的实例变量的布局时,在早期版本中你需要重新编译它的子类,而现行版就不需要。 - -Runtime基本是用C和汇编写的,可见苹果为了动态系统的高效而作出的努力。你可以在[这里](https://opensource.apple.com/source/objc4/)下到苹果维护的开源代码。苹果和GNU各自维护一个开源的runtime版本,这两个版本之间都在努力的保持一致。 - -### 与Runtime交互 - -Objc 从`三种`不同的层级上与 Runtime 系统进行交互,分别是通过 `Objective-C 源代码`,通过 Foundation 框架的`NSObject类定义的方法`,通过对 `runtime 函数`的直接调用。 - -#### Objective-C源代码 - -大部分情况下你就只管写你的Objc代码就行,runtime 系统自动在幕后辛勤劳作着。 -还记得引言中举的例子吧,消息的执行会使用到一些编译器为实现动态语言特性而创建的数据结构和函数,Objc中的类、方法和协议等在 runtime 中都由一些数据结构来定义,这些内容在后面会讲到。(比如`objc_msgSend`函数及其参数列表中的`id`和`SEL`都是啥) - -#### NSObject的方法 - -Cocoa 中大多数类都继承于`NSObject`类,也就自然继承了它的方法。最特殊的例外是`NSProxy`,它是个抽象超类,它实现了一些消息转发有关的方法,可以通过继承它来实现一个其他类的替身类或是虚拟出一个不存在的类,说白了就是领导把自己展现给大家风光无限,但是把活儿都交给幕后小弟去干。 - -有的`NSObject`中的方法起到了抽象接口的作用,比如`description`方法需要你重载它并为你定义的类提供描述内容。`NSObject`还有些方法能在运行时获得类的信息,并检查一些特性,比如`class`返回对象的类;`isKindOfClass`:和`isMemberOfClass:`则检查对象是否在指定的类继承体系中;`respondsToSelector:`检查对象能否响应指定的消息;`conformsToProtocol:`检查对象是否实现了指定协议类的方法;`methodForSelector:`则返回指定方法实现的地址。 - -#### Runtime的函数 - -Runtime 系统是一个由一系列函数和数据结构组成,具有公共接口的动态共享库。头文件存放于`/usr/include/objc`目录下。许多函数允许你用纯C代码来重复实现 Objc 中同样的功能。虽然有一些方法构成了`NSObject`类的基础,但是你在写 Objc 代码时一般不会直接用到这些函数的,除非是写一些 Objc 与其他语言的桥接或是底层的debug工作。在[Objective-C Runtime Reference](https://developer.apple.com/reference/objectivec/1657527-objective_c_runtime)中有对 Runtime 函数的详细文档。 - -### Runtime术语 - -还记得引言中的`objc_msgSend:`方法吧,它的真身是这样的 - -``` -id objc_msgSend ( id self, SEL op, ... ); -``` -下面将会逐渐展开介绍一些术语,其实它们都对应着数据结构。 - -#### SEL - -`objc_msgSend`函数第二个参数类型为`SEL`,它是`selector`在Objc中的表示类型(Swift中是`Selector`类)。`selector`是方法选择器,可以理解为区分方法的标识,而这个标识的数据结构是SEL: - -``` -typedef struct objc_selector *SEL; -``` - -本质上,SEL只是一个指向方法的指针(准确的说,只是一个根据方法名hash化了的KEY值,能唯一代表一个方法),它的存在只是为了加快方法的查询速度。这个查找过程我们将在下面讨论。 - -我们可以在运行时添加新的selector,也可以在运行时获取已存在的selector,我们可以通过下面三种方法来获取SEL: - -1. sel_registerName函数 - -2. Objective-C编译器提供的@selector() - -3. NSSelectorFromString()方法 - -#### id - -`objc_msgSend`第一个参数类型为`id`,大家对它都不陌生,它是一个指向类实例的指针: - -``` -typedef struct objc_object *id; -``` - -那`objc_object`又是啥呢: - -``` -struct objc_object { Class isa; }; -``` -`objc_object`结构体包含一个`isa`指针,根据`isa`指针就可以顺藤摸瓜找到对象所属的类。 - -PS:`isa`指针不总是指向实例对象所属的类,不能依靠它来确定类型,而是应该用`class`方法来确定实例对象的类。因为KVO的实现机理就是将被观察对象的isa指针指向一个中间类而不是真实的类,这是一种叫做 **isa-swizzling** 的技术,详见[官方文档](https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/KeyValueObserving/Articles/KVOImplementation.html#//apple_ref/doc/uid/20002307-BAJEAIEE)的这句段说明 - ->Key-Value Observing Implementation Details - ->Automatic key-value observing is implemented using a technique called isa-swizzling. - ->The isa pointer, as the name suggests, points to the object's class which maintains a dispatch table. This dispatch table essentially contains pointers to the methods the class implements, among other data. - ->When an observer is registered for an attribute of an object the isa pointer of the observed object is modified, pointing to an intermediate class rather than at the true class. As a result the value of the isa pointer does not necessarily reflect the actual class of the instance. - ->You should never rely on the isa pointer to determine class membership. Instead, you should use the class method to determine the class of an object instance. - -#### Class - -之所以说`isa`是指针是因为`Class`其实是一个指向`objc_class`结构体的指针: - -``` -typedef struct objc_class *Class; -``` -而`objc_class`就是我们摸到的那个瓜,里面的东西多着呢: - -``` -struct objc_class { - Class isa OBJC_ISA_AVAILABILITY; - -#if !__OBJC2__ - Class super_class OBJC2_UNAVAILABLE; - const char *name OBJC2_UNAVAILABLE; - long version OBJC2_UNAVAILABLE; - long info OBJC2_UNAVAILABLE; - long instance_size OBJC2_UNAVAILABLE; - struct objc_ivar_list *ivars OBJC2_UNAVAILABLE; - struct objc_method_list **methodLists OBJC2_UNAVAILABLE; - struct objc_cache *cache OBJC2_UNAVAILABLE; - struct objc_protocol_list *protocols OBJC2_UNAVAILABLE; -#endif - -} OBJC2_UNAVAILABLE; -``` -可以看到运行时一个类还关联了它的超类指针,类名,成员变量,方法,缓存,还有附属的协议 - -PS:`OBJC2_UNAVAILABLE`之类的宏定义是苹果在 Objc 中对系统运行版本进行约束的黑魔法,为的是兼容非Objective-C 2.0的遗留逻辑,但我们仍能从中获得一些有价值的信息,有兴趣的可以查看源代码 - -`Objective-C 2.0` 的头文件虽然没暴露出`objc_class`结构体更详细的设计,我们依然可以从`Objective-C 1.0` 的定义中小窥端倪 - -在`objc_class`结构体中:`ivars`是`objc_ivar_list`指针;`methodLists`是指向`objc_method_list`指针的指针。也就是说可以动态修改`*methodLists`的值来添加成员方法,这也是Category实现的原理,同样解释了Category不能添加属性的原因。而最新版的 Runtime 源码对这一块的描述已经有很大变化,可以参考下美团技术团队的[深入理解Objective-C:Category](http://tech.meituan.com/DiveIntoCategory.html). - -PS:任性的话可以在Category中添加`@dynamic`的属性,并利用运行期动态提供存取方法或干脆动态转发;或者干脆使用关联度对象(AssociatedObject) - -其中`objc_ivar_list`和`objc_method_list`分别是成员变量列表和方法列表: - -``` -struct objc_ivar_list { - int ivar_count OBJC2_UNAVAILABLE; -#ifdef __LP64__ - int space OBJC2_UNAVAILABLE; -#endif - /* variable length structure */ - struct objc_ivar ivar_list[1] OBJC2_UNAVAILABLE; -} OBJC2_UNAVAILABLE; - -struct objc_method_list { - struct objc_method_list *obsolete OBJC2_UNAVAILABLE; - - int method_count OBJC2_UNAVAILABLE; -#ifdef __LP64__ - int space OBJC2_UNAVAILABLE; -#endif - /* variable length structure */ - struct objc_method method_list[1] OBJC2_UNAVAILABLE; -} -``` -如果你C语言不是特别好,可以理解为`objc_ivar_list`结构体存储着`objc_ivar`数组列表,而`objc_ivar`结构体存储了类的单个成员变量的信息;同理`objc_method_list`结构体存储着`objc_method`数组列表,而o`bjc_method`结构体存储了类的某个方法的信息。 - -最后要提到的还有一个`objc_cache`,顾名思义它是缓存,它在`objc_class`的作用很重要,在后面会讲到。 - -不知道你是否注意到了`objc_class`中也有一个`isa`对象,这是因为一个 ObjC 类本身同时也是一个对象,为了处理类和对象的关系,runtime 库创建了一种叫做元类 (Meta Class) 的东西,类对象所属类型就叫做元类,它用来表述类对象本身所具备的元数据。类方法就定义于此处,因为这些方法可以理解成类对象的实例方法。每个类仅有一个类对象,而每个类对象仅有一个与之相关的元类。当你发出一个类似`[NSObject alloc]`的消息时,你事实上是把这个消息发给了一个类对象 (Class Object) ,这个类对象必须是一个元类的实例,而这个元类同时也是一个根元类 (root meta class) 的实例。所有的元类最终都指向根元类为其超类。所有的元类的方法列表都有能够响应消息的类方法。所以当 `[NSObject alloc]` 这条消息发给类对象的时候,`objc_msgSend()`会去它的元类里面去查找能够响应消息的方法,如果找到了,然后对这个类对象执行方法调用。 - -![](http://7ni3rk.com1.z0.glb.clouddn.com/Runtime/class-diagram.jpg) - -上图实线是 `super_class` 指针,虚线是`isa`指针。 有趣的是根元类的超类是`NSObjec`t,而`isa`指向了自己,而`NSObject`的超类为`nil`,也就是它没有超类 - -####Method - -`Method`是一种代表类中的某个方法的类型。 - -``` -typedef struct objc_method *Method; -``` -而`objc_method`在上面的方法列表中提到过,它存储了方法名,方法类型和方法实现: - -``` -struct objc_method { - SEL method_name OBJC2_UNAVAILABLE; - char *method_types OBJC2_UNAVAILABLE; - IMP method_imp OBJC2_UNAVAILABLE; -} OBJC2_UNAVAILABLE; - -``` -- 方法名 `method_name` 类型为 `SEL`, 相同名字的方法即使在不同类中定义,它们的方法选择器也相同。 -- 方法类型`method_types`是个`char`指针,存储着方法的 参数类型 和 返回值 类型。 -- `method_imp`指向了方法的实现,本质上是一个函数指针,后面会详细讲到。 - -#### Ivar - -Ivar是一种代表类中实例变量的类型。定义如下: - -``` -typedef struct objc_ivar *Ivar; -``` -它是一个指向objc_ivar结构体的指针,结构体有如下定义: - -``` -struct objc_ivar { - char *ivar_name OBJC2_UNAVAILABLE; - char *ivar_type OBJC2_UNAVAILABLE; - int ivar_offset OBJC2_UNAVAILABLE; -#ifdef __LP64__ - int space OBJC2_UNAVAILABLE; -#endif -} OBJC2_UNAVAILABLE; -``` -这里我们注意第三个成员 `ivar_offset`。它表示基地址偏移字节。 - -在编译我们的类时,编译器生成了一个 `ivar` 布局,显示了在类中从哪可以访问我们的 `ivars` 。 - -我们对 ivar 的访问就可以通过 `对象地址` + `ivar偏移字节`的方法。 - -但是当我们增加了父类的`ivar`,这个时候布局就出错了,我们就不得不重新编译子类来恢复兼容性。 - -而Objective-C Runtime中使用了`Non Fragile ivars`来避免这个问题 - -使用`Non Fragile ivars`时,Runtime会进行检测来调整类中新增的`ivar`的偏移量。 这样我们就可以通过 `对象地址 + 基类大小 + ivar偏移字节`的方法来计算出`ivar`相应的地址,并访问到相应的`ivar`。 - -可以根据实例查找其在类中的名字,也就是“反射”: - -``` --(NSString *)nameWithInstance:(id)instance { - unsigned int numIvars = 0; - NSString *key=nil; - Ivar * ivars = class_copyIvarList([self class], &numIvars); - for(int i = 0; i < numIvars; i++) { - Ivar thisIvar = ivars[i]; - const char *type = ivar_getTypeEncoding(thisIvar); - NSString *stringType = [NSString stringWithCString:type encoding:NSUTF8StringEncoding]; - if (![stringType hasPrefix:@"@"]) { - continue; - } - if ((object_getIvar(self, thisIvar) == instance)) {//此处若 crash 不要慌! - key = [NSString stringWithUTF8String:ivar_getName(thisIvar)]; - break; - } - } - free(ivars); - return key; -} -``` -`class_copyIvarList` 函数获取的不仅有实例变量,还有属性。但会在原本的属性名前加上一个下划线。(属性的本质就是 `_属性名+set+get方法`) - -#### IMP - -`IMP`在`objc.h`中的定义是: - -``` -typedef id (*IMP)(id, SEL, ...); -``` -它就是一个[函数指针](http://yulingtianxia.com/blog/2014/04/17/han-shu-zhi-zhen-yu-zhi-zhen-han-shu/),这是由编译器生成的。当你发起一个 ObjC 消息之后,最终它会执行的那段代码,就是由这个函数指针指定的。而 `IMP` 这个函数指针就指向了这个方法的实现。既然得到了执行某个实例某个方法的入口,我们就可以绕开消息传递阶段,直接执行方法,这在后面会提到。 - -我们再来看看objc_msgSend()的定义:` id objc_msgSend(id self, SEL op, ...)` - -你会发现`IMP`指向的方法与`objc_msgSend`函数类型相同,参数都包含id和SEL类型。每个方法名都对应一个SEL类型的方法选择器,而每个实例对象中的`SEL`对应的方法实现肯定是唯一的,通过一组`id`和`SEL`参数就能确定唯一的方法实现地址。 - -#### Cache - -在`runtime.h`中Cache的定义如下: - -``` -typedef struct objc_cache *Cache -``` -还记得之前 `objc_class` 结构体中有一个 `struct objc_cache *cache` 吧,它到底是缓存啥的呢,先看看 `objc_cache` 的实现: - -``` -struct objc_cache { - unsigned int mask /* total = mask + 1 */ OBJC2_UNAVAILABLE; - unsigned int occupied OBJC2_UNAVAILABLE; - Method buckets[1] OBJC2_UNAVAILABLE; -}; -``` -`objc_cache` 的定义看起来很简单,它包含了下面三个变量: - -- `mask`:可以认为是当前能达到的最大index(从0开始的),所以缓存的size(total)是mask+1 -- `occupied`:被占用的槽位,因为缓存是以散列表的形式存在的,所以会有空槽,而occupied表示当前被占用的数目 -- `buckets`:用数组表示的hash表,cache_entry类型,每一个cache_entry代表一个方法缓存 - -(buckets定义在objc_cache的最后,说明这是一个可变长度的数组) - -`Cache`为方法调用的性能进行优化,下面我们来看看`objc_msgSend`具体又是如何分发的呢? 我们来看下runtime层`objc_msgSend`的源码。 - -在`objc-msg-arm.s`中,`objc_msgSend`的代码如下: - -ps:Apple为了高度优化objc_msgSend的性能,这个文件是汇编写成的,不过即使我们不懂汇编,详尽的注释也可以让我们一窥其真面目 - -``` -ENTRY objc_msgSend -# check whether receiver is nil -teq a1, #0 - beq LMsgSendNilReceiver -# save registers and load receiver's class for CacheLookup -stmfd sp!, {a4,v1} -ldr v1, [a1, #ISA] -# receiver is non-nil: search the cache -CacheLookup a2, v1, LMsgSendCacheMiss -# cache hit (imp in ip) and CacheLookup returns with nonstret (eq) set, restore registers and call -ldmfd sp!, {a4,v1} -bx ip -# cache miss: go search the method lists -LMsgSendCacheMiss: -ldmfd sp!, {a4,v1} -b _objc_msgSend_uncached -LMsgSendNilReceiver: - mov a2, #0 - bx lr -LMsgSendExit: -END_ENTRY objc_msgSend -STATIC_ENTRY objc_msgSend_uncached -# Push stack frame -stmfd sp!, {a1-a4,r7,lr} -add r7, sp, #16 -# Load class and selector -ldr a3, [a1, #ISA] /* class = receiver->isa */ -/* selector already in a2 */ -/* receiver already in a1 */ -# Do the lookup -MI_CALL_EXTERNAL(__class_lookupMethodAndLoadCache3) -MOVE ip, a1 -# Prep for forwarding, Pop stack frame and call imp -teq v1, v1 /* set nonstret (eq) */ -ldmfd sp!, {a1-a4,r7,lr} -bx ip -``` - -如果向更深入了解 `objc_cache` ,可以看看这篇博文[深入理解Objective-C:方法缓存](http://www.cocoachina.com/ios/20150818/13075.html) - -从上述代码中可以看到,`objc_msgSend`(就ARM平台而言)的消息分发分为以下几个步骤: - -1. 判断receiver是否为nil,也就是objc_msgSend的第一个参数self,也就是要调用的那个方法所属对象 -2. 从缓存里寻找,找到了则分发,否则 -3. 利用objc-class.mm中_class_lookupMethodAndLoadCache3(为什么有个这么奇怪的方法。本文末尾会解释)方法去寻找selector -4. 如果支持GC,忽略掉非GC环境的方法(retain等) -5. 从本class的method list寻找selector,如果找到,填充到缓存中,并返回selector,否则 -6. 寻找父类的method list,并依次往上寻找,直到找到selector,填充到缓存中,并返回selector,否则 -7. 调用_class_resolveMethod,如果可以动态resolve为一个selector,不缓存,方法返回,否则 -8. 转发这个selector,否则 -9. 报错,抛出异常 - -从上面的分析中我们可以看到,当一个方法在比较“上层”的类中,用比较“下层”(继承关系上的上下层)对象去调用的时候,如果没有缓存,那么整个查找链是相当长的。就算方法是在这个类里面,当方法比较多的时候,每次都查找也是费事费力的一件事情。 - -当我们需要去调用一个方法数十万次甚至更多地时候,查找方法的消耗会变的非常显著。就算我们平常的非大规模调用,`除非一个方法只会调用一次,否则缓存都是有用的。`在运行时,那么多对象,那么多方法调用,节省下来的时间也是非常可观的。可见缓存的重要性。 - -方法缓存存在什么地方? - -让我们再去去翻看 `objc_class` 的定义, - -``` -struct objc_class { - Class isa OBJC_ISA_AVAILABILITY; - -#if !__OBJC2__ - Class super_class OBJC2_UNAVAILABLE; - const char *name OBJC2_UNAVAILABLE; - long version OBJC2_UNAVAILABLE; - long info OBJC2_UNAVAILABLE; - long instance_size OBJC2_UNAVAILABLE; - struct objc_ivar_list *ivars OBJC2_UNAVAILABLE; - struct objc_method_list **methodLists OBJC2_UNAVAILABLE; - struct objc_cache *cache OBJC2_UNAVAILABLE; - struct objc_protocol_list *protocols OBJC2_UNAVAILABLE; -#endif - -} OBJC2_UNAVAILABLE; -``` - -我们看到在类的定义里就有`cache`字段,没错,类的所有缓存都存在metaclass上,所以每个类都只有一份方法缓存,而不是每一个类的object都保存一份。 - -子类类即便是从父类取到的方法,也会存在类本身的方法缓存里。而当用一个父类对象去调用那个方法的时候,也会在父类的metaclass里缓存一份。 - -#### Property - -`@property`标记了类中的属性,这个不必多说大家都很熟悉,它是一个指向objc_property结构体的指针: - -``` -typedef struct objc_property *Property; -typedef struct objc_property *objc_property_t;//这个更常用 -``` - -现在在类中声明声明属性和成员变量: - -``` -@interface ViewController () -{ - int age; - NSString *name; -} -@property (nonatomic, strong) NSString *property1; -@property (nonatomic, strong) NSString *property2; -@property (nonatomic, assign) int age;//这里的age为属性,对应变量:_age -@property (nonatomic, assign) long ID; - -@end -``` - -然后用下面的方法来获取类中属性列表: - -``` -id LenderClass = objc_getClass("ViewController");//获取calss -//id LenderClass = [MyViewController class];//同上 -unsigned int outCount;//属性数量 -objc_property_t *properties = class_copyPropertyList(LenderClass, &outCount);//获取属性列表 -for (int i = 0; i < outCount; i++) {// 遍历 - objc_property_t property = properties[i]; - const char *propertyName = property_getName(property); - const char *propertyAttributes = property_getAttributes(property); - printf("propertyName:%s \n", propertyName); - printf("propertyAttributes:%s\n--------\n", propertyAttributes);//属性名及描述 -} -``` - -控制台输出: - -``` -propertyName:property1 -propertyAttributes:T@"NSString",&,N,V_property1 --------- -propertyName:property2 -propertyAttributes:T@"NSString",&,N,V_property2 --------- -propertyName:age -propertyAttributes:Ti,N,V_age --------- -propertyName:ID -propertyAttributes:Tq,N,V_ID -``` - - -我们再来来看看获取成员变量的方法: - -``` -id selfClass = [self class]; -unsigned int numIvars = 0; -Ivar *ivars = class_copyIvarList(selfClass, &numIvars); -for(int i = 0; i < numIvars; i++) { - Ivar ivar = ivars[i]; - const char *ivarType = ivar_getTypeEncoding(ivar);// 获取类型 - const char *ivarName = ivar_getName(ivar); - printf("ivarName:%s\n", ivarName); - printf("ivarType:%s\n------\n", ivarType); -} -``` -控制台输出: - -``` -ivarName:age -ivarType:i ------- -ivarName:name -ivarType:@"NSString" ------- -ivarName:_age -ivarType:i ------- -ivarName:_property1 -ivarType:@"NSString" ------- -ivarName:_property2 -ivarType:@"NSString" ------- -ivarName:_ID -ivarType:q -``` - -我们会发现与 `class_copyIvarList` 函数不同,使用 `class_copyPropertyList` 函数只能获取类的属性,而不包含成员变量。但此时获取的属性名是不带下划线的,得到属性或者变量名后我们就可以使用KVC去修改访问类中的私有属性或变量。所以OC中没有真正意义上的私有变量,私有方法也是。 - -### 消息 -前面做了这么多铺垫,现在终于说到了消息了。Objc 中发送消息是用中括号 `[]` 把接收者和消息括起来,而直到运行时才会把消息与方法实现绑定。 - -有关消息发送和消息转发机制的原理,可以查看[这篇文章](http://yulingtianxia.com/blog/2016/06/15/Objective-C-Message-Sending-and-Forwarding/)。 - -#### objc_msgSend函数 - -在引言中已经对 `objc_msgSend` 进行了一点介绍,看起来像是 `objc_msgSend` 返回了数据,其实 `objc_msgSend` 从不返回数据而是你的方法被调用后返回了数据。下面详细叙述下消息发送步骤: - -1. 检测这个 消息 是不是要忽略的。比如 Mac OS X 开发,在ARC中有了垃圾回收就不理会MRC的 `retain`, `release` 这些函数了。 -2. 检测这个 目标对象 是不是 `nil` 对象。ObjC 的特性是允许对一个 `nil` 对象执行任何一个方法不会 Crash,因为会被忽略掉。 -3. 如果上面两个都过了,那就开始查找这个类的 `IMP`,先从 `cache` 里面找,完了找得到就跳到对应的函数去执行。 -4. 如果 `cache` 找不到就找一下方法分发表。 -5. 如果分发表找不到就到超类的分发表去找,一直找,直到找到NSObject类为止。 -6. 如果还找不到就要开始进入动态方法解析了,后面会提到。 - -PS:这里说的分发表其实就是 `Class` 中的方法列表,它将方法选择器和方法实现地址联系起来。 -![](https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Art/messaging1.gif) - -其实编译器会根据情况在`objc_msgSend`, `objc_msgSend_stret`, `objc_msgSendSuper`, 或 `objc_msgSendSuper_stret`四个方法中选择一个来调用。如果消息是传递给超类,那么会调用名字带有”Super”的函数;如果消息返回值是数据结构而不是简单值时,那么会调用名字带有`”stret”`的函数。排列组合正好四个方法 - -PS:有木有发现这些函数的命名规律哦?带 `“Super”` 的是消息传递给超类;`“stret”`可分为`“st”`+`“ret”`两部分,分别代表 `“struct”` 和 `“return”` ;`“fpret”`就是 `“fp”` + `“ret”`,分别代表`“floating-point”`和 `“return”`。 - -#### 方法中的隐藏参数 -我们经常在方法中使用 `self` 关键字来引用实例本身,但从没有想过为什么 `self` 就能取到调用当前方法的对象吧。其实 `self` 的内容是在方法运行时被偷偷的动态传入的 - -当 `objc_msgSend` 找到方法对应的实现时,它将直接调用该方法实现,并将消息中所有的参数都传递给方法实现,同时,它还将传递两个隐藏的参数: - -- 接收消息的对象(也就是`self`指向的内容) -- 方法选择器(`_cmd`指向的内容) - -之所以说它们是隐藏的是因为在源代码方法的定义中并没有声明这两个参数。它们是在代码被编译时被插入实现中的。尽管这些参数没有被明确声明,在源代码中我们仍然可以引用它们。在下面的例子中,`self`引用了接收者对象,而`_cmd`引用了方法本身的选择器: - -``` -- strange -{ - id target = getTheReceiver(); - SEL method = getTheMethod(); - - if ( target == self || method == _cmd ) - return nil; - return [target performSelector:method]; -} -``` -在这两个参数中,`self` 更有用。实际上,它是在方法实现中访问消息接收者对象的实例变量的途径 - -而当方法中的 `super` 关键字接收到消息时,编译器会创建一个 `objc_super` 结构体: - -``` -struct objc_super { id receiver; Class class; }; -``` -这个结构体指明了消息应该被传递给特定父类的定义。但`receiver`仍然是`self`本身,这点需要注意,因为当我们想通过`[super class]`获取超类时,编译器只是将指向`self`的`id`指针和`class`的`SEL`传递给了o`bjc_msgSendSuper`函数,因为只有在`NSObject`类才能找到`class`方法,然后`class`方法调用`object_getClass()`,接着调用`objc_msgSend(objc_super->receiver`, `@selector(class))`,传入的第一个参数是指向`self`的`id`指针,与调用`[self class]`相同,所以我们得到的永远都是`self`的类型。 - -#### 获取方法地址 - -在 `IMP` 那节提到过可以避开消息绑定而直接获取方法的地址并调用方法。这种做法很少用,除非是需要持续大量重复调用某方法的极端情况,避开消息发送泛滥而直接调用该方法会更高效。 -NSObject类中有个`methodForSelector:`实例方法,你可以用它来获取某个方法选择器对应的 `IMP` ,举个栗子: - -``` -void (*imp)(id, SEL, BOOL);//定义一个函数指针 -imp = (void (*)(id, SEL, BOOL))[self methodForSelector:@selector(setFilled:)];//获取setFilled:函数的IMP -``` - -### 动态方法解析 - -你可以动态地提供一个方法的实现。例如我们可以用 `@dynamic` 关键字在类的实现文件中修饰一个属性: - -``` -@dynamic propertyName; -``` -这表明我们会为这个属性提供存取方法,也就是说编译器不会默认为我们生成 `setPropertyName:`和 `prepertyName` 方法,而需要我们自己提供动态方法。我们可以通过分别重载 `resolveIntanceMethod:` 和 `resolvrClassMethod:` 方法分别添加实例方法实现和类方法实现。因为当 Runtime 系统在 `Cache` 和方法分发表中(包括父类)找不到要执行的方法时,Runtime会调用 `resolveIntanceMethod:` 和 `resolvrClassMethod:` 来给我们一次动态添加实现的机会。我们需要 `class_addMethod`函数完成向特定类添加特定方法实现的操作: - -``` -void dynamicMethodIMP(id self, SEL _cmd) { - // implementation .... -} -@implementation MyClass -+ (BOOL)resolveInstanceMethod:(SEL)aSEL -{ - if (aSEL == @selector(resolveThisMethodDynamically)) { - class_addMethod([self class], aSEL, (IMP) dynamicMethodIMP, "v@:"); - return YES; - } - return [super resolveInstanceMethod:aSEL]; -} -@end - -``` -上面的例子为resolveThisMethodDynamically方法添加了实现内容,也就是dynamicMethodIMP方法中的代码。其中 “v@:” 表示返回值和参数,这个符号涉及 [Type Encoding](https://developer.apple.com/library/mac/DOCUMENTATION/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtTypeEncodings.html) - -PS:动态方法解析会在消息转发机制浸入前执行。如果 `respondsToSelector:` 或 `instancesRespondToSelector:` 方法被执行,动态方法解析器将会被首先给予一个提供该方法选择器对应的 `IMP` 的机会。如果你想让该方法选择器被传送到转发机制,那么就让`resolveInstanceMethod:` 返回 `NO` 。 - -### 消息转发 -#### 重定向 -在消息转发机制执行前,Runtime 系统会再给我们一次偷梁换柱的机会,即通过重载`- (id)forwardingTargetForSelector:(SEL)aSelector` 方法替换消息的接受者为其他对象: - -``` -- (id)forwardingTargetForSelector:(SEL)aSelector -{ - if(aSelector == @selector(mysteriousMethod:)){ - return alternateObject; - } - return [super forwardingTargetForSelector:aSelector]; -} -``` - -毕竟消息转发要耗费更多时间,抓住这次机会将消息重定向给别人是个不错的选择,不过千万别返回`self`,因为那样会死循环 - -#### 转发 - -当动态方法解析不作处理返回NO时,消息转发机制会被触发。在这时`forwardInvocation:`方法会被执行,我们可以重写这个方法来定义我们的转发逻辑: - -``` -- (void)forwardInvocation:(NSInvocation *)anInvocation -{ - if ([someOtherObject respondsToSelector: - [anInvocation selector]]) - [anInvocation invokeWithTarget:someOtherObject]; - else - [super forwardInvocation:anInvocation]; -} -``` - -该消息的唯一参数是个`NSInvocation`类型的对象——该对象封装了原始的消息和消息的参数。我们可以实现`forwardInvocation:`方法来对不能处理的消息做一些默认的处理,也可以将消息转发给其他对象来处理,而不抛出错误。 - -当一个对象由于没有相应的方法实现而无法响应某消息时,运行时系统将通过 `forwardInvocation:` 消息通知该对象。每个对象都从NSObject类中继承了 `forwardInvocation:` 方法。然而,NSObject中的方法实现只是简单地调用了 `doesNotRecognizeSelector:` 。通过实现我们自己的 `forwardInvocation:` 方法,我们可以在该方法实现中将消息转发给其它对象。 - -`forwardInvocation:` 方法就像一个不能识别的消息的分发中心,将这些消息转发给不同接收对象。或者它也可以象一个运输站将所有的消息都发送给同一个接收对象。它可以将一个消息翻译成另外一个消息,或者简单的”吃掉“某些消息,因此没有响应也没有错误。`forwardInvocation:`方法也可以对不同的消息提供同样的响应,这一切都取决于方法的具体实现。该方法所提供是将不同的对象链接到消息链的能力。 - -注意: `forwardInvocation:` 方法只有在消息接收对象中无法正常响应消息时才会被调用。 所以,如果我们希望一个对象将negotiate消息转发给其它对象,则这个对象不能有`negotiate`方法。否则,`forwardInvocation:`将不可能会被调用。 - -#### 转发和多继承 -转发和继承相似,可以用于为Objc编程添加一些多继承的效果。就像下图那样,一个对象把消息转发出去,就好似它把另一个对象中的方法借过来或是“继承”过来一样。 - -![](https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Art/forwarding.gif) - -这使得不同继承体系分支下的两个类可以“继承”对方的方法,在上图中 `Warrior` 和 `Diplomat` 没有继承关系,但是 `Warrior` 将`negotiate` 消息转发给了 `Diplomat` 后,就好似 `Diplomat` 是 `Warrior` 的超类一样。 -消息转发弥补了 Objc 不支持多继承的性质,也避免了因为多继承导致单个类变得臃肿复杂。它将问题分解得很细,只针对想要借鉴的方法才转发,而且转发机制是透明的 - -#### 替代者对象(Surrogate Objects) -转发不仅能模拟多继承,也能使轻量级对象代表重量级对象。弱小的女人背后是强大的男人,毕竟女人遇到难题都把它们转发给男人来做了。这里有一些适用案例,可以参看[官方文档](https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtForwarding.html#//apple_ref/doc/uid/TP40008048-CH105-SW11)。 - -#### 转发于继承 -尽管转发很像继承,但是NSObject类不会将两者混淆。像 `respondsToSelector:` 和 `isKindOfClass:` 这类方法只会考虑继承体系,不会考虑转发链。比如上图中一个 `Warrior` 对象如果被问到是否能响应 `negotiate` 消息: - -``` -if ( [aWarrior respondsToSelector:@selector(negotiate)] ) - ... -``` -结果是 `NO` ,尽管它能够接受 `negotiate` 消息而不报错,因为它靠转发消息给 `Diplomat` 类来响应消息。 - -如果你为了某些意图偏要“弄虚作假”让别人以为`Warrior` 继承到了 `Diplomat` 的 `negotiate` 方法,你得重新实现 `respondsToSelector: ` 和 `isKindOfClass:` 来加入你的转发算法: - -``` -- (BOOL)respondsToSelector:(SEL)aSelector -{ - if ( [super respondsToSelector:aSelector] ) - return YES; - else { - /* Here, test whether the aSelector message can * - * be forwarded to another object and whether that * - * object can respond to it. Return YES if it can. */ - } - return NO; -} -``` - -除了`respondsToSelector: `和 `isKindOfClass:`之外,`instancesRespondToSelector:`中也应该写一份转发算法。如果使用了协议,`conformsToProtocol:`同样也要加入到这一行列中。类似地,如果一个对象转发它接受的任何远程消息,它得给出一个`methodSignatureForSelector:`来返回准确的方法描述,这个方法会最终响应被转发的消息。比如一个对象能给它的替代者对象转发消息,它需要像下面这样实现`methodSignatureForSelector:`: - -``` -- (NSMethodSignature*)methodSignatureForSelector:(SEL)selector -{ - NSMethodSignature* signature = [super methodSignatureForSelector:selector]; - if (!signature) { - signature = [surrogate methodSignatureForSelector:selector]; - } - return signature; -} -``` - -### 健壮的实例变量(Non Fragile ivars) - -在 Runtime 的现行版本中,最大的特点就是健壮的实例变量。当一个类被编译时,实例变量的布局也就形成了,它表明访问类的实例变量的位置。从对象头部地址开始,实例变量依次根据自己所占空间而产生位移: - -再翻出Ivar的定义: - -``` -struct objc_ivar { - char *ivar_name OBJC2_UNAVAILABLE; - char *ivar_type OBJC2_UNAVAILABLE; - int ivar_offset OBJC2_UNAVAILABLE; -#ifdef __LP64__ - int space OBJC2_UNAVAILABLE; -#endif -} OBJC2_UNAVAILABLE; -``` - -`ivar` 的访问可以通过 `对象地址` + `ivar偏移字节(ivar_offset)`的方法。 - -当我们增加了父类的`ivar`,这个时候布局就出错了,我们就不得不重新编译子类来恢复兼容性。 - -在健壮的实例变量下编译器生成的实例变量布局跟以前一样,但是当 runtime 系统检测到与超类有部分重叠时它会调整你新添加的实例变量的位移,那样你在子类中新添加的成员就被保护起来了 - -需要注意的是在健壮的实例变量下,不要使用 `sizeof(SomeClass)`,而是用 `class_getInstanceSize([SomeClass class])` 代替;也不要使用 `offsetof(SomeClass, SomeIvar)` ,而要用 `ivar_getOffset(class_getInstanceVariable([SomeClass class], "SomeIvar"))` 来代替。 - -``` -/* 定义一个Student类 */ -@interface Student : NSObject -{ -@private - int age; -} -@end - -@implementation Student -// 重写%@输出方法 -- (NSString *)description -{ - NSLog(@"current pointer = %p", self); - NSLog(@"age pointer = %p", &age); - return [NSString stringWithFormat:@"age = %d", age]; -} - -@end - -int main(int argc, const char * argv[]) { - @autoreleasepool { - // insert code here... - - Student *student = [[Student alloc] init]; - Ivar age_ivar = class_getInstanceVariable(object_getClass(student), "age");//获取"age"的ivar - int *age_pointer = (int *)((__bridge void *)(student) + ivar_getOffset(age_ivar));//定义一个指向age_ivar的指针:指向地址为 student对象地址 + age_ivar的偏移量(ivar_offset) - NSLog(@"age ivar offset = %td", ivar_getOffset(age_ivar));//输出offset偏移量 - *age_pointer = 10;//对指针age_pointer指向的变量(age_ivar)赋值 - NSLog(@"%@", student);//输出重写的description方法 - - } - return 0; -} -``` -观察控制台输出: - -``` -2016-11-11 16:22:56.364 Iavr_offset[1501:928608] age ivar offset = 8 -2016-11-11 16:22:56.365 Iavr_offset[1501:928608] current pointer = 0x100400170 -2016-11-11 16:22:56.365 Iavr_offset[1501:928608] age pointer = 0x100400178 -2016-11-11 16:22:56.366 Iavr_offset[1501:928608] age = 10 -``` - -我们发现`age pointer = current pointer + age ivar offset` - -### Objective-C Associated Objects - -在 OS X 10.6 之后,Runtime系统让Objc支持向对象动态添加变量。涉及到的函数有以下三个: - -``` - void objc_setAssociatedObject ( id object, const void *key, id value, objc_AssociationPolicy policy ); - id objc_getAssociatedObject ( id object, const void *key ); - void objc_removeAssociatedObjects ( id object ); -``` -这些方法以键值对的形式动态地向对象添加、获取或删除关联值。其中关联政策是一组枚举常量: - -``` -enum { - OBJC_ASSOCIATION_ASSIGN = 0, - OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1, - OBJC_ASSOCIATION_COPY_NONATOMIC = 3, - OBJC_ASSOCIATION_RETAIN = 01401, - OBJC_ASSOCIATION_COPY = 01403 -}; -``` -这些常量对应着引用关联值的政策,也就是 Objc 内存管理的引用计数机制。 - -### Method Swizzling - -之前所说的消息转发虽然功能强大,但需要我们了解并且能更改对应类的源代码,因为我们需要实现自己的转发逻辑。当我们无法触碰到某个类的源代码,却想更改这个类某个方法的实现时,该怎么办呢?可能继承类并重写方法是一种想法,但是有时无法达到目的。这里介绍的是 Method Swizzling ,它通过重新映射方法对应的实现来达到“偷天换日”的目的。跟消息转发相比,Method Swizzling 的做法更为隐蔽,甚至有些冒险,也增大了debug的难度。 - -这里摘抄一个 NSHipster 的例子 - -``` -#import - -@implementation UIViewController (Tracking) - -+ (void)load { - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - Class class = [self class]; - // When swizzling a class method, use the following: - // Class class = object_getClass((id)self); - SEL originalSelector = @selector(viewWillAppear:); - SEL swizzledSelector = @selector(xxx_viewWillAppear:); - Method originalMethod = class_getInstanceMethod(class, originalSelector); - Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector); - BOOL didAddMethod = - class_addMethod(class, - originalSelector, - method_getImplementation(swizzledMethod), - method_getTypeEncoding(swizzledMethod)); - if (didAddMethod) { - class_replaceMethod(class, - swizzledSelector, - method_getImplementation(originalMethod), - method_getTypeEncoding(originalMethod)); - } else { - method_exchangeImplementations(originalMethod, swizzledMethod); - } - }); -} -#pragma mark - Method Swizzling -- (void)xxx_viewWillAppear:(BOOL)animated { - [self xxx_viewWillAppear:animated]; - NSLog(@"viewWillAppear: %@", self); -} -@end -``` -上面的代码通过添加一个 `Tracking` 类别到 `UIViewController` 类中,将 `UIViewController` 类的 `viewWillAppear:` 方法和 `Tracking` 类别中 `xxx_viewWillAppear:` 方法的实现相互调换。`Swizzling` 应该在 `+load` 方法中实现,因为 `+load` 是在一个类最开始加载时调用。`dispatch_once` 是GCD中的一次性方法,它保证了代码块只执行一次,并让其为一个原子操作,线程安全是很重要的。 - -先用 `class_addMethod` 和 `class_replaceMethod` 函数将两个方法的实现进行调换,如果类中已经有了 `viewWillAppear:` 方法的实现,那么就调用 `method_exchangeImplementations` 函数交换了两个方法的 `IMP` ,这是苹果提供给我们用于实现 `Method Swizzling` 的便捷方法。 -最后 `xxx_viewWillAppear:` 方法的定义看似是递归调用引发死循环,其实不会的。因为 `[self xxx_viewWillAppear:animated]` 消息会动态找到 `xxx_viewWillAppear:` 方法的实现,而它的实现已经被我们与 `viewWillAppear:`方法实现进行了互换,所以这段代码不仅不会死循环,如果你把 `[self xxx_viewWillAppear:animated]` 换成 `[self viewWillAppear:animated]` 反而会引发死循环。 -看到有人说 `+load`方法本身就是线程安全的,因为它在程序刚开始就被调用,很少会碰到并发问题,于是 `stackoverflow` 上也有大神给出了另一个 `Method Swizzling` 的实现: - -``` -- (void)replacementReceiveMessage:(const struct BInstantMessage *)arg1 { - NSLog(@"arg1 is %@", arg1); - [self replacementReceiveMessage:arg1]; -} -+ (void)load { - SEL originalSelector = @selector(ReceiveMessage:); - SEL overrideSelector = @selector(replacementReceiveMessage:); - Method originalMethod = class_getInstanceMethod(self, originalSelector); - Method overrideMethod = class_getInstanceMethod(self, overrideSelector); - if (class_addMethod(self, originalSelector, method_getImplementation(overrideMethod), method_getTypeEncoding(overrideMethod))) { - class_replaceMethod(self, overrideSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod)); - } else { - method_exchangeImplementations(originalMethod, overrideMethod); - } -} -``` -其实也就是去掉了`dispatch_once`的部分罢了。 - -`Method Swizzling` 的确是一个值得深入研究的话题,`Method Swizzling` 的最佳实现是什么呢?小弟才疏学浅理解的不深刻,找了几篇不错的资源推荐给大家: - -- [Objective-C的hook方案(一): Method Swizzling](http://blog.csdn.net/yiyaaixuexi/article/details/9374411) -- [Method Swizzling](http://nshipster.com/method-swizzling/) -- [How do I implement method swizzling?](http://stackoverflow.com/questions/5371601/how-do-i-implement-method-swizzling) -- [What are the Dangers of Method Swizzling in Objective C?](http://stackoverflow.com/questions/5339276/what-are-the-dangers-of-method-swizzling-in-objective-c) -- [JRSwizzle](https://github.com/rentzsch/jrswizzle) - -### 总结 - -我们之所以让自己的类继承 `NSObject` 不仅仅因为苹果帮我们完成了复杂的内存分配问题,更是因为这使得我们能够用上 Runtime 系统带来的便利。深入理解 Runtime 系统的细节更有利于我们利用消息机制写出功能更强大的代码,比如 `Method Swizzling` 等。 - - - -参考链接 - -- 原文:[Objective-C Runtime](http://yulingtianxia.com/blog/2014/11/05/objective-c-runtime/) -- Apple官方文档:[Objective-C Runtime Programming Guide](https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Introduction/Introduction.html#//apple_ref/doc/uid/TP40008048) -- Apple开源代码:[Objective-C Runtime源码](https://opensource.apple.com/source/objc4/) -- [Objective-C runtime之运行时的基本特点](http://blog.csdn.net/wzzvictory/article/details/8615569) -- [Understanding the Objective-C Runtime](http://cocoasamurai.blogspot.jp/2010/01/understanding-objective-c-runtime.html) \ No newline at end of file diff --git "a/_posts/2016-11-15-iOS\345\231\252\351\237\263\350\256\241\346\250\241\345\236\213.md" "b/_posts/2016-11-15-iOS\345\231\252\351\237\263\350\256\241\346\250\241\345\236\213.md" deleted file mode 100644 index c641d55a4a3..00000000000 --- "a/_posts/2016-11-15-iOS\345\231\252\351\237\263\350\256\241\346\250\241\345\236\213.md" +++ /dev/null @@ -1,118 +0,0 @@ ---- -layout: post -title: iOS噪音计 -subtitle: 一个iOS噪音计模型、以及测量原理及分贝计算 -date: 2016-11-15 -author: BY -header-img: img/post-bg-ios9-web.jpg -catalog: true -tags: - - iOS - - Demo ---- - - -# 前言 - -最近在办公室觉得有点吵,然后忽然想做一个噪音计测试一下噪音,在App Store下载了几款测噪音软件,使用原来都大同小异。于是决定自己实现测噪音的原理。 - -## 分贝dB -首先要测量噪音,必须知道噪音的大小的参考的单位为分贝(dB),分贝的定义如下: - -``` -SPL = 20lg[p(e)/p(ref)] -``` -`p(e)`为待测的有效声压,`p(ref)`为参考声压,一般取2*10E-5帕,这是人耳能分辨的最小声压(1KHz)。 - -就是说噪音每增加20dB,声压增强了10倍。 - -## iOS测噪音原理 - -iOS设备测量噪音原理非常简单:调用系统麦克风,根据麦克风输入强度计算转化为对应的dB值。但是,实现的过程可是坑满满。 - -找到了一篇博客介绍iOS硬件的调用:[iOS开发系列--音频播放、录音、视频播放、拍照、视频录制](http://www.cnblogs.com/kenshincui/p/4186022.html) - -iOS的`AVFoundation`框架中有一个`AVAudioRecorder`类专门处理录音操作,详见[Apple文档](https://developer.apple.com/reference/avfoundation/1668872-av_foundation_audio_settings_con) - -在`AVAudioRecorder.h`中找到下列方法 - -``` -- (void)updateMeters; /* call to refresh meter values */ 更新麦克风测量值 -- (float)peakPowerForChannel:(NSUInteger)channelNumber; /* returns peak power in decibels for a given channel */ 获取峰值 -- (float)averagePowerForChannel:(NSUInteger)channelNumber; /* returns average power in decibels for a given channel */ 获取平局值 -``` - -`- (float)averagePowerForChannel:(NSUInteger)channelNumber;`文档中描述: - ->Return Value - ->The current average power, in decibels, for the sound being recorded. A return value of 0 dB indicates full scale, or maximum power; a return value of -160 dB indicates minimum power (that is, near silence). - ->If the signal provided to the audio recorder exceeds ±full scale, then the return value may exceed 0 (that is, it may enter the positive range). - ->Discussion - ->To obtain a current average power value, you must call the updateMeters method before calling this method. - -也就是说获取的麦克风测量值返回值范围为 `-160dB ~ 0dB`,并且注意最后那句话返回值可能超过0。 - -## 转化公式 - -获取的的测量值为 `-160 ~ 0dB` ,如何转化为我们所要的噪音值呢?在网上找了很多资料都没有结果,于是就自己摸索转化公式。 - -刚开始想到的是利用分贝计算公式`SPL = 20lg[p(e)/p(ref)]`进行计算,后来直接放弃这个方案,因为这是一个对数运算,获取到的值非常稳定,几乎不会波动,与其他的测噪软件所得的分贝值出入太大。 - -然后发现有个App在麦克风没有输入时显示-55dB - -![](http://ww2.sinaimg.cn/large/7853084cgw1f9u0nu3xv3j205n0a0glq.jpg) - -于是思路就有了。 - -其他测噪音软件的量程均为`0~110dB`,而我们获取的的测量值为 `-160 ~ 0dB`,两者之间差了`50dB`,也就是说以麦克风的测量值的`-160dB+50dB = -110dB`作为起点,`0dB`作为Max值,恰好量程为`0~110dB`. - -问题看似结束,但是直接以`50dB`作为补偿测量结果会偏大。最后选择了分段进行处理,代码如下 - -``` - --(void)audioPowerChange{ - - [self.audioRecorder updateMeters];//更新测量值 - float power = [self.audioRecorder averagePowerForChannel:0];// 均值 - float powerMax = [self.audioRecorder peakPowerForChannel:0];// 峰值 - NSLog(@"power = %f, powerMax = %f",power, powerMax); - - CGFloat progress = (1.0 / 160.0) * (power + 160.0); - - // 关键代码 - power = power + 160 - 50; - - int dB = 0; - if (power < 0.f) { - dB = 0; - } else if (power < 40.f) { - dB = (int)(power * 0.875); - } else if (power < 100.f) { - dB = (int)(power - 15); - } else if (power < 110.f) { - dB = (int)(power * 2.5 - 165); - } else { - dB = 110; - } - - NSLog(@"progress = %f, dB = %d", progress, dB); - self.powerLabel.text = [NSString stringWithFormat:@"%ddB", dB]; - [self.audioPowerProgress setProgress:progress]; - -} - -``` - -# 效果 - -效果如下: - -![](http://ww4.sinaimg.cn/large/7853084cgw1f9u1gqgqieg20k00zk7d8.gif) - -# 下载地址 - -Demo下载地址:[Noise-meter-Demo](https://github.com/qiubaiying/Noise-meter-Demo) diff --git "a/_posts/2016-11-18-Objective-C-Runtime-\345\237\272\346\234\254\344\275\277\347\224\250.md" "b/_posts/2016-11-18-Objective-C-Runtime-\345\237\272\346\234\254\344\275\277\347\224\250.md" deleted file mode 100644 index 199109bbc52..00000000000 --- "a/_posts/2016-11-18-Objective-C-Runtime-\345\237\272\346\234\254\344\275\277\347\224\250.md" +++ /dev/null @@ -1,724 +0,0 @@ ---- -layout: post -title: Objective-C Runtime 基本使用 -subtitle: Runtime 使用案例 -date: 2017-02-04 -author: BY -header-img: img/post-bg-ios9-web.jpg -catalog: true -tags: - - Obj-C - - Runtime - - iOS ---- - -# 前言 - ->在上一篇文章[《Objective-C Runtime详解》](http://www.jianshu.com/p/a36bfc976b8e)中我们探讨了Runtime的基本原理,这篇文章我们将总结一下Runtime的一些基本使用 - -# 使用方法 - - -- 查询方法 -- 给分类添加属性 -- 更换代码的实现方法 -- 动态添加方法 -- 字典转属性 - -# 准备 - - 先创建两个类 - -`ClassA.h` - -``` -#import - -@interface ClassA : NSObject { - // 公有变量 - NSString *_publicVar1; - NSString *_publicVar2; - -} -// 公有属性 -@property(nonatomic,copy) NSString *publicProperty1; -@property(nonatomic,copy) NSString *publicProperty2; - -/* 公有方法 */ --(void)methodAOfClassAWithArg:(NSString *)arg; - -@end - -``` - -`ClassA.m` - -``` -#import "ClassA.h" - -@interface ClassA() -// 私有属性 -@property(nonatomic,copy) NSString *privateProperty1; -@property(nonatomic,copy) NSString *privateProperty2; - -@end - -@implementation ClassA { - // 私有变量 - NSString *_privateVar1; - NSString *_privateVar2; -} - -/* 公有方法 */ --(void)methodAOfClassAWithArg:(NSString *)arg { - NSLog(@" methodAOfClassA arg = %@", arg); -} - -/* 私有方法 */ --(void)MethodBOfClassAWithArg:(NSString *)arg { - NSLog(@" methodBOfClassA arg = %@", arg); -} -@end -``` - -`ClassB.h` - -``` -#import - -@interface ClassB : NSObject - -/* 公有方法 */ --(void)methodAOfClassBWithArg:(NSString *)arg; - -@end -``` - -`ClassB.m` - -``` -#import "ClassB.h" - -@implementation ClassB -- (void)methodAOfClassBWithArg:(NSString *)arg { - NSLog(@" methodAOfClassB arg = %@", arg); -} - --(void)methodBOfClassBWithArg:(NSString *)arg { - NSLog(@" methodBOfClassB arg = %@", arg); -} - -@end -``` - -## 查询方法 ---- - -在Objective-C Runtime下没有真正意义上的私有变量和方法,因为这些私有变量和方法都可以通过Runtime方法获取,这当然包括系统的私有API。接下来我们来一一介绍获取类中属性和方法的方法。当然不要忘了`#import `. - -#### 获取类的名称 - -方法:`const char *object_getClassName(id obj)`,使用比较简单,传入对象即可得到对应分类名。 - -``` -ClassA *classA = [[ClassA alloc] init]; -const char *className = object_getClassName(classA); -NSLog(@"className = %@", [NSString stringWithUTF8String:className]); - -//输出 -className = ClassA -``` - -#### 获取类中的方法 - -方法:`Method *class_copyMethodList(Class cls, unsigned int *outCount) ` - -上代码: - -``` -UInt32 count; -char dst; -Method *methods = class_copyMethodList([classA class], &count);//获取方法列表 -for (int i = 0; i < count; i++) { - Method method = methods[i];// 获取方法 - SEL methodName = method_getName(method);// 获取方法名 - method_getReturnType(method, &dst, sizeof(char));// 获取方法返回类型 - const char *methodType = method_getTypeEncoding(method);// 获取方法参数类型和返回类型 - NSLog(@"methodName = %@",NSStringFromSelector(methodName)); - NSLog(@"dst = %c", dst); -} - - // 输出 - methodName = methodAOfClassAWithArg: - dst = v - methodType = v24@0:8@16 - methodName = MethodBOfClassAWithArg: - dst = v - methodType = v24@0:8@16 - methodName = publicProperty1 - dst = @ - methodType = @16@0:8 - methodName = setPublicProperty1: - dst = v - methodType = v24@0:8@16 - methodName = publicProperty2 - dst = @ - methodType = @16@0:8 - methodName = setPublicProperty2: - dst = v - methodType = v24@0:8@16 - methodName = privateProperty1 - dst = @ - methodType = @16@0:8 - methodName = setPrivateProperty1: - dst = v - methodType = v24@0:8@16 - methodName = privateProperty2 - dst = @ - methodType = @16@0:8 - methodName = setPrivateProperty2: - dst = v - methodType = v24@0:8@16 - methodName = .cxx_destruct - dst = v - methodType = v16@0:8 - -``` - -`class_copyMethodList([classA class], &count)` 传入元类和计数器地址,返回方法列表。这里注意,返回的是`Method`结构体类型的C数组,`Method`类型我们在[上篇文章](http://www.jianshu.com/p/a36bfc976b8e)中已经详细说明, - -``` -typedef struct objc_method *Method; - -struct objc_method { - SEL method_name OBJC2_UNAVAILABLE; - char *method_types OBJC2_UNAVAILABLE; - IMP method_imp OBJC2_UNAVAILABLE; -} -``` - -但要区分`Method *methods`与`Method method`的区别,这是比较基础C语言知识。还有`Uint32`是OC定义的`unsigned int`类型`typedef unsigned int UInt32;` - -这里我们来看看 `method_getReturnType(method, &dst, sizeof(char))` 方法简单输出返回值类型,输出为 `v` 和 `@` ,参考Apple文档可知道返回类型为 `void` 和 `id` - -``` -A void v -A method selector (SEL) : -An object (whether statically typed or typed id) @ -``` - -`method_getTypeEncoding(method)`方法可以输出返回值,参数类型以及接收器类型。我们看输出的`v24@0:8@16`,分析上面的说明就可以知道: `v24`返回类型为`viod`,`@0`接收器类型为`id`,`@16`参数类型为`id` - -至于类型后面的值观察可以发现都是相差8,我认为是在method中的位置,分别以8bit存储不同类型的数据。 - -若有两个参数返回值为 `v32@0:8@16@24` ,对比可以猜测,在method中各个成员的排列是这样的: `接收器|SEl标识|参数1|参数2|...|返回值`,然后由 `method_getTypeEncoding(method)` 输出的顺序为: `返回值类型|接收器类型|SEL标识|参数1|参数2|...` 此处为个人见解,如有错误或不同意见欢迎提出探讨。 - -最后发现了一个奇怪的方法 `.cxx_destruct` ,在中[这篇文章](http://my.safaribooksonline.com/book/programming/objective-c/9780132908641/3dot-memory-management/ch03)中: ->ARC actually creates a -.cxx_destruct method to handle freeing instance variables. This method was originally created for calling C++ destructors automatically when an object was destroyed. - -和《Effective Objective-C 2.0》中提到的: ->When the compiler saw that an object contained C++ objects, it would generate a method called .cxx_destruct. ARC piggybacks on this method and emits the required cleanup code within it. - -可以了解到,`.cxx_destruct` 方法原本是为了C++对象析构的,ARC借用了这个方法插入代码实现了自动内存释放的工作 - -关于 `.cxx_destruct` 可以参考这篇文章:[ARC下dealloc过程及.cxx_destruct的探究](http://blog.jobbole.com/65028/) - -#### 获取类中的属性 - -在 [上篇文章](http://www.jianshu.com/p/a36bfc976b8e) 的 `Property` 中我们也提到了获取类中的属性的方法,如下: - -``` -id LenderClass = objc_getClass("ClassA");//获取classA 的元类同[ClassA class] -unsigned int outCount;//属性数量 -// 获取属性列表 -objc_property_t *properties = class_copyPropertyList(LenderClass, &outCount); - -// 遍历 -for (int i = 0; i < outCount; i++) { - - objc_property_t property = properties[i]; - - const char *propertyName = property_getName(property);// 获取属性名 - const char *propertyAttributes = property_getAttributes(property);// 获取属性描述 - - printf("propertyName:%s \n", propertyName); - printf("propertyAttributes:%s\n--------\n", propertyAttributes);//属性名及描述 -} -``` - -``` -// 输出 -propertyName:privateProperty1 -propertyAttributes:T@"NSString",C,N,V_privateProperty1 --------- -propertyName:privateProperty2 -propertyAttributes:T@"NSString",C,N,V_privateProperty2 --------- -propertyName:publicProperty1 -propertyAttributes:T@"NSString",C,N,V_publicProperty1 --------- -propertyName:publicProperty2 -propertyAttributes:T@"NSString",C,N,V_publicProperty2 --------- -``` -发现会输出公有属性以及私有属性。 - -#### 获取类中的成员变量 - -我们可以发现获取类中的方法,属性过程基本一致:通过元类获取方法列表或属性列表,然后在进行遍历。获取成员变量也一样: - -``` -id selfClass = [Calss class]; -unsigned int numIvars = 0; -Ivar *ivars = class_copyIvarList(selfClass, &numIvars); -for(int i = 0; i < numIvars; i++) { - Ivar ivar = ivars[i]; - const char *ivarName = ivar_getName(ivar); - const char *ivarType = ivar_getTypeEncoding(ivar);// 获取类型 - - printf("ivarName:%s\n", ivarName); - printf("ivarType:%s\n------\n", ivarType); -} -``` - -``` -// 输出 -ivarName:_publicVar1 -ivarType:@"NSString" ------- -ivarName:_publicVar2 -ivarType:@"NSString" ------- -ivarName:_privateVar1 -ivarType:@"NSString" ------- -ivarName:_privateVar2 -ivarType:@"NSString" ------- -ivarName:_publicProperty1 -ivarType:@"NSString" ------- -ivarName:_publicProperty2 -ivarType:@"NSString" ------- -ivarName:_privateProperty1 -ivarType:@"NSString" ------- -ivarName:_privateProperty2 -ivarType:@ -``` -可以发现输出了所有的成员变量,包括属性声明的 `_+属性名` 变量。 - -## 给分类添加属性 ---- - -众所周知,分类中是不能声明属性的。 - -我们创建一个 `CalssA` 的分类 `ClassA+CategoryA` ,在 `ClassA+CategoryA` 中添加一个属性 `name` - -``` -#import "ClassA.h" - -@interface ClassA (CategoryA) - -@property (nonatomic, strong) NSString *name; - -@end - -``` - -若在我们调用`CalssA`分类的`name` 将会crash,原因是分类中使用 `@property` 声明属性并不会生成`setter`和`getter`方法,但是我们会想,我们可以自己实现呀,没错,看下面的代码 - -``` -#import "ClassA+CategoryA.h" -#import - -@implementation ClassA (CategoryA) - -- (NSString *)name { - return name; -} - -- (void)setName:(NSString *)name { - _name = name; -} - -@end -``` -这里会报编译错误,因为分类中使用 `@property` 声明属性也不会生成成员变量 _name,并且手动声明也不行 - -![](http://ww1.sinaimg.cn/large/7853084cgw1f9zsknx42yj20fw033gm0.jpg) - -编译错误,提示实例变量无法添加到分类中,用正常的方法确实无法在分类中添加属性。 - -但是可以通过Runtim机制进行“添加”。其本质是给这个类添加属性关联,而非把这个属性添加到类中。 - -``` -#import "ClassA+CategoryA.h" -#import - - -@implementation ClassA (CategoryA) - -- (NSString *)name { - return objc_getAssociatedObject(self, @selector(name)); -} - -- (void)setName:(NSString *)name { - objc_setAssociatedObject(self, @selector(name), name, OBJC_ASSOCIATION_COPY_NONATOMIC); -} - -@end -``` - -调用: - -``` -classA.name = @"邱帅"; -NSLog(@"%@",classA.name); - -// 输出 -2016-11-21 16:18:48.084 UseRuntime[4392:1325037] 邱帅 -``` -可以看出添加属性成功! - -我们来看看关联属性的这几个方法: - -``` -OBJC_EXPORT void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy) - OBJC_AVAILABLE(10.6, 3.1, 9.0, 1.0); - -OBJC_EXPORT id objc_getAssociatedObject(id object, const void *key) - OBJC_AVAILABLE(10.6, 3.1, 9.0, 1.0); - -OBJC_EXPORT void objc_removeAssociatedObjects(id object) - OBJC_AVAILABLE(10.6, 3.1, 9.0, 1.0); - -``` -`objc_setAssociatedObject()` 方法为关联属性,参数如下: - -- `object`:属性关联的源对象,这里使用了`self`,代表关联本类的对象 -- `key`:区分属性的唯一标识,因为关联的属性可能不止一个,我们使用了`- (NSString *)name`方法的`SEL` `@selector(name)`作为唯一标示,当然也可以用下面的方法来生成Key : - -``` -//利用静态变量地址唯一不变的特性 -1、static void *strKey = &strKey; - -2、static NSString *strKey = @"strKey"; - -3、static char strKey; -``` - -- `value`:关联的属性值 -- `policy`:设置关联对象的`copy`、`story`、`nonatomic`等参数: - -这些常量对应着引用关联值的政策,也就是 Objc 内存管理的引用计数机制。 - -``` -typedef OBJC_ENUM(uintptr_t, objc_AssociationPolicy) { - OBJC_ASSOCIATION_ASSIGN = 0, - OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1, - OBJC_ASSOCIATION_COPY_NONATOMIC = 3, - OBJC_ASSOCIATION_RETAIN = 01401, - OBJC_ASSOCIATION_COPY = 01403 -}; -``` -`objc_getAssociatedObject()` 方法通过 `object` 与 `Key` 直接获取关联的属性值 - -`objc_removeAssociatedObjects()` 移除关联 - -我们使用上面的获取类中属性和成员变量的方法,发现输出: - -``` -// 有属性输出 -propertyName:name -propertyAttributes:T@"NSString",&,N -``` -没有成员变量 `_name`,进一步说明分类中不能添加成员变量!其本质是添加属性与分类之间关联。 - - -## 更换代码实现方法(Method Swizzling) ---- -在[上篇](http://www.jianshu.com/p/a36bfc976b8e)中详细介绍了`Method Swizzling`的原理,其本质是更换了 `selector` 的 `IMP` 。 - -``` -#import "ViewController.h" -#import -#import "ClassA.h" -#import "ClassB.h" - -@interface ViewController () - -@end - -@implementation ViewController - -+ (void)load { - Method classA_method = class_getInstanceMethod([ClassA class], @selector(methodAOfClassAWithArg:)); - Method classB_method = class_getInstanceMethod([ClassB class], @selector(methodAOfClassBWithArg:)); - method_exchangeImplementations(classA_method, classB_method); -} -- (void)viewDidLoad { - [super viewDidLoad]; - - [classA methodAOfClassAWithArg:@"classA 发出的 A方法"]; - [classB methodAOfClassBWithArg:@"classB 发出的 A方法"]; -} - -// 输出 - -2016-11-22 13:07:15.151 UseRuntime[1015:533335] methodAOfClassB arg = classA 发出的 A方法 -2016-11-22 13:07:15.151 UseRuntime[1015:533335] methodAOfClassA arg = classB 发出的 A方法 -``` - -首先交换方法写在 `+(void)load`,在程序的一开始就调用执行,你将不会碰到并发问题。 - -我们可以发现两个方法的实现过程以及对换。 - -当然,平时使用我们并不会这么做,当我们要在系统提供的方法上再扩充功能时(不能重写系统方法),就可以使用`Method Swizzling`. - -我们给`NSArray`添加一个分类`AddLog`,给 `arrayByAddingObject:`方法添加一个输出方法: - -``` -#import "NSArray+AddLog.h" -#import - -@implementation NSArray (AddLog) - -+ (void)load { - - SEL ori_selector = @selector(arrayByAddingObject:); - SEL my_selector = @selector(my_arrayByAddingObject:); - - Method ori_method = class_getInstanceMethod([NSArray class], ori_selector); - Method my_method = class_getInstanceMethod([NSArray class], my_selector); - - if (([NSArray class], ori_selector, method_getImplementation(my_method), method_getTypeEncoding(my_method))) { - - class_replaceMethod([NSArray class], my_selector, method_getImplementation(ori_method), method_getTypeEncoding(ori_method)); - - } else { - method_exchangeImplementations(ori_method, my_method); - } - -} - -- (NSArray *)my_arrayByAddingObject:(id)anObject { - - NSArray *array = [self my_arrayByAddingObject:anObject]; - NSLog(@"添加了一个元素 %@", anObject); - return array; -} - -@end - -``` -我们来看看这三个方法: - -- `class_addMethod()`:给一个方法添加新的方法和实现 -- `class_replaceMethod()`:取代了对于一个给定的类的实现方法 -- `method_exchangeImplementations()`:交换两个类的实现方法 - -这里我们先使用 `class_addMethod()` 在类中添加方法,若返回Yes说明类中没有该方法,然后再使用 `class_replaceMethod()` 方法进行取代;若返回NO,说明类中有该方法,使用`method_exchangeImplementations()`直接交换两者的 `IMP`. - -其实在这里直接使用`method_exchangeImplementations()`进行交换就可以了。因为类中必定有`arrayByAddingObject:`方法。 - - -我给我们自己的方法命名为`my_arrayByAddingObject:`,在原来的方法名上加上前缀,既可以防止命名冲突,又方便阅读,在我们`my_arrayByAddingObject:`方法中调用本身 - -``` -NSArray *array = [self my_arrayByAddingObject:anObject]; -``` - -看似会陷入递归调用,其实则不会,因为我们已经在`+ (void)load `方法中更换了`IMP`,他会调用`arrayByAddingObject:`方法,然后在后面添加我们需要添加的功能。 - -`arrayByAddingObject:`方法的调用不变; - - -``` -NSArray *arr1 = @[@"one", @"two"]; -NSArray *arr2 = [arr1 arrayByAddingObject:@"three"]; -NSLog(@"arr2 = %@", arr2); -``` - -``` -// 输出 -2016-11-22 13:57:00.021 UseRuntime[1147:743449] 添加了一个元素 three -2016-11-22 13:57:00.021 UseRuntime[1147:743449] arr2 = ( - one, - two, - three -) -``` - -## 动态添加方法 - -动态添加方法就是在消息转发前在`+ (BOOL)resolveInstanceMethod:(SEL)sel`方法中使用`class_addMethod()` 添加方法。 - -下面我面添加一个名为`resolveThisMethodDynamically`的方法: - -``` -void dynamicMethodIMP(id self, SEL _cmd) { - // implementation .... - printf("执行了dynamicMethodIMP!!!!"); -} - -+ (BOOL)resolveInstanceMethod:(SEL)sel { - - if (sel == @selector(resolveThisMethodDynamically)) { - class_addMethod([self class], sel, (IMP) dynamicMethodIMP, "v@:"); - return YES; - } - return [super resolveInstanceMethod:sel]; -} -``` -调用: - -``` -performSelector:@selector(resolveThisMethodDynamically)]; - -// 输出 -执行了dynamicMethodIMP!!!! -``` -对于上面添加的的方法 `resolveThisMethodDynamically` ,使用 `[self performSelector:@selector(resolveThisMethodDynamically)]` 进行调用,不能使用`[self resolveThisMethodDynamically]`,因为压根就没有声明 `-(void)resolveThisMethodDynamically`,会报编译错误。 - -整个过程就是,`performSelector:`调用`resolveThisMethodDynamically`方法,然后在列表中找不到(因为类中根本就没有注册该方法),然后跳入 `+ (BOOL)resolveInstanceMethod:` 中,我们再为`resolveThisMethodDynamically`方法添加具体实现。 - -## 字典转属性 - -将字典转化为模型,是在我们iOS开发中最为常用的技能。iOS的模型框架如`JSONModel`,`MJExtension`,`MJExtension`等皆是利用了runtime,将字典转为模型,不过兼顾的细节更多。下面我们来实现一个简易的字典转模型框架。 - -先上代码: - -``` -#import "NSObject+BYModel.h" -#import -#import - -@implementation NSObject (BYModel) - -- (void)by_modelSetDictionary:(NSDictionary *)dic { - - Class cls = [self class]; - - // 遍历本类和父类的变量 - while (cls) { - //获取所有成员变量 - unsigned int outCount = 0; - Ivar *ivars = class_copyIvarList(cls, &outCount); - - for (int i = 0; i < outCount; i++) { - Ivar ivar = ivars[i]; - - // 获取变量名 - NSMutableString *ivar_Name = [NSMutableString stringWithUTF8String:ivar_getName(ivar)]; - - [ivar_Name replaceCharactersInRange:NSMakeRange(0, 1) withString:@""];// _ivar -> ivar - - // - NSString *key = [ivar_Name copy]; - if ([key isEqualToString:@"dece"]) { - key = @"description"; - } - if ([key isEqualToString:@"ID"]) { - key = @"id"; - } - - id value = dic[key]; - if (!value) continue; - - // 拼接SEL ivar -> setIvar: - - NSString *cap = [ivar_Name substringToIndex:1]; - cap = cap.uppercaseString; // a->A - [ivar_Name replaceCharactersInRange:NSMakeRange(0, 1) withString:cap]; - [ivar_Name insertString:@"set" atIndex:0]; - [ivar_Name appendString:@":"]; - - SEL selector = NSSelectorFromString(ivar_Name); - - // 判断类型并发送消息 - NSString *type = [NSString stringWithUTF8String:ivar_getTypeEncoding(ivar)]; - - if ([type hasPrefix:@"@"]) { // 对象类型 - objc_msgSend(self, selector, value); - } else { // 非对象类型 - if ([type isEqualToString:@"d"]) { - objc_msgSend(self, selector, [value doubleValue]); - } else if ([type isEqualToString:@"f"]) { - objc_msgSend(self, selector, [value floatValue]); - } else if ([type isEqualToString:@"i"]) { - objc_msgSend(self, selector, [value intValue]); - } else { - objc_msgSend(self, selector, [value longLongValue]); - } - } - - - } - // 获取父类进行遍历变量 - cls = class_getSuperclass(cls); - } - -} - -``` - -这个这个段代码可能出现编译错误: - -![](http://ww3.sinaimg.cn/large/7853084cgw1fa3b5fbvsqj20k001fjrn.jpg) - -解决办法很简单: - -将项目 Project -> Build Settings -> Enable strct checking of objc_msgSend Calls 设置为 **NO** 即可 - -![](http://ww1.sinaimg.cn/large/7853084cgw1fa3b6mm9h7j20oh0aj0v8.jpg) - -接下来我们创建一个模型类`Student` - -``` -#import - -@interface Student : NSObject - -@property (nonatomic, strong) NSString *name; -@property (nonatomic, assign) int age; -@property (nonatomic, assign) int idNumber; - -@end - - -``` - -使用我们的转模型方法: - -``` -NSDictionary *dic = @{ @"name":@"邱帅", @"age": @(23), @"idNumber":@(1234567)}; - -Student *stu = [Student new]; -[stu by_modelSetDictionary:dic]; - -NSLog(@"%@", [NSString stringWithFormat:@"%@, %d, %d", stu.name, stu.age, stu.idNumber]); - -// 输出 -2016-11-24 15:32:46.351 Demo_字典转模型(Runtime)[2131:884627] 邱帅, 23, 1234567 -``` - -该方法先利用我们上面介绍的`class_copyIvarList()`获取类中的成员变量列表,然后进行遍历,拼接字符串`setIvar:`,最后调用`objc_msgSend()`直接发送设置变量的消息,完成属性的赋值。 - -``` -while (cls) { - - //code.. - - cls = class_getSuperclass(cls); -} -``` -这个循环是则获取父类中的属性:当前类的属性遍历结束之后,指向父类,若父类存在则在继续遍历属性,否则就退出循环。 - -当然,这个方法只是介绍了利用runtime进行字典转模型的原理,实际中还有很多需要考虑的细节,项目中我还是推荐使用像[YYModel](https://github.com/ibireme/YYModel)这些比较成熟而且安全的模型框架。 - -关于快速字典转模型可以参考我写的一篇[《快速完成JSON\字典转模型 For YYModel》](http://www.jianshu.com/p/b7d8cf650722)。 - - - diff --git a/_posts/2016-11-28-Objective-C-RunLoop.md b/_posts/2016-11-28-Objective-C-RunLoop.md deleted file mode 100644 index cd0d55bcc82..00000000000 --- a/_posts/2016-11-28-Objective-C-RunLoop.md +++ /dev/null @@ -1,724 +0,0 @@ ---- -layout: post -title: Objective-C:RunLoop -subtitle: 深入理解RunLoop -date: 2016-11-28 -author: BY -header-img: img/post-bg-ios9-web.jpg -catalog: true -tags: - - iOS - - RunLoop - - Obj-C ---- - -# 深入理解RunLoop ->本文转自:[《深入理解RunLoop》](http://blog.ibireme.com/2015/05/18/runloop/) - -# 前言 - -RunLoop 是 iOS 和 OSX 开发中非常基础的一个概念,这篇文章将从 CFRunLoop 的源码入手,介绍 RunLoop 的概念以及底层实现原理。之后会介绍一下在 iOS 中,苹果是如何利用 RunLoop 实现自动释放池、延迟回调、触摸事件、屏幕刷新等功能的。 - -# 目录 - -- RunLoop 的概念 -- RunLoop 与线程的关系 -- RunLoop 对外的接口 -- RunLoop 的 Mode -- RunLoop 的内部逻辑 -- RunLoop 的底层实现 -- 苹果用 RunLoop 实现的功能 - - AutoreleasePool - - 事件响应 - - 手势识别 - - 界面更新 - - 定时器 - - PerformSelecter - - 关于GCD - - 关于网络请求 -- RunLoop 的实际应用举例 - - AFNetworking - - AsyncDisplayKit - - -# 正文 -## RunLoop 的概念 - -一般来讲,一个线程一次只能执行一个任务,执行完成后线程就会退出。如果我们需要一个机制,让线程能随时处理事件但并不退出,通常的代码逻辑是这样的: - -``` -function loop() { - initialize(); - do { - var message = get_next_message(); - process_message(message); - } while (message != quit); -} -``` - -这种模型通常被称作 [Event Loop](https://en.wikipedia.org/wiki/Event_loop)。 Event Loop 在很多系统和框架里都有实现,比如 Node.js 的事件处理,比如 Windows 程序的消息循环,再比如 OSX/iOS 里的 RunLoop。实现这种模型的关键点在于:如何管理事件/消息,如何让线程在没有处理消息时休眠以避免资源占用、在有消息到来时立刻被唤醒。 - -所以,RunLoop 实际上就是一个对象,这个对象管理了其需要处理的事件和消息,并提供了一个入口函数来执行上面 Event Loop 的逻辑。线程执行了这个函数后,就会一直处于这个函数内部 "接受消息->等待->处理" 的循环中,直到这个循环结束(比如传入 quit 的消息),函数返回。 - -OSX/iOS 系统中,提供了两个这样的对象:NSRunLoop 和 CFRunLoopRef。 - -CFRunLoopRef 是在 CoreFoundation 框架内的,它提供了纯 C 函数的 API,所有这些 API 都是线程安全的。 - -NSRunLoop 是基于 CFRunLoopRef 的封装,提供了面向对象的 API,但是这些 API 不是线程安全的。 - -CFRunLoopRef 的代码是[开源](https://opensource.apple.com/source/CF/CF-855.17/CFRunLoop.c)的,你可以在[这里](http://opensource.apple.com/tarballs/CF/)下载到整个 CoreFoundation 的源码来查看。 - -(Update: Swift 开源后,苹果又维护了一个跨平台的 CoreFoundation 版本:,这个版本的源码可能和现有 iOS 系统中的实现略不一样,但更容易编译,而且已经适配了 Linux/Windows。) - -## RunLoop 与线程的关系 - -首先,iOS 开发中能遇到两个线程对象: pthread_t 和 NSThread。过去苹果有份[文档](http://www.fenestrated.net/~macman/mirrors/Apple%20Technotes%20(As%20of%202002)/tn/tn2028.html)标明了 NSThread 只是 pthread_t 的封装,但那份文档已经失效了,现在它们也有可能都是直接包装自最底层的 mach thread。苹果并没有提供这两个对象相互转换的接口,但不管怎么样,可以肯定的是 pthread_t 和 NSThread 是一一对应的。比如,你可以通过 `pthread_main_thread_np()` 或 `[NSThread mainThread]` 来获取主线程;也可以通过 `pthread_self()` 或 `[NSThread currentThread]` 来获取当前线程。CFRunLoop 是基于 pthread 来管理的。 - -苹果不允许直接创建 RunLoop,它只提供了两个自动获取的函数:`CFRunLoopGetMain()` 和 `CFRunLoopGetCurrent()`。 这两个函数内部的逻辑大概是下面这样: - - -``` -/// 全局的Dictionary,key 是 pthread_t, value 是 CFRunLoopRef -static CFMutableDictionaryRef loopsDic; -/// 访问 loopsDic 时的锁 -static CFSpinLock_t loopsLock; - -/// 获取一个 pthread 对应的 RunLoop。 -CFRunLoopRef _CFRunLoopGet(pthread_t thread) { - OSSpinLockLock(&loopsLock); - - if (!loopsDic) { - // 第一次进入时,初始化全局Dic,并先为主线程创建一个 RunLoop。 - loopsDic = CFDictionaryCreateMutable(); - CFRunLoopRef mainLoop = _CFRunLoopCreate(); - CFDictionarySetValue(loopsDic, pthread_main_thread_np(), mainLoop); - } - - /// 直接从 Dictionary 里获取。 - CFRunLoopRef loop = CFDictionaryGetValue(loopsDic, thread)); - - if (!loop) { - /// 取不到时,创建一个 - loop = _CFRunLoopCreate(); - CFDictionarySetValue(loopsDic, thread, loop); - /// 注册一个回调,当线程销毁时,顺便也销毁其对应的 RunLoop。 - _CFSetTSD(..., thread, loop, __CFFinalizeRunLoop); - } - - OSSpinLockUnLock(&loopsLock); - return loop; -} - -CFRunLoopRef CFRunLoopGetMain() { - return _CFRunLoopGet(pthread_main_thread_np()); -} - -CFRunLoopRef CFRunLoopGetCurrent() { - return _CFRunLoopGet(pthread_self()); -} -``` -从上面的代码可以看出,线程和 RunLoop 之间是一一对应的,其关系是保存在一个全局的 Dictionary 里。线程刚创建时并没有 RunLoop,如果你不主动获取,那它一直都不会有。RunLoop 的创建是发生在第一次获取时,RunLoop 的销毁是发生在线程结束时。你只能在一个线程的内部获取其 RunLoop(主线程除外)。 - -## RunLoop 对外的接口 - -在 CoreFoundation 里面关于 RunLoop 有5个类: - -- CFRunLoopRef -- CFRunLoopModeRef -- CFRunLoopSourceRef -- CFRunLoopTimerRef -- CFRunLoopObserverRef - -其中 CFRunLoopModeRef 类并没有对外暴露,只是通过 CFRunLoopRef 的接口进行了封装。他们的关系如下: - -![RunLoop 有5个类的关系](http://blog.ibireme.com/wp-content/uploads/2015/05/RunLoop_0.png) - -一个 RunLoop 包含若干个 Mode,每个 Mode 又包含若干个 Source/Timer/Observer。每次调用 RunLoop 的主函数时,只能指定其中一个 Mode,这个Mode被称作 CurrentMode。如果需要切换 Mode,只能退出 Loop,再重新指定一个 Mode 进入。这样做主要是为了分隔开不同组的 Source/Timer/Observer,让其互不影响。 - -**CFRunLoopSourceRef** 是事件产生的地方。Source 有两个版本:Source0 和 Source1。 - -- Source0 只包含了一个回调(函数指针),它并不能主动触发事件。使用时,你需要先调用 `CFRunLoopSourceSignal(source)`,将这个 Source 标记为待处理,然后手动调用 `CFRunLoopWakeUp(runloop)` 来唤醒 RunLoop,让其处理这个事件。 -- Source1 包含了一个 mach_port 和一个回调(函数指针),被用于通过内核和其他线程相互发送消息。这种 Source 能主动唤醒 RunLoop 的线程,其原理在下面会讲到。 - -**CFRunLoopTimerRef** 是基于时间的触发器,它和 NSTimer 是toll-free bridged 的,可以混用。其包含一个时间长度和一个回调(函数指针)。当其加入到 RunLoop 时,RunLoop会注册对应的时间点,当时间点到时,RunLoop会被唤醒以执行那个回调。 - -**CFRunLoopObserverRef** 是观察者,每个 Observer 都包含了一个回调(函数指针),当 RunLoop 的状态发生变化时,观察者就能通过回调接受到这个变化。可以观测的时间点有以下几个: - -``` -typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) { - kCFRunLoopEntry = (1UL << 0), // 即将进入Loop - kCFRunLoopBeforeTimers = (1UL << 1), // 即将处理 Timer - kCFRunLoopBeforeSources = (1UL << 2), // 即将处理 Source - kCFRunLoopBeforeWaiting = (1UL << 5), // 即将进入休眠 - kCFRunLoopAfterWaiting = (1UL << 6), // 刚从休眠中唤醒 - kCFRunLoopExit = (1UL << 7), // 即将退出Loop -}; -``` -上面的 Source/Timer/Observer 被统称为 mode item,一个 item 可以被同时加入多个 mode。但一个 item 被重复加入同一个 mode 时是不会有效果的。如果一个 mode 中一个 item 都没有,则 RunLoop 会直接退出,不进入循环 - -## RunLoop 的 Mode - -CFRunLoopMode 和 CFRunLoop 的结构大致如下: - -``` -struct __CFRunLoopMode { - CFStringRef _name; // Mode Name, 例如 @"kCFRunLoopDefaultMode" - CFMutableSetRef _sources0; // Set - CFMutableSetRef _sources1; // Set - CFMutableArrayRef _observers; // Array - CFMutableArrayRef _timers; // Array - ... -}; - -struct __CFRunLoop { - CFMutableSetRef _commonModes; // Set - CFMutableSetRef _commonModeItems; // Set - CFRunLoopModeRef _currentMode; // Current Runloop Mode - CFMutableSetRef _modes; // Set - ... -}; -``` -这里有个概念叫 "CommonModes":一个 Mode 可以将自己标记为"Common"属性(通过将其 ModeName 添加到 RunLoop 的 "commonModes" 中)。每当 RunLoop 的内容发生变化时,RunLoop 都会自动将 _commonModeItems 里的 Source/Observer/Timer 同步到具有 "Common" 标记的所有Mode里。 - -CFRunLoop对外暴露的管理 Mode 接口只有下面2个: - -``` -CFRunLoopAddCommonMode(CFRunLoopRef runloop, CFStringRef modeName); -CFRunLoopRunInMode(CFStringRef modeName, ...); -``` - -Mode 暴露的管理 mode item 的接口有下面几个: - -``` -CFRunLoopAddSource(CFRunLoopRef rl, CFRunLoopSourceRef source, CFStringRef modeName); -CFRunLoopAddObserver(CFRunLoopRef rl, CFRunLoopObserverRef observer, CFStringRef modeName); -CFRunLoopAddTimer(CFRunLoopRef rl, CFRunLoopTimerRef timer, CFStringRef mode); -CFRunLoopRemoveSource(CFRunLoopRef rl, CFRunLoopSourceRef source, CFStringRef modeName); -CFRunLoopRemoveObserver(CFRunLoopRef rl, CFRunLoopObserverRef observer, CFStringRef modeName); -CFRunLoopRemoveTimer(CFRunLoopRef rl, CFRunLoopTimerRef timer, CFStringRef mode); - -``` - -你只能通过 mode name 来操作内部的 mode,当你传入一个新的 mode name 但 RunLoop 内部没有对应 mode 时,RunLoop会自动帮你创建对应的 CFRunLoopModeRef。对于一个 RunLoop 来说,其内部的 mode `只能增加不能删除`。 - -苹果公开提供的 Mode 有两个:`kCFRunLoopDefaultMode (NSDefaultRunLoopMode)` 和 `UITrackingRunLoopMode`,你可以用这两个 Mode Name 来操作其对应的 Mode。 - -同时苹果还提供了一个操作 Common 标记的字符串:`kCFRunLoopCommonModes (NSRunLoopCommonModes)`,你可以用这个字符串来操作 Common Items,或标记一个 Mode 为 "Common"。使用时注意区分这个字符串和其他 mode name。 - -RunLoop 的内部逻辑 - -根据苹果在[文档](https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/Multithreading/RunLoopManagement/RunLoopManagement.html#//apple_ref/doc/uid/10000057i-CH16-SW23)里的说明,RunLoop 内部的逻辑大致如下: - -![](http://blog.ibireme.com/wp-content/uploads/2015/05/RunLoop_1.png) - -其内部代码整理如下 (太长了不想看可以直接跳过去,后面会有说明): - -``` -/// 用DefaultMode启动 -void CFRunLoopRun(void) { - CFRunLoopRunSpecific(CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 1.0e10, false); -} - -/// 用指定的Mode启动,允许设置RunLoop超时时间 -int CFRunLoopRunInMode(CFStringRef modeName, CFTimeInterval seconds, Boolean stopAfterHandle) { - return CFRunLoopRunSpecific(CFRunLoopGetCurrent(), modeName, seconds, returnAfterSourceHandled); -} - -/// RunLoop的实现 -int CFRunLoopRunSpecific(runloop, modeName, seconds, stopAfterHandle) { - - /// 首先根据modeName找到对应mode - CFRunLoopModeRef currentMode = __CFRunLoopFindMode(runloop, modeName, false); - /// 如果mode里没有source/timer/observer, 直接返回。 - if (__CFRunLoopModeIsEmpty(currentMode)) return; - - /// 1. 通知 Observers: RunLoop 即将进入 loop。 - __CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopEntry); - - /// 内部函数,进入loop - __CFRunLoopRun(runloop, currentMode, seconds, returnAfterSourceHandled) { - - Boolean sourceHandledThisLoop = NO; - int retVal = 0; - do { - - /// 2. 通知 Observers: RunLoop 即将触发 Timer 回调。 - __CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopBeforeTimers); - /// 3. 通知 Observers: RunLoop 即将触发 Source0 (非port) 回调。 - __CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopBeforeSources); - /// 执行被加入的block - __CFRunLoopDoBlocks(runloop, currentMode); - - /// 4. RunLoop 触发 Source0 (非port) 回调。 - sourceHandledThisLoop = __CFRunLoopDoSources0(runloop, currentMode, stopAfterHandle); - /// 执行被加入的block - __CFRunLoopDoBlocks(runloop, currentMode); - - /// 5. 如果有 Source1 (基于port) 处于 ready 状态,直接处理这个 Source1 然后跳转去处理消息。 - if (__Source0DidDispatchPortLastTime) { - Boolean hasMsg = __CFRunLoopServiceMachPort(dispatchPort, &msg) - if (hasMsg) goto handle_msg; - } - - /// 通知 Observers: RunLoop 的线程即将进入休眠(sleep)。 - if (!sourceHandledThisLoop) { - __CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopBeforeWaiting); - } - - /// 7. 调用 mach_msg 等待接受 mach_port 的消息。线程将进入休眠, 直到被下面某一个事件唤醒。 - /// • 一个基于 port 的Source 的事件。 - /// • 一个 Timer 到时间了 - /// • RunLoop 自身的超时时间到了 - /// • 被其他什么调用者手动唤醒 - __CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort) { - mach_msg(msg, MACH_RCV_MSG, port); // thread wait for receive msg - } - - /// 8. 通知 Observers: RunLoop 的线程刚刚被唤醒了。 - __CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopAfterWaiting); - - /// 收到消息,处理消息。 - handle_msg: - - /// 9.1 如果一个 Timer 到时间了,触发这个Timer的回调。 - if (msg_is_timer) { - __CFRunLoopDoTimers(runloop, currentMode, mach_absolute_time()) - } - - /// 9.2 如果有dispatch到main_queue的block,执行block。 - else if (msg_is_dispatch) { - __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg); - } - - /// 9.3 如果一个 Source1 (基于port) 发出事件了,处理这个事件 - else { - CFRunLoopSourceRef source1 = __CFRunLoopModeFindSourceForMachPort(runloop, currentMode, livePort); - sourceHandledThisLoop = __CFRunLoopDoSource1(runloop, currentMode, source1, msg); - if (sourceHandledThisLoop) { - mach_msg(reply, MACH_SEND_MSG, reply); - } - } - - /// 执行加入到Loop的block - __CFRunLoopDoBlocks(runloop, currentMode); - - - if (sourceHandledThisLoop && stopAfterHandle) { - /// 进入loop时参数说处理完事件就返回。 - retVal = kCFRunLoopRunHandledSource; - } else if (timeout) { - /// 超出传入参数标记的超时时间了 - retVal = kCFRunLoopRunTimedOut; - } else if (__CFRunLoopIsStopped(runloop)) { - /// 被外部调用者强制停止了 - retVal = kCFRunLoopRunStopped; - } else if (__CFRunLoopModeIsEmpty(runloop, currentMode)) { - /// source/timer/observer一个都没有了 - retVal = kCFRunLoopRunFinished; - } - - /// 如果没超时,mode里没空,loop也没被停止,那继续loop。 - } while (retVal == 0); - } - - /// 10. 通知 Observers: RunLoop 即将退出。 - __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit); -} -``` - -可以看到,实际上 RunLoop 就是这样一个函数,其内部是一个 do-while 循环。当你调用 `CFRunLoopRun()` 时,线程就会一直停留在这个循环里;直到超时或被手动停止,该函数才会返回。 - -## RunLoop 的底层实现 - -从上面代码可以看到,RunLoop 的核心是基于 mach port 的,其进入休眠时调用的函数是 `mach_msg()`。为了解释这个逻辑,下面稍微介绍一下 OSX/iOS 的系统架构。 - -![](http://ww4.sinaimg.cn/large/7853084cjw1fa7xzae9dlj206203nwel.jpg) - -苹果官方将整个系统大致划分为上述4个层次: - -应用层包括用户能接触到的图形应用,例如 Spotlight、Aqua、SpringBoard 等。 - -应用框架层即开发人员接触到的 Cocoa 等框架。 - -核心框架层包括各种核心框架、OpenGL 等内容。 - -Darwin 即操作系统的核心,包括系统内核、驱动、Shell 等内容,这一层是开源的,其所有源码都可以在 [opensource.apple.com](https://opensource.apple.com/) 里找到。 - -我们在深入看一下 Darwin 这个核心的架构: - -![](http://ww4.sinaimg.cn/large/7853084cjw1fa7xzt0n5wj2070060wel.jpg) - -其中,在硬件层上面的三个组成部分:Mach、BSD、IOKit (还包括一些上面没标注的内容),共同组成了 XNU 内核。 -XNU 内核的内环被称作 Mach,其作为一个微内核,仅提供了诸如处理器调度、IPC (进程间通信)等非常少量的基础服务。 -BSD 层可以看作围绕 Mach 层的一个外环,其提供了诸如进程管理、文件系统和网络等功能。 -IOKit 层是为设备驱动提供了一个面向对象(C++)的一个框架。 - -Mach 本身提供的 API 非常有限,而且苹果也不鼓励使用 Mach 的 API,但是这些API非常基础,如果没有这些API的话,其他任何工作都无法实施。在 Mach 中,所有的东西都是通过自己的对象实现的,进程、线程和虚拟内存都被称为"对象"。和其他架构不同, Mach 的对象间不能直接调用,只能通过消息传递的方式实现对象间的通信。"消息"是 Mach 中最基础的概念,消息在两个端口 (port) 之间传递,这就是 Mach 的 `IPC (进程间通信)` 的核心。 - -Mach 的消息定义是在 `` 头文件的,很简单: - -``` -typedef struct { - mach_msg_header_t header; - mach_msg_body_t body; -} mach_msg_base_t; - -typedef struct { - mach_msg_bits_t msgh_bits; - mach_msg_size_t msgh_size; - mach_port_t msgh_remote_port; - mach_port_t msgh_local_port; - mach_port_name_t msgh_voucher_port; - mach_msg_id_t msgh_id; -} mach_msg_header_t; -``` - -一条 Mach 消息实际上就是一个二进制数据包 (BLOB),其头部定义了当前端口 `local_port` 和目标端口 `remote_port`, - -发送和接受消息是通过同一个 API 进行的,其 option 标记了消息传递的方向: - -``` -mach_msg_return_t mach_msg( - mach_msg_header_t *msg, - mach_msg_option_t option, - mach_msg_size_t send_size, - mach_msg_size_t rcv_size, - mach_port_name_t rcv_name, - mach_msg_timeout_t timeout, - mach_port_name_t notify); -``` - -为了实现消息的发送和接收,`mach_msg()` 函数实际上是调用了一个 Mach 陷阱 (trap),即函数 `mach_msg_trap()`,陷阱这个概念在 Mach 中等同于系统调用。当你在用户态调用 `mach_msg_trap()` 时会触发陷阱机制,切换到内核态;内核态中内核实现的 `mach_msg()` 函数会完成实际的工作,如下图: - -![](http://blog.ibireme.com/wp-content/uploads/2015/05/RunLoop_5.png) - -这些概念可以参考维基百科: [System_call](http://en.wikipedia.org/wiki/System_call)、[Trap_(computing)](http://en.wikipedia.org/wiki/Trap_(computing))。 - -RunLoop 的核心就是一个 `mach_msg()` (见上面代码的第7步),RunLoop 调用这个函数去接收消息,如果没有别人发送 port 消息过来,内核会将线程置于等待状态。例如你在模拟器里跑起一个 iOS 的 App,然后在 App 静止时点击暂停,你会看到主线程调用栈是停留在 `mach_msg_trap()` 这个地方。 - -关于具体的如何利用 mach port 发送信息,可以看看 [NSHipster 这一篇文章](http://nshipster.com/inter-process-communication/),或者[这里](http://segmentfault.com/a/1190000002400329)的中文翻译 。 - -关于Mach的历史可以看看这篇很有趣的文章:[Mac OS X 背后的故事(三)Mach 之父 Avie Tevanian](http://www.programmer.com.cn/8121/)。 - -## 苹果用 RunLoop 实现的功能 - -首先我们可以看一下 App 启动后 RunLoop 的状态: - -``` -CFRunLoop { - current mode = kCFRunLoopDefaultMode - common modes = { - UITrackingRunLoopMode - kCFRunLoopDefaultMode - } - - common mode items = { - - // source0 (manual) - CFRunLoopSource {order =-1, { - callout = _UIApplicationHandleEventQueue}} - CFRunLoopSource {order =-1, { - callout = PurpleEventSignalCallback }} - CFRunLoopSource {order = 0, { - callout = FBSSerialQueueRunLoopSourceHandler}} - - // source1 (mach port) - CFRunLoopSource {order = 0, {port = 17923}} - CFRunLoopSource {order = 0, {port = 12039}} - CFRunLoopSource {order = 0, {port = 16647}} - CFRunLoopSource {order =-1, { - callout = PurpleEventCallback}} - CFRunLoopSource {order = 0, {port = 2407, - callout = _ZL20notify_port_callbackP12__CFMachPortPvlS1_}} - CFRunLoopSource {order = 0, {port = 1c03, - callout = __IOHIDEventSystemClientAvailabilityCallback}} - CFRunLoopSource {order = 0, {port = 1b03, - callout = __IOHIDEventSystemClientQueueCallback}} - CFRunLoopSource {order = 1, {port = 1903, - callout = __IOMIGMachPortPortCallback}} - - // Ovserver - CFRunLoopObserver {order = -2147483647, activities = 0x1, // Entry - callout = _wrapRunLoopWithAutoreleasePoolHandler} - CFRunLoopObserver {order = 0, activities = 0x20, // BeforeWaiting - callout = _UIGestureRecognizerUpdateObserver} - CFRunLoopObserver {order = 1999000, activities = 0xa0, // BeforeWaiting | Exit - callout = _afterCACommitHandler} - CFRunLoopObserver {order = 2000000, activities = 0xa0, // BeforeWaiting | Exit - callout = _ZN2CA11Transaction17observer_callbackEP19__CFRunLoopObservermPv} - CFRunLoopObserver {order = 2147483647, activities = 0xa0, // BeforeWaiting | Exit - callout = _wrapRunLoopWithAutoreleasePoolHandler} - - // Timer - CFRunLoopTimer {firing = No, interval = 3.1536e+09, tolerance = 0, - next fire date = 453098071 (-4421.76019 @ 96223387169499), - callout = _ZN2CAL14timer_callbackEP16__CFRunLoopTimerPv (QuartzCore.framework)} - }, - - modes = { - CFRunLoopMode { - sources0 = { /* same as 'common mode items' */ }, - sources1 = { /* same as 'common mode items' */ }, - observers = { /* same as 'common mode items' */ }, - timers = { /* same as 'common mode items' */ }, - }, - - CFRunLoopMode { - sources0 = { /* same as 'common mode items' */ }, - sources1 = { /* same as 'common mode items' */ }, - observers = { /* same as 'common mode items' */ }, - timers = { /* same as 'common mode items' */ }, - }, - - CFRunLoopMode { - sources0 = { - CFRunLoopSource {order = 0, { - callout = FBSSerialQueueRunLoopSourceHandler}} - }, - sources1 = (null), - observers = { - CFRunLoopObserver >{activities = 0xa0, order = 2000000, - callout = _ZN2CA11Transaction17observer_callbackEP19__CFRunLoopObservermPv} - )}, - timers = (null), - }, - - CFRunLoopMode { - sources0 = { - CFRunLoopSource {order = -1, { - callout = PurpleEventSignalCallback}} - }, - sources1 = { - CFRunLoopSource {order = -1, { - callout = PurpleEventCallback}} - }, - observers = (null), - timers = (null), - }, - - CFRunLoopMode { - sources0 = (null), - sources1 = (null), - observers = (null), - timers = (null), - } - } -} -``` - -可以看到,系统默认注册了5个Mode: - -1. kCFRunLoopDefaultMode: App的默认 Mode,通常主线程是在这个 Mode 下运行的。 -2. UITrackingRunLoopMode: 界面跟踪 Mode,用于 ScrollView 追踪触摸滑动,保证界面滑动时不受其他 Mode 影响。 -3. UIInitializationRunLoopMode: 在刚启动 App 时第进入的第一个 Mode,启动完成后就不再使用。 -4. GSEventReceiveRunLoopMode: 接受系统事件的内部 Mode,通常用不到。 -5. kCFRunLoopCommonModes: 这是一个占位的 Mode,没有实际作用。 - -你可以在[这里](http://iphonedevwiki.net/index.php/CFRunLoop)看到更多的苹果内部的 Mode,但那些 Mode 在开发中就很难遇到了。 - -当 RunLoop 进行回调时,一般都是通过一个很长的函数调用出去 (call out), 当你在你的代码中下断点调试时,通常能在调用栈上看到这些函数。下面是这几个函数的整理版本,如果你在调用栈中看到这些长函数名,在这里查找一下就能定位到具体的调用地点了: - -``` -{ - /// 1. 通知Observers,即将进入RunLoop - /// 此处有Observer会创建AutoreleasePool: _objc_autoreleasePoolPush(); - __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__(kCFRunLoopEntry); - do { - - /// 2. 通知 Observers: 即将触发 Timer 回调。 - __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__(kCFRunLoopBeforeTimers); - /// 3. 通知 Observers: 即将触发 Source (非基于port的,Source0) 回调。 - __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__(kCFRunLoopBeforeSources); - __CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK__(block); - - /// 4. 触发 Source0 (非基于port的) 回调。 - __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__(source0); - __CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK__(block); - - /// 6. 通知Observers,即将进入休眠 - /// 此处有Observer释放并新建AutoreleasePool: _objc_autoreleasePoolPop(); _objc_autoreleasePoolPush(); - __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__(kCFRunLoopBeforeWaiting); - - /// 7. sleep to wait msg. - mach_msg() -> mach_msg_trap(); - - - /// 8. 通知Observers,线程被唤醒 - __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__(kCFRunLoopAfterWaiting); - - /// 9. 如果是被Timer唤醒的,回调Timer - __CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__(timer); - - /// 9. 如果是被dispatch唤醒的,执行所有调用 dispatch_async 等方法放入main queue 的 block - __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(dispatched_block); - - /// 9. 如果如果Runloop是被 Source1 (基于port的) 的事件唤醒了,处理这个事件 - __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION__(source1); - - - } while (...); - - /// 10. 通知Observers,即将退出RunLoop - /// 此处有Observer释放AutoreleasePool: _objc_autoreleasePoolPop(); - __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__(kCFRunLoopExit); -} -``` - -#### AutoreleasePool - -App启动后,苹果在主线程 RunLoop 里注册了两个 Observer,其回调都是 `_wrapRunLoopWithAutoreleasePoolHandler()`。 - -第一个 Observer 监视的事件是 Entry(即将进入Loop),其回调内会调用 `_objc_autoreleasePoolPush()` 创建自动释放池。其 order 是-2147483647,优先级最高,保证创建释放池发生在其他所有回调之前。 - -第二个 Observer 监视了两个事件: BeforeWaiting(准备进入休眠) 时调用`_objc_autoreleasePoolPop()` 和 `_objc_autoreleasePoolPush()` 释放旧的池并创建新池;Exit(即将退出Loop) 时调用 `_objc_autoreleasePoolPop()` 来释放自动释放池。这个 Observer 的 order 是 2147483647(`2^31-1` -),优先级最低,保证其释放池子发生在其他所有回调之后。 - -在主线程执行的代码,通常是写在诸如事件回调、Timer回调内的。这些回调会被 RunLoop 创建好的 AutoreleasePool 环绕着,所以不会出现内存泄漏,开发者也不必显示创建 Pool 了。 - -#### 事件响应 - -苹果注册了一个 Source1 (基于 mach port 的) 用来接收系统事件,其回调函数为 `__IOHIDEventSystemClientQueueCallback()`。 - -当一个硬件事件(触摸/锁屏/摇晃等)发生后,首先由 `IOKit.framework` 生成一个 IOHIDEvent 事件并由 SpringBoard 接收。这个过程的详细情况可以参考[这里](http://iphonedevwiki.net/index.php/IOHIDFamily)。SpringBoard 只接收按键(锁屏/静音等),触摸,加速,接近传感器等几种 Event,随后用 mach port 转发给需要的App进程。随后苹果注册的那个 Source1 就会触发回调,并调用 `_UIApplicationHandleEventQueue()` 进行应用内部的分发。 - -`_UIApplicationHandleEventQueue()` 会把 IOHIDEvent 处理并包装成 UIEvent 进行处理或分发,其中包括识别 UIGesture/处理屏幕旋转/发送给 UIWindow 等。通常事件比如 UIButton 点击、touchesBegin/Move/End/Cancel 事件都是在这个回调中完成的。 - -#### 手势识别 - -当上面的 `_UIApplicationHandleEventQueue()` 识别了一个手势时,其首先会调用 Cancel 将当前的 touchesBegin/Move/End 系列回调打断。随后系统将对应的 UIGestureRecognizer 标记为待处理。 - -苹果注册了一个 Observer 监测 BeforeWaiting (Loop即将进入休眠) 事件,这个Observer的回调函数是 `_UIGestureRecognizerUpdateObserver()`,其内部会获取所有刚被标记为待处理的 GestureRecognizer,并执行GestureRecognizer的回调。 - -当有 UIGestureRecognizer 的变化(创建/销毁/状态改变)时,这个回调都会进行相应处理。 - -#### 界面更新 - -当在操作 UI 时,比如改变了 Frame、更新了 UIView/CALayer 的层次时,或者手动调用了 UIView/CALayer 的 `setNeedsLayout/setNeedsDisplay`方法后,这个 UIView/CALayer 就被标记为待处理,并被提交到一个全局的容器去。 - -苹果注册了一个 Observer 监听 BeforeWaiting(即将进入休眠) 和 Exit (即将退出Loop) 事件,回调去执行一个很长的函数: -`_ZN2CA11Transaction17observer_callbackEP19__CFRunLoopObservermPv()`。这个函数里会遍历所有待处理的 UIView/CALayer 以执行实际的绘制和调整,并更新 UI 界面。 - -这个函数内部的调用栈大概是这样的: - -``` -_ZN2CA11Transaction17observer_callbackEP19__CFRunLoopObservermPv() - QuartzCore:CA::Transaction::observer_callback: - CA::Transaction::commit(); - CA::Context::commit_transaction(); - CA::Layer::layout_and_display_if_needed(); - CA::Layer::layout_if_needed(); - [CALayer layoutSublayers]; - [UIView layoutSubviews]; - CA::Layer::display_if_needed(); - [CALayer display]; - [UIView drawRect]; -``` -#### 定时器 - -**NSTimer** 其实就是 CFRunLoopTimerRef,他们之间是 toll-free bridged 的。一个 NSTimer 注册到 RunLoop 后,RunLoop 会为其重复的时间点注册好事件。例如 10:00, 10:10, 10:20 这几个时间点。RunLoop为了节省资源,并不会在非常准确的时间点回调这个Timer。Timer 有个属性叫做 Tolerance (宽容度),标示了当时间点到后,容许有多少最大误差。 - -如果某个时间点被错过了,例如执行了一个很长的任务,则那个时间点的回调也会跳过去,不会延后执行。就比如等公交,如果 10:10 时我忙着玩手机错过了那个点的公交,那我只能等 10:20 这一趟了。 - -**CADisplayLink** 是一个和屏幕刷新率一致的定时器(但实际实现原理更复杂,和 NSTimer 并不一样,其内部实际是操作了一个 Source)。如果在两次屏幕刷新之间执行了一个长任务,那其中就会有一帧被跳过去(和 NSTimer 相似),造成界面卡顿的感觉。在快速滑动TableView时,即使一帧的卡顿也会让用户有所察觉。Facebook 开源的 AsyncDisplayLink 就是为了解决界面卡顿的问题,其内部也用到了 RunLoop,这个稍后我会再单独写一页博客来分析。 - -#### PerformSelecter - -当调用 NSObject 的 `performSelecter:afterDelay:` 后,实际上其内部会创建一个 Timer 并添加到当前线程的 RunLoop 中。所以如果当前线程没有 RunLoop,则这个方法会失效。 - -当调用 `performSelector:onThread:` 时,实际上其会创建一个 Timer 加到对应的线程去,同样的,如果对应线程没有 RunLoop 该方法也会失效。 - -#### 关于GCD - -实际上 RunLoop 底层也会用到 GCD 的东西,~~比如 RunLoop 是用 dispatch_source_t 实现的 Timer~~(评论中有人提醒,NSTimer 是用了 XNU 内核的 mk_timer,我也仔细调试了一下,发现 NSTimer 确实是由 mk_timer 驱动,而非 GCD 驱动的)。但同时 GCD 提供的某些接口也用到了 RunLoop, 例如 dispatch_async()。 - -当调用 `dispatch_async(dispatch_get_main_queue(), block)` 时,libDispatch 会向主线程的 RunLoop 发送消息,RunLoop会被唤醒,并从消息中取得这个 block,并在回调 `__CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__()` 里执行这个 block。但这个逻辑仅限于 dispatch 到主线程,dispatch 到其他线程仍然是由 libDispatch 处理的。 - -#### 关于网络请求 - -iOS 中,关于网络请求的接口自下至上有如下几层: - -``` -CFSocket -CFNetwork ->ASIHttpRequest -NSURLConnection ->AFNetworking -NSURLSession ->AFNetworking2, Alamofire -``` - -- CFSocket 是最底层的接口,只负责 socket 通信。 -- CFNetwork 是基于 CFSocket 等接口的上层封装,ASIHttpRequest 工作于这一层。 -- NSURLConnection 是基于 CFNetwork 的更高层的封装,提供面向对象的接口,AFNetworking 工作于这一层。 -- NSURLSession 是 iOS7 中新增的接口,表面上是和 NSURLConnection 并列的,但底层仍然用到了 NSURLConnection 的部分功能 (比如 `com.apple.NSURLConnectionLoader` 线程),AFNetworking2 和 Alamofire 工作于这一层。 - -下面主要介绍下 NSURLConnection 的工作过程。 - -通常使用 NSURLConnection 时,你会传入一个 Delegate,当调用了 `[connection start]` 后,这个 Delegate 就会不停收到事件回调。实际上,start 这个函数的内部会会获取 CurrentRunLoop,然后在其中的 DefaultMode 添加了4个 Source0 (即需要手动触发的Source)。CFMultiplexerSource 是负责各种 Delegate 回调的,CFHTTPCookieStorage 是处理各种 Cookie 的。 - -当开始网络传输时,我们可以看到 NSURLConnection 创建了两个新线程:com.apple.NSURLConnectionLoader 和 com.apple.CFSocket.private。其中 CFSocket 线程是处理底层 socket 连接的。NSURLConnectionLoader 这个线程内部会使用 RunLoop 来接收底层 socket 的事件,并通过之前添加的 Source0 通知到上层的 Delegate。 - -![](http://ww3.sinaimg.cn/large/7853084cjw1fa7xj3vs8ij20hs0cdt9m.jpg) - -NSURLConnectionLoader 中的 RunLoop 通过一些基于 mach port 的 Source 接收来自底层 CFSocket 的通知。当收到通知后,其会在合适的时机向 CFMultiplexerSource 等 Source0 发送通知,同时唤醒 Delegate 线程的 RunLoop 来让其处理这些通知。CFMultiplexerSource 会在 Delegate 线程的 RunLoop 对 Delegate 执行实际的回调。 - -## RunLoop 的实际应用举例 - -#### AFNetworking - -[AFURLConnectionOperation](https://github.com/AFNetworking/AFNetworking) 这个类是基于 NSURLConnection 构建的,其希望能在后台线程接收 Delegate 回调。为此 AFNetworking 单独创建了一个线程,并在这个线程中启动了一个 RunLoop: - -``` -+ (void)networkRequestThreadEntryPoint:(id)__unused object { - @autoreleasepool { - [[NSThread currentThread] setName:@"AFNetworking"]; - NSRunLoop *runLoop = [NSRunLoop currentRunLoop]; - [runLoop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode]; - [runLoop run]; - } -} - -+ (NSThread *)networkRequestThread { - static NSThread *_networkRequestThread = nil; - static dispatch_once_t oncePredicate; - dispatch_once(&oncePredicate, ^{ - _networkRequestThread = [[NSThread alloc] initWithTarget:self selector:@selector(networkRequestThreadEntryPoint:) object:nil]; - [_networkRequestThread start]; - }); - return _networkRequestThread; -} -``` - -RunLoop 启动前内部必须要有至少一个 Timer/Observer/Source,所以 AFNetworking 在 [runLoop run] 之前先创建了一个新的 NSMachPort 添加进去了。通常情况下,调用者需要持有这个 NSMachPort (mach_port) 并在外部线程通过这个 port 发送消息到 loop 内;但此处添加 port 只是为了让 RunLoop 不至于退出,并没有用于实际的发送消息。 - -``` -- (void)start { - [self.lock lock]; - if ([self isCancelled]) { - [self performSelector:@selector(cancelConnection) onThread:[[self class] networkRequestThread] withObject:nil waitUntilDone:NO modes:[self.runLoopModes allObjects]]; - } else if ([self isReady]) { - self.state = AFOperationExecutingState; - [self performSelector:@selector(operationDidStart) onThread:[[self class] networkRequestThread] withObject:nil waitUntilDone:NO modes:[self.runLoopModes allObjects]]; - } - [self.lock unlock]; -} -``` - -当需要这个后台线程执行任务时,AFNetworking 通过调用 [NSObject performSelector:onThread:..] 将这个任务扔到了后台线程的 RunLoop 中。 - -#### AsyncDisplayKit - -[AsyncDisplayKit](https://github.com/facebook/AsyncDisplayKit) 是 Facebook 推出的用于保持界面流畅性的框架,其原理大致如下: - -UI 线程中一旦出现繁重的任务就会导致界面卡顿,这类任务通常分为3类:排版,绘制,UI对象操作。 - -排版通常包括计算视图大小、计算文本高度、重新计算子式图的排版等操作。 - -绘制一般有文本绘制 (例如 CoreText)、图片绘制 (例如预先解压)、元素绘制 (Quartz)等操作。 - -UI对象操作通常包括 UIView/CALayer 等 UI 对象的创建、设置属性和销毁。 - -其中前两类操作可以通过各种方法扔到后台线程执行,而最后一类操作只能在主线程完成,并且有时后面的操作需要依赖前面操作的结果 (例如TextView创建时可能需要提前计算出文本的大小)。ASDK 所做的,就是尽量将能放入后台的任务放入后台,不能的则尽量推迟 (例如视图的创建、属性的调整)。 - -为此,ASDK 创建了一个名为 ASDisplayNode 的对象,并在内部封装了 UIView/CALayer,它具有和 UIView/CALayer 相似的属性,例如 frame、backgroundColor等。所有这些属性都可以在后台线程更改,开发者可以只通过 Node 来操作其内部的 UIView/CALayer,这样就可以将排版和绘制放入了后台线程。但是无论怎么操作,这些属性总需要在某个时刻同步到主线程的 UIView/CALayer 去。 - -ASDK 仿照 QuartzCore/UIKit 框架的模式,实现了一套类似的界面更新的机制:即在主线程的 RunLoop 中添加一个 Observer,监听了 kCFRunLoopBeforeWaiting 和 kCFRunLoopExit 事件,在收到回调时,遍历所有之前放入队列的待处理的任务,然后一一执行。 -具体的代码可以看这里[_ASAsyncTransactionGroup](https://github.com/facebook/AsyncDisplayKit/blob/master/AsyncDisplayKit%2FDetails%2FTransactions%2F_ASAsyncTransactionGroup.m)。 - diff --git "a/_posts/2016-12-01-\345\277\253\351\200\237\346\267\273\345\212\240\345\234\206\350\247\222\345\222\214\346\217\217\350\276\271.md" "b/_posts/2016-12-01-\345\277\253\351\200\237\346\267\273\345\212\240\345\234\206\350\247\222\345\222\214\346\217\217\350\276\271.md" deleted file mode 100644 index d1420da75ea..00000000000 --- "a/_posts/2016-12-01-\345\277\253\351\200\237\346\267\273\345\212\240\345\234\206\350\247\222\345\222\214\346\217\217\350\276\271.md" +++ /dev/null @@ -1,136 +0,0 @@ ---- -layout: post -title: 快速添加圆角和描边 -subtitle: iOS 为图片添加圆角和描边的几种方式 -date: 2016-12-01 -author: BY -header-img: img/post-bg-ios9-web.jpg -catalog: true -tags: - - iOS - - 开发技巧 ---- - -# 前言 - -对于习惯使用Storyboard的人来说,设置圆角、描边是一件比较蛋疼的事,因为苹果没有在xcode的Interface Builder上直接提供修改控件的圆角,边框设置。 - -我们来说说如何对某个控件进行圆角、描边处理: -# 初级 -对于一个初学者来说,如果要进行某个控件的圆角、描边设置,就要从Storyboard关联出属性,然后再对属性进行代码处理。 - -如下代码: - -``` -self.myButton.layer.cornerRadius = 20; -self.myButton.layer.masksToBounds = YES; -self.myButton.layer.borderWidth = 2; -self.myButton.layer.borderColor = [UIColor blackColor].CGColor; -``` -这样不仅需要Storyboard关联出属性,还要写一堆代码对属性进行设置,不得不说实在麻烦~ - -# 中级 -更聪明的做法是使用Storyboard提供的Runtime Attributes为控件添加圆角描边。 - -选中控件,然后在Runtime Attributes框中输入对应的`Key`与`Type`与`Value`,这样程序在运行时就会通过KVC为你的控件属性进行赋值。(不仅仅是圆角、描边~) - -如下图 - -![](http://ww4.sinaimg.cn/large/7853084cgw1fabg89aeqkj207b08j74y.jpg) - -设置圆角、描边的Key为: - -``` -layer.borderWidth -layer.borderColorFromUIColor -layer.cornerRadius -clipsToBounds -``` -我这次在测试时, - -这样做不用关联出属性,但是需要输入大串字符串,也是不够方便。 - -# 高级 - -创建UIView的分类,使用`IBInspectable`+ `IB_DESIGNABLE`关键字: - -``` -#import - -IB_DESIGNABLE - -@interface UIView (Inspectable) - -@property(nonatomic,assign) IBInspectable CGFloat cornerRadius; -@property(nonatomic,assign) IBInspectable CGFloat borderWidth; -@property(nonatomic,assign) IBInspectable UIColor *borderColor; - -@end -``` - -``` -#import "UIView+Inspectable.h" - -@implementation UIView (Inspectable) - --(void)setCornerRadius:(CGFloat)cornerRadius{ - self.layer.masksToBounds = YES; - self.layer.cornerRadius = cornerRadius; -} --(void)setBorderColor:(UIColor *)borderColor{ - self.layer.borderColor = borderColor.CGColor; -} --(void)setBorderWidth:(CGFloat)borderWidth{ - self.layer.borderWidth = borderWidth; -} - -- (CGFloat)cornerRadius{ - return self.layer.cornerRadius; -} -- (CGFloat)borderWidth{ - return self.layer.borderWidth; -} -- (UIColor *)borderColor{ - return [UIColor colorWithCGColor:self.layer.borderColor]; -} - -@end -``` - -附上:[GitHub地址](https://github.com/qiubaiying/CircularAndStroke.git) - - -#### 直接使用 - -直接将这两个文件拖入项目中即可使用,在右边栏将会显示圆角和描边的属性设置 - -如图: - -![](http://ww4.sinaimg.cn/large/7853084cgw1facfqugjtbj20mp07v401.jpg) - -#### 动态显示设置效果 - -直接使用的话只有在运行时才能看到效果, - -例如要实时显示一个`UIBUtton`圆角、描边效果,需要创建一个类继承`UIButton` - -``` -#import -#import "UIView+Inspectable.h" - -@interface myButton : UIButton - -@end -``` - -``` -#import "myButton.h" - -@implementation myButton - -@end -``` - -只要将button的Class选择该空白类即可 - -关于`IBInspectable`与`IB_DESIGNABLE`的使用详情可以参考这篇文章[《谈不完美的IBDesignable/IBInspectable可视化效果编程》](http://www.jianshu.com/p/a90e44ba1f2b) \ No newline at end of file diff --git "a/_posts/2016-12-07-\345\277\275\347\225\245-Xcode8-\344\270\255\347\232\204\346\263\250\351\207\212\350\255\246\345\221\212\342\232\240\357\270\217.md" "b/_posts/2016-12-07-\345\277\275\347\225\245-Xcode8-\344\270\255\347\232\204\346\263\250\351\207\212\350\255\246\345\221\212\342\232\240\357\270\217.md" deleted file mode 100644 index 4effeeab900..00000000000 --- "a/_posts/2016-12-07-\345\277\275\347\225\245-Xcode8-\344\270\255\347\232\204\346\263\250\351\207\212\350\255\246\345\221\212\342\232\240\357\270\217.md" +++ /dev/null @@ -1,27 +0,0 @@ ---- -layout: post -title: 忽略 Xcode 8 中的注释警告 -subtitle: Bulid Settings -> Documentation Comments -> NO -date: 2016-12-07 -author: BY -header-img: img/post_bg_debug.png -catalog: true -tags: - - iOS - - Xcode ---- - -#### 原因 - -从Xcode8.0开始,引入了文档注释警告,虽然是件好事,可是各种三方库爆出了一大堆警告: - - -![](http://ww2.sinaimg.cn/large/7853084cgw1fai8d9fu90j20ko0kpk21.jpg) - -#### 解决方法: - -`Bulid Settings` -> `Documentation Comments` -> **`NO`** - -![](http://ww1.sinaimg.cn/large/7853084cgw1fai8e613e5j20kk03cdga.jpg) - - diff --git "a/_posts/2016-12-13-\345\256\232\346\227\266\345\231\250\344\275\240\347\234\237\347\232\204\344\274\232\344\275\277\347\224\250\345\220\227\357\274\237.md" "b/_posts/2016-12-13-\345\256\232\346\227\266\345\231\250\344\275\240\347\234\237\347\232\204\344\274\232\344\275\277\347\224\250\345\220\227\357\274\237.md" deleted file mode 100644 index 4642c6d34e1..00000000000 --- "a/_posts/2016-12-13-\345\256\232\346\227\266\345\231\250\344\275\240\347\234\237\347\232\204\344\274\232\344\275\277\347\224\250\345\220\227\357\274\237.md" +++ /dev/null @@ -1,430 +0,0 @@ ---- -layout: post -title: 定时器 你真的会使用吗? -subtitle: iOS定时器详解 -date: 2016-12-13 -author: BY -header-img: img/post-bg-ios10.jpg -catalog: true -tags: - - iOS - - 定时器 ---- - - -# 前言 - -定时器的使用是软件开发基础技能,用于延时执行或重复执行某些方法。 - -我相信大部分人接触iOS的定时器都是从这段代码开始的: - -```objc -[NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(action:) userInfo:nil repeats:YES] -``` - -但是你真的会用吗? - -# 正文 - -## iOS定时器 - -首先来介绍iOS中的定时器 - -iOS中的定时器大致分为这几类: - -- **NSTimer** -- **CADisplayLink** -- **GCD定时器** - -### NSTimer - -#### 使用方法 - -**NSTime**定时器是我们比较常使用的定时器,比较常使用的方法有两种: - -```objc -+ (NSTimer *)timerWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(nullable id)userInfo repeats:(BOOL)yesOrNo; - -+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(nullable id)userInfo repeats:(BOOL)yesOrNo; -``` -这两种方法都是创建一个定时器,区别是用`timerWithTimeInterval:`方法创建的定时器需要手动加入RunLoop中。 - -``` -// 创建NSTimer对象 -NSTimer *timer = [NSTimer timerWithTimeInterval:3 target:self selector:@selector(timerAction) userInfo:nil repeats:YES]; -// 加入RunLoop中 -[[NSRunLoop mainRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode]; -``` - -需要**注意**的是: `UIScrollView` 滑动时执行的是 `UITrackingRunLoopMode`,`NSDefaultRunLoopMode`被挂起,会导致定时器失效,等恢复为**滑动结束**时才恢复定时器。其原因可以查看我这篇[《Objective-C RunLoop 详解》](http://www.jianshu.com/p/c4f552ceda63)中的 “RunLoop 的 Mode“章节,有详细的介绍。 - -举个例子: - -``` -- (void)startTimer{ - NSTimer *UIScrollView = [NSTimer timerWithTimeInterval:0.5 target:self selector:@selector(action:) userInfo:nil repeats:YES]; - [[NSRunLoop mainRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode]; -} - -- (void)action:(NSTimer *)sender { - static int i = 0; - NSLog(@"NSTimer: %d",i); - i++; -} -``` - -将`timer`添加到**NSDefaultRunLoopMode**中,没0.5秒打印一次,然后滑动`UIScrollView`. - -打印台输出: - -![](http://upload-images.jianshu.io/upload_images/2178672-9de097ecc618b498.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) - -可以看出在滑动`UIScrollView`时,定时器被暂停了。 - -所以如果需要定时器在 `UIScrollView` 拖动时也不影响的话,有两种解决方法 - - -1. **timer**分别添加到 `UITrackingRunLoopMode` 和 `NSDefaultRunLoopMode`中 - -```objc -[[NSRunLoop mainRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode]; -[[NSRunLoop mainRunLoop] addTimer:timer forMode: UITrackingRunLoopMode]; -``` - -2. 直接将**timer**添加到`NSRunLoopCommonModes` 中: - -```objc -[[NSRunLoop mainRunLoop] addTimer:timer forMode: NSRunLoopCommonModes]; -``` - -但并不是都**timer**所有的需要在滑动`UIScrollView`时继续执行,比如使用**NSTimer**完成的帧动画,滑动`UIScrollView`时就可以停止帧动画,保证滑动的流程性。 - -若没有特殊要求的话,一般使用第二种方法创建完**timer**,会自动添加到`NSDefaultRunLoopMode`中去执行,也是平时最常用的方法。 - -``` -NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(action:) userInfo:nil repeats:YES]; -``` -参数: - -`TimeInterval`:延时时间 - -`target`:目标对象,一般就是`self`本身 - -`selector`:执行方法 - -`userInfo`:传入信息 - -`repeats`:是否重复执行 - -以上创建的定时器,若`repeats`参数设为`NO`,执行一次后就会被释放掉; - -若`repeats`参数设为`YES`重复执行时,必须手动关闭,否则定时器不会释放(停止)。 - -释放方法: - -``` -// 停止定时器 -[timer invalidate]; -``` - -实际开发中,我们会将`NSTimer`对象设置为属性,这样方便释放。 - -**iOS10.0** 推出了两个新的API,与上面的方法相比,`selector`换成Block回调以、减少传入的参数(那几个参数真是鸡肋)。不过开发中一般需要适配低版本,还是尽量使用上面的方法吧。 - -``` -+ (NSTimer *)timerWithTimeInterval:(NSTimeInterval)interval repeats:(BOOL)repeats block:(void (^)(NSTimer *timer))block API_AVAILABLE(macosx(10.12), ios(10.0), watchos(3.0), tvos(10.0)); - -+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)interval repeats:(BOOL)repeats block:(void (^)(NSTimer *timer))block API_AVAILABLE(macosx(10.12), ios(10.0), watchos(3.0), tvos(10.0)); -``` - -###特点 - -- **必须加入Runloop** - - 上面不管使用哪种方法,实际最后都会加入RunLoop中执行,区别就在于是否手动加入而已。 -- **存在延迟** - - 不管是一次性的还是周期性的timer的实际触发事件的时间,都会与所加入的RunLoop和RunLoop Mode有关,如果此RunLoop正在执行一个连续性的运算,timer就会被延时出发。重复性的timer遇到这种情况,如果延迟超过了一个周期,则会在延时结束后立刻执行,并按照之前指定的周期继续执行,这个延迟时间大概为50-100毫秒. - - 所以NSTimer不是绝对准确的,而且中间耗时或阻塞错过下一个点,那么下一个点就pass过去了. - -- **UIScrollView滑动会暂停计时** - - 添加到`NSDefaultRunLoopMode`的 `timer` 在 `UIScrollView`滑动时会暂停,若不想被`UIScrollView`滑动影响,需要将 `timer` 添加再到 `UITrackingRunLoopMode` 或 直接添加到`NSRunLoopCommonModes` 中 - - - -##CADisplayLink - - -CADisplayLink官方介绍: ->A CADisplayLink object is a timer object that allows your application to synchronize its drawing to the refresh rate of the display - -**CADisplayLink**对象是一个和屏幕刷新率同步的定时器对象。每当屏幕显示内容刷新结束的时候,runloop就会向CADisplayLink指定的`target`发送一次指定的`selector`消息, CADisplayLink类对应的 `selector` 就会被调用一次。 - -从原理上可以看出,CADisplayLink适合做界面的不停重绘,比如视频播放的时候需要不停地获取下一帧用于界面渲染,或者做动画。 -###使用方法 - -创建: - -``` -@property (nonatomic, strong) CADisplayLink *displayLink; - -self.displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(handleDisplayLink:)]; - -// 每隔1帧调用一次 -self.displayLink.frameInterval = 1; - -[self.displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode]; -``` -释放方法: - -``` -[self.displayLink invalidate]; - -self.displayLink = nil; -``` -当把**CADisplayLink**对象添加到runloop中后,`selector`就能被周期性调用,类似于重复的NSTimer被启动了;执行`invalidate`操作时,CADisplayLink对象就会从runloop中移除,`selector`调用也随即停止,类似于NSTimer的`invalidate`方法。 - -**CADisplayLink**中有两个重要的属性: - -- **frameInterval** - - NSInteger类型的值,用来设置间隔多少帧调用一次`selector`方法,默认值是1,即每帧都调用一次。 - -- **duration** - - `CFTimeInterval`值为`readOnly`,表示两次屏幕刷新之间的时间间隔。需要注意的是,该属性在`targe`t的`selector`被首次调用以后才会被赋值。`selector`的调用间隔时间计算方式是:**调用间隔时间 = duration × frameInterval**。 - - -###特点 - -- **刷新频率固定** - - 正常情况iOS设备的屏幕刷新频率是固定**60Hz**,如果CPU过于繁忙,无法保证屏幕60次/秒的刷新率,就会导致跳过若干次调用回调方法的机会,跳过次数取决CPU的忙碌程度。 -- **屏幕刷新时调用** - - CADisplayLink在正常情况下会在每次刷新结束都被调用,精确度相当高。但如果调用的方法比较耗时,超过了屏幕刷新周期,就会导致跳过若干次回调调用机会 - -- **适合做界面渲染** - - CADisplayLink可以确保系统渲染每一帧的时候我们的方法都被调用,从而保证了动画的流畅性。 - -##GCD定时器 - -**GCD定时器**和NSTimer是不一样的,NSTimer受RunLoop影响,但是GCD的定时器不受影响,因为通过源码可知RunLoop也是基于GCD的实现的,所以GCD定时器有非常高的精度。关于GCD的使用可一看看[这篇博客](http://www.cnblogs.com/pure/archive/2013/03/31/2977420.html)。 - -###使用方法 -创建GCD定时器定时器的方法稍微比较复杂,看下面的代码: - -####单次的延时调用 -NSObject中的`performSelector:withObject:afterDelay:`以及 `performSelector:withObject:afterDelay:inModes:` 这两个方法在调用的时候会设置当前 runloop 中 `timer` ,前者设置的 `timer` 在 `NSDefaultRunLoopMode` 运行,后者则可以指定 **NSRunLoop** 的 `mode` 来执行。我们上面介绍过 runloop 中 `timer` 在 `UITrackingRunLoopMode` 被挂起,就导致了代码就会一直等待 `timer` 的调度,解决办法在上面也有说明。 - -不过我们可以用另一套方案来解决这个问题,就是使用GCD中的 `dispatch_after` 来实现单次的延时调用: - -``` -double delayInSeconds = 2.0; - dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC)); - dispatch_after(popTime, dispatch_get_main_queue(), ^(void){ - [self someMethod]; - }); -``` - -####循环调用 -``` -// 创建GCD定时器 -dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); - -dispatch_source_t _timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue); - -dispatch_source_set_timer(_timer, dispatch_walltime(NULL, 0), 1.0 * NSEC_PER_SEC, 0); //每秒执行 - -// 事件回调 -dispatch_source_set_event_handler(_timer, ^{ - - dispatch_async(dispatch_get_main_queue(), ^{ - // 在主线程中实现需要的功能 - - } -} - -}); - -// 开启定时器 -dispatch_resume(_timer); - -// 挂起定时器(dispatch_suspend 之后的 Timer,是不能被释放的!会引起崩溃) -dispatch_suspend(_timer); - -// 关闭定时器 -dispatch_source_cancel(_timer); - -``` - -上面代码中要注意的是: - -1. `dispatch_source_set_event_handler()`中的任务实在子线程中执行的,若需要回到主线程,要调用`dispatch_async(dispatch_get_main_queue(), ^{}`. -- `dispatch_source_set_timer` 中第二个参数,当我们使用 `dispatch_time` 或者 `DISPATCH_TIME_NOW` 时,系统会使用默认时钟来进行计时。然而当系统休眠的时候,默认时钟是不走的,也就会导致计时器停止。使用 `dispatch_walltime ` 可以让计时器按照真实时间间隔进行计时. -- 第三个参数, ` 1.0 * NSEC_PER_SEC` 为每秒执行一次,对应的还有毫秒,分秒,纳秒可以选择. - - -- `dispatch_source_set_event_handler` 这个函数在执行完之后,block 会立马执行一遍,后面隔一定时间间隔再执行一次。而 `NSTimer` 第一次执行是到计时器触发之后。这也是和 `NSTimer` 之间的一个显著区别。 -- 挂起(暂停)定时器, `dispatch_suspend` 之后的 `Timer`,不能被释放的,会引起崩溃. -- 创建的`timer`一定要有`dispatch_suspend(_timer)`或`dispatch_source_cancel(_timer)`这两句话来指定出口,否则定时器将不执行,若我们想无限循环可将 `dispatch_source_cancel(_timer)` 写在一句永不执行的`if`判断语句中。 - - -##使用场景 - -介绍完iOS中的各种定时器,接下来我们来说说这几种定时器在开发中的几种用法。 -###短信重发倒计时 - -短信倒计时使我们登录注册常用的功能,一般设置为60s,实现方法如下: - -``` -// 计时时间 -@property (nonatomic, assign) int timeout; - -/** 开启倒计时 */ -- (void)startCountdown { - - if (_timeout > 0) { - return; - } - - _timeout = 60; - - // GCD定时器 - dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); - - dispatch_source_t _timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue); - - dispatch_source_set_timer(_timer, dispatch_walltime(NULL, 0), 1.0 * NSEC_PER_SEC, 0); //每秒执行 - - dispatch_source_set_event_handler(_timer, ^{ - - if(_timeout <= 0 ){// 倒计时结束 - - // 关闭定时器 - dispatch_source_cancel(_timer); - - dispatch_async(dispatch_get_main_queue(), ^{ - - //设置界面的按钮显示 根据自己需求设置 - [self.sendMsgBtn setTitle:@"发送" forState:UIControlStateNormal]; - - self.sendMsgBtn.enabled = YES; - - }); - - }else{// 倒计时中 - - // 显示倒计时结果 - - NSString *strTime = [NSString stringWithFormat:@"重发(%.2d)", _timeout]; - - dispatch_async(dispatch_get_main_queue(), ^{ - - //设置界面的按钮显示 根据自己需求设置 - - [self.sendMsgBtn setTitle:[NSString stringWithFormat:@"%@",strTime] forState:UIControlStateNormal]; - - self.sendMsgBtn.enabled = NO; - - }); - - _timeout--; - } - }); - - // 开启定时器 - dispatch_resume(_timer); - -} -``` - -在上面代码中,我们设置了一个60s循环倒计时,当我们向服务器获取短信验证码成功时 调用该方法开始倒计时。每秒刷新按钮的倒计时数,倒计时结束时再将按钮 `Title` 恢复为“发送”. - -有一点需要注意的是,按钮的样式要设置为 **UIButtonTypeCustom**,否则会出现刷新 `Title` 时闪烁. - -我们可以把这个方法封装一下,方便调用,否则在控制器中写这么一大段代码确实也不优雅。 - -效果如下: - -![](http://upload-images.jianshu.io/upload_images/2178672-3d4d1353bcc36026.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) - -##### [代码链接](https://github.com/qiubaiying/BYTimer) - - -###每个几分钟向服务器发送数据 - -在有定位服务的APP中,我们需要每个一段时间将定位数据发送到服务器,比如每5s定位一次每隔5分钟将再统一将数据发送服务器,这样会处理比较省电。 -一般程序进入后台时,定时器会停止,但是在定位APP中,需要持续进行定位,APP在后台时依旧可以运行,所以在后台定时器也是可以运行的。 - -注:关于iOS后台常驻,可以查看[这篇博客](http://waitingyuan.blog.163.com/blog/static/2155781652014111133150534/) - -在使用GCD定时的时候发现GCD定时器也可以在后代运行,创建方法同上面的短信倒计时. - -这里我们使用**NSTimer**来创建一个每个5分钟执行一次的定时器. - -``` -#import - -typedef void(^TimerBlock)(); - -@interface BYTimer : NSObject - -- (void)startTimerWithBlock:(TimerBlock)timerBlock; - -- (void)stopTimer; - -@end - -``` - -``` -#import "BYTimer.h" - -@interface BYTimer () - -@property (nonatomic, strong) NSTimer *timer; -@property (nonatomic, strong) TimerBlock timerBlock; - -@end - -@implementation BYTimer - -- (void)startTimerWithBlock:(TimerBlock)timerBlock { - - self.timer = [NSTimer timerWithTimeInterval:300 target:self selector:@selector(_timerAction) userInfo:nil repeats:YES]; - - [[NSRunLoop mainRunLoop] addTimer:self.timer forMode:NSRunLoopCommonModes]; - _timerBlock = timerBlock; - -} - -- (void)_timerAction { - if (self.timerBlock) { - self.timerBlock(); - } -} - -- (void)stopTimer { - [self.timer invalidate]; -} - -@end -``` - -该接口的实现很简单,就是 **NSTimer** 创建了一个300s执行一次的定时器,但是要注意定时器需要加入`NSRunLoopCommonModes`中。 - -要使定时器在后台能运行,app 就需要在 [后台常驻](http://waitingyuan.blog.163.com/blog/static/2155781652014111133150534/)。 - -# 结语 - -最后总结一下: - -NSTimer 使用简单方便,但是应用条件有限。 - -CADisplayLink 刷新频率与屏幕帧数相同,用于绘制动画。具体使用可看我封装好的一个 [水波纹动画](https://github.com/qiubaiying/WaterRippleView)。 - -GCD定时器 精度高,可控性强,使用稍复杂。 diff --git a/_posts/2016-12-21-Objective-C-Category.md b/_posts/2016-12-21-Objective-C-Category.md deleted file mode 100644 index eead0c13275..00000000000 --- a/_posts/2016-12-21-Objective-C-Category.md +++ /dev/null @@ -1,645 +0,0 @@ ---- -layout: post -title: Objective-C:Category -subtitle: 深入解析 Category 的实现原理 -date: 2016-12-21 -author: BY -header-img: img/post-bg-ios9-web.jpg -catalog: true -tags: - - Category - - iOS - - ObjC ---- - ->本文转载自美图点评技术团队的:[深入理解Objective-C:Category](http://tech.meituan.com/DiveIntoCategory.html),略有修改。 - -# 前言 - - -无论一个类设计的多么完美,在未来的需求演进中,都有可能会碰到一些无法预测的情况。那怎么扩展已有的类呢?一般而言,继承和组合是不错的选择。但是在Objective-C 2.0中,又提供了category这个语言特性,可以动态地为已有类添加新行为。如今category已经遍布于Objective-C代码的各个角落,从Apple官方的framework到各个开源框架,从功能繁复的大型APP到简单的应用,catagory无处不在。本文对category做了比较全面的整理,希望对读者有所裨益。 - -# 简介 - -本文系学习Objective-C的runtime源码时整理所成,主要剖析了category在runtime层的实现原理以及和category相关的方方面面,内容包括: - -- 初入宝地 category简介 -- 连类比事 category和extension -- 挑灯细览 category真面目 -- 追本溯源 category如何加载 -- 旁枝末叶 category和+load方法 -- 触类旁通 category和方法覆盖 -- 更上一层 category和关联对象 - -## 初入宝地 Category简介 - -Category是Objective-C 2.0之后添加的语言特性,Category的主要作用是为已经存在的类添加方法。除此之外,apple还推荐了Category的另外两个使用场景,详见[Apple Category文档](https://developer.apple.com/library/content/documentation/General/Conceptual/DevPedia-CocoaCore/Category.html)。 - -- 可以把类的实现分开在几个不同的文件里面。这样做有几个显而易见的好处, - - 可以减少单个文件的体积 b)可以把不同的功能组织到不同的category里 - - 可以由多个开发者共同完成一个类 - - 可以按需加载想要的 Category 等等。 -- 声明私有方法 - -不过除了apple推荐的使用场景,广大开发者脑洞大开,还衍生出了category的其他几个使用场景: - -- 模拟多继承 -- 把framework的私有方法公开 - -Objective-C的这个语言特性对于纯动态语言来说可能不算什么,比如javascript,你可以随时为一个“类”或者对象添加任意方法和实例变量。但是对于不是那么“动态”的语言而言,这确实是一个了不起的特性。 - -## 连类比事 Category和Extension - -extension看起来很像一个匿名的category,但是extension和有名字的category几乎完全是两个东西。 extension在编译期决议,它就是类的一部分,在编译期和头文件里的@interface以及实现文件里的@implement一起形成一个完整的类,它伴随类的产生而产生,亦随之一起消亡。extension一般用来隐藏类的私有信息,你必须有一个类的源码才能为一个类添加extension,所以你无法为系统的类比如NSString添加extension。(详见[Apple文档](https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/ProgrammingWithObjectiveC/CustomizingExistingClasses/CustomizingExistingClasses.html)) - -## 挑灯细览 category真面目 - -我们知道,所有的OC类和对象,在runtime层都是用struct表示的,category也不例外,在runtime层,category用结构体category_t(在objc-runtime-new.h中可以找到此定义),它包含了 - -1. 类的名字(name) -- 类(cls) -- category中所有给类添加的实例方法的列表(instanceMethods) -- category中所有添加的类方法的列表(classMethods) -- category实现的所有协议的列表(protocols) -- category中添加的所有属性(instanceProperties) - -``` -typedef struct category_t { - const char *name; - classref_t cls; - struct method_list_t *instanceMethods; - struct method_list_t *classMethods; - struct protocol_list_t *protocols; - struct property_list_t *instanceProperties; -} category_t; -``` -从category的定义也可以看出category的可为(可以添加实例方法,类方法,甚至可以实现协议,添加属性)和不可为(无法添加实例变量)。 -ok,我们先去写一个category看一下category到底为何物: - -MyClass.h: - -``` -#import - -@interface MyClass : NSObject - -- (void)printName; - -@end - -@interface MyClass(MyAddition) - -@property(nonatomic, copy) NSString *name; - -- (void)printName; - -@end -``` -MyClass.m: - -``` -#import "MyClass.h" - -@implementation MyClass - -- (void)printName -{ - NSLog(@"%@",@"MyClass"); -} - -@end - -@implementation MyClass(MyAddition) - -- (void)printName -{ - NSLog(@"%@",@"MyAddition"); -} - -@end -``` - -我们使用clang的命令去看看category到底会变成什么: - -``` -clang -rewrite-objc MyClass.m -``` -好吧,我们得到了一个3M大小,10w多行的.cpp文件(这绝对是Apple值得吐槽的一点),我们忽略掉所有和我们无关的东西,在文件的最后,我们找到了如下代码片段: - -``` -static struct /*_method_list_t*/ { -unsigned int entsize; // sizeof(struct _objc_method) -unsigned int method_count; -struct _objc_method method_list[1]; -} _OBJC_$_CATEGORY_INSTANCE_METHODS_MyClass_$_MyAddition __attribute__ ((used, section ("__DATA,__objc_const"))) = { -sizeof(_objc_method), -1, -{{(struct objc_selector *)"printName", "v16@0:8", (void *)_I_MyClass_MyAddition_printName}} -}; - -static struct /*_prop_list_t*/ { -unsigned int entsize; // sizeof(struct _prop_t) -unsigned int count_of_properties; -struct _prop_t prop_list[1]; -} _OBJC_$_PROP_LIST_MyClass_$_MyAddition __attribute__ ((used, section ("__DATA,__objc_const"))) = { -sizeof(_prop_t), -1, -{{"name","T@\"NSString\",C,N"}} -}; - -extern "C" __declspec(dllexport) struct _class_t OBJC_CLASS_$_MyClass; - -static struct _category_t _OBJC_$_CATEGORY_MyClass_$_MyAddition __attribute__ ((used, section ("__DATA,__objc_const"))) = -{ -"MyClass", -0, // &OBJC_CLASS_$_MyClass, -(const struct _method_list_t *)&_OBJC_$_CATEGORY_INSTANCE_METHODS_MyClass_$_MyAddition, -0, -0, -(const struct _prop_list_t *)&_OBJC_$_PROP_LIST_MyClass_$_MyAddition, -}; -static void OBJC_CATEGORY_SETUP_$_MyClass_$_MyAddition(void ) { -_OBJC_$_CATEGORY_MyClass_$_MyAddition.cls = &OBJC_CLASS_$_MyClass; -} -#pragma section(".objc_inithooks$B", long, read, write) -__declspec(allocate(".objc_inithooks$B")) static void *OBJC_CATEGORY_SETUP[] = { -(void *)&OBJC_CATEGORY_SETUP_$_MyClass_$_MyAddition, -}; -static struct _class_t *L_OBJC_LABEL_CLASS_$ [1] __attribute__((used, section ("__DATA, __objc_classlist,regular,no_dead_strip")))= { -&OBJC_CLASS_$_MyClass, -}; -static struct _class_t *_OBJC_LABEL_NONLAZY_CLASS_$[] = { -&OBJC_CLASS_$_MyClass, -}; -static struct _category_t *L_OBJC_LABEL_CATEGORY_$ [1] __attribute__((used, section ("__DATA, __objc_catlist,regular,no_dead_strip")))= { -&_OBJC_$_CATEGORY_MyClass_$_MyAddition, -}; -``` -我们可以看到, - -1. 首先编译器生成了实例方法列表OBJC$_CATEGORY_INSTANCE_METHODSMyClass$_MyAddition和属性列表OBJC$_PROP_LISTMyClass$_MyAddition,两者的命名都遵循了公共前缀+类名+category名字的命名方式,而且实例方法列表里面填充的正是我们在MyAddition这个category里面写的方法printName,而属性列表里面填充的也正是我们在MyAddition里添加的name属性。还有一个需要注意到的事实就是category的名字用来给各种列表以及后面的category结构体本身命名,而且有static来修饰,所以在同一个编译单元里我们的category名不能重复,否则会出现编译错误。 -- 其次,编译器生成了category本身OBJC$_CATEGORYMyClass$_MyAddition,并用前面生成的列表来初始化category本身。 -- 最后,编译器在DATA段下的objc_catlist section里保存了一个大小为1的category_t的数组L_OBJC_LABELCATEGORY$(当然,如果有多个category,会生成对应长度的数组^_^),用于运行期category的加载。 -到这里,编译器的工作就接近尾声了,对于category在运行期怎么加载,我们下节揭晓。 - -## 追本溯源 category如何加载 - -我们知道,Objective-C的运行是依赖OC的runtime的,而OC的runtime和其他系统库一样,是OS X和iOS通过dyld动态加载的。 -想了解更多dyld地同学可以移步[这里](https://www.mikeash.com/pyblog/friday-qa-2012-11-09-dyld-dynamic-linking-on-os-x.html)。 - -对于OC运行时,入口方法如下(在objc-os.mm文件中): - -``` -void _objc_init(void) -{ - static bool initialized = false; - if (initialized) return; - initialized = true; - - // fixme defer initialization until an objc-using image is found? - environ_init(); - tls_init(); - lock_init(); - exception_init(); - - // Register for unmap first, in case some +load unmaps something - _dyld_register_func_for_remove_image(&unmap_image); - dyld_register_image_state_change_handler(dyld_image_state_bound, - 1/*batch*/, &map_images); - dyld_register_image_state_change_handler(dyld_image_state_dependents_initialized, 0/*not batch*/, &load_images); -} -``` -category被附加到类上面是在map_images的时候发生的,在new-ABI的标准下,_objc_init里面的调用的map_images最终会调用objc-runtime-new.mm里面的_read_images方法,而在_read_images方法的结尾,有以下的代码片段: - -``` -// Discover categories. - for (EACH_HEADER) { - category_t **catlist = - _getObjc2CategoryList(hi, &count); - for (i = 0; i < count; i++) { - category_t *cat = catlist[i]; - class_t *cls = remapClass(cat->cls); - - if (!cls) { - // Category's target class is missing (probably weak-linked). - // Disavow any knowledge of this category. - catlist[i] = NULL; - if (PrintConnecting) { - _objc_inform("CLASS: IGNORING category \?\?\?(%s) %p with " - "missing weak-linked target class", - cat->name, cat); - } - continue; - } - - // Process this category. - // First, register the category with its target class. - // Then, rebuild the class's method lists (etc) if - // the class is realized. - BOOL classExists = NO; - if (cat->instanceMethods || cat->protocols - || cat->instanceProperties) - { - addUnattachedCategoryForClass(cat, cls, hi); - if (isRealized(cls)) { - remethodizeClass(cls); - classExists = YES; - } - if (PrintConnecting) { - _objc_inform("CLASS: found category -%s(%s) %s", - getName(cls), cat->name, - classExists ? "on existing class" : ""); - } - } - - if (cat->classMethods || cat->protocols - /* || cat->classProperties */) - { - addUnattachedCategoryForClass(cat, cls->isa, hi); - if (isRealized(cls->isa)) { - remethodizeClass(cls->isa); - } - if (PrintConnecting) { - _objc_inform("CLASS: found category +%s(%s)", - getName(cls), cat->name); - } - } - } - } -``` -首先,我们拿到的catlist就是上节中讲到的编译器为我们准备的category_t数组,关于是如何加载catlist本身的,我们暂且不表,这和category本身的关系也不大,有兴趣的同学可以去研究以下Apple的二进制格式和load机制。 - -略去PrintConnecting这个用于log的东西,这段代码很容易理解: - -1. 把category的实例方法、协议以及属性添加到类上 -2. 把category的类方法和协议添加到类的metaclass上 - -值得注意的是,在代码中有一小段注释 `/ || cat->classProperties /`,看来苹果有过给类添加属性的计划啊。 -ok,我们接着往里看,category的各种列表是怎么最终添加到类上的,就拿实例方法列表来说吧: -在上述的代码片段里,addUnattachedCategoryForClass只是把类和category做一个关联映射,而remethodizeClass才是真正去处理添加事宜的功臣。 - -``` -static void remethodizeClass(class_t *cls) -{ - category_list *cats; - BOOL isMeta; - - rwlock_assert_writing(&runtimeLock); - - isMeta = isMetaClass(cls); - - // Re-methodizing: check for more categories - if ((cats = unattachedCategoriesForClass(cls))) { - chained_property_list *newproperties; - const protocol_list_t **newprotos; - - if (PrintConnecting) { - _objc_inform("CLASS: attaching categories to class '%s' %s", - getName(cls), isMeta ? "(meta)" : ""); - } - - // Update methods, properties, protocols - - BOOL vtableAffected = NO; - attachCategoryMethods(cls, cats, &vtableAffected); - - newproperties = buildPropertyList(NULL, cats, isMeta); - if (newproperties) { - newproperties->next = cls->data()->properties; - cls->data()->properties = newproperties; - } - - newprotos = buildProtocolList(cats, NULL, cls->data()->protocols); - if (cls->data()->protocols && cls->data()->protocols != newprotos) { - _free_internal(cls->data()->protocols); - } - cls->data()->protocols = newprotos; - - _free_internal(cats); - - // Update method caches and vtables - flushCaches(cls); - if (vtableAffected) flushVtables(cls); - } -} -``` -而对于添加类的实例方法而言,又会去调用attachCategoryMethods这个方法,我们去看下attachCategoryMethods: - -``` -static void -attachCategoryMethods(class_t *cls, category_list *cats, - BOOL *inoutVtablesAffected) -{ - if (!cats) return; - if (PrintReplacedMethods) printReplacements(cls, cats); - - BOOL isMeta = isMetaClass(cls); - method_list_t **mlists = (method_list_t **) - _malloc_internal(cats->count * sizeof(*mlists)); - - // Count backwards through cats to get newest categories first - int mcount = 0; - int i = cats->count; - BOOL fromBundle = NO; - while (i--) { - method_list_t *mlist = cat_method_list(cats->list[i].cat, isMeta); - if (mlist) { - mlists[mcount++] = mlist; - fromBundle |= cats->list[i].fromBundle; - } - } - - attachMethodLists(cls, mlists, mcount, NO, fromBundle, inoutVtablesAffected); - - _free_internal(mlists); - -} - -``` -attachCategoryMethods做的工作相对比较简单,它只是把所有category的实例方法列表拼成了一个大的实例方法列表,然后转交给了attachMethodLists方法(我发誓,这是本节我们看的最后一段代码了^_^),这个方法有点长,我们只看一小段: - -``` -for (uint32_t m = 0; - (scanForCustomRR || scanForCustomAWZ) && m < mlist->count; - m++) - { - SEL sel = method_list_nth(mlist, m)->name; - if (scanForCustomRR && isRRSelector(sel)) { - cls->setHasCustomRR(); - scanForCustomRR = false; - } else if (scanForCustomAWZ && isAWZSelector(sel)) { - cls->setHasCustomAWZ(); - scanForCustomAWZ = false; - } - } - - // Fill method list array - newLists[newCount++] = mlist; - . - . - . - - // Copy old methods to the method list array - for (i = 0; i < oldCount; i++) { - newLists[newCount++] = oldLists[i]; - } -``` -需要注意的有两点: - -1. category的方法没有“完全替换掉”原来类已经有的方法,也就是说如果category和原来类都有methodA,那么category附加完成之后,类的方法列表里会有两个methodA -2. category的方法被放到了新方法列表的前面,而原来类的方法被放到了新方法列表的后面,这也就是我们平常所说的category的方法会“覆盖”掉原来类的同名方法,这是因为运行时在查找方法的时候是顺着方法列表的顺序查找的,它只要一找到对应名字的方法,就会罢休^_^,殊不知后面可能还有一样名字的方法。 - -## 旁枝末叶 category 和 +load 方法 - -我们知道,在类和category中都可以有+load方法,那么有两个问题: - -1. 在类的+load方法调用的时候,我们可以调用category中声明的方法么? -2. 这么些个+load方法,调用顺序是咋样的呢? - -鉴于上述几节我们看的代码太多了,对于这两个问题我们先来看一点直观的: - -![](http://tech.meituan.com/img/diveintocategory/project.png) - -我们的代码里有MyClass和MyClass的两个category (Category1和Category2),MyClass和两个category都添加了+load方法,并且Category1和Category2都写了MyClass的printName方法。 -在Xcode中点击Edit Scheme,添加如下两个环境变量(可以在执行load方法以及加载category的时候打印log信息,更多的环境变量选项可参见objc-private.h): -![](http://tech.meituan.com/img/diveintocategory/environment_vars.png) - -运行项目,我们会看到控制台打印很多东西出来,我们只找到我们想要的信息,顺序如下: - -``` -objc[1187]: REPLACED: -[MyClass printName] by category Category1 -objc[1187]: REPLACED: -[MyClass printName] by category Category2 -. -. -. -objc[1187]: LOAD: class 'MyClass' scheduled for +load -objc[1187]: LOAD: category 'MyClass(Category1)' scheduled for +load -objc[1187]: LOAD: category 'MyClass(Category2)' scheduled for +load -objc[1187]: LOAD: +[MyClass load] -. -. -. -objc[1187]: LOAD: +[MyClass(Category1) load] -. -. -. -objc[1187]: LOAD: +[MyClass(Category2) load] -``` - -所以,对于上面两个问题,答案是很明显的: - -1. 可以调用,因为附加category到类的工作会先于+load方法的执行 -- +load的执行顺序是先类,后category,而category的+load执行顺序是根据编译顺序决定的。 -目前的编译顺序是这样的: - -![](http://tech.meituan.com/img/diveintocategory/compile1.png) - -我们调整一个Category1和Category2的编译顺序,run。ok,我们可以看到控制台的输出顺序变了: - -![](http://tech.meituan.com/img/diveintocategory/compile2.png) - -``` -objc[1187]: REPLACED: -[MyClass printName] by category Category2 -objc[1187]: REPLACED: -[MyClass printName] by category Category1 -. -. -. -objc[1187]: LOAD: class 'MyClass' scheduled for +load -objc[1187]: LOAD: category 'MyClass(Category2)' scheduled for +load -objc[1187]: LOAD: category 'MyClass(Category1)' scheduled for +load -objc[1187]: LOAD: +[MyClass load] -. -. -. -objc[1187]: LOAD: +[MyClass(Category2) load] -. -. -. -objc[1187]: LOAD: +[MyClass(Category1) load] -``` - -虽然对于+load的执行顺序是这样,但是对于“覆盖”掉的方法,则会先找到最后一个编译的category里的对应方法。 - -这一节我们只是用很直观的方式得到了问题的答案,有兴趣的同学可以继续去研究一下OC的运行时代码。 - -## 触类旁通 category 和方法覆盖 - -鉴于上面几节我们已经把原理都讲了,这一节只有一个问题: - -怎么调用到原来类中被category覆盖掉的方法? - -对于这个问题,我们已经知道category其实并不是完全替换掉原来类的同名方法,只是category在方法列表的前面而已,所以我们只要顺着方法列表找到最后一个对应名字的方法,就可以调用原来类的方法: - -``` -Class currentClass = [MyClass class]; -MyClass *my = [[MyClass alloc] init]; - -if (currentClass) { - unsigned int methodCount; - Method *methodList = class_copyMethodList(currentClass, &methodCount); - IMP lastImp = NULL; - SEL lastSel = NULL; - for (NSInteger i = 0; i < methodCount; i++) { - Method method = methodList[i]; - NSString *methodName = [NSString stringWithCString:sel_getName(method_getName(method)) - encoding:NSUTF8StringEncoding]; - if ([@"printName" isEqualToString:methodName]) { - lastImp = method_getImplementation(method); - lastSel = method_getName(method); - } - } - typedef void (*fn)(id,SEL); - - if (lastImp != NULL) { - fn f = (fn)lastImp; - f(my,lastSel); - } - free(methodList); -} -``` - -## 更上一层 category 和关联对象 - -如上所见,我们知道在category里面是无法为category添加实例变量的。但是我们很多时候需要在category中添加和对象关联的值,这个时候可以求助关联对象来实现。 - -MyClass+Category1.h: - -``` -#import "MyClass.h" - -@interface MyClass (Category1) - -@property(nonatomic,copy) NSString *name; - -@end -``` - -MyClass+Category1.m: - -``` -#import "MyClass+Category1.h" -#import - -@implementation MyClass (Category1) - -+ (void)load -{ - NSLog(@"%@",@"load in Category1"); -} - -- (void)setName:(NSString *)name -{ - objc_setAssociatedObject(self, - "name", - name, - OBJC_ASSOCIATION_COPY); -} - -- (NSString*)name -{ - NSString *nameObject = objc_getAssociatedObject(self, "name"); - return nameObject; -} - -@end -``` - -但是关联对象又是存在什么地方呢? 如何存储? 对象销毁时候如何处理关联对象呢? -我们去翻一下runtime的源码,在objc-references.mm文件中有个方法_object_set_associative_reference: - -``` -void _object_set_associative_reference(id object, void *key, id value, uintptr_t policy) { - // retain the new value (if any) outside the lock. - ObjcAssociation old_association(0, nil); - id new_value = value ? acquireValue(value, policy) : nil; - { - AssociationsManager manager; - AssociationsHashMap &associations(manager.associations()); - disguised_ptr_t disguised_object = DISGUISE(object); - if (new_value) { - // break any existing association. - AssociationsHashMap::iterator i = associations.find(disguised_object); - if (i != associations.end()) { - // secondary table exists - ObjectAssociationMap *refs = i->second; - ObjectAssociationMap::iterator j = refs->find(key); - if (j != refs->end()) { - old_association = j->second; - j->second = ObjcAssociation(policy, new_value); - } else { - (*refs)[key] = ObjcAssociation(policy, new_value); - } - } else { - // create the new association (first time). - ObjectAssociationMap *refs = new ObjectAssociationMap; - associations[disguised_object] = refs; - (*refs)[key] = ObjcAssociation(policy, new_value); - _class_setInstancesHaveAssociatedObjects(_object_getClass(object)); - } - } else { - // setting the association to nil breaks the association. - AssociationsHashMap::iterator i = associations.find(disguised_object); - if (i != associations.end()) { - ObjectAssociationMap *refs = i->second; - ObjectAssociationMap::iterator j = refs->find(key); - if (j != refs->end()) { - old_association = j->second; - refs->erase(j); - } - } - } - } - // release the old value (outside of the lock). - if (old_association.hasValue()) ReleaseValue()(old_association); -} -``` - -我们可以看到所有的关联对象都由AssociationsManager管理,而AssociationsManager定义如下: - -``` -class AssociationsManager { - static OSSpinLock _lock; - static AssociationsHashMap *_map; // associative references: object pointer -> PtrPtrHashMap. -public: - AssociationsManager() { OSSpinLockLock(&_lock); } - ~AssociationsManager() { OSSpinLockUnlock(&_lock); } - - AssociationsHashMap &associations() { - if (_map == NULL) - _map = new AssociationsHashMap(); - return *_map; - } -}; -``` - -AssociationsManager里面是由一个静态AssociationsHashMap来存储所有的关联对象的。这相当于把所有对象的关联对象都存在一个全局map里面。而map的的key是这个对象的指针地址(任意两个不同对象的指针地址一定是不同的),而这个map的value又是另外一个AssociationsHashMap,里面保存了关联对象的kv对。 -而在对象的销毁逻辑里面,见objc-runtime-new.mm: - -``` -void *objc_destructInstance(id obj) -{ - if (obj) { - Class isa_gen = _object_getClass(obj); - class_t *isa = newcls(isa_gen); - - // Read all of the flags at once for performance. - bool cxx = hasCxxStructors(isa); - bool assoc = !UseGC && _class_instancesHaveAssociatedObjects(isa_gen); - - // This order is important. - if (cxx) object_cxxDestruct(obj); - if (assoc) _object_remove_assocations(obj); - - if (!UseGC) objc_clear_deallocating(obj); - } - - return obj; -} -``` - -嗯,runtime的销毁对象函数objc_destructInstance里面会判断这个对象有没有关联对象,如果有,会调用_object_remove_assocations做关联对象的清理工作。 - -# 后记 - -正如侯捷先生所讲-“源码面前,了无秘密”,Apple的Cocoa Touch框架虽然并不开源,但是Objective-C的runtime和Core Foundation却是完全开放源码的(在可以下载到全部的开源代码)。 -本系列runtime源码学习将会持续更新,意犹未尽的同学可以自行到上述网站下载源码学习。行笔简陋,如有错误,望指正。 \ No newline at end of file diff --git "a/_posts/2016-12-26-ReactiveCocoa-\345\237\272\347\241\200.md" "b/_posts/2016-12-26-ReactiveCocoa-\345\237\272\347\241\200.md" deleted file mode 100644 index 3fc19b915a4..00000000000 --- "a/_posts/2016-12-26-ReactiveCocoa-\345\237\272\347\241\200.md" +++ /dev/null @@ -1,970 +0,0 @@ ---- -layout: post -title: ReactiveCocoa 基础 -subtitle: 函数式编程框架 ReactiveCocoa 基础入门 -date: 2016-12-26 -author: BY -header-img: img/post-bg-ios9-web.jpg -catalog: true -tags: - - iOS - - ReactiveCocoa - - 函数式编程 - - 开源框架 ---- - -# ReactiveCocoa基础 ->本文修改自[最快让你上手ReactiveCocoa之基础篇](http://www.jianshu.com/p/87ef6720a096) -> ->有关对 **ReactiveCocoa** 的看法可以看一下唐巧的这篇[ReactiveCocoa 讨论会](https://gold.xitu.io/entry/568bd2ae60b2e57ba2cd2c7b) - - - -![ReactiveCocoa思维导图](http://ww2.sinaimg.cn/large/006y8lVagw1fb7g0gukk8j30m90rl78j.jpg) - - -# ReactiveCocoa简介 - -[![](http://ww1.sinaimg.cn/large/006y8lVagw1fb7g6on3iwj30c2029q2z.jpg)](https://github.com/ReactiveCocoa/ReactiveCocoa) - -ReactiveCocoa(简称为RAC),是由Github开源的一个应用于iOS和OS开发的新框架,Cocoa是苹果整套框架的简称,因此很多苹果框架喜欢以Cocoa结尾。 - -在我们iOS开发过程中,当某些事件响应的时候,需要处理某些业务逻辑,这些事件都用不同的方式来处理。 - -比如按钮的点击使用action,ScrollView滚动使用delegate,属性值改变使用KVO等系统提供的方式。其实这些事件,都可以通过RAC处理 - -ReactiveCocoa为事件提供了很多处理方法,而且利用RAC处理事件很方便,可以把要处理的事情,和监听的事情的代码放在一起,这样非常方便我们管理,就不需要跳到对应的方法里。 - -非常符合我们开发中高聚合,低耦合的思想。 - - -# ReactiveCocoa编程思想 - -在开发中我们也不能太依赖于某个框架,否则这个框架不更新了,导致项目后期没办法维护,比如之前Facebook提供的 `Three20` 框架,在当时也是神器,但是后来不更新了,也就没什么人用了。因此我感觉学习一个框架,还是有必要了解它的编程思想。 - -先简单介绍下目前咱们已知的编程思想: - -#### 响应式编程思想 - -**响应式编程思想**:不需要考虑调用顺序,只需要知道考虑结果,类似于蝴蝶效应,产生一个事件,会影响很多东西,这些事件像流一样的传播出去,然后影响结果,借用面向对象的一句话,万物皆是流。 - -`代表`:KVO - -#### 链式编程思想 - -**链式编程** 是将多个操作(多行代码)通过点号(.)链接在一起成为一句代码,使代码可读性好。如: - -``` -make.add(1).add(2).sub(5).muilt(-4).divide(4); -``` - -`特点`:方法的返回值是block,block必须有返回值(本身对象),block参数(需要操作的值) - -`代表`:masonry框架。 - -`实现`:模仿masonry,写一个加法计算器,练习链式编程思想。 - -NSObject+Caculator.h - -``` -# import - -@class CaculatorMaker; - -@interface NSObject (Caculator) - -// 计算 -+ (int)makeCaculators:(void (^)(CaculatorMaker *))block; - -@end -``` - -NSObject+Caculator.m - -``` -@implementation NSObject (Caculator) - -+ (int)makeCaculators:(void (^)(CaculatorMaker *))block { - - CaculatorMaker *mgr = [[CaculatorMaker alloc] init]; - - block(mgr); - - return (mgr.result); -} - -@end -``` - -CaculatorMaker.h - -``` -# import - -@class CaculatorMaker; - -typedef CaculatorMaker *(^CasulatorBlock)(int); - -@interface CaculatorMaker : NSObject - -@property (nonatomic, assign) int result; - -// 算数方法 -- (CaculatorMaker *(^)(int))add; -- (CasulatorBlock)sub; -- (CasulatorBlock)muilt; -- (CasulatorBlock)divide; - - -@end -``` - -CaculatorMaker.m - -``` -# import "CaculatorMaker.h" - -@implementation CaculatorMaker - -- (CaculatorMaker *(^)(int))add { - - return ^CaculatorMaker *(int value) { - - _result += value; - - return self; - }; -} - -- (CasulatorBlock)sub { - - return ^CaculatorMaker *(int value) { - - _result -= value; - - return self; - }; -} - -- (CasulatorBlock)muilt { - - return ^CaculatorMaker *(int value) { - - _result *= value; - - return self; - }; -} - -- (CasulatorBlock)divide { - - return ^CaculatorMaker *(int value) { - - _result /= value; - - return self; - }; -} - -@end -``` - -使用: - -``` -int result = [NSObject makeCaculators:^(CaculatorMaker *make) { - - // ( 1 + 2 - 5 ) * (-4) / 4 - make.add(1).add(2).sub(5).muilt(-4).divide(4); - - }]; - - NSLog(@"%d", result); -``` - - - - -#### 函数式编程思想 - -**函数式编程思想**:是把操作尽量写成一系列嵌套的函数或者方法调用。 - -`特点`:每个方法必须有返回值(本身对象),把函数或者Block当做参数,block参数(需要操作的值)block返回值(操作结果) - -`代表`:**ReactiveCocoa** - -`实现`:用函数式编程实现,写一个加法计算器,并且加法计算器自带判断是否等于某个值. - -``` - Calculator *caculator = [[Calculator alloc] init]; - - BOOL isqule = [[[caculator caculator:^int(int result) { - - result += 2; - result *= 5; - return result; - - }] equle:^BOOL(int result) { - - return result == 10; - - }] isEqule]; - - NSLog(@"%d", isqule); -``` - -Calculator.h - -``` -#import - -@interface Calculator : NSObject - -@property (nonatomic, assign) BOOL isEqule; - -@property (nonatomic, assign) int result; - -- (Calculator *)caculator:(int (^)(int result))caculator; - -- (Calculator *)equle:(BOOL (^)(int result))operation; - -@end -``` - -Calculator.m - -``` -#import "Calculator.h" - -@implementation Calculator - -- (Calculator *)caculator:(int (^)(int))caculator { - - _result = caculator(_result); - - return self; - -} - - -- (Calculator *)equle:(BOOL (^)(int))operation { - - _isEqule = operation(_result); - - return self; -} - -@end -``` -**ReactiveCocoa** 结合了这两种种编程风格: - -- **函数式编程**(Functional Programming) - -- **响应式编程**(Reactive Programming) - -所以,你可能听说过 **ReactiveCocoa** 被描述为函数响应式编程(FRP)框架。 - -以后使用RAC解决问题,就不需要考虑调用顺序,直接考虑结果,把每一次操作都写成一系列嵌套的方法中,使代码高聚合,方便管理。 - -# 导入ReactiveCocoa ---- - - ->ReactiveCocoa的[GitHub地址](https://github.com/ReactiveCocoa/ReactiveCocoa) - -#### Objective-C - -**ReactiveCocoa 2.5**版本以后改用了**Swift**,所以**Objective-C**项目需要导入**2.5版本** - -`CocoaPods`集成: - -``` -platform :ios, '8.0' - -target 'YouProjectName' do - -use_frameworks! -pod 'ReactiveCocoa', '~> 2.5' - -end -``` -PS:新版本的`CocoaPods`需要加入 - -``` -target 'YouProjectName' do -... -end -``` -这句话来限定项目,否则导入失败。 - -#### Swift - -**Swift**项目导入2.5后的版本 - -``` -platform :ios, '8.0' - -target 'YouProjectName' do - -use_frameworks! -pod 'ReactiveCocoa' - -end - -``` -使用时在[全局头文件](http://www.jianshu.com/p/587b83b6665c)导入头文件即可 - -`PrefixHeader.pch` - -``` -#ifndef PrefixHeader_pch -#define PrefixHeader_pch - -#import - -#endif -``` - -# ReactiveCocoa常见类 - -#### RACSiganl 信号类 - ->信号类,一般表示将来有数据传递,只要有数据改变,信号内部接收到数据,就会马上发出数据。 - -注意: - -- 信号类(RACSiganl),只是表示当数据改变时,信号内部会发出数据,它本身不具备发送信号的能力,而是交给内部一个订阅者去发出。 -- 默认一个信号都是冷信号,也就是值改变了,也不会触发,只有订阅了这个信号,这个信号才会变为热信号,值改变了才会触发。 -- 如何订阅信号:调用信号RACSignal的subscribeNext就能订阅 - -使用: - -``` -// RACSignal使用步骤: - // 1.创建信号 + (RACSignal *)createSignal:(RACDisposable * (^)(id subscriber))didSubscribe - // 2.订阅信号,才会激活信号. - (RACDisposable *)subscribeNext:(void (^)(id x))nextBlock - // 3.发送信号 - (void)sendNext:(id)value - - - // RACSignal底层实现: - // 1.创建信号,首先把didSubscribe保存到信号中,还不会触发。 - // 2.当信号被订阅,也就是调用signal的subscribeNext:nextBlock - // 2.2 subscribeNext内部会创建订阅者subscriber,并且把nextBlock保存到subscriber中。 - // 2.1 subscribeNext内部会调用siganl的didSubscribe - // 3.siganl的didSubscribe中调用[subscriber sendNext:@1]; - // 3.1 sendNext底层其实就是执行subscriber的nextBlock - - // 1.创建信号 - RACSignal *siganl = [RACSignal createSignal:^RACDisposable *(id subscriber) { - - // block调用时刻:每当有订阅者订阅信号,就会调用block。 - - // 2.发送信号 - [subscriber sendNext:@1]; - - // 如果不在发送数据,最好发送信号完成,内部会自动调用[RACDisposable disposable]取消订阅信号。 - [subscriber sendCompleted]; - - return [RACDisposable disposableWithBlock:^{ - - // block调用时刻:当信号发送完成或者发送错误,就会自动执行这个block,取消订阅信号。 - - // 执行完Block后,当前信号就不在被订阅了。 - - NSLog(@"信号被销毁"); - - }]; - }]; - - // 3.订阅信号,才会激活信号. - [siganl subscribeNext:^(id x) { - // block调用时刻:每当有信号发出数据,就会调用block. - NSLog(@"接收到数据:%@",x); - }]; -``` - -#### RACSubscriber ->表示订阅者的意思,用于发送信号,这是一个协议,不是一个类,只要遵守这个协议,并且实现方法才能成为订阅者。通过create创建的信号,都有一个订阅者,帮助他发送数据。 - -#### RACDisposable - ->用于取消订阅或者清理资源,当信号发送完成或者发送错误的时候,就会自动触发它。 - -**使用场景**:不想监听某个信号时,可以通过它主动取消订阅信号。 - -#### RACSubject ->RACSubject:信号提供者,自己可以充当信号,又能发送信号。 - -**使用场景**:通常用来代替代理,有了它,就不必要定义代理了。 - -#### **RACReplaySubject** - ->重复提供信号类,RACSubject的子类。 - -`RACReplaySubject`与`RACSubject`区别: - - `RACReplaySubject`可以先发送信号,在订阅信号,`RACSubject`就不可以。 - - **使用场景一**:如果一个信号每被订阅一次,就需要把之前的值重复发送一遍,使用重复提供信号类。 - - **使用场景二**:可以设置capacity数量来限制缓存的value的数量,即只缓充最新的几个值。 - - **ACSubject** 和 **RACReplaySubject** 简单使用: - - **ACSubject** - - ``` - // RACSubject使用步骤 - // 1.创建信号 [RACSubject subject],跟RACSiganl不一样,创建信号时没有block。 - // 2.订阅信号 - (RACDisposable *)subscribeNext:(void (^)(id x))nextBlock - // 3.发送信号 sendNext:(id)value - - // RACSubject:底层实现和RACSignal不一样。 - // 1.调用subscribeNext订阅信号,只是把订阅者保存起来,并且订阅者的nextBlock已经赋值了。 - // 2.调用sendNext发送信号,遍历刚刚保存的所有订阅者,一个一个调用订阅者的nextBlock。 - - - // 1. 创建信号 - RACSubject *subject = [RACSubject subject]; - - // 2.订阅信号 - [subject subscribeNext:^(id x) { - - // block调用时机:当信号发出新值,就会调用 - NSLog(@"收到信号"); - - }]; - - // 3.发送信号 - NSLog(@"发送信号"); - [subject sendNext:@"1"]; - ``` - - ``` - // RACReplaySubject使用步骤: - // 1.创建信号 [RACSubject subject],跟RACSiganl不一样,创建信号时没有block。 - // 2.可以先订阅信号,也可以先发送信号。 - // 2.1 订阅信号 - (RACDisposable *)subscribeNext:(void (^)(id x))nextBlock - // 2.2 发送信号 sendNext:(id)value - - // RACReplaySubject:底层实现和RACSubject不一样。 - // 1.调用sendNext发送信号,把值保存起来,然后遍历刚刚保存的所有订阅者,一个一个调用订阅者的nextBlock。 - // 2.调用subscribeNext订阅信号,遍历保存的所有值,一个一个调用订阅者的nextBlock - - // 如果想当一个信号被订阅,就重复播放之前所有值,需要先发送信号,在订阅信号。 - // 也就是先保存值,在订阅值。 - - - // 1.创建信号 - RACReplaySubject *replaySubject = [RACReplaySubject subject]; - - - - // 3.先订阅信号 - [replaySubject subscribeNext:^(id x) { - - NSLog(@"第一个订阅者接受到的数据%@", x); - }]; - - // 2.发送信号 - [replaySubject sendNext:@1]; - [replaySubject sendNext:@2]; - - // 后订阅信号 - [replaySubject subscribeNext:^(id x) { - - NSLog(@"第二个订阅者接收到的数据%@",x); - }]; - ``` - -**RACSubject**替换代理(与block类似) - -``` -// 需求: - // 1.给当前控制器添加一个按钮,modal到另一个控制器界面 - // 2.另一个控制器view中有个按钮,点击按钮,通知当前控制器 - -步骤一:在第二个控制器.h,添加一个RACSubject代替代理。 -@interface TwoViewController : UIViewController - -@property (nonatomic, strong) RACSubject *delegateSignal; - -@end - -步骤二:监听第二个控制器按钮点击 -@implementation TwoViewController -- (IBAction)notice:(id)sender { - // 通知第一个控制器,告诉它,按钮被点了 - - // 通知代理 - // 判断代理信号是否有值 - if (self.delegateSignal) { - // 有值,才需要通知 - [self.delegateSignal sendNext:nil]; - } -} -@end - -步骤三:在第一个控制器中,监听跳转按钮,给第二个控制器的代理信号赋值,并且监听. -@implementation OneViewController -- (IBAction)btnClick:(id)sender { - - // 创建第二个控制器 - TwoViewController *twoVc = [[TwoViewController alloc] init]; - - // 设置代理信号 - twoVc.delegateSignal = [RACSubject subject]; - - // 订阅代理信号 - [twoVc.delegateSignal subscribeNext:^(id x) { - - NSLog(@"点击了通知按钮 %@", x); - }]; - - // 跳转到第二个控制器 - [self presentViewController:twoVc animated:YES completion:@"hi"]; - -} -@end -``` - -#### RACTuple - ->元组类,类似NSArray,用来包装值.(`@[key, value]`) - - -#### RACSequence - ->RAC中的集合类,用于代替NSArray,NSDictionary,可以使用它来快速遍历数组和字典。 - -使用场景:字典转模型 - -``` - // 1.遍历数组 - NSArray *numbers = @[@1,@2,@3,@4]; - - // 这里其实是三步 - // 第一步: 把数组转换成集合RACSequence numbers.rac_sequence - // 第二步: 把集合RACSequence转换RACSignal信号类,numbers.rac_sequence.signal - // 第三步: 订阅信号,激活信号,会自动把集合中的所有值,遍历出来。 - - [numbers.rac_sequence.signal subscribeNext:^(id x) { - - NSLog(@"%@", x); - }]; - - - - // 2.遍历字典,遍历出来的键值对 都会包装成 RACTuple(元组对象) @[key, value] - NSDictionary *dic = @{@"name": @"BYqiu", @"age": @18}; - - [dic.rac_sequence.signal subscribeNext:^(RACTuple *x) { - - // 解元组包,会把元组的值,按顺序给参数里的变量赋值 - // 写法相当与 - // NSString *key = x[0]; - // NSString *value = x[1]; - RACTupleUnpack(NSString *key, NSString *value) = x; - - NSLog(@"key:%@, value:%@", key, value); - - }]; - - // 3.字典转模型 - - NSString *filePath = [[NSBundle mainBundle] pathForResource:@"flags.plist" ofType:nil]; - - NSArray *dicArray = [NSArray arrayWithContentsOfFile:filePath]; - - NSMutableArray *items = [NSMutableArray array]; - - // OC写法 - for (NSDictionary *dic in dicArray) { - - //FlagItem *item = [FlagItem flagWithDict:dict]; - //[items addObject:item]; - } - - - // RAC写法 - [dicArray.rac_sequence.signal subscribeNext:^(id x) { - // 利用RAC遍历, x:字典 - - //FlagItem *item = [FlagItem flagWithDict:x]; - //[items addObject:item]; - }]; - - // RAC高级用法(函数式编程) - NSArray *flags = [[dicArray.rac_sequence map:^id(id value) { - - return [FlagItem flagWithDict:value]; - - }] array]; - -``` - -#### RACCommand - ->RAC中用于处理事件的类,可以把事件如何处理,事件中的数据如何传递,包装到这个类中,他可以很方便的监控事件的执行过程。 - - 一、RACCommand使用步骤: - - 1. 创建命令 initWithSignalBlock:(RACSignal * (^)(id input))signalBlock - 2. 在signalBlock中,创建RACSignal,并且作为signalBlock的返回值 - 3. 执行命令 - (RACSignal *)execute:(id)input - - 二、RACCommand使用注意: - - 1. signalBlock必须要返回一个信号,不能传nil. - 2. 如果不想要传递信号,直接创建空的信号[RACSignal empty]; - 3. RACCommand中信号如果数据传递完,必须调用[subscriber sendCompleted],这时命令才会执行完毕,否则永远处于执行中。 - 4. RACCommand需要被强引用,否则接收不到RACCommand中的信号,因此RACCommand中的信号是延迟发送的。 - - 三、RACCommand设计思想: - - 内部signalBlock为什么要返回一个信号,这个信号有什么用。 - - 1. 在RAC开发中,通常会把网络请求封装到RACCommand,直接执行某个RACCommand就能发送请求。 - 2. 当RACCommand内部请求到数据的时候,需要把请求的数据传递给外界,这时候就需要通过signalBlock返回的信号传递了。 - - 四、如何拿到RACCommand中返回信号发出的数据。 - - 1. RACCommand有个执行信号源executionSignals,这个是signal of signals(信号的信号),意思是信号发出的数据是信号,不是普通的类型。 - 2. 订阅executionSignals就能拿到RACCommand中返回的信号,然后订阅signalBlock返回的信号,就能获取发出的值。 - - 五、监听当前命令是否正在执行executing - - 六、使用场景,监听按钮点击,网络请求 - -使用: - -``` -// 1.创建命令 - RACCommand *command = [[RACCommand alloc] initWithSignalBlock:^RACSignal *(id input) { - NSLog(@"执行命令"); - - // 返回空信号 - //return [RACSignal empty]; - - // 2.创建信号 传递数据 - return [RACSignal createSignal:^RACDisposable *(id subscriber) { - - [subscriber sendNext:@"请求数据"]; - - // 注意:数据传递完,最好调用sendCompleted,这时命令才执行完毕 - [subscriber sendCompleted]; - - return nil; - }]; - }]; - - // 强引用命令,不要被销毁,否则接收不到数据 - _command = command; - - // 3.订阅RACCommand的信号 - [command.executionSignals subscribeNext:^(id x) { - [x subscribeNext:^(id x) { - - NSLog(@"订阅RACCommand的信号: %@", x); - }]; - }]; - - // RAC高级用法 - // switchToLatest:用于signal of signals,获取signal of signals发出的最新信号,也就是可以直接拿到RACCommand中的信号 - [command.executionSignals.switchToLatest subscribeNext:^(id x) { - - NSLog(@"RAC高级用法: %@", x); - }]; - - // 4.监听命令是否执行完毕,默认会来一次,可以直接跳过,skip表示跳过第一次信号。 - [[command.executing skip:1] subscribeNext:^(id x) { - - if ([x boolValue] == YES) { - - // 正在执行 - NSLog(@"正在执行"); - - } else { - - // 执行完毕 - NSLog(@"执行完成"); - } - }]; - - // 5.执行命名 - [self.command execute:@1]; -``` - -#### RACMulticastConnection ->用于当一个信号,被多次订阅时,为了保证创建信号时,避免多次调用创建信号中的block,造成副作用,可以使用这个类处理。 - -注意:RACMulticastConnection通过RACSignal的-publish或者-muticast:方法创建. - - - -RACMulticastConnection使用步骤: - -1. 创建信号 + (RACSignal *)createSignal:(RACDisposable * (^)(id subscriber))didSubscribe -2. 创建连接 RACMulticastConnection *connect = [signal publish]; -3. 订阅信号,注意:订阅的不在是之前的信号,而是连接的信号。 [connect.signal subscribeNext:nextBlock] -4. 连接 [connect connect] - -RACMulticastConnection底层原理: - -1. 创建connect,connect.sourceSignal -> RACSignal(原始信号) connect.signal -> RACSubject -2. 订阅connect.signal,会调用RACSubject的subscribeNext,创建订阅者,而且把订阅者保存起来,不会执行block。 -3. [connect connect]内部会订阅RACSignal(原始信号),并且订阅者是RACSubject - 1. 订阅原始信号,就会调用原始信号中的didSubscribe - 2. didSubscribe,拿到订阅者调用sendNext,其实是调用RACSubject的sendNext -4. RACSubject的sendNext,会遍历RACSubject所有订阅者发送信号。 - - 因为刚刚第二步,都是在订阅RACSubject,因此会拿到第二步所有的订阅者,调用他们的nextBlock - - -需求:假设在一个信号中发送请求,每次订阅一次都会发送请求,这样就会导致多次请求。 - -解决:使用RACMulticastConnection就能解决. - - -问题:每次订阅一次都会发送请求 - -``` -// 创建请求信号 -RACSignal *signal = [RACSignal createSignal:^RACDisposable *(id subscriber) { - - NSLog(@"发送请求"); - [subscriber sendNext:@1]; - - return nil; -}]; - -// 订阅信号 -[signal subscribeNext:^(id x) { - - NSLog(@"接受数据: %@", x); -}]; - -// 再次订阅信号,会再次执行发送请求,也就是每次订阅都会发送一次请求 -[signal subscribeNext:^(id x) { - - NSLog(@"接受数据: %@", x); -}]; -``` - -输出: - -``` -2016-12-28 11:37:04.397 ReactiveCacoa[1505:340573] 发送请求 -2016-12-28 11:37:04.398 ReactiveCacoa[1505:340573] 接受数据: 1 -2016-12-28 11:37:04.398 ReactiveCacoa[1505:340573] 发送请求 -2016-12-28 11:37:04.398 ReactiveCacoa[1505:340573] 接受数据: 1 -``` -可以发现每次订阅都会重新发送请求. - -下面我们使用RACMulticastConnection: - -``` -RACSignal *signal = [RACSignal createSignal:^RACDisposable *(id subscriber) { - - NSLog(@"发送请求"); - [subscriber sendNext:@1]; - - return nil; -}]; - -// 创建连接 -RACMulticastConnection *connect = [signal publish]; - -// 订阅信号 -// 注意:订阅信号,也不能激活信号,只是保存订阅者到数组,必须通过连接,当调用连接,就会一次性调用所有订阅者的SendNext -[connect.signal subscribeNext:^(id x) { - - NSLog(@"订阅者1信号: %@", x); -}]; - -[connect.signal subscribeNext:^(id x) { - - NSLog(@"订阅者2信号: %@", x); -}]; - -// 连接、激活信号 -[connect connect]; - -``` - -输出: - -``` -2016-12-28 11:37:04.399 ReactiveCacoa[1505:340573] 发送请求 -2016-12-28 11:37:04.399 ReactiveCacoa[1505:340573] 订阅者1信号: 1 -2016-12-28 11:37:04.399 ReactiveCacoa[1505:340573] 订阅者2信号: 1 -``` -#### RACScheduler ->RAC中的队列,用GCD封装的。 - -#### RACUnit ->表⽰stream不包含有意义的值,也就是看到这个,可以直接理解为nil. - -#### RACEven ->把数据包装成信号事件(signal event)。它主要通过RACSignal的-materialize来使用,然并卵。 - - -# ReactiveCocoa开发中常见用法 - -1. 替换代理 -2. 替换KVO -3. 监听事件 -4. 替换通知 -5. 监听文本框文字改变 -6. 统一处理多个网络请求 - - - -#### 替换代理: - -**rac_signalForSelector:** - -`rac_signalForSelector:` 直接监听 `Selector` 事件的调用 - -应用场景:监听 `RedViewController` 中按钮的点击事件 `btnTap:` - -跳转到`RedViewController`前,先使用`rac_signalForSelector`订阅rvc中的 `btnTap:` 点击事件 - -``` -// 使用segue跳转 -- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender { -- - if ([segue.identifier isEqualToString:@"goRedVC"]) { - - RedViewController *rvc = segue.destinationViewController; - - // 订阅rvc中的 btnTap: 点击事件 - [[rvc rac_signalForSelector:@selector(btnTap:)] subscribeNext:^(id x) { - - NSLog(@"RedVC btnTap!"); - }]; - } -} -``` - -`RedViewController.m` 中的按钮事件 - -``` -- (IBAction)btnTap:(id)sender { - - NSLog(@"!"); -} -``` - -#### 替换KVO - -**rac_valuesForKeyPath:** - -``` -// KVO -// 监听 slider 的 value 变化 -[[self.slider rac_valuesForKeyPath:@"value" observer:nil] subscribeNext:^(id x) { - - NSLog(@"slider value Change:%@", x); -}]; -``` - -#### 替换通知 - -**rac_addObserverForName** - -``` -// 原生的订阅通知 -[[NSNotificationCenter defaultCenter] addObserver:self - selector:@selector(userDidChange:) - name:kTTCurrentUserLoggedOffNotification - object:nil]; - -// 使用RAC订阅通知 ,takeUntil限定信号的声明周期 -[[[[NSNotificationCenter defaultCenter] rac_addObserverForName:UIApplicationDidEnterBackgroundNotification object:nil] - takeUntil:[self rac_willDeallocSignal]] - subscribeNext:^(id x) { - NSLog(@"Notification received"); -}]; -``` - -#### 监听事件 - -**rac_signalForControlEvents:** - -``` -// 监听 btn 的 UIControlEventTouchUpInside 点击事件 -[[self.btn rac_signalForControlEvents:UIControlEventTouchUpInside] subscribeNext:^(id x) { - - NSLog(@"btnTap"); -}]; -``` - - -#### 监听 textField 文字变化 - -**rac_textSignal** - -``` -[[self.textField rac_textSignal] subscribeNext:^(id x) { - - NSLog(@"textField change: %@", x); -}]; -``` - -#### 统一处理多个网络请求 - -**rac_liftSelector:** - -``` -- (void)viewDidLoad { - [super viewDidLoad]; - - // 处理多个请求都返回结果的时候,统一处理 - // 如同时进行多个网络请求,每个请求都正确返回时,再去刷新页面 - - RACSignal *signalOne = [RACSignal createSignal:^RACDisposable *(id subscriber) { - - // 网络请求1 - // ... - - // 返回成功 - [subscriber sendNext:@"网络请求1 data"]; - - return nil; - }]; - - RACSignal *signalTwo = [RACSignal createSignal:^RACDisposable *(id subscriber) { - - // 网络请求2 - // ... - - // 返回成功 - [subscriber sendNext:@"网络请求2 data"]; - - return nil; - }]; - - [self rac_liftSelector:@selector(updateWithR1:R2:) withSignalsFromArray:@[signalOne, signalTwo]]; - -} - -// 更新界面 -- (void)updateWithR1:(id)r1 R2:(id)r2 { - - NSLog(@"R1:%@, R2:%@ 完成!", r1, r2); - -} -``` - - -#### **注意**: - -- `替换KVO`和 `监听文本框文字改变` 方法在创建监听方法时就会执行一次。 - - ``` -2016-12-28 16:53:50.746 ReactiveCacoa[4956:1246592] slider value Change:0.5 -2016-12-28 16:53:50.748 ReactiveCacoa[4956:1246592] textField change: -``` - -- 使用`rac_liftSelector`时 `@selector(updateWithR1:R2:) `中的方 **参数个数** 要与 **signal个数** 相同,否则会被断言Crash! - diff --git a/_posts/2017-02-04-Hello-2017.md b/_posts/2017-02-04-Hello-2017.md deleted file mode 100644 index 0cd086c6086..00000000000 --- a/_posts/2017-02-04-Hello-2017.md +++ /dev/null @@ -1,65 +0,0 @@ ---- -layout: post -title: Hello 2017 -subtitle: "\"Hello World, Hello Blog\"" -date: 2017-02-04 -author: BY -header-img: img/post-bg-2015.jpg -catalog: true -tags: - - 生活 ---- - -> “🙉🙉🙉 ” - - -## 前言 - -BY 的 Blog 就这么开通了。 - -本来打算在年前完成 Blog 的搭建,不曾料想踩了很多坑。。。 - -[跳过废话,直接看技术实现 ](#build) - -2017 年,BY Blog 总算是搭建好了。 - -最开始写博客是在[简书](www.jianshu.com)这个平台上,简书确实不错,支持markdown在线编辑。 - -在一次偶然间,听到我的好基友 **阳阳** 想搭建个人主页,觉得作为一个程序员,是应该倒腾倒腾自己的Blog,于是乎就开始了撸起袖子干了。 - -

---- - -## 正文 - -接下来说说搭建这个博客的技术细节。 - -正好之前就有关注过 [GitHub Pages](https://pages.github.com/) + [Jekyll](http://jekyllrb.com/) 快速 Building Blog 的技术方案,非常轻松时尚。 - -其优点非常明显: - -* **Markdown** 带来的优雅写作体验 -* 非常熟悉的 Git workflow ,**Git Commit 即 Blog Post** -* 利用 GitHub Pages 的域名和免费无限空间,不用自己折腾主机 - * 如果需要自定义域名,也只需要简单改改 DNS 加个 CNAME 就好了 -* Jekyll 的自定制非常容易,基本就是个模版引擎 - - - ---- - - -主题我直接 Downlosd 了 [Hux的博客主题](https://huangxuan.me/) 的进行修改,简单粗暴,不过遇到了很多坑😂,好在都填完了。。。 - -本地调试环境需要 `gem install jekyll`,结果 rubygem 的源居然被墙了,~~后来手动改成了我大淘宝的镜像源才成功~~,淘宝的源已经[停止维护](https://gems.ruby-china.org/),换成了OSChina的源 `https://gems.ruby-china.org/`。 - - -## 后记 - -最后,感谢 Hux 提供的的 [Blog 主题](https://github.com/Huxpro/huxpro.github.io) - -如果你恰好逛到了这里,希望你也能喜欢这个博客主题,感兴趣的话可以自己动手搭建一个。 - -—— BY 后记于 2017.2 - - diff --git "a/_posts/2017-02-06-\345\277\253\351\200\237\346\220\255\345\273\272\344\270\252\344\272\272\345\215\232\345\256\242.md" "b/_posts/2017-02-06-\345\277\253\351\200\237\346\220\255\345\273\272\344\270\252\344\272\272\345\215\232\345\256\242.md" deleted file mode 100644 index d94ec002759..00000000000 --- "a/_posts/2017-02-06-\345\277\253\351\200\237\346\220\255\345\273\272\344\270\252\344\272\272\345\215\232\345\256\242.md" +++ /dev/null @@ -1,731 +0,0 @@ ---- -layout: post -title: 快速搭建个人博客 -subtitle: 手把手教你在半小时内搭建自己的个人博客(如果不踩坑的话🙈🙊🙉) -date: 2017-02-06 -author: BY -header-img: img/post-bg-re-vs-ng2.jpg -catalog: true -tags: - - Blog ---- - -> 正所谓前人栽树,后人乘凉。 -> -> 感谢[Huxpro](https://github.com/huxpro)提供的博客模板 -> -> [我的的博客](http://qiubaiying.top) - -# 前言 -从 Jekyll 到 GitHub Pages 中间踩了许多坑,终于把我的个人博客[BY Blog](http://qiubaiying.top)搭建出来了。。。 - -本教程针对的是不懂技术又想搭建个人博客的小白,操作简单暴力且快速。当然懂技术那就更好了。 - -看看看博客的主页样式: - -[![](http://upload-images.jianshu.io/upload_images/2178672-51a2fe6fbe24d1cd.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)](http://qiubaiying.github.io/) - -在手机上的布局: - -[![](http://upload-images.jianshu.io/upload_images/2178672-d58bb45f9faedb70.jpg)](http://qiubaiying.github.io/) - -废话不多说了,开始进入正文。 - -# 快速开始 - -### 从注册一个Github账号开始 - -我采用的搭建博客的方式是使用 [GitHub Pages](https://pages.github.com/) + [jekyll](http://jekyll.com.cn/) 的方式。 - -要使用 GitHub Pages,首先你要注册一个[GitHub](https://github.com/)账号,GitHub 是全球最大的同性交友网站(吐槽下程序员~),你值得拥有。 - -![](http://upload-images.jianshu.io/upload_images/2178672-e65e5cda50f38cef.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) - -### 拉取我的博客模板 - -注册完成后搜索 `qiubaiying.github.io` 进入[我的仓库](https://github.com/qiubaiying/qiubaiying.github.io) - - -![](http://upload-images.jianshu.io/upload_images/2178672-1b234fb8549e58aa.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) - -点击右上角的 **Fork** 将我的仓库拉倒你的账号下 - -稍等一下,点击刷新,你会看到**Fork**了成功的页面 - -![](http://upload-images.jianshu.io/upload_images/2178672-b2347768a1f2d993.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) - - -### 修改仓库名 - -点击**settings**进入设置 - -![](http://upload-images.jianshu.io/upload_images/2178672-f47b7e4802de6a34.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) - -

-修改仓库名为 `你的Github账号名.github.io`,然后 Rename - -![](http://upload-images.jianshu.io/upload_images/2178672-ca3d843e526cdd5b.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) - -这时你在在浏览器中输入 `你的Github账号名.github.io` 例如:`baiyingqiu.github.io` - -你将会看到如下界面 - -![](http://upload-images.jianshu.io/upload_images/2178672-96b5db55df9db422.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) - -说明已经成功一半了😀。。。当然,还需要修改博客的配置才能变成你的博客。 - -若是出现 - -![](http://upload-images.jianshu.io/upload_images/2178672-cfd55a22902a9d2c.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) - -则需要 [检查一下你的仓库名是否正确](#Rename) - -### 整个网站结构 - -修改Blog前我们来看看Jekyll 网站的基础结构,当然我们的网站比这个复杂。 - -``` -├── _config.yml -├── _drafts -| ├── begin-with-the-crazy-ideas.textile -| └── on-simplicity-in-technology.markdown -├── _includes -| ├── footer.html -| └── header.html -├── _layouts -| ├── default.html -| └── post.html -├── _posts -| ├── 2007-10-29-why-every-programmer-should-play-nethack.textile -| └── 2009-04-26-barcamp-boston-4-roundup.textile -├── _data -| └── members.yml -├── _site -├── img -└── index.html -``` - -很复杂看不懂是不是,不要紧,你只要记住其中几个OK了 - -- `_config.yml` 全局配置文件 -- `_posts` 放置博客文章的文件夹 -- `img` 存放图片的文件夹 - -其他的想继续深究可以[看这里](http://jekyll.com.cn/docs/structure/) - - - -### 修改博客配置 - -来到你的仓库,找到`_config.yml`文件,这是网站的全局配置文件。 - -![](http://upload-images.jianshu.io/upload_images/2178672-c23d4a5d67c88084.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) - -点击修改 - -![](http://upload-images.jianshu.io/upload_images/2178672-b37268df7a7852ca.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) - -然后编辑`_config.yml`的内容 - -![](http://upload-images.jianshu.io/upload_images/2178672-0c8750f5a18dbe03.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) - -接下来我们来详细说说以下配置文件的内容: - -#### 基础设置 - -``` -# Site settings -title: You Blog #你博客的标题 -SEOTitle: 你的博客 | You Blog #显示在浏览器上搜索的时候显示的标题 -header-img: img/post-bg-rwd.jpg #显示在首页的背景图片 -email: You@gmail.com -description: "You Blog" #网站介绍 -keyword: "BY, BY Blog, 柏荧的博客, qiubaiying, 邱柏荧, iOS, Apple, iPhone" #关键词 -url: "https://qiubaiying.github.io" # 这个就是填写你的博客地址 -baseurl: "" # 这个我们不用填写 - -``` -#### 侧边栏 - -``` -# Sidebar settings -sidebar: true # 是否开启侧边栏. -sidebar-about-description: "说点装逼的话。。。" -sidebar-avatar:/img/avatar-by.JPG # 你的个人头像 这里你可以改成我在img文件夹中的两张备用照片 img/avatar-m 或 avatar-g -``` -#### 社交账号 -展示你的其他社交平台 - -![](http://upload-images.jianshu.io/upload_images/2178672-ec775a22f76e2f40.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) - -在下面你的社交账号的用户名就可以了,若没有可不用填 - -``` -# SNS settings -RSS: false -weibo_username: username -zhihu_username: username -github_username: username -facebook_username: username -jianshu_username: jianshu_id -``` - -新加入了**简书**,`jianshu_id` 在你打开你的简书主页后的地址如:`http://www.jianshu.com/u/e71990ada2fd`中,后面这一串数字:`e71990ada2fd ` - -#### 评论系统 - - -博客中使用的是 [Disqus](https://disqus.com/) 评论系统,在 [官网](https://disqus.com/) 注册帐号后,按下面的步骤简单的配置即可: - -进入 [设置页面](https://disqus.com/home/settings/profile/) 配置个人信息 - -![配置 Disqus 个人信息](http://upload-images.jianshu.io/upload_images/2178672-904ecb30c536c73b.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) - -找到 **Username** - -![Disqus Account](http://upload-images.jianshu.io/upload_images/2178672-19d1b9e7d2624bfb.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) - -这个 **Username** 就是我们 `_config.yml` 中 `disqus_username` - -``` -# Disqus settings(https://disqus.com/) -disqus_username: qiubaiying -``` - -> 很对人反映 Disqus 评论插件加载不出来,因为 Disqus 在国内加载缓慢,所以我新集成了 Gitalk 评论插件(感谢[@FeDemo](https://github.com/FeDemo)的推荐),喜欢折腾的朋友可以看这篇:[《为博客添加 Gitalk 评论插件》](http://qiubaiying.top/2017/12/19/%E4%B8%BA%E5%8D%9A%E5%AE%A2%E6%B7%BB%E5%8A%A0-Gitalk-%E8%AF%84%E8%AE%BA%E6%8F%92%E4%BB%B6/)。 我已经在`_config.yml` 配置就好了,只需要填写参数可以了。 - -#### 网站统计 - -集成了 [Baidu Analytics](http://tongji.baidu.com/web/welcome/login) 和 [Google Analytics](http://www.google.cn/analytics/),到各个网站注册拿到track_id替换下面的就可以了 - -这是我的 Google Analytics - -![](http://upload-images.jianshu.io/upload_images/2178672-c36b895c53196fdb.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) - -**不要使用我的track_id**😂。。。 - -若不想启用统计,直接删除或注释掉就可以了 - -``` -# Analytics settings -# Baidu Analytics -ba_track_id: 83e259f69b37d02a4633a2b7d960139c - -# Google Analytics -ga_track_id: 'UA-90855596-1' # Format: UA-xxxxxx-xx -ga_domain: auto -``` - -#### 好友 - -``` -friends: [ - { - title: "简书·BY", - href: "http://www.jianshu.com/u/e71990ada2fd" - },{ - title: "Apple", - href: "https://apple.com" - },{ - title: "Apple Developer", - href: "https://developer.apple.com/" - } -] -``` - -#### 保存 -讲网页拉倒底部,点击 `Commit changes` 提交保存 - -![](http://upload-images.jianshu.io/upload_images/2178672-0781006b5d15d149.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) - -再次进入你的主页, - -![](http://upload-images.jianshu.io/upload_images/2178672-a49ee2975d524c93.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) - -恭喜你,你的个人博客搭建完成了😀。 - -# 写文章 - -利用 Github网站 ,我们可以不用学习[git](https://git-scm.com/),就可以轻松管理自己的博客 - -对于轻车熟路的程序猿来说,使用git管理会更加方便。。。 - -## 创建 -文章统一放在网站根目录下的 `_posts` 的文件夹中。 - -![](http://upload-images.jianshu.io/upload_images/2178672-fb74cdc11a950bd4.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) - -创建一个文件 - -![](http://upload-images.jianshu.io/upload_images/2178672-9a47b2074362e570.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) - -在下面写文章,和标题,还能实时预览,最后提交保存就能看到自己的新文章了。 - -![](http://upload-images.jianshu.io/upload_images/2178672-88acd9e29fa3ae8a.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) - - -## 格式 -每一篇文章文件命名采用的是`2017-02-04-Hello-2017.md`时间+标题的形式,空格用`-`替换连接。 - -文件的格式是 `.md` 的 [**MarkDown**](http://sspai.com/25137/) 文件。 - -我们的博客文章格式采用是 **MarkDown**+ **YAML** 的方式。 - -[**YAML**](http://www.ruanyifeng.com/blog/2016/07/yaml.html?f=tt) 就是我们配置 `_config`文件用的语言。 - -[**MarkDown**](http://sspai.com/25137/) 是一种轻量级的「标记语言」,很简单。[花半个小时看一下](http://sspai.com/25137)就能熟练使用了 - -大概就是这么一个结构。 - -``` ---- -layout: post # 使用的布局(不需要改) -title: My First Post # 标题 -subtitle: Hello World, Hello Blog #副标题 -date:       2017-02-06 # 时间 -author: BY # 作者 -header-img: img/post-bg-2015.jpg #这篇文章标题背景图片 -catalog: true # 是否归档 -tags: #标签 - - 生活 ---- - -## Hey ->这是我的第一篇博客。 - -进入你的博客主页,新的文章将会出现在你的主页上. -``` - -按格式创建文章后,提交保存。进入你的博客主页,新的文章将会出现在你的主页上. - -![](http://upload-images.jianshu.io/upload_images/2178672-f4d5bb65ae3abd00.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) - -到这里,恭喜你! - -你已经成功搭建了自己的个人博客以及学会在博客上撰写文字的技能了(是不是有点小兴奋🙈)。 - - -#### 首页标签 - -在首页可以看到这些特色标签,当你的文章出现相同标签(默认相同的**标签数量大于1**),才会自动生成。 - -所以当你只放一篇文章的时候是不会出现标签的。 - - - -![](http://upload-images.jianshu.io/upload_images/2178672-9281b7176c456f92.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) - - -建站的初期,博客比较少,若你想直接在首页生成比较多的标签。你可以在 `_congfig.yml`中找到这段: - -``` -# Featured Tags -featured-tags: true # 是否使用首页标签 -featured-condition-size: 1 # 相同标签数量大于这个数,才会出现在首页 -``` - -将其修改为`featured-condition-size: 0`, 这样只有一个标签时也会出现在首页了。 - -相反,当你博客比较多,标签也很多时,这时你就需要改回 `1` 甚至是 `2` 了。 - - -# 自定义域名 - -搭建好博客之后 你可能不想直接使用 [baiyingqiu.github.io](http://baiyingqiu.github.io) 这么长的博客域名吧, 想换成想 [qiubaiying.top](http://qiubaiying.top) 这样简短的域名。那我们开始吧! - -#### 购买域名 -首先,你必须购买一个自己的域名。 - -我是在[阿里云](https://wanwang.aliyun.com/domain/?spm=5176.8006371.1007.dnetcndomain.q1ys4x)购买的域名 - -![](http://upload-images.jianshu.io/upload_images/2178672-ef3844cab15e35ff.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) - -用**阿里云** app也可以注册域名,域名的价格根据后缀的不同和域名的长度而分,比如我这个 `qiubaiying.top` 的域名第一年才只要4元~ - -域名尽量选择短一点比较好记住,注意,不能选择中文域名,比如 `张三.top` ,GitHub Pages **无法处理中文域名**,会导致你的域名在你的主页上使用。 - -注册的步骤就不在介绍了 - -#### 解析域名 - -注册好域名后,需要将域名解析到你的博客上 - -管理控制台 → 域名与网站(万网) → 域名 - -![](http://upload-images.jianshu.io/upload_images/2178672-9a75bba50d1b14d7.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) - -选择你注册好的域名,点击解析 - -![](http://upload-images.jianshu.io/upload_images/2178672-0968a8dd2045f4fd.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) - -添加解析 - -分别添加两个`A` 记录类型, - -一个主机记录为 `www`,代表可以解析 `www.qiubaiying.top`的域名 - -另一个为 `@`, 代表 `qiubaiying.top` - -记录值就是我们博客的IP地址,是 GitHub Pagas 在美国的服务器的地址 `151.101.100.133` - -![](http://upload-images.jianshu.io/upload_images/2178672-0769a93bc487e9d8.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) - - -可以通过 [这个网站](http://ip.chinaz.com/) 或者直接在终端输入`ping 你的地址`,查看博客的IP - - ping qiubaiying.github.io - -细心地你会发现所有人的博客都解析到 `151.101.100.133` 这个IP。 - -然后 GitHub Pages 再通过 CNAME记录 跳转到你的主页上。 - - -#### 修改CNAME - -最后一步,只需要修改 我们github仓库下的 **CNAME** 文件。 - -选择 **CNAME** 文件 - -![](http://upload-images.jianshu.io/upload_images/2178672-a422f3dab436dfb7.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) - -使用的注册的域名进行替换,然后提交保存 - -![](http://upload-images.jianshu.io/upload_images/2178672-6e613004fb410b44.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) - - -这时,输入你自己的域名,就可以解析到你的主页了。 - -大功告成! - -# 进阶 - -若你对博客模板进行修改,你就要看看 Jekyll 的[开发文档](http://jekyll.com.cn),是中文文档哦,对英语一般的朋友简直是福利啊(比如说我😀)。 - -还要学习 **Git** 和 **GitHub** 的工作机制了及使用。 - -你可以先看看这个[git教程](http://www.liaoxuefeng.com/wiki/0013739516305929606dd18361248578c67b8067c8c017b000/),对git有个初步的了解后,那么相信你就能将自己图片传到GitHub仓库上,或者可以说掌握了 **使用git管理自己的GitHub仓库** 的技能呢。 - -对于轻车熟路的程序猿来说,这篇教程就算就结束了,因为下面的内容对于你们来说 so eazy~ - -但相信很多小白都一脸懵逼,那我们继续👇。 - -# 利用GithHub Desktop管理GitHub仓库 - -[GithHub Desktop](https://desktop.github.com/) 是 **GithHub** 推出的一款管理GitHub仓库的桌面软件,换句话说就是将你在**Github**上的文件同步到本地电脑上,并将修改后的文件同步到**Github**远程仓库。 - -#### 下载 - -点击图片进入下载页面,选择对应的平台进行下载 - -[![](http://upload-images.jianshu.io/upload_images/2178672-6022ba3938b3088e.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)](https://desktop.github.com/) - -下面以**Mac**平台为例: - -#### 安装 - -将下载好的文件解压,将这只小猫拖到应用程序文件夹中 - -![](http://upload-images.jianshu.io/upload_images/2178672-8f8c27f4e5c72276.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) - -就可以在**Launchpad**找到这只小猫咪~ - -![](http://upload-images.jianshu.io/upload_images/2178672-0f2da4717361459c.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) - -#### 登录 - -点开应用,会弹出**登录**框, - -![](http://upload-images.jianshu.io/upload_images/2178672-adb7d6824e471ef5.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) - -输入你的**GitHub**账号和密码进行登录 - -![](http://upload-images.jianshu.io/upload_images/2178672-2d7c407ebddbb44f.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) - -登录后关闭窗口 - -![](http://upload-images.jianshu.io/upload_images/2178672-93cdccc42024914b.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) - -然后返回引导窗,一直按 **Continue** 继续 - -![](http://upload-images.jianshu.io/upload_images/2178672-450ccef6b1ab7b0a.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) - -**Continue** - -![](http://upload-images.jianshu.io/upload_images/2178672-06b6e6792472ecae.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) - -还是**Continue**~ -![](http://upload-images.jianshu.io/upload_images/2178672-681a6c455f6b512f.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) - -进入主界面,先 **右键Remve** 删除这个用户指导,贼烦~ - -![](http://upload-images.jianshu.io/upload_images/2178672-604f6f23b8fab6f3.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) - -#### 克隆仓库 - -选择你的仓库克隆到本地 - -![](http://upload-images.jianshu.io/upload_images/2178672-45ddcd27e2f858a1.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) - -![](http://upload-images.jianshu.io/upload_images/2178672-625be1220fea36b6.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) - - -#### 管理仓库 - -现在文件夹中打开 - -![](http://upload-images.jianshu.io/upload_images/2178672-92c1616af56b501a.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) - -打开后你会的发现文件结构和你在Github上的一模一样~ - -![](http://upload-images.jianshu.io/upload_images/2178672-bf3580ae1cd9a29e.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) - -你最先关心的可能是你的头像~在**img**文件夹中把替换我的头像就好了。 - -![](http://upload-images.jianshu.io/upload_images/2178672-c9421d64538c3ba6.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) - -不仅是图片,所有在Github上的的操作都可以进行。 - -#### 保存修改 - -当你对仓库文件夹的文件下进行修改、添加或删除时,都可以在 **GitHub Desktop** 中看到 - -例如我在 `img` 中添加了一张图片 `avatar-demo.png` 添加了一张图片 - -就可以在看到**GitHub Desktop**显示了我的修改 - -保存修改只要按 **Commit to master**,然后可以写上你的修改说明 - -![](http://upload-images.jianshu.io/upload_images/2178672-4bfbfec37cbb8eb6.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) - -#### 同步 - -将修改同步到 **GitHub** 远程仓库上只需要一步:点击右上角的**同步按钮** - -![](http://upload-images.jianshu.io/upload_images/2178672-3c2ee8234a7f1832.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) - -#### 完成 - -打开你的GitHub上的仓库,你就可以看到已经和本地同步了 - -可以看到你提交的详情: `add img` - -![](http://upload-images.jianshu.io/upload_images/2178672-293bdd4cbee0e9e3.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) - -这样,你已经能轻松管理自己的博客了。 - -想上传头像,背景,或者是删掉你不要的图片(我的头像😏)已经是 so eazy了吧~ - -#### 注意 -你在 **GitHub** 网站上进行 **Commit** 操作后,需要在**GitHub Desktop**上按一下 **同步按键** 才能同步网站上的修改到你的本地。 - - -# 修改个人介绍 - - -![](https://ws4.sinaimg.cn/large/006tNc79gy1fme0poz7gqj30vq0l8whh.jpg) - -修改个人介绍需要修改根目录下的 `about.html` 文件 - -![](https://ws2.sinaimg.cn/large/006tNc79gy1fme0rna33tj30bw0bntah.jpg) - -看不懂 HTML 标签?没关系,对照着修改就好了~ 还有注意这个有中英介绍 - -![](https://ws1.sinaimg.cn/large/006tNc79gy1fme0sbvmmcj30zp0os7ap.jpg) - -# 常见问题 - -最近有很多人给我提问题,我这边总结一下 - -#### 配置文件修改后没有效果 -刷新几遍浏览器就好了~ - -不行的话,先清除浏览器缓存再试试。 - -#### 404错误 - -1. 检查你的仓库名是否有按照要求填写 -2. 确定 **Fork** 的是不是我的仓库~ - -#### 修改CNAME文件,域名还是不变 - -清除浏览器缓存就OK~ - -#### 其他问题 - -直接在评论中提出来或私信我,我会一一替大家解决的😀 - - -# 其他 - -最近有人往我的远程仓库不停的 **push**,一天连收几十封邮件!例如像这样的 - -![](http://upload-images.jianshu.io/upload_images/2178672-1347f2cc9a4a8dc8.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) - -原因大多是直接Clone了我的仓库到本地,**没有删除我的远程仓库地址**,添加完自己的仓库地址后,一口气推送到所有远程仓库(包括我的😂)~ - -打扰了我的工作和生活~ - -所以,**请不要往我的仓库上推送分支**! - -我发现一个问题是,很多人每次修改博客的内容都commit一次到远程仓库,然后再查看修改结果,这样效率非常低! - -#### 来,上车! - -## 在本地调试博客 - -> 注:下面的操作是在 **Mac** 终端进行的。 -> **Windows** 环境下的配置请参考 [@梦幻之云](http://www.jianshu.com/u/a13e7484dc21) 提供的 [这篇文章](https://agcaiyun.cn/2017/09/10/%E6%90%AD%E5%BB%BA%E4%B8%AA%E4%BA%BA%E5%8D%9A%E5%AE%A2/)。 - -有心的同学在 [jekyll官网](http://jekyllcn.com/) 就会发现 `jekyll` 的 提供的实例代码。 - -``` -~ $ gem install jekyll bundler -~ $ jekyll new my-awesome-site -~ $ cd my-awesome-site -~/my-awesome-site $ bundle install -~/my-awesome-site $ bundle exec jekyll serve -# => 打开浏览器 http://localhost:4000 -``` - - -这段命令创建了一个默认的 `jekll` 网站,然后在本机的 4000 窗口展示。聪明的你应该发现怎么做了吧~ - -安装 `jekyll`和 `jekyll bundler` - -``` -$ gem install jekyll -$ gem install jekyll bundler -``` - -进入你的 **Blog 所在目录**,然后创建本地服务器 - -``` -$ jekyll s - -``` - -然后会显示 - -``` - Auto-regeneration: enabled for '/Users/baiying/Blog' -Configuration file: /Users/baiying/Blog/_config.yml - Server address: http://127.0.0.1:4000/ - Server running... press ctrl-c to stop. -``` - -你就可以在 看到你的博客,你对本地博客的修改都会在这个地址进行显示,这大大提高了对博客的配置效率。 - -使用`ctrl+c`就可以停止 **serve** - -# Star - -若本教程顺利帮你搭建了自己的个人博客,请不要 **害羞**,给我的 [github仓库](https://github.com/qiubaiying/qiubaiying.github.io) 点个 **star** 吧! - -因为最近发现 Fork 将近破百,加上直接 Clone 仓库的,保守估计已经帮助上百人成功的搭建了自己的博客,~~可是 Star 却仅仅只有 **12**!可能还是做的不够好吧!~~现在已经破百了,感谢大家的Star! - -### **别无他求,点个 [Star](https://github.com/qiubaiying/qiubaiying.github.io) 吧**! - -![](http://upload-images.jianshu.io/upload_images/2178672-768a38ee9fb0df28.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) - -**心满意足!** - -# 补充 - -#### 修改网站的 **icon** - -![](https://ws2.sinaimg.cn/large/006tNc79gy1flgh6k23ppj30ad00uq2t.jpg) - -要修改如图所示的网站 **icon**: - -在博客 `img` 目录下找到并替换 `favicon.ico` 这个图标即可,图标尺寸为`32x32`。 - - -![](https://ws2.sinaimg.cn/large/006tNc79gy1flghahch1oj30gu09y419.jpg) - - -#### 修改主页的座右铭 - -最近有不少小伙伴私信我:**如何修改主页的座右铭?** - -就是这个: - -![](http://upload-images.jianshu.io/upload_images/2178672-31dc0068f256aca3.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) - -很简单,找到博客目录下的 **index.html** 文件,修改这句话就可以了。 - -![](http://upload-images.jianshu.io/upload_images/2178672-9e4785654523bf07.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) - -#### 如何在博客文章中上插入图片 - -博客的文章用的是 MarkDown 格式,如果没用过 MarkDown 真的 强烈推荐 [花半个小时学习一下](http://sspai.com/25137)。 - -MarkDown 中添加图片的形式是 :`![](图片的URL)` - -例如: - -`![MarkDown示例图片](http://upload-images.jianshu.io/upload_images/2178672-eb2effd6b942a500.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)`就会显示下面这张图片 - -![MarkDown示例图片](http://upload-images.jianshu.io/upload_images/2178672-98965f66db8f5856.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) - -`https://ws3.sinaimg.cn/large/006tNc79gy1fj9xhjzobbj30yg0my75z.jpg`就是这张图片的URL,我们可以在浏览器输入这个URL找到或下载这张图片。 - -所以,要在 MacDown 中插入图片,这张图片就需要上传到图床(网上),然后在引 -用这张图片的URL。 - -##### 将图片上传到图床 - -Mac 上的图床神器:iPic - -直接在App Store上下载,谁用谁知道! - -使用方法很简单,直接拖动图片到 P 图标上,或者选中图片按快捷键 `⌘+U`,就能请示上传。 - -上传成功就能直接粘贴图片的URL。 - -![iPic](http://upload-images.jianshu.io/upload_images/2178672-7399aeaced6f1e29.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) - -用 iPic 上传图片后,获取URL插入文章中就可以了。 - -![iPic上传图片](http://upload-images.jianshu.io/upload_images/2178672-4be76fb02708de5e.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) - - -#### 推荐几个好用软件 - -##### MarkDown编辑器 - -[MacDown](https://macdown.uranusjr.com/):可能是Mac上最好的MacDown编辑器了 - -![](http://upload-images.jianshu.io/upload_images/2178672-2226239a63278302.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) - -##### 图片压缩工具 - -[ImageOptim](https://imageoptim.com/) - -对于我们的博客来说,图片越大,加载速度越慢。 - -不信你用手机打开你的博客试试~ - -所以有必要对我们上传到博客网站中的图片:指的是你的头像,首页背景图片,文章背景图片等。对于博客文章中插入的图片,其实也可以压缩了再上传。 - -对博客中的所有图片进行压缩: - -看看压缩结果,最高的一张压缩了78.7%,这简直是太可怕了! - -![ImageOptim压缩图片](http://upload-images.jianshu.io/upload_images/2178672-0f8e643fa1da8674.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) - -好了,现在个人博客的加载速度估计要起飞了~ - -# 最后要说个事情 - -我在博客中的文章,你们可以保留,让更多需要帮助人的看到,当然也可以删除。 - -但是,我发现居然有人把文章的作者改成了自己,然后当成自己的文章放在自己的博客上,这就令人感到气愤了。 - -比如说向我请教问题的这位: - -![](http://upload-images.jianshu.io/upload_images/2178672-ed45ebafec7f5d34.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) - - -我在博客中的每篇文章都是我一字一句敲出来的,转载的文章我也注明了出处,表示对原作者的尊重。同时也希望大家都能尊重我的付出。 - -谢谢~ \ No newline at end of file diff --git "a/_posts/2017-02-14-\351\232\217\344\276\277\350\201\212\350\201\212.md" "b/_posts/2017-02-14-\351\232\217\344\276\277\350\201\212\350\201\212.md" deleted file mode 100644 index a28b2bc7831..00000000000 --- "a/_posts/2017-02-14-\351\232\217\344\276\277\350\201\212\350\201\212.md" +++ /dev/null @@ -1,32 +0,0 @@ ---- -layout: post -title: 随便聊聊 -subtitle: 2017 情人节快乐~ -date: 2017-02-14 -author: BY -header-img: img/post-bg-2015.jpg -catalog: true -tags: - - 生活 - - 博客 - - 漫谈 ---- - -# 随便谈谈 - -今天是情人节,首先祝大家情人节快乐~ - -在这特殊的节**日**里,我特意花了半天的时间,我将在简书中的文章都搬到 [**BY Blog**](http://qiubaiying.github.io) 上,顺便又修改了下博客框架。 - -# 关于分享 - -最近有很多人来请教我关于建站的问题,我都花时间为其解答一一解答。 - -感觉在解答别人的问题时,你自己也能学到很多新的知识,同时能沉浸其中。 - -我想这就是分享的魔力吧。 - -所以,把问题都砸过来吧~ - - - diff --git "a/_posts/2017-02-15-Git\346\214\207\344\273\244\346\225\264\347\220\206.md" "b/_posts/2017-02-15-Git\346\214\207\344\273\244\346\225\264\347\220\206.md" deleted file mode 100644 index eceee4ff6e6..00000000000 --- "a/_posts/2017-02-15-Git\346\214\207\344\273\244\346\225\264\347\220\206.md" +++ /dev/null @@ -1,279 +0,0 @@ ---- -layout: post -title: Git指令整理 -subtitle: 不适合阅读的整理的一些个人常用的 Git 指令 -date: 2017-02-15 -author: BY -header-img: img/post-bg-ios9-web.jpg -catalog: true -tags: - - Mac - - 终端 - - Git ---- - ->随便整理的一些自用的Git指令 - - -# GitHub创建仓库提示代码 - - echo "# 项目名" >> README.md - git init - git add README.md - git commit -m "first commit" - git remote add origin git@github.com:qiubaiying/项目名.git - git push -u origin master - -若仓库存在直接push - - git remote add origin git@github.com:qiubaiying/test.git - git push -u origin master - - -# 常用操作 - -#### 创建仓库(初始化) - 在当前指定目录下创建 - git init - - 新建一个仓库目录 - git init [project-name] - - 克隆一个远程项目 - git clone [url] - -#### 添加文件到缓存区 - - 添加所有变化的文件 - git add . - - 添加名称指定文件 - git add text.txt - -#### 配置 - - 设置提交代码时的用户信息 - git config [--global] user.name "[name]" - git config [--global] user.email "[email address]" - - -#### 提交 - 提交暂存区到仓库区 - git commit -m "msg" - - # 提交暂存区的指定文件到仓库区 - $ git commit [file1] [file2] ... -m [message] - - # 提交工作区自上次commit之后的变化,直接到仓库区 - $ git commit -a - - # 提交时显示所有diff信息 - $ git commit -v - - # 使用一次新的commit,替代上一次提交 - # 如果代码没有任何新变化,则用来改写上一次commit的提交信息 - $ git commit --amend -m [message] - - # 重做上一次commit,并包括指定文件的新变化 - $ git commit --amend [file1] [file2] ... - -#### 远程同步 - - # 下载远程仓库的所有变动 - $ git fetch [remote] - - # 显示所有远程仓库 - $ git remote -v - - # 显示某个远程仓库的信息 - $ git remote show [remote] - - # 增加一个新的远程仓库,并命名 - $ git remote add [shortname] [url] - - # 取回远程仓库的变化,并与本地分支合并 - $ git pull [remote] [branch] - - # 上传本地指定分支到远程仓库 - $ git push [remote] [branch] - - # 强行推送当前分支到远程仓库,即使有冲突 - $ git push [remote] --force - - # 推送所有分支到远程仓库 - $ git push [remote] --all - - - -#### 分支 - - # 列出所有本地分支 - $ git branch - - # 列出所有远程分支 - $ git branch -r - - # 列出所有本地分支和远程分支 - $ git branch -a - - # 新建一个分支,但依然停留在当前分支 - $ git branch [branch-name] - - # 新建一个分支,并切换到该分支 - $ git checkout -b [branch] - - # 新建一个分支,指向指定commit - $ git branch [branch] [commit] - - # 新建一个分支,与指定的远程分支建立追踪关系 - $ git branch --track [branch] [remote-branch] - - # 切换到指定分支,并更新工作区 - $ git checkout [branch-name] - - # 切换到上一个分支 - $ git checkout - - - # 建立追踪关系,在现有分支与指定的远程分支之间 - $ git branch --set-upstream [branch] [remote-branch] - - # 合并指定分支到当前分支 - $ git merge [branch] - - # 选择一个commit,合并进当前分支 - $ git cherry-pick [commit] - - # 删除分支 - $ git branch -d [branch-name] - - # 删除远程分支 - $ git push origin --delete [branch-name] - $ git branch -dr [remote/branch] - -#### 标签Tags - - 添加标签 在当前commit - git tag -a v1.0 -m 'xxx' - - 添加标签 在指定commit - git tag v1.0 [commit] - - 查看 - git tag - - 删除 - git tag -d V1.0 - - 删除远程tag - git push origin :refs/tags/[tagName] - - 推送 - git push origin --tags - - 拉取 - git fetch origin tag V1.0 - - 新建一个分支,指向某个tag - git checkout -b [branch] [tag] - -#### 查看信息 - - # 显示有变更的文件 - $ git status - - # 显示当前分支的版本历史 - $ git log - - # 显示commit历史,以及每次commit发生变更的文件 - $ git log --stat - - # 搜索提交历史,根据关键词 - $ git log -S [keyword] - - # 显示某个commit之后的所有变动,每个commit占据一行 - $ git log [tag] HEAD --pretty=format:%s - - # 显示某个commit之后的所有变动,其"提交说明"必须符合搜索条件 - $ git log [tag] HEAD --grep feature - - # 显示某个文件的版本历史,包括文件改名 - $ git log --follow [file] - $ git whatchanged [file] - - # 显示指定文件相关的每一次diff - $ git log -p [file] - - # 显示过去5次提交 - $ git log -5 --pretty --oneline - - # 显示所有提交过的用户,按提交次数排序 - $ git shortlog -sn - - # 显示指定文件是什么人在什么时间修改过 - $ git blame [file] - - # 显示暂存区和工作区的差异 - $ git diff - - # 显示暂存区和上一个commit的差异 - $ git diff --cached [file] - - # 显示工作区与当前分支最新commit之间的差异 - $ git diff HEAD - - # 显示两次提交之间的差异 - $ git diff [first-branch]...[second-branch] - - # 显示今天你写了多少行代码 - $ git diff --shortstat "@{0 day ago}" - - # 显示某次提交的元数据和内容变化 - $ git show [commit] - - # 显示某次提交发生变化的文件 - $ git show --name-only [commit] - - # 显示某次提交时,某个文件的内容 - $ git show [commit]:[filename] - - # 显示当前分支的最近几次提交 - $ git reflog - -#### 撤销 - - # 恢复暂存区的指定文件到工作区 - $ git checkout [file] - - # 恢复某个commit的指定文件到暂存区和工作区 - $ git checkout [commit] [file] - - # 恢复暂存区的所有文件到工作区 - $ git checkout . - - # 重置暂存区的指定文件,与上一次commit保持一致,但工作区不变 - $ git reset [file] - - # 重置暂存区与工作区,与上一次commit保持一致 - $ git reset --hard - - # 重置当前分支的指针为指定commit,同时重置暂存区,但工作区不变 - $ git reset [commit] - - # 重置当前分支的HEAD为指定commit,同时重置暂存区和工作区,与指定commit一致 - $ git reset --hard [commit] - - # 重置当前HEAD为指定commit,但保持暂存区和工作区不变 - $ git reset --keep [commit] - - # 新建一个commit,用来撤销指定commit - # 后者的所有变化都将被前者抵消,并且应用到当前分支 - $ git revert [commit] - - # 暂时将未提交的变化移除,稍后再移入 - $ git stash - $ git stash pop - -#### 其他 - - # 生成一个可供发布的压缩包 - $ git archives \ No newline at end of file diff --git "a/_posts/2017-02-15-Mac-\346\226\207\346\234\254\350\275\254\351\237\263\351\242\221.md" "b/_posts/2017-02-15-Mac-\346\226\207\346\234\254\350\275\254\351\237\263\351\242\221.md" deleted file mode 100644 index 279eac4ed9e..00000000000 --- "a/_posts/2017-02-15-Mac-\346\226\207\346\234\254\350\275\254\351\237\263\351\242\221.md" +++ /dev/null @@ -1,25 +0,0 @@ ---- -layout: post -title: Mac 文本转音频 -subtitle: 在Mac终端上将文本文件转换为音频文件 -date: 2017-02-15 -author: BY -header-img: img/post-bg-ios9-web.jpg -catalog: true -tags: - - Mac - - 终端 ---- - -# 文本转语音 - ->分享一条在Mac上将一个文本转换为音频文件的终端命令,个人认为还是蛮实用的。 -> ->来自: - -![](https://ww2.sinaimg.cn/large/006tNbRwgy1fcqwv0i9ovj30du04p74y.jpg) - -#### 指令: - - cat sample.txt | say -o sample.aiff - diff --git "a/_posts/2017-02-16-Git-\344\273\243\347\240\201\345\233\236\346\273\232.md" "b/_posts/2017-02-16-Git-\344\273\243\347\240\201\345\233\236\346\273\232.md" deleted file mode 100644 index c51905144aa..00000000000 --- "a/_posts/2017-02-16-Git-\344\273\243\347\240\201\345\233\236\346\273\232.md" +++ /dev/null @@ -1,63 +0,0 @@ ---- -layout: post -title: Git 代码回滚 -subtitle: 回滚代码的正确姿势 -date: 2017-02-16 -author: BY -header-img: img/post-bg-debug.png -catalog: true -tags: - - Mac - - 终端 - - Git ---- - - ->并不适合阅读的个人文档。 - -# **git revert** 和 **git reset** 的区别 - 先看图: - -![](https://ww3.sinaimg.cn/large/006tNbRwgy1fcr9tu6vdjj30t30ez0y8.jpg) - -**sourceTree** 中 **revert** 译为**`提交回滚`**,作用为忽略你指定的版本,然后提交一个新的版本。新的版本中已近删除了你所指定的版本。 - -**reset** 为 **重置到这次提交**,将内容重置到指定的版本。`git reset` 命令后面是需要加2种参数的:`–-hard` 和 `–-soft`。这条命令默认情况下是 `-–soft`。 - -执行上述命令时,这该条commit号之 后(时间作为参考点)的所有commit的修改都会退回到git缓冲区中。使用`git status` 命令可以在缓冲区中看到这些修改。而如果加上`-–hard`参数,则缓冲区中不会存储这些修改,git会直接丢弃这部分内容。可以使用 `git push origin HEAD --force` 强制将分区内容推送到远程服务器。 - - -#### 代码回退 - -默认参数 `-soft`,所有commit的修改都会退回到git缓冲区 -参数`--hard`,所有commit的修改直接丢弃 - - $ git reset --hard HEAD^ 回退到上个版本 - $ git reset --hard commit_id 退到/进到 指定commit_id -推送到远程 - - $ git push origin HEAD --force - - -#### 可以吃的后悔药->版本穿梭 - -当你回滚之后,又后悔了,想恢复到新的版本怎么办? - -用`git reflog`打印你记录你的每一次操作记录 - - $ git reflog - - 输出: - c7edbfe HEAD@{0}: reset: moving to c7edbfefab1bdbef6cb60d2a7bb97aa80f022687 - 470e9c2 HEAD@{1}: reset: moving to 470e9c2 - b45959e HEAD@{2}: revert: Revert "add img" - 470e9c2 HEAD@{3}: reset: moving to 470e9c2 - 2c26183 HEAD@{4}: reset: moving to 2c26183 - 0f67bb7 HEAD@{5}: revert: Revert "add img" - -找到你操作的id如:`b45959e`,就可以回退到这个版本 - - $ git reset --hard b45959e - - - diff --git "a/_posts/2017-02-22-Mac-\346\226\207\344\273\266\347\232\204\351\232\220\350\227\217\344\270\216\346\230\276\347\244\272.md" "b/_posts/2017-02-22-Mac-\346\226\207\344\273\266\347\232\204\351\232\220\350\227\217\344\270\216\346\230\276\347\244\272.md" deleted file mode 100644 index ffe2976db73..00000000000 --- "a/_posts/2017-02-22-Mac-\346\226\207\344\273\266\347\232\204\351\232\220\350\227\217\344\270\216\346\230\276\347\244\272.md" +++ /dev/null @@ -1,48 +0,0 @@ ---- -layout: post -title: Mac 文件的隐藏与显示 -subtitle: 使用终端 显示/隐藏 文件 -date: 2017-02-22 -author: BY -header-img: img/post-bg-debug.png -catalog: true -tags: - - Mac - - 终端 ---- - -> 让 Finder 显示隐藏文件和文件夹 - -# 基本 - -#### 显示 - - $ defaults write com.apple.finder AppleShowAllFiles -boolean true ; killall Finder - -#### 隐藏 - - $ defaults write com.apple.finder AppleShowAllFiles -boolean false ; killall Finder - -# 进阶 - -创建终端快捷命令 - -在 **zsh** shell 下,创建快捷命令 - -#### 创建显示命令 fd (fileDisplay) - $ echo "alias fd='defaults write com.apple.finder AppleShowAllFiles -boolean true ; killall Finder'">> ~/.zshrc && source ~/.zshrc - -#### 创建隐藏命令 fh(fileHide) - - $ echo "alias fd='defaults write com.apple.finder AppleShowAllFiles -boolean false ; killall Finder'">> ~/.zshrc && source ~/.zshrc - -使用方法 - -显示隐藏文件 - - $ fd -隐藏文件 - - $ fh - - \ No newline at end of file diff --git "a/_posts/2017-02-22-\344\275\277\347\224\250-.gitignore-\345\277\275\347\225\245-git-\344\273\223\345\272\223\344\270\255\347\232\204\346\226\207\344\273\266.md" "b/_posts/2017-02-22-\344\275\277\347\224\250-.gitignore-\345\277\275\347\225\245-git-\344\273\223\345\272\223\344\270\255\347\232\204\346\226\207\344\273\266.md" deleted file mode 100644 index 7b26b2abe61..00000000000 --- "a/_posts/2017-02-22-\344\275\277\347\224\250-.gitignore-\345\277\275\347\225\245-git-\344\273\223\345\272\223\344\270\255\347\232\204\346\226\207\344\273\266.md" +++ /dev/null @@ -1,75 +0,0 @@ ---- -layout: post -title: 使用 .gitignore 忽略 Git 仓库中的文件 -subtitle: .gitignore 文件在Git中的使用 -date: 2017-02-22 -author: BY -header-img: img/post-bg-debug.png -catalog: true -tags: - - Mac - - 终端 - - Git - - Github ---- - - -> 使用 `.gitignore` 文件忽略指定文件 - -## .gitignore - -在Git中,很多时候你只想将代码提交到仓库,而不是将当前文件目录下的文件全部提交到Git仓库中,例如在MacOS系统下面的`.DS_Store`文件,或者是Xocde的操作记录,又或者是pod库的中一大串的源代码。这种情况下使用`.gitignore`就能够在Git提交时自动忽略掉这些文件。 - - - -## 忽略的格式 - -- `#` :此为注释 – 将被 Git 忽略 -- `*.a` :忽略所有 `.a` 结尾的文件 -- `!lib.a` : 不忽略 `lib.a` 文件 -- `/TODO` :仅仅忽略项目根目录下的 `TODO` 文件,不包括 `subdir/TODO` -- `build/` : 忽略 `build/` 目录下的所有文件 -- `doc/*.txt` : 会忽略 `doc/notes.txt` 但不包括 `doc/server/arch.txt` - -## 创建方法 - -#### 从 [github](https://github.com/github/gitignore.git) 上获取 - -github上整理了一些常用需要的项目中需要忽略的文件配置,根据需要进行获取 - - https://github.com/github/gitignore.git - -与 Xcode 相关的三个文件 - -- Xcode.gitignore -- Objective-C.gitignore -- Swift.gitignore - -`Xcode.gitignore`忽略 `Xcode` 配置信息,如操作记录,默认打开窗口等 - -其他两个在 `Xcode.gitignore` 基础上针对不同的语言进行忽略 - -将这些文件重写命名为 `.gittignore` - - $ mv Swift.gitignore .gittignore - -#### 通过 [gitignore.io](https://www.gitignore.io/) 创建(推荐) - -###### 先自定义终端命令: - -macOS下默认是`\#!/bin/bash`: - - $ echo "function gi() { curl -L -s https://www.gitignore.io/api/\$@ ;}" >> ~/.bash_profile && source ~/.bash_profile - -如果是 `#!/bin/zsh` - - $ echo "function gi() { curl -L -s https://www.gitignore.io/api/\$@ ;}" >> ~/.zshrc && source ~/.zshrc - -###### 使用 - -在当前终端目录下 - - $ gi swift > .gitignore - - -就会针对 Swifit 类型的工程创建 `.gitignore` 文件。 diff --git "a/_posts/2017-03-01-Xcode-\346\216\247\345\210\266\345\217\260\350\276\223\345\207\272\344\270\255\346\226\207.md" "b/_posts/2017-03-01-Xcode-\346\216\247\345\210\266\345\217\260\350\276\223\345\207\272\344\270\255\346\226\207.md" deleted file mode 100644 index 0a1dd90973f..00000000000 --- "a/_posts/2017-03-01-Xcode-\346\216\247\345\210\266\345\217\260\350\276\223\345\207\272\344\270\255\346\226\207.md" +++ /dev/null @@ -1,177 +0,0 @@ ---- -layout: post -title: Xcode 控制台输出中文 -subtitle: 在 Xcode 控制台输出中文的方法 -date: 2017-03-01 -author: BY -header-img: img/post-bg-cook.jpg -catalog: true -tags: - - iOS - - Xcode - - macOS ---- - -> 重写 NSArray、NSSet、NSDictionary 的输出方法,在Xcode实现中文(Unicode)字符在控制台的输出 - -## 原理 - Xcode 控制台中在输出 NSArray、NSSet、NSDictionary 时,其中的中文字符会变成Unicode编码 如`"\U67cf\U8367"`. - -我们通过分类重写这些类的输出方法即可实现在控制台输出中文: - - - (NSString *)descriptionWithLocale:(id)locale indent:(NSUInteger)level - -## 效果 -先看看效果: - -创建一个字典并输出: - - NSData *strData = [@"str -> data格式的字符串" dataUsingEncoding:NSUTF8StringEncoding]; - - NSData *dicData = [NSJSONSerialization dataWithJSONObject:@{@"key0": @"字典 -> data 的数据",} - options:NSJSONWritingPrettyPrinted - error:nil]; - - NSMutableSet *set = [NSMutableSet setWithArray:@[@"set0", - strData, - dicData]]; - NSDictionary *dic = @{@"name" : @"BY", - @"My bolg" : @"http://qiubaiying.top", - @"count" : @(11), - @"strData" : strData, - @"dicData" : dicData, - @"set" : set, - @"Unicode" : @"😀😁🤣😂😄", - @"contact" : @[@"BY Blog:http://qiubaiying.top", - @"GitHub:https://github.com/qiubaiying", - @"简书:https://http://www.jianshu.com/u/e71990ada2fd"]}; - NSLog(@"%@", dic); - -输出结果: - - 2017-03-01 10:36:45.709 BYFoundationLog_Demo[1657:53604] { - "My bolg" = "http://qiubaiying.top"; - Unicode = "\Ud83d\Ude00\Ud83d\Ude01\Ud83e\Udd23\Ud83d\Ude02\Ud83d\Ude04"; - contact = ( - "BY Blog:http://qiubaiying.top", - "GitHub:https://github.com/qiubaiying", - "\U7b80\U4e66:https://http://www.jianshu.com/u/e71990ada2fd" - ); - count = 11; - dicData = <7b0a2020 226b6579 3022203a 2022e5ad 97e585b8 202d3e20 64617461 20e79a84 e695b0e6 8dae220a 7d>; - name = BY; - set = "{(\n <73747220 2d3e2064 617461e6 a0bce5bc 8fe79a84 e5ad97e7 aca6e4b8 b2>,\n set0,\n <7b0a2020 226b6579 3022203a 2022e5ad 97e585b8 202d3e20 64617461 20e79a84 e695b0e6 8dae220a 7d>\n)}"; - strData = <73747220 2d3e2064 617461e6 a0bce5bc 8fe79a84 e5ad97e7 aca6e4b8 b2>; - } - -将`BYFoundationLog.m`拖入项目,再次运行 - - 2017-03-01 10:35:52.545 BYFoundationLog_Demo[1635:52772] { - set = {( - "str -> data格式的字符串", - "set0", - { - key0 = "字典 -> data 的数据", - }, - )}, - Unicode = "😀😁🤣😂😄", - strData = "str -> data格式的字符串", - count = 11, - dicData = { - key0 = "字典 -> data 的数据", - }, - contact = ( - "BY Blog:http://qiubaiying.top", - "GitHub:https://github.com/qiubaiying", - "简书:https://http://www.jianshu.com/u/e71990ada2fd", - ), - name = "BY", - My bolg = "http://qiubaiying.top", - } - - -## 实现方法 - -以 `NSArray` 为例: - -创建一个 `NSArray` 的分类,重写输出方法 - -``` -@implementation NSArray (Log) - -- (NSString *)descriptionWithLocale:(id)locale indent:(NSUInteger)level { - NSMutableString *desc = [NSMutableString string]; - - NSMutableString *tabString = [[NSMutableString alloc] initWithCapacity:level]; - for (NSUInteger i = 0; i < level; ++i) { - [tabString appendString:@"\t"]; - } - - NSString *tab = @""; - if (level > 0) { - tab = tabString; - } - [desc appendString:@"\t(\n"]; - - for (id obj in self) { - if ([obj isKindOfClass:[NSDictionary class]] - || [obj isKindOfClass:[NSArray class]] - || [obj isKindOfClass:[NSSet class]]) { - NSString *str = [((NSDictionary *)obj) descriptionWithLocale:locale indent:level + 1]; - [desc appendFormat:@"%@\t%@,\n", tab, str]; - } else if ([obj isKindOfClass:[NSString class]]) { - [desc appendFormat:@"%@\t\"%@\",\n", tab, obj]; - } else if ([obj isKindOfClass:[NSData class]]) { - - NSError *error = nil; - NSObject *result = [NSJSONSerialization JSONObjectWithData:obj - options:NSJSONReadingMutableContainers - error:&error]; - - if (error == nil && result != nil) { - if ([result isKindOfClass:[NSDictionary class]] - || [result isKindOfClass:[NSArray class]] - || [result isKindOfClass:[NSSet class]]) { - NSString *str = [((NSDictionary *)result) descriptionWithLocale:locale indent:level + 1]; - [desc appendFormat:@"%@\t%@,\n", tab, str]; - } else if ([obj isKindOfClass:[NSString class]]) { - [desc appendFormat:@"%@\t\"%@\",\n", tab, result]; - } - } else { - @try { - NSString *str = [[NSString alloc] initWithData:obj encoding:NSUTF8StringEncoding]; - if (str != nil) { - [desc appendFormat:@"%@\t\"%@\",\n", tab, str]; - } else { - [desc appendFormat:@"%@\t%@,\n", tab, obj]; - } - } - @catch (NSException *exception) { - [desc appendFormat:@"%@\t%@,\n", tab, obj]; - } - } - } else { - [desc appendFormat:@"%@\t%@,\n", tab, obj]; - } - } - - [desc appendFormat:@"%@)", tab]; - - return desc; -} - -@end - -``` - -NSSet、NSDictionary 与 NSArray 实现方法类似 - -## 代码下载 - -代码及Demo地址:[GitHub](https://github.com/qiubaiying/BYFoundationLog) - -## 使用方法 - -直接将 `BYFoundationLog.m` 文件拖入项目中就能使用 - - diff --git "a/_posts/2017-03-06-Swift-\344\273\243\347\220\206\346\250\241\345\274\217.md" "b/_posts/2017-03-06-Swift-\344\273\243\347\220\206\346\250\241\345\274\217.md" deleted file mode 100644 index a7a40c5bc39..00000000000 --- "a/_posts/2017-03-06-Swift-\344\273\243\347\220\206\346\250\241\345\274\217.md" +++ /dev/null @@ -1,131 +0,0 @@ ---- -layout: post -title: Swift 代理模式 -subtitle: Swift中如何使用代理模式 -date: 2017-03-06 -author: BY -header-img: img/post-bg-ios10.jpg -catalog: true -tags: - - iOS - - Swift - - 设计模式 ---- - -> Xcode 8.2 | Swift 3.0 - -在iOS开发中,无论是 **Objective-C** 还是 **Swift** ,**Delegate** 有着具足轻重的位置,如`TabelViewDelegate` 与 `TableViewDataSource`。 - -**Swift** 中的代理模式 和 **Objective-C** 除了语法外,几乎一样。 - -## Objective-C 代理模式 - -在介绍 Swift 代理模式前,先来看回顾一下 Objective-C 中的代理模式如何实现 - -Objective-C 中用代理实现反向传值: - -![](https://ww4.sinaimg.cn/large/006tKfTcgy1fdd51zf5cwg307i0dck3f.gif) - -#### 委托方(子控制器) - -委托方需要实现 - -- 创建协议 、声明协议方法 - - @protocol SubViewDelegate - - - (void)backWithStr:(NSString *) str; - - @end -- 创建一个代理属性 - - // weak声明 - @property (nonatomic, weak) id delegate; -- 执行协议方法 - - // 判断代理是实现该方法,避免carsh - if ([self.delegate respondsToSelector:@selector(backWithStr:)]) { - [self.delegate backWithStr:self.textField.text]; - } - - -#### 代理方(主控制器) -代理方需要实现 - -- 遵守(继承)协议 - - @interface ViewController () -- 将代理设为自己 - - subVC.delegate = self; -- 实现代理方法 - - - (void)backWithStr:(NSString *)str { - self.label.text = str; - } - -## Swift 代理模式 - -Swift 代理模式 与 Objective-C 一样,只是语法不同。 - -Swift 中用代理实现反向传值: - -![](https://ww1.sinaimg.cn/large/006tKfTcgy1fdd5oi9048g307i0dc7co.gif) - -#### 委托方(子控制器) - -- 创建协议 、声明协议方法 - - protocol SubViewDelegate { - func backStr(str: String) - } -- 创建一个代理属性 - - var delegate: SubViewDelegate? -- 执行协议方法 - - /// 执行代理方法,将值回传 - delegate?.backStr(str: textField.text ?? "") - -#### 代理方(主控制器) -- 继承协议 - - class ViewController: UIViewController, SubViewDelegate -- 将代理设为自己 - - subVC.delegate = self - -- 实现代理方法 - - ```swift - func backStr(str: String) { - self.textF.text = str - } - ``` - - -## 总结 - -对比可以方法 Swift 代理模式 与 Objective-C 用法完全相同,只是语法发生了变化。 - -值得一提的是Swift 的扩展 `extension`可以用来继承协议,实现代码隔离,便于维护。 - -```swift -/// 使用扩展继承协议 实现协议方法 可以分离代码 -extension ViewController: SubViewDelegate{ - /// 实现代理方法 - func backStr(str: String) { - self.textF.text = str - } -} -``` - - -## Demo源码 - -最后附上[Demo源码](https://github.com/qiubaiying/iOS-Delegate_Demo) - -如果对你有帮助的话,**Star**✨下一吧! - - - diff --git "a/_posts/2017-03-08-CocoaPods\345\205\254\346\234\211\344\273\223\345\272\223\347\232\204\345\210\233\345\273\272.md" "b/_posts/2017-03-08-CocoaPods\345\205\254\346\234\211\344\273\223\345\272\223\347\232\204\345\210\233\345\273\272.md" deleted file mode 100644 index 3b84c904f85..00000000000 --- "a/_posts/2017-03-08-CocoaPods\345\205\254\346\234\211\344\273\223\345\272\223\347\232\204\345\210\233\345\273\272.md" +++ /dev/null @@ -1,263 +0,0 @@ ---- -layout: post -title: CocoaPods公有仓库的创建 -subtitle: 手把手教你创建 CocoaPods 公有仓库 -date: 2017-03-08 -author: BY -header-img: img/post-bg-ios10.jpg -catalog: true -tags: - - iOS - - CocoaPods - - Git ---- - -> 本文发布于 [BY Blog](http://qiubaiying.github.io)、[简书](http://www.jianshu.com/p/d2d98298b1b8) 转载请保留链接 - -# 前言 - -作为iOS开发者,CocoaPods的使用为我们开发带来了极大的便利。 - -我们先来看看CocoaPods本地目录中有什么 - - $ cd ~/.cocoapods/repos/master - -或者显示隐藏文件 - - $ defaults write com.apple.finder AppleShowAllFiles -boolean true ; killall Finder - -然后进入 `~/.cocoapods/repos/master` - -你会发现 `master` 是一个 git 仓库,输出仓库的远程地址,发现是一个GitHub仓库 - - $ git remote -v - - origin https://github.com/CocoaPods/Specs.git (fetch) - origin https://github.com/CocoaPods/Specs.git (push) - - -[![](https://ww4.sinaimg.cn/large/006tKfTcgy1fdgdi59dnnj31kw10247u.jpg)]() - -继续,我们进入`Specs`文件夹一直往里点 - -![](https://ww3.sinaimg.cn/large/006tKfTcgy1fdgdpyex7mj30yk0bkdi5.jpg) - -你会发现很多框架以及版本号,选择一个框架,通过 - - $ pod search YYImage - -pod搜索 Specs 文件夹中的框架,输出框架信息 - - -> YYImage (1.0.4) - Image framework for iOS to display/encode/decode animated WebP, APNG, GIF, - and more. - pod 'YYImage', '~> 1.0.4' - - Homepage: https://github.com/ibireme/YYImage - - Source: https://github.com/ibireme/YYImage.git - - Versions: 1.0.4, 1.0.3, 1.0.2, 1.0.1, 1.0, 0.9.5, 0.9.4, 0.9.3, 0.9.2, - 0.9.1, 0.9.0, 0.8.9 [master repo] - - Subspecs: - - YYImage/Core (1.0.4) - - YYImage/WebP (1.0.4) -每个版本号对应的一个json文件,描述了每个对应版本的框架的信息、配置、及源码下载地。 - -![](https://ww4.sinaimg.cn/large/006tKfTcgy1fdgdsl5tdxj318q14mdq2.jpg) - -我们在 CocoaPods 发布我们的框架时,就是要在 `master` 仓库中添加我们的仓库描述信息,然后push到远程仓库中。不过这个过程不用我们手动去操作,只需要通过`pod`命令进行操作即可。 - - -下面我们将一步步把我封装的这个简单的TextFiled控件 [BYPhoneNumTF](https://github.com/qiubaiying/BYPhoneNumTF) 上传到 Cocoapods 公有仓库中。 - -# 正文 - -#### 注册 CocoaPods 账号 -想创建开源的Pod库,就要注册一个CocoaPods账号,我们使用终端注册, `email` 用你的 `GitHub` 邮箱 - - $ pod trunk register GitHub_email 'user_name' --verbose - -等终端出现下面文字,CocoaPods 会发一个`确认邮件`到你的邮箱上,登录你的邮箱进行确认。 - - [!] Please verify the session by clicking the link in the verification email that has been sent to you_email@163.com - -![](https://ww3.sinaimg.cn/large/006tNbRwgy1fdeco0ndc9j30r10h3wgt.jpg) - -注册成功! - -确认后再终端输入 - - pod trunk me - -可以看到你的注册信息 - -![](https://ww4.sinaimg.cn/large/006tNbRwgy1fdecs0z72oj30n004q3z2.jpg) - -#### 创建Git仓库 - -在 [GitHub](https://github.com) 上创建一个公开项目,项目中必须包含这几个文件 - -- `LICENSE`:开源许可证 -- `README.md`:仓库说明 -- 你的代码 -- `BYPhoneNumTF.podspec`: CocoaPods 的描述文件,这个文件**非常重要** - -如下图: - -![](https://ww2.sinaimg.cn/large/006tNbRwgy1fdfhvy3c19j31iq0dqn03.jpg) - -`BYPhoneNumTF` 文件夹下是我存放代码的地方 - -`BYPhoneNumTF_Demo` 是代码使用样例(不是必须的) - - -#### 创建`.podspec` -`.podspec` 是用 Ruby 的配置文件,描述你项目的信息。 - -在你的仓库目录下,使用终端命令创建 - - $ pod spec create BYPhoneNumTF - -这时就会在你的仓库下生成 `BYPhoneNumTF.podspec` 文件 - -![](https://ww4.sinaimg.cn/large/006tNbRwgy1fdfioo1c4zj31bq0s20zn.jpg) - -修改里面的配置就可以发布了~当然,没这么简单。 - -配置文件中的注释很多,而且很多配置都不是必须的,写多了等下验证还不让过~ - -so~**强烈建议**,直接拷贝下面的主要配置进行修改 - -```ruby -Pod::Spec.new do |s| - s.name = "BYPhoneNumTF" # 项目名称 - s.version = "1.0.0" # 版本号 与 你仓库的 标签号 对应 - s.license = "MIT" # 开源证书 - s.summary = "A delightful TextField of PhoneNumber" # 项目简介 - - s.homepage = "https://github.com/qiubaiying/BYPhoneNumTF" # 你的主页 - s.source = { :git => "https://github.com/qiubaiying/BYPhoneNumTF.git", :tag => "#{s.version}" }#你的仓库地址,不能用SSH地址 - s.source_files = "BYPhoneNumTF/*.{h,m}" # 你代码的位置, BYPhoneNumTF/*.{h,m} 表示 BYPhoneNumTF 文件夹下所有的.h和.m文件 - s.requires_arc = true # 是否启用ARC - s.platform = :ios, "7.0" #平台及支持的最低版本 - s.frameworks = "UIKit", "Foundation" #支持的框架 - # s.dependency = "AFNetworking" # 依赖库 - - # User - s.author = { "BY" => "qiubaiyingios@163.com" } # 作者信息 - s.social_media_url = "http://qiubaiying.github.io" # 个人主页 - -end -``` -最最关键的步骤的到了,验证 `.podspec` 文件的格式是否正确, - - $ pod lib lint - -验证会出现成功出现 - - -> BYPhoneNumTF (1.0.0) - - BYPhoneNumTF passed validation. - -但是很多情况没这么顺利,比如: - - -> BYPhoneNumTF (1.0.0) - - WARN | url: There was a problem validating the URL http://qiubaiying.github.io. - - [!] BYPhoneNumTF did not pass validation, due to 1 warning (but you can use `--allow-warnings` to ignore it) and all results apply only to public specs, but you can use `--private` to ignore them if linting the specification for a private pod. - [!] The validator for Swift projects uses Swift 3.0 by default, if you are using a different version of swift you can use a `.swift-version` file to set the version for your Pod. For example to use Swift 2.3, run: - `echo "2.3" > .swift-version`. - You can use the `--no-clean` option to inspect any issue. - -提示我们需要加`--allow-warnings`这么一句话,命令改为 - - $ pod lib lint --allow-warnings - -若还是提示什么`'echo "2.3" > .swift-version'`的,就加这么一个东西。 - - $ echo "2.3" > .swift-version -然后在进行验证,这是应该就可以了。若还是不行,回到配置文件中检查有没有写错配置信息~ - -#### 给仓库打标签 - -验证成功后,将仓库提交到远程,然后给仓库打上标签并将标签也推送到远程。 - -标签相当于将你的仓库的一个压缩包,用于稳定存储当前版本。标签号与你在 `s.version = "1.0.0"`的版本号一致 `1.0.0` - - 创建标签 - $ git tag -a 1.0.0 -m '标签说明' - 推送到远程 - $ git push origin --tags - -#### 发布`.podspec` - -最后一步,发布项目的描述的文件 `BYPhoneNumTF.podspec` - -在仓库目录下执行 - - pod trunk push BYPhoneNumTF.podspec - -将你的 `BYPhoneNumTF.podspec` 发布到公有的speecs上,这一步其实做了很多操作,包括 - -1. 更新本地 pods 库 `~/.cocoaPods.repo/master` -- 验证`.podspec`格式是否正确 -- 将 `.podspec` 文件转成 JSON 格式 -- 对 `master` 仓库 进行合并、提交.[master仓库地址](https://github.com/CocoaPods/Specs) - - -成功后将会出现下列信息: - - Updating spec repo `master` - Validating podspec - -> BYPhoneNumTF (1.0.0) - - Updating spec repo `master` - - -------------------------------------------------------------------------------- - 🎉 Congrats - - 🚀 BYPhoneNumTF (1.0.0) successfully published - 📅 March 7th, 01:39 - 🌎 https://cocoapods.org/pods/BYPhoneNumTF - 👍 Tell your friends! - -说明发布成功,你就可以通过上面的URL: 进入的Pods查看自己的仓库信息了. - -![](https://ww3.sinaimg.cn/large/006tNbRwgy1fded7yh8ugj31kw19djyk.jpg) - -#### 使用仓库 - -发布到Cocoapods后,在终端更新本地pods仓库信息 - - $ pod setup - -查询仓库 - - $ pod search BYPhoneNumTF ---- - -> BYPhoneNumTF (1.0.0) - A delightful TextField of PhoneNumber - pod 'BYPhoneNumTF', '~> 1.0.0' - - Homepage: https://github.com/qiubaiying/BYPhoneNumTF - - Source: https://github.com/qiubaiying/BYPhoneNumTF.git - - Versions: 1.0.0, 0.0.1 [BYPhoneNumTF repo] - (END) - -若出现仓库信息说明已经成功了,这时候你就可以在 `Podfile` 添加、使用自己的仓库了 `pod 'BYPhoneNumTF', '~> 1.0.0'` - -![](https://ww1.sinaimg.cn/large/006tNbRwgy1fdedvficvaj30fu0loaex.jpg) - -#### 更新维护 - -当你的代码更新维护后,就需要重写发布,流程是: - -- 更新`BYPhoneNumTF.podspec`中的版本号 -- 打上标签推送远程 -- `pod trunk push BYPhoneNumTF.podspec` 推送到pods仓库 - -更新后你就可以在 [CocoaPods Master Repo](https://github.com/CocoaPods/Specs) 仓库上看到自己的提交记录了。 - -![](https://ww4.sinaimg.cn/large/006tNbRwgy1fdfkr2l7omj31kw0d7446.jpg) - -# 结语 - -到此,你已经掌握了创建和维护一个Cocoapods公有仓库的技能了,是不是很棒~ - diff --git "a/_posts/2017-03-10-CocoaPods\347\247\201\346\234\211\344\273\223\345\272\223\347\232\204\345\210\233\345\273\272.md" "b/_posts/2017-03-10-CocoaPods\347\247\201\346\234\211\344\273\223\345\272\223\347\232\204\345\210\233\345\273\272.md" deleted file mode 100644 index b0d5588daa6..00000000000 --- "a/_posts/2017-03-10-CocoaPods\347\247\201\346\234\211\344\273\223\345\272\223\347\232\204\345\210\233\345\273\272.md" +++ /dev/null @@ -1,229 +0,0 @@ ---- -layout: post -title: CocoaPods私有仓库的创建 -subtitle: 继续带你创建 CocoaPods 私有有仓库 -date: 2017-03-10 -author: BY -header-img: img/post-bg-iWatch.jpg -catalog: true -tags: - - iOS - - CocoaPods - - Git ---- - -> 本文发布于 [BY Blog](http://qiubaiying.github.io)、[简书](http://www.jianshu.com/p/d2d98298b1b8) 转载请保留链接 -> -> 上一篇文章 [《CocoaPods公有仓库的创建》](http://qiubaiying.top/2017/03/08/CocoaPods公有仓库的创建/) - -# 前言 - -最近参照了网上一大堆 CocoaPods私有仓库 的教程,按教程操作得到的pod仓库里面是这样的~ - -![](https://ww3.sinaimg.cn/large/006tKfTcgy1fdgexnidglj30yq0eqn0r.jpg) - -代码和版本描述居然混在了一起,简直太糟糕~ - -虽然也能用,但是和CocoaPods本身的结构设计就不相符。 - -在上一篇[《CocoaPods公有仓库的创建》](http://qiubaiying.top/2017/03/08/CocoaPods公有仓库的创建/)中我们了解到,`master` 目录中只存放 代码库 的描述文件,而不是存放代码。就像这样 - -![](https://ww4.sinaimg.cn/large/006tKfTcgy1fdgf4l54rxj30ya09ujst.jpg) - -代码我们另外存放在代码仓库中 - -![](https://ww4.sinaimg.cn/large/006tKfTcgy1fdgf9t7vcgj30n206s0u8.jpg) - -很多人不了解CocoaPods的工作原理就复制粘贴别人的教程来做教程~ - -吐槽结束,进入正文 - -# 正文 - -#### 创建版本库(**repo**) - -首先,创建一个像 `master` 一样的存放版本描述文件的git仓库,因为是私人git仓库,我们选择 [oschina](http://git.oschina.net/) 创建远程私有仓库(因为是免费的)或者也可以在GitHub上创建(**$7/month**)。 - -下面以 [oschina](http://git.oschina.net/) 为例 - -创建版本描述仓库 - -![](https://ww1.sinaimg.cn/large/006tKfTcgy1fdgfqdqyy1j31kw1c2th0.jpg) - - -回到终端,将这个远程的私有版本仓库添加到本地,`repo` 就是 repository 储存库的缩写。 - - $ pod repo add MyRepo https://git.oschina.net/baiyingqiu/MyRepo.git - -查看在 Finder 目录 `~/.cocoapods/repos`, 可以发现增加了一个 MyRepo 的储存库 - -![](https://ww2.sinaimg.cn/large/006tKfTcgy1fdgfyfl6v6j316y0piwhz.jpg) - -#### 创建代码库 - -回到 [oschina](http://git.oschina.net/) 创建私人代码库 - -创建时添加 `MIT License` 和 `README` - - - -![](https://ww2.sinaimg.cn/large/006tKfTcgy1fdgjfu7n96j31kw17y7cq.jpg) - -将仓库克隆到本地,添加`你的代码文件`、`仓库名.podspec` 描述文件,还有`.swift-version`. - -如下 - -![](https://ww2.sinaimg.cn/large/006tKfTcgy1fdgmyefutej311a0kegqh.jpg) - -`.swift-version`文件用来知道swift版本,用命令行创建 - - $ echo "3.0" > .swift-version - -**`.podspec`** 文件是你这个代码库的pod描述文件,可以通过pod指令创建空白模板: - - $ pod spec create MyAdditions - -或者 **强烈建议** 直接拷贝下面的模板进行修改 - -```ruby -Pod::Spec.new do |s| - s.name = "MyAdditions" # 项目名称 - s.version = "0.0.1" # 版本号 与 你仓库的 标签号 对应 - s.license = "MIT" # 开源证书 - s.summary = "私人pod代码" # 项目简介 - - s.homepage = "https://git.oschina.net/baiyingqiu/MyAdditions" # 仓库的主页 - s.source = { :git => "https://git.oschina.net/baiyingqiu/MyAdditions.git", :tag => "#{s.version}" }#你的仓库地址,不能用SSH地址 - s.source_files = "MyAdditions/*.{h,m}" # 你代码的位置, BYPhoneNumTF/*.{h,m} 表示 BYPhoneNumTF 文件夹下所有的.h和.m文件 - s.requires_arc = true # 是否启用ARC - s.platform = :ios, "7.0" #平台及支持的最低版本 - # s.frameworks = "UIKit", "Foundation" #支持的框架 - # s.dependency = "AFNetworking" # 依赖库 - - # User - s.author = { "BY" => "qiubaiyingios@163.com" } # 作者信息 - s.social_media_url = "http://qiubaiying.github.io" # 个人主页 - -end -``` -这里我要说一下一个坑,用 [oschina](http://git.oschina.net/) 创建私人仓库时, 在验证时可能会找不到 `MIT LICENSE`证书,将其中的 - - s.license = "MIT" - 修改为,指定文件 - s.license = { :type => "MIT", :file => "LICENSE" } - -然后开始验证我们的仓库配置是否正确,并按照要求进行修改 - - $ pod lib lint - -一般出现错误警告,需要添加 `--private` 或者 `--allow-warnings`,就可以通过验证 - - $ pod lib lint --private - -验证成功后出现 - - -> MyAdditions (0.0.1) - - MyAdditions passed validation. - -#### 将描述文件推送到版本库 - -将项目打上标签推到远程仓库,标签号 和 版本号对应 都是`0.0.1` - -最后将我们的代码仓库的描述信息,push 到我们的版本仓库中 - - $ pod repo push MyRepo MyAdditions.podspec - -这时会对远程仓库进行验证,成功的话就会在 `~/.cocoapods/repos/MyRep`中发现新增的仓库描述信息了 - -![](https://ww3.sinaimg.cn/large/006tKfTcgy1fdgo62knrwj31ko0s8784.jpg) - -若是出现错误信息 - - [!] The repo `MyRepo` at `../.cocoapods/repos/MyRepo` is not clean - -更新下我们的版本库, - - $ pod repo update MyRepo - - -再继续上传即可。 - -`pod repo push MyRepo MyAdditions.podspec` 的过程就是 - -1. 验证 `MyAdditions.podspec` 文件 -- 拉取远程版本库 `MyRepo` -- 添加 `MyAdditions.podspec` 到版本库中 -- push 到远程 - -添加完成后我们就可以在pod中搜索 - - $ pod search MyAdditions ---- - -> MyAdditions (0.0.1) - Some category of the framework and UIKit - pod 'MyAdditions', '~> 0.0.1' - - Homepage: https://git.oschina.net/baiyingqiu/MyAdditions - - Source: https://git.oschina.net/baiyingqiu/MyAdditions.git - - Versions: 0.0.1 [MyRepo repo] - (END) - -### 私人pod库的使用 - -使用私人pod库的需要在`Podflie`中添加这句话,指明你的版本库地址。 - - source ‘https://git.oschina.net/baiyingqiu/MyRepo.git’ -**注意**是版本库的地址,而不是代码库的地址,很多教程都把我搞晕了~ - - -若有还使用了公有的pod库,需要把公有库地址也带上 - - source ‘https://github.com/CocoaPods/Specs.git’ - -最后的`Podflie`文件变成这个样子 - - source ‘https://github.com/CocoaPods/Specs.git’ - source ‘https://git.oschina.net/baiyingqiu/MyRepo.git’ - - platform :ios, '8.0' - - target ‘MyPodTest’ do - use_frameworks! - - pod “BYPhoneNumTF” #公有库 - pod ‘MyAdditions’ #我们的私有库 - pod ‘BYAdditions’ #这是我又添加到版本库中的另一个代码库 - - end - -测试: - - $ pod install - -加载完成可以看到代码已经整合到我们的项目中了 - -**perfect!** - - - -回到Fender中 `~/.cocoapods/repos`,会发现 repos 中增加了一个pod版本库。 - -![](https://ww2.sinaimg.cn/large/006tKfTcgy1fdhlc59rl9j30ya08y0ub.jpg) - -执行 `pod install` 命令时 - -- 会拉取远程 `Podflie` 中 `source` 标记 版本库 到本地的 repos 文件夹中 - -- 在 版本库 中搜索我们`pod ‘MyAdditions’` 的 `MyAdditions.podspec` 文件。 -- 根据 `MyAdditions.podspec` 文件中描述的源码地址下载并整合到项目中 - - - -# 结语 - -通过 [《CocoaPods私有仓库的创建》](http://qiubaiying.top/2017-03-10-CocoaPods私有仓库的创建/) 和 [《CocoaPods公有仓库的创建](http://qiubaiying.top/2017/03/08/CocoaPods公有仓库的创建/)》这两篇文章,相信大家对CocoaPods的工作原理都有了更深层次的了解。 - -在写博客和和创建的过程中,踩了不少的坑(😀前人教程留下的),很多的东西只有自己操作完才能真正的领会。 - -最后,如果本文有什么错误或者有什么不同的观点欢迎提出交流。😉 - diff --git "a/_posts/2017-03-17-Mac\347\273\210\347\253\257(zsh)\344\270\213\347\224\250\344\273\243\347\240\201\347\274\226\350\276\221\345\231\250\346\211\223\345\274\200\346\226\207\344\273\266\346\210\226\347\233\256\345\275\225.md" "b/_posts/2017-03-17-Mac\347\273\210\347\253\257(zsh)\344\270\213\347\224\250\344\273\243\347\240\201\347\274\226\350\276\221\345\231\250\346\211\223\345\274\200\346\226\207\344\273\266\346\210\226\347\233\256\345\275\225.md" deleted file mode 100644 index 89509398b6c..00000000000 --- "a/_posts/2017-03-17-Mac\347\273\210\347\253\257(zsh)\344\270\213\347\224\250\344\273\243\347\240\201\347\274\226\350\276\221\345\231\250\346\211\223\345\274\200\346\226\207\344\273\266\346\210\226\347\233\256\345\275\225.md" +++ /dev/null @@ -1,93 +0,0 @@ ---- -layout: post -title: - -subtitle: 更改 zsh 配置文件,在终端使用sublime、vscode、atom快速打开文件或目录 -date: 2017-03-17 -author: BY -header-img: img/post-bg-debug.png -catalog: true -tags: - - Mac - - 效率 - - 终端 - - zsh ---- - -# 前言 - - 最近在喵神 onevcat 的直播中发现喵神直接在终端就能用 vsCode 打开当前代码目录,非常方便。 - - 在`zsh`终端中 使用 `code .`,在 **vcCode** 打开当前文件目录 - -![](https://ww2.sinaimg.cn/large/006tKfTcgy1fdpxob9m7sj31000rkam7.jpg) - - -# 正文 - -## 配置终端环境 - -终端环境为:[iTerm2](https://www.iterm2.com/) + [zsh](https://wiki.archlinux.org/index.php/Zsh_(%E7%AE%80%E4%BD%93%E4%B8%AD%E6%96%87)) - -zsh 使用 [oh_my_zsh](http://ohmyz.sh/) 配置 - - -## 安装zsh - -#### 查看你的系统有几种shell - - cat /etc/shells - -显示 - - /bin/bash - /bin/csh - /bin/ksh - /bin/sh - /bin/tcsh - /bin/zsh - -#### 安装 oh my zsh - - git clone git://github.com/robbyrussell/oh-my-zsh.git ~/.oh-my-zsh - cp ~/.oh-my-zsh/templates/zshrc.zsh-template ~/.zshrc - -重新打开终端,输入 - - zsh - -即可切换终端,并且发现 oh my zsh 已经帮我们配置好 zsh 了 - -#### 修改主题 - - open ~/.zshrc - -修改 `ZSH_THEME=”robbyrussell”`,主题在 ~/.oh-my-zsh/themes 目录下。 -修改为 - - ZSH_THEME="kolo" - -可以[参照这里](https://github.com/robbyrussell/oh-my-zsh/wiki/themes)进行选择. - -#### 设置为默认shell - - chsh -s /bin/zsh - -## 修改 `zsh` 配置文件 - - $ open ~/.zshrc - -在文件中加上这几行代码 - -对应 atom、SublimeText、与 vcCode。 - - alias atom='/Applications/Atom.app/Contents/MacOS/Atom' - alias subl='/Applications/Sublime\ Text.app/Contents/SharedSupport/bin/subl' - alias code='/Applications/Visual\ Studio\ Code.app/Contents/Resources/app/bin/code' - -#### 测试 -使用 vcCode 打开 - - $ code . - -> 本文首次发布于 [BY Blog](http://qiubaiying.github.io), 作者 [@柏荧(BY)](http://github.com/qiubaiying) ,转载请保留原文链接. \ No newline at end of file diff --git "a/_posts/2017-03-23-AsyncDisplayKit-2.0-\346\225\231\347\250\213\357\274\232\345\205\245\351\227\250\343\200\214\350\257\221\343\200\215.md" "b/_posts/2017-03-23-AsyncDisplayKit-2.0-\346\225\231\347\250\213\357\274\232\345\205\245\351\227\250\343\200\214\350\257\221\343\200\215.md" deleted file mode 100644 index 6cb767322df..00000000000 --- "a/_posts/2017-03-23-AsyncDisplayKit-2.0-\346\225\231\347\250\213\357\274\232\345\205\245\351\227\250\343\200\214\350\257\221\343\200\215.md" +++ /dev/null @@ -1,483 +0,0 @@ ---- -layout: post -title: AsyncDisplayKit 2.0 教程:入门「译」 -subtitle: AsyncDisplayKit Tutorial:Getting Started -date: 2017-03-23 -author: BY -header-img: img/post-bg-iWatch.jpg -catalog: true -tags: - - iOS - - Objective-C - - AsyncDisplayKit - - 开源库 ---- - -> AsyncDisplayKit 2.0 Tutorial: Getting Started - -# 前言 - -> "艺术是你任何能做到极致的事" - -[**AsyncDisplayKit**](http://asyncdisplaykit.org/) 是一个UI框架,最初诞生于 Facebook 的 **Paper** 应用程序。它是为了解决 Paper 团队面临的核心问题之一:如何尽可能缓解主线程的压力? - -现在,许多应用程序的用户体验,很大程度上依赖于持续手势和物理动画。至少,你的UI可能是依赖于某种形式的 `scrollView`。 - -这些类型的用户界面完全依赖于主线程,并且对主线程阻塞非常敏感。主线程阻塞将导致丢帧,降低用户的体验。 - -一些主线程开销较大的任务包括: - -- **计算尺寸和布局**:比如 `-heightForRowAtIndexPath:`,或者在UILbel中调用 `-sizeThatFits` 以及[指数上升](http://floriankugler.com/2013/04/22/auto-layout-performance-on-ios/)的 `AutoLayout‘s`布局计算。 -- **图像解码**:想要在一个 image view 中使用 `UIImage`,首先要进行解码。 -- **绘图**:复杂的文本以及手动绘制渐变和阴影。 -- **对象生命周期**:创建,操纵和销毁系统对象(即创建一个UIView) - -当正确使用时,AsyncDisplayKit 允许您在默认情况下异步执行所有测量、布局和渲染。无需任何额外的优化,一个应用程序可以减少约一个数量级的主线程开销。 - -除了这些性能优势,酷炫的 AsyncDisplayKit 还为开发者提供的便利接口,用简洁的代码就能完成复杂的功能。 - -在这两部分 **AsyncDisplayKit 2.0** 教程中,你将掌握使用ASDK构建一个实用的和动态的应用程序的所有要素。在第一部分中,你将要学习一些在你构建应用程序时可以用到的宏观思想。在[第二部分](https://www.raywenderlich.com/124696/asyncdisplaykit-2-0-tutorial-automatic-layout)中,你将学习如何构建自己 node 的 subclass,以及如何使用ASDK强大的布局引擎。为了更好的完成本教程,你需要会使用 Xcode 以及 熟悉 Objective-C。 - -> **免责声明**:ASDK不兼容 [Interface Builder和AutoLayout](http://www.youtube.com/watch?v=RY_X7l1g79Q&feature=youtu.be&t=29m37s),因此,您将不会在本教程中使用它们,虽然ASDK完全支持Swift(除了ComponentKit),许多开发者仍在使用 -Objective-C。免费App排行榜前100大多数都没有使用Swift(至少6个使用ASDK)。出于这些原因,本系列将重点介绍 Objective-C。话虽这么说,我们已经包括了一个Swift版本的实例项目。(嘴上说没有,代码还是很诚实的😂~) - -# 开始 - -首先,[下载初始项目](https://koenig-media.raywenderlich.com/uploads/2016/12/AsyncDisplayKit-Starter-4.zip)。 - -该项目使用 [CocoaPods](https://cocoapods.org/) 来拉入AsyncDisplayKit。所以,在正常的 CocoaPods 体系下,打开 `RainforestStarter.xcworkspace` 而不是`RainforestStarter.xcodeproj`。 - -> **注意**:需要网络连接才能完成本教程。 - -构建并运行以查看包含 `UITableView` 动物列表的应用程序。如果你看过了代码,`AnimalTableController` 你会发现这是一个正常且熟悉的 `UITableViewController` 类。 - -> **注意**:确保在真机上运行本教程中的代码,而不是在模拟器中运行。 - -向上滑动你将看到帧数丢失引起的卡顿。你不需要启动控制台,以便能发现到这个应用程序需要在性能方面上的一些优化。 - -你可以通过 **AsyncDisplayKit** 的力量来解决这个问题 - -# ASDisplayNode 简介 - -`ASDisplayNode` 是ASDK的核心类,它只是一个类似于 MVC 中的 “View” 一样的`UIView` 或 `CALayer`。认识一个 node 的最佳方法是参照你已经熟悉的 `UIViews` 和 `CALayers` 之间的关系。 - -记住,iOS应用程序中的所有在屏幕上的显示都通过`CALayer`对象表示的。`UIViews` 创建并且拥有一个底层的 `CALayer`,并为他们添加触摸处理和其他交互功能。`UIView` 并不是 `CALayer` 的子类,而是相互环绕,扩展其功能。 - -![](https://koenig-media.raywenderlich.com/uploads/2016/03/view-layer-480x229.png) - -这种抽象的情况下扩展 `ASDisplayNode`:您可以将它们视为包装一个 view,就像在 view 上添加一个 layer 一样。 - -通常由 Node 创建的一个常规的view,其创建和配置都在行队列中执行,并且异步渲染。 - -![](https://koenig-media.raywenderlich.com/uploads/2016/03/node-view-layer-480x161.png) - -幸运的是,用于处理 Node 的 API 对于任何使用过的 `UIViews` 或者 `CALayers` 的人来说应该异常的熟悉。所有 View 的属性都可以等效为 Node 类。你可以访问基础的 view 或者 layer 本身,就像是访问 `view.layer` 一样 - -# 节点容器(The Node Containers) - -虽然 Node 本身提供了巨大的性能改进的可能,但真正的强大的是它们与四个容器类结合使用时产生的黑魔法。 - -这些类包括: - -- **ASViewController**:一个 `UIViewController` 的子类,允许你提供要管理的 Node。 -- **ASCollectionNode** and **ASTableNode**:Node 等效于 `UICollectionView` 和 `UITableView`,其子类实际上保留在底层。 -- **ASPagerNode**:一个`ASCollectionNode`的子类,提供极好的滑动性能相比与 `UIKit` 的 `UIPageViewController` 来说。 - -![](https://koenig-media.raywenderlich.com/uploads/2016/03/ragecomic-480x229.png) - -说得好,但真正的黑魔法来自 `ASRangeController` 这些类用于影响所包含的 Node 的行为。现在,跟着我并把你们的脑袋放空吧~ - -# TableNode - -你要做的第一件事就是将当前 TableView 替换为 TableNode。这个没什么难度。 - -#### 将 TableView 替换为 TableNode - -首先,进入到 `AnimalTableController.m` 。在此类中添加下面代码下面代码。 - - #import - -这就导入了 ASDK 框架。 - -然后,我们继续,替换 `tableView` 的声明属性 : - - @property ( strong,nonatomic ) UITableView * tableView; - -替换为 `tableNode`: - - @property ( strong,nonatomic ) ASTableNode * tableNode; - -这将导致这个类中很多地方报错,但不要慌张! - -![](https://koenig-media.raywenderlich.com/uploads/2016/03/butBut-1-480x229.png) - -别担心。这些错误和警告将作为你的向导,将代码转换成我们想要的。 - -`-viewDidLoad` 中的报错是理所当然,因为 `tableView` 已经被替换掉。我不会让你通过 `tableNode` 替换 所有的 `tableView` 实例(我的意思是,查找和替换并非那么难),但是如果你做了,你会看到: - -1. 你应该为 `ASTableNode` 分配一个属性。 -2. table Node 没有调用 `-registerClass:forCellReuseIdentifier:` 方法。 -3. 你不能添加一个 node 到 subview - -此时,你应该将 `-viewDidLoad` 中的方法替换为: - - - (void)viewDidLoad { - [super viewDidLoad]; - - [self.view addSubnode:self.tableNode]; - [self applyStyle]; - } - -这里要注意一个有趣的情况,你调用的是 UIView 的一个 `-addSubnode:` 方法,该方法是通过 category 添加到 `UIView` 上的,等效于: - - [self.view addSubview:self.tableNode.view]; - -接下来,修改 `-viewWillLayoutSubviews` 中的代码: - - - (void)viewWillLayoutSubviews { - [super viewWillLayoutSubviews]; - - self.tableNode.frame = self.view.bounds; - } - -这样就替换用 `self.tableNode` 替换了 `self.tableView`,并且设置了 table 的 Frame - -继续修改 `-applyStyle` 方法中的代码为: - - - (void)applyStyle { - self.view.backgroundColor = [UIColor blackColor]; - self.tableNode.view.separatorStyle = UITableViewCellSeparatorStyleNone; - } - -这是唯一设置 table 的 `separatorStyle` 的一行代码。注意 tableNode 的 view 是如何访问 table 的 `separatorStyle` 属性的。`ASTableNode` 不会暴露所有`UITableView`的的属性,所以你必须通过 tableNode 底层的 `UITableView` 实例去设置 `UITableView ` 的特殊属性。 - -然后,在 `-initWithAnimals:` 方法中添加。 - - _tableNode = [[ASTableNode alloc] initWithStyle:UITableViewStylePlain]; - -并且在 **return** 之前,调用: - - - [self wireDelegation]; - -这就会在初始化 `AnimalTableController` 的时候,创建了一个 tableNode 并且调用 `-wireDelegation` 方法 设置 tableNode 的 代理。 - -#### 设置 TableNode 的 DataSource & Delegate - -类似于 `UITableView`,`ASTableNode` 也使用 DataSource 和 Delegate 来设置本身。TableNode 的`ASTableDataSource` 和 `ASTableDelegate` protocols 非常类似于 `UITableViewDataSource` 和 `UITableViewDelegate`。 - -事实上,虽然他们定义了一些完全相同的方法,如 `-tableNode:numberOfRowsInSection:`,但两组协议也不完全相同,因为 `ASTableNode` 行为和`UITableView`还以所有不同的。 - -找到 `-wireDelegation` 方法, 并用 `tableNode` 替换 `tableView`: - - - (void)wireDelegation { - self.tableNode.dataSource = self; - self.tableNode.delegate = self; - } - -现在, 你会收到警告, `AnimalTableController` 实际上不符合协议。目前,`AnimalTableController` 仅遵循 `UITableViewDataSource` 和 `UITableViewDelegate`协议。在下面的章节中,我们将遵循这些协议,使我们能够使用 tableNode 的功能。 - -#### 遵循 ASTableDataSource - -在 `AnimalTableController.m` 开头的地方找到 `AnimalTableController` 的 `DataSource` 扩展声明: - -```objc -@interface AnimalTableController (DataSource) -@end -``` - -用 `ASTableDataSource` 替换 `UITableViewDataSource`为: - - @interface AnimalTableController (DataSource) - @end - -现在,`AnimalTableController` 已经遵循了 `AnimalTableController` 协议。本就该如此了。 - -导航到 `AnimalTableController.m` 的底部并找到 `DataSource` category 的实现。 - -首先,将 `UITableViewDataSource` 的 `-tableView:numberOfRowsInSection:`方法, -更改为`ASTableDataSource` 的版本。 - -```objc -- (NSInteger)tableNode:(ASTableNode *)tableNode numberOfRowsInSection:(NSInteger)section { - return self.animals.count; -} -``` - -接着,`ASTableNodes` 的 cells 会以不同于 `UITableView` 的方式返回。用下面的代码替换 `-tableView:cellForRowAtIndexPath:` 以适应新的规则。 - -```objc -// 1 -- (ASCellNodeBlock)tableNode:(ASTableView *)tableView nodeBlockForRowAtIndexPath:(NSIndexPath *)indexPath { - - // 2 - RainforestCardInfo *animal = self.animals[indexPath.row]; - - // 3 return ASCellNodeBlock - return ^{ - // 4 - CardNode *cardNode = [[CardNode alloc] initWithAnimal:animal]; - - //You'll add something extra here later... - return cardNode; - }; -} -``` -让我们整理一下: - -1. ASDK 中的 `ASCellNode` 等价于 `UITableViewCell` 或者 `UICollectionViewCell`。要注意的是这个方法返回的是一个 `ASCellNodeBlock`,`ASTableNode` 维持着内部所有的 Cell,每个 indexPath 对应一个 block,并且随时准备进行初始化。 -2. 你的首要任务是通过数据模型构建cell。这是非常重要的一步,要注意!你获取数据后在 下面的 block 处理。不要在 block 里引用`indexPath`,以防止 block 运行前的数据变动。 -3. 然后返回一个 block,其返回值必须为 `ASCellNode`。 -4. 没有必要担心Cell的复用以及初始化一个Cell的方法。您可能会注意到您现在返回了`CardNode`,而不是`CardCell`。 - -这让我想到一个重要的点。或许你已经了解到,**使用 ASDK 不需要复用 cell**,好吧,我已经说了两遍了,但能记住就好。请随意删除顶部`kCellReuseIdentifier`的定义吧 - -```objc -static NSString *kCellReuseIdentifier = @"CellReuseIdentifier"; -``` - -你不必再担心 `-prepareForReuse`了 - -#### 遵循 ASTableDelegate - -在 `AnimalTableController.m` 顶部,找到以下Delegate类别接口声明: - -```objc -@interface AnimalTableController (Delegate) -@end -``` - -用 `ASTableDelegate` 替换 `UITableViewDelegate`: - -```objc -@interface AnimalTableController (Delegate) -@end -``` - -现在 `AnimalTableController` 已经遵循了 `ASTableDelegate`,是时候做处理了。在 `AnimalTableController.m` 底部找到 `Delegate` 分类的实现。 - -我们都知道,每个 `UITableView` 至少都要提供一个 `-tableView:heightForRowAtIndexPath:` 实现方法,因为每个 cell 的高度都由代理计算和返回。 - -`ASTableDelegate` 中没有 `-tableView:heightForRowAtIndexPath:`。再 ASDK 中,所有的 `ASCellNode` 都负责确定自己的大小。你可以选择为单元格定义最小和最大尺寸,而不是提供静态高度。这种情况下,你希望每个cell的高度至少为屏幕的 2/3。 - -现在不用担心太多,这个会在第二部分中介绍。 - -现在只需要替换 `-tableView:heightForRowAtIndexPath:`为: - -``` objc -- (ASSizeRange)tableView:(ASTableView *)tableNode - constrainedSizeForRowAtIndexPath:(NSIndexPath *)indexPath { - CGFloat width = [UIScreen mainScreen].bounds.size.width; - CGSize min = CGSizeMake(width, ([UIScreen mainScreen].bounds.size.height/3) * 2); - CGSize max = CGSizeMake(width, INFINITY); - return ASSizeRangeMake(min, max); -} -``` - -经过我们的辛勤劳动,重新编译、运行项目,看看发生了什么。 - -![](https://koenig-media.raywenderlich.com/uploads/2016/06/InfiniteScrollingGif.gif) - -真是一个流畅的 `tableView`!一旦你开始做了,那就让我们做的更好吧! - -## 无限滚动 - -在大多数应用中,服务器的数据点的个数往往会多于当前 tableView 中显示的单元格数量。这意味着,你必须通过某些手段做无缝处理,以便用户刷完当前数据列表时从服务端加载新的数据。 - -很多时候,这是通过手动观察滚动视图方法中的内容偏移来处理 `scrollViewDidScroll:`, 使用 ASDK, 有一种更具说明性的处理方式。相反的,你可以预先确定好你需要加载的页数。 - -你要做的第一件事是取消已经存在的方法的注释。在 `AnimalTableController.m` 的结尾,取消 `Helpers` 分类中的两个方法。你可以认为 `-retrieveNextPageWithCompletion:` 是你的网络调用,而 `-insertNewRowsInTableNode:` 是个非常典型的再表中添加新的元素的方法。 - -接下来,在 `-viewDidLoad` 添加: - -```objc -self.tableNode.view.leadingScreensForBatching = 1.0; // overriding default of 2.0 -``` - -设置 `leadingScreensForBatching` 为 **1.0** 意味着当用户滑动一个屏的时候,就会载入新的数据。 - -继续,在 `Delegate` 分类中实现: - -```objc -- (BOOL)shouldBatchFetchForTableNode:(ASTableNode *)tableNode { - return YES; -} -``` - -该方法用于告诉 `tableView` 是否继续请求新的数据。如果返回 `NO`,则在到达 API 数据末尾时,不会再不会发出任何请求。 - -因为你希望无限滚动,那就返回 `YES`,以确保总是请求新的数据。 - -接下来,还要添加: - -```objc -- (void)tableNode:(ASTableNode *)tableNode willBeginBatchFetchWithContext:(ASBatchContext *)context { - //1 - [self retrieveNextPageWithCompletion:^(NSArray *animals) { - //2 - [self insertNewRowsInTableNode:animals]; - //3 - [context completeBatchFetching:YES]; - }]; -} -``` - -该方法在用户滑动到 table 的末端并,且 `-shouldBatchFetchForTableNode:` 方法返回 `YES` 时被调用。 - -让我们回顾下上面的章节: - -1. 首先,你要请求新的 animals 数据来展示。通常是通过 API 来获取的一组array。 -2. 完成后,用新下载的数据更新 tableView -3. 最后,确保 `-completeBatchFetching:`返回的是`YES`,即大功告成。在完成操作之前,不会进行新的数据请求。 - -Build and Run,并且不停的滚呀滚。你将会看到不停的看到一只鸟,他们是无限的。 - -![](https://koenig-media.raywenderlich.com/uploads/2016/06/InfiniteScrollingGif.gif) - -## 智能预加载 - -你在工作中是否曾经遇到需要预先加载内容到 scrollView 或者 pageView 控制器中?也许你正在处理一个充满屏幕 image ,并且总是希望在接下来的几张图片加载时处于等待状态,所以用户很少看到占位符。 - -![](https://koenig-media.raywenderlich.com/uploads/2016/03/iThinkIveGotThis-480x229.png) - -当你再这样的体系下工作时,你很快就会意识到有很多问题要考虑。 - -- 你占用了多少内存 -- 你应该提前多久加载内容 -- 你决定什么时候忽略用户的交互反映 - -并且当你考虑到多个维度的内容时,将些问题将会变得更加复杂。假设你有一个`pageViewController`,里面每个 `viewController` 都带有一个 `collectionView`。现在,你就需要考虑如何在两个方向上动态加载内容。同时,还要对每个设备进行优化。 - -![](https://koenig-media.raywenderlich.com/uploads/2016/12/officespaceboss.png) - -还记得告诉你 `ASRangeController` 是不重要的吗?现在,这将是我们的重点。 - -在每个容器类中,所有包含的 node 都有一个接口状态的概念。在任何给定的时间,一个 node 可以是下面的任意组合: - -- **Preload Range(预载范围)**:通常最远的范围从可见区域。这是当cell的每个 subNode (例如ASNetworkImageNode) 的内容从外源加载,例如API和本地缓存。这与批量获取时,使用用模型对象代表cell本身形成对比。 -- **Display Range(显示范围)**:在这里进行显示任务,例如文本绘制和进行图像解码。 -- **Visible Range(可见范围)**:此时,node 至少有一个像素在屏幕上。 - -![](https://koenig-media.raywenderlich.com/uploads/2016/12/preloadingRanges-small.png) - -这些范围也适用于 **screenfuls** 的度量,并且可以使用 `ASRangeTuningParameters` 属性轻松调整。 - -例如:你正在使用一个 `ASNetworkImageNode`在 gallery 的每个页面中展示图像,当每个cell进入 **Preload Range** 时,会发送网络请求,并且在进入 **Display Range** 时进行图像解码。 - -通常来说,你不必对这些 **Ranges** 太较真。利用好已有的组件,如:`ASNetworkImageNode` 和 `ASTextNode`,通常来说你将会获得极大的便利。 - -> **注意**: 有件不明显的事,这些 **Ranges** 不是堆栈的。相反,它们会在 -**Visible Range** 上重叠和汇聚。如果将显示和预取都设置为一个屏幕,则它们将完全相同。通常数据需要存在才能显示,所以一般预取范围应该稍大一点。那么在 node 到达该范围时,就可以开始显示。 - -通常,该范围的前侧大于后侧。当用户改变其滚动方向时,范围的大小也是相反的,以便于对应用户实际移动的方向。 - -## Node接口的状态回调 - -你可能会疑惑:这些 **Ranges** 是如何正确工作的?很高兴你这样问~ - -系统中的每个 node 都有一个`interfaceState` 属性,是一个带有字段((NS_OPTION)`ASInterfaceState`类型。`ASRangeController` 负责管理 `ASCellNode` 在 `scrolView` 上的移动,每个subNode 都由一个 `interfaceState` 属性做对应的更新。这意味着即使时 tree 中最深的 nodes 也可以相应 `interfaceState` 的变化。 - -幸运的是,我们很少需要直接去操作 node 的 `interfaceState` 上的 二进制位。更常见的做法时,你只需要对某 node 的特定的状态进行更改。这就是接口的状态回调。 - -#### Node 命名 - -为了看到一个 node 的各种状态,给它命名时很有必要的。这样,你就可以监测每个 node 的数据加载、内容成、屏幕展示以及所以的事情。 - -回到代码`-tableNode:nodeBlockForRowAtIndexPath:`,添加一句注释 - - //You'll add something extra here later... - -在它的下面,给 `cardNode` 添加一个 `debugName`: - -```objc -cardNode.debugName = [NSString stringWithFormat:@"cell %zd", indexPath.row]; -``` -#### 观察 Cells - -进入 `CardNode_InterfaceCallbacks.m` 中,你可以找到六种追踪 node 在 ranges 中的状态的方法。取消注释,Build and Run。打开你的控制台,然后慢慢滑动 table。对照你的滑动,观察cell在对应的状态变化。 - -> **注意**: 大多数情况下,你只要关心 `-didEnterVisibleState` 或 `-didExitVisibleState` 方法对 `ASInterfaceState` 的改变。或者说,已经为你做好了许多引擎。你可以查看 `ASNetworkImageNode` 中的代码,看看你集成的通过`Preload` 和 `Display` 状态实现的功能。 所有 node 网络图片的请求和解码,以及内存的释放都是自动完成,不费吹灰之力。 - -## 智能预加载(续) - -在 **2.0** 版本中,已经介绍了多个维度上智能与加载的概念。假设你有一个竖直滚动的`tableView`,在其中某些Cell包含了水平滚动的 `collectionView`。 - -![](https://koenig-media.raywenderlich.com/uploads/2016/07/proaldGif%5E2.gif) - -尽管现在的技术能够实现,但你不会希望在到达可见区域之前预先加载全部的 collection。相反的,两个方向上的 scrollView 都由各自的 `ASRangeController` 单独控制自己的 range 参数。 - - -## 来到二次元 - -现在,你已经有了完整的 `AnimalTableController`, 你可以把它做为 ASPagerNode 的一个page。 - -项目已经提前写好了控制器的代码,首先进入 **`AppDelegate.m`**。 - -找到 `-installRootViewController` 的下面代码: - -```objc -AnimalTableController *vc = [[AnimalTableController alloc] - initWithAnimals:[RainforestCardInfo allAnimals]]; -``` -替换为: - -```obcj -AnimalPagerController *vc = [[AnimalPagerController alloc] init]; -``` - -然后,跳到 **`AnimalPagerController.m`** 在 `-init` 方法中添加创建 `pager` 方法以及 `dataSource` 的数据源: - -```objc -_pagerNode = [[ASPagerNode alloc] init]; -_pagerNode.dataSource = self; -``` - -pagerNode 是 `ASCollectionNode` 的子类,使用方法与 `UIPageViewController` 一样。API 实际上比 `UIPageViewController` 要简单的多。 - -接下来要实现 pager 的 `dataSource` 方法,在底部找到 `ASPagerDataSource` 分类. - -首先,告诉 pager 有几个页面。实际上当前的 animal 数组中有三组不同动物,我们需要重写 `-numberOfPagesInPagerNode:`方法: - -```objc -- (NSInteger)numberOfPagesInPagerNode:(ASPagerNode *)pagerNode { - return self.animals.count; -} -``` - -然后,你需要实现 `-pagerNode:nodeAtIndex` 方法,类似于先前实现的 ASTableNode 的 `dataSource` 方法。 - -```objc -- (ASCellNode *)pagerNode:(ASPagerNode *)pagerNode nodeAtIndex:(NSInteger)index { - // 1 - NSArray *animals = self.animals[index]; - // 2 - ASCellNode *node = [[ASCellNode alloc] initWithViewControllerBlock:^UIViewController * _Nonnull{ - return [[AnimalTableController alloc] initWithAnimals:animals]; - } didLoadBlock:nil]; - - return node; -} -``` - -我们来总结下这部分: - -1. 尽管这个版本中没有进行模块化分,但是首先获取数据模型是个好习惯。 -2. 这一次,你使用的正是强大的 `-initWithViewControllerBlock:` 构造器。你所要做的就是返回一个block,这个 block 返回你提前设置好的 tableNodeController,它将自动展示在pager 的 页面中。真是太酷了😏~ - -一旦你添加了这个方法,你将拥有一个完整功能的 Pagar,其中的 cell 是从你原先创建的 `tableNodeController` 生成的。现在,就可以在用户的垂直和水平滑动下,充分发挥二维预加载的功能! - -要查看这个 AsyncDisplayKit 2.0 教程完整的项目,[点击这里进行下载](https://koenig-media.raywenderlich.com/uploads/2016/12/AsyncDisplayKit-Finished-4.zip)。如果你想查看swift版本,[这里也有](https://koenig-media.raywenderlich.com/uploads/2016/12/RainForestSwift-1.zip)。 - -准备好之后,请转到该项目的第2部分,了解 AsyncDisplayKit 2.0 引入的强大的新的布局系统。 - -如果你想先进行深入了解,你可以阅读 [AsyncDisplayKit主页](https://asyncdisplaykit.org/) 的文档。Scott Goodson(AsyncDisplayKit的原创作者)也有几个你可能会感兴趣的话题。最近的话题很好的概述了一些框架对处理大图片存在问题的的尝试。 - -你可能会对 [Paper的构建](https://www.youtube.com/watch?v=OiY1cheLpmI) 感兴趣。虽然当时并没有开源,并且有许多地方发生了变化,但看到这一切的开始还是挺有意思的。 - -这里有一个 [public Skack channel](https://github.com/facebook/AsyncDisplayKit/issues/1582) ,欢迎来提问~ - -# 著作权声明 - -本文译自 [AsyncDisplayKit 2.0 Tutorial: Getting Started](https://www.raywenderlich.com/124311/asyncdisplaykit-2-0-tutorial-getting-started) . - -由[@柏荧(BY)](http://github.com/qiubaiying)进行翻译,首次发布于 [BY Blog](http://qiubaiying.github.io),转载请保留原文链接. \ No newline at end of file diff --git "a/_posts/2017-03-30-Swift 3.1-\347\232\204\346\226\260\345\217\230\345\214\226\343\200\214\350\257\221\343\200\215.md" "b/_posts/2017-03-30-Swift 3.1-\347\232\204\346\226\260\345\217\230\345\214\226\343\200\214\350\257\221\343\200\215.md" deleted file mode 100644 index 73d99ab2e97..00000000000 --- "a/_posts/2017-03-30-Swift 3.1-\347\232\204\346\226\260\345\217\230\345\214\226\343\200\214\350\257\221\343\200\215.md" +++ /dev/null @@ -1,417 +0,0 @@ ---- -layout: post -title: Swift 3.1 的新变化「译」 -subtitle: What’s New in Swift 3.1? -date: 2017-03-30 -author: BY -header-img: img/post-bg-iWatch.jpg -catalog: true -tags: - - iOS - - Swift - - Xcode ---- - - -![](https://koenig-media.raywenderlich.com/uploads/2017/03/WhatsNewSwift3.1-feature-1-250x250.png) - -Xcode 8.3 和 Swift 3.1 现在已经发布了(3/28)! - -可以通过 [AppStore](https://itunes.apple.com/us/app/xcode/id497799835?ls=1&mt=12#) 或 [Apple Developer](https://developer.apple.com/download/more/) 进行下载 - -![](https://ww2.sinaimg.cn/large/006tNbRwgy1fe2it3xwt6j30s50g3mya.jpg) - -Xcode 8.3 优化了 Objective-C 与 Swift 混编项目的编译速度. - -Swift 3.1 版本包含一些期待已久的 [Swift package manager](https://github.com/apple/swift-package-manager) 功能和语法本身的改进。 - -如果您没有密切关注 [Swift Evolution](https://github.com/apple/swift-evolution) 进程,请继续阅读 - 本文非常适合您! - -在本文中,我将强调Swift 3.1中最重要的变化,这将对您的代码产生重大影响。我们来吧!😃 - -## 开始 - -Swift 3.1与Swift 3.0源代码兼容,因此如果您已经使用Xcode 中的 `Edit \ Convert \ To Current Swift Syntax ...` 将项目迁移到Swift 3.0,新功能将不会破坏您的代码。不过,苹果已经在Xcode 8.3中支持Swift 2.3。所以如果你还没有从Swift 2.3迁移,现在是时候这样做了! - -在下面的部分,您会看到链接的标签,如`[SE-0001]`。这些是 [Swift Evolution](https://apple.github.io/swift-evolution/) 提案号码。我已经列出了每个提案的链接,以便您可以发现每个特定更改的完整详细信息。我建议您尝试在Playground上验证新的功能,以便更好地了解所有更改的内容。 - -> Note:如果你想了解 swift 3.0 中的新功能,可以看[这篇文章](https://www.raywenderlich.com/135655/whats-new-swift-3)。 - -## 语法改进 - -首先,我们来看看这个版本中的语法改进,包括关于数值类型的`可失败构造器`(`Failable Initializers`),新的序列函数等等。 - -#### 可失败的数值转换构造器(Failable Numeric Conversion Initializers) - -Swift 3.1 为所有数值类型 `(Int, Int8, Int16, Int32, Int64, UInt, UInt8, UInt16, UInt32, UInt64, Float, Float80, Double)` 添加了[可失败构造器](https://www.swiftmi.com/topic/121.html)。 - -这个功能非常有用,例如,以安全、可恢复的方式处理外源松散类型数据的转换,下面来看 Student 的 JSON 数组的处理: - -```swift -class Student { - let name: String - let grade: Int - - init?(json: [String: Any]) { - guard let name = json["name"] as? String, - let gradeString = json["grade"] as? String, - let gradeDouble = Double(gradeString), - let grade = Int(exactly: gradeDouble) // <-- 3.1 的改动在这 - else { - return nil - } - self.name = name - self.grade = grade - } -} - -func makeStudents(with data: Data) -> [Student] { - guard let json = try? JSONSerialization.jsonObject(with: data, options: .allowFragments), - let jsonArray = json as? [[String: Any]] else { - return [] - } - return jsonArray.flatMap(Student.init) -} - -let rawStudents = "[{\"name\":\"Ray\", \"grade\":\"5.0\"}, {\"name\":\"Matt\", \"grade\":\"6\"}, - {\"name\":\"Chris\", \"grade\":\"6.33\"}, {\"name\":\"Cosmin\", \"grade\":\"7\"}, - {\"name\":\"Steven\", \"grade\":\"7.5\"}]" -let data = rawStudents.data(using: .utf8)! -let students = makeStudents(with: data) -dump(students) // [(name: "Ray", grade: 5), (name: "Matt", grade: 6), (name: "Cosmin", grade: 7)] -``` -在 `Student` 类中使用了一个可失败构造器将 `grade` 属性从 `Double ` 转变为 `Int`,像这样 - -```swift -let grade = Int(exactly: gradeDouble) -``` - -如果`gradeDouble`不是整数,例如6.33,它将失败。如果它可以用一个正确的表示Int,例如6.0,它将成功。 - -> Note:虽然`throwing initializers` 可以用来替代 `failable initializers`。但是使用 `failable initializers` 会更好,更符合人的思维。 - -##### 新的序列函数(Sequence Functions) - -swift3.1添加了两个新的标准库函数在 `Sequence` 协议中:`prefix(while:)``和prefix(while:)`[[SE-0045]](https://github.com/apple/swift-evolution/blob/master/proposals/0045-scan-takewhile-dropwhile.md)。 - -构造一个[斐波纳契](https://en.wikipedia.org/wiki/Fibonacci_number)无限序列: - -```swift -let fibonacci = sequence(state: (0, 1)) { - (state: inout (Int, Int)) -> Int? in - defer {state = (state.1, state.0 + state.1)} - return state.0 -} -``` - -在Swift 3.0中,您只需指定`迭代次数`即可遍历fibonacci序列: - -```swift -// Swift 3.0 -for number in fibonacci.prefix(10) { - print(number) // 0 1 1 2 3 5 8 13 21 34 -} -``` - -在swift 3.1中,您可以使用`prefix(while:)`和`drop(while:)`获得符合条件在两个给定值之间的序列中的所有元素,就像这样: - -```swift -// Swift 3.1 -let interval = fibonacci.prefix(while: {$0 < 1000}).drop(while: {$0 < 100}) -for element in interval { - print(element) // 144 233 377 610 987 -} -``` - -`prefix(while:)`返回满足某个谓词的最长子序列。它从序列的开头开始,并停在给定闭包返回false的第一个元素上。 - -`drop(while:)` 相反:它返回从给定关闭返回false的第一个元素开始的子序列,并在序列结尾完成。 - -> Note:这种情况,可以使用尾随闭包的写法: -> -> ```swift -> let interval = fibonacci.prefix{$0 < 1000}.drop{$0 < 100} -> ``` - - -## Concrete Constrained Extensions(姑且翻译为类的约束扩展吧) - -Swift 3.1允许您扩展具有类型约束的通用类型。以前,你不能像这样扩展类型,因为约束必须是一个协议。我们来看一个例子。 - -例如,Ruby on Rails提供了一种`isBlank`检查用户输入的非常有用的方法。以下是在Swift 3.0中用 `String` 类型的扩展实现这个**计算型属性**: - -```swift -// Swift 3.0 -extension String { - var isBlank: Bool { - return trimmingCharacters(in: .whitespaces).isEmpty - } -} - -let abc = " " -let def = "x" - -abc.isBlank // true -def.isBlank // false -``` - -如果你希望`isBlank`计算型属性为一个可选值所用,在swift 3.0中,你将要这样做 - -```swift -// Swift 3.0 -protocol StringProvider { - var string: String {get} -} - -extension String: StringProvider { - var string: String { - return self - } -} - -extension Optional where Wrapped: StringProvider { - var isBlank: Bool { - return self?.string.isBlank ?? true - } -} - -let foo: String? = nil -let bar: String? = " " -let baz: String? = "x" - -foo.isBlank // true -bar.isBlank // true -baz.isBlank // false -``` - -这创建了一个采用 `String` 的 `StringProvider` 协议而在你使用StringProvider扩展可选的 wrapped 类型时,添加isBlank方法。 - -Swift 3.1中,用来替代协议方法,扩展具体类型的方法像这样: - -```swift -// Swift 3.1 -extension Optional where Wrapped == String { - var isBlank: Bool { - return self?.isBlank ?? true - } -} -``` -这就用更少的代码实现了和原先相同的功能~ - -## 泛型嵌套(Nested Generics) - -Swift 3.1允许您将嵌套类型与泛型混合。作为一个练习,考虑这个(不是太疯狂)的例子。每当某个团队领导raywenderlich.com想在博客上发布一篇文章时,他会分配一批专门的开发人员来处理这个问题,以满足网站的高质量标准: - -```swift -class Team { - enum TeamType { - case swift - case iOS - case macOS - } - - class BlogPost { - enum BlogPostType { - case tutorial - case article - } - - let title: T - let type: BlogPostType - let category: TeamType - let publishDate: Date - - init(title: T, type: BlogPostType, category: TeamType, publishDate: Date) { - self.title = title - self.type = type - self.category = category - self.publishDate = publishDate - } - } - - let type: TeamType - let author: T - let teamLead: T - let blogPost: BlogPost - - init(type: TeamType, author: T, teamLead: T, blogPost: BlogPost) { - self.type = type - self.author = author - self.teamLead = teamLead - self.blogPost = blogPost - } -} -``` - -将`BlogPost`内部类嵌套在其对应的`Team`外部类中,并使两个类都通用。这是团队如何寻找我在网站上发布的教程和文章: - -```swift -Team(type: .swift, author: "Cosmin Pupăză", teamLead: "Ray Fix", - blogPost: Team.BlogPost(title: "Pattern Matching", type: .tutorial, - category: .swift, publishDate: Date())) - -Team(type: .swift, author: "Cosmin Pupăză", teamLead: "Ray Fix", - blogPost: Team.BlogPost(title: "What's New in Swift 3.1?", type: .article, - category: .swift, publishDate: Date())) -``` - -但实际上,在这种情况下,您可以简化该代码。如果嵌套的内部类型使用通用外部类型,那么它默认继承父类的类型。因此,您不需要如此声明: - -```swift -class Team { - // original code - - class BlogPost { - // original code - } - - // original code - let blogPost: BlogPost - - init(type: TeamType, author: T, teamLead: T, blogPost: BlogPost) { - // original code - } -} -``` - -> Note:如果您想了解更多关于Swift中的**泛型**,请阅读我们最近更新的[Swift泛型入门的教程](https://www.raywenderlich.com/154371/swift-generics-tutorial-getting-started)。 - -## Swift版本的可用性 - -您可以使用**#if swift(>= N)** `静态构造`来检查特定的Swift版本: - -```swift -// Swift 3.0 -#if swift(>=3.1) - func intVersion(number: Double) -> Int? { - return Int(exactly: number) - } -#elseif swift(>=3.0) - func intVersion(number: Double) -> Int { - return Int(number) - } -#endif -``` - -然而,当使用Swift标准库时,这种方法有一个主要缺点。它需要为每个受支持的旧语言版本编译标准库。这是因为当您以向后兼容模式运行Swift编译器时,例如您要使用Swift 3.0行为,则需要使用针对该特定兼容性版本编译的标准库版本。如果您使用版本3.1模式编译的,那么您根本就没有正确的代码 - -因此,@available除了现有平台版本 [[SE-0141]](https://github.com/apple/swift-evolution/blob/master/proposals/0141-available-by-swift-version.md) 之外,Swift 3.1扩展了该属性以支持指定Swift版本号: - -```swift -// Swift 3.1 - -@available(swift 3.1) -func intVersion(number: Double) -> Int? { - return Int(exactly: number) -} - -@available(swift, introduced: 3.0, obsoleted: 3.1) -func intVersion(number: Double) -> Int { - return Int(number) -} -``` - -这个新功能提供了与`intVersionSwift`版本有关的方法相同的行为。但是,它只允许像标准库这样的库被编译一次。编译器然后简单地选择可用于所选择的给定兼容性版本的功能。 - -> Note:注意:如果您想了解更多关于Swift 的`可用性属性( availability attributes)`,请参阅我们关于[Swift中可用性属性的教程](https://www.raywenderlich.com/139077/availability-attributes-swift)。 - -## 逃逸闭包(Escaping Closures) - -在Swift 3.0 [[ SE-0103 ]](https://github.com/apple/swift-evolution/blob/master/proposals/0103-make-noescape-default.md) 中函数中的闭包的参数是默认是不逃逸的(non-escaping)。在Swift 3.1中,您可以使用新的函数`withoutActuallyEscaping()`将非逃逸闭包转换为临时逃逸。 - -```swift -func perform(_ f: () -> Void, simultaneouslyWith g: () -> Void, - on queue: DispatchQueue) { - withoutActuallyEscaping(f) { escapableF in // 1 - withoutActuallyEscaping(g) { escapableG in - queue.async(execute: escapableF) // 2 - queue.async(execute: escapableG) - - queue.sync(flags: .barrier) {} // 3 - } // 4 - } -} -``` - -此函数同时加载两个闭包,然后在两个完成之后返回。 - -1. `f` 与 `g` 进入函数后由非逃逸状态,分别转换为逃逸闭包:`escapableF`和`escapableG`。 -2. async(execute:) 的调用需要逃逸闭包,我们在上面已经进行了转换。 -3. 通过运行`sync(flags: .barrier)`,您确保`async(execute:)`方法完全完成,稍后将不会调用闭包。 -4. 在范围内使用 `escapableF` and `escapableG`. - -如果你存储临时逃离闭包(即真正逃脱)这将是一个Bug。未来版本的标准库可以检测这个陷阱,如果你试图调用它们。 - -## Swift Package Manager 更新 - -啊,期待已久的 **Swift Package Manage** 的更新了! - -#### 可编辑软件包(Editable Packages) - -Swift 3.1将`可编辑软件包(editable packages)`的概念添加到Swift软件包管理器 [[ SE-0082 ]](https://github.com/apple/swift-package-manager/blob/master/Documentation/Usage.md#editable-packages)。 - -该`swift package edit`命令使用现有的`Packages`并将其转换为`editable Packages`。使用`--end-edit`命令将 `package manager` 还原回 **规范解析的软件包(canonical resolved packag)**。 - -#### 版本固定(Version Pinning) - -Swift 3.1 添加了版本固定的概念[ SE-0145 ]。该 `pin` 命令 固定一个或所有依赖关系如下所示: - -```swift -$ swift package pin --all // 固定所有的依赖 -$ swift package pin Foo // 固定 Foo 在当前的闭包 -$ swift package pin Foo --version 1.2.3 // 固定 Foo 在 1.2.3 版本 - -``` - -使用`unpin`命令恢复到以前的包版本: - -```swift -$ swift package unpin —all -$ swift package unpin Foo -``` - -Package manager 将每个依赖库的版本固定信息存储在 `Package.pins` 文件中。如果该文件不存在,则Package manager 会自动创建。 - -#### 其他 - -`swift package reset` 命令将会把 Package 重置干净。 - -`swift test --parallel` 命令 执行测试。 - -## 其他改动 - -在 swift 3.1 中还有一些小改动 - -#### 多重返回函数 -C函数返回两次,例如`vfork` 和 `vfork `已经不用了。他们以有趣的方式改变了程序的控制流程。所以 Swift 社区 已经禁止了该行为,以免导致编译错误。 - -#### 自动链接失效(Disable Auto-Linking) - -[Swift Package Manager](https://github.com/apple/swift-package-manager) 禁用了在C语言 [**模块映射(module maps)**](http://nsomar.com/modular-framework-creating-and-using-them/)中的**自动链接**的功能: - -```swift -// Swift 3.0 -module MyCLib { - header “foo.h" - link “MyCLib" - export * -} - -// Swift 3.1 -module MyCLib { - header “foo.h” - export * -} -``` - -## 结语 - -Swift 3.1改善了Swift 3.0的一些功能,为即将到来的Swift 4.0的大改动做准备。这些包括对泛型,正则表达式,更科学的`String`等方面的作出极大的改进。 - -如果你想了解更多,请转到 [Swift standard library diffs](https://developer.apple.com/reference/swift?changes=latest_minor) 或者查看官方的的[Swift CHANGELOG](https://github.com/apple/swift/blob/master/CHANGELOG.md),您可以在其中阅读所有更改的信息。或者您可以使用它来了解 **Swift 4.0** 中的内容! - -## 著作权声明 - -本文译自 [What’s New in Swift 3.1?](https://www.raywenderlich.com/156352/whats-new-in-swift-3-1) - -由[@柏荧(BY)](http://github.com/qiubaiying)进行翻译,首次发布于 [BY Blog](http://qiubaiying.github.io),转载请保留原文链接. \ No newline at end of file diff --git "a/_posts/2017-04-07-\345\274\272\345\214\226-Swift-\344\270\255\347\232\204-print.md" "b/_posts/2017-04-07-\345\274\272\345\214\226-Swift-\344\270\255\347\232\204-print.md" deleted file mode 100644 index c77f601e01d..00000000000 --- "a/_posts/2017-04-07-\345\274\272\345\214\226-Swift-\344\270\255\347\232\204-print.md" +++ /dev/null @@ -1,81 +0,0 @@ ---- -layout: post -title: 强化 swift 中的 print -subtitle: 强化 swift 中的 print 输出函数 -date: 2017-04-07 -author: BY -header-img: img/post-bg-universe.jpg -catalog: true -tags: - - iOS - - Swift - - Xcode - - Debug ---- - -在 Swift 中,最简单的输出方法就是使用 `print()`,在我们关心的地方输出字符串和值。 - -当程序变得非常复杂的时候,我们可能会输出很多内容,而想在其中寻找到我们希望的输出其实并不容易。我们往往需要更好更精确的输出,这包括输出这个 log 的文件,调用的行号以及所处的方法名字等等。 - -在 Swift 中,编译器为我们准备了几个很有用的编译符号,它们分别是: - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
符号类型描述
#fileString包含这个符号的文件的路径
#lineInt符号出现处的行号
#columnInt符号出现处的列
#functionString包含这个符号的方法名字
- - -有了上面的这些编译符号,我们就可以自定义一个输出函数:`printm` - -```swift -public func printm(items: Any..., filename: String = #file, function: String = #function, line: Int = #line) { - print("[\((filename as NSString).lastPathComponent) \(line) \(function)]\n",items) -} -``` - -因为输出是一个很消耗性能的操作,所以在releass环境下需要将输出函数去掉,将上面的函数换成: - -```swift -#if DEBUG - -public func printm(items: Any..., filename: String = #file, function: String = #function, line: Int = #line) { - print("[\((filename as NSString).lastPathComponent) \(line) \(function)]\n",items) -} - -#else - -public func printm(items: Any..., filename: String = #file, function: String = #function, line: Int = #line) { } - -#endif -``` - -#### 参考: - -- [《LOG 输出》](http://swifter.tips/log/) - 王巍 (@ONEVCAT) - - -> 本文首次发布于 [BY Blog](http://qiubaiying.github.io), 作者 [@柏荧(BY)](http://github.com/qiubaiying) ,转载请保留原文链接. \ No newline at end of file diff --git "a/_posts/2017-04-13-CocoaPods-\345\256\211\350\243\205\345\222\214\344\275\277\347\224\250.md" "b/_posts/2017-04-13-CocoaPods-\345\256\211\350\243\205\345\222\214\344\275\277\347\224\250.md" deleted file mode 100644 index e16493dbf1e..00000000000 --- "a/_posts/2017-04-13-CocoaPods-\345\256\211\350\243\205\345\222\214\344\275\277\347\224\250.md" +++ /dev/null @@ -1,181 +0,0 @@ ---- -layout: post -title: CocoaPods 安装和使用 -subtitle: 安装时间 2017/04/13, 环境macOS 12.10.1, cocoapod版本 1.2.1 -date: 2017-04-13 -author: BY -header-img: img/post-bg-hacker.jpg -catalog: true -tags: - - iOS - - Xcode - - Cocopods - - ruby ---- - -# 前言 - -最近换了新机器,重新搭建了开发环境,其中当然包括 **CocoaPods**。 - -装完顺便更新下 **CocoaPods** 安装文档。 - - -# 正文 - - -## 安装 - -**CocoaPods** 是用 ruby 实现的,要想使用它首先需要有 ruby 的环境。 - -#### 升级ruby - - 查看ruby版本 - $ ruby -v - - ruby 2.0.0p648 (2015-12-16 revision 53162) [universal.x86_64-darwin16] - -CocoaPods需要**2.2.2**版本及以上的,我们先升级ruby。 - -使用 **rvm** 安装 ruby - - curl -L get.rvm.io | bash -s stable - source ~/.bashrc - source ~/.bash_profile - -切换 ruby 源 - -ruby 下载源使用亚马逊的云服务被墙了,切换国内的 **ruby-china源** (已经停止维护,详情[查看公告](https://ruby.taobao.org/)): - - $ gem sources --add https://gems.ruby-china.org/ --remove https://rubygems.org/ - $ gem sources -l - *** CURRENT SOURCES *** - - https://gems.ruby-china.org - -安装并切换 ruby - -> 这里不建议安装最新的 2.4.0 版本,因为次版本的 ruby,在xcodebuild 自动打包时,会出现问题! 所以退一步,安装 2.3.3版本~ - - rvm install 2.3.3 --disable-binary - rvm use 2.3.3 --default - -到此ruby升级完毕. - -有关RVM的使用可以看这篇 [RVM 使用指南](http://qiubaiying.github.io/2017/04/28/RVM-使用指南/) - -#### 安装CocoaPods - -1. 安装 - - sudo gem install -n /usr/local/bin cocoapods -2. 升级版本库 - - pod setup - - 这里需要下载版本库(非常庞大),需要等很久 - - Receiving objects: 72% (865815/1197150), 150.07 MiB | 190.00 KiB/s - - 或者直接从其他装有cocoapod的电脑中拷贝`~/.cocoapods`到你的用户目录,然后再 `pod setup`会节省不少时间 - -# 使用 - -#### 创建 `podfile` 文件 - -绝大多数人创建`podfile`都是用 `vim Podfile` 命令 - -其实pod 已经提供了创建 `podfile` 文件的命令,在工程目录下 - - pod init - -将会自动生成 `podfile` 文件,并且为你写好了格式,稍做修改就能使用 - -``` -# Uncomment the next line to define a global platform for your project -# platform :ios, '9.0' - -target 'projectName' do - # Comment the next line if you're not using Swift and don't want to use dynamic frameworks - use_frameworks! - - # Pods for projectName - - target 'projectNameTests' do - inherit! :search_paths - # Pods for testing - end - - target 'projectNameUITests' do - inherit! :search_paths - # Pods for testing - end - -end -``` - -其中的 - -``` -target 'projectNameTests' do - inherit! :search_paths - # Pods for testing - end - - target 'projectNameUITests' do - inherit! :search_paths - # Pods for testing - end -``` - -是指定在单元测试和UI测试时导入的测试框架,若没有使用测试框架可以删除。 - -修改iOS版本,添加`Alamofire`库 - -``` -# Uncomment the next line to define a global platform for your project -# platform :ios, '8.0' - -target 'projectName' do - # Comment the next line if you're not using Swift and don't want to use dynamic frameworks - use_frameworks! - - # Pods for projectName - - pod 'Alamofire', '~> 4.4' - -end -``` - -#### 加载代码库 - -使用下面的命令,直接在本地版本库中查找对应的代码库信息,不升级版本库,节省时间 - - pod install --verbose --no-repo-update - -若找不到库,再使用下面的命令 - - pod install - -#### 版本号 - -对版本号的操作除了指定与不指定,你还可以做其他操作: - -- `\>0.1` 高于0.1的任何版本 -- `\>=0.1` 版本0.1和任何更高版本 -- `<0.1` 低于0.1的任何版本 -- `<=0.1` 版本0.1和任何较低的版本 -- `〜>0.1.2` 版本 0.1.2的版本到0.2 ,不包括0.2。 -这个基于你指定的版本号的最后一个部分。这个例子等效于>= 0.1.2并且 <0.2.0,并且始终是你指定范围内的最新版本 - -# 结语 - -关于**CocoaPods**的安装和使用就这样简单的介绍完了,至于更多使用的方法(平时也用不到~)你可以用下面命令查看 - - $ pod - -若对 CocoaPods 的**个人仓库**感兴趣,也可以看看我的这两篇博客 - -- [CocoaPods公有仓库的创建](http://qiubaiying.top/2017/03/08/CocoaPods%E5%85%AC%E6%9C%89%E4%BB%93%E5%BA%93%E7%9A%84%E5%88%9B%E5%BB%BA/) -- [CocoaPods私有仓库的创建](http://qiubaiying.top/2017/03/10/CocoaPods%E7%A7%81%E6%9C%89%E4%BB%93%E5%BA%93%E7%9A%84%E5%88%9B%E5%BB%BA/) - - > 本文首次发布于 [BY Blog](http://qiubaiying.github.io), 作者 [@柏荧(BY)](http://github.com/qiubaiying) ,转载请保留原文链接. diff --git "a/_posts/2017-04-20-iOS\350\207\252\345\212\250\346\211\223\345\214\205.md" "b/_posts/2017-04-20-iOS\350\207\252\345\212\250\346\211\223\345\214\205.md" deleted file mode 100644 index a435a9276f2..00000000000 --- "a/_posts/2017-04-20-iOS\350\207\252\345\212\250\346\211\223\345\214\205.md" +++ /dev/null @@ -1,152 +0,0 @@ ---- -layout: post -title: iOS自动打包 -subtitle: 利用 xcdeobulid 打包项目、上传 -date: 2017-04-20 -author: BY -header-img: img/post-bg-hacker.jpg -catalog: true -tags: - - iOS - - Xcode - - shell - - ruby ---- - - - -> 利用xcode的命令行工具 `xcdeobulid` 进行项目的编译打包,生成ipa包,并上传到fir - -# 前言 -现在网上的自动打包教程几乎都还是`xcodebuild + xcrun`的方式先生成`.app`包 再生成`.ipa`包,结果弄了一整天硬是没成功~ - -后来发现`PackageApplication is deprecated`,悲剧。然后手动压缩的 `.ipa`包因为签名问题无法装到手机上。 - -后来用了`archive + -exportArchive`终于可以了~ - -# 正文 - -## Xcodebuild - -**xcodebuild** 的使用可以用 `man xcodebuild`查看。 - -查看项目详情 - - # cd 项目主目录 - xcodebuild -list - -输出项目的信息 - - Information about project "StackGameSceneKit": - Targets: - StackGameSceneKit - StackGameSceneKitTests - - Build Configurations: - Debug - Release - - If no build configuration is specified and -scheme is not passed then "Release" is used. - - Schemes: - StackGameSceneKit - -要留意 `Configurations`,`Schemes`这两个属性。 - -## 自动打包流程 - -### 生成 archive - -生成archive的命令是 `xcodebuild archive` - - xcodebuild archive -workspace ${project_name}.xcworkspace \ - -scheme ${scheme_name} \ - -configuration ${build_configuration} \ - -archivePath ${export_archive_path} - -- 参数一:项目类型,,如果是混合项目 workspace 就用 `-workspace`,如果是 project 就用 `-project` - -- `-scheme`:项目名,上面`xcodebuild -list`中的 `Schemes` - -- `-configuration `:编译类型,在`configuration`选择, `Debug` 或者 `Release` - -- `-archivePath`:生成 archive 包的路径,需要精确到 `xx/xx.archive` - -首先需要创建一个`AdHocExportOptions.plist`文件 - - -### 导出ipa包 - -导出`.ipa`包经常会出现错误,~~在ruby2.4.0版本中会报错,所以请使用其他版本的ruby~~,最初的原因是使用了 ruby2.4.0 进行编译时出现的错误。 - -解决方法是低版本的 ruby 进行编译,如使用系统版本:`rvm use system`。后面升级macOS系统(10.12.5)后发现 ruby2.4.0 能成功 导出ipa包了。 - -导出ipa包使用命令:`xcodebuild -exportArchive` - - xcodebuild -exportArchive \ - -archivePath ${export_archive_path} \ - -exportPath ${export_ipa_path} \ - -exportOptionsPlist ${ExportOptionsPlistPath} - - -- `archivePath`:上面生成 archive 的路径 -- `-exportPath`:导出 ipa包 的路径 -- `exportOptionsPlist`:导出 ipa包 类型,需要指定格式的`plist`文件,分别是`AppStroe`、`AdHoc`、`Enterprise`,如下图 - -![](https://ww3.sinaimg.cn/large/006tNc79gy1ff1bcz534ij30g609uq48.jpg) - -选择这三个类别需要分别创建三个`plist`文件: - -- `AdHocExportOptionsPlist.plist` - - ![](https://ww3.sinaimg.cn/large/006tNc79gy1ff1bhmwvxfj30ax01pdfu.jpg) -- `AppStoreExportOptionsPlist.plist` - - ![](https://ww3.sinaimg.cn/large/006tNc79gy1ff1bijdlsgj30bh01st8q.jpg) -- `EnterpriseExportOptionsPlist.plist` - - ![](https://ww4.sinaimg.cn/large/006tNc79gy1ff1bishpk8j30be01sglm.jpg) - - -### 上传到 Fir - -将项目上传到 [Fir](https://fir.im) - -下载 [fir 命令行工具](https://github.com/FIRHQ/fir-cli/blob/master/doc/install.md) - - gem install fir-cli - -获取 fir 的 API Token(右上角) - -![](https://ww3.sinaimg.cn/large/006tNc79gy1ff28ccsqhyj304t07bwei.jpg) - -上传 - - fir publish "ipa_Path" -T "firApiToken" - - - -## 自动打包脚本 - -~~再次提醒,请不要使用 ruby 2.4.0 运行该脚本!~~,若在 ruby 2.4.0 下编译失败,请切换低版本的ruby。 - -切换完毕记得重新安装 fir 命令行工具。 - -脚本我fork了 [jkpang](https://github.com/jkpang/PPAutoPackageScript) 的脚本进行修改,添加了自动上传到 fir 的功能。 - -使用方法在Github上有详细介绍。 - -GitHub: - - -### 利用 自定义终端指令 简化打包过程 - -以zsh为例: - - open ~/.zshrc -添加自定义命令 cd + sh - - alias mybuild='cd 项目地址/iOSAutoArchiveScript/ && sh 项目地址/iOSAutoArchiveScript/iOSAutoArchiveScript.sh' -这样打开终端输入`mybuild`,就可以轻松实现一键打包上传了 - -> 本文首次发布于 [BY Blog](http://qiubaiying.github.io), 作者 [@柏荧(BY)](http://github.com/qiubaiying) ,转载请保留原文链接. diff --git "a/_posts/2017-04-28-RVM-\344\275\277\347\224\250\346\214\207\345\215\227.md" "b/_posts/2017-04-28-RVM-\344\275\277\347\224\250\346\214\207\345\215\227.md" deleted file mode 100644 index 0e0632b2673..00000000000 --- "a/_posts/2017-04-28-RVM-\344\275\277\347\224\250\346\214\207\345\215\227.md" +++ /dev/null @@ -1,62 +0,0 @@ ---- -layout: post -title: RVM 使用指南 -subtitle: RVM 常用的命令整理 -date: 2017-04-28 -author: BY -header-img: img/post-bg-hacker.jpg -catalog: true -tags: - - iOS - - RVM - - shell - - ruby ---- - -> RVM 常用的命令整理 - -RVM 是一个命令行工具,可以提供一个便捷的多版本 Ruby 环境的管理和切换。 - -我相信做为iOS开发者,对ruby的使用都是从安装 **CocoaPods** 开始的吧~ - ->**Note**:这里所有的命令都是再用户权限下操作的,任何命令最好都不要用 sudo. - -## RVM 安装 - - $ gpg --keyserver hkp://keys.gnupg.net --recv-keys 409B6B1796C275462A1703113804BB82D39DC0E3 - $ \curl -sSL https://get.rvm.io | bash -s stable - $ source ~/.bashrc - $ source ~/.bash_profile - -修改 RVM 的 Ruby 安装源到 [Ruby China](https://ruby-china.org/) 的 Ruby 镜像服务器,这样能提高安装速度 - - $ echo "ruby_url=https://cache.ruby-china.org/pub/ruby" > ~/.rvm/user/db - -## Ruby版本的安装与切换 - -列出已知的 Ruby 版本 - - rvm list known - -安装一个 Ruby 版本 - - rvm install 2.2.0 --disable-binary - -切换 Ruby 版本 - - rvm use 2.2.0 - -如果想设置为默认版本,这样一来以后新打开的控制台默认的 Ruby 就是这个版本 - - rvm use 2.2.0 --default - -查询已经安装的ruby - - rvm list - -卸载一个已安装版本 - - rvm remove 1.8.7 - - ->参考: \ No newline at end of file diff --git "a/_posts/2017-05-03-Swift-\347\232\204\346\207\222\345\212\240\350\275\275\345\222\214\350\256\241\347\256\227\345\236\213\345\261\236\346\200\247.md" "b/_posts/2017-05-03-Swift-\347\232\204\346\207\222\345\212\240\350\275\275\345\222\214\350\256\241\347\256\227\345\236\213\345\261\236\346\200\247.md" deleted file mode 100644 index 28640f972d9..00000000000 --- "a/_posts/2017-05-03-Swift-\347\232\204\346\207\222\345\212\240\350\275\275\345\222\214\350\256\241\347\256\227\345\236\213\345\261\236\346\200\247.md" +++ /dev/null @@ -1,96 +0,0 @@ ---- -layout: post -title: Swift 的懒加载和计算型属性 -subtitle: 比较水的个人笔记 -date: 2017-05-03 -author: BY -header-img: img/post-bg-swift.jpg -catalog: true -tags: - - iOS - - Swift - - Swift语法 ---- - - -> 本文首次发布于 [BY Blog](http://qiubaiying.github.io), 作者 [@柏荧(BY)](http://github.com/qiubaiying) ,转载请保留原文链接. - -### 懒加载 - -常规(简化)写法 - -懒加载的属性用 `var` 声明 - -``` -lazy var name: String = { - return "BY" -}() -``` - -完整写法 - -``` -lazy var name: String = { () -> String i - return "BY" -}() -``` - -本质是一个创建一个闭包 `{}` 并且在调用该属性时执行闭包 `()`。 - -如OC的懒加载不同的是 swift 懒加载闭包只调用一次,再次调用该属性时因为属性已经创建,不再执行闭包。 - -### 计算型属性 - -常规写法 - -``` -var name: string { - return "BY" -} -``` - -完整写法 - -``` -var name: string { - get { - return "BY" - } -} -``` - -计算型属性本质是重写了 `get` 方法,其类似一个无参有返回值函数,每次调用该属性都会执行 `return` - -通常这样使用 - -``` -struct Cuboid { - var width = 0.0, height = 0.0, depth = 0.0 - var volume: Double { - return width * height * depth - } -} -let fourByFiveByTwo = Cuboid(width: 4.0, height: 5.0, depth: 2.0) -print("the volume of fourByFiveByTwo is \(fourByFiveByTwo.volume)") -// Prints "the volume of fourByFiveByTwo is 40.0" -``` - -### 两者对比 - -相同点 - -- 使用方法完全一致 -- 都是用 `var` 声明 - -不同点 - -- 实现原理不同 - - 懒加载是第一次调用属性时执行闭包进行赋值 - - 计算型属性是重写 `get` 方法 - -- 调用 `{}`的次数不同 - - 懒加载的闭包只在属性第一次调用时执行 - 计算型属性每次调用都要进入 `{}` 中,`return` 新的值 diff --git "a/_posts/2017-05-04-R.swift-\347\232\204\344\275\277\347\224\250.md" "b/_posts/2017-05-04-R.swift-\347\232\204\344\275\277\347\224\250.md" deleted file mode 100644 index 5e7a4c295c8..00000000000 --- "a/_posts/2017-05-04-R.swift-\347\232\204\344\275\277\347\224\250.md" +++ /dev/null @@ -1,75 +0,0 @@ ---- -layout: post -title: R.swift 的使用 -subtitle: 在项目中引入 R.swift,更安全的获取资源 -date: 2017-05-04 -author: BY -header-img: img/post-bg-swift2.jpg -catalog: true -tags: - - iOS - - Swift - - 开源库 ---- - - -> 本文首次发布于 [BY Blog](http://qiubaiying.github.io), 作者 [@柏荧(BY)](http://github.com/qiubaiying) ,转载请保留原文链接. - - -# 什么是 R.swift - -介绍 [R.swift](https://github.com/mac-cain13/R.swift) 前,我们先看看 R.swift 能做什么 - -通常,我们是基于 字符串 来获取资源,例如:图片、xib、或者是 segue - -```swift -let myImage = UIImage(named: "myImage") -let myViewController = R.storyboard.main.myViewController() -``` - -使用 R.swfit,我们可以这样写 - -```swift -let myImage = R.image.myImage() -let viewController = R.storyboard.main.myViewController() - -``` - -R.swift 通过扫描你的各种基于字符串命名的资源,创建一个使用类型来获取资源。 - -在保证类型安全的同时,也在自动补全的帮助下节省了大量的时间。 - -# 导入 R.swift - -[R.swift](https://github.com/mac-cain13/R.swift) 开源在 github 上。 - -这里是导入的[视频教程](https://vimeo.com/122888912) - -使用 CocoaPods 导入项目中 - -1. 添加 `pod 'R.swift'`到 Podfile 文件,然后运行 `pod install` -2. 添加一个 `New Run Script Phase` - - ![](https://ww4.sinaimg.cn/large/006tKfTcgy1ff84sw06qxj30vm0hrq6s.jpg) - -3. 将 `Run Script` 拖动到 `Check Pods Manifest.lock` 的下面,并且添加脚本 `"$PODS_ROOT/R.swift/rswift" "$SRCROOT/项目名称"` - - ![](https://ww4.sinaimg.cn/large/006tNc79gy1ff853qjiucj30yp0kkn1b.jpg) - -4. `Command+B` 编译项目,在项目代码目录下,会生成一个 `R.generated.swift` 的文件,将它拖如项目中 - - >注意:不要勾选 `Copy items if needed` 选项,因为每次编译都会生成新的 `R.generated.swift` 文件,copy 的话,旧的 `R.generated.swift` 将不会被覆盖。 - - ![](https://ww4.sinaimg.cn/large/006tNc79gy1ff85epj1qpj30qj0hdn17.jpg) - ->tip: 可以在添加 `.gitignore` 添加一行 `*.generated.swift` 忽略该文件,避免造成冲突 - - -# 用法 - - -导入完成后,就可以在使用 R.swift 了 - - ![](https://github.com/mac-cain13/R.swift/raw/master/Documentation/Images/DemoUseImage.gif) - -关于 R.swift 的更多用法,可以 [看这里](https://github.com/mac-cain13/R.swift/blob/master/Documentation/Examples.md)。 \ No newline at end of file diff --git "a/_posts/2017-05-05-\345\234\250-Swift-\344\270\255\344\275\277\347\224\250-IBInspectable.md" "b/_posts/2017-05-05-\345\234\250-Swift-\344\270\255\344\275\277\347\224\250-IBInspectable.md" deleted file mode 100644 index f3e6165d584..00000000000 --- "a/_posts/2017-05-05-\345\234\250-Swift-\344\270\255\344\275\277\347\224\250-IBInspectable.md" +++ /dev/null @@ -1,161 +0,0 @@ ---- -layout: post -title: 在 Swift 中使用 IBInspectable -subtitle: IBInspectable 在 Swift 中的实际应用 -date: 2017-05-05 -author: BY -header-img: img/post-bg-swift.jpg -catalog: true -tags: - - iOS - - Swift - - IBInspectable ---- - - -> 本文首次发布于 [BY Blog](http://qiubaiying.github.io), 作者 [@柏荧(BY)](http://github.com/qiubaiying) ,转载请保留原文链接. - -# 前言 - -通过 IB 设置 控件 的属性非常的方便。 - -![](https://ww3.sinaimg.cn/large/006tNc79gy1ff9fpog0vrj30ho084t9m.jpg) - -但是缺点也很明显,那就是有一些属性没有暴露在 IB 的设置面板中。这时候就要使用 `@IBInspectable` 在 IB 面板中添加这些没有的属性。 - -关于在 OC 中使用 `IBInspectable` 可以看一下我的 [这篇文章](http://qiubaiying.top/2016/12/01/%E5%BF%AB%E9%80%9F%E6%B7%BB%E5%8A%A0%E5%9C%86%E8%A7%92%E5%92%8C%E6%8F%8F%E8%BE%B9/#高级) - -# 正文 - -在项目中最常遇到的情况是为 view 设置圆角、描边,以及为 文本控件 添加本地化字符串。 - -## 圆角、描边 - -先来看看设置圆角、描边 - -```swift -extension UIView { - @IBInspectable var cornerRadius: CGFloat { - get { - return layer.cornerRadius - } - - set { - layer.cornerRadius = newValue - layer.masksToBounds = newValue > 0 - } - } - - @IBInspectable var borderWidth: CGFloat { - get { - return layer.borderWidth - } - set { - layer.borderWidth = newValue > 0 ? newValue : 0 - } - } - - @IBInspectable var borderColor: UIColor { - get { - return UIColor(cgColor: layer.borderColor!) - } - set { - layer.borderColor = newValue.cgColor - } - } - -} -``` - -添加完成就可以在 IB 中设置 view 的这些属性了 - -![](https://ww4.sinaimg.cn/large/006tNc79gy1ff9h5afhv2j30f803ajri.jpg) - -运行效果 - -![](https://ww3.sinaimg.cn/large/006tNc79gy1ff9h70z922j30ag061wf7.jpg) - -## 利用 @IBDesignable 在 IB 中实时显示 @IBInspectable 的样式 - -创建一个新的 class 继承 `UIView` ,并且使用 `@IBDesignable` 声明 - -```swift -import UIKit - -@IBDesignable class IBDesignableView: UIView { - -} -``` - -在 IB 中,选择 view 的 class 为 我们新建的 `IBDesignableView` - - - -![](https://ww1.sinaimg.cn/large/006tNc79gy1ff9hs6z5q1j30fr03vweu.jpg) - -这样在 IB 调整属性时,这些属性的变化就会实时显示在 IB 中。 - - -## 本地化字符串 - -本地化字符串的解决方法和上面的添加圆角一样 - -```swift -extension UILabel { - @IBInspectable var localizedKey: String? { - set { - guard let newValue = newValue else { return } - text = NSLocalizedString(newValue, comment: "") - } - get { return text } - } -} - -extension UIButton { - @IBInspectable var localizedKey: String? { - set { - guard let newValue = newValue else { return } - setTitle(NSLocalizedString(newValue, comment: ""), for: .normal) - } - get { return titleLabel?.text } - } -} - -extension UITextField { - @IBInspectable var localizedKey: String? { - set { - guard let newValue = newValue else { return } - placeholder = NSLocalizedString(newValue, comment: "") - } - get { return placeholder } - } -} -``` - -这样,在 IB 中我们就可以利用对应类型的 Localized Key 来直接设置本地化字符串了: - -![](https://ww1.sinaimg.cn/large/006tNc79gy1ff9h94um01j30aj01vjre.jpg) - - - -# 结语 - -`IBInspectable` 可以使用这些的类型 - -- `Int` -- `CGFloat` -- `Double` -- `String` -- `Bool` -- `CGPoint` -- `CGSize` -- `CGRect` -- `UIColor` -- `UIImage` - -合理的使用`@IBInspectable` 能减少很多的模板代码,提高我们的开发效率。 - -> 参考 -> -> - [《再看关于 Storyboard 的一些争论》](https://onevcat.com/2017/04/storyboard-argue/) -> - [《@IBDesignable and @IBInspectable in Swift 3》](https://medium.com/@Anantha1992/ibdesignable-and-ibinspectable-in-swift-3-702d7dd00ca) \ No newline at end of file diff --git "a/_posts/2017-06-19-\345\277\253\351\200\237\351\205\215\347\275\256zsh.md" "b/_posts/2017-06-19-\345\277\253\351\200\237\351\205\215\347\275\256zsh.md" deleted file mode 100644 index cdcc0d81860..00000000000 --- "a/_posts/2017-06-19-\345\277\253\351\200\237\351\205\215\347\275\256zsh.md" +++ /dev/null @@ -1,63 +0,0 @@ ---- -layout: post -title: 快速配置zsh -subtitle: zsh的快速配置 -date: 2017-06-19 -author: BY -header-img: img/post-bg-universe.jpg -catalog: true -tags: - - 终端 - - zsh - - Notes ---- - - -> 比较水的 Personal Notes - -## 查看你的系统有几种shell - - cat /etc/shells - -显示 - - /bin/bash - /bin/csh - /bin/ksh - /bin/sh - /bin/tcsh - /bin/zsh - -## 安装 oh my zsh - - git clone git://github.com/robbyrussell/oh-my-zsh.git ~/.oh-my-zsh - cp ~/.oh-my-zsh/templates/zshrc.zsh-template ~/.zshrc - -重新打开终端,输入 - - zsh - -即可切换终端,并且发现 oh my zsh 已经帮我们配置好 zsh 了 - -## 修改主题 - - open ~/.zshrc - -修改 `ZSH_THEME=”robbyrussell”`,主题在 ~/.oh-my-zsh/themes 目录下。 -修改为 - - ZSH_THEME="kolo" - -可以[参照这里](https://github.com/robbyrussell/oh-my-zsh/wiki/themes)进行选择. - -## 设置为默认shell - - chsh -s /bin/zsh - -## 添加自定义命令 - - open ~/.zshrc -添加显示隐藏文件的快捷命令 - - alias fd='defaults write com.apple.finder AppleShowAllFiles -boolean true ; killall Finder' - alias fh='defaults write com.apple.finder AppleShowAllFiles -boolean false ; killall Finder' \ No newline at end of file diff --git "a/_posts/2017-07-04-Xcode9-\346\227\240\347\272\277\350\260\203\350\257\225\345\212\237\350\203\275.md" "b/_posts/2017-07-04-Xcode9-\346\227\240\347\272\277\350\260\203\350\257\225\345\212\237\350\203\275.md" deleted file mode 100644 index 44e7d0623a8..00000000000 --- "a/_posts/2017-07-04-Xcode9-\346\227\240\347\272\277\350\260\203\350\257\225\345\212\237\350\203\275.md" +++ /dev/null @@ -1,36 +0,0 @@ ---- -layout: post -title: Xcode9 无线调试功能 -subtitle: zsh的快速配置 -date: 2017-07-04 -author: BY -header-img: img/post-bg-coffee.jpeg -catalog: true -tags: - - Xcode - - 开发技巧 ---- - -> 支持:Xcode 9 及 iOS 11 - -使用数据线连接 iPhone 到电 Mac,Mac 和 iPhone 必须在同一个局域网 - -### 1. 打开设备列表 - -使用快捷键盘 `⇧⌘2` -或 在 Xcode 菜单栏选择 Window > Devices and Simulators,打开设备列表 - -![](https://ws2.sinaimg.cn/large/006tNc79gy1fh6uij2kq9j30dg08l417.jpg) - -### 2. 勾选在线调试按钮 - -![](https://ws2.sinaimg.cn/large/006tNc79gy1fh6ugx3097j30rl06kq4i.jpg) - -![](https://ws3.sinaimg.cn/large/006tNc79gy1fh6ugwec7sj30re06gdhl.jpg) - -### 3. 拔掉数据线 - -这时就可以无线调试了。 - -![](https://ws3.sinaimg.cn/large/006tNc79gy1fh6uhd01f8j30ef05v75k.jpg) - diff --git "a/_posts/2017-07-10-\343\200\214\344\275\223\350\203\275\350\256\255\347\273\203\347\220\206\350\256\272\343\200\215\344\271\213\345\212\237\350\203\275\346\200\247.md" "b/_posts/2017-07-10-\343\200\214\344\275\223\350\203\275\350\256\255\347\273\203\347\220\206\350\256\272\343\200\215\344\271\213\345\212\237\350\203\275\346\200\247.md" deleted file mode 100644 index 322ad8dbdd5..00000000000 --- "a/_posts/2017-07-10-\343\200\214\344\275\223\350\203\275\350\256\255\347\273\203\347\220\206\350\256\272\343\200\215\344\271\213\345\212\237\350\203\275\346\200\247.md" +++ /dev/null @@ -1,126 +0,0 @@ ---- -layout: post -title: 「体能训练理论」之功能性 -subtitle: 「健身先健脑」科学的运动需要科学的运动理论 -date: 2017-07-10 -author: BY -header-img: img/post-bg-mma-3.jpg -catalog: true -tags: - - 健身 - - 生活 ---- - - -## 引言 - -体能训练是一种开源的程序它所依仗的并不是固有的方法手段,而是能够贯穿始终的逻辑,它是一种指导实践的思维方式,我们管它叫“体能思路”。 - -体能思路有两个方向,一个是原点,一个是过程。所谓原点我们认为是人体的本质属性,比如之前我们分享的五大运动素质以及动力链理论。所谓过程是我们分析问题实现目的的思考方向以及逻辑,它主要体现在接下来要跟大家分享的功能性原则和金字塔。 - -今天先来说功能性原则。 - -## 功能性 - - -我们所说的功能性是一种解决问题的思维方式,而功能性训练则定义了一种多关节参与,多平面运动的复杂练习。功能性训练是具体的,比较好理解,我就不赘述了,也不评价其优劣,因为在“功能性”的思维方式下,只存在目标之下的合适与否。 - -### 什么是功能性? - -我们把它定义为目标导向下的效率,所以它是一种程度的体现。如果一个练习与目标的相关性强,那么我们认为它具备较强的功能意义;而如果一个练习与它的目标背道而驰,那么我们就认为它缺乏功能意义。 - -举个例子,对于偏瘫患者来说,一个手指的屈伸就已经具备非常强的功能意义了,而对于一个马拉松爱好者来说,静蹲的价值可能并不是想象中那么高。 - -一般来说,在思考功能性问题的时候我习惯从以下三个方面入手:**肌肉的生理适应** 、**动作模式** 和 **专项需求**。 - -#### 1. 肌肉的生理适应 - -其实练肌肉谁都会,是一个相对好入手的技能,但是当你给这个行为赋予体能训练使命的时候就需要思考一些问题,比如说你现在练习所发展的东东真的是你实际运动中所需要的东东么? - -![](https://ws1.sinaimg.cn/large/006tKfTcgy1fhg24pm22dj30e709474v.jpg) - -我们都知道肌肉的生理收缩模式可以简单的分为向心收缩,离心收缩和静力收缩。现在的研究表明,这三种收缩模式的练习所产生的适应性提高存在显著的特异性。也就是说我向心练习所发展的能力只在向心运动中表现最好,在离心和静力中都不佳。同样,离心收缩也只能获得最好的离心能力收益。而静力就更变态,其训练最佳效果仅仅体现在所锻炼的关节角度下,换一个角度能够迁移的效果有可能都不到一半儿。这样看来,你的训练是不是并没有达到你想要的效果呢? - -举个例子: - -![](https://ws1.sinaimg.cn/large/006tKfTcgy1fhg251pmatj30go0b5q5d.jpg) - -为什么静蹲对于跑步爱好者来说可能并没有那么理想?因为膝关节股四头肌在跑步中以离心缓冲为主,而且角度在伸膝末端的30°左右,而静蹲却是在屈曲90°左右的角度下呆着不动……着不动……不动……动…… - -除此之外,需要考虑的问题还很多,比如说发力模式,是加速?减速?还是匀速?再比如关节活动角度上发力点的位置,是伸展末端发力?屈曲极限发力?还是在屈伸过程中的某一点发力?阻力加在哪里,就会在哪里产生最好的适应,那么功能的意义就体现在这里。 - -#### 2. 动作模式 - -动作模式是动作程序的体现,而基础动作模式是诸多复杂动作模式共性的抽象体现,并且基础动作模式一定是符合解剖结构和生物力学特点的,说白了也就是我们人体被设计来应该完成的动作。 - -如果说大多数运动都可以认为是基础动作模式的升级与排列组合,同时基础动作模式本身又能衍生出来很多训练动作,那么选择和实际运动相对应的练习就是另一个功能性的体现了。 - -![](https://ws2.sinaimg.cn/large/006tKfTcgy1fhg25l7ep2j30g609o0ue.jpg) - -比如说发展起跳能力,因为跳是蹲的升级,所以我一定首选深蹲练习;再比如说跑步,存在大量的下肢摆动与支撑的交替,摆动可以认为是下肢开链屈髋与蹬伸,而支撑可以认为是下肢单腿蹲的一瞬间,那么我会选择箱式单腿蹲,保加利亚蹲,悬垂屈髋等等;再再比如,拳击是基于“旋转”加“上肢推”加“单腿蹲”的动作模式,那么我就要练习剪蹲...旋转...单臂...推举...吗? - -其实动作模式的选择要结合动力链一起去思考,这里除了要思考开链还是闭链之外,还要考虑动力链的完整性以及发力的顺序或者说是力学结构。说到上肢推的动作模式,水平推的话我相信很多人都会想到卧推和俯卧撑,那么这两个动作的功能性如何评价呢? - -![](https://ws2.sinaimg.cn/large/006tKfTcgy1fhg26a2ve0j30go0ce76n.jpg) - -**卧推**,一个挺奇葩的动作,奇葩在哪呢?来,咱们数数卧推的主动关节都有哪些:肩关节,肘关节。那么我们上肢链在上肢推动作模式下参与的关节都有哪些呢?肩关节,肘关节!就这些么?再想想!其实你还疏漏了一个非常重要的关节——**肩胛胸关节**!几乎所有上肢的动作都以肩胛胸关节的运动为基础,而卧推却并没有,特别是标准的卧推~ - -![](https://ws4.sinaimg.cn/large/006tKfTcgy1fhg26h6oejj30go09lwgj.jpg) - -**俯卧撑**,虽然肩胛胸,肩关节,肘关节全面参与到运动中去,但不巧的是它是一个闭链运动,而实际运动中我们的上肢会以开链为主!呵呵~ - -别着急,认真你就输了!上面两段其实是个伪命题,我这么做主要是想通过这个平易近人的例子来帮助大家掌握的分析问题的思路!如果你需要发展上肢最大力量表现,那么显然卧推是你的首选。而如果你要优化上肢的力学结构,特别是水平推的发力顺序,那么俯卧撑是你首选。再如果你要提高上肢的延展性以及伴随旋转的加速能力,那么单臂水平推的练习给你的帮助最大! - -所以,选择什么,看目标喽~ - -#### 3. 专项需求 - -其实这个非常好理解,也是功能性原则的根本目的,但是为了和上面两个方向区分开,这里主要针对的是不同的运动素质需求。 - -Q:对一个英超的后卫进行长距离高强度的游泳练习是否具备功能性意义?! - -A:具备! - -Q:为什么? - -A:因为他喜欢游泳,这个可以让他心情愉悦然后更好的训练和比赛! - -咳咳!当然,这样的答案是合情合理的!但是我们不妨换一个角度去分析。 - -![](https://ws2.sinaimg.cn/large/006tKfTcgy1fhg26rdi6vj30go0b4goo.jpg) - -英超,几乎是足球联赛中对抗最强的,他们的后卫每场比赛动不动就跑个8千1万的,而这8千1万真心不是慢慢悠悠颠儿下来的,而是各种加速减速变向拼抢,所以其强度非常之大。那么这就需要很好的心肺系统功能,一方面体现在有氧与无氧耐力上,另一方面呼吸器官的机能上。游泳练习,不仅可以提高有氧以及无氧耐力,其水环境还可以给胸扩张带来阻力,直接锻炼了呼吸肌的收缩能力。另外,水环境真的能够给人们带来愉悦的感觉,特别是水流水压给肌肉和筋膜的按摩效果,真的是一举两得的“功能性”训练。 - -还有,你以为篮球运动员的拳击练习真的只是给枯燥乏味的体能训练增加一点乐趣么?并不是! - -![](https://ws2.sinaimg.cn/large/006tKfTcgy1fhg276m81pj31h50rgtbv.jpg) - -**1. 拳击可以在发生场内冲突的时候很好地保护自己;** - -**2. 拳击运动可以强化旋转动作模式下的速度、稳定、和准确性;** - -**3. 拳击是手脚高度协调的运动,对于发展手脚搭配的动作灵敏有神奇的效果。** - -而这些不就是一名篮球运动员所需要的么?! - -## 总结 - -所以,功能性原则,解决的是“**为什么练**(for!not why)以及 **练什么**”的问题! - -如果我们是简单活动活动身体那就算了,但如果我们要进行一个有针对性的体能训练,那么请琢磨琢磨你选择的动作是否合理,是否能够满足你的专项需求! - -所以,招财猫式弹力带抗阻外旋真的是练习肩袖首推的动作么? - -所以,蚌式练习和dirty dog真的是发展髋外旋外展能力最好的练习么? - -![](https://ws2.sinaimg.cn/large/006tKfTcgy1fhg28jvnhqj30dw099q3z.jpg) - -所以,仰卧卷腹发展出来的腹直肌是好看呢?还是好用呢? - -所以,我们真的要来一次大清洗,摒弃掉我们以前那些练习么? - -当然不要!每一个动作都有它存在的意义,都有它的价值所在!有可能这个动作和你要发展的能力不直接相关,但是它可能是你进行“功能性”训练的基础,你不得不去做它! - -所以,训练的逻辑很重要! - ->转自[《体能训练之功能性》](https://zhuanlan.zhihu.com/p/20786373) \ No newline at end of file diff --git "a/_posts/2017-07-10-\343\200\214\344\275\223\350\203\275\350\256\255\347\273\203\347\220\206\350\256\272\343\200\215\344\271\213\345\212\250\345\212\233\351\223\276.md" "b/_posts/2017-07-10-\343\200\214\344\275\223\350\203\275\350\256\255\347\273\203\347\220\206\350\256\272\343\200\215\344\271\213\345\212\250\345\212\233\351\223\276.md" deleted file mode 100644 index f646e2e22e9..00000000000 --- "a/_posts/2017-07-10-\343\200\214\344\275\223\350\203\275\350\256\255\347\273\203\347\220\206\350\256\272\343\200\215\344\271\213\345\212\250\345\212\233\351\223\276.md" +++ /dev/null @@ -1,160 +0,0 @@ ---- -layout: post -title: 「体能训练理论」之动力链 -subtitle: 「健身先健脑」科学的运动需要科学的运动理论 -date: 2017-07-10 -author: BY -header-img: img/post-bg-mma-0.png -catalog: true -tags: - - 健身 - - 生活 ---- - - -## 引言 - -与其说体能训练是一种行为,不如说体能训练是一种程序。只要符合逻辑,就可以自由组合。 - 那么体能训练的逻辑是什么?我们将之总结为:[**动力链**](http://qiubaiying.top/2017/07/10/%E4%BD%93%E8%83%BD%E8%AE%AD%E7%BB%83%E7%90%86%E8%AE%BA-%E4%B9%8B%E5%8A%A8%E5%8A%9B%E9%93%BE/)、[**功能性**](http://qiubaiying.top/2017/07/10/%E4%BD%93%E8%83%BD%E8%AE%AD%E7%BB%83%E7%90%86%E8%AE%BA-%E4%B9%8B%E5%8A%9F%E8%83%BD%E6%80%A7/)、[**金字塔**](http://qiubaiying.top/2017/07/10/%E4%BD%93%E8%83%BD%E8%AE%AD%E7%BB%83%E7%90%86%E8%AE%BA-%E4%B9%8B%E9%87%91%E5%AD%97%E5%A1%94/)。 - -## 动力链 - -如果说 **五大运动素质**(力量、速度、耐力、灵敏、柔韧)代表了体能的宏观表现,那么动力链理论则阐释了人体解剖结构在运动中的客观规律,这二者同为人体的本质属性。 - -动力链这一理论早在1875年就被提出过,当时的定义还很简单,就是指几个相邻的关节所组成的复杂动作单元。后来在不断地实践与研究中,动力链理论也不断的升级,越来越清晰,越来越客观,也越来越复杂,并且逐渐成为了体能训练师们必备的思考工具之一。 - -来看看动力链的英文解释: - ->The concept of the kinetic chain originated in 1875, when a mechanical engineer named Franz Reuleaux proposed that if a series of overlapping segments were connected via pin joints, these interlocking joints would create a system that would allow the movement of one joint to affect the movement of another joint within the kinetic link. Dr. Arthur Steindler adapted this theory in 1955, and included an analysis of human movement. Steindler suggested that the extremities be viewed as a series of rigid, overlapping segments and defined the kinetic chain as a "combination of several successively arranged joints constituting a complex motor unit." The movements that occur within these segments present as two primary types—open and closed. - -这种模糊形容根本无法让人们理解它真正的内核,虽然它看起来就像是一堆联动的齿轮和杠杆。实际上它也真的很像一堆齿轮和杠杆,有的负责驱动,有的负责传力,有的负责稳定。 - -为了方便大家理解,下面我要将这个理论拆解开来跟大家分享。 - -首先,我们需要从以上的定义中提炼出来一些关键词,比如说 “运动”、“几个”、“相邻”等等。那么这几个词分别代表了什么? - -1. 我们讨论问题的角度是**运动**的; -2. 我们需要考虑的**人体解剖结构**问题; -3. 我们需要考虑相邻关节的**协作关系**; -4. 我们需要分析每一个关节的**使用特点**。 - -所以,它似乎并不能被完美的定义,而是只可意会不可言传。 - 那么关于动力链,我们需要掌握两个最基本的知识:**动力链模型**、**开链与闭链**。 - -### 动力链模型 - -在动力链理论中,我们考虑运动的最小单位是**关节**,诸多关节运动的协作产生了整体上的复杂动作。所以每一个关节的功能就决定了整体动作的表现,任何一个关节功能受限都会导致整体动作的失衡。 - -而我们所指的关节功能,可以从生物力学角度简单的概括为: - -- **灵活**(**M**,Mobility) -- **稳定**(**S**,Stability) - -但看起来简单的两个词,其实意义非常。 - -#### 什么是灵活? - -![](https://ws2.sinaimg.cn/large/006tKfTcgy1fhg2avap5sj30ao08rdg4.jpg) - -很多人的第一反应就是能自由的运动呗~然后部分专业人士可能会想到活动度。但是你的关节如果仅仅具备很好的活动度就能够胜任运动中的需求么?显然是不能的。所以灵活的意义远不止关节活动度,关节活动度仅仅是灵活的基础,而更重要的还有产力的能力。没错,这里的灵活既包括关节主被动活动范围,也包括产力的能力,比如产力的大小,产力的快慢等等。 - 举个例子:小明的髋关节活动范围非常好,能竖叉能横叉,但是臀大肌并没有很好的力量,所以不能够支撑你的跑步与跳跃,所以此时的髋关节灵活性仍然是不足的,只不过这里强调的是力量的缺失。 - -#### 什么是稳定? - -![](https://ws2.sinaimg.cn/large/006tKfTcgy1fhg2b1gendj30fa08t74f.jpg) - -稳定就是稳定呗~就是待着不动呗~就是牢固呗~灵活还能说出个关节活动度,而稳定的定义真的让很多人摸不到头脑,因为似乎“稳定”一词已经很好地形容了关节的功能表现。但是实际上我们仍然可以对其进行深究,并且这样做是有意义的,因为表现的不同直接影响训练的手段。 - -如果我们把“稳定”定义为是一种提供安全性的保护,那么我们就可以假想出两个现象: - -1. 一个非常贵重的瓷器抱在手中,我不能把它摔碎,所以我抱着不动~ -2. 同样是这个非常贵重的瓷器抱在手中,我不能把它摔碎,但是我可以慢慢的把它放在地上。 -同样是保护瓷器不被摔碎,但是却有两个表现,一个是hold住,另外一个是慢慢的放在地上,一个不动,一个动。所以我们人体关节的稳定也是如此,既包括保持身体姿态,关节位置的相对固定,也包括有控制的缓冲外力,退让做功。 - -![](https://ws1.sinaimg.cn/large/006tKfTcgy1fhg2b6eo7uj30m80h477k.jpg) - -举个例子:我们的核心区域在运动中应该尽可能的保持姿态的稳定,所以是抱着瓷器不动;我们的膝关节在走路与跑步中从伸到屈,缓冲脚落地产生的冲击力,所以是抱着瓷器往下放。 - -在了解了 SM (稳定和灵活)的意义之后,更重要的是明白:这两种并不会孤立的存在,而是相辅相成同时存在的,只不过在人体整体动力链中体现的侧重点不一样,在肢体的协同运动中扮演的角色不一样。比如对于核心区域来说,灵活恰是其稳定的基础,因为不同体位下脊柱的排列形式直接影响稳定的表现。 - -当 SM 代表了关节功能之后,在人体的整体运动结构中,不同的关节所凸显出来的功能是不同的,并且它们遵循一定的逻辑分布。 - -![](https://ws3.sinaimg.cn/large/006tKfTcgy1fhg2bewsh2j30hs0b0t8y.jpg) - -从下往上说: - -1. 足弓——稳定 - - 第一个缓冲冲击力的关节,并且没有多大的关节活动度。 - -2. 足踝——灵活 - - “足” “踝”形成了一个稳定与灵活兼备的整体,但是在运动中它是下肢蹬伸最后一个主动发力的关节,并且无时无刻不在调整着身体与地面之间的位置关系,所以在这里我们更强调它的灵活性。 - -3. 膝关节——稳定 - - 强大的承重关节,且仅存在屈伸的动作(屈膝位的内外旋的意义是提供可控的缓冲空间,并非叫你主动旋转),更重要的是,无论走路、跑步、跳跃,膝关节都是非常重要的离心缓冲关节。 -4. 髋关节——灵活 - - 强大的发力关节,而且活动范围也非常广泛,它引领着下肢的动作产生。但是由于位置与功能的特殊,所以其稳定性也相当重要,直接可以影响核心的稳定结构,特别是在闭链状态下。 - -5. 腰椎——稳定 - - 相对的绝对稳定体。所谓绝对,是因为腰椎所处的位置恰好为核心地带,这里的功能是维持姿态以及为上下肢的运动提供稳定基础,所以要“抱着缸不动”。而所谓相对,是因为不同的体位下腰椎的姿态是需要随之调整的,并不能以不变应万变。 - -6. 胸椎——灵活 - - 胸椎的灵活性其实并不好,但是相比于腰椎来说就好太多了,特别是在旋转动作上。在旋转鞭打的动作模式中,胸椎是继下肢产力之后的第一个角速度放大的关节,其灵活程度直接影响了上肢的鞭打效果。当然,在更多的时候胸椎要参与承重,但即便承重,也是以其良好的灵活性为基础的,比如说手臂上举过头负重的动作。 - -7. 颈椎——稳定 - - 虽然很灵活,但却需要很稳定!因为头部的位置变化会直接改变身体肌张力的大小分布,这个不仅可以让我们身体姿态发生变化,还会破坏掉本体感觉的准确性。当然,这也是猫在空中可以转体的原因,以及为什么我们打拳的时候不能回头。 - -8. 肩胛胸关节——稳定 - - 这是一个很奇葩的关节,从动力链结构上看,它是稳定关节,但稳定的并不是它自己,而是肩关节。在实际运动中,肩胛胸关节和肩关节是联动运动的,而且前者为后者提供稳定性,是后者得以安全展现灵活的基础。但是这个“稳定”恰恰是通过肩胛胸关节本身的灵活性来展现的,比如手臂上举时的上回旋。 - -9. 肩关节——灵活 - - 没的说,人体最灵活的关节,也是人体最不稳定的关节,其球窝关节的解剖结构已经说明了一切。 - -10. 肘关节——稳定 - - 结构上跟膝关节相对,但是实际上要比膝关节灵活的多。所以如果进化论成立的话,人类从四脚着地变成双脚着地的过程,使我们的下肢关节趋向于稳定,上肢关节趋向于灵活。而这正与 “开链”或者“闭链”的需求相适应。 - -### 开链&闭链 - -我们的关节同时存在S与M,而在整体的运动中有不同的体现,甚至于同样是S或者M的上下肢关节却存在了显著的差别。那么在此我们需要引出一个新的概念:**开链** & **闭链**。 - -#### 开链 - -![](https://ws2.sinaimg.cn/large/006tKfTcgy1fhg2bltahoj30dg09h74k.jpg) - -开链,简单地说就是我们身体产生力量,改变了外界物体的运动状态。比如说哑铃二头弯举,投掷,摘苹果等动作都是开链动作。我们可以认为我们的身体在对抗趋于无穷小的阻力,那么我们就可以随意改变物体的运动状态,随便摆弄它,所以此时我们的肢体的灵活性就可以充分的发挥。比如我们的上肢就是以开链运动为主的,所以它整体表现出更好的灵活性。 - -#### 闭链 - -![](https://ws3.sinaimg.cn/large/006tKfTcgy1fhg2bq4z76j30fk0dp0tf.jpg) - -闭链,简单的说就是我们的身体产生力量,却没能推动外界的物体,反而改变了我们自身的运动状态。比如说跑步与深蹲,我们扒地,我们蹬地,并没有让地板产生位移,我们自己却向前或者向上运动了。所以我们可以认为闭链运动时,我们的身体在对抗趋于无穷大的阻力,我们根本不可能改变它,所以只能运动我们自己。而在面对这样无穷大的阻力的时候,我们需要将我们的关节摆在力学结构最优的位置上才能发挥我们自身的最大经济性和效率,而且在这个状态下,各个关节的位置直接影响了身体的整体姿态和状态,所以灵活性被抑制。我们的下肢,最擅长、做的最多的就是闭链运动,所以它更加的趋于稳定。 - -开链和闭链直接影响我们的训练适应,因为它们所表现的力学结构是不同的。 - - -## 总结 - -了解动力链并不是让我们**装逼**的,而是让我们更加了解人体的客观规律来指导训练的。 - -它是一个非常好的思考工具。比如我们在训练下肢力量的时候,我们就需要考虑髋关节灵活性对于下肢力量表现的影响,于是乎我们可能更加重点强化髋的产力能力。但是当考虑到屈髋动作时,也许实际中更多的是开链的屈髋,所以我们就能以此为依据来选择髂腰肌和股直肌的训练动作。 - -除此之外,每个关节本身的功能完整性是非常重要的,如果一个关节有功能缺陷,那么在整体运动中它就不能够尽到它的职责,所以一定会有另外一个或者几个关节来代偿它的功能,那么就相当于一个3人的团队,一个请假了,另外两个就得加班。如果一次两次没关系,它要是请了一年的产假,那么另外俩人可能会由于长期超负荷工作而积劳成疾。当然,对于公司来说我可以再雇人,但是我们的人体可没有能再多长一个关节之说。 - -所以其实很多跑步膝的问题恰是由于髋和踝的功能缺陷而导致的。 - -![](https://ws3.sinaimg.cn/large/006tKfTcgy1fhg2buv4nwj30ia0a2dfx.jpg) - -**最后我要再次强调**:任何一个关节,稳定与灵活同时存在,只不过体现的程度和侧重不同。在动力链中,灵活的关节不代表没有稳定,更不代表稳定不重要;稳定的关节也需要灵活,而且灵活可能是稳定的基础。 - -了解了动力链,你会更懂运动中的人体,也许你有了思考问题的方向,但仍然缺少方法,所以你还需要具备[「功能性」](http://qiubaiying.github.io/2017/07/10/%E4%BD%93%E8%83%BD%E8%AE%AD%E7%BB%83%E7%90%86%E8%AE%BA-%E4%B9%8B%E5%8A%9F%E8%83%BD%E6%80%A7/)的思维方式。 - ->转自 [《体能训练之动力链》](https://zhuanlan.zhihu.com/p/20774747) \ No newline at end of file diff --git "a/_posts/2017-07-10-\343\200\214\344\275\223\350\203\275\350\256\255\347\273\203\347\220\206\350\256\272\343\200\215\344\271\213\351\207\221\345\255\227\345\241\224.md" "b/_posts/2017-07-10-\343\200\214\344\275\223\350\203\275\350\256\255\347\273\203\347\220\206\350\256\272\343\200\215\344\271\213\351\207\221\345\255\227\345\241\224.md" deleted file mode 100644 index 8ccb77cd977..00000000000 --- "a/_posts/2017-07-10-\343\200\214\344\275\223\350\203\275\350\256\255\347\273\203\347\220\206\350\256\272\343\200\215\344\271\213\351\207\221\345\255\227\345\241\224.md" +++ /dev/null @@ -1,95 +0,0 @@ ---- -layout: post -title: 「体能训练理论」之金字塔 -subtitle: 「健身先健脑」科学的运动需要科学的运动理论 -date: 2017-07-10 -author: BY -header-img: img/post-bg-mma-2.jpg -catalog: true -tags: - - 健身 - - 生活 ---- - - -## 引言 - -体能,人体基础运动能力的统称,人体的本质属性,它支撑着日常生活工作的需要,也支撑着运动技战术的表现。 体能思路,是指导我们设计实施体能训练的思维方式,分析逻辑。它包括回归原点的 **五大运动素质** & [**动力链理论**](http://qiubaiying.top/2017/07/10/%E4%BD%93%E8%83%BD%E8%AE%AD%E7%BB%83%E7%90%86%E8%AE%BA-%E4%B9%8B%E5%8A%A8%E5%8A%9B%E9%93%BE/),也包括在过程中引领方向的 [**功能性原则**](http://qiubaiying.top/2017/07/10/%E4%BD%93%E8%83%BD%E8%AE%AD%E7%BB%83%E7%90%86%E8%AE%BA-%E4%B9%8B%E5%8A%9F%E8%83%BD%E6%80%A7/) 和 [**金字塔**](http://qiubaiying.top/2017/07/10/%E4%BD%93%E8%83%BD%E8%AE%AD%E7%BB%83%E7%90%86%E8%AE%BA-%E4%B9%8B%E9%87%91%E5%AD%97%E5%A1%94/) 。 - -## 金字塔 - -体能训练是一门实践科学,实践先于理论,而理论印证实践。体能训练有四大基础学科,分别是运动解剖学、运动生理学、运动生物力学和运动训练学。 - -五大运动素质对应生理学和运动训练学;动力链对应解剖学和运动生物力学;功能性对应着生理和生物力。而金字塔则对应了它们全部!或者说金字塔其实是诸学科在体能训练中的交汇,它囊括了所有之前的理论,并且赋予了更深层的意义。 - -![](https://ws4.sinaimg.cn/large/006tKfTcgy1fhg20ydk8uj30go0brwh1.jpg) - -金字塔代表了人体运动能力发展的客观规律,它是一个流程的引领,思维的分级,以及训练阶段的划分。 - -这里包括 **关节功能+核心控制**、**基础动作模式**、**基础力量**、**综合体能**、**专项运动**。他们在逻辑上互为基础和进阶,关节是动作的基础,动作承载力量,力量支撑各个运动素质,而专项是各个运动素质在具体运动中的表现。 - -也许这么说可能不能够让大家有清晰的认识,那么接下来我就把每层的内容和它们之间的逻辑关系简单的跟大家分享一下。 99%的运动者都是基础不足,上层过度。我们从基础开始,从下往上说起。 - -### 运动基础(关节功能 + 核心控制) - -运动基础主要内容包括 **关节功能** 和 **核心控制** 能力。 - -人体的关节功能有两个属性,一个是灵活性,一个是稳定性。举例来说,很多人由于长期缺乏锻炼,肩关节灵活性缺失,第一次学习竖直上举时,怎么努力都举不到头顶,显然应该先改善肩关节灵活性。再比如,膝关节的结构导致它只能进行屈伸的运动,所以我们要保证运动过程中不出现膝内扣,膝外翻的现象,也就是膝关节需要具备的稳定能力。在健身之前我们,应该先评估我们的关节功能。 - -运动基础中的第二部分内容就是核心控制能力。很多健身者入门者都会觉得核心是腹肌,觉得练核心的目的是拥有一个好看的腹肌。实际上,腹肌只是核心的一部分,核心是指一个区域,我们的整个躯干都属于核心区域。 - -运动的外在表现虽来源于四肢,比如跑步时你的四肢在运动,但是一个出色的外在运动表现是建立在稳定的核心基础之上的。如果躯干不稳定,在跑步的过程中整个脊柱很松散,甩来甩去,这样是很难提高跑步速度的。所以在学习动作之前,应该先加强核心控制能力。 - -所以我们会推荐没有进行过抗阻训练、长期久坐的同学先去练习一段时间的瑜伽和普拉提,瑜伽可以很好地改善关节灵活性,进而提高身体的柔韧性;而普拉提能提高的核心控制能力,并提高关节稳定性。 -一个合格的健身训练者,应该了解不同的训练体系,他要知道自身还缺乏什么,然后向不同的训练体系去借鉴,以提高自己。 - -### 基础动作模式 - -#### 什么是基础动作模式? - -简单地说就是,所有动作肢体特有的运动程序。人体就这么些零件,所以很多的动作之间都存在着些许的共性,我们将这些共性提炼出来并进行功能上的抽象,那么就形成了我们现在所要说的基础动作模式——**双腿蹲**、**单腿蹲**、**推**、**拉**、**旋转**、**屈髋**。 - -- **蹲**:分为单腿蹲、双腿蹲。对应的训练动作有剪蹲和深蹲。 -- **推**:分为水平推、竖直推。对应的训练动作是卧推和实力举。 -- **拉**:分为竖直拉、水平拉。竖直拉包括引体向上、高位下拉,水平拉包括弹力带划船等等。 -- **屈髋**:最具代表性的动作就是硬拉。 -- **旋转**:动作比较复杂,在训练当中比较少出现,适合比较资深的训练者,比如说劈和砍,比如下劈球,比如拿锤子砸轮胎。前期不建议做,当你有一定训练水平的时候再去做旋转类动作。 - -![](https://ws4.sinaimg.cn/large/006tKfTcgy1fhg20yeticj30go0ptdmg.jpg) - -#### 基础动作模式的意义是什么? - -1. 教会我们如何正确的使用我们的身体 -2. 评估你的是否存在关节功能缺陷 - -基于基础动作模式的学习意义和诊断意义,我们对待健身者或者需要进行体能训练的人很多时候都从这里开始。如果诊断结果良好,那么我们学习动作之后就可以上升到基础力量训练,如果诊断出关节功能缺陷,我们就要进行针对性的解决。 - - -### 基础力量(肌肉力量) - -力量是所有运动素质的基础,如果你没有足够的力量,很多事情都很难完成。你想学习倒立,如果上肢力量足够,只需要了解动作技巧和细节,可能半个小时就能学会倒立。但是如果力量水平很低,就算把各种技巧和细节都学会了,也没有力气把自身撑起来,更不可能完成倒立。日常生活中,力量水平不足经常会成为我们突破运动瓶颈的障碍,有足够的力量才能跑得更快、跑得更远、跳得更高。所以,力量是所有运动素质的基础。 - -因此,在金字塔的这层,我们就要从徒手训练的阶段进阶到自由力量训练的阶段。负重和徒手的训练效果差异非常大,它不仅仅在于力量的提高,在重心控制、身体平衡、协调性控制等方面的区别也很大。力量训练能让身体各项能力同时提高,只有真正进入力量训练阶段(对于健身来说),才可以说真正踏上健身入门之路。 - -### 训练目标(综合体能和专项体能) - -最后,我们来到了金字塔的顶端,这就是我们的最终追求。 - -综合体能在此指的是体能所包含的五大运动素质——力量,速度,灵敏,耐力,柔韧。对于有专项运动需求的人,我们需要有针对性的重点发展这五大运功素质中的某几个。 - -当我们身体各关节灵活性和稳定性都可以满足要求,且核心控制能力也很强的情况下,又在标准动作的基础上储备了足够的肌肉力量,那么,不管你的目标是减肥,增肌,或者是进行某一个竞技性运动项目,你都可以相对安全且轻松地达成你的目标。 - -一般我们把还处在第一第二层的健身者称为健身入门者。不管你是刚刚走进健身房的新手,还是健身多年的老司机,都可以根据这个健身发展流程来审视自己目前的训练处在哪一个阶段。 - -## 总结 - -体能训练金字塔告诉我们,体能训练要从关节功能和核心控制开始训练,通过六大基础运动模式的训练增强身体基础力量,只有基础力量够了,我们才能真正开始我们的训练目标,根据我们训练的项目增强专项体能,比如拳击,我们要增强力量,耐力,灵敏。 - -清楚了这个体能训练金字塔之后,更重要的事还是要去执行。执行层面会涉及更多技术问题,也就是我们常说的如何做标准的动作,如何制定适合自己的训练计划等等。 - -健身是一项系统性工程,愿每一个人都能找到方法,科学有效地塑造自己的身体。 - ->参考 - ->- [《体能训练之金字塔》](https://zhuanlan.zhihu.com/p/20801623) ->- [零基础健身者的运动发展流程](http://www.jianshenjiaolian.com.cn/lingjichu-fazhan.html) \ No newline at end of file diff --git "a/_posts/2017-07-19-Swift\347\232\204HMAC\345\222\214SHA1\345\212\240\345\257\206.md" "b/_posts/2017-07-19-Swift\347\232\204HMAC\345\222\214SHA1\345\212\240\345\257\206.md" deleted file mode 100644 index dd9262875b1..00000000000 --- "a/_posts/2017-07-19-Swift\347\232\204HMAC\345\222\214SHA1\345\212\240\345\257\206.md" +++ /dev/null @@ -1,111 +0,0 @@ ---- -layout: post -title: Swift的HMAC和SHA1加密 -subtitle: swift中利用HMAC的SHA1对文本进行加密 -date: 2017-07-19 -author: BY -header-img: img/post-bg-hacker.jpg -catalog: true -tags: - - Swift ---- - ->HMAC是密钥相关的哈希运算消息认证码(Hash-based Message Authentication Code)。 HMAC运算利用哈希算法,以一个密钥和一个消息为输入,生成一个消息摘要作为输出。也就是说HMAC通过将哈希算法(SHA1, MD5)与密钥进行计算生成摘要。 - -## Objectice-C - -在上个 Objectice-C 项目中,使用的 HMAC 和 SHA1 进行加密。如下代码: - -```objc -+ (NSString *)hmacsha1:(NSString *)text key:(NSString *)secret { - - NSData *secretData = [secret dataUsingEncoding:NSUTF8StringEncoding]; - NSData *clearTextData = [text dataUsingEncoding:NSUTF8StringEncoding]; - unsigned char result[20]; - // SHA1加密 - CCHmac(kCCHmacAlgSHA1, [secretData bytes], [secretData length], [clearTextData bytes], [clearTextData length], result); - char base64Result[32]; - size_t theResultLength = 32; - // 转为Base64 - Base64EncodeData(result, 20, base64Result, &theResultLength,YES); - NSData *theData = [NSData dataWithBytes:base64Result length:theResultLength]; - NSString *base64EncodedResult = [[NSString alloc] initWithData:theData encoding:NSUTF8StringEncoding]; - return base64EncodedResult; -} -``` - - - -## swift - -最近用 swift 重构项目,用 Swift [重写了](https://stackoverflow.com/questions/26970807/implementing-hmac-and-sha1-encryption-in-swift?rq=1) HMAC 的 SHA1 加密方式。 - -### 使用 - -```swift -// 使用HMAC和SHA加密 -let hmacResult:String = "myStringToHMAC".hmac(HMACAlgorithm.SHA1, key: "myKey") -``` - -### 代码 - -使用下面代码时,需要在 OC 桥接文件`xxx-Bridging-Header`中 `#import ` - -```swift -extension String { - func hmac(algorithm: HMACAlgorithm, key: String) -> String { - let cKey = key.cStringUsingEncoding(NSUTF8StringEncoding) - let cData = self.cStringUsingEncoding(NSUTF8StringEncoding) - var result = [CUnsignedChar](count: Int(algorithm.digestLength()), repeatedValue: 0) - CCHmac(algorithm.toCCHmacAlgorithm(), cKey!, strlen(cKey!), cData!, strlen(cData!), &result) - var hmacData:NSData = NSData(bytes: result, length: (Int(algorithm.digestLength()))) - var hmacBase64 = hmacData.base64EncodedStringWithOptions(NSDataBase64EncodingOptions.Encoding76CharacterLineLength) - return String(hmacBase64) - } -} - -enum HMACAlgorithm { - case MD5, SHA1, SHA224, SHA256, SHA384, SHA512 - - func toCCHmacAlgorithm() -> CCHmacAlgorithm { - var result: Int = 0 - switch self { - case .MD5: - result = kCCHmacAlgMD5 - case .SHA1: - result = kCCHmacAlgSHA1 - case .SHA224: - result = kCCHmacAlgSHA224 - case .SHA256: - result = kCCHmacAlgSHA256 - case .SHA384: - result = kCCHmacAlgSHA384 - case .SHA512: - result = kCCHmacAlgSHA512 - } - return CCHmacAlgorithm(result) - } - - func digestLength() -> Int { - var result: CInt = 0 - switch self { - case .MD5: - result = CC_MD5_DIGEST_LENGTH - case .SHA1: - result = CC_SHA1_DIGEST_LENGTH - case .SHA224: - result = CC_SHA224_DIGEST_LENGTH - case .SHA256: - result = CC_SHA256_DIGEST_LENGTH - case .SHA384: - result = CC_SHA384_DIGEST_LENGTH - case .SHA512: - result = CC_SHA512_DIGEST_LENGTH - } - return Int(result) - } -} - - -``` - diff --git "a/_posts/2017-07-24-iTunes-Connect-\346\236\204\345\273\272\347\211\210\346\234\254\344\270\215\346\230\276\347\244\272.md" "b/_posts/2017-07-24-iTunes-Connect-\346\236\204\345\273\272\347\211\210\346\234\254\344\270\215\346\230\276\347\244\272.md" deleted file mode 100644 index 031da948120..00000000000 --- "a/_posts/2017-07-24-iTunes-Connect-\346\236\204\345\273\272\347\211\210\346\234\254\344\270\215\346\230\276\347\244\272.md" +++ /dev/null @@ -1,38 +0,0 @@ ---- -layout: post -title: iTunes Connect 构建版本不显示 -subtitle: App打包上传到 App Store, iTunes Connect 构建版本不显示 -date: 2017-07-24 -author: BY -header-img: img/post-bg-ios10.jpg -catalog: true -tags: - - iOS - - Xcode ---- - - -## 前言 - -今天新项目上架,在Xcode打包上传到App Store后,在iTunes Connect构建版本中居然找不到上传的App... - -## 解决 - -从iOS10开始,苹果更加注重对用于隐私的保护,App 里边如果需要访问用户隐私,必须要做描述,所以要在 plist 文件中添加描述。 - -而这三个基础描述是必须添加的: - -- **麦克风权限**:`Privacy - Microphone Usage Description` 是否允许此App使用你的麦克风? - -- **相机权限**:`Privacy - Camera Usage Description` 是否允许此App使用你的相机? - -- **相册权限**:`Privacy - Photo Library Usage Description` 是否允许此App访问你的媒体资料库? - - -其他的权限可以根据自己 APP 的情况来添加。 - -添加完权限之后然后继续提交 App 就可以了。 - -若还是找不到,返回 plist 文件中,删除之前的权限,重新添加一下,有可能你哪不小心添加的权限末尾有空格,或者字段不对。 - -End \ No newline at end of file diff --git "a/_posts/2017-07-26-\345\210\251\347\224\250-Debug-Memory-Graph-\346\243\200\346\265\213\345\206\205\346\265\213\346\263\204\346\274\217.md" "b/_posts/2017-07-26-\345\210\251\347\224\250-Debug-Memory-Graph-\346\243\200\346\265\213\345\206\205\346\265\213\346\263\204\346\274\217.md" deleted file mode 100644 index 3f4a32d933c..00000000000 --- "a/_posts/2017-07-26-\345\210\251\347\224\250-Debug-Memory-Graph-\346\243\200\346\265\213\345\206\205\346\265\213\346\263\204\346\274\217.md" +++ /dev/null @@ -1,78 +0,0 @@ ---- -layout: post -title: 利用 Debug Memory Graph 检测内测泄漏 -subtitle: 利用 Xcode 内存表(Debug Memory Graph)检测内测泄漏 -date: 2017-07-26 -author: BY -header-img: img/post-bg-hacker.jpg -catalog: true -tags: - - Xcode - - 开发技巧 ---- - - -## 前言 - -平常我们都会用 Instrument 的 Leaks / Allocations 或其他一些开源库进行内存泄露的排查,但它们都存在各种问题和不便, - -在这个 ARC 时代更常见的内存泄露是循环引用导致的 Abandoned memory,Leaks 工具查不出这类内存泄露,应用有限。 - -今天介绍一种简单直接的检测内测泄漏的方法:**Debug Memory Graph** - -就是这货: - -![](https://ws4.sinaimg.cn/large/006tNc79gy1fhxct12udnj311x0s3grw.jpg) - -## 正文 - -我最近的项目中,退出登录后(跳转到登录页),发现首页控制器没有被销毁,依旧能接收通知。 - -退出登录代码: - -```objc -UIStoryboard *storyboard = [UIStoryboard storyboardWithName:@"Login" bundle:[NSBundle mainBundle]]; -AppDelegate *appDelegate = (AppDelegate *)[UIApplication sharedApplication].delegate; -appDelegate.window.rootViewController = [storyboard instantiateViewControllerWithIdentifier:@"LoginVC"]; -``` - -很明显发生了循环引用导致的内测泄漏。 - -接下来就使用 **Debug Memory Graph** 来查看内测泄漏了。 - -### 运行程序 - -首先启动 Xcode 运行程序。 - -### Debug Memory Graph - -![](https://ws3.sinaimg.cn/large/006tNc79gy1fhxend1a8aj315y0s3gw5.jpg) - -点击 Debug Memory Graph 按钮后,可以看到红框内的是当前内存中存在的对象。其中,绿色的就是视图控制器。 - -这样,我们随时都可以查看内测中存在的对象,换句话说,就是可以通过观察 Memory Graph 查看内测泄漏。 - -### 调试你的App - -继续运行你的程序 - -![](https://ws2.sinaimg.cn/large/006tNc79gy1fhxeuh1np5j30v90kvn03.jpg) - -然后对App进行调试、push、pop 操作,再次点击 Debug Memory Graph 按钮。那些该释放而依旧在内测中的 `控制器` 或 `对象` 就能一一找出来了。 - -接下来,只要进入对应的控制器找到内测泄漏的代码就OK了,一般是Block里引用了 `self`,改为 `weakSelf` 就解决了。 - -```objc -#define WS(weakSelf) __weak __typeof(&*self)weakSelf = self; - -WS(weakSelf) -sView.btnBlock = ^(NSInteger idx){ - [weakSelf.tableView reloadSections:[NSIndexSet indexSetWithIndex:idx] withRowAnimation:UITableViewRowAnimationAutomatic]; -}; -``` - -## 结语 - -就这样,利用 Debug Memory Graph,可以简单快速的检测内测泄漏。 - -一般由两个对象循环引用的内测泄漏是比较好发现的,如果是由三个及其三个以上的对象形成的大的循环引用,就会比较难排查了。 diff --git "a/_posts/2017-09-11-Swift-4-\346\226\260\347\211\271\346\200\247.md" "b/_posts/2017-09-11-Swift-4-\346\226\260\347\211\271\346\200\247.md" deleted file mode 100644 index 6d22055271f..00000000000 --- "a/_posts/2017-09-11-Swift-4-\346\226\260\347\211\271\346\200\247.md" +++ /dev/null @@ -1,172 +0,0 @@ ---- -layout: post -title: Swift 4 新特性 -subtitle: 很高兴 Swift 4 不再是一门新语言了😅 -date: 2017-09-11 -author: BY -header-img: img/post-bg-ios10.jpg -catalog: true -tags: - - iOS - - Swift ---- - - -### private 权限扩大 - -在 Swift 4 中,`extension` 可以读取 `private` 变量了。 - -Swift 3 中,如果将主体函数的变量定义为 `private`,则其 `extension` 无法读取此变量,必须将其改为 `filePrivate` 才可以。 - -### 单向区间 - -单向区间是一个新的类型,主要分两种:确定上限和确定下限的区间。直接用字面量定义大概可以写成 `…6`和 `2…` - -例如 - -```swift -let intArr = [0, 1, 2, 3, 4] - -let arr1 = intArr[...3] // [0, 1, 2, 3] -let arr2 = intArr[3...] // [3, 4] -``` - -### 字符串改动 - - -#### String 操作简化了 - -`String` 许多要通过 `.characters` 进行的操作,可以直接用 String 进行操作了。 - -例如: - -```swift -let greeting = "Hello, 😜!" -// No need to drill down to .characters -let n = greeting.count -let endOfSentence = greeting.index(of: "!")! - -``` - -#### 新增 Substring 类型 - - -swift 4 为字符串片段新增了一个叫 `Substring` 的类型。 - -当你创建一个字符串的片段时,会产生一个 `Substring` 实例。`Substring` 与 `String` 用法相同, 因为子串和原字符串共享内存,所以对子串的操作快速而且高效。 - -```swift -let greeting = "Hi there! It's nice to meet you! 👋" -let endOfSentence = greeting.index(of: "!")! - -// 产生 Substring 实例 -let firstSentence = greeting[...endOfSentence] -// firstSentence == "Hi there!" - -// `Substring` 与 `String` 用法相同 -let shoutingSentence = firstSentence.uppercased() -// shoutingSentence == "HI THERE!" -``` - -但是要注意一个 `Substring` 保留从其生成的完整的 `String`值。 当您传递一个看似很小的 `Substring` 时,这可能导致意外的高内存开销。所以使用 `Substring`时,最好转化为 `String`. - -```swift -let newString = String(substring) -``` - - -#### 换行可以不用 `\n`了! - -Swift 3,字符串换行要插入 `\n`。 -例如: - -![](https://ws4.sinaimg.cn/large/006tNc79gy1fjdam0wvhhj305d0283yf.jpg) - -在 Swift 4 可以这样操作: - -![](https://ws2.sinaimg.cn/large/006tNc79gy1fjdas2yri4j303q0260sm.jpg) - -用两个 `“”“` 包裹起来的字符串会自动添加 `\n` 换行,更加直观了。注意:换行与缩进参照的是第二个 `“”“` 号的位置。 - -嗯,我觉得OK! - -#### 支持 Unicode 9 - -Swift 4 支持 Unicode 9,[为现代表情符号修正了一些问题](https://oleb.net/blog/2016/12/emoji-4-0/)。 - - -```swift -let family1 = "👨‍👩‍👧‍👦" -let family2 = "👨\u{200D}👩\u{200D}👧\u{200D}👦" -family1 == family2 // → true -``` - -居然还有这种操作~ - -### 新增 KeyPath 数据类型 - -KeyPath 是 Swift 4 新增加的数据类型。 - -定义两个结构体 `Person`与`Book` - -```swift -struct Person { - var name: String -} - -struct Book { - var title: String - var authors: [Person] - var primaryAuthor: Person { - return authors.first! - } -} - -let abelson = Person(name: "Harold Abelson") -let sussman = Person(name: "Gerald Jay Sussman") -let book = Book(title: "Structure and Interpretation of Computer Programs", authors: [abelson, sussman]) -``` -```swift -book[keyPath: \Book.title] -book[keyPath: \Book.primaryAuthor.name] -// 相当与 -book.title -book.primaryAuthor.name -``` - -这里 `\Book.title` 与 `\Book.primaryAuthor.name` 就是 KeyPath. - -KeyPath 可以用 `.appending` 拼接 - -```swift -let authorKeyPath = \Book.primaryAuthor -let nameKeyPath = authorKeyPath.appending(path: \.name) -// nameKeyPath = \Book.primaryAuthor.name -``` - -### 新增 `swapAt()` 函数 -Swift 4 引入了一种在集合中交换两个元素的新方法: `swapAt()` - -Swift 3 交换集合中的元素的用 `swap()` - -```swift -var numbers = [1,2,3,4,5] -swap(&numbers[0], &numbers[1]) -// numbers = [2,1,3,4,5] -``` - -Swift 4 中可以直接用 - -```swift -var numbers = [1,2,3,4,5] -numbers.swapAt(0,1) -// numbers = [2,1,3,4,5] -``` - - - -### 其他改动 - -其他改动如:**新的整数协议**、**泛型下标**、**NSNumber bridging**等 - -可以参考:[whats new in swift4](https://github.com/ole/whats-new-in-swift-4) \ No newline at end of file diff --git "a/_posts/2017-10-04-GCD-\345\234\250-Swift-\344\270\255\347\232\204\347\224\250\346\263\225.md" "b/_posts/2017-10-04-GCD-\345\234\250-Swift-\344\270\255\347\232\204\347\224\250\346\263\225.md" deleted file mode 100644 index 33faf6697c3..00000000000 --- "a/_posts/2017-10-04-GCD-\345\234\250-Swift-\344\270\255\347\232\204\347\224\250\346\263\225.md" +++ /dev/null @@ -1,96 +0,0 @@ ---- -layout: post -title: GCD 在 Swift 中的用法 -subtitle: -date: 2017-10-04 -author: BY -header-img: img/home-bg-art.jpg -catalog: true -tags: - - iOS - - Swift - - GCD ---- - - -## DispatchQueue - -Swift 中,对 GCD 语法进行了彻底改写。引入了 `DispatchQueue` 这个类。 - -先来看看在一个异步队列中读取数据, 然后再返回主线程更新 UI, 这种操作在新的 Swift 语法中是这样的: - -```swift -DispatchQueue.global().async { - - DispatchQueue.main.async { - - // 更新UI操作 - - } - -} - -``` - -`DispatchQueue.global().async` 相当于使用全局队列进行异步操作。然后在调用 `DispatchQueue.main.async` 使用主线程更新相应的 UI 内容。 - -## 优先级 - -新的 GCD 引入了 QoS (Quality of Service) 的概念。 - -先看看下面的代码: - -```swift -DispatchQueue.global(qos: .userInitiated).async { - -} - -``` - - -QoS 对应的就是 `Global Queue` 中的优先级。 其优先级由最低的 `background` 到最高的 `userInteractive` 共五个,还有一个未定义的 `unspecified`。 - -```swift -public static let background: DispatchQoS - -public static let utility: DispatchQoS - -public static let `default`: DispatchQoS - -public static let userInitiated: DispatchQoS - -public static let userInteractive: DispatchQoS - -public static let unspecified: DispatchQoS -``` - -## 自定义 Queue - -除了直接使用 `DispatchQueue.global().async` 这种封装好的代码外,还可以通过`DispatchWorkItem` 自定义队列的优先级,特性: - -```swift -let queue = DispatchQueue(label: "swift_queue") -let dispatchworkItem = DispatchWorkItem(qos: .userInitiated, flags: .inheritQoS) { - -} -queue.async(execute: dispatchworkItem) - -``` -## GCD定时器 - -Swift 中 `dispatch_time`的用法改成了: - -```swift -let delay = DispatchTime.now() + .seconds(60) -DispatchQueue.main.asyncAfter(deadline: delay) { - -} -``` - -相较与OC来说更易读了: - -```objc -let dispatch_time = dispatch_time(DISPATCH_TIME_NOW, Int64(60 * NSEC_PER_SEC)) -``` - -- 参考 [GCD 在 Swift 3 中的玩儿法](https://www.swiftcafe.io/2016/10/16/swift-gcd/) \ No newline at end of file diff --git "a/_posts/2017-12-19-\344\270\272\345\215\232\345\256\242\346\267\273\345\212\240-Gitalk-\350\257\204\350\256\272\346\217\222\344\273\266.md" "b/_posts/2017-12-19-\344\270\272\345\215\232\345\256\242\346\267\273\345\212\240-Gitalk-\350\257\204\350\256\272\346\217\222\344\273\266.md" deleted file mode 100644 index 11a31091072..00000000000 --- "a/_posts/2017-12-19-\344\270\272\345\215\232\345\256\242\346\267\273\345\212\240-Gitalk-\350\257\204\350\256\272\346\217\222\344\273\266.md" +++ /dev/null @@ -1,125 +0,0 @@ ---- -layout: post -title: 为博客添加 Gitalk 评论插件 -subtitle: BY Blog 添加 Gitalk 的评论插件了 -date: 2017-12-19 -author: BY -header-img: img/post-bg-universe.jpg -catalog: true -tags: - - Blog ---- - - -## 前言 - -由于 **Disqus** 对于国内网路的支持十分糟糕,很多人反映 Disqus 评论插件一直加载不出来。而我一直是处于翻墙状态的~(话说你们做程序员的都不翻墙用Google的吗😅,哈哈,吐嘈下) - -针对这个问题,我添加了[Gitalk](https://github.com/gitalk/gitalk) 评论插件。在此,非常感谢 [@FeDemo](https://github.com/FeDemo) 的推荐 。 - -## 正文 - -### Gitalk 评论插件 - -首先来看看 Gitalk 的界面和功能: - -[![](https://ws4.sinaimg.cn/large/006tKfTcgy1fmm4u3j0lmj30nk0kl40i.jpg)](https://gitalk.github.io/) - -gitalk 使用 Github 帐号登录,界面干净整洁,最喜欢的一点是支持 `MarkDown语法`。 - -### 原理 - -Gitalk 是一个利用 Github API,基于 Github issue 和 Preact 开发的评论插件,在 Gitalk 之前还有一个 [gitment](https://github.com/imsun/gitment) 插件也是基于这个原理开发的,不过 gitment 已经很久没人维护了。 - -可以看到在 gitalk 的评论框进行评论时,其实就是在对应的 issue 上提问题。 - -![gitalk评论框](https://ws4.sinaimg.cn/large/006tKfTcgy1fmm5916av1j30i209rab7.jpg) - -![Github issue](https://ws4.sinaimg.cn/large/006tKfTcgy1fmm596ggkfj30mx0gfjuk.jpg) - - -### 集成 Gitalk - -到这里,你应该对 Gitalk 有个大致的了解了,现在,开始集成 gitalk 插件吧。 - - -将这段代码插入到你的网站: - - -```js - -{% if site.gitalk.enable %} - - - - -
- -{% endif %} - -``` - -我们需要关心的就是配置下面几个参数: - -```js -clientID: `Github Application clientID`, -clientSecret: `Github Application clientSecret`, -repo: `Github 仓库名`,//存储你评论 issue 的 Github 仓库名(建议直接用 GitHub Page 的仓库名) -owner: 'Github 用户名', -admin: ['Github 用户名'], //这个仓库的管理员,可以有多个,用数组表示,一般写自己, -id: 'window.location.pathname', //页面的唯一标识,gitalk 会根据这个标识自动创建的issue的标签,我们使用页面的相对路径作为标识 -``` -当然,还有其他很多参数,有兴趣的话可以 [ 点这里](https://github.com/gitalk/gitalk#options)。 - -比如我就增加了这个全屏遮罩的参数。 - -``` -distractionFreeMode: true, -``` - -### 创建 Github Application - -Gitalk 需要一个 **Github Application**,[点击这里申请](https://github.com/settings/applications/new)。 - -填写下面参数: - -![](https://ws1.sinaimg.cn/large/006tKfTcgy1fmm7jaib6fj30jo0gaacs.jpg) - -点击创建 - -获取 `Client ID` 和 `Client Secret` 填入你的我们 Gitalk 参数中 - -![](https://ws1.sinaimg.cn/large/006tKfTcgy1fmm7jrzff6j30lc0budhp.jpg) - -当你参数都设置好,将代码推送到 Github 仓库后,没什么问题的话,当你点击进入你的博客页面后就会出现评论框了。 - -当你用 github 帐号登录(管理员),并且第一次加载该会比较慢,因为第一次加载会自动在你 `repo` 的仓库下创建对应 issue。 - -比如说这样: - -![](https://ws2.sinaimg.cn/large/006tKfTcgy1fmm867n88cj30l809mjse.jpg) - -![](https://ws4.sinaimg.cn/large/006tKfTcgy1fmm8a0i0jkj30rr0ct42t.jpg) - -当然,你也可以手动创建issue作为 gitalk评论容器。只要有 `Gitalk` 标签 和 `id` 对应标签就可以。比我我自己创建的 [About issue](https://github.com/qiubaiying/qiubaiying.github.io/issues/38) 。 - -# 结语 - -最后说几句吐嘈几句, Gitalk 需要你点开每篇文章的页面才会创建对应的 issue,对我来说真是个糟糕的体验(文章有点多~)。 - -当然,也有解决办法,这篇 [自动初始化 Gitalk 和 Gitment 评论](https://draveness.me/git-comments-initialize),就解决了这个问题。 - -最后,[给个 star 吧](https://github.com/qiubaiying/qiubaiying.github.io)~ \ No newline at end of file diff --git "a/_posts/2017-12-26-\344\273\216\344\270\200\351\201\223\347\275\221\346\230\223\351\235\242\350\257\225\351\242\230\346\265\205\350\260\210-Tagged-Pointer.md" "b/_posts/2017-12-26-\344\273\216\344\270\200\351\201\223\347\275\221\346\230\223\351\235\242\350\257\225\351\242\230\346\265\205\350\260\210-Tagged-Pointer.md" deleted file mode 100644 index f93fa76daeb..00000000000 --- "a/_posts/2017-12-26-\344\273\216\344\270\200\351\201\223\347\275\221\346\230\223\351\235\242\350\257\225\351\242\230\346\265\205\350\260\210-Tagged-Pointer.md" +++ /dev/null @@ -1,107 +0,0 @@ ---- -layout: post -title: 从一道网易面试题浅谈 Tagged Pointer -subtitle: 浅谈 Tagged Pointer -date: 2017-12-26 -author: BY -header-img: img/post-bg-universe.jpg -catalog: true -tags: - - iOS ---- - - -## 前言 - -这篇博客九月就想写了,因为赶项目拖了到现在,抓住17年尾巴写吧~ - - -## 正文 - -上次看了一篇 [《从一道网易面试题浅谈OC线程安全》](https://www.jianshu.com/p/cec2a41aa0e7) 的博客,主要内容是: - -作者去网易面试,面试官出了一道面试题:下面代码会发生什么问题? - -```objc -@property (nonatomic, strong) NSString *target; -//.... -dispatch_queue_t queue = dispatch_queue_create("parallel", DISPATCH_QUEUE_CONCURRENT); -for (int i = 0; i < 1000000 ; i++) { - dispatch_async(queue, ^{ - self.target = [NSString stringWithFormat:@"ksddkjalkjd%d",i]; - }); -} -``` - -答案是:会 crash。 - -我们来看看对`target`属性(`strong`修饰)进行赋值,相当与 MRC 中的 - -``` -- (void)setTarget:(NSString *)target { - if (target == _target) return; - id pre = _target; - [target retain];//1.先保留新值 - _target = target;//2.再进行赋值 - [pre release];//3.释放旧值 -} -``` - -因为在 *并行队列* `DISPATCH_QUEUE_CONCURRENT` 中*异步* `dispatch_async` 对 `target`属性进行赋值,就会导致 target 已经被 `release`了,还会执行 `release`。这就是向已释放内存对象发送消息而发生 crash 。 - - -### 但是 - -我敲了这段代码,执行的时候发现并不会 crash~ - -```objc -@property (nonatomic, strong) NSString *target; -dispatch_queue_t queue = dispatch_queue_create("parallel", DISPATCH_QUEUE_CONCURRENT); -for (int i = 0; i < 1000000 ; i++) { - dispatch_async(queue, ^{ - // ‘ksddkjalkjd’删除了 - self.target = [NSString stringWithFormat:@"%d",i]; - }); -} -``` - -原因就出在对 `self.target` 赋值的字符串上。博客的最后也提到了 - *‘上述代码的字符串改短一些,就不会崩溃’*,还有 `Tagged Pointer` 这个东西。 - -我们将上面的代码修改下: - - -```objc -NSString *str = [NSString stringWithFormat:@"%d", i]; -NSLog(@"%d, %s, %p", i, object_getClassName(str), str); -self.target = str; -``` - -输出: - -``` -0, NSTaggedPointerString, 0x3015 -``` - -发现这个字符串类型是 `NSTaggedPointerString`,那我们来看看 Tagged Pointer 是什么? - -### Tagged Pointer - -Tagged Pointer 详细的内容可以看这里 [深入理解Tagged Pointer](http://www.infoq.com/cn/articles/deep-understanding-of-tagged-pointer)。 - -Tagged Pointer 是一个能够提升性能、节省内存的有趣的技术。 - -- Tagged Pointer 专门用来存储小的对象,例如 **NSNumber** 和 **NSDate**(后来可以存储小字符串) -- Tagged Pointer 指针的值不再是地址了,而是真正的值。所以,实际上它不再是一个对象了,它只是一个披着对象皮的普通变量而已。 -- 它的内存并不存储在堆中,也不需要 malloc 和 free,所以拥有极快的读取和创建速度。 - - - - -### 参考: - -- [从一道网易面试题浅谈OC线程安全](https://www.jianshu.com/p/cec2a41aa0e7) - -- [深入理解Tagged Pointer](http://www.infoq.com/cn/articles/deep-understanding-of-tagged-pointer) - -- [【译】采用Tagged Pointer的字符串](http://www.cocoachina.com/ios/20150918/13449.html) - diff --git "a/_posts/2017-2-9-Mac\345\277\253\351\200\237\350\260\203\345\207\272\347\273\210\347\253\257.md" "b/_posts/2017-2-9-Mac\345\277\253\351\200\237\350\260\203\345\207\272\347\273\210\347\253\257.md" deleted file mode 100644 index a01dc11c8f6..00000000000 --- "a/_posts/2017-2-9-Mac\345\277\253\351\200\237\350\260\203\345\207\272\347\273\210\347\253\257.md" +++ /dev/null @@ -1,97 +0,0 @@ ---- -layout: post -title: Mac 快速调出终端 -subtitle: 在Mac下为终端设置快捷键 -date: 2017-02-06 -author: BY -header-img: img/post-bg-re-vs-ng2.jpg -catalog: true -tags: - - Mac - - 效率 - - 开发技巧 ---- - ->在Mac下快速调出终端的方法是:为终端添加一个快捷键打开方式 - -## 为终端添加一个快捷键打开方式 - -打开Mac下自带的软件 **Automator** - -![](https://ww2.sinaimg.cn/large/006tKfTcgy1fckb184f74j319v0q01kx.jpg) - -新建文稿 - -![](https://ww1.sinaimg.cn/large/006tKfTcgy1fckb6zzo28j30mo0fvgn7.jpg) - -创建一个服务 - -![](https://ww1.sinaimg.cn/large/006tKfTcgy1fckb93qmy5j30g00fh0vq.jpg) - -![](https://ww2.sinaimg.cn/large/006tKfTcgy1fckbfe8o0zj30t10lb0wv.jpg) - -![](https://ww1.sinaimg.cn/large/006tKfTcgy1fckbff4e7pj30t10lbwis.jpg) - -修改框内的脚本 - -``` -on run {input, parameters} tell application "Terminal" reopen activate end tell end run - -``` - -运行:`command + R`,如果没有问题,则会打开终端 - -![](https://ww2.sinaimg.cn/large/006tKfTcgy1fckaqdd2m1j30t10lb42a.jpg) - -![](https://ww3.sinaimg.cn/large/006tKfTcgy1fckaq4nn9hj30iy0daaan.jpg) - -保存:`Command + S`,将其命名为`打开终端`或你想要的名字 - -设置快捷键 - -在 **系统偏好设置** -> **键盘设置** -> **快捷键** -> **服务** - -选择我们创建好的 '**打开终端**',设置你想要的快捷键,比我我设置了`⌘+空格` - -![](https://ww4.sinaimg.cn/large/006tKfTcgy1fckbvaixhnj30kw0ihq67.jpg) - -到此,设置完成。 - -聪明的你也许会发现,这个技巧能为所有的程序设置快捷启动。 - -将脚本中的 `Terminal` 替换成 其他程序就可以 - -``` -on run {input, parameters} - tell application "Terminal" - reopen - activate - end tell -end run - -``` - -## 黑技能 - -既然学了 `Automator` ,那就在附上一个黑技能吧。为你的代码排序。在 **Xcode8**以前,有个插件能为代码快速排序,不过时过境迁~ 对于没用的插件而且又有患有强迫症的的小伙伴,只能手动排序了(😂). - -首先还是创建一个服务 - -创建一个`Shell`脚本, - -勾选:`用输出内容替换所选文本` - -输入:`sort|uniq` - -保存: 存为`Sort & Uniq` - -![](https://ww4.sinaimg.cn/large/006tKfTcgy1fckd40rgwmj30rt0ildiy.jpg) - -**选中你的代代码** -> **鼠标右键** -> **Servies** -> **Sort&Uniq** - -![](https://ww2.sinaimg.cn/large/006tKfTcgy1fckd6tx1dzj30h90b7mzm.jpg) - -排序后的代码: - -![](https://ww3.sinaimg.cn/large/006tKfTcgy1fckd6lak55j309j05y3yo.jpg) - diff --git "a/_posts/2017-3-07-\346\226\207\344\273\266\347\233\256\345\275\225\346\240\221\347\212\266(tree)\346\230\276\347\244\272.md" "b/_posts/2017-3-07-\346\226\207\344\273\266\347\233\256\345\275\225\346\240\221\347\212\266(tree)\346\230\276\347\244\272.md" deleted file mode 100644 index 7ab70fce87a..00000000000 --- "a/_posts/2017-3-07-\346\226\207\344\273\266\347\233\256\345\275\225\346\240\221\347\212\266(tree)\346\230\276\347\244\272.md" +++ /dev/null @@ -1,40 +0,0 @@ ---- -layout: post -title: 文件目录树状(tree)显示 -subtitle: Mac终端显示输树状文件结构 -date: 2017-03-07 -author: BY -header-img: img/post-bg-debug.png -catalog: true -tags: - - Mac - - 效率 - - 开发技巧 ---- - -> 使用 **tree** 在终端显示树状文件结构 - -![](https://ww4.sinaimg.cn/large/006tKfTcgy1fdhotefcb5j315s0ugjwk.jpg) - -#### 安装 tree -使用 **brew** 进行安装 - - $ brew install tree - -#### 使用 -- 直接使用 `tree` 命令,会在当前文件目录下,递归输出所有文件层级 - - $ tree - -- 限制层级 - - $ tree -L 2 - -- 指定当前目录下的某个文件夹 - - $ tree Desktop - -#### 导出文件 -用`> 文件名.格式` 的形式导出 - - $ tree -L 1 > tree.md \ No newline at end of file diff --git "a/_posts/2018-01-04-\346\237\224\346\234\257\346\234\200\345\244\247\347\232\204\350\260\216\350\250\200\343\200\214\350\257\221\343\200\215.md" "b/_posts/2018-01-04-\346\237\224\346\234\257\346\234\200\345\244\247\347\232\204\350\260\216\350\250\200\343\200\214\350\257\221\343\200\215.md" deleted file mode 100644 index b4890db5e55..00000000000 --- "a/_posts/2018-01-04-\346\237\224\346\234\257\346\234\200\345\244\247\347\232\204\350\260\216\350\250\200\343\200\214\350\257\221\343\200\215.md" +++ /dev/null @@ -1,168 +0,0 @@ ---- -layout: post -title: 柔术最大的谎言「译」 -subtitle: 辩证分析柔术中技术与力量 -date: 2018-01-04 -author: BY -header-img: img/post-bg-BJJ.jpg -catalog: true -tags: - - BJJ ---- - -> 译自 [《THE BIGGEST LIE IN JIU JITSU》](http://www.jiujitsubrotherhood.com/the-biggest-lie-in-jiu-jitsu/) - - -![Marcus'Buchecha'Almeida - 现任IBJJF绝对冠军。这家伙很坚强,相信我!图片由BJJ Pix的William Burkhardt提供 。](http://mjrnxewya3t1in23ybpwjw59.wpengine.netdna-cdn.com/wp-content/uploads/buchecha-marcus-almeida-roger-gracie.jpg) - - -最近我看到了一个让我捧腹的柔术笑话。 - -“柔术的技术是无敌的!” - -你可能不想听听下面的内容,但是作为一名柔术教练,我的工作是与你分享我认为的真理,而不是虚假的谎言。所以在这里与你们分享我的见解: - -### 目录 - -- 技术不是无敌的 -- 我的见解 -- 一个假设 -- 真实的例子 -- 这个神话是从哪里来的? -- 好消息 -- 如何变得更强 -- 你该怎么做 - - -## 技术不是无敌的 - -基础运动能力,特别是力量,对柔术的表现起着巨大的作用,而且往往可以克服技巧。尽管你被告知了卓越的技术并不总是能克服体型和力量优势。但在我看来,力量和技术一样重要。 - -## 我的见解 - -我练习巴西柔术将近二十年,已经是一个黑带了。我认为自己是一个技术顶尖的柔术运动员,我致力于使自己的技术动作更加高效和精准。 - -但是这里有个小秘密:有时候,我会利用力量强行完成一个柔术动作。我并不常这样做,但我明白力量对于柔术的重要性,并且它可以更好的帮助我完成动作,我已经认识到这样做是正确的。 - -我的脑袋中仍然有一种“无力游戏”的想法,那就是我技术非常优秀,以至于我不需要出力就可以降服对手。但我知道这只是我的一个天真的想法。 - -是的,世界顶尖的柔术运动员毫无疑问都拥有顶尖的柔术技术。但是,他们无一例外都是非常强壮的人。由于先天的遗传和后天科学的训练,这些家伙拥有不可思议的力量和体格。 - -我的经验得出的这个等式适用与大多数情况: - -> **运动员A(中等技术 + 上等身体素质) > 运动员B(上等技术 + 下等身体素质)** - -### 一个假设 - -我知道你还不相信我,所以我会用一个例子来说明我的观点。让我们来看看使用两个战士 Steve 和 JoJo 的假想情景。 - -#### Steve: - -Steve 5岁开始学习柔术,由马塞洛·加西亚,拉法·门德斯和瑞克森·格雷西执教。它学会了他们所有的技巧,并且吸收了他们所有关于压力,时间和人体力学的智慧。他在接下来的13年里每周训练6天。 - -18岁,体重200磅的 Steve 击败了所有对手取得了 [IBJJF(国际巴西柔术联合会](https://en.wikipedia.org/wiki/International_Brazilian_Jiu-Jitsu_Federation))世界锦标赛的棕色带中量级冠军,并且立即被授予黑带。第二年,在对战拥有绝对黑带实力的 Buchecha(开头照片中的人)的决赛中,用了一个飞身十字固在13秒内降服了他。 - -#### JoJo: - -JoJo 是一个10岁的银背大猩猩。他体重400磅。他从未接受过柔术或其他武术的训练。 - -#### 一决胜负: - -假设 JoJo 与 Steve 展开一场柔术规则的比赛。 - -你认为谁会赢得这场比赛?如果你认为 Steve 会用他的“无敌技巧”击败 JoJo,那么你就是妄想。(此外,你可以用 [点穴](https://en.wikipedia.org/wiki/Dim_Mak_Records) 试试~) - -JoJo 的**体格**与**力量**优势根本无法用技术来克服。 - -![你可以知道世界上所有的柔术运动,但是你不会打败JoJo。](http://mjrnxewya3t1in23ybpwjw59.wpengine.netdna-cdn.com/wp-content/uploads/gorilla.jpg) - -### 真实的例子 - -好吧,上面的例子非常不切实际,根本不会发生。但是,我可以举一些我身边的例子: - -#### 例子1: - -在2013年,我亲眼目睹了世界冠军中,一位黑带女性与体重相同的紫色带男性的比赛,他们在一个开放的垫子上打成一片。这个女人一点机会都没有。她在6分钟内拍垫近十次。 - -那么现在是因为“女人不擅长柔术”还是因为“男人比女人好”呢?当然不是。这只是一个简单的力量问题。这位男性拥有更高的睾酮水平,因此拥有更强大的结缔组织和更多的肌肉。 - -#### 例子2: - -我有一个朋友身高 1.95m,重达 300磅(136kg),是一个前NCAA中后卫球员。同时他也是柔术棕色腰带。他可以(而且经常)很容易地只用一只手臂将我从地面上抬起。当我们滚动时他绝对砸我,这时候基本上我是无能为力的。 - -这是否因为他的技术比我好?当然不是。我的训练的时间比他更长,训练频率和强度要高得多。这是因为他比我更高,更大,更强壮。 - -#### 例子3: - -我的正常体重大约是203磅(92kg)。有时,由于各种原因,包括力量训练计划,肌酸周期或假日过度放纵,可能会高达218甚至220磅。 - -因为我一直在垫子上呆着,所以我可以敏锐的察觉到体重对于柔术的影响。我可以直接告诉你:你越重,对抗越轻松。我可以更轻松的控制体重较轻的对手,并且能对抗更长的时间。 - -### 这个神话是从哪里来的? - -#### 传统武术的胡扯 - -这个误解也是传统武术的骗人的精髓所在。告诉一个弱小的人学习了某种武术,他就轻松可以击败比他高大,更强壮的坏人。 - -在20世纪,一个巨大的产业就建立在这个基础之上,各种乱七八糟的武术系统被包装并推给了好骗的西方人。尽管MMA中的柔术技术帮助清除了许多武术的骗局,但现在仍然受到影响。 - - -#### 柔术课的结构 - -还有一部分原因是由于柔术学院商业模式的本质。虽然柔术比赛竞争激烈,但是现在的柔术学院通常还只是围绕着`技术动作`和`实战对抗`这两个方面进行教学和训练。因此,早期的先驱者重视身体训练,这是有道理的。 - -乔治·圣皮埃尔的教练Firhas Zahabi曾经对我说过。“随着柔术学院商业化的推广,我们看到了很多必要的体能训练消失了。”他说的对,在绝大多数的柔术学院中,体能训练并不被重视。当然,你也可以做一些跳跃俯卧撑和俯卧撑作为热身的一部分,但这还远远不够。看看拳击手和摔跤手。体能训练往往是他们训练的最重要的组成部分,而对抗往往是花时间最小的一个。 - -#### 罗伊斯·格雷斯 与 UFC - -罗伊斯·格雷西(Royce Gracie)在 UFC 早期的比赛中的惊人表现导致了一些人相信技术确实是无敌的。在我看来,罗伊斯赢了,因为他打的比赛看起来像这样: - -> **斗士A(中等属性+强大的技术)> 斗士B(伟大的属性+没有技术)** - -由于第二代 MMA 斗士的的属性已经改变,因为家伙们已经开始学习柔术了。比赛开始更像这样: - -> **斗士A(中等属性+强大技术)≥ 斗士B(强大属性+一点点技巧)** - -在如今的 MMA 比赛中,我们经常看到的情况是这样的: - -> **斗士A(卓越的属性+伟大的技术)> 斗士B(伟大的属性+伟大的技术)** - -#### 杠杠原理的迷惑 - -杠杠原理能成倍加强力量,但不是力量的来源。当然,杠杠原理能帮你能更有效的利用力量,但没有力量来源,这个杠杠力也不复存在。这就是‘柔术’中‘杠杠原理’这个概念的迷惑性。 - -尽管可能会有人告诉你,没有人能为柔术添加杠杠作用。但是一些聪明的运动员及教练确实能够准确的找到杠杠的支点,并且使用的力量来完成动作,效果惊人。 - -### 好消息 - -好消息是就算你只进行柔术对抗训练也能自然而然的提升你的体能,尽管这个提升有局限性并且基因决定了你的体能极限(抱歉,就是这样),而通过科学而且集中体能训练可以大幅度提升你的体能。 - -同时,体型小的训练者并不是总是处于劣势。相对力量会随着体型的增加而减小。所以假设其他条件相同的情况下,一个体重比你大20%的对手,力量并不会比你大20%,通常这个值会是12%~15%。这就意味着那些拥有惊人身体的小个子训练者通常会扳平体型的劣势,有时候甚至还会反超。 - -最后一个就是力量的增长也会随着年龄的增长而称下降的趋势,并在年老的时候就维持不变了。“人的力量就是这么真实”。 - - - -### 如何变得更强 - -#### 检查你的激素水平 - -如果你是一个男性柔术运动员,我建议你去内分泌专家那检查你的激素水平。如果你的睾丸酮激素水平过低,不管你如何训练,你的身体素质都不会有较大的提升。一个好医生会建议你使用多种补剂和药品来解决这个问题。 - -#### 体操 - -总体来说,拥有了功能性力量与身体控制能力,你将很难被击败。如果让我在力量训练之外再挑选一个最为柔术的赋值训练,那就是体操了。 - -#### 攀岩 - -另一项能直接对柔术的运动表现及其力量提升极大的运动就是攀岩了,尤其是握力。 - -#### 举重 - -举重对运动表现的提升不是通过几组二头弯举或者卧推就可以的,那是健身。你需要在专业教练的指导下练习奥运举和力量举(例如挺举,深蹲)。 - -### 你该怎么做 - -提高柔术水平不仅仅是提升柔术技术。我喜欢柔术的技术,它是那样的直接有效,令人着迷。如果你想在道垫上降服对手,高质量的动作是必不可少的。但这还不够。你可以在[这篇文章](http://www.jiujitsubrotherhood.com/brazilian-jiu-jitsu-tips-a-c-t-model/)中找到答案。 - -真正的武术家是一个在各个方面力精益求精的人。这包括变得更加强壮。如果你想成为顶级的柔术家,将你的体能提升到极限是你的必修课。 \ No newline at end of file diff --git "a/_posts/2018-05-05-Xcode\345\221\275\344\273\244\350\241\214\345\267\245\345\205\267\347\256\241\347\220\206.md" "b/_posts/2018-05-05-Xcode\345\221\275\344\273\244\350\241\214\345\267\245\345\205\267\347\256\241\347\220\206.md" deleted file mode 100644 index cc482549913..00000000000 --- "a/_posts/2018-05-05-Xcode\345\221\275\344\273\244\350\241\214\345\267\245\345\205\267\347\256\241\347\220\206.md" +++ /dev/null @@ -1,28 +0,0 @@ ---- -layout: post -title: Xcode命令行工具管理 -subtitle: 如何切换Xcode命令行工具 -date: 2018-05-05 -author: BY -header-img: img/post-bg-kuaidi.jpg -catalog: true -tags: - - Xcode - - iOS ---- - -## 安装 - - xcode-select --install - -![](https://upload-images.jianshu.io/upload_images/545662-f9031dfcce085f8f.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/459) - -## Xcode版本切换 - -### 显示当前使用的xocde版本 - - $ xcode-select --print-path - -### 选择Xcode中的默认版本 - - $ sudo xcode-select -switch /Applications/Xcode.app \ No newline at end of file diff --git "a/_posts/2018-06-05-5\345\210\206\351\222\237\345\270\246\344\275\240\347\234\213\345\256\214-WWDC-2018.md" "b/_posts/2018-06-05-5\345\210\206\351\222\237\345\270\246\344\275\240\347\234\213\345\256\214-WWDC-2018.md" deleted file mode 100644 index 4bf80dc53db..00000000000 --- "a/_posts/2018-06-05-5\345\210\206\351\222\237\345\270\246\344\275\240\347\234\213\345\256\214-WWDC-2018.md" +++ /dev/null @@ -1,228 +0,0 @@ ---- -layout: post -title: 5分钟带你看完 WWDC 2018 -subtitle: WWDC 2018 Keynote 全记录 -date: 2018-06-05 -author: BY -header-img: img/post-bg-cook.jpg -catalog: true -tags: - - iOS ---- - -## 前言 - -一年一度的 WWDC(苹果全球开发者大会)于北京时间 6月5日 凌晨1点开幕。废话不多说,来看看这次WWDC 都有哪些亮点吧! - - -## iOS 12 和 ARKit 2.0 - ->关键词:官方防沉迷最为致命 - -### iOS 12 - -iOS 12 相较于 iOS 11 并没有太多UI上的变动,刚更新完 bate 版本的 iOS 12,完全感觉不到这是个新系统。 - -iOS 12 主要是对安全和性能的优化,iOS 12 在旧设备上的运行速度比 iOS 11更块,程序加载速度快了一倍。(PS:看来苹果并没有放弃旧设备) - -![](https://cdn.mos.cms.futurecdn.net/RdxhPVv8fAyM6oHsRgF6dH-650-80.png) - -### ARKit 2.0 - -Apple 与 皮克斯 合作开发了一种用于共享AR内容的新文件格式,新的 AR 格式名为 USDZ。 - -作为一个含着金苹果出生的新生儿,USDZ 一开始就得到了 Adobe Creative Cloud (包括 Photoshop CC、InDesign CC、Illustrator CC、Dreamweaver CC、Premiere Pro CC)套件的支持。 - -![](https://images.ifanr.cn/wp-content/uploads/2018/06/WWDC-10.jpg) - -同时,面向开发者的开发套件 ARKit 则升级到了二代,主要提升了面部跟踪、渲染能力、3D 探测和共享体验等能力。 - -随后展示了一款名为 Measure 的 App,可使用AR查看物品大小。 - -![](https://cdn.mos.cms.futurecdn.net/4tbGCxGUGsH9VwSLsfMDK5-650-80.png) - -最后为了演示新的 AR 能力和效果,苹果请来了乐高的创意总监来捧场。这是一个真实的乐高积木建筑物为基础,最多四个人可以用苹果 AR 应用进行游戏,可以在真实环境中模拟出各种虚拟的形象和建筑。 - -![](https://images.ifanr.cn/wp-content/uploads/2018/06/WWDC-9.jpg) -![](https://images.ifanr.cn/wp-content/uploads/2018/06/WWDC-11.jpg) - -### 相册优化 - -iOS 12 的相册将大大提升搜索性能,系统不仅会提出搜索建议,还会帮你按主题整理照片。 - -![](https://images.ifanr.cn/wp-content/uploads/2018/06/WWDC-13.jpg) - -![](https://images.ifanr.cn/wp-content/uploads/2018/06/WWDC-14.jpg) - -### Siri变得更聪明 - -iOS 12 中,苹果为 Siri 提供了更加高效的操作,让它可以操作各个应用内部的功能,并且能在锁屏界面建议用户下一步的行动。 - -苹果还发布了一款名为「Shortcuts」的应用,它允许用户自定义 Siri 搜索指令,支持通过拖拽来快速编辑指令,同时还提供了一个指令库供用户下载现成的命令,就像是为 Siri 打造的 Workflow 自动化工具。 - -![](https://cdn.sspai.com/2018-06-04-Artboard.jpg?imageView2/2/w/1120/q/90/interlace/1/ignore-error/1) - -看到这里,相比熟悉苹果的朋友大概明白了,Siri 的本次改进,很可能是源于它收购的效率神器 Workflow,堪称一个用 Siri 唤醒的 Workflow。 - -### 原生应用大更新 - -iOS 12 中,不少原生应用都得到了更新。 - -- iBooks 更名为 **Apple Books**,采用类似 App Store 的新界面设计。 -- 新闻应用(News)在 iPad 上新添加了侧边栏,方便浏览,也突出策划内容。 -- 语音备忘录现在支持 iCloud 了 -- 股市的界面重构,可以看到股票全天走势,并打通 News 应用,方便看财经新闻; - -### CarPlay 开放了 - -![](https://images.ifanr.cn/wp-content/uploads/2018/06/WWDC-21.jpg) - -CarPlay 变得更加开放了,曾经被苹果狠心抛弃的 Google Maps 和 Google 的干儿子地图 Waze,以及来自东方的神秘力量高德地图成为首批 CarPlay 支持的第三方导航,从此“志玲姐姐为您导航”将可以常伴林肯领航者车主左右,中国梦和美国梦一起实现。 - -### 防手机沉迷 - 划重点! - -本次 iOS 12 的重点就是:**防沉迷!** - -![](https://images.ifanr.cn/wp-content/uploads/2018/06/WWDC-25.jpg) - -鉴于手机上瘾成为了一个社会议题,今年 Google 和苹果都不约而同地将“防沉迷”加入到了系统更新当中: - -- **Do Not Disturb(别吵我)**功能将关掉手机的视觉通知,在夜深人静想起她或梦见她的时候,来了邮件也不会亮屏刺破夜的静谧和黑暗 -- Deliver Quietly(安静通知)则是将消息推送静默化、不显示在锁屏,也不出声,也不会在 app 右上角标红 -- **Grouped Notifications(分组通知**)可以将某一类型的通知归组,微信群聊消息不再有轰炸的感觉 -- **Reports(应用报告)**可以用周报告的形式,告诉用户用什么应用最多,哪个应用通知最多,每天起床第一个打开的是什么应用等等 -- **App Limits(应用限制**)可以给某个应用规定使用时间,当然这不是强制性的,用户可以突破限制继续“吃鸡” -- **Allowances(零用钱?)** 是家长限制孩子使用应用的新特性 - -### iMessage 和 FeacTime - -Animoji 新增了 4 个新表情(幽灵,考拉,老虎和霸王龙),用户还可以为自己量身定做 Animoji ,并用到各种场景——这就是全新的 Memoji 技术 - -![](https://images.ifanr.cn/wp-content/uploads/2018/06/WWDC-26.jpg) - -![](https://cdn.sspai.com/2018/06/05/67b6fba3d36bdd7caf09bec94dcb157b.jpg?imageView2/2/w/1120/q/90/interlace/1/ignore-error/1) - -iOS 版 **FaceTime** 迎来了群聊功能,最多支持 32 人同时聊天,成员可以随时加入或离开聊天。聊天界面用瀑布流的形式呈现,正在说话的成员窗口会自动放大。macOS 版 FaceTime 同样也得到了更新。 - -![](https://cdn.sspai.com/2018-06-04-Screen%20Shot%202018-06-05%20at%202.00.58%20AM.png?imageView2/2/w/1120/q/90/interlace/1/ignore-error/1) - -## tvOS - -> 关键词:优化试听体验 - -tvOS 今年的变化比较小,更新主要集中在了影视资源以及细节优化上。 - -Apple TV 4K 将支持杜比全景声和杜比视界,让你在家里也能获得电影院般的听觉体验。 - -![](https://cdn.sspai.com/2018-06-04-Screen%20Shot%202018-06-05%20at%202.26.21%20AM.png?imageView2/2/w/1120/q/90/interlace/1/ignore-error/1) - -## watchOS 5 - -> 关键词:运动进行到底 - -随着 Apple Watch 成长的,还有它的操作系统 watchOS,这一次 watchOS 升级到了第五代。 - - -![](https://images.ifanr.cn/wp-content/uploads/2018/06/WWDC-31.jpg) - -Apple Watch 的功能朝着运动的方向发展,此次 watchOS 5 的更新,也以运动为主。 - -![](https://images.ifanr.cn/wp-content/uploads/2018/06/WWDC-35.jpg) - -watchOS 5 的一个小惊喜是让 Apple Watch 成为了对讲机,这个应用名为 Walkie Talkie(对讲机)。 - -![](https://images.ifanr.cn/wp-content/uploads/2018/06/WWDC-38.jpg) - - -Apple Watch 早就支持了 Apple Pay,不过在通知上,Apple Watch 显然可以做得更多,比如值机和给滴滴师傅付款评分,手表不再只是个通知器,也能做些轻交互。 - -![](https://images.ifanr.cn/wp-content/uploads/2018/06/WWDC-51.jpg) - -还有 Apple Watch 可以浏览网页了~ - -## macOS Mojave - -> 关键词:夜间模式、全新的App Store - -![](https://cdn.sspai.com/2018-06-04-macOS01.png?imageView2/2/w/1120/q/90/interlace/1/ignore-error/1) - -对于大多数人来说,macOS 更新最大的悬念,是新系统叫什么名字。 - -答案是:**macOS Mojave**,Mojave 中译名是莫哈韦沙漠,位于在美国加利福尼亚西南,出于洛杉矶和拉斯维加斯之间。 - -在 Mojave 这版系统中,苹果加入了一套适应暗光环境下使用的夜间主题,并对 Mac App Store 的交互界面进行了重塑,整个系统的改变甚至连库克都称为是苹果的一次 “巨大的跨越”。 - -![](https://images.ifanr.cn/wp-content/uploads/2018/06/WWDC-89.jpg) - -### 夜间模式 / 动态桌面 - -不少用户会在暗光环境下使用电脑,即便是将屏幕亮度调到最低,也难免会因为白底色为主的主题而感到刺眼。在这次更新中,macOS Mojave 新增加了一套暗色主题,不同于目前将菜单和程序栏调成暗色的选项,新系统上的是一套全局暗色主题,即便是在文件夹、应用里都是以黑色为主色呈现。 - -![](https://images.ifanr.cn/wp-content/uploads/2018/06/WWDC-63.jpg) - -另外,系统可根据时间变化对桌面进行自动调整,日间使用时系统为正常主题;夜间使用时系统则自动切换至暗色模式主题。此时,台下的开发者们爆发出了一阵欢呼,大概是这个主题能够提升程序员朋友夜间加班的幸福感吧。 - -![](https://images.ifanr.cn/wp-content/uploads/2018/06/WWDC-63.jpg) - -### 智能分类 - -macOS 会跟据文件类型和标签对桌面的文件进行自动分类整理,从此再也不用担心满桌面都是文件了。 - -![](https://images.ifanr.cn/wp-content/uploads/2018/06/WWDC-82.jpg) - -![](https://cdn.sspai.com/2018-06-04-macOS04.png?imageView2/2/w/1120/q/90/interlace/1/ignore-error/1) - -### 快速查看升级 - -![](https://images.ifanr.cn/wp-content/uploads/2018/06/WWDC-92.jpg) - -访达在这次系统上的更新并不多,只是针对图片查看增添了 “图库视图” 功能。通过 “图库视图”,用户可更加便捷快速地浏览到访达文件夹内的图片内容,与此同时功能内部也内置了图片元数据显示窗,用户可以借助数据窗口了解到图片的相关详情,并且可对图片进行快速编辑操作。 - -### 截图/录屏操作 - 类似iOS - -![](https://images.ifanr.cn/wp-content/uploads/2018/06/WWDC-93.jpg) - -此次 macOS Mojave 加入了 “系统级” 的截屏功能,用户也可以在截屏后对截屏图片进行简单的标记。不过实际上,目前不少社交软件其实都已经具备了截图 + 标记的相关功能,且在体验上也相当不错。 - -### 安全权限 - -当我们在 iOS 系统上打开刚下载的应用程序时,系统会弹出弹窗,提示是否允许程序访问用户信息和手机硬件。而这次苹果也将相关的安全控制策略从 iOS “搬” 到了 macOS 上,当用户打开某个网址或程序时,系统会弹出 “是否允许访问” 的弹窗以获得用户批准。这也可能是为了呼应最近越发严格的隐私政策。 - -### 在 macOS 上运行的几款 iOS 程序 - -![](https://images.ifanr.cn/wp-content/uploads/2018/06/WWDC-84.jpg) - -将 iOS 的应用程序搬到 macOS 上运行是不少玩家曾经有过的想法。此次苹果在新版的 macOS 系统上加入了 iOS 端的新闻、股市、家、语音备忘录四个程序,用户可以在桌面系统上通过这几款应用阅读新闻、控制家庭设备等等。 - -在发布会的最后,苹果否认了将对 iOS 和 macOS 进行合并的传闻,但考虑到 iOS 平台有非常庞大的应用数量,他们也希望其中的部分应用能来到 macOS。于是苹果在发布会上为大家提前预览了一个准备多年的项目,macOS 将可以使用 iOS 的 UIKit 框架进行开发,以降低开发多平台应用的成本。 - -![](https://cdn.sspai.com/2018-06-04-Screen%20Shot%202018-06-05%20at%203.08.05%20AM.png?imageView2/2/w/1120/q/90/interlace/1/ignore-error/1) - -比如这次 macOS Mojave 中新增的 4 款应用——News、股票、语音备忘录、家庭——均采用了这种新技术。 - -### 全新的 Mac App Store - -![](https://cdn.sspai.com/2018-06-04-macOS09.jpg?imageView2/2/w/1120/q/90/interlace/1/ignore-error/1) - -在 iOS 11 对 Mac App Store 进行了重新设计后,macOS Mojave 也迎来了全新设计的 Mac App Store。新版拥有与 iOS 上 App Store 类似的发现页,里面能看到每日编辑推荐和一些 App 的使用技巧。进入 App 页面后,你可以看到视频预览和与 iOS 类似的评分系统。为了方便用户评分,新版 macOS 还加入了和 iOS 一样的 App 内打分功能。此外,苹果还宣布包括 Office 365 和 Adobe Lightroom CC 在内的一批重量级 App 将在今年稍后登录 Mac App Store。 - -## 结语 - -是的,这次的 WWDC 只有软件,没有新的电子设备发布,没有新 iPad Pro、没有 iPhone SE2、没有带八代酷睿的新 MacBook,唯一能和“硬件”沾上边的就是一个新的彩虹表带。 - -![](https://images.ifanr.cn/wp-content/uploads/2018/06/WWDC-56.jpg) - -时至今日,苹果生态已经日趋完善了,大概苹果的产品经理们也想不出什么石破天惊的功能让大家 wow 一声了,有的只是细节层面的改进。作为看客和用户,也只能接受这样的现实了。 - -对了,那个可以四个人一起玩的乐高积木和 AR 应用,倒是可以考虑买来玩一下,不要一边说没有新东西,一边又对新东西视而不见。 - -对于 iOS 开发者来说,macOS 将可以使用 iOS 的 UIKit 框架进行开发是一个值得关注的点。 - -### 参考 - -- [WWDC 2018 Keynote](https://developer.apple.com/videos/play/wwdc2018/101/) -- [Apple WWDC 2018: what's new? All the announcements from the keynote](https://www.techradar.com/news/apple-wwdc-2018-keynote) -- [iOS 加入「防沉迷」,macOS 有了暗色主题,今年的 WWDC 重点都在系统上](http://www.ifanr.com/1043270) -- [苹果 WWDC 2018:最全总结看这里,不错过任何重点](https://sspai.com/post/44816) - - diff --git "a/_posts/2020-10-09-OC\347\232\204\345\206\205\345\255\230\347\256\241\347\220\206\347\232\204\345\237\272\346\234\254\346\246\202\345\277\265.md" "b/_posts/2020-10-09-OC\347\232\204\345\206\205\345\255\230\347\256\241\347\220\206\347\232\204\345\237\272\346\234\254\346\246\202\345\277\265.md" deleted file mode 100644 index 2375bc96998..00000000000 --- "a/_posts/2020-10-09-OC\347\232\204\345\206\205\345\255\230\347\256\241\347\220\206\347\232\204\345\237\272\346\234\254\346\246\202\345\277\265.md" +++ /dev/null @@ -1,106 +0,0 @@ ---- -layout: post -title: OC 内存管理的基本概念 -subtitle: OC 内存管理的基本概念以及应用 -date: 2020-10-09 -author: BY -header-img: img/post-bg-cook.jpg -catalog: true -tags: - - iOS ---- - - - -## OC 内存管理的基本概念 - - -###1. 内存管理的基本概念 - -- 栈区 stack - -- 堆区 heap - - heap(堆)是最自由的一种内存,它完全由程序来负责内存的管理,包括什么时候申请,什么时候释放,而且对它的使用也没有什么大小的限制。在C/C++中,用alloc系统函数和new申请的内存都存在于heap段中。 - -- BSS区 - - 来存放没有被初始化或初始化为0的全局变量,因为是全局变量,所以在程序运行的整个生命周期内都存在于内存中。有趣的是这个段中的变量只占用程序运行时的内存空间,而不占用程序文件的储存空间。 - -- 数据区 Data - - 初始化过的全局变量数据段,该段用来保存初始化了的非0的全局变量,如果全局变量初始化为0,则编译有时会出于优化的考虑,将其放在bss段中。因为也是全局变量,所以在程序运行的整个生命周期内都存在于内存中。与bss段不同的是,data段中的变量既占程序运行时的内存空间,也占程序文件的储存空间。 - -- 代码区 text - -### 2. OC内存管理的范围 - -`堆区` 中 继承 `NSObject ` 的 `对象` - - -## 内存管理的原理及分类 - - -- **引用计数** - -- `ARC` or `MRC` - -## MRC快速入门 - -- +1 - - alloc new copy -- -1 - - release autorelease - -## 内存管理原则 - - -- 原则 - - 对象有人使用,不回收 - - 使用对象,+1 - - 不使用对象 -1 - -- 谁创建, 谁release - -- 谁retain, 谁release -- 总结 - - +1 就该-1 - - -内存管理的研究内容: - -1. 野指针 - - 1. 定义的指针没有初始化(没有指向) - 2. 指向的空间已经被释放 (僵尸对象) - -2. 内存泄漏 - - 栈区指针变量已经被释放,而堆区的空间还没有被释放 - -## 单个对象的内存管理(野指针) - -###僵尸对象 -已经被释放的对象,在内存中可能还能继续访问,但容易出 - -开启僵尸对象检测: - - - -### 避免使用僵尸对象 - -对象释放后,将 `指针` -> `nil` - -### 带个对象的内存泄漏 - -⚠️ 注意 相互持有 、自己调用自己 的情况 - - [self mothd:self]; - - diff --git "a/_posts/2020-11-03-ARC\344\270\216MRC\346\267\267\345\220\210\344\275\277\347\224\250.md" "b/_posts/2020-11-03-ARC\344\270\216MRC\346\267\267\345\220\210\344\275\277\347\224\250.md" deleted file mode 100644 index d90c336175e..00000000000 --- "a/_posts/2020-11-03-ARC\344\270\216MRC\346\267\267\345\220\210\344\275\277\347\224\250.md" +++ /dev/null @@ -1,35 +0,0 @@ ---- -layout: post -title: ARC 与 MRC 混合使用 -subtitle: MRC 库的处理 -date: 2020-11-03 -author: BY -header-img: img/post-bg-cook.jpg -catalog: true -tags: - - iOS ---- - - - -## Xcode 项目中我们可以使用 ARC 和非 ARC 的混合模式。 - -> 有些时候我们工程中引用了十分古老的库为MRC模式,这时就需要采用混合模式进行处理 - -- 如果你的项目使用的 MRC 模式,要为 ARC 模式的代码文件加入 `-fobjc-arc` 标签。 - - -- 如果你的项目使用的是 ARC 模式,要为 MRC 代码文件加入 `-fno-objc-arc` 标签 - -添加标签的方法: - -打开:你的`target -> Build Phases -> Compile Sources` - -双击对应的 *.m 文件 - -在弹出窗口中输入上面提到的标签 `-fobjc-arc` 或 `-fno-objc-arc` -点击 done 保存 - -![](https://ws3.sinaimg.cn/large/006tKfTcgy1fl3kvg0yp1j30pl0ahgov.jpg) - - diff --git a/_posts/2020-12-12-No-error.md b/_posts/2020-12-12-No-error.md new file mode 100644 index 00000000000..81621fab56c --- /dev/null +++ b/_posts/2020-12-12-No-error.md @@ -0,0 +1,16 @@ +--- +layout: post +title: Hello world +subtitle: we're coming now +date: 2020-12-12 +author: No-ERROR +header-img: img/post-bg-ios9-web.jpg +catalog: true +tags: + - 小组 +--- +# 喜羊羊 +# 美羊羊 +# 懒羊羊 + It's too late, so I'll go to my bed falling asleep right now!!! +