Найпростіший спосіб виявити підключення до Інтернету на iOS?


147

Я знаю, що це питання видасться безладним для багатьох інших, проте я не вважаю, що простий випадок тут добре пояснений. Починаючи з фону Android та BlackBerry, HTTPUrlConnectionмиттєво не вдається робити запити, якщо немає підключення. Це здається абсолютно розумною поведінкою, і я здивувався, коли NSURLConnectionв iOS це не наслідувало.

Я розумію, що Apple (та інші, хто її розширив) надають Reachabilityклас, який допомагає визначити стан мережі. Я був радий вперше побачити це і повністю сподівався, що побачу щось подібне bool isNetworkAvailable(), але замість того, щоб здивувати, я знайшов складну систему, яка вимагає реєстрації сповіщень та зворотних зворотних дзвінків, і купу, здавалося б, непотрібних деталей. Має бути кращий спосіб.

Мій додаток вже витончено вирішує проблеми з підключенням, включаючи відсутність підключення. Користувача сповіщають про помилку, і додаток рухається далі.

Таким чином, мої вимоги прості: Одинарна, синхронна функція, яку я можу зателефонувати перед усіма HTTP-запитами, щоб визначити, чи потрібно мені перешкоджати надсилати запит чи ні. В ідеалі він не вимагає налаштування і просто повертає булеву форму.

Це справді неможливо на iOS?



Ніколи не слід викликати метод синхронної мережі доступності перед кожним запитом HTTP; просто безглуздо робити цю роботу, якщо тільки доступність асинхронно не скаже вам, що відбулися зміни в мережевих умовах, і тоді ви можете вирішити не надсилати HTTP-запит. Це також вся причина, що існують тайм-аути (і тоді ви повинні виправити цей сценарій витончено). Дивіться посилання вище про спосіб асинхронізації
iwasrobbed

Відповіді:


251

Я зробив трохи більше досліджень, і я доповнював свою відповідь більш сучасним рішенням. Я не впевнений, чи ви вже це подивилися, але є приємний зразок коду, наданий Apple.

Завантажте зразок коду тут

Включіть у свій проект файли Reachability.h та Reachability.m. Подивіться на ReachabilityAppDelegate.m, щоб побачити приклад того, як визначити доступність хоста, доступність через WiFi, WWAN тощо. Для дуже просто перевірити доступність мережі, ви можете зробити щось подібне

Reachability *networkReachability = [Reachability reachabilityForInternetConnection];   
NetworkStatus networkStatus = [networkReachability currentReachabilityStatus];    
if (networkStatus == NotReachable) {        
    NSLog(@"There IS NO internet connection");        
} else {        
     NSLog(@"There IS internet connection");        
}

@ BenjaminPiette: Не забудьте додати SystemConfiguration.framework до свого проекту.


46
Не забудьте додати SystemConfiguration.framework до свого проекту.
Бенджамін Піет

1
@chandru Це працює. Це найпростіший і найефективніший спосіб виявлення мережевого зв'язку. Просто і легко!
S1U

3
Обережно з цим. Я вважаю, що вона не працює надійно при відновленні мережі, принаймні в тренажері. Я запускаю з вимкненого Wi-Fi, і він правильно повідомляє про відсутність мережі; але потім я знову включаю Wi-Fi, і SCNetworkReachabilityGetFlags продовжує повертати 0, хоча фактичні мережеві запити працюють просто чудово.
Джо Строут

2
Я вважаю, що моя відповідь нижче ( stackoverflow.com/a/26593728/557362 ) вимагає менше роботи (без завантаження інших файлів), але це лише моя думка.
GilesDMiddleton

3
Зауважте, що це говорить лише про те, чи є маршрутизація з'єднання з певним хостом . Це не означає, що ви насправді можете підключитися. Приклад: ви підключаєте wifi свого iPhone до wifi камери. Wi-Fi підключений до шлюзу за замовчуванням: всі хости будуть повідомляти про доступність. Але насправді їх немає - камера - тупиковий зв’язок.
xaphod

45

Бачачи, що ця тема є головним результатом google для такого типу запитань, я зрозумів, що запропоную рішення, яке працювало на мене. Я вже використовував AFNetworking , але пошук не виявив, як виконати це завдання за допомогою AFNetworking до середини мого проекту.

Що ви хочете - це AFNetworkingReachabilityManager .

// -- Start monitoring network reachability (globally available) -- //
[[AFNetworkReachabilityManager sharedManager] startMonitoring];

[[AFNetworkReachabilityManager sharedManager] setReachabilityStatusChangeBlock:^(AFNetworkReachabilityStatus status) {

    NSLog(@"Reachability changed: %@", AFStringFromNetworkReachabilityStatus(status));


    switch (status) {
        case AFNetworkReachabilityStatusReachableViaWWAN:
        case AFNetworkReachabilityStatusReachableViaWiFi:
            // -- Reachable -- //
            NSLog(@"Reachable");
            break;
        case AFNetworkReachabilityStatusNotReachable:
        default:
            // -- Not reachable -- //
            NSLog(@"Not Reachable");
            break;
    }

}];

Ви також можете використати наступне для тестування доступності синхронно (як тільки моніторинг розпочато):

-(BOOL) isInternetReachable
{
    return [AFNetworkReachabilityManager sharedManager].reachable;
}

3
Я виступав за те, щоб вивести AFNetworkReachabilityManagerна світло!
Селвін

Для тих, кому це було не відразу очевидно: AFNetworking - це стороння бібліотека.
арломедія

1
Доступність мережі - це діагностичний інструмент, за допомогою якого можна зрозуміти, чому запит не вдався. Його не слід використовувати для визначення того, чи потрібно робити запит. - Ось що говорить документація.
Носов Павло

AFNetworkReachabilityManager.reachableНЕ виконує будь-якого типу синхронної перевірки. Він повертає логічний або з reachableViaWWANі reachableViaWifi.
jdolan

40

Вибачте, що відповіли занадто пізно, але сподіваюся, що ця відповідь може допомогти комусь у майбутньому.

Далі наведено невеликий нарізний фрагмент коду С, який може перевірити підключення до Інтернету без зайвого класу.

Додайте такі заголовки:

#include<unistd.h>
#include<netdb.h>

Код:

-(BOOL)isNetworkAvailable
{
    char *hostname;
    struct hostent *hostinfo;
    hostname = "google.com";
    hostinfo = gethostbyname (hostname);
    if (hostinfo == NULL){
        NSLog(@"-> no connection!\n");
        return NO;
    }
    else{
        NSLog(@"-> connection established!\n");
        return YES;
    }
}

Швидкий 3

func isConnectedToInternet() -> Bool {
    let hostname = "google.com"
    //let hostinfo = gethostbyname(hostname)
    let hostinfo = gethostbyname2(hostname, AF_INET6)//AF_INET6
    if hostinfo != nil {
        return true // internet available
      }
     return false // no internet
    }

3
працює набагато краще, ніж Apple Reachability. Але згідно з документами gethostbyname є застарілим, і ви повинні використовувати getaddrinfo: struct addrinfo * res = NULL; int s = getaddrinfo ("apple.com", NULL, NULL, & res); bool network_ok = (s == 0 && res! = NULL); freeaddrinfo (res);
Терцій

1
Хоча звичайно використовувати google.com для перевірки наявності Інтернету, варто зазначити, що в деяких країнах він заблокований. Кращим вибором були б домени від більш "зручних для уряду" компаній, таких як yahoo.com або microsoft.com.
Лоран

8
@Laurent: Найкращим варіантом є використання адреси сервера, з якого ви збираєтеся запитувати дані. google.com тут - лише приклад.
dispatchMain

7
Це просто пошук DNS? Якщо так, чи може результат кешування DNS може призвести до того, що функція помилково вважає, що мережа доступна?
Стівен Мур

2
@amar Запуск цього циклу споживає бездротові дані, ви цього не хочете робити.
сенс має значення

31

В даний час я використовую цей простий синхронний метод, який не потребує додаткових файлів у ваших проектах або делегатах.

Імпорт:

#import <SystemConfiguration/SCNetworkReachability.h>

Створіть цей метод:

+(bool)isNetworkAvailable
{
    SCNetworkReachabilityFlags flags;
    SCNetworkReachabilityRef address;
    address = SCNetworkReachabilityCreateWithName(NULL, "www.apple.com" );
    Boolean success = SCNetworkReachabilityGetFlags(address, &flags);
    CFRelease(address);

    bool canReach = success
                    && !(flags & kSCNetworkReachabilityFlagsConnectionRequired)
                    && (flags & kSCNetworkReachabilityFlagsReachable);

    return canReach;
}

Потім, якщо ви поставили це в MyNetworkClass:

if( [MyNetworkClass isNetworkAvailable] )
{
   // do something networky.
}

Якщо ви протестуєте в тренажері, увімкніть і вимкніть Wi-Fi вашого Mac, оскільки, здається, тренажер ігнорує налаштування телефону.

Оновлення:

  1. Зрештою, я використовував потоковий / асинхронний зворотний виклик, щоб уникнути блокування основного потоку; і регулярно проводити повторне тестування, щоб я міг використовувати кешований результат - хоча вам слід уникати зайвого відкриття з'єднань даних.

  2. Як описав @thunk, існують кращі URL-адреси, якими користуються самі Apple. http://cadinc.com/blog/why-your-apple-ios-7-device-wont-connect-to-the-wifi-network


1
Дякуємо, як і більшість інших у цій темі: Не забудьте додати SystemConfiguration.framework до свого проекту.
eselk

1
Як не дивно, в моєму проекті Xcode мені не довелося додавати SystemConfiguration.framework. У мене є SpriteKit, UIKit, StoreKit, Foundation та CoreGraphics, але не SystemConfiguration .... Чи відбувається якесь неявне включення?
GilesDMiddleton

3
Це працювало для мене, але коли я запускав його під час підключення до мережі Wi-Fi, що не має підключення до Інтернету, він заблокував мій інтерфейс приблизно на 30 секунд. У цій ситуації потрібен асинхронний підхід.
арломедія

2
www.apple.comвеликий веб-сайт ... кращим варіантом будеwww.google.com
Фахім Паркар

2
відмінна відповідь! Одне з пропозицій: тестова URL-адреса, www.appleiphonecell.comначебто, доступна суворо для цієї мети і, очевидно, не заблокована в Китаї.
Thunk

11

Це можливо, і це дуже просто, якщо ви подивитеся на це під час завершення впровадження, що знову ж таки - дуже просто, оскільки єдині необхідні вам елементи - дві булеві змінні: доступність в Інтернеті та доступність хоста (часто вам потрібно більше однієї з них ). Після того, як ви збираєте свій допоміжний клас, який може визначати стан з'єднання, ви знову не переймаєтесь реалізацією, необхідною для знання цих процедур.

Приклад:

#import <Foundation/Foundation.h>

@class Reachability;

@interface ConnectionManager : NSObject {
    Reachability *internetReachable;
    Reachability *hostReachable;
}

@property BOOL internetActive;
@property BOOL hostActive;

- (void) checkNetworkStatus:(NSNotification *)notice;

@end

І файл .m:

#import "ConnectionManager.h"
#import "Reachability.h"

@implementation ConnectionManager
@synthesize internetActive, hostActive;

-(id)init {
    self = [super init];
    if(self) {

    }
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(checkNetworkStatus:) name:kReachabilityChangedNotification object:nil];

    internetReachable = [[Reachability reachabilityForInternetConnection] retain];
    [internetReachable startNotifier];

    hostReachable = [[Reachability reachabilityWithHostName:@"www.apple.com"] retain];
    [hostReachable startNotifier];

    return self;
}

- (void) checkNetworkStatus:(NSNotification *)notice {
    NetworkStatus internetStatus = [internetReachable currentReachabilityStatus];
    switch (internetStatus)

    {
        case NotReachable:
        {
            NSLog(@"The internet is down.");
            self.internetActive = NO;

            break;

        }
        case ReachableViaWiFi:
        {
            NSLog(@"The internet is working via WIFI.");
            self.internetActive = YES;

            break;

        }
        case ReachableViaWWAN:
        {
            NSLog(@"The internet is working via WWAN.");
            self.internetActive = YES;

            break;

        }
    }

    NetworkStatus hostStatus = [hostReachable currentReachabilityStatus];
    switch (hostStatus)

    {
        case NotReachable:
        {
            NSLog(@"A gateway to the host server is down.");
            self.hostActive = NO;

            break;

        }
        case ReachableViaWiFi:
        {
            NSLog(@"A gateway to the host server is working via WIFI.");
            self.hostActive = YES;

            break;

        }
        case ReachableViaWWAN:
        {
            NSLog(@"A gateway to the host server is working via WWAN.");
            self.hostActive = YES;

            break;

        }
    }

}

// If lower than SDK 5 : Otherwise, remove the observer as pleased.

- (void)dealloc {
    [[NSNotificationCenter defaultCenter] removeObserver:self];
    [super dealloc];
}

@end


4

Я витяг код і вклав в один єдиний метод, сподіваюся, що він допоможе іншим.

#import <SystemConfiguration/SystemConfiguration.h>

#import <netinet/in.h>
#import <netinet6/in6.h>

...

- (BOOL)isInternetReachable
{    
    struct sockaddr_in zeroAddress;
    bzero(&zeroAddress, sizeof(zeroAddress));
    zeroAddress.sin_len = sizeof(zeroAddress);
    zeroAddress.sin_family = AF_INET;

    SCNetworkReachabilityRef reachability = SCNetworkReachabilityCreateWithAddress(kCFAllocatorDefault, (const struct sockaddr*)&zeroAddress);
    SCNetworkReachabilityFlags flags;

    if(reachability == NULL)
        return false;

    if (!(SCNetworkReachabilityGetFlags(reachability, &flags)))
        return false;

    if ((flags & kSCNetworkReachabilityFlagsReachable) == 0)
        // if target host is not reachable
        return false;


    BOOL isReachable = false;


    if ((flags & kSCNetworkReachabilityFlagsConnectionRequired) == 0)
    {
        // if target host is reachable and no connection is required
        //  then we'll assume (for now) that your on Wi-Fi
        isReachable = true;
    }


    if ((((flags & kSCNetworkReachabilityFlagsConnectionOnDemand ) != 0) ||
         (flags & kSCNetworkReachabilityFlagsConnectionOnTraffic) != 0))
    {
        // ... and the connection is on-demand (or on-traffic) if the
        //     calling application is using the CFSocketStream or higher APIs

        if ((flags & kSCNetworkReachabilityFlagsInterventionRequired) == 0)
        {
            // ... and no [user] intervention is needed
            isReachable = true;
        }
    }

    if ((flags & kSCNetworkReachabilityFlagsIsWWAN) == kSCNetworkReachabilityFlagsIsWWAN)
    {
        // ... but WWAN connections are OK if the calling application
        //     is using the CFNetwork (CFSocketStream?) APIs.
        isReachable = true;
    }


    return isReachable;


}

[A] Звідки ви дістали цей код? Чи можете ви опублікувати посилання на джерело? [B] Дякую вам дуже за роботу з видобутку! Тільки те, що мені було потрібно. Я збирався сам виконати цю справу.
Василь Бурк

Отже, здається, що це єдина відповідь, яка не перевіряє жодного віддаленого сервера?
Джаспер

Для інших, які тестують це на MacOS, зауважте, що kSCNetworkReachabilityFlagsIsWWAN для OS_IPHONE не є macOS.
GeoffCoope

4

Я пишу версію скорохода загальноприйнятої відповіді тут , упаковує , якщо хто - то вважає , що Usefull, код написаний скор 2,

Ви можете завантажити потрібні файли з SampleCode

Додайте Reachability.hта Reachability.mподайте файл у свій проект,

Тепер потрібно створити Bridging-Header.hфайл, якщо для вашого проекту не існує жодного,

Всередині вашого Bridging-Header.hфайлу додайте цей рядок:

#import "Reachability.h"

Тепер для того, щоб перевірити наявність підключення до Інтернету

static func isInternetAvailable() -> Bool {
    let networkReachability : Reachability = Reachability.reachabilityForInternetConnection()
    let networkStatus : NetworkStatus = networkReachability.currentReachabilityStatus()

    if networkStatus == NotReachable {
        print("No Internet")
        return false
    } else {
        print("Internet Available")
        return true
    }

}

3

Я думаю, це може допомогти ..

[[AFNetworkReachabilityManager sharedManager] startMonitoring];

if([AFNetworkReachabilityManager sharedManager].isReachable)
{
    NSLog(@"Network reachable");
}
else
{   
   NSLog(@"Network not reachable");
}

Це не працює, якщо мережевий інтерфейс не змінено та втрачено підключення до Інтернету.
Пратік Сомая

2

Ви також можете спробувати цю, якщо ви вже налаштували AFNetworking у своєму проекті.

-(void)viewDidLoad{  // -- add connectivity notification --//
[[NSNotificationCenter defaultCenter ] addObserver:self selector:@selector(ReachabilityDidChangeNotification:) name:AFNetworkingReachabilityDidChangeNotification object:nil];}
-(void)ReachabilityDidChangeNotification:(NSNotification *)notify
{
// -- NSLog(@"Reachability changed: %@", AFStringFromNetworkReachabilityStatus(status));  -- //
NSDictionary *userInfo =[notif userInfo];
AFNetworkReachabilityStatus status= [[userInfo valueForKey:AFNetworkingReachabilityNotificationStatusItem] intValue];
switch (status)
{
    case AFNetworkReachabilityStatusReachableViaWWAN:
    case AFNetworkReachabilityStatusReachableViaWiFi:
        // -- Reachable -- //
// -- Do your stuff when internet connection is available -- //
        [self getLatestStuff];
        NSLog(@"Reachable");
        break;
    case AFNetworkReachabilityStatusNotReachable:
    default:
        // -- Not reachable -- //
        // -- Do your stuff for internet connection not available -- //
NSLog(@"Not Reachable");
        break;
}
}

1

Ось хороше рішення для перевірки підключення за допомогою Swift, без використання Reachability. Я знайшов це в цьому блозі .

Створіть у своєму проекті новий файл Swift Network.swift(наприклад). Вставте цей код у цей файл:

import Foundation

public class Network {

    class func isConnectedToNetwork()->Bool{

        var Status:Bool = false
        let url = NSURL(string: "http://google.com/")
        let request = NSMutableURLRequest(URL: url!)
        request.HTTPMethod = "HEAD"
        request.cachePolicy = NSURLRequestCachePolicy.ReloadIgnoringLocalAndRemoteCacheData
        request.timeoutInterval = 10.0

        var response: NSURLResponse?

        var data = NSURLConnection.sendSynchronousRequest(request, returningResponse: &response, error: nil) as NSData?

        if let httpResponse = response as? NSHTTPURLResponse {
            if httpResponse.statusCode == 200 {
                Status = true
            }
        }

        return Status
    }
}

Потім ви можете перевірити наявність підключення в будь-якому місці проекту, скориставшись:

if Network.isConnectedToNetwork() == true {
    println("Internet connection OK")
} else {
    println("Internet connection FAILED")
}

0

EDIT: Це не працюватиме для мережевих URL-адрес (див. Коментарі)

Станом на iOS 5 існує новий метод екземпляра NSURL:

- (BOOL)checkResourceIsReachableAndReturnError:(NSError **)error

Наведіть його на веб-сайт, який вам цікавий, або наведіть його на apple.com; Я думаю, що це новий однокличний дзвінок, щоб дізнатися, чи працює Інтернет на вашому пристрої.


Насправді в моїх власних тестах це завжди повертається помилково. Документи констатують, що це так для 4.x, але в 5+ він повинен працювати. YMMV
SG1

Схоже, це не працює в ситуації, яку ви шукаєте. Див stackoverflow.com/questions/9266154 / ...
Михей Hainline

7
Він використовується для URL-адрес файлів, а не Інтернет-URL-адрес.
Віктор Богдан

0

Я також не був задоволений доступними параметрами перевірки Інтернетом (Чому це не рідний API?!?!)

Моя власна проблема була зі 100% втратою пакетів - коли до маршрутизатора підключено пристрій, але маршрутизатор не підключений до Інтернету. Доступність та інші будуть зависати на віки. Я створив клас утиліти для одноразового вирішення, додавши тайм-аут для асинхронізації. Це добре працює в моєму додатку. Сподіваюся, це допомагає. Ось посилання на github:

https://github.com/fareast555/TFInternetChecker


0

Перевірка наявності підключення до Інтернету в (iOS) Xcode 8.2, Swift 3.0

Це простий метод перевірки доступності мережі. Мені вдалося перевести його на Swift 2.0 і ось остаточний код. Існуючий клас Apple Reachability та інші бібліотеки третіх сторін видалися занадто складними для перекладу на Swift.

Це працює як для підключення 3G, так і для Wi-Fi.

Не забудьте додати "SystemConfiguration.framework" до свого конструктора проекту.

//Create new swift class file Reachability in your project.

import SystemConfiguration

public class Reachability {
class func isConnectedToNetwork() -> Bool {
    var zeroAddress = sockaddr_in()
    zeroAddress.sin_len = UInt8(MemoryLayout.size(ofValue: zeroAddress))
    zeroAddress.sin_family = sa_family_t(AF_INET)
    let defaultRouteReachability = withUnsafePointer(to: &zeroAddress) {
        $0.withMemoryRebound(to: sockaddr.self, capacity: 1) {zeroSockAddress in
            SCNetworkReachabilityCreateWithAddress(nil, zeroSockAddress)
        }
    }
    var flags = SCNetworkReachabilityFlags()
    if !SCNetworkReachabilityGetFlags(defaultRouteReachability! , &flags) {
        return false
    }
    let isReachable = (flags.rawValue & UInt32(kSCNetworkFlagsReachable)) != 0
    let needsConnection = (flags.rawValue & UInt32(kSCNetworkFlagsConnectionRequired)) != 0
    return (isReachable && !needsConnection)
   }
}

// Check network connectivity from anywhere in project by using this code.

if Reachability.isConnectedToNetwork() == true {
     print("Internet connection OK")
} else {
 print("Internet connection FAILED")
}

З Xcode версії 7.2 (7C68) цей код має помилки компілятора
Даніель

@Daniel, дякую за вказівку на проблему, це через небезпечні вказівники, оскільки Swift 2 / Xcode оновлює там синтаксиси, ми повинні слідувати за ними. я оновив код для того ж. ty;)
ViJay Avhad

Якщо ви підключені до wi-fi, але не можете підключитися до Інтернету (відкрийте сторінку Google), він все одно повернеться справжнім для досяжності. Це неправильно.
Арлідо Юніор

0

Заміна Apple Reachability перезаписана у Swift із закриттями , натхненими tommillion: https://github.com/ashleymills/Reachability.swift

  1. Завантажте файл Reachability.swiftу свій проект. Крім того, використовуйте CocoaPods або Carthage - Дивіться розділ " Встановлення " в проекті README.

  2. Отримуйте сповіщення про підключення до мережі:

    //declare this property where it won't go out of scope relative to your listener
    let reachability = Reachability()!
    
    reachability.whenReachable = { reachability in
        if reachability.isReachableViaWiFi {
            print("Reachable via WiFi")
        } else {
            print("Reachable via Cellular")
        }
    }
    
    reachability.whenUnreachable = { _ in
        print("Not reachable")
    }
    
    do {
        try reachability.startNotifier()
    } catch {
        print("Unable to start notifier")
    }

    і для зупинки сповіщень

    reachability.stopNotifier()

0

Аламофір

Якщо ви вже використовуєте Alamofire для ВІДКРИТТЯ Api, ось що ви можете з цього принести користь.

Ви можете додати наступний клас у свою програму та зателефонувати, MNNetworkUtils.main.isConnected()щоб отримати булеве значення, підключено він чи ні.

#import Alamofire

class MNNetworkUtils {
  static let main = MNNetworkUtils()
  init() {
    manager = NetworkReachabilityManager(host: "google.com")
    listenForReachability()
  }

  private let manager: NetworkReachabilityManager?
  private var reachable: Bool = false
  private func listenForReachability() {
    self.manager?.listener = { [unowned self] status in
      switch status {
      case .notReachable:
        self.reachable = false
      case .reachable(_), .unknown:
        self.reachable = true
      }
    }
    self.manager?.startListening()
  }

  func isConnected() -> Bool {
    return reachable
  }
}

Це одиночний клас. Кожен раз, коли користувач підключає або відключає мережу, вона буде перекривати self.reachableвірно / помилково правильно, тому що ми починаємо слухати NetworkReachabilityManagerініціалізацію в одиночному режимі.

Для того, щоб відстежувати доступність, вам потрібно надати хоста, в даний час я використовую, google.comякщо потрібно, будь ласка, переходьте на будь-який інший хост або один із ваших.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.