sunshine of 5 o’clock

early morining

最近5点钟起床,可能昨天是1点钟睡的(哦不对,是今天),可能中午会补一觉回来。这里绝不是学习科技公司的 CEO,必须早上起来处理完事情,员工才能上班按计划做事。而是因为学习 iOS 的激情和自我的压力。希望不会想初中高中那会,累的白了头,毕竟现在还是稍微完善了自我管理和娱乐方式也多了。

学习的激情

接触 iOS 可能有2年了,自学开发断断续续也有1年了。问题来了之前没有激情吗?答案是我只是停留在用户角度喜欢苹果的设计的美感和简介,优雅是苹果追求的,但是只有少数产品实现了。另外闭门造车太难了,根据学习本来是就社交属性的,联系到学习金字塔理论,培训和参加社区,才能找到开发的乐趣。包括认识很多不同的人,有趣的人,又或者精神导师之类,可以讨论问题、新的技术或新的产品。这一切都是与人打交道的。孤立起来的人,要么天才要么疯子。显然我不是前者,随意为了避免成为后者我要主动开放的心态参加社区,结交朋友。尤其最近参加了 CocoaHeads Shanghai Meetup,发了很多有意思的开发者和有趣的人。还是 tinyfool 很久以前提到的 Meetup,最近想认识更多的 iOS 开发者才想起来参加的活动。

We just grab a coffee and speak French. Some people have been coming every week for months… it creates a kind of warmth to the group.

— Rafaël, started French Conversation Group

书非借不能读也,软件非买不珍惜也。两者真心是个矛盾的东西,按照割肉理论,可能是书太便宜了,买了也是几乎零成本。最近买了些开发利器如:Duet Display、Dash 3,还有免费的 Alcatraz、FuzzyAutocomplete、iOS Charts 等等。也让我很是着迷之中。

最近一周学习到很多知识,尤其学会利用 Apple iOS Documentation,这里强烈推荐 Dash 3,可以一步打开 Documentation(学习快人一步)。而且继承了 Google 和 Stack Overflow,非常方便。还有 GeekBand 段松老师的课程也是很好的,毕竟是一线开发者,提到了很多经验和工作中常用的方法和知识点。学会看文档掌握了主动权,可以练习和实战课程没有提到的点,练习也是非常重要的学习方法。以前也看了借本书和视频教程,但是很快就忘记了,究其原因不过是似懂非懂的了解一下,还是不会实际开发。正所谓听了很多道理,依然过不好这一生。没有去实践,就是过了听瘾,如同看电影肥皂剧一样。别人的道理又怎么会改变你的人生?

自我压力

本来想说是自我管理,生活的动力往往来自压力或激情。尤其是最近读了一篇文章:不和穷人谈恋爱?,简直是是醍醐灌顶。本非简单地说你穷,不愿理你,而是说你甘愿做一个穷人这就有问题。我再也不愿逃避我现阶段失败的状态。直接导致我每天5点钟自然醒,因为压力,因为我要改变自己。

我说你说的就跟穷人有罪似的。她说对啊,穷人就是有罪。然后她看着夜空说,“你不觉得现在这个社会,年轻人很难穷吗?真的只要稍微学点什么,用点脑子,对生活稍微用力一点,就可以养活自己。在这个时代,还坚持穷下去的人他绝对不是简单的穷的问题了,一定是他性格或者人品上有什么缺陷和问题,才导致他穷。你不要小看穷,也不用动不动掏出你一颗圣母心来疼爱万物,我再说一遍,在这个时代一个人穷说明他自身有着很大的问题。”

“尤其男人,穷就判定了这个人没有责任心,也没有任何人脉,换句话说没有人脉就是不会看人,不会交朋友。你肯定又要跟我扯阶级,说一个人也必须拥有差不多的实力,才能跻身比自己高一个级别的圈子。但是就算门口烤白薯的人特别好,特别会做人,他也能交到几个朋友愿意帮他的。这是一个人情商问题,没有朋友愿意帮他,第一说明他情商低,第二说明这个人人品有问题,第三,正常人都有社交,一个人没对象很正常,要是一个朋友都没有,那你不用跟我争,这人就是有问题。”

矫情一下😸:

从明天起,做一个有趣的人

学习,吃饭,周游世界

重读前言提到的学习方法:

设定目标一天一章,找一个安静地场所,关闭手机和电脑各种聊天和通知,读书无法多任务并行,必须集中精力。

  1. 通读整章
  2. 编写代码和调试(特别有帮助)
  3. 笔记

最终目标:

  • 必须学会 Objective-C
  • 必须掌握 Cocoa 的常用技术:视图、控制器、内存管理、代理
  • 必须掌握框架和学会查看官方文档

The 4 Most Important Skills for a Software Developer: If you can solve problems, learn things quickly, name things well and deal with people, you will have a much greater level of success in the long run than you will in specializing in any particular technology.

上面这篇文章中提到解决问题的能力很重要,因为真实的编程工作内容就是解决问题,所以习题很重要,做习题就是解决问题,这也和本书提到调试非常有帮助不谋而合,因为你在调试就是在解决问题。

发现我一直在寻求简单的道理(大道至简),习惯于归纳要旨和大纲。但是却忽略了细节,其实全局的理解和记忆也是必经之路。读书先薄再厚再薄,第二阶段最为繁长的耗时,但也是最重要的。

7.2 委托

Delegation 翻译为委托/代理

代理的前三步:

  • 视图中创建一个代理协议
  • 视图中创建一个代理的属性,其类型是代理协议
  • 视图中使用代理的属性

代理的后三步:

  • 控制器声明并完成代理协议
  • 控制器把自己作为视图的代理,通过设置代理的属性
  • 控制器实施代理的方法

7.8 中级练习:捏合-缩放

习题提示部分大不大懂。对比了英文版,翻译的不对啊。看中文翻译有出入,看英文太慢。解决方法:还是看中文版,Demo 和习题都做完了,再看一遍英文版。先精通一门语言的再说,以后学其他的就容易了。

谁让开发语言和大部分开发者都用英文呢,其实我现阶段看简单 Stack Overflow 和 Apple Document 都没有问题了,毕竟英文单词不多,也都是简单的词。希望以后能够直接看英文教材。

又是看了 BNR 论坛答案: http://forums.bignerdranch.com/viewtopic.php?f=488&t=9983

是我想复杂了,这里只是一个框架内置的协议,所以只要在
AppDelegate.m 完成后三步就行了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
//
// AppDelegate.m
// Hypnosister


#import "AppDelegate.h"
#import "GWHypnosisView.h"


@interface AppDelegate () <UIScrollViewDelegate> // ①

// setting property for the image view...
@property (nonatomic) GWHypnosisView *scrollHypnosisView; // ②

@end

@implementation AppDelegate


- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];

// Override point for customization after application launch.

CGRect screenRect = self.window.bounds;
CGRect bigRect = screenRect;
bigRect.size.width *= 2.0;
bigRect.size.height *= 2.0;


UIScrollView *scrollView = [[UIScrollView alloc] initWithFrame:screenRect];

// setting some zooming properties
scrollView.pagingEnabled = NO;
scrollView.minimumZoomScale = 0.5;
scrollView.maximumZoomScale = 6.0;
scrollView.contentSize = CGSizeMake(1280, 960);
scrollView.delegate = self;

[self.window addSubview:scrollView];

self.scrollHypnosisView = [[GWHypnosisView alloc] initWithFrame:bigRect];


[scrollView addSubview:self.scrollHypnosisView];

scrollView.contentSize = bigRect.size;

self.window.backgroundColor = [UIColor whiteColor];
[self.window makeKeyAndVisible];


return YES;
}

// ③ zooming method definition
-(UIView *) viewForZoomingInScrollView:(UIScrollView *)scrollView
{
return self.scrollHypnosisView;
}


项目代码保存在我的 GitHub: iOSProgramming4edSolutions

7. 经典 UI 应用框架

UITabBarController + UINavigationController 基本上就可以完成一个 APP。

删除多余注释,不是强迫症而是要保证代码简洁干净。

有了 BLDemo03 就有了一个基础 UI 框架 ,就可以快速开发一个 APP,很给力。

9. 应用界面的切换

添加按钮,push/modal 到自定义子视图

10. UI 界面编程基础

1
2
3
4
5
6
7
8
9
// add an UIImageView and an image in it

UIImageView *imageView = [[UIImageView alloc] initWithFrame:CGRectMake(10, 212, 200, 200)];

UIImage *image = [UIImage imageNamed:@"bg5.png"];

[imageView initWithImage:image];

[self.view addSubview:imageView];

感觉代码添加 UI 很简单,设置大小属性内容,添加到 view。

完成相应地代理,可以实现视图“控制” model。

13.UIView 和常用的组件

类似画画上色过程,自底向上以此上色。

Views 自上到下的层次关系:

  • UIImage
  • UIImageView
  • UIView
  • UIScrollView 和 UIPagecontrol
  • self.view
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

//
// BLThreeViewController.h
// BLDemo03


#import <UIKit/UIKit.h>
#import "BLBaseViewController.h"

@interface BLThreeViewController : BLBaseViewController <UIScrollViewDelegate>
{
UIScrollView *_scrollView;
UIPageControl *_pageControl;
UIView *_contentView;
}


@end

BLDemo01 L13 课开始集成了 Reveal,很直观看懂层次关系

源码保存在我的 GitHub: GeekBand-iOS-Demo

6.5 添加本地通知

书中没有提到申请通知权限,方法如下

add this code, it will show a alert view to ask user for permission.

1
2
3
4
5
6
7

if ([UIApplication instancesRespondToSelector:@selector(registerUserNotificationSettings:)]) {
[[UIApplication sharedApplication] registerUserNotificationSettings:[UIUserNotificationSettings settingsForTypes:UIUserNotificationTypeAlert|UIUserNotificationTypeSound|UIUserNotificationTypeBadge
categories:nil]];
}


6.7 与视图控制器及其视图进行交互

视图控制器的生命周期方法(lifecycle method):

  • application:didFinishLaunchingWithOptions:在该方法中设置和初始化应用窗口的根视图控制器。该方法只会在应用启动完毕后调用一次。

  • initWithNibName:bundle:该方法是UIViewController的指定初始化方法,创建视图控制器时,就会调用该方法。请注意,某些情况下,需要在同一个应用中创建多个相同的UIViewController子类对象,每次创建一个该类的对象时,都会调用一次该类的initWithNibName:bundle:方法。

  • loadView:可以覆盖该方法,使用代码方式设置视图控制器的view属性。

  • viewDidLoad可以覆盖该方法,设置使用NIB文件创建的视图对象。该方法会在视图控制器加载完视图后被调用。

  • viewWillAppear:可以覆盖该方法,设置使用NIB文件创建的视图对象。该方法和

  • viewDidAppear:会在每次视图控制器的view显示在屏幕上时被调用;相反,

  • viewWillDisappear:和viewDidDisappear:方法会在每次视图控制器的view从屏幕上消失时被调用。

6.9 中级练习:控制逻辑

查文档不懂,这是个大问题,有待提高看文档具体怎么组织的能力。Stack Overflow 的答案运行崩溃,没有理解原理,mainSegmentControl: 方法没有声明。最后还是去 BNR论坛 找到了答案。

熟悉文档浏览 UIColor,其实也是不很精通颜色,但是还是大部分看懂了,也了解了目录结构和对应文本的样式。

GWHypnosisViewController.m

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
NSLog(@"GWHypnosisViewController loaded its view.");

// 初始化UISegmentedControl,设置大小和颜色
UISegmentedControl *segmentedControl = [[UISegmentedControl alloc] initWithItems:@[@"Red", @"Green", @"Blue"]];
segmentedControl.frame = CGRectMake(0, 0, 250, 50);
segmentedControl.tintColor = [UIColor blackColor];

// 注册UISegmentedControl
[segmentedControl addTarget:self.view
action:@selector(mainSegmentControl:)
forControlEvents: UIControlEventValueChanged];
// 添加到视图
[self.view addSubview:segmentedControl];

}

GWHypnosisView.h

1
2
3

- (void)mainSegmentControl:(UISegmentedControl *)segment;

GWHypnosisView.m

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// mainSegmentControl: 方法用来接受 UISegmentedControl 发的消息
- (void)mainSegmentControl:(UISegmentedControl *)segment
{

if(segment.selectedSegmentIndex == 0)
{
// action for the first button (Current or Default)
self.circleColor = [UIColor redColor];
}
else if(segment.selectedSegmentIndex == 1)
{
// action for the second button
self.circleColor = [UIColor greenColor];
}
else if(segment.selectedSegmentIndex == 2)
{
// action for the third button
self.circleColor = [UIColor blueColor];
}

}

项目代码保存在我的 GitHub: iOSProgramming4edSolutions

5.1 运行循环和重绘原理

iOS 每次事件处理周期中只发送一次 drawRect: 消息。所以视图要重绘必须向其发送 setNeedDisplay 消息。

5.3 使用 UIScrollView

添加子视图,设置大小即可。类似一张大的画布,可以方便移动局部查看。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

CGRect screenRect = self.window.bounds;
CGRect bigRect = screenRect;
bigRect.size.width *= 2.0;

UIScrollView *scrollView = [[UIScrollView alloc] initWithFrame:screenRect];
[scrollView setPagingEnabled:YES];
[self.window addSubview:scrollView];


GWHypnosisView *hypnosisView = [[GWHypnosisView alloc] initWithFrame:screenRect];
[scrollView addSubview:hypnosisView];

screenRect.origin.x += screenRect.size.width;
GWHypnosisView *anotherView = [[GWHypnosisView alloc] initWithFrame:screenRect];
[scrollView addSubview:anotherView];


scrollView.contentSize = bigRect.size;

项目代码保存在我的 GitHub: iOSProgramming4edSolutions

独立做练习的过程中学习和收获的很多,很多原本认为了解的知识变成了理解和会用的知识。官方文档还有待熟悉。

frame vs bounds

4.2 视图层次结构

View

4.6 初级练习:绘制图片

GWHypnosisView.m- (void)drawRect:(CGRect)rect{}添加以下代码:

1
2
3
4
5
6
7
// 添加logo,中心点为屏幕中心点
CGRect logoFrame = CGRectMake(center.x - bounds.size.width / 4, center.y - bounds.size.height / 4, bounds.size.width / 4 * 2, bounds.size.height / 4 * 2);
UIImage *logoimage = [UIImage imageNamed:@"logo.png"];
[logoimage drawInRect:logoFrame];
UIView *logoView = [[UIView alloc]initWithFrame:logoFrame];
[self.window addSubview:logoView];

4.8 高级练习:阴影和渐变

GWHypnosisView.m- (void)drawRect:(CGRect)rect{}添加以下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47

// 添加三角形,并添加渐变效果

CGContextRef triangleContext = UIGraphicsGetCurrentContext();

UIBezierPath *trianglePath = [[UIBezierPath alloc] init];
[trianglePath moveToPoint:CGPointMake(center.x + bounds.size.width / 3, center.y + bounds.size.height / 3)];
[trianglePath addLineToPoint:CGPointMake(center.x - bounds.size.width / 3, center.y + bounds.size.height / 3)];
[trianglePath addLineToPoint:CGPointMake(center.x, center.y - bounds.size.height / 3)];
[trianglePath addLineToPoint:CGPointMake(center.x + bounds.size.width / 3, center.y + bounds.size.height / 3)];
[trianglePath stroke];

CGContextSaveGState(triangleContext);
[trianglePath addClip];


CGFloat locations[2] = { 0.0, 1.0 };
CGFloat components[8] = { 1.0, 1.0, 0.0, 1.0,
0.0, 0.5, 0.0, 1.0 };
CGColorSpaceRef colorspace = CGColorSpaceCreateDeviceRGB();
CGGradientRef gradient = CGGradientCreateWithColorComponents(colorspace, components, locations, 2);

CGPoint startPoint = CGPointMake(center.x, center.y + bounds.size.height / 3 );
CGPoint endPoint = CGPointMake(center.x, center.y - bounds.size.height / 3 );
CGContextDrawLinearGradient(triangleContext, gradient, startPoint, endPoint, 0);

CGGradientRelease(gradient);
CGColorSpaceRelease(colorspace);

CGContextRestoreGState(triangleContext);

// 添加阴影
CGContextRef logoContext = UIGraphicsGetCurrentContext();
CGContextSaveGState(logoContext);
CGContextSetShadow(logoContext, CGSizeMake(4, 7), 3);


// 添加logo,中心点为屏幕中心点
CGRect logoFrame = CGRectMake(center.x - bounds.size.width / 4, center.y - bounds.size.height / 4, bounds.size.width / 4 * 2, bounds.size.height / 4 * 2);
UIImage *logoimage = [UIImage imageNamed:@"logo.png"];
[logoimage drawInRect:logoFrame];
UIView *logoView = [[UIView alloc]initWithFrame:logoFrame];
[self.window addSubview:logoView];

CGContextRestoreGState(logoContext);


最终效果图:

Hypnosister

项目代码保存在我的 GitHub: iOSProgramming4edSolutions

看到极客班同学 Kevin Wang 分享的 CocoaPods 笔记:iOS学习备忘录:CocoaPods基本使用技巧,我自己就试着一下。

以下安装 AFNetworking 的过程,同时参考了:Getting Started with AFNetworking 和 唐巧的 《iOS 开发进阶》

感觉 Getting Started with AFNetworking,写的非常好了。我已经没有地方修改,就加了我在安装 CocoaPods 遇到的一个坑。看样子还是要英文顺溜才行。

CocoaPods 网络库加 Podfile 配置文件,一个可以方便添加第三方库,而是团队协作容易统一版本,真真一个好东西。

Step 1: Download CocoaPods

第一步开 VPN,不要问为什么。终端输入:

$ sudo gem install cocoapods
$ pod setup

我遇到没有安装没有如何进度和提示,重装 Ruby 和 RubyGems 就好了。

 $ brew install ruby
 $ gem update --system
 
 

Step 2: Create a Podfile

用 Xcode 或其他编辑器新建 Podfile,放在项目根目录下:

$ touch Podfile
$ open -a Xcode Podfile

并复制下面内容到 Podfile

source 'https://github.com/CocoaPods/Specs.git'
platform :ios, '7.0'
pod 'AFNetworking', '~> 2.5'

Step 3: Install Dependencies

$ pod install

以后只要打开 Xcode workspace (.xcworkspace)才行

$ open <YourProjectName>.xcworkspace

Step 4: Dive In!

大功告成,至此就可以使用 AFNetworking 了。记得在需要的类中 #import 头文件。

最后分享一个在利器上看到的利器:Pushbullet 是跨平台双向文件传输工具。如果你同时使用 Mac、ios 和 Android 又想要在设备间无线传输图片、文字,那么你一定需要这样的工具。

其实还有隐藏功能共享剪切板,瞬间变成神器了,有没有?比微信和 Airdrop 方便多了。

课堂笔记

@段松 老师上课喜欢类比生活中的对象,还是比较容易理解的。

断点的使用可以帮助理解每一行的作用和追踪对象或属性的变化。

内存管理讲的挺详细,发现课程结构顺序和《iOS 编程》很像。

源码保存在我的 GitHub: GeekBand-iOS-Demo

@李建忠:课程安排

startup:账号系统,登陆系统,setting,有时间的组员可以多做一点

项目变化,很正常,确定人员分配,推动项目往前走。

@袁店明:探讨产品

我们小组的项目“一秒”:需求真实性,目的在哪里,目标用户为什么会用,解决什么问题。

添加一个主题:亲子成长,空气环境变化。

分享带有主题的视频,类似 iMovie 的手机版

很多时候需求都是你自己猜想的,产品的使用场景,都是需要验证的!

言语表达要求很高。

语言描述要准确,不一定要非常简练华丽。

@李建忠: A good thinker is a good talker.

例子:上班很远,并不是所有人都是问题,并不是所有人都一样的界定。所以提供的解决方案都不一样,用户真实的上班方式开车自行车走路,又该如何解决?要面对面访谈用户,电话次之。

产品探索过程中,要抛弃自己的创意和解决方法,重点在用户的需求和相应的解决方案,最后收敛(投票和排序重要性)成一个产品。

所有人:销售运营开发测试产品经理坐在一起产品的逻辑推理,然后才能进行用户访谈。

用户细分,不然找错人浪费了时间经理。

这一套做产品思维,可以用工作中,如正在培训的创业公司敏捷开发。

不要让天使用户看到你的访谈的问题。

热点事件来分析,提高逻辑推理。推荐《系统化思维导论》

最近在看《精益数据分析》

用户群不要混合现有用户群来定义你的用户群,如:色影无忌和蜂鸟网

用户发现问题->寻找解决方案->找到产品->愿意付费

我是 iOS 学员,关于产品方面听来挺过瘾。多谢 Tele Deng 的笔记:【极客班-精益产品探索】第一周课堂笔记

@段松:iOS 大纲的梳理

说话真心快

创业中:健美乐

过去培训经验:5小时 * 12天完成培训,走向职场。

不是看了多少书,死记硬背了多少知识点,更多是写代码的能力,写的多了自然会用了。

学习过程中类比现实生活中的东西,可能比较容易理解。

代理就是委托,力有不逮时,联系朋友去完成。

学习方法:就是梳理大纲知识点,掌握要点,理清楚后,自己写代码自然就会了。

先学习整体框架,后学习细节。

UITabBarViewController 90%使用的框架。

UITableView

实战中的两种应用框架(后续课程)

查看开源项目,删除多余的部分,只看自己关注的部分。最基本也要会用开源框架。

iOS 数据结构用的不多。

分享易庆晟的笔记:GeekBand第二周线下课堂,总结的非常好。

@李建忠:C++ 辅导

分享 X-Lion 的 C++ 笔记:极客班GeekBand - C++第一次课程辅导 - 李建忠,和于航的笔记:GeekBank极客班C++线下班学习心得(二)

@李建忠:确定项目

均衡组员分配,iOS/C++/PM,考虑到产品上线审核产生的延迟,要提前两周就要完成项目,约9月12日。

基于 MVP,产品原型图和技术要点分解同步进行,快速迭代,让真实项目催促你前进。

一天下来,我们小组项目最终打磨为:“一秒变大人”,张诚 PM、我和易庆晟iOS 方面、郭意亮 C++ 技术、还有新加入的沈秋艳 PM,希望尽快下周末前出第一个版本。

我负责项目图片视频处理方面,TODO:

-阅读图片视频相关的文档
-objc.io 关于图片视频的小节
-找几个相关开源的项目

Swift or Objective-C?虽然两门课都有,但是我还是❤️Swift,并不仅仅因为❤️Taylor。

3.1 栈

栈对应方法或函数的帧,堆则是包含大量 iOS 应用创建的对象

3.3 指针变量与对象所有权

引用计数就是社保局,调查每个人有几份工作,一个正式工作,或兼职几份工作,或失业。失业,内存就会被释放,就会给你这个 free man 推荐新工作。

那些情况会使对象失去拥有者:

  • 修改指针指向另一个变量(跳槽)
  • 设为 nil(辞职)
  • 对象拥有者被释放(公司倒闭)
  • 从 collection 对象中删除对象(裁员)

当对象没有拥有者时,指针变量的内存就该被释放。

3.4 强引用与弱引用

解决强引用循环问题后的RandomItems
大部分强引用循环可以确定一个父子关系,子对象改为弱引用即可。

Xcode 的 Leaks 工具可以帮忙找出强引用循环问题。

3.5 属性

声明属性,等于隐式地声明和创建相应名称的实例变量,声明一对存取方法。当然我们还可以自定义存取方法。

属性的特性(attribute):

  • 多线程:nonatomic/atomic
  • 读写:readwrite/readonly
  • 内存管理:strong/weak/copy/unsafe_unretained

项目代码保存在我的 GitHub: iOSProgramming4edSolutions

0%